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