1 /* 2 Copyright (c) 2012, 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.math.BigDecimal; 28 import java.math.BigInteger; 29 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 import com.mysql.clusterj.ClusterJDatastoreException; 37 import com.mysql.clusterj.ClusterJFatalInternalException; 38 import com.mysql.clusterj.ClusterJFatalUserException; 39 import com.mysql.clusterj.ClusterJUserException; 40 import com.mysql.clusterj.ColumnType; 41 42 import com.mysql.clusterj.core.store.Column; 43 import com.mysql.clusterj.core.store.Index; 44 import com.mysql.clusterj.core.store.Table; 45 46 import com.mysql.clusterj.core.util.I18NHelper; 47 import com.mysql.clusterj.core.util.Logger; 48 import com.mysql.clusterj.core.util.LoggerFactoryService; 49 50 import com.mysql.clusterj.tie.DbImpl.BufferManager; 51 52 import com.mysql.ndbjtie.ndbapi.NdbRecord; 53 import com.mysql.ndbjtie.ndbapi.NdbRecordConst; 54 import com.mysql.ndbjtie.ndbapi.NdbDictionary; 55 import com.mysql.ndbjtie.ndbapi.NdbDictionary.ColumnConst; 56 import com.mysql.ndbjtie.ndbapi.NdbDictionary.Dictionary; 57 import com.mysql.ndbjtie.ndbapi.NdbDictionary.IndexConst; 58 import com.mysql.ndbjtie.ndbapi.NdbDictionary.RecordSpecification; 59 import com.mysql.ndbjtie.ndbapi.NdbDictionary.RecordSpecificationArray; 60 import com.mysql.ndbjtie.ndbapi.NdbDictionary.TableConst; 61 62 /** 63 * Wrapper around an NdbRecord. Operations may use one or two instances. 64 * <ul><li>The table implementation can be used for create, read, update, or delete 65 * using an NdbRecord that defines every column in the table. 66 * </li><li>The index implementation for unique indexes can be used with a unique lookup operation. 67 * </li><li>The index implementation for ordered (non-unique) indexes can be used with an index scan operation. 68 * </li></ul> 69 * After construction, the instance is 70 * read-only and can be shared among all threads that use the same cluster connection; and the size of the 71 * buffer required for operations is available. 72 * Methods on the instance generally require a buffer to be passed, which is modified by the method. 73 * The NdbRecord instance is released when the cluster 74 * connection is closed or when schema change invalidates it. Column values can be set using a provided 75 * buffer and buffer manager. 76 */ 77 public class NdbRecordImpl { 78 79 /** My message translator */ 80 static final I18NHelper local = I18NHelper 81 .getInstance(NdbRecordImpl.class); 82 83 /** My logger */ 84 static final Logger logger = LoggerFactoryService.getFactory() 85 .getInstance(NdbRecordImpl.class); 86 87 /** The size of the NdbRecord struct */ 88 protected final static int SIZEOF_RECORD_SPECIFICATION = ClusterConnectionServiceImpl.SIZEOF_RECORD_SPECIFICATION; 89 90 /** The NdbRecord for this operation, created at construction */ 91 private NdbRecord ndbRecord = null; 92 93 /** The store columns for this operation */ 94 protected Column[] storeColumns = null; 95 96 /** The RecordSpecificationArray used to define the columns in the NdbRecord */ 97 private RecordSpecificationArray recordSpecificationArray; 98 99 /** The NdbTable */ 100 TableConst tableConst = null; 101 102 /** The NdbIndex, which will be null for complete-table instances */ 103 IndexConst indexConst = null; 104 105 /** The name of this NdbRecord; table name + index name */ 106 String name; 107 108 /** The size of the buffer for this NdbRecord, set during analyzeColumns */ 109 protected int bufferSize; 110 111 /** The maximum column id for this NdbRecord */ 112 protected int maximumColumnId; 113 114 /** The offsets into the buffer for each column */ 115 protected int[] offsets; 116 117 /** The lengths of the column data */ 118 protected int[] lengths; 119 120 /** Values for setting column mask and null bit mask: 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 */ 121 protected final static byte[] BIT_IN_BYTE_MASK = new byte[] {1, 2, 4, 8, 16, 32, 64, -128}; 122 123 /** Values for resetting column mask and null bit mask: 0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf, 0xbf, 0x7f */ 124 protected static final byte[] RESET_BIT_IN_BYTE_MASK = new byte[] {-2, -3, -5, -9, -17, -33, -65, 127}; 125 126 /** The null indicator for the field bit in the byte */ 127 protected int nullbitBitInByte[] = null; 128 129 /** The null indicator for the field byte offset*/ 130 protected int nullbitByteOffset[] = null; 131 132 /** The size of the null indicator byte array */ 133 protected int nullIndicatorSize; 134 135 /** The maximum length of any column in this operation */ 136 protected int maximumColumnLength; 137 138 /** The dictionary used to create (and release) the NdbRecord */ 139 private Dictionary ndbDictionary; 140 141 /** Number of columns for this NdbRecord */ 142 private int numberOfTableColumns; 143 144 /** ByteBuffer pool for new records, created during createNdbRecord once the buffer size is known */ 145 private FixedByteBufferPoolImpl bufferPool = null; 146 147 /** These fields are only used during construction of the RecordSpecificationArray */ 148 int offset = 0; 149 int nullablePosition = 0; 150 byte[] defaultValues; 151 152 private int[] recordSpecificationIndexes = null; 153 154 /** The autoincrement column or null if none */ 155 private Column autoIncrementColumn; 156 157 /** The function to handle setting autoincrement values */ 158 private AutoIncrementValueSetter autoIncrementValueSetter; 159 160 /** Constructor for table operations. 161 * 162 * @param storeTable the store table 163 * @param ndbDictionary the ndb dictionary 164 */ NdbRecordImpl(Table storeTable, Dictionary ndbDictionary)165 protected NdbRecordImpl(Table storeTable, Dictionary ndbDictionary) { 166 this.ndbDictionary = ndbDictionary; 167 this.tableConst = getNdbTable(storeTable.getName()); 168 this.name = storeTable.getName(); 169 this.numberOfTableColumns = tableConst.getNoOfColumns(); 170 this.recordSpecificationArray = RecordSpecificationArray.create(numberOfTableColumns); 171 recordSpecificationIndexes = new int[numberOfTableColumns]; 172 this.offsets = new int[numberOfTableColumns]; 173 this.lengths = new int[numberOfTableColumns]; 174 this.nullbitBitInByte = new int[numberOfTableColumns]; 175 this.nullbitByteOffset = new int[numberOfTableColumns]; 176 this.storeColumns = new Column[numberOfTableColumns]; 177 this.autoIncrementColumn = storeTable.getAutoIncrementColumn(); 178 if (this.autoIncrementColumn != null) { 179 chooseAutoIncrementValueSetter(); 180 } 181 this.ndbRecord = createNdbRecord(storeTable, ndbDictionary); 182 if (logger.isDetailEnabled()) logger.detail(storeTable.getName() + " " + dumpDefinition()); 183 initializeDefaultBuffer(); 184 } 185 186 /** Constructor for index operations. The NdbRecord has columns just for 187 * the columns in the index. 188 * 189 * @param storeIndex the store index 190 * @param storeTable the store table 191 * @param ndbDictionary the ndb dictionary 192 */ NdbRecordImpl(Index storeIndex, Table storeTable, Dictionary ndbDictionary)193 protected NdbRecordImpl(Index storeIndex, Table storeTable, Dictionary ndbDictionary) { 194 this.ndbDictionary = ndbDictionary; 195 this.tableConst = getNdbTable(storeTable.getName()); 196 this.indexConst = getNdbIndex(storeIndex.getInternalName(), tableConst.getName()); 197 this.name = storeTable.getName() + ":" + storeIndex.getInternalName(); 198 this.numberOfTableColumns = tableConst.getNoOfColumns(); 199 int numberOfIndexColumns = this.indexConst.getNoOfColumns(); 200 this.recordSpecificationArray = RecordSpecificationArray.create(numberOfIndexColumns); 201 recordSpecificationIndexes = new int[numberOfTableColumns]; 202 this.offsets = new int[numberOfTableColumns]; 203 this.lengths = new int[numberOfTableColumns]; 204 this.nullbitBitInByte = new int[numberOfTableColumns]; 205 this.nullbitByteOffset = new int[numberOfTableColumns]; 206 this.storeColumns = new Column[numberOfTableColumns]; 207 this.ndbRecord = createNdbRecord(storeIndex, storeTable, ndbDictionary); 208 if (logger.isDetailEnabled()) logger.detail(storeIndex.getInternalName() + " " + dumpDefinition()); 209 initializeDefaultBuffer(); 210 } 211 212 /** Initialize the byte buffer containing default values for all columns. 213 * Non-null columns are initialized to zero. Nullable columns are initialized to null. 214 * When a new byte buffer is required, the byte buffer is used for initialization. 215 */ initializeDefaultBuffer()216 private void initializeDefaultBuffer() { 217 // create the default value for the buffer: null values or zeros for all columns 218 defaultValues = new byte[bufferSize]; 219 ByteBuffer zeros = bufferPool.borrowBuffer(); 220 zeros.order(ByteOrder.nativeOrder()); 221 // just to be sure, initialize with zeros 222 zeros.put(defaultValues); 223 // not all columns are set at this point, so only check for those that are set 224 for (Column storeColumn: storeColumns) { 225 // nullable columns get the null bit set 226 if (storeColumn != null && storeColumn.getNullable()) { 227 setNull(zeros, storeColumn); 228 } 229 } 230 zeros.position(0); 231 zeros.limit(bufferSize); 232 zeros.get(defaultValues); 233 bufferPool.returnBuffer(zeros); 234 // default values is now immutable and can be used thread-safe 235 } 236 237 /** Initialize a new direct buffer with default values for all columns. 238 * The buffer returned is positioned at 0 with the limit set to the buffer size. 239 * @return a new byte buffer for use with this NdbRecord. 240 */ newBuffer()241 protected ByteBuffer newBuffer() { 242 ByteBuffer result = bufferPool.borrowBuffer(); 243 initializeBuffer(result); 244 return result; 245 } 246 247 /** Return the buffer to the buffer pool */ returnBuffer(ByteBuffer buffer)248 protected void returnBuffer(ByteBuffer buffer) { 249 bufferPool.returnBuffer(buffer); 250 } 251 252 /** Check the NdbRecord buffer guard */ checkGuard(ByteBuffer buffer, String where)253 protected void checkGuard(ByteBuffer buffer, String where) { 254 bufferPool.checkGuard(buffer, where); 255 } 256 257 /** Initialize an already-allocated buffer with default values for all columns. 258 * 259 * @param buffer 260 */ initializeBuffer(ByteBuffer buffer)261 protected void initializeBuffer(ByteBuffer buffer) { 262 buffer.order(ByteOrder.nativeOrder()); 263 buffer.clear(); 264 buffer.put(defaultValues); 265 buffer.clear(); 266 } 267 setNull(ByteBuffer buffer, Column storeColumn)268 public int setNull(ByteBuffer buffer, Column storeColumn) { 269 int columnId = storeColumn.getColumnId(); 270 if (!storeColumn.getNullable()) { 271 return columnId; 272 } 273 int index = nullbitByteOffset[columnId]; 274 byte mask = BIT_IN_BYTE_MASK[nullbitBitInByte[columnId]]; 275 byte nullbyte = buffer.get(index); 276 buffer.put(index, (byte)(nullbyte|mask)); 277 return columnId; 278 } 279 resetNull(ByteBuffer buffer, Column storeColumn)280 public int resetNull(ByteBuffer buffer, Column storeColumn) { 281 int columnId = storeColumn.getColumnId(); 282 if (!storeColumn.getNullable()) { 283 return columnId; 284 } 285 int index = nullbitByteOffset[columnId]; 286 byte mask = RESET_BIT_IN_BYTE_MASK[nullbitBitInByte[columnId]]; 287 byte nullbyte = buffer.get(index); 288 buffer.put(index, (byte)(nullbyte & mask)); 289 return columnId; 290 } 291 setBigInteger(ByteBuffer buffer, Column storeColumn, BigInteger value)292 public int setBigInteger(ByteBuffer buffer, Column storeColumn, BigInteger value) { 293 resetNull(buffer, storeColumn); 294 int columnId = storeColumn.getColumnId(); 295 int newPosition = offsets[columnId]; 296 buffer.position(newPosition); 297 Utility.convertValue(buffer, storeColumn, value); 298 buffer.limit(bufferSize); 299 buffer.position(0); 300 return columnId; 301 } 302 setByte(ByteBuffer buffer, Column storeColumn, byte value)303 public int setByte(ByteBuffer buffer, Column storeColumn, byte value) { 304 resetNull(buffer, storeColumn); 305 int columnId = storeColumn.getColumnId(); 306 if (storeColumn.getType() == ColumnType.Bit) { 307 // the byte is stored as a BIT array of four bytes 308 buffer.putInt(offsets[columnId], value & 0xff); 309 } else { 310 buffer.put(offsets[columnId], value); 311 } 312 buffer.limit(bufferSize); 313 buffer.position(0); 314 return columnId; 315 } 316 setBytes(ByteBuffer buffer, Column storeColumn, byte[] value)317 public int setBytes(ByteBuffer buffer, Column storeColumn, byte[] value) { 318 resetNull(buffer, storeColumn); 319 int columnId = storeColumn.getColumnId(); 320 int offset = offsets[columnId]; 321 int length = storeColumn.getLength(); 322 int prefixLength = storeColumn.getPrefixLength(); 323 if (length < value.length) { 324 throw new ClusterJUserException(local.message("ERR_Data_Too_Long", 325 storeColumn.getName(), length, value.length)); 326 } 327 buffer.limit(offset + prefixLength + length); 328 buffer.position(offset); 329 Utility.convertValue(buffer, storeColumn, value); 330 buffer.limit(bufferSize); 331 buffer.position(0); 332 return columnId; 333 } 334 setDecimal(ByteBuffer buffer, Column storeColumn, BigDecimal value)335 public int setDecimal(ByteBuffer buffer, Column storeColumn, BigDecimal value) { 336 resetNull(buffer, storeColumn); 337 int columnId = storeColumn.getColumnId(); 338 int newPosition = offsets[columnId]; 339 buffer.position(newPosition); 340 Utility.convertValue(buffer, storeColumn, value); 341 return columnId; 342 } 343 setDouble(ByteBuffer buffer, Column storeColumn, double value)344 public int setDouble(ByteBuffer buffer, Column storeColumn, double value) { 345 resetNull(buffer, storeColumn); 346 int columnId = storeColumn.getColumnId(); 347 buffer.putDouble(offsets[columnId], value); 348 return columnId; 349 } 350 setFloat(ByteBuffer buffer, Column storeColumn, float value)351 public int setFloat(ByteBuffer buffer, Column storeColumn, float value) { 352 resetNull(buffer, storeColumn); 353 int columnId = storeColumn.getColumnId(); 354 buffer.putFloat(offsets[columnId], value); 355 return columnId; 356 } 357 setInt(ByteBuffer buffer, Column storeColumn, int value)358 public int setInt(ByteBuffer buffer, Column storeColumn, int value) { 359 resetNull(buffer, storeColumn); 360 int columnId = storeColumn.getColumnId(); 361 int storageValue = Utility.convertIntValueForStorage(storeColumn, value); 362 buffer.putInt(offsets[columnId], storageValue); 363 return columnId; 364 } 365 setLong(ByteBuffer buffer, Column storeColumn, long value)366 public int setLong(ByteBuffer buffer, Column storeColumn, long value) { 367 resetNull(buffer, storeColumn); 368 int columnId = storeColumn.getColumnId(); 369 long storeValue = Utility.convertLongValueForStorage(storeColumn, value); 370 buffer.putLong(offsets[columnId], storeValue); 371 return columnId; 372 } 373 setShort(ByteBuffer buffer, Column storeColumn, short value)374 public int setShort(ByteBuffer buffer, Column storeColumn, short value) { 375 resetNull(buffer, storeColumn); 376 int columnId = storeColumn.getColumnId(); 377 if (storeColumn.getLength() == 4) { 378 // the short is stored as a BIT array of four bytes 379 buffer.putInt(offsets[columnId], value & 0xffff); 380 } else { 381 buffer.putShort(offsets[columnId], (short)value); 382 } 383 return columnId; 384 } 385 setString(ByteBuffer buffer, BufferManager bufferManager, Column storeColumn, String value)386 public int setString(ByteBuffer buffer, BufferManager bufferManager, Column storeColumn, String value) { 387 resetNull(buffer, storeColumn); 388 int columnId = storeColumn.getColumnId(); 389 int offset = offsets[columnId]; 390 int prefixLength = storeColumn.getPrefixLength(); 391 int length = storeColumn.getLength() + prefixLength; 392 buffer.limit(offset + length); 393 buffer.position(offset); 394 // TODO provide the buffer to Utility.encode to avoid copying 395 // for now, use the encode method to encode the value then copy it 396 ByteBuffer converted = Utility.encode(value, storeColumn, bufferManager); 397 if (length < converted.remaining()) { 398 throw new ClusterJUserException(local.message("ERR_Data_Too_Long", 399 storeColumn.getName(), length - prefixLength, converted.remaining() - prefixLength)); 400 } 401 buffer.put(converted); 402 buffer.limit(bufferSize); 403 buffer.position(0); 404 return columnId; 405 } 406 getBoolean(ByteBuffer buffer, int columnId)407 public boolean getBoolean(ByteBuffer buffer, int columnId) { 408 int value = buffer.getInt(offsets[columnId]); 409 return Utility.getBoolean(storeColumns[columnId], value); 410 } 411 getByte(ByteBuffer buffer, int columnId)412 public byte getByte(ByteBuffer buffer, int columnId) { 413 Column storeColumn = storeColumns[columnId]; 414 if (storeColumn.getType() == ColumnType.Bit) { 415 // the byte was stored in a BIT column as four bytes 416 return (byte)(buffer.getInt(offsets[columnId])); 417 } else { 418 // the byte was stored as a byte 419 return buffer.get(offsets[columnId]); 420 } 421 } 422 getBytes(ByteBuffer byteBuffer, int columnId)423 public byte[] getBytes(ByteBuffer byteBuffer, int columnId) { 424 return getBytes(byteBuffer, storeColumns[columnId]); 425 } 426 getBytes(ByteBuffer byteBuffer, Column storeColumn)427 public byte[] getBytes(ByteBuffer byteBuffer, Column storeColumn) { 428 int columnId = storeColumn.getColumnId(); 429 if (isNull(byteBuffer, columnId)) { 430 return null; 431 } 432 int prefixLength = storeColumn.getPrefixLength(); 433 int actualLength = lengths[columnId]; 434 int offset = offsets[columnId]; 435 switch (prefixLength) { 436 case 0: 437 break; 438 case 1: 439 actualLength = (byteBuffer.get(offset) + 256) % 256; 440 offset += 1; 441 break; 442 case 2: 443 actualLength = (byteBuffer.get(offset) + 256) % 256; 444 int length2 = (byteBuffer.get(offset + 1) + 256) % 256; 445 actualLength += 256 * length2; 446 offset += 2; 447 break; 448 default: 449 throw new ClusterJFatalInternalException( 450 local.message("ERR_Invalid_Prefix_Length", prefixLength)); 451 } 452 byteBuffer.position(offset); 453 byte[] result = new byte[actualLength]; 454 byteBuffer.get(result); 455 byteBuffer.limit(bufferSize); 456 byteBuffer.position(0); 457 return result; 458 } 459 getDouble(ByteBuffer buffer, int columnId)460 public double getDouble(ByteBuffer buffer, int columnId) { 461 double result = buffer.getDouble(offsets[columnId]); 462 return result; 463 } 464 getFloat(ByteBuffer buffer, int columnId)465 public float getFloat(ByteBuffer buffer, int columnId) { 466 float result = buffer.getFloat(offsets[columnId]); 467 return result; 468 } 469 getInt(ByteBuffer buffer, int columnId)470 public int getInt(ByteBuffer buffer, int columnId) { 471 int value = buffer.getInt(offsets[columnId]); 472 return Utility.getInt(storeColumns[columnId], value); 473 } 474 getLong(ByteBuffer buffer, int columnId)475 public long getLong(ByteBuffer buffer, int columnId) { 476 long value = buffer.getLong(offsets[columnId]); 477 return Utility.getLong(storeColumns[columnId], value); 478 } 479 getShort(ByteBuffer buffer, int columnId)480 public short getShort(ByteBuffer buffer, int columnId) { 481 Column storeColumn = storeColumns[columnId]; 482 if (storeColumn.getLength() == 4) { 483 // the short was stored in a BIT column as four bytes 484 return (short)buffer.getInt(offsets[columnId]); 485 } else { 486 // the short was stored as a short 487 return buffer.getShort(offsets[columnId]); 488 } 489 } 490 getString(ByteBuffer byteBuffer, int columnId, BufferManager bufferManager)491 public String getString(ByteBuffer byteBuffer, int columnId, BufferManager bufferManager) { 492 if (isNull(byteBuffer, columnId)) { 493 return null; 494 } 495 Column storeColumn = storeColumns[columnId]; 496 int prefixLength = storeColumn.getPrefixLength(); 497 int actualLength; 498 int offset = offsets[columnId]; 499 byteBuffer.limit(byteBuffer.capacity()); 500 switch (prefixLength) { 501 case 0: 502 actualLength = lengths[columnId]; 503 break; 504 case 1: 505 actualLength = (byteBuffer.get(offset) + 256) % 256; 506 offset += 1; 507 break; 508 case 2: 509 actualLength = (byteBuffer.get(offset) + 256) % 256; 510 int length2 = (byteBuffer.get(offset + 1) + 256) % 256; 511 actualLength += 256 * length2; 512 offset += 2; 513 break; 514 default: 515 throw new ClusterJFatalInternalException( 516 local.message("ERR_Invalid_Prefix_Length", prefixLength)); 517 } 518 byteBuffer.position(offset); 519 byteBuffer.limit(offset + actualLength); 520 521 String result = Utility.decode(byteBuffer, storeColumn.getCharsetNumber(), bufferManager); 522 if(prefixLength == 0) { 523 result = result.trim(); 524 } 525 byteBuffer.limit(bufferSize); 526 byteBuffer.position(0); 527 return result; 528 } 529 getBigInteger(ByteBuffer byteBuffer, int columnId)530 public BigInteger getBigInteger(ByteBuffer byteBuffer, int columnId) { 531 Column storeColumn = storeColumns[columnId]; 532 int index = storeColumn.getColumnId(); 533 int offset = offsets[index]; 534 int precision = storeColumn.getPrecision(); 535 int scale = storeColumn.getScale(); 536 int length = Utility.getDecimalColumnSpace(precision, scale); 537 byteBuffer.position(offset); 538 BigInteger result = Utility.getBigInteger(byteBuffer, length, precision, scale); 539 byteBuffer.limit(bufferSize); 540 byteBuffer.position(0); 541 return result; 542 } 543 getBigInteger(ByteBuffer byteBuffer, Column storeColumn)544 public BigInteger getBigInteger(ByteBuffer byteBuffer, Column storeColumn) { 545 int index = storeColumn.getColumnId(); 546 int offset = offsets[index]; 547 int precision = storeColumn.getPrecision(); 548 int scale = storeColumn.getScale(); 549 int length = Utility.getDecimalColumnSpace(precision, scale); 550 byteBuffer.position(offset); 551 BigInteger result = Utility.getBigInteger(byteBuffer, length, precision, scale); 552 byteBuffer.limit(bufferSize); 553 byteBuffer.position(0); 554 return result; 555 } 556 getDecimal(ByteBuffer byteBuffer, int columnId)557 public BigDecimal getDecimal(ByteBuffer byteBuffer, int columnId) { 558 Column storeColumn = storeColumns[columnId]; 559 int index = storeColumn.getColumnId(); 560 int offset = offsets[index]; 561 int precision = storeColumn.getPrecision(); 562 int scale = storeColumn.getScale(); 563 int length = Utility.getDecimalColumnSpace(precision, scale); 564 byteBuffer.position(offset); 565 BigDecimal result = Utility.getDecimal(byteBuffer, length, precision, scale); 566 byteBuffer.limit(bufferSize); 567 byteBuffer.position(0); 568 return result; 569 } 570 getDecimal(ByteBuffer byteBuffer, Column storeColumn)571 public BigDecimal getDecimal(ByteBuffer byteBuffer, Column storeColumn) { 572 int index = storeColumn.getColumnId(); 573 int offset = offsets[index]; 574 int precision = storeColumn.getPrecision(); 575 int scale = storeColumn.getScale(); 576 int length = Utility.getDecimalColumnSpace(precision, scale); 577 byteBuffer.position(offset); 578 BigDecimal result = Utility.getDecimal(byteBuffer, length, precision, scale); 579 byteBuffer.limit(bufferSize); 580 byteBuffer.position(0); 581 return result; 582 } 583 getObjectBoolean(ByteBuffer byteBuffer, int columnId)584 public Boolean getObjectBoolean(ByteBuffer byteBuffer, int columnId) { 585 if (isNull(byteBuffer, columnId)) { 586 return null; 587 } 588 return Boolean.valueOf(getBoolean(byteBuffer, columnId)); 589 } 590 getObjectBoolean(ByteBuffer byteBuffer, Column storeColumn)591 public Boolean getObjectBoolean(ByteBuffer byteBuffer, Column storeColumn) { 592 return getObjectBoolean(byteBuffer, storeColumn.getColumnId()); 593 } 594 getObjectByte(ByteBuffer byteBuffer, int columnId)595 public Byte getObjectByte(ByteBuffer byteBuffer, int columnId) { 596 if (isNull(byteBuffer, columnId)) { 597 return null; 598 } 599 return getByte(byteBuffer, columnId); 600 } 601 getObjectByte(ByteBuffer byteBuffer, Column storeColumn)602 public Byte getObjectByte(ByteBuffer byteBuffer, Column storeColumn) { 603 return getObjectByte(byteBuffer, storeColumn.getColumnId()); 604 } 605 getObjectFloat(ByteBuffer byteBuffer, int columnId)606 public Float getObjectFloat(ByteBuffer byteBuffer, int columnId) { 607 if (isNull(byteBuffer, columnId)) { 608 return null; 609 } 610 return getFloat(byteBuffer, columnId); 611 } 612 getObjectFloat(ByteBuffer byteBuffer, Column storeColumn)613 public Float getObjectFloat(ByteBuffer byteBuffer, Column storeColumn) { 614 return getObjectFloat(byteBuffer, storeColumn.getColumnId()); 615 } 616 getObjectDouble(ByteBuffer byteBuffer, int columnId)617 public Double getObjectDouble(ByteBuffer byteBuffer, int columnId) { 618 if (isNull(byteBuffer, columnId)) { 619 return null; 620 } 621 return getDouble(byteBuffer, columnId); 622 } 623 getObjectDouble(ByteBuffer byteBuffer, Column storeColumn)624 public Double getObjectDouble(ByteBuffer byteBuffer, Column storeColumn) { 625 return getObjectDouble(byteBuffer, storeColumn.getColumnId()); 626 } 627 getObjectInteger(ByteBuffer byteBuffer, int columnId)628 public Integer getObjectInteger(ByteBuffer byteBuffer, int columnId) { 629 if (isNull(byteBuffer, columnId)) { 630 return null; 631 } 632 return getInt(byteBuffer, columnId); 633 } 634 getObjectInteger(ByteBuffer byteBuffer, Column storeColumn)635 public Integer getObjectInteger(ByteBuffer byteBuffer, Column storeColumn) { 636 return getObjectInteger(byteBuffer, storeColumn.getColumnId()); 637 } 638 getObjectLong(ByteBuffer byteBuffer, int columnId)639 public Long getObjectLong(ByteBuffer byteBuffer, int columnId) { 640 if (isNull(byteBuffer, columnId)) { 641 return null; 642 } 643 return getLong(byteBuffer, columnId); 644 } 645 getObjectLong(ByteBuffer byteBuffer, Column storeColumn)646 public Long getObjectLong(ByteBuffer byteBuffer, Column storeColumn) { 647 return getObjectLong(byteBuffer, storeColumn.getColumnId()); 648 } 649 getObjectShort(ByteBuffer byteBuffer, int columnId)650 public Short getObjectShort(ByteBuffer byteBuffer, int columnId) { 651 if (isNull(byteBuffer, columnId)) { 652 return null; 653 } 654 return getShort(byteBuffer, columnId); 655 } 656 getObjectShort(ByteBuffer byteBuffer, Column storeColumn)657 public Short getObjectShort(ByteBuffer byteBuffer, Column storeColumn) { 658 return getObjectShort(byteBuffer, storeColumn.getColumnId()); 659 } 660 isNull(ByteBuffer buffer, int columnId)661 public boolean isNull(ByteBuffer buffer, int columnId) { 662 if (!storeColumns[columnId].getNullable()) { 663 return false; 664 } 665 byte nullbyte = buffer.get(nullbitByteOffset[columnId]); 666 boolean result = isSet(nullbyte, nullbitBitInByte[columnId]); 667 return result; 668 } 669 isPresent(byte[] mask, int columnId)670 public boolean isPresent(byte[] mask, int columnId) { 671 byte present = mask[columnId/8]; 672 return isSet(present, columnId % 8); 673 } 674 markPresent(byte[] mask, int columnId)675 public void markPresent(byte[] mask, int columnId) { 676 int offset = columnId/8; 677 int bitMask = BIT_IN_BYTE_MASK[columnId % 8]; 678 mask[offset] |= (byte)bitMask; 679 } 680 isSet(byte test, int bitInByte)681 protected boolean isSet(byte test, int bitInByte) { 682 int mask = BIT_IN_BYTE_MASK[bitInByte]; 683 boolean result = (test & mask) != 0; 684 return result; 685 } 686 handleError(Object object, Dictionary ndbDictionary)687 protected static void handleError(Object object, Dictionary ndbDictionary) { 688 if (object != null) { 689 return; 690 } else { 691 Utility.throwError(null, ndbDictionary.getNdbError()); 692 } 693 } 694 createNdbRecord(Index storeIndex, Table storeTable, Dictionary ndbDictionary)695 protected NdbRecord createNdbRecord(Index storeIndex, Table storeTable, Dictionary ndbDictionary) { 696 String[] columnNames = storeIndex.getColumnNames(); 697 // analyze columns; sort into alignment buckets, allocate space in the buffer 698 // and build the record specification array 699 analyzeColumns(storeTable, columnNames); 700 // create the NdbRecord 701 NdbRecord result = ndbDictionary.createRecord(indexConst, tableConst, recordSpecificationArray, 702 columnNames.length, SIZEOF_RECORD_SPECIFICATION, 0); 703 int rowLength = NdbDictionary.getRecordRowLength(result); 704 // create the buffer pool now that the size of the record is known 705 if (this.bufferSize < rowLength) { 706 logger.warn("NdbRecordImpl.createNdbRecord rowLength for " + this.name + " is " + rowLength + 707 " but we only allocate length of " + this.bufferSize); 708 this.bufferSize = rowLength; 709 } 710 bufferPool = new FixedByteBufferPoolImpl(this.bufferSize, this.name); 711 // delete the RecordSpecificationArray since it is no longer needed 712 RecordSpecificationArray.delete(recordSpecificationArray); 713 handleError(result, ndbDictionary); 714 return result; 715 } 716 createNdbRecord(Table storeTable, Dictionary ndbDictionary)717 protected NdbRecord createNdbRecord(Table storeTable, Dictionary ndbDictionary) { 718 String[] columnNames = storeTable.getColumnNames(); 719 // analyze columns; sort into alignment buckets, allocate space in the buffer, 720 // and build the record specification array 721 analyzeColumns(storeTable, columnNames); 722 // create the NdbRecord 723 NdbRecord result = ndbDictionary.createRecord(tableConst, recordSpecificationArray, 724 columnNames.length, SIZEOF_RECORD_SPECIFICATION, 0); 725 int rowLength = NdbDictionary.getRecordRowLength(result); 726 // create the buffer pool now that the size of the record is known 727 if (this.bufferSize < rowLength) { 728 logger.warn("NdbRecordImpl.createNdbRecord rowLength for " + this.name + " is " + rowLength + 729 " but we only allocate length of " + this.bufferSize); 730 this.bufferSize = rowLength; 731 } 732 bufferPool = new FixedByteBufferPoolImpl(this.bufferSize, this.name); 733 // delete the RecordSpecificationArray since it is no longer needed 734 RecordSpecificationArray.delete(recordSpecificationArray); 735 handleError(result, ndbDictionary); 736 return result; 737 } 738 analyzeColumns(Table storeTable, String[] columnNames)739 private void analyzeColumns(Table storeTable, String[] columnNames) { 740 List<Column> align8 = new ArrayList<Column>(); 741 List<Column> align4 = new ArrayList<Column>(); 742 List<Column> align2 = new ArrayList<Column>(); 743 List<Column> align1 = new ArrayList<Column>(); 744 List<Column> nullables = new ArrayList<Column>(); 745 int i = 0; 746 for (String columnName: columnNames) { 747 Column storeColumn = storeTable.getColumn(columnName); 748 int columnId = storeColumn.getColumnId(); 749 recordSpecificationIndexes[columnId] = i; 750 if (logger.isDetailEnabled()) logger.detail("storeColumn: " + storeColumn.getName() + " id: " + storeColumn.getColumnId() + " index: " + i); 751 lengths[i] = storeColumn.getLength(); 752 storeColumns[i++] = storeColumn; 753 // for each column, put into alignment bucket 754 switch (storeColumn.getType()) { 755 case Bigint: 756 case Bigunsigned: 757 case Bit: 758 case Blob: 759 case Date: 760 case Datetime: 761 case Datetime2: 762 case Double: 763 case Text: 764 case Time: 765 case Time2: 766 case Timestamp: 767 case Timestamp2: 768 align8.add(storeColumn); 769 break; 770 case Binary: 771 case Char: 772 case Decimal: 773 case Decimalunsigned: 774 case Longvarbinary: 775 case Longvarchar: 776 case Olddecimal: 777 case Olddecimalunsigned: 778 case Tinyint: 779 case Tinyunsigned: 780 case Varbinary: 781 case Varchar: 782 align1.add(storeColumn); 783 break; 784 case Float: 785 case Int: 786 case Mediumint: 787 case Mediumunsigned: 788 case Unsigned: 789 align4.add(storeColumn); 790 break; 791 case Smallint: 792 case Smallunsigned: 793 case Year: 794 align2.add(storeColumn); 795 break; 796 case Undefined: 797 throw new ClusterJFatalUserException(local.message("ERR_Unknown_Column_Type", 798 storeTable.getName(), columnName, storeColumn.getType())); 799 default: 800 throw new ClusterJFatalInternalException(local.message("ERR_Unknown_Column_Type", 801 storeTable.getName(), columnName, storeColumn.getType())); 802 } 803 if (storeColumn.getNullable()) { 804 nullables.add(storeColumn); 805 } 806 } 807 // for each column, allocate space in the buffer, starting with align8 and ending with align1 808 // null indicators are allocated first, with one bit per nullable column 809 // nullable columns take one bit each 810 offset = nullables.size() + 7 / 8; 811 // align the first column following the nullable column indicators to 8 812 offset = (7 + offset) / 8 * 8; 813 nullIndicatorSize = offset; 814 for (Column storeColumn: align8) { 815 analyzeColumn(8, storeColumn); 816 } 817 for (Column storeColumn: align4) { 818 analyzeColumn(4, storeColumn); 819 } 820 for (Column storeColumn: align2) { 821 analyzeColumn(2, storeColumn); 822 } 823 for (Column storeColumn: align1) { 824 analyzeColumn(1, storeColumn); 825 } 826 bufferSize = offset; 827 828 if (logger.isDebugEnabled()) logger.debug(dumpDefinition()); 829 } 830 831 /** Create a record specification for a column. Keep track of the offset into the buffer 832 * and the null indicator position for each column. 833 * 834 * @param alignment the alignment for this column in the buffer 835 * @param storeColumn the column 836 */ analyzeColumn(int alignment, Column storeColumn)837 private void analyzeColumn(int alignment, Column storeColumn) { 838 int columnId = storeColumn.getColumnId(); 839 int recordSpecificationIndex = recordSpecificationIndexes[columnId]; 840 RecordSpecification recordSpecification = recordSpecificationArray.at(recordSpecificationIndex); 841 ColumnConst columnConst = tableConst.getColumn(columnId); 842 recordSpecification.column(columnConst); 843 recordSpecification.offset(offset); 844 offsets[columnId] = offset; 845 int columnSpace = storeColumn.getColumnSpace(); 846 offset += ((columnSpace==0)?alignment:columnSpace); 847 if (storeColumn.getNullable()) { 848 int nullbitByteOffsetValue = nullablePosition/8; 849 int nullbitBitInByteValue = nullablePosition - nullablePosition / 8 * 8; 850 nullbitBitInByte[columnId] = nullbitBitInByteValue; 851 nullbitByteOffset[columnId] = nullbitByteOffsetValue; 852 recordSpecification.nullbit_byte_offset(nullbitByteOffsetValue); 853 recordSpecification.nullbit_bit_in_byte(nullbitBitInByteValue); 854 ++nullablePosition; 855 } else { 856 recordSpecification.nullbit_byte_offset(0); 857 recordSpecification.nullbit_bit_in_byte(0); 858 } 859 } 860 dumpDefinition()861 private String dumpDefinition() { 862 StringBuilder builder = new StringBuilder(tableConst.getName()); 863 builder.append(" numberOfColumns: "); 864 builder.append(numberOfTableColumns); 865 builder.append('\n'); 866 for (int columnId = 0; columnId < numberOfTableColumns; ++columnId) { 867 Column storeColumn = storeColumns[columnId]; 868 if (storeColumn != null) { 869 builder.append(" column: "); 870 builder.append(storeColumn.getName()); 871 builder.append(" offset: "); 872 builder.append(offsets[columnId]); 873 builder.append(" length: "); 874 builder.append(lengths[columnId]); 875 builder.append(" nullbitBitInByte: "); 876 builder.append(nullbitBitInByte[columnId]); 877 builder.append(" nullbitByteOffset: "); 878 builder.append(nullbitByteOffset[columnId]); 879 builder.append('\n'); 880 } 881 } 882 return builder.toString(); 883 } 884 dumpValues(ByteBuffer data, byte[] mask)885 public String dumpValues(ByteBuffer data, byte[] mask) { 886 StringBuilder builder = new StringBuilder(tableConst.getName()); 887 builder.append(" numberOfColumns: "); 888 builder.append(numberOfTableColumns); 889 builder.append('\n'); 890 for (int columnId = 0; columnId < numberOfTableColumns; ++columnId) { 891 Column storeColumn = storeColumns[columnId]; 892 if (storeColumn != null) { 893 builder.append(" column: "); 894 builder.append(storeColumn.getName()); 895 builder.append(" offset: "); 896 builder.append(offsets[columnId]); 897 builder.append(" length: "); 898 builder.append(lengths[columnId]); 899 builder.append(" nullbitBitInByte: "); 900 int nullBitInByte = nullbitBitInByte[columnId]; 901 builder.append(nullBitInByte); 902 builder.append(" nullbitByteOffset: "); 903 int nullByteOffset = nullbitByteOffset[columnId]; 904 builder.append(nullByteOffset); 905 builder.append(" data: "); 906 int size = storeColumn.getColumnSpace() != 0 ? storeColumn.getColumnSpace():storeColumn.getSize(); 907 int offset = offsets[columnId]; 908 data.limit(bufferSize); 909 data.position(0); 910 for (int index = offset; index < offset + size; ++index) { 911 builder.append(String.format("%2x ", data.get(index))); 912 } 913 builder.append(" null: "); 914 builder.append(isNull(data, columnId)); 915 if (mask != null) { 916 builder.append(" present: "); 917 builder.append(isPresent(mask, columnId)); 918 } 919 builder.append('\n'); 920 } 921 } 922 data.position(0); 923 return builder.toString(); 924 } 925 getNdbTable(String tableName)926 TableConst getNdbTable(String tableName) { 927 TableConst ndbTable = ndbDictionary.getTable(tableName); 928 if (ndbTable == null) { 929 // try the lower case table name 930 ndbTable = ndbDictionary.getTable(tableName.toLowerCase()); 931 } 932 if (ndbTable == null) { 933 Utility.throwError(ndbTable, ndbDictionary.getNdbError(), tableName); 934 } 935 return ndbTable; 936 } 937 getNdbTable()938 TableConst getNdbTable() { 939 return tableConst; 940 } 941 getNdbIndex(String indexName, String tableName)942 IndexConst getNdbIndex(String indexName, String tableName) { 943 IndexConst ndbIndex = ndbDictionary.getIndex(indexName, tableName); 944 if (ndbIndex == null) { 945 Utility.throwError(ndbIndex, ndbDictionary.getNdbError(), tableName+ "+" + indexName); 946 } 947 return ndbIndex; 948 } 949 getNdbIndex()950 IndexConst getNdbIndex() { 951 return indexConst; 952 } 953 getBufferSize()954 public int getBufferSize() { 955 return bufferSize; 956 } 957 getNdbRecord()958 public NdbRecordConst getNdbRecord() { 959 return ndbRecord; 960 } 961 getNumberOfColumns()962 public int getNumberOfColumns() { 963 return numberOfTableColumns; 964 } 965 releaseNdbRecord()966 protected void releaseNdbRecord() { 967 if (ndbRecord != null) { 968 if (logger.isDebugEnabled())logger.debug("Releasing NdbRecord for " + tableConst.getName()); 969 ndbDictionary.releaseRecord(ndbRecord); 970 ndbRecord = null; 971 // release the buffer pool; pooled byte buffers will be garbage collected 972 this.bufferPool = null; 973 } 974 } 975 getNullIndicatorSize()976 public int getNullIndicatorSize() { 977 return nullIndicatorSize; 978 } 979 isLob(int columnId)980 public boolean isLob(int columnId) { 981 return storeColumns[columnId].isLob(); 982 } 983 setAutoIncrementValue(ByteBuffer valueBuffer, long value)984 public void setAutoIncrementValue(ByteBuffer valueBuffer, long value) { 985 autoIncrementValueSetter.set(valueBuffer, value); 986 } 987 988 /** Choose the appropriate autoincrement value setter based on the column type. 989 * This is done once during construction when the autoincrement column is known. 990 */ chooseAutoIncrementValueSetter()991 private void chooseAutoIncrementValueSetter() { 992 switch (autoIncrementColumn.getType()) { 993 case Int: 994 case Unsigned: 995 logger.debug("chooseAutoIncrementValueSetter autoIncrementValueSetterInt."); 996 autoIncrementValueSetter = autoIncrementValueSetterInt; 997 break; 998 case Bigint: 999 case Bigunsigned: 1000 logger.debug("chooseAutoIncrementValueSetter autoIncrementValueSetterBigint."); 1001 autoIncrementValueSetter = autoIncrementValueSetterLong; 1002 break; 1003 case Smallint: 1004 case Smallunsigned: 1005 logger.debug("chooseAutoIncrementValueSetter autoIncrementValueSetterSmallint."); 1006 autoIncrementValueSetter = autoIncrementValueSetterShort; 1007 break; 1008 case Tinyint: 1009 case Tinyunsigned: 1010 logger.debug("chooseAutoIncrementValueSetter autoIncrementValueSetterTinyint."); 1011 autoIncrementValueSetter = autoIncrementValueSetterByte; 1012 break; 1013 default: 1014 logger.error("chooseAutoIncrementValueSetter undefined."); 1015 autoIncrementValueSetter = autoIncrementValueSetterError; 1016 throw new ClusterJFatalInternalException(local.message("ERR_Unsupported_AutoIncrement_Column_Type", 1017 autoIncrementColumn.getType(), autoIncrementColumn.getName(), tableConst.getName())); 1018 } 1019 } 1020 1021 protected interface AutoIncrementValueSetter { set(ByteBuffer valueBuffer, long value)1022 void set(ByteBuffer valueBuffer, long value); 1023 } 1024 1025 protected AutoIncrementValueSetter autoIncrementValueSetterError = new AutoIncrementValueSetter() { 1026 public void set(ByteBuffer valueBuffer, long value) { 1027 throw new ClusterJFatalInternalException(local.message("ERR_No_AutoIncrement_Column", 1028 tableConst.getName())); 1029 } 1030 }; 1031 1032 protected AutoIncrementValueSetter autoIncrementValueSetterInt = new AutoIncrementValueSetter() { 1033 public void set(ByteBuffer valueBuffer, long value) { 1034 if (logger.isDetailEnabled()) logger.detail("autoincrement set value: " + value); 1035 if (value < 0 || value > Integer.MAX_VALUE) { 1036 throw new ClusterJDatastoreException(local.message("ERR_AutoIncrement_Value_Out_Of_Range", 1037 value, autoIncrementColumn.getName(), tableConst.getName())); 1038 } 1039 setInt(valueBuffer, autoIncrementColumn, (int)value); 1040 } 1041 }; 1042 1043 protected AutoIncrementValueSetter autoIncrementValueSetterLong = new AutoIncrementValueSetter() { 1044 public void set(ByteBuffer valueBuffer, long value) { 1045 if (logger.isDetailEnabled()) logger.detail("autoincrement set value: " + value); 1046 if (value < 0) { 1047 throw new ClusterJDatastoreException(local.message("ERR_AutoIncrement_Value_Out_Of_Range", 1048 value, autoIncrementColumn.getName(), tableConst.getName())); 1049 } 1050 setLong(valueBuffer, autoIncrementColumn, value); 1051 } 1052 }; 1053 1054 protected AutoIncrementValueSetter autoIncrementValueSetterShort = new AutoIncrementValueSetter() { 1055 public void set(ByteBuffer valueBuffer, long value) { 1056 if (logger.isDetailEnabled()) logger.detail("autoincrement set value: " + value); 1057 if (value < 0 || value > Short.MAX_VALUE) { 1058 throw new ClusterJDatastoreException(local.message("ERR_AutoIncrement_Value_Out_Of_Range", 1059 value, autoIncrementColumn.getName(), tableConst.getName())); 1060 } 1061 setShort(valueBuffer, autoIncrementColumn, (short)value); 1062 } 1063 }; 1064 1065 protected AutoIncrementValueSetter autoIncrementValueSetterByte = new AutoIncrementValueSetter() { 1066 public void set(ByteBuffer valueBuffer, long value) { 1067 if (logger.isDetailEnabled()) logger.detail("autoincrement set value: " + value); 1068 if (value < 0 || value > Byte.MAX_VALUE) { 1069 throw new ClusterJDatastoreException(local.message("ERR_AutoIncrement_Value_Out_Of_Range", 1070 value, autoIncrementColumn.getName(), tableConst.getName())); 1071 } 1072 setByte(valueBuffer, autoIncrementColumn, (byte)value); 1073 } 1074 }; 1075 } 1076