1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl;
18
19 import java.io.BufferedWriter;
20 import java.io.IOException;
21 import java.io.StringWriter;
22 import java.lang.System.Logger;
23 import java.nio.BufferOverflowException;
24 import java.nio.ByteBuffer;
25 import java.nio.charset.Charset;
26 import java.time.LocalDateTime;
27 import java.util.AbstractMap;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.Comparator;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.TreeSet;
40
41 import com.healthmarketscience.jackcess.BatchUpdateException;
42 import com.healthmarketscience.jackcess.Column;
43 import com.healthmarketscience.jackcess.ColumnBuilder;
44 import com.healthmarketscience.jackcess.ConstraintViolationException;
45 import com.healthmarketscience.jackcess.CursorBuilder;
46 import com.healthmarketscience.jackcess.DataType;
47 import com.healthmarketscience.jackcess.Index;
48 import com.healthmarketscience.jackcess.IndexBuilder;
49 import com.healthmarketscience.jackcess.InvalidValueException;
50 import com.healthmarketscience.jackcess.JackcessException;
51 import com.healthmarketscience.jackcess.PropertyMap;
52 import com.healthmarketscience.jackcess.Row;
53 import com.healthmarketscience.jackcess.RowId;
54 import com.healthmarketscience.jackcess.Table;
55 import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
56 import com.healthmarketscience.jackcess.expr.Identifier;
57 import com.healthmarketscience.jackcess.util.ErrorHandler;
58 import com.healthmarketscience.jackcess.util.ExportUtil;
59
60
61
62
63
64
65
66
67
68 public class TableImpl implements Table, PropertyMaps.Owner
69 {
70 private static final Logger LOG = System.getLogger(TableImpl.class.getName());
71
72 private static final short OFFSET_MASK = (short)0x1FFF;
73
74 private static final short DELETED_ROW_MASK = (short)0x8000;
75
76 private static final short OVERFLOW_ROW_MASK = (short)0x4000;
77
78 static final int MAGIC_TABLE_NUMBER = 1625;
79
80 private static final int MAX_BYTE = 256;
81
82
83 private static final char BYTE_ORDER_MARK = '\uFEFF';
84
85
86
87
88
89 public static final byte TYPE_SYSTEM = 0x53;
90
91
92
93
94 public static final byte TYPE_USER = 0x4e;
95
96 public enum IndexFeature {
97 EXACT_MATCH, EXACT_UNIQUE_ONLY, ANY_MATCH;
98 }
99
100
101
102 private static final Comparator<ColumnImpl> VAR_LEN_COLUMN_COMPARATOR =
103 Comparator.comparingInt(ColumnImpl::getVarLenTableIndex);
104
105
106 private final DatabaseImpl _database;
107
108 private final int _flags;
109
110 private final byte _tableType;
111
112 private int _indexCount;
113
114 private int _logicalIndexCount;
115
116 private final int _tableDefPageNumber;
117
118 private short _maxColumnCount;
119
120 private short _maxVarColumnCount;
121
122 private final List<ColumnImpl> _columns = new ArrayList<>();
123
124 private final List<ColumnImpl> _varColumns = new ArrayList<>();
125
126 private final List<ColumnImpl> _autoNumColumns = new ArrayList<>(1);
127
128 private final CalcColEvaluator _calcColEval = new CalcColEvaluator();
129
130
131 private final List<IndexImpl> _indexes = new ArrayList<>();
132
133
134 private final List<IndexData> _indexDatas = new ArrayList<>();
135
136 private final Set<ColumnImpl> _indexColumns = new LinkedHashSet<>();
137
138 private final String _name;
139
140 private final UsageMap _ownedPages;
141
142 private final UsageMap _freeSpacePages;
143
144 private int _rowCount;
145
146 private int _lastLongAutoNumber;
147
148 private int _lastComplexTypeAutoNumber;
149
150 private int _modCount;
151
152 private final TempPageHolder _addRowBufferH =
153 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
154
155 private final TempPageHolder _tableDefBufferH =
156 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
157
158 private final TempBufferHolder _writeRowBufferH =
159 TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
160
161 private final TempPageHolder _longValueBufferH =
162 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
163
164 private ErrorHandler _tableErrorHandler;
165
166 private PropertyMap _props;
167
168 private PropertyMaps _propertyMaps;
169
170 private TableNameMapImpl _nameMap;
171
172
173 private Boolean _allowAutoNumInsert;
174
175 private final FKEnforcer _fkEnforcer;
176
177 private RowValidatorEvalContext _rowValidator;
178
179
180
181 private String _writeDefError;
182
183
184
185 private CursorImpl _defaultCursor;
186
187
188
189
190
191 protected TableImpl(boolean testing, List<ColumnImpl> columns)
192 {
193 if(!testing) {
194 throw new IllegalArgumentException();
195 }
196 _database = null;
197 _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
198 _name = null;
199
200 _columns.addAll(columns);
201 for(ColumnImpl col : _columns) {
202 if(col.getType().isVariableLength()) {
203 _varColumns.add(col);
204 }
205 }
206 _maxColumnCount = (short)_columns.size();
207 _maxVarColumnCount = (short)_varColumns.size();
208 initAutoNumberColumns();
209
210 _fkEnforcer = null;
211 _flags = 0;
212 _tableType = TYPE_USER;
213 _indexCount = 0;
214 _logicalIndexCount = 0;
215 _ownedPages = null;
216 _freeSpacePages = null;
217 }
218
219
220
221
222
223
224
225 protected TableImpl(DatabaseImpl database, ByteBuffer tableBuffer,
226 int pageNumber, String name, int flags)
227 throws IOException
228 {
229 _database = database;
230 _tableDefPageNumber = pageNumber;
231 _name = name;
232 _flags = flags;
233
234
235 tableBuffer = loadCompleteTableDefinitionBuffer(tableBuffer, null);
236
237 _rowCount = tableBuffer.getInt(getFormat().OFFSET_NUM_ROWS);
238 _lastLongAutoNumber = tableBuffer.getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER);
239 if(getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER >= 0) {
240 _lastComplexTypeAutoNumber = tableBuffer.getInt(
241 getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER);
242 }
243 _tableType = tableBuffer.get(getFormat().OFFSET_TABLE_TYPE);
244 _maxColumnCount = tableBuffer.getShort(getFormat().OFFSET_MAX_COLS);
245 _maxVarColumnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_VAR_COLS);
246 short columnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_COLS);
247 _logicalIndexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
248 _indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES);
249
250 tableBuffer.position(getFormat().OFFSET_OWNED_PAGES);
251 _ownedPages = UsageMap.read(getDatabase(), tableBuffer);
252 tableBuffer.position(getFormat().OFFSET_FREE_SPACE_PAGES);
253 _freeSpacePages = UsageMap.read(getDatabase(), tableBuffer);
254
255 for (int i = 0; i < _indexCount; i++) {
256 _indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
257 }
258
259 readColumnDefinitions(tableBuffer, columnCount);
260
261 validateColumnDefs();
262
263 readIndexDefinitions(tableBuffer);
264
265
266 while((tableBuffer.remaining() >= 2) &&
267 readColumnUsageMaps(tableBuffer)) {
268
269 }
270
271
272 if(getDatabase().getColumnOrder() != ColumnOrder.DATA) {
273 Collections.sort(_columns, ColumnImpl.DISPLAY_ORDER_COMPARATOR);
274 }
275
276 for(ColumnImpl col : _columns) {
277
278
279 col.postTableLoadInit();
280 }
281
282 _fkEnforcer = new FKEnforcer(this);
283
284 if(!isSystem()) {
285
286
287 for(ColumnImpl col : _columns) {
288 col.initColumnValidator();
289 }
290
291 reloadRowValidator();
292 }
293 }
294
295 private void reloadRowValidator() throws IOException {
296
297
298 _rowValidator = null;
299
300 if(!getDatabase().isEvaluateExpressions()) {
301 return;
302 }
303
304 PropertyMap props = getProperties();
305
306 String exprStr = PropertyMaps.getTrimmedStringProperty(
307 props, PropertyMap.VALIDATION_RULE_PROP);
308
309 if(exprStr != null) {
310 String helpStr = PropertyMaps.getTrimmedStringProperty(
311 props, PropertyMap.VALIDATION_TEXT_PROP);
312
313 _rowValidator = new RowValidatorEvalContext(this)
314 .setExpr(exprStr, helpStr);
315 }
316 }
317
318 @Override
319 public String getName() {
320 return _name;
321 }
322
323 @Override
324 public boolean isHidden() {
325 return((_flags & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
326 }
327
328 @Override
329 public boolean isSystem() {
330 return(_tableType != TYPE_USER);
331 }
332
333
334
335
336 public int getMaxColumnCount() {
337 return _maxColumnCount;
338 }
339
340 @Override
341 public int getColumnCount() {
342 return _columns.size();
343 }
344
345 @Override
346 public DatabaseImpl getDatabase() {
347 return _database;
348 }
349
350
351
352
353 public JetFormat getFormat() {
354 return getDatabase().getFormat();
355 }
356
357
358
359
360 public PageChannel getPageChannel() {
361 return getDatabase().getPageChannel();
362 }
363
364 @Override
365 public ErrorHandler getErrorHandler() {
366 return((_tableErrorHandler != null) ? _tableErrorHandler :
367 getDatabase().getErrorHandler());
368 }
369
370 @Override
371 public void setErrorHandler(ErrorHandler newErrorHandler) {
372 _tableErrorHandler = newErrorHandler;
373 }
374
375 public int getTableDefPageNumber() {
376 return _tableDefPageNumber;
377 }
378
379 @Override
380 public boolean isAllowAutoNumberInsert() {
381 return ((_allowAutoNumInsert != null) ? (boolean)_allowAutoNumInsert :
382 getDatabase().isAllowAutoNumberInsert());
383 }
384
385 @Override
386 public void setAllowAutoNumberInsert(Boolean allowAutoNumInsert) {
387 _allowAutoNumInsert = allowAutoNumInsert;
388 }
389
390
391
392
393 public RowState createRowState() {
394 return new RowState(TempBufferHolder.Type.HARD);
395 }
396
397
398
399
400 public UsageMap.PageCursor getOwnedPagesCursor() {
401 return _ownedPages.cursor();
402 }
403
404
405
406
407
408
409
410
411
412
413
414
415
416 public int getApproximateOwnedPageCount() {
417
418
419
420 int count = _ownedPages.getPageCount() + 1;
421
422 for(ColumnImpl col : _columns) {
423 count += col.getOwnedPageCount();
424 }
425
426
427
428 for(IndexData indexData : _indexDatas) {
429 count += indexData.getOwnedPageCount();
430 }
431
432 return count;
433 }
434
435 protected TempPageHolder getLongValueBuffer() {
436 return _longValueBufferH;
437 }
438
439 @Override
440 public List<ColumnImpl> getColumns() {
441 return Collections.unmodifiableList(_columns);
442 }
443
444 @Override
445 public ColumnImpl getColumn(String name) {
446 for(ColumnImpl column : _columns) {
447 if(isSameColumnName(column.getName(), name)) {
448 return column;
449 }
450 }
451 throw new IllegalArgumentException(withErrorContext(
452 "Column with name " + name + " does not exist in this table"));
453 }
454
455 public boolean hasColumn(String name) {
456 for(ColumnImpl column : _columns) {
457 if(isSameColumnName(column.getName(), name)) {
458 return true;
459 }
460 }
461 return false;
462 }
463
464
465
466
467
468
469
470
471 private static boolean isSameColumnName(String name, String other) {
472 return stripByteOrderMark(name).equalsIgnoreCase(
473 stripByteOrderMark(other));
474 }
475
476 private static String stripByteOrderMark(String name) {
477 return ((!StringUtil.isEmpty(name) &&
478 (name.charAt(0) == BYTE_ORDER_MARK)) ? name.substring(1) : name);
479 }
480
481 @Override
482 public PropertyMap getProperties() throws IOException {
483 if(_props == null) {
484 _props = getPropertyMaps().getDefault();
485 }
486 return _props;
487 }
488
489 @Override
490 public TableNameMapImpl getNameMap() throws IOException {
491 if(_nameMap == null) {
492 byte[] bytes = (byte[])getProperties().getValue(
493 PropertyMap.NAME_MAP_PROP);
494 if(bytes != null) {
495 _nameMap = TableNameMapImpl.read(bytes, this);
496 }
497 }
498 return _nameMap;
499 }
500
501 @Override
502 public LocalDateTime getCreatedDate() throws IOException {
503 return getDatabase().getCreateDateForObject(_tableDefPageNumber);
504 }
505
506 @Override
507 public LocalDateTime getUpdatedDate() throws IOException {
508 return getDatabase().getUpdateDateForObject(_tableDefPageNumber);
509 }
510
511
512
513
514
515 public PropertyMaps getPropertyMaps() throws IOException {
516 if(_propertyMaps == null) {
517 _propertyMaps = getDatabase().getPropertiesForObject(
518 _tableDefPageNumber, this);
519 }
520 return _propertyMaps;
521 }
522
523 @Override
524 public void propertiesUpdated() throws IOException {
525
526 _nameMap = null;
527
528
529 for(ColumnImpl col : _columns) {
530 col.propertiesUpdated();
531 }
532
533 reloadRowValidator();
534
535
536
537 _calcColEval.reSort();
538 }
539
540 @Override
541 public List<IndexImpl> getIndexes() {
542 return Collections.unmodifiableList(_indexes);
543 }
544
545 @Override
546 public IndexImpl getIndex(String name) {
547 for(IndexImpl index : _indexes) {
548 if(index.getName().equalsIgnoreCase(name)) {
549 return index;
550 }
551 }
552 throw new IllegalArgumentException(withErrorContext(
553 "Index with name " + name + " does not exist on this table"));
554 }
555
556 @Override
557 public IndexImpl getPrimaryKeyIndex() {
558 for(IndexImpl index : _indexes) {
559 if(index.isPrimaryKey()) {
560 return index;
561 }
562 }
563 throw new IllegalArgumentException(withErrorContext(
564 "No primary key index found"));
565 }
566
567 @Override
568 public IndexImpl getForeignKeyIndex(Table otherTable) {
569 for(IndexImpl index : _indexes) {
570 if(index.isForeignKey() && (index.getReference() != null) &&
571 (index.getReference().getOtherTablePageNumber() ==
572 ((TableImpl)otherTable).getTableDefPageNumber())) {
573 return index;
574 }
575 }
576 throw new IllegalArgumentException(withErrorContext(
577 "No foreign key reference to " +
578 otherTable.getName() + " found"));
579 }
580
581
582
583
584
585 public List<IndexData> getIndexDatas() {
586 return Collections.unmodifiableList(_indexDatas);
587 }
588
589
590
591
592
593 public int getLogicalIndexCount() {
594 return _logicalIndexCount;
595 }
596
597 int getIndexCount() {
598 return _indexCount;
599 }
600
601 public IndexImpl findIndexForColumns(Collection<String> searchColumns,
602 IndexFeature feature) {
603
604 IndexImpl partialIndex = null;
605 for(IndexImpl index : _indexes) {
606
607 Collection<? extends Index.Column> indexColumns = index.getColumns();
608 if(indexColumns.size() < searchColumns.size()) {
609 continue;
610 }
611 boolean exactMatch = (indexColumns.size() == searchColumns.size());
612
613 Iterator<String> sIter = searchColumns.iterator();
614 Iterator<? extends Index.Column> iIter = indexColumns.iterator();
615 boolean searchMatches = true;
616 while(sIter.hasNext()) {
617 String sColName = sIter.next();
618 String iColName = iIter.next().getName();
619 if((sColName != iColName) &&
620 ((sColName == null) || !sColName.equalsIgnoreCase(iColName))) {
621 searchMatches = false;
622 break;
623 }
624 }
625
626 if(searchMatches) {
627
628 if(exactMatch && ((feature != IndexFeature.EXACT_UNIQUE_ONLY) ||
629 index.isUnique())) {
630 return index;
631 }
632
633 if(!exactMatch && (feature == IndexFeature.ANY_MATCH) &&
634 ((partialIndex == null) ||
635 (indexColumns.size() < partialIndex.getColumnCount()))) {
636
637 partialIndex = index;
638 }
639 }
640 }
641
642 return partialIndex;
643 }
644
645 List<ColumnImpl> getAutoNumberColumns() {
646 return _autoNumColumns;
647 }
648
649 @Override
650 public CursorImpl getDefaultCursor() {
651 if(_defaultCursor == null) {
652 _defaultCursor = CursorImpl.createCursor(this);
653 }
654 return _defaultCursor;
655 }
656
657 @Override
658 public CursorBuilder newCursor() {
659 return new CursorBuilder(this);
660 }
661
662 @Override
663 public void reset() {
664 getDefaultCursor().reset();
665 }
666
667 @Override
668 public Rowf="../../../../com/healthmarketscience/jackcess/Row.html#Row">Row deleteRow(Row row) throws IOException {
669 deleteRow(row.getId());
670 return row;
671 }
672
673
674
675
676
677
678
679
680 public RowId"../../../../com/healthmarketscience/jackcess/RowId.html#RowId">RowId deleteRow(RowId rowId) throws IOException {
681 deleteRow(getDefaultCursor().getRowState(), (RowIdImpl)rowId);
682 return rowId;
683 }
684
685
686
687
688
689 public void deleteRow(RowState rowState, RowIdImpl rowId)
690 throws IOException
691 {
692 requireValidRowId(rowId);
693
694 getPageChannel().startWrite();
695 try {
696
697
698 ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
699
700 if(rowState.isDeleted()) {
701
702 return;
703 }
704 requireNonDeletedRow(rowState, rowId);
705
706
707
708 int pageNumber = rowState.getHeaderRowId().getPageNumber();
709 int rowNumber = rowState.getHeaderRowId().getRowNumber();
710
711
712 Object[] rowValues = null;
713 if(!_indexDatas.isEmpty()) {
714
715
716 rowBuffer = positionAtRowData(rowState, rowId);
717
718 for(ColumnImpl idxCol : _indexColumns) {
719 getRowColumn(getFormat(), rowBuffer, idxCol, rowState, null);
720 }
721
722
723 rowValues = rowState.getRowCacheValues();
724
725
726 _fkEnforcer.deleteRow(rowValues);
727
728
729 rowBuffer = positionAtRowHeader(rowState, rowId);
730 }
731
732
733 int rowIndex = getRowStartOffset(rowNumber, getFormat());
734 rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex)
735 | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
736 writeDataPage(rowBuffer, pageNumber);
737
738
739 for(IndexData indexData : _indexDatas) {
740 indexData.deleteRow(rowValues, rowId);
741 }
742
743
744 updateTableDefinition(-1);
745
746 } finally {
747 getPageChannel().finishWrite();
748 }
749 }
750
751
752
753
754
755
756 private byte[] readRawRowValue(ByteBuffer rowBuffer, ColumnImpl column,
757 RowState rowState)
758 {
759 try {
760 return getRawRowValue(getFormat(), rowBuffer, column, rowState);
761 } catch(Exception e) {
762
763
764 return null;
765 }
766 }
767
768 @Override
769 public Row getNextRow() throws IOException {
770 return getDefaultCursor().getNextRow();
771 }
772
773
774
775
776
777 public Object getRowValue(RowState rowState, RowIdImpl rowId,
778 ColumnImpl column)
779 throws IOException
780 {
781 if(this != column.getTable()) {
782 throw new IllegalArgumentException(withErrorContext(
783 "Given column " + column + " is not from this table"));
784 }
785 requireValidRowId(rowId);
786
787
788 ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
789 requireNonDeletedRow(rowState, rowId);
790
791 return getRowColumn(getFormat(), rowBuffer, column, rowState, null);
792 }
793
794
795
796
797
798
799 public RowImpl getRow(
800 RowState rowState, RowIdImpl rowId, Collection<String> columnNames)
801 throws IOException
802 {
803 requireValidRowId(rowId);
804
805
806 ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
807 requireNonDeletedRow(rowState, rowId);
808
809 return getRow(getFormat(), rowState, rowBuffer, _columns, columnNames);
810 }
811
812
813
814
815
816 private static RowImpl getRow(
817 JetFormat format,
818 RowState rowState,
819 ByteBuffer rowBuffer,
820 Collection<ColumnImpl> columns,
821 Collection<String> columnNames)
822 throws IOException
823 {
824 RowImplckcess/impl/RowImpl.html#RowImpl">RowImpl rtn = new RowImpl(rowState.getHeaderRowId(), columns.size());
825 for(ColumnImpl column : columns) {
826
827 if((columnNames == null) || (columnNames.contains(column.getName()))) {
828
829 column.setRowValue(
830 rtn, getRowColumn(format, rowBuffer, column, rowState, null));
831 }
832 }
833 return rtn;
834 }
835
836
837
838
839
840 private static Object getRowColumn(JetFormat format,
841 ByteBuffer rowBuffer,
842 ColumnImpl column,
843 RowState rowState,
844 Map<ColumnImpl,byte[]> rawVarValues)
845 throws IOException
846 {
847 byte[] columnData = null;
848 try {
849
850 NullMask nullMask = rowState.getNullMask(rowBuffer);
851 boolean isNull = nullMask.isNull(column);
852 if(column.storeInNullMask()) {
853
854
855 return rowState.setRowCacheValue(column.getColumnIndex(),
856 column.readFromNullMask(isNull));
857 } else if(isNull) {
858
859 return null;
860 }
861
862 Object cachedValue = rowState.getRowCacheValue(column.getColumnIndex());
863 if(cachedValue != null) {
864
865 return cachedValue;
866 }
867
868 columnData = getRowColumnData(format, rowBuffer, column, rowState,
869 nullMask);
870
871 if((rawVarValues != null) && column.isVariableLength()) {
872
873
874 rawVarValues.put(column, columnData);
875 }
876
877
878
879
880
881 return rowState.setRowCacheValue(column.getColumnIndex(),
882 column.read(columnData));
883
884 } catch(Exception e) {
885
886
887 rowState.setRowCacheValue(column.getColumnIndex(),
888 ColumnImpl.rawDataWrapper(columnData));
889
890 return rowState.handleRowError(column, columnData, e);
891 }
892 }
893
894
895
896
897
898 private static byte[] getRowColumnData(JetFormat format,
899 ByteBuffer rowBuffer,
900 ColumnImpl column,
901 RowState rowState,
902 NullMask nullMask)
903 {
904
905 rowBuffer.reset();
906
907
908 int rowStart = rowBuffer.position();
909 int colDataPos = 0;
910 int colDataLen = 0;
911 if(!column.isVariableLength()) {
912
913
914 int dataStart = rowStart + format.OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
915 colDataPos = dataStart + column.getFixedDataOffset();
916 colDataLen = column.getFixedDataSize();
917
918 } else {
919 int varDataStart;
920 int varDataEnd;
921
922 if(format.SIZE_ROW_VAR_COL_OFFSET == 2) {
923
924
925 int varColumnOffsetPos =
926 (rowBuffer.limit() - nullMask.byteSize() - 4) -
927 (column.getVarLenTableIndex() * 2);
928
929 varDataStart = rowBuffer.getShort(varColumnOffsetPos);
930 varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
931
932 } else {
933
934
935 short[] varColumnOffsets = readJumpTableVarColOffsets(
936 rowState, rowBuffer, rowStart, nullMask);
937
938 varDataStart = varColumnOffsets[column.getVarLenTableIndex()];
939 varDataEnd = varColumnOffsets[column.getVarLenTableIndex() + 1];
940 }
941
942 colDataPos = rowStart + varDataStart;
943 colDataLen = varDataEnd - varDataStart;
944 }
945
946
947 rowBuffer.position(colDataPos);
948 return ByteUtil.getBytes(rowBuffer, colDataLen);
949 }
950
951
952
953
954
955
956
957
958 private static byte[] getRawRowValue(JetFormat format, ByteBuffer rowBuffer,
959 ColumnImpl column, RowState rowState)
960 {
961 NullMask nullMask = rowState.getNullMask(rowBuffer);
962 if(nullMask.isNull(column)) {
963 return null;
964 }
965 return getRowColumnData(format, rowBuffer, column, rowState, nullMask);
966 }
967
968 private static short[] readJumpTableVarColOffsets(
969 RowState rowState, ByteBuffer rowBuffer, int rowStart,
970 NullMask nullMask)
971 {
972 short[] varColOffsets = rowState.getVarColOffsets();
973 if(varColOffsets != null) {
974 return varColOffsets;
975 }
976
977
978 int nullMaskSize = nullMask.byteSize();
979 int rowEnd = rowStart + rowBuffer.remaining() - 1;
980 int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
981 rowEnd - nullMaskSize);
982 varColOffsets = new short[numVarCols + 1];
983
984 int rowLen = rowEnd - rowStart + 1;
985 int numJumps = (rowLen - 1) / MAX_BYTE;
986 int colOffset = rowEnd - nullMaskSize - numJumps - 1;
987
988
989 if(((colOffset - rowStart - numVarCols) / MAX_BYTE) < numJumps) {
990 numJumps--;
991 }
992
993 int jumpsUsed = 0;
994 for(int i = 0; i < numVarCols + 1; i++) {
995
996 while((jumpsUsed < numJumps) &&
997 (i == ByteUtil.getUnsignedByte(
998 rowBuffer, rowEnd - nullMaskSize-jumpsUsed - 1))) {
999 jumpsUsed++;
1000 }
1001
1002 varColOffsets[i] = (short)
1003 (ByteUtil.getUnsignedByte(rowBuffer, colOffset - i)
1004 + (jumpsUsed * MAX_BYTE));
1005 }
1006
1007 rowState.setVarColOffsets(varColOffsets);
1008 return varColOffsets;
1009 }
1010
1011
1012
1013
1014 private NullMask getRowNullMask(ByteBuffer rowBuffer)
1015 {
1016
1017 rowBuffer.reset();
1018
1019
1020 int columnCount = ByteUtil.getUnsignedVarInt(
1021 rowBuffer, getFormat().SIZE_ROW_COLUMN_COUNT);
1022
1023
1024 NullMask/impl/NullMask.html#NullMask">NullMask nullMask = new NullMask(columnCount);
1025 rowBuffer.position(rowBuffer.limit() - nullMask.byteSize());
1026 nullMask.read(rowBuffer);
1027
1028 return nullMask;
1029 }
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039 public static ByteBuffer positionAtRowHeader(RowState rowState,
1040 RowIdImpl rowId)
1041 throws IOException
1042 {
1043 ByteBuffer rowBuffer = rowState.setHeaderRow(rowId);
1044
1045 if(rowState.isAtHeaderRow()) {
1046
1047 return rowBuffer;
1048 }
1049
1050 if(!rowState.isValid()) {
1051
1052 rowState.setStatus(RowStateStatus.AT_HEADER);
1053 return null;
1054 }
1055
1056
1057 short rowStart = rowBuffer.getShort(
1058 getRowStartOffset(rowId.getRowNumber(),
1059 rowState.getTable().getFormat()));
1060
1061
1062
1063 RowStatus rowStatus = RowStatus.NORMAL;
1064 if(isDeletedRow(rowStart)) {
1065 rowStatus = RowStatus.DELETED;
1066 } else if(isOverflowRow(rowStart)) {
1067 rowStatus = RowStatus.OVERFLOW;
1068 }
1069
1070 rowState.setRowStatus(rowStatus);
1071 rowState.setStatus(RowStateStatus.AT_HEADER);
1072 return rowBuffer;
1073 }
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084 public static ByteBuffer positionAtRowData(RowState rowState,
1085 RowIdImpl rowId)
1086 throws IOException
1087 {
1088 positionAtRowHeader(rowState, rowId);
1089 if(!rowState.isValid() || rowState.isDeleted()) {
1090
1091 rowState.setStatus(RowStateStatus.AT_FINAL);
1092 return null;
1093 }
1094
1095 ByteBuffer rowBuffer = rowState.getFinalPage();
1096 int rowNum = rowState.getFinalRowId().getRowNumber();
1097 JetFormat format = rowState.getTable().getFormat();
1098
1099 if(rowState.isAtFinalRow()) {
1100
1101 return narrowToRow(rowState, rowBuffer,
1102 findRowStart(rowBuffer, rowNum, format),
1103 findRowEnd(rowBuffer, rowNum, format));
1104 }
1105
1106 while(true) {
1107
1108
1109 short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
1110 short rowEnd = findRowEnd(rowBuffer, rowNum, format);
1111
1112
1113
1114
1115 boolean overflowRow = isOverflowRow(rowStart);
1116
1117
1118 rowStart = (short)(rowStart & OFFSET_MASK);
1119
1120 if (overflowRow) {
1121
1122 if((rowEnd - rowStart) < 4) {
1123 throw new IOException(rowState.getTable().withErrorContext(
1124 "invalid overflow row info"));
1125 }
1126
1127
1128
1129 int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
1130 int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer, rowStart + 1);
1131 rowBuffer = rowState.setOverflowRow(
1132 new RowIdImpl(overflowPageNum, overflowRowNum));
1133 rowNum = overflowRowNum;
1134
1135 } else {
1136
1137 rowState.setStatus(RowStateStatus.AT_FINAL);
1138 return narrowToRow(rowState, rowBuffer, rowStart, rowEnd);
1139 }
1140 }
1141 }
1142
1143
1144
1145
1146
1147
1148
1149 private static ByteBuffer narrowToRow(RowState rowState,
1150 ByteBuffer rowBuffer,
1151 int rowStart, int rowEnd)
1152 throws IOException
1153 {
1154 if((rowStart > rowEnd) || (rowEnd > rowBuffer.limit())) {
1155 throw new IOException(rowState.getTable().withErrorContext(
1156 "Invalid row, " + rowStart + " to " + rowEnd +
1157 " is not within a page of " +
1158 rowBuffer.limit() + " bytes"));
1159 }
1160 return PageChannel.narrowBuffer(rowBuffer, rowStart, rowEnd);
1161 }
1162
1163 @Override
1164 public Iterator<Row> iterator() {
1165 return getDefaultCursor().iterator();
1166 }
1167
1168
1169
1170
1171
1172 protected static void writeTableDefinition(TableCreator creator)
1173 throws IOException
1174 {
1175
1176 createUsageMapDefinitionBuffer(creator);
1177
1178
1179
1180 JetFormat format = creator.getFormat();
1181 int idxDataLen = (creator.getIndexCount() *
1182 (format.SIZE_INDEX_DEFINITION +
1183 format.SIZE_INDEX_COLUMN_BLOCK)) +
1184 (creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK);
1185 int colUmapLen = creator.getLongValueColumns().size() * 10;
1186 int totalTableDefSize = format.SIZE_TDEF_HEADER +
1187 (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) +
1188 idxDataLen + colUmapLen + format.SIZE_TDEF_TRAILER;
1189
1190
1191
1192 for(ColumnBuilder col : creator.getColumns()) {
1193 totalTableDefSize += DBMutator.calculateNameLength(col.getName());
1194 }
1195
1196 for(IndexBuilder idx : creator.getIndexes()) {
1197 totalTableDefSize += DBMutator.calculateNameLength(idx.getName());
1198 }
1199
1200
1201
1202 ByteBuffer buffer = PageChannel.createBuffer(Math.max(totalTableDefSize,
1203 format.PAGE_SIZE));
1204 writeTableDefinitionHeader(creator, buffer, totalTableDefSize);
1205
1206 if(creator.hasIndexes()) {
1207
1208 IndexData.writeRowCountDefinitions(creator, buffer);
1209 }
1210
1211
1212 ColumnImpl.writeDefinitions(creator, buffer);
1213
1214 if(creator.hasIndexes()) {
1215
1216 IndexData.writeDefinitions(creator, buffer);
1217 IndexImpl.writeDefinitions(creator, buffer);
1218 }
1219
1220
1221 ColumnImpl.writeColUsageMapDefinitions(creator, buffer);
1222
1223
1224 buffer.put((byte) 0xff);
1225 buffer.put((byte) 0xff);
1226 buffer.flip();
1227
1228
1229 writeTableDefinitionBuffer(buffer, creator.getTdefPageNumber(), creator,
1230 Collections.<Integer>emptyList());
1231 }
1232
1233 private static void writeTableDefinitionBuffer(
1234 ByteBuffer buffer, int tdefPageNumber,
1235 TableMutator mutator, List<Integer> reservedPages)
1236 throws IOException
1237 {
1238 buffer.rewind();
1239 int totalTableDefSize = buffer.remaining();
1240 JetFormat format = mutator.getFormat();
1241 PageChannel pageChannel = mutator.getPageChannel();
1242
1243
1244 if(totalTableDefSize <= format.PAGE_SIZE) {
1245
1246
1247
1248
1249 buffer.putShort(format.OFFSET_FREE_SPACE,
1250 (short)(Math.max(
1251 format.PAGE_SIZE - totalTableDefSize - 8, 0)));
1252
1253 buffer.clear();
1254 pageChannel.writePage(buffer, tdefPageNumber);
1255
1256 } else {
1257
1258
1259
1260 ByteBuffer partialTdef = pageChannel.createPageBuffer();
1261 buffer.rewind();
1262 int nextTdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
1263 while(buffer.hasRemaining()) {
1264
1265
1266 partialTdef.clear();
1267
1268 if(nextTdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1269
1270
1271
1272 nextTdefPageNumber = tdefPageNumber;
1273
1274 } else {
1275
1276
1277 writeTablePageHeader(partialTdef);
1278 }
1279
1280
1281 int curTdefPageNumber = nextTdefPageNumber;
1282 int writeLen = Math.min(partialTdef.remaining(), buffer.remaining());
1283 partialTdef.put(buffer.array(), buffer.position(), writeLen);
1284 ByteUtil.forward(buffer, writeLen);
1285
1286 if(buffer.hasRemaining()) {
1287
1288 if(reservedPages.isEmpty()) {
1289 nextTdefPageNumber = pageChannel.allocateNewPage();
1290 } else {
1291 nextTdefPageNumber = reservedPages.remove(0);
1292 }
1293 partialTdef.putInt(format.OFFSET_NEXT_TABLE_DEF_PAGE,
1294 nextTdefPageNumber);
1295 }
1296
1297
1298 partialTdef.putShort(format.OFFSET_FREE_SPACE,
1299 (short)(Math.max(
1300 partialTdef.remaining() - 8, 0)));
1301
1302
1303 pageChannel.writePage(partialTdef, curTdefPageNumber);
1304 }
1305
1306 }
1307
1308 }
1309
1310
1311
1312
1313
1314 protected ColumnImpl mutateAddColumn(TableUpdater mutator) throws IOException
1315 {
1316 ColumnBuilder column = mutator.getColumn();
1317 JetFormat format = mutator.getFormat();
1318 boolean isVarCol = column.isVariableLength();
1319 boolean isLongVal = column.getType().isLongValue();
1320
1321
1322
1323 if(isLongVal) {
1324 mutator.addTdefLen(10);
1325 }
1326
1327 mutator.addTdefLen(format.SIZE_COLUMN_DEF_BLOCK);
1328
1329 int nameByteLen = DBMutator.calculateNameLength(column.getName());
1330 mutator.addTdefLen(nameByteLen);
1331
1332
1333
1334 ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate(
1335 mutator);
1336
1337 ColumnImpl newCol = null;
1338 int umapPos = -1;
1339 boolean success = false;
1340 try {
1341
1342
1343
1344 ByteUtil.forward(tableBuffer, 29);
1345 tableBuffer.putShort((short)(_maxColumnCount + 1));
1346 short varColCount = (short)(_maxVarColumnCount + (isVarCol ? 1 : 0));
1347 tableBuffer.putShort(varColCount);
1348 tableBuffer.putShort((short)(_columns.size() + 1));
1349
1350
1351 tableBuffer.position(format.SIZE_TDEF_HEADER +
1352 (_indexCount * format.SIZE_INDEX_DEFINITION) +
1353 (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
1354
1355
1356 int varOffset = 0;
1357
1358 for(ColumnImpl col : _varColumns) {
1359 if(col.isVariableLength() && (col.getVarLenTableIndex() >= varOffset)) {
1360 varOffset = col.getVarLenTableIndex() + 1;
1361 }
1362 }
1363
1364 int fixedOffset = 0;
1365 if(!column.isVariableLength() && !column.storeInNullMask()) {
1366
1367 for(ColumnImpl col : _columns) {
1368 if(!col.isVariableLength() &&
1369 (col.getFixedDataOffset() >= fixedOffset)) {
1370 fixedOffset = col.getFixedDataOffset() + col.getFixedDataSize();
1371 }
1372 }
1373 }
1374
1375 mutator.setColumnOffsets(fixedOffset, varOffset, varOffset);
1376
1377
1378 int colDefPos = tableBuffer.position();
1379 ByteUtil.insertEmptyData(tableBuffer, format.SIZE_COLUMN_DEF_BLOCK);
1380 ColumnImpl.writeDefinition(mutator, column, tableBuffer);
1381
1382
1383 skipNames(tableBuffer, _columns.size());
1384 ByteUtil.insertEmptyData(tableBuffer, nameByteLen);
1385 writeName(tableBuffer, column.getName(), mutator.getCharset());
1386
1387 if(isLongVal) {
1388
1389
1390 Map.Entry<Integer,Integer> umapInfo = addUsageMaps(2, null);
1391 TableMutator.ColumnState colState = mutator.getColumnState(column);
1392 colState.setUmapPageNumber(umapInfo.getKey());
1393 byte rowNum = umapInfo.getValue().byteValue();
1394 colState.setUmapOwnedRowNumber(rowNum);
1395 colState.setUmapFreeRowNumber((byte)(rowNum + 1));
1396
1397
1398 ByteUtil.forward(tableBuffer, (_indexCount *
1399 format.SIZE_INDEX_COLUMN_BLOCK));
1400 ByteUtil.forward(tableBuffer,
1401 (_logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK));
1402 skipNames(tableBuffer, _logicalIndexCount);
1403
1404
1405 while(tableBuffer.remaining() >= 2) {
1406 if(tableBuffer.getShort() == IndexData.COLUMN_UNUSED) {
1407
1408 ByteUtil.forward(tableBuffer, -2);
1409 break;
1410 }
1411
1412 ByteUtil.forward(tableBuffer, 8);
1413
1414
1415 }
1416
1417
1418 umapPos = tableBuffer.position();
1419 ByteUtil.insertEmptyData(tableBuffer, 10);
1420 ColumnImpl.writeColUsageMapDefinition(
1421 mutator, column, tableBuffer);
1422 }
1423
1424
1425 validateTableDefUpdate(mutator, tableBuffer);
1426
1427
1428 newCol = ColumnImpl.create(this, tableBuffer, colDefPos,
1429 column.getName(), _columns.size());
1430 newCol.setColumnIndex(_columns.size());
1431
1432
1433
1434 writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
1435 mutator.getNextPages());
1436 success = true;
1437
1438 } finally {
1439 if(!success) {
1440
1441 _tableDefBufferH.invalidate();
1442 }
1443 }
1444
1445
1446
1447
1448 _columns.add(newCol);
1449 ++_maxColumnCount;
1450 if(newCol.isVariableLength()) {
1451 _varColumns.add(newCol);
1452 ++_maxVarColumnCount;
1453 }
1454 if(newCol.isAutoNumber()) {
1455 _autoNumColumns.add(newCol);
1456 }
1457 if(newCol.isCalculated()) {
1458 _calcColEval.add(newCol);
1459 }
1460
1461 if(umapPos >= 0) {
1462
1463 tableBuffer.position(umapPos);
1464 readColumnUsageMaps(tableBuffer);
1465 }
1466
1467 newCol.postTableLoadInit();
1468
1469 if(!isSystem()) {
1470
1471
1472 newCol.initColumnValidator();
1473 }
1474
1475
1476 Map<String,PropertyMap.Property> colProps = column.getProperties();
1477 if(colProps != null) {
1478 newCol.getProperties().putAll(colProps.values());
1479 getProperties().save();
1480 }
1481
1482 completeTableMutation(tableBuffer);
1483
1484 if(newCol.getType() == DataType.COMPLEX_TYPE) {
1485 fillComplexColumnValues(newCol);
1486 }
1487
1488 return newCol;
1489 }
1490
1491
1492
1493
1494
1495
1496 private void fillComplexColumnValues(ColumnImpl newCol) throws IOException
1497 {
1498 Boolean origAllowAutoNumInsert = _allowAutoNumInsert;
1499
1500
1501 _allowAutoNumInsert = Boolean.TRUE;
1502 try {
1503
1504 for(Row row : newCursor().toCursor()) {
1505 row.put(newCol.getName(), getRowComplexId(row, newCol));
1506 updateRow(row);
1507 }
1508 } finally {
1509 _allowAutoNumInsert = origAllowAutoNumInsert;
1510 }
1511 }
1512
1513
1514
1515
1516
1517 private int getRowComplexId(Row row, ColumnImpl newCol) {
1518 for(ColumnImpl col : _columns) {
1519 if((col == newCol) || (col.getType() != DataType.COMPLEX_TYPE)) {
1520 continue;
1521 }
1522 ComplexValueForeignKey complexFk =
1523 (ComplexValueForeignKey)row.get(col.getName());
1524 if(complexFk != null) {
1525 return complexFk.get();
1526 }
1527 }
1528 return getNextComplexTypeAutoNumber();
1529 }
1530
1531
1532
1533
1534
1535 protected IndexData mutateAddIndexData(TableUpdater mutator) throws IOException
1536 {
1537 IndexBuilder index = mutator.getIndex();
1538 JetFormat format = mutator.getFormat();
1539
1540
1541
1542 mutator.addTdefLen(format.SIZE_INDEX_DEFINITION +
1543 format.SIZE_INDEX_COLUMN_BLOCK);
1544
1545
1546
1547 ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate(
1548 mutator);
1549
1550 IndexData newIdxData = null;
1551 boolean success = false;
1552 try {
1553
1554
1555
1556 ByteUtil.forward(tableBuffer, 39);
1557 tableBuffer.putInt(_indexCount + 1);
1558
1559
1560 tableBuffer.position(format.SIZE_TDEF_HEADER +
1561 (_indexCount * format.SIZE_INDEX_DEFINITION));
1562
1563
1564 ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_DEFINITION);
1565 IndexData.writeRowCountDefinitions(mutator, tableBuffer, 1);
1566
1567
1568 ByteUtil.forward(tableBuffer,
1569 (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
1570 skipNames(tableBuffer, _columns.size());
1571
1572
1573 ByteUtil.forward(tableBuffer, (_indexCount *
1574 format.SIZE_INDEX_COLUMN_BLOCK));
1575
1576
1577 TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(index);
1578 int rootPageNumber = getPageChannel().allocateNewPage();
1579 Map.Entry<Integer,Integer> umapInfo = addUsageMaps(1, rootPageNumber);
1580 idxDataState.setRootPageNumber(rootPageNumber);
1581 idxDataState.setUmapPageNumber(umapInfo.getKey());
1582 idxDataState.setUmapRowNumber(umapInfo.getValue().byteValue());
1583
1584
1585 int idxDataDefPos = tableBuffer.position();
1586 ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_COLUMN_BLOCK);
1587 IndexData.writeDefinition(mutator, tableBuffer, idxDataState, null);
1588
1589
1590 validateTableDefUpdate(mutator, tableBuffer);
1591
1592
1593 tableBuffer.position(0);
1594 newIdxData = IndexData.create(
1595 this, tableBuffer, idxDataState.getIndexDataNumber(), format);
1596 tableBuffer.position(idxDataDefPos);
1597 newIdxData.read(tableBuffer, _columns);
1598
1599
1600
1601 writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
1602 mutator.getNextPages());
1603 success = true;
1604
1605 } finally {
1606 if(!success) {
1607
1608 _tableDefBufferH.invalidate();
1609 }
1610 }
1611
1612
1613
1614
1615 for(IndexData.ColumnDescriptor iCol : newIdxData.getColumns()) {
1616 _indexColumns.add(iCol.getColumn());
1617 }
1618
1619 ++_indexCount;
1620 _indexDatas.add(newIdxData);
1621
1622 completeTableMutation(tableBuffer);
1623
1624
1625 populateIndexData(newIdxData);
1626
1627 return newIdxData;
1628 }
1629
1630 private void populateIndexData(IndexData idxData)
1631 throws IOException
1632 {
1633
1634 List<ColumnImpl> idxCols = new ArrayList<>();
1635 for(IndexData.ColumnDescriptor col : idxData.getColumns()) {
1636 idxCols.add(col.getColumn());
1637 }
1638
1639
1640 Object[] rowVals = new Object[_columns.size()];
1641 for(Row row : getDefaultCursor().newIterable().addColumns(idxCols)) {
1642 for(Column col : idxCols) {
1643 col.setRowValue(rowVals, col.getRowValue(row));
1644 }
1645
1646 IndexData.commitAll(
1647 idxData.prepareAddRow(rowVals, (RowIdImpl)row.getId(), null));
1648 }
1649
1650 updateTableDefinition(0);
1651 }
1652
1653
1654
1655
1656
1657 protected IndexImpl mutateAddIndex(TableUpdater mutator) throws IOException
1658 {
1659 IndexBuilder index = mutator.getIndex();
1660 JetFormat format = mutator.getFormat();
1661
1662
1663
1664 mutator.addTdefLen(format.SIZE_INDEX_INFO_BLOCK);
1665
1666 int nameByteLen = DBMutator.calculateNameLength(index.getName());
1667 mutator.addTdefLen(nameByteLen);
1668
1669
1670
1671 ByteBuffer tableBuffer = loadCompleteTableDefinitionBufferForUpdate(
1672 mutator);
1673
1674 IndexImpl newIdx = null;
1675 boolean success = false;
1676 try {
1677
1678
1679
1680 ByteUtil.forward(tableBuffer, 35);
1681 tableBuffer.putInt(_logicalIndexCount + 1);
1682
1683
1684 tableBuffer.position(format.SIZE_TDEF_HEADER +
1685 (_indexCount * format.SIZE_INDEX_DEFINITION));
1686
1687
1688 ByteUtil.forward(tableBuffer,
1689 (_columns.size() * format.SIZE_COLUMN_DEF_BLOCK));
1690 skipNames(tableBuffer, _columns.size());
1691
1692
1693 ByteUtil.forward(tableBuffer, (_indexCount *
1694 format.SIZE_INDEX_COLUMN_BLOCK));
1695
1696 ByteUtil.forward(tableBuffer, (_logicalIndexCount *
1697 format.SIZE_INDEX_INFO_BLOCK));
1698
1699 int idxDefPos = tableBuffer.position();
1700 ByteUtil.insertEmptyData(tableBuffer, format.SIZE_INDEX_INFO_BLOCK);
1701 IndexImpl.writeDefinition(mutator, index, tableBuffer);
1702
1703
1704 skipNames(tableBuffer, _logicalIndexCount);
1705 ByteUtil.insertEmptyData(tableBuffer, nameByteLen);
1706 writeName(tableBuffer, index.getName(), mutator.getCharset());
1707
1708
1709 validateTableDefUpdate(mutator, tableBuffer);
1710
1711
1712 tableBuffer.position(idxDefPos);
1713 newIdx = new IndexImpl(tableBuffer, _indexDatas, format);
1714 newIdx.setName(index.getName());
1715
1716
1717
1718 writeTableDefinitionBuffer(tableBuffer, _tableDefPageNumber, mutator,
1719 mutator.getNextPages());
1720 success = true;
1721
1722 } finally {
1723 if(!success) {
1724
1725 _tableDefBufferH.invalidate();
1726 }
1727 }
1728
1729
1730
1731
1732 ++_logicalIndexCount;
1733 _indexes.add(newIdx);
1734
1735 completeTableMutation(tableBuffer);
1736
1737 return newIdx;
1738 }
1739
1740 private void validateTableDefUpdate(TableUpdater mutator, ByteBuffer tableBuffer)
1741 {
1742 if(!mutator.validateUpdatedTdef(tableBuffer)) {
1743 throw new IllegalStateException(
1744 withErrorContext("Failed updating table definition (unexpected length)"));
1745 }
1746 }
1747
1748 private void completeTableMutation(ByteBuffer tableBuffer)
1749 {
1750
1751 _tableDefBufferH.possiblyInvalidate(_tableDefPageNumber, tableBuffer);
1752
1753
1754 _fkEnforcer.reset();
1755
1756
1757
1758 ++_modCount;
1759 }
1760
1761
1762
1763
1764 private static void skipNames(ByteBuffer tableBuffer, int count) {
1765 for(int i = 0; i < count; ++i) {
1766 ByteUtil.forward(tableBuffer, tableBuffer.getShort());
1767 }
1768 }
1769
1770 private ByteBuffer loadCompleteTableDefinitionBufferForUpdate(
1771 TableUpdater mutator)
1772 throws IOException
1773 {
1774
1775 ByteBuffer tableBuffer = _tableDefBufferH.setPage(getPageChannel(),
1776 _tableDefPageNumber);
1777 tableBuffer = loadCompleteTableDefinitionBuffer(
1778 tableBuffer, mutator.getNextPages());
1779
1780
1781 int addedLen = mutator.getAddedTdefLen();
1782 int origTdefLen = tableBuffer.getInt(8);
1783 mutator.setOrigTdefLen(origTdefLen);
1784 int newTdefLen = origTdefLen + addedLen;
1785 while(newTdefLen > tableBuffer.capacity()) {
1786 tableBuffer = expandTableBuffer(tableBuffer);
1787 tableBuffer.flip();
1788 }
1789
1790 tableBuffer.limit(origTdefLen);
1791
1792
1793 tableBuffer.position(8);
1794 tableBuffer.putInt(newTdefLen);
1795
1796 return tableBuffer;
1797 }
1798
1799
1800
1801
1802
1803 private Map.Entry<Integer,Integer> addUsageMaps(
1804 int numMaps, Integer firstUsedPage)
1805 throws IOException
1806 {
1807 JetFormat format = getFormat();
1808 PageChannel pageChannel = getPageChannel();
1809 int umapRowLength = format.OFFSET_USAGE_MAP_START +
1810 format.USAGE_MAP_TABLE_BYTE_LENGTH;
1811 int totalUmapSpaceUsage = getRowSpaceUsage(umapRowLength, format) * numMaps;
1812 int umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
1813 int firstRowNum = -1;
1814 int freeSpace = 0;
1815
1816
1817
1818
1819
1820 Set<Integer> knownPages = new TreeSet<>(Collections.reverseOrder());
1821 collectUsageMapPages(knownPages);
1822
1823 ByteBuffer umapBuf = pageChannel.createPageBuffer();
1824 for(Integer pageNum : knownPages) {
1825 pageChannel.readPage(umapBuf, pageNum);
1826 freeSpace = umapBuf.getShort(format.OFFSET_FREE_SPACE);
1827 if(freeSpace >= totalUmapSpaceUsage) {
1828
1829 umapPageNumber = pageNum;
1830 firstRowNum = getRowsOnDataPage(umapBuf, format);
1831 break;
1832 }
1833 }
1834
1835 if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1836
1837
1838 umapPageNumber = pageChannel.allocateNewPage();
1839 freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
1840 firstRowNum = 0;
1841 umapBuf = createUsageMapDefPage(pageChannel, freeSpace);
1842 }
1843
1844
1845 int rowStart = findRowEnd(umapBuf, firstRowNum, format) - umapRowLength;
1846 int umapRowNum = firstRowNum;
1847 for(int i = 0; i < numMaps; ++i) {
1848 umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
1849 umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
1850
1851 int dataOffset = rowStart + 1;
1852 if(firstUsedPage != null) {
1853
1854 umapBuf.putInt(dataOffset, firstUsedPage);
1855 dataOffset += 4;
1856 umapBuf.put(dataOffset, (byte)1);
1857 dataOffset++;
1858 }
1859
1860
1861 ByteUtil.clearRange(umapBuf, dataOffset, (rowStart + umapRowLength));
1862
1863 rowStart -= umapRowLength;
1864 ++umapRowNum;
1865 }
1866
1867
1868 freeSpace -= totalUmapSpaceUsage;
1869 umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
1870 umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
1871 (short)umapRowNum);
1872 pageChannel.writePage(umapBuf, umapPageNumber);
1873
1874 return new AbstractMap.SimpleImmutableEntry<Integer,Integer>(
1875 umapPageNumber, firstRowNum);
1876 }
1877
1878 void collectUsageMapPages(Collection<Integer> pages) {
1879 pages.add(_ownedPages.getTablePageNumber());
1880 pages.add(_freeSpacePages.getTablePageNumber());
1881
1882 for(IndexData idx : _indexDatas) {
1883 idx.collectUsageMapPages(pages);
1884 }
1885
1886 for(ColumnImpl col : _columns) {
1887 col.collectUsageMapPages(pages);
1888 }
1889 }
1890
1891
1892
1893
1894 private static void writeTableDefinitionHeader(
1895 TableCreator creator, ByteBuffer buffer, int totalTableDefSize)
1896 {
1897 List<ColumnBuilder> columns = creator.getColumns();
1898
1899
1900 writeTablePageHeader(buffer);
1901 buffer.putInt(totalTableDefSize);
1902 buffer.putInt(MAGIC_TABLE_NUMBER);
1903 buffer.putInt(0);
1904 buffer.putInt(0);
1905 buffer.put((byte) 1);
1906
1907
1908 for (int i = 0; i < 15; i++) {
1909 buffer.put((byte) 0);
1910 }
1911 buffer.put(TYPE_USER);
1912 buffer.putShort((short) columns.size());
1913 buffer.putShort(ColumnImpl.countVariableLength(columns));
1914 buffer.putShort((short) columns.size());
1915 buffer.putInt(creator.getLogicalIndexCount());
1916 buffer.putInt(creator.getIndexCount());
1917 buffer.put((byte) 0);
1918 ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber());
1919 buffer.put((byte) 1);
1920 ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber());
1921 }
1922
1923
1924
1925
1926
1927 private static void writeTablePageHeader(ByteBuffer buffer)
1928 {
1929 buffer.put(PageTypes.TABLE_DEF);
1930 buffer.put((byte) 0x01);
1931
1932
1933 buffer.putShort((short) 0);
1934 buffer.putInt(0);
1935 }
1936
1937
1938
1939
1940
1941 static void writeName(ByteBuffer buffer, String name, Charset charset)
1942 {
1943 ByteBuffer encName = ColumnImpl.encodeUncompressedText(name, charset);
1944 buffer.putShort((short) encName.remaining());
1945 buffer.put(encName);
1946 }
1947
1948
1949
1950
1951
1952
1953 private static void createUsageMapDefinitionBuffer(TableCreator creator)
1954 throws IOException
1955 {
1956 List<ColumnBuilder> lvalCols = creator.getLongValueColumns();
1957
1958
1959 int indexUmapEnd = 2 + creator.getIndexCount();
1960 int umapNum = indexUmapEnd + (lvalCols.size() * 2);
1961
1962 JetFormat format = creator.getFormat();
1963 int umapRowLength = format.OFFSET_USAGE_MAP_START +
1964 format.USAGE_MAP_TABLE_BYTE_LENGTH;
1965 int umapSpaceUsage = getRowSpaceUsage(umapRowLength, format);
1966 PageChannel pageChannel = creator.getPageChannel();
1967 int umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
1968 ByteBuffer umapBuf = null;
1969 int freeSpace = 0;
1970 int rowStart = 0;
1971 int umapRowNum = 0;
1972
1973 for(int i = 0; i < umapNum; ++i) {
1974
1975 if(umapBuf == null) {
1976
1977
1978 if(umapPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1979
1980 umapPageNumber = creator.getUmapPageNumber();
1981 } else {
1982
1983 umapPageNumber = creator.reservePageNumber();
1984 }
1985
1986 freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE;
1987
1988 umapBuf = createUsageMapDefPage(pageChannel, freeSpace);
1989
1990 rowStart = findRowEnd(umapBuf, 0, format) - umapRowLength;
1991 umapRowNum = 0;
1992 }
1993
1994 umapBuf.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
1995
1996 if(i == 0) {
1997
1998
1999 umapBuf.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
2000
2001 } else if(i == 1) {
2002
2003
2004 umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
2005
2006 } else if(i < indexUmapEnd) {
2007
2008
2009 int indexIdx = i - 2;
2010 TableMutator.IndexDataState idxDataState =
2011 creator.getIndexDataStates().get(indexIdx);
2012
2013
2014 int rootPageNumber = pageChannel.allocateNewPage();
2015
2016
2017 idxDataState.setRootPageNumber(rootPageNumber);
2018 idxDataState.setUmapRowNumber((byte)umapRowNum);
2019 idxDataState.setUmapPageNumber(umapPageNumber);
2020
2021
2022 umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
2023 umapBuf.putInt(rowStart + 1, rootPageNumber);
2024 umapBuf.put(rowStart + 5, (byte)1);
2025
2026 } else {
2027
2028
2029 int lvalColIdx = i - indexUmapEnd;
2030 int umapType = lvalColIdx % 2;
2031 lvalColIdx /= 2;
2032
2033 ColumnBuilder lvalCol = lvalCols.get(lvalColIdx);
2034 TableMutator.ColumnState colState =
2035 creator.getColumnState(lvalCol);
2036
2037 umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE);
2038
2039 if((umapType == 1) &&
2040 (umapPageNumber != colState.getUmapPageNumber())) {
2041
2042
2043 --i;
2044 umapType = 0;
2045 }
2046
2047 if(umapType == 0) {
2048
2049 colState.setUmapOwnedRowNumber((byte)umapRowNum);
2050 colState.setUmapPageNumber(umapPageNumber);
2051 } else {
2052
2053 colState.setUmapFreeRowNumber((byte)umapRowNum);
2054 }
2055 }
2056
2057 rowStart -= umapRowLength;
2058 freeSpace -= umapSpaceUsage;
2059 ++umapRowNum;
2060
2061 if((freeSpace <= umapSpaceUsage) || (i == (umapNum - 1))) {
2062
2063 umapBuf.putShort(format.OFFSET_FREE_SPACE, (short)freeSpace);
2064 umapBuf.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
2065 (short)umapRowNum);
2066 pageChannel.writePage(umapBuf, umapPageNumber);
2067 umapBuf = null;
2068 }
2069 }
2070 }
2071
2072 private static ByteBuffer createUsageMapDefPage(
2073 PageChannel pageChannel, int freeSpace)
2074 {
2075 ByteBuffer umapBuf = pageChannel.createPageBuffer();
2076 umapBuf.put(PageTypes.DATA);
2077 umapBuf.put((byte) 0x1);
2078 umapBuf.putShort((short)freeSpace);
2079 umapBuf.putInt(0);
2080 umapBuf.putInt(0);
2081 umapBuf.putShort((short)0);
2082 return umapBuf;
2083 }
2084
2085
2086
2087
2088
2089 private ByteBuffer loadCompleteTableDefinitionBuffer(
2090 ByteBuffer tableBuffer, List<Integer> pages)
2091 throws IOException
2092 {
2093 int nextPage = tableBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
2094 ByteBuffer nextPageBuffer = null;
2095 while (nextPage != 0) {
2096 if(pages != null) {
2097 pages.add(nextPage);
2098 }
2099 if (nextPageBuffer == null) {
2100 nextPageBuffer = getPageChannel().createPageBuffer();
2101 }
2102 getPageChannel().readPage(nextPageBuffer, nextPage);
2103 nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
2104 tableBuffer = expandTableBuffer(tableBuffer);
2105 tableBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8);
2106 tableBuffer.flip();
2107 }
2108 return tableBuffer;
2109 }
2110
2111 private ByteBuffer expandTableBuffer(ByteBuffer tableBuffer) {
2112 ByteBuffer newBuffer = PageChannel.createBuffer(
2113 tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
2114 newBuffer.put(tableBuffer);
2115 return newBuffer;
2116 }
2117
2118 private void readColumnDefinitions(ByteBuffer tableBuffer, short columnCount)
2119 throws IOException
2120 {
2121 int colOffset = getFormat().OFFSET_INDEX_DEF_BLOCK +
2122 _indexCount * getFormat().SIZE_INDEX_DEFINITION;
2123
2124 tableBuffer.position(colOffset +
2125 (columnCount * getFormat().SIZE_COLUMN_HEADER));
2126 List<String> colNames = new ArrayList<>(columnCount);
2127 for (int i = 0; i < columnCount; i++) {
2128 colNames.add(readName(tableBuffer));
2129 }
2130
2131 int dispIndex = 0;
2132 for (int i = 0; i < columnCount; i++) {
2133 ColumnImpl column = ColumnImpl.create(this, tableBuffer,
2134 colOffset + (i * getFormat().SIZE_COLUMN_HEADER), colNames.get(i),
2135 dispIndex++);
2136 _columns.add(column);
2137 if(column.isVariableLength()) {
2138
2139
2140 _varColumns.add(column);
2141 }
2142 }
2143
2144 Collections.sort(_columns, ColumnImpl.DEFAULT_ORDER_COMPARATOR);
2145 initAutoNumberColumns();
2146 initCalculatedColumns();
2147
2148
2149 int colIdx = 0;
2150 for(ColumnImpl col : _columns) {
2151 col.setColumnIndex(colIdx++);
2152 }
2153
2154
2155
2156 Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
2157 }
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168 private void validateColumnDefs() {
2169 int maxRowSize = getFormat().MAX_ROW_SIZE;
2170
2171 for(ColumnImpl col : _columns) {
2172
2173 if(col.getColumnNumber() >= _maxColumnCount) {
2174 setWriteDefError("column " + col.getName() + " has number " +
2175 col.getColumnNumber() + ", which is outside the " +
2176 "table column count " + _maxColumnCount);
2177 } else if(col.isVariableLength()) {
2178 if(col.getVarLenTableIndex() >= _maxVarColumnCount) {
2179 setWriteDefError("variable length column " + col.getName() +
2180 " has offset index " + col.getVarLenTableIndex() +
2181 ", which is outside the table variable length " +
2182 "column count " + _maxVarColumnCount);
2183 }
2184 } else if((col.getFixedDataOffset() + col.getLength()) > maxRowSize) {
2185 setWriteDefError("fixed length column " + col.getName() +
2186 " ends at offset " +
2187 (col.getFixedDataOffset() + col.getLength()) +
2188 ", which is beyond the maximum row size " +
2189 maxRowSize);
2190 }
2191 }
2192 }
2193
2194
2195
2196
2197 private void setWriteDefError(String reason) {
2198 if(_writeDefError != null) {
2199 return;
2200 }
2201 _writeDefError = "Table definition is corrupt, " + reason;
2202 LOG.log(Logger.Level.WARNING, withErrorContext(
2203 _writeDefError + ". Table is read-only"));
2204 }
2205
2206 private void readIndexDefinitions(ByteBuffer tableBuffer) throws IOException
2207 {
2208
2209 for (int i = 0; i < _indexCount; i++) {
2210 IndexData idxData = _indexDatas.get(i);
2211 idxData.read(tableBuffer, _columns);
2212
2213 for(IndexData.ColumnDescriptor iCol : idxData.getColumns()) {
2214 _indexColumns.add(iCol.getColumn());
2215 }
2216 }
2217
2218
2219 for (int i = 0; i < _logicalIndexCount; i++) {
2220 _indexes.add(new IndexImpl(tableBuffer, _indexDatas, getFormat()));
2221 }
2222
2223
2224 for (int i = 0; i < _logicalIndexCount; i++) {
2225 _indexes.get(i).setName(readName(tableBuffer));
2226 }
2227
2228 Collections.sort(_indexes, IndexImpl.DEFAULT_ORDER_COMPARATOR);
2229 }
2230
2231 private boolean readColumnUsageMaps(ByteBuffer tableBuffer)
2232 throws IOException
2233 {
2234 short umapColNum = tableBuffer.getShort();
2235 if(umapColNum == IndexData.COLUMN_UNUSED) {
2236 return false;
2237 }
2238
2239 int pos = tableBuffer.position();
2240 int nextPos = pos + (2 * UsageMap.POINTER_SIZE);
2241 if(nextPos > tableBuffer.limit()) {
2242 throw new IOException(withErrorContext(
2243 "Table definition ends inside the usage map pointers of column " +
2244 umapColNum));
2245 }
2246
2247 UsageMap colOwnedPages = null;
2248 UsageMap colFreeSpacePages = null;
2249 try {
2250 colOwnedPages = UsageMap.read(getDatabase(), tableBuffer);
2251 colFreeSpacePages = UsageMap.read(getDatabase(), tableBuffer);
2252 } catch(IOException | IllegalStateException e) {
2253
2254
2255
2256 colOwnedPages = null;
2257 colFreeSpacePages = null;
2258 tableBuffer.position(nextPos);
2259 LOG.log(Logger.Level.WARNING, withErrorContext("Invalid column " + umapColNum +
2260 " usage map definition: " + e));
2261 }
2262
2263 for(ColumnImpl col : _columns) {
2264 if(col.getColumnNumber() == umapColNum) {
2265 col.setUsageMaps(colOwnedPages, colFreeSpacePages);
2266 break;
2267 }
2268 }
2269
2270 return true;
2271 }
2272
2273
2274
2275
2276
2277 private void writeDataPage(ByteBuffer pageBuffer, int pageNumber)
2278 throws IOException
2279 {
2280
2281 getPageChannel().writePage(pageBuffer, pageNumber);
2282
2283
2284
2285 _addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
2286
2287
2288
2289 ++_modCount;
2290 }
2291
2292
2293
2294
2295
2296
2297 private String readName(ByteBuffer buffer) {
2298 int nameLength = readNameLength(buffer);
2299 byte[] nameBytes = ByteUtil.getBytes(buffer, nameLength);
2300 return ColumnImpl.decodeUncompressedText(nameBytes,
2301 getDatabase().getCharset());
2302 }
2303
2304
2305
2306
2307 private int readNameLength(ByteBuffer buffer) {
2308 return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
2309 }
2310
2311 @Override
2312 public Object[] asRow(Map<String,?> rowMap) {
2313 return asRow(rowMap, null, false);
2314 }
2315
2316
2317
2318
2319
2320
2321
2322
2323 public Object[] asRowWithRowId(Map<String,?> rowMap) {
2324 return asRow(rowMap, null, true);
2325 }
2326
2327 @Override
2328 public Object[] asUpdateRow(Map<String,?> rowMap) {
2329 return asRow(rowMap, Column.KEEP_VALUE, false);
2330 }
2331
2332
2333
2334
2335
2336
2337 public RowId getRowId(Object[] row) {
2338 return (RowId)row[_columns.size()];
2339 }
2340
2341
2342
2343
2344 private Object[] asRow(Map<String,?> rowMap, Object defaultValue,
2345 boolean returnRowId)
2346 {
2347 int len = _columns.size();
2348 if(returnRowId) {
2349 ++len;
2350 }
2351 Object[] row = new Object[len];
2352 if(defaultValue != null) {
2353 Arrays.fill(row, defaultValue);
2354 }
2355 if(returnRowId) {
2356 row[len - 1] = ColumnImpl.RETURN_ROW_ID;
2357 }
2358 if(rowMap == null) {
2359 return row;
2360 }
2361 for(ColumnImpl col : _columns) {
2362 if(rowMap.containsKey(col.getName())) {
2363 col.setRowValue(row, col.getRowValue(rowMap));
2364 }
2365 }
2366 return row;
2367 }
2368
2369 @Override
2370 public Object[] addRow(Object... row) throws IOException {
2371 return addRows(Collections.singletonList(row), false).get(0);
2372 }
2373
2374 @Override
2375 public <M extends Map<String,Object>> M addRowFromMap(M row)
2376 throws IOException
2377 {
2378 Object[] rowValues = asRow(row);
2379
2380 addRow(rowValues);
2381
2382 returnRowValues(row, rowValues, _columns);
2383 return row;
2384 }
2385
2386 @Override
2387 public List<? extends Object[]> addRows(List<? extends Object[]> rows)
2388 throws IOException
2389 {
2390 return addRows(rows, true);
2391 }
2392
2393 @Override
2394 public <M extends Map<String,Object>> List<M> addRowsFromMaps(List<M> rows)
2395 throws IOException
2396 {
2397 List<Object[]> rowValuesList = new ArrayList<>(rows.size());
2398 for(Map<String,Object> row : rows) {
2399 rowValuesList.add(asRow(row));
2400 }
2401
2402 addRows(rowValuesList);
2403
2404 for(int i = 0; i < rowValuesList.size(); ++i) {
2405 Map<String,Object> row = rows.get(i);
2406 Object[] rowValues = rowValuesList.get(i);
2407 returnRowValues(row, rowValues, _columns);
2408 }
2409 return rows;
2410 }
2411
2412 private static void returnRowValues(Map<String,Object> row, Object[] rowValues,
2413 List<ColumnImpl> cols)
2414 {
2415 for(ColumnImpl col : cols) {
2416 col.setRowValue(row, col.getRowValue(rowValues));
2417 }
2418 }
2419
2420
2421
2422
2423
2424
2425 protected List<? extends Object[]> addRows(List<? extends Object[]> rows,
2426 final boolean isBatchWrite)
2427 throws IOException
2428 {
2429 if(rows.isEmpty()) {
2430 return rows;
2431 }
2432
2433 getPageChannel().startWrite();
2434 try {
2435
2436 ByteBuffer dataPage = null;
2437 int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
2438 int updateCount = 0;
2439 int autoNumAssignCount = 0;
2440 WriteRowState writeRowState =
2441 (!_autoNumColumns.isEmpty() ? new WriteRowState() : null);
2442 try {
2443
2444 List<Object[]> dupeRows = null;
2445 final int numCols = _columns.size();
2446 for (int i = 0; i < rows.size(); i++) {
2447
2448
2449
2450
2451
2452
2453 Object[] row = rows.get(i);
2454 if((row.length < numCols) || (row.getClass() != Object[].class)) {
2455 row = dupeRow(row, numCols);
2456
2457
2458 if(dupeRows == null) {
2459 dupeRows = new ArrayList<>(rows);
2460 rows = dupeRows;
2461 }
2462
2463 dupeRows.set(i, row);
2464 }
2465
2466
2467 for(ColumnImpl column : _columns) {
2468 if(!column.isAutoNumber()) {
2469 Object val = column.getRowValue(row);
2470 if(val == null) {
2471 val = column.generateDefaultValue();
2472 }
2473
2474 column.setRowValue(row, column.validate(val));
2475 }
2476 }
2477
2478
2479 handleAutoNumbersForAdd(row, writeRowState);
2480 ++autoNumAssignCount;
2481
2482
2483
2484 _calcColEval.calculate(row);
2485
2486
2487 if(_rowValidator != null) {
2488 _rowValidator.validate(row);
2489 }
2490
2491
2492 ByteBuffer rowData = createRow(
2493 row, _writeRowBufferH.getPageBuffer(getPageChannel()));
2494
2495 int rowSize = rowData.remaining();
2496 if (rowSize > getFormat().MAX_ROW_SIZE) {
2497 throw createTooLargeException(rowSize);
2498 }
2499
2500
2501 dataPage = findFreeRowSpace(rowSize, dataPage, pageNumber);
2502 pageNumber = _addRowBufferH.getPageNumber();
2503
2504
2505 int rowNum = getRowsOnDataPage(dataPage, getFormat());
2506
2507 RowIdImplss/impl/RowIdImpl.html#RowIdImpl">RowIdImpl rowId = new RowIdImpl(pageNumber, rowNum);
2508
2509
2510
2511 if(!_indexDatas.isEmpty()) {
2512
2513 IndexData.PendingChange idxChange = null;
2514 try {
2515
2516
2517 _fkEnforcer.addRow(row);
2518
2519
2520 for(IndexData indexData : _indexDatas) {
2521 idxChange = indexData.prepareAddRow(row, rowId, idxChange);
2522 }
2523
2524
2525 IndexData.commitAll(idxChange);
2526
2527 } catch(ConstraintViolationException ce) {
2528 IndexData.rollbackAll(idxChange);
2529 throw ce;
2530 }
2531 }
2532
2533
2534 addDataPageRow(dataPage, rowSize, getFormat(), 0);
2535 dataPage.put(rowData);
2536
2537
2538 if((row.length > numCols) &&
2539 (row[numCols] == ColumnImpl.RETURN_ROW_ID)) {
2540 row[numCols] = rowId;
2541 }
2542
2543 ++updateCount;
2544 }
2545
2546 writeDataPage(dataPage, pageNumber);
2547
2548
2549 updateTableDefinition(rows.size());
2550
2551 } catch(Exception rowWriteFailure) {
2552
2553 boolean isWriteFailure = isWriteFailure(rowWriteFailure);
2554
2555 if(!isWriteFailure && (autoNumAssignCount > updateCount)) {
2556
2557
2558 restoreAutoNumbersFromAdd(rows.get(autoNumAssignCount - 1));
2559 }
2560
2561 if(!isBatchWrite) {
2562
2563 if(rowWriteFailure instanceof IOException) {
2564 throw (IOException)rowWriteFailure;
2565 }
2566 throw (RuntimeException)rowWriteFailure;
2567 }
2568
2569
2570 if(isWriteFailure) {
2571
2572
2573
2574 updateCount = 0;
2575
2576 } else if(updateCount > 0) {
2577
2578
2579 try {
2580
2581 writeDataPage(dataPage, pageNumber);
2582
2583
2584 updateTableDefinition(updateCount);
2585
2586 } catch(Exception flushFailure) {
2587
2588
2589
2590
2591 LOG.log(Logger.Level.WARNING, withErrorContext(
2592 "Secondary row failure which preceded the write failure"),
2593 rowWriteFailure);
2594 updateCount = 0;
2595 rowWriteFailure = flushFailure;
2596 }
2597 }
2598
2599 throw new BatchUpdateException(
2600 updateCount, withErrorContext("Failed adding rows"),
2601 rowWriteFailure);
2602 }
2603
2604 } finally {
2605 getPageChannel().finishWrite();
2606 }
2607
2608 return rows;
2609 }
2610
2611 private static boolean isWriteFailure(Throwable t) {
2612 while(t != null) {
2613 if((t instanceof IOException) && !(t instanceof JackcessException)) {
2614 return true;
2615 }
2616 t = t.getCause();
2617 }
2618
2619 return false;
2620 }
2621
2622 @Override
2623 public Rowf="../../../../com/healthmarketscience/jackcess/Row.html#Row">Row updateRow(Row row) throws IOException {
2624 return updateRowFromMap(
2625 getDefaultCursor().getRowState(), (RowIdImpl)row.getId(), row);
2626 }
2627
2628
2629
2630
2631
2632
2633
2634
2635 public Object[] updateRow(RowId rowId, Object... row) throws IOException {
2636 return updateRow(
2637 getDefaultCursor().getRowState(), (RowIdImpl)rowId, row);
2638 }
2639
2640
2641
2642
2643
2644
2645
2646 public void updateValue(Column column, RowId rowId, Object value)
2647 throws IOException
2648 {
2649 Object[] row = new Object[_columns.size()];
2650 Arrays.fill(row, Column.KEEP_VALUE);
2651 column.setRowValue(row, value);
2652
2653 updateRow(rowId, row);
2654 }
2655
2656 public <M extends Map<String,Object>> M updateRowFromMap(
2657 RowState rowState, RowIdImpl rowId, M row)
2658 throws IOException
2659 {
2660 Object[] rowValues = updateRow(rowState, rowId, asUpdateRow(row));
2661 returnRowValues(row, rowValues, _columns);
2662 return row;
2663 }
2664
2665
2666
2667
2668
2669 public Object[] updateRow(RowState rowState, RowIdImpl rowId, Object... row)
2670 throws IOException
2671 {
2672 requireValidRowId(rowId);
2673
2674 getPageChannel().startWrite();
2675 try {
2676
2677
2678 ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
2679 int oldRowSize = rowBuffer.remaining();
2680
2681 requireNonDeletedRow(rowState, rowId);
2682
2683
2684
2685 if((row.length < _columns.size()) || (row.getClass() != Object[].class)) {
2686 row = dupeRow(row, _columns.size());
2687 }
2688
2689
2690
2691
2692 Map<ColumnImpl,byte[]> keepRawVarValues =
2693 (!_varColumns.isEmpty() ? new HashMap<>() : null);
2694
2695
2696 for(ColumnImpl column : _columns) {
2697
2698 if(column.isAutoNumber()) {
2699
2700 continue;
2701 }
2702
2703 Object rowValue = column.getRowValue(row);
2704 if(rowValue == Column.KEEP_VALUE) {
2705
2706
2707
2708
2709
2710
2711
2712
2713 Map<ColumnImpl,byte[]> rawValues =
2714 (((keepRawVarValues != null) && column.isVariableLength() &&
2715 !_calcColEval.willCalculate(column)) ? keepRawVarValues : null);
2716
2717 if((rawValues != null) &&
2718 (rowState.getRowCacheValue(column.getColumnIndex()) != null)) {
2719
2720
2721 byte[] oldRawValue = readRawRowValue(rowBuffer, column, rowState);
2722 if(oldRawValue != null) {
2723 rawValues.put(column, oldRawValue);
2724 }
2725 }
2726
2727
2728 rowValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
2729 rawValues);
2730
2731 } else {
2732
2733
2734 Object oldValue = Column.KEEP_VALUE;
2735 if(_indexColumns.contains(column)) {
2736
2737 oldValue = getRowColumn(getFormat(), rowBuffer, column, rowState,
2738 null);
2739 } else {
2740 oldValue = rowState.getRowCacheValue(column.getColumnIndex());
2741 }
2742
2743
2744 if(oldValue != rowValue) {
2745
2746 rowValue = column.validate(rowValue);
2747 }
2748 }
2749
2750 column.setRowValue(row, rowValue);
2751 }
2752
2753
2754 handleAutoNumbersForUpdate(row, rowBuffer, rowState);
2755
2756
2757
2758 _calcColEval.calculate(row);
2759
2760
2761 if(_rowValidator != null) {
2762 _rowValidator.validate(row);
2763 }
2764
2765
2766 ByteBuffer newRowData = createRow(
2767 row, _writeRowBufferH.getPageBuffer(getPageChannel()), oldRowSize,
2768 keepRawVarValues);
2769
2770 if (newRowData.limit() > getFormat().MAX_ROW_SIZE) {
2771 throw createTooLargeException(newRowData.limit());
2772 }
2773
2774 if(!_indexDatas.isEmpty()) {
2775
2776 IndexData.PendingChange idxChange = null;
2777 try {
2778
2779 Object[] oldRowValues = rowState.getRowCacheValues();
2780
2781
2782 _fkEnforcer.updateRow(oldRowValues, row);
2783
2784
2785 for(IndexData indexData : _indexDatas) {
2786 idxChange = indexData.prepareUpdateRow(oldRowValues, rowId, row,
2787 idxChange);
2788 }
2789
2790
2791 IndexData.commitAll(idxChange);
2792
2793 } catch(ConstraintViolationException ce) {
2794 IndexData.rollbackAll(idxChange);
2795 throw ce;
2796 }
2797 }
2798
2799
2800 rowBuffer.reset();
2801 int rowSize = newRowData.remaining();
2802
2803 ByteBuffer dataPage = null;
2804 int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
2805
2806 if(oldRowSize >= rowSize) {
2807
2808
2809 rowBuffer.put(newRowData);
2810
2811
2812 dataPage = rowState.getFinalPage();
2813 pageNumber = rowState.getFinalRowId().getPageNumber();
2814
2815 } else {
2816
2817
2818 dataPage = findFreeRowSpace(rowSize, null,
2819 PageChannel.INVALID_PAGE_NUMBER);
2820 pageNumber = _addRowBufferH.getPageNumber();
2821
2822 RowIdImpl headerRowId = rowState.getHeaderRowId();
2823 ByteBuffer headerPage = rowState.getHeaderPage();
2824 if(pageNumber == headerRowId.getPageNumber()) {
2825
2826 dataPage = headerPage;
2827 }
2828
2829
2830
2831 int rowNum = addDataPageRow(dataPage, rowSize, getFormat(),
2832 DELETED_ROW_MASK);
2833 dataPage.put(newRowData);
2834
2835
2836
2837 rowBuffer = PageChannel.narrowBuffer(
2838 headerPage,
2839 findRowStart(headerPage, headerRowId.getRowNumber(), getFormat()),
2840 findRowEnd(headerPage, headerRowId.getRowNumber(), getFormat()));
2841 rowBuffer.put((byte)rowNum);
2842 ByteUtil.put3ByteInt(rowBuffer, pageNumber);
2843 ByteUtil.clearRemaining(rowBuffer);
2844
2845
2846 int headerRowIndex = getRowStartOffset(headerRowId.getRowNumber(),
2847 getFormat());
2848 headerPage.putShort(headerRowIndex,
2849 (short)(headerPage.getShort(headerRowIndex)
2850 | OVERFLOW_ROW_MASK));
2851 if(pageNumber != headerRowId.getPageNumber()) {
2852 writeDataPage(headerPage, headerRowId.getPageNumber());
2853 }
2854 }
2855
2856 writeDataPage(dataPage, pageNumber);
2857
2858 updateTableDefinition(0);
2859
2860 } finally {
2861 getPageChannel().finishWrite();
2862 }
2863
2864 return row;
2865 }
2866
2867 private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
2868 int pageNumber)
2869 throws IOException
2870 {
2871
2872 boolean modifiedPage = true;
2873
2874 if(dataPage == null) {
2875
2876
2877 dataPage = findFreeRowSpace(_ownedPages, _freeSpacePages,
2878 _addRowBufferH);
2879
2880 if(dataPage == null) {
2881
2882 return newDataPage();
2883 }
2884
2885
2886 pageNumber = _addRowBufferH.getPageNumber();
2887
2888 modifiedPage = false;
2889 }
2890
2891 if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
2892
2893
2894 if(modifiedPage) {
2895 writeDataPage(dataPage, pageNumber);
2896 }
2897 _freeSpacePages.removePageNumber(pageNumber);
2898
2899 dataPage = newDataPage();
2900 }
2901
2902 return dataPage;
2903 }
2904
2905 static ByteBuffer findFreeRowSpace(
2906 UsageMap./../../com/healthmarketscience/jackcess/impl/UsageMap.html#UsageMap">UsageMap ownedPages, UsageMap freeSpacePages,
2907 TempPageHolder rowBufferH)
2908 throws IOException
2909 {
2910
2911
2912 UsageMap.PageCursor revPageCursor = ownedPages.cursor();
2913 revPageCursor.afterLast();
2914 while(true) {
2915 int tmpPageNumber = revPageCursor.getPreviousPage();
2916 if(tmpPageNumber < 0) {
2917 break;
2918 }
2919
2920 if(!freeSpacePages.containsPageNumber(tmpPageNumber)) {
2921 continue;
2922 }
2923 ByteBuffer dataPage = rowBufferH.setPage(ownedPages.getPageChannel(),
2924 tmpPageNumber);
2925 if(dataPage.get() == PageTypes.DATA) {
2926
2927 return dataPage;
2928 }
2929 }
2930
2931 return null;
2932 }
2933
2934
2935
2936
2937 private void updateTableDefinition(int rowCountInc) throws IOException
2938 {
2939
2940 ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
2941 _tableDefPageNumber);
2942
2943
2944 _rowCount += rowCountInc;
2945 tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
2946 tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastLongAutoNumber);
2947 int ctypeOff = getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER;
2948 if(ctypeOff >= 0) {
2949 tdefPage.putInt(ctypeOff, _lastComplexTypeAutoNumber);
2950 }
2951
2952
2953 for (IndexData indexData : _indexDatas) {
2954
2955
2956 tdefPage.putInt(indexData.getUniqueEntryCountOffset(),
2957 indexData.getUniqueEntryCount());
2958
2959 indexData.update();
2960 }
2961
2962
2963 getPageChannel().writePage(tdefPage, _tableDefPageNumber);
2964 }
2965
2966
2967
2968
2969
2970 private ByteBuffer newDataPage() throws IOException {
2971 ByteBuffer dataPage = _addRowBufferH.setNewPage(getPageChannel());
2972 dataPage.put(PageTypes.DATA);
2973 dataPage.put((byte) 1);
2974 dataPage.putShort((short)getFormat().DATA_PAGE_INITIAL_FREE_SPACE);
2975 dataPage.putInt(_tableDefPageNumber);
2976
2977
2978 dataPage.putInt(0);
2979 dataPage.putShort((short)0);
2980 int pageNumber = _addRowBufferH.getPageNumber();
2981 getPageChannel().writePage(dataPage, pageNumber);
2982 _ownedPages.addPageNumber(pageNumber);
2983 _freeSpacePages.addPageNumber(pageNumber);
2984 return dataPage;
2985 }
2986
2987
2988 protected ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
2989 throws IOException
2990 {
2991 return createRow(rowArray, buffer, 0,
2992 Collections.<ColumnImpl,byte[]>emptyMap());
2993 }
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005 private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
3006 int minRowSize,
3007 Map<ColumnImpl,byte[]> rawVarValues)
3008 throws IOException
3009 {
3010 if(_writeDefError != null) {
3011
3012
3013 throw new JackcessException(withErrorContext(_writeDefError));
3014 }
3015
3016 buffer.putShort(_maxColumnCount);
3017 NullMask/impl/NullMask.html#NullMask">NullMask nullMask = new NullMask(_maxColumnCount);
3018
3019
3020 int fixedDataStart = buffer.position();
3021 int fixedDataEnd = fixedDataStart;
3022 for (ColumnImpl col : _columns) {
3023
3024 if(col.isVariableLength()) {
3025 continue;
3026 }
3027
3028 Object rowValue = col.getRowValue(rowArray);
3029
3030 if (col.storeInNullMask()) {
3031
3032 if(col.writeToNullMask(rowValue)) {
3033 nullMask.markNotNull(col);
3034 }
3035 rowValue = null;
3036 }
3037
3038 if(rowValue != null) {
3039
3040
3041 nullMask.markNotNull(col);
3042
3043
3044 buffer.position(fixedDataStart + col.getFixedDataOffset());
3045 buffer.put(col.write(rowValue, 0));
3046 }
3047
3048
3049
3050
3051 buffer.position(fixedDataStart + col.getFixedDataOffset() +
3052 col.getLength());
3053
3054
3055 if(buffer.position() > fixedDataEnd) {
3056 fixedDataEnd = buffer.position();
3057 }
3058
3059 }
3060
3061
3062 buffer.position(fixedDataEnd);
3063
3064
3065 if(_maxVarColumnCount > 0) {
3066
3067 int maxRowSize = getFormat().MAX_ROW_SIZE;
3068
3069
3070
3071 maxRowSize -= buffer.position();
3072
3073 int trailerSize = (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
3074 maxRowSize -= trailerSize;
3075
3076
3077
3078
3079 for (ColumnImpl varCol : _varColumns) {
3080 if((varCol.getType().isLongValue()) &&
3081 (varCol.getRowValue(rowArray) != null)) {
3082 maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
3083 }
3084 }
3085
3086
3087 short[] varColumnOffsets = new short[_maxVarColumnCount];
3088 int varColumnOffsetsIndex = 0;
3089 for (ColumnImpl varCol : _varColumns) {
3090 short offset = (short) buffer.position();
3091 Object rowValue = varCol.getRowValue(rowArray);
3092 if (rowValue != null) {
3093
3094 nullMask.markNotNull(varCol);
3095
3096 byte[] rawValue = null;
3097 ByteBuffer varDataBuf = null;
3098 if(((rawValue = rawVarValues.get(varCol)) != null) &&
3099 (rawValue.length <= maxRowSize)) {
3100
3101 varDataBuf = ByteBuffer.wrap(rawValue);
3102 } else {
3103
3104 varDataBuf = varCol.write(rowValue, maxRowSize);
3105 }
3106
3107 maxRowSize -= varDataBuf.remaining();
3108 if(varCol.getType().isLongValue()) {
3109
3110
3111 maxRowSize += getFormat().SIZE_LONG_VALUE_DEF;
3112 }
3113 try {
3114 buffer.put(varDataBuf);
3115 } catch(BufferOverflowException e) {
3116
3117
3118 throw createTooLargeException(buffer.limit());
3119 }
3120 }
3121
3122
3123 while(varColumnOffsetsIndex <= varCol.getVarLenTableIndex()) {
3124 varColumnOffsets[varColumnOffsetsIndex++] = offset;
3125 }
3126 }
3127
3128
3129 while(varColumnOffsetsIndex < varColumnOffsets.length) {
3130 varColumnOffsets[varColumnOffsetsIndex++] = (short) buffer.position();
3131 }
3132
3133
3134 int eod = buffer.position();
3135
3136 if(buffer.remaining() < trailerSize) {
3137
3138 throw createTooLargeException(eod + trailerSize);
3139 }
3140
3141
3142 padRowBuffer(buffer, minRowSize, trailerSize);
3143
3144 buffer.putShort((short) eod);
3145
3146
3147
3148 for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
3149 buffer.putShort(varColumnOffsets[i]);
3150 }
3151 buffer.putShort(_maxVarColumnCount);
3152
3153 } else {
3154
3155
3156 padRowBuffer(buffer, minRowSize, nullMask.byteSize());
3157 }
3158
3159 nullMask.write(buffer);
3160 buffer.flip();
3161 return buffer;
3162 }
3163
3164 private InvalidValueException createTooLargeException(int size) {
3165 return new InvalidValueException(
3166 withErrorContext(
3167 "Row size " + size + " is too large (max " +
3168 getFormat().MAX_ROW_SIZE + ")"));
3169 }
3170
3171
3172
3173
3174 private void handleAutoNumbersForAdd(Object[] row, WriteRowState writeRowState)
3175 throws IOException
3176 {
3177 if(_autoNumColumns.isEmpty()) {
3178 return;
3179 }
3180
3181 boolean enableInsert = isAllowAutoNumberInsert();
3182 writeRowState.resetAutoNumber();
3183 for(ColumnImpl col : _autoNumColumns) {
3184
3185
3186
3187 Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
3188
3189 ColumnImpl.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
3190 Object rowValue = ((inRowValue == null) ?
3191 autoNumGen.getNext(writeRowState) :
3192 autoNumGen.handleInsert(writeRowState, inRowValue));
3193
3194 col.setRowValue(row, rowValue);
3195 }
3196 }
3197
3198
3199
3200
3201 private void handleAutoNumbersForUpdate(Object[] row, ByteBuffer rowBuffer,
3202 RowState rowState)
3203 throws IOException
3204 {
3205 if(_autoNumColumns.isEmpty()) {
3206 return;
3207 }
3208
3209 boolean enableInsert = isAllowAutoNumberInsert();
3210 rowState.resetAutoNumber();
3211 for(ColumnImpl col : _autoNumColumns) {
3212
3213
3214
3215 Object inRowValue = getInputAutoNumberRowValue(enableInsert, col, row);
3216
3217 Object rowValue =
3218 ((inRowValue == null) ?
3219 getRowColumn(getFormat(), rowBuffer, col, rowState, null) :
3220 col.getAutoNumberGenerator().handleInsert(rowState, inRowValue));
3221
3222 col.setRowValue(row, rowValue);
3223 }
3224 }
3225
3226
3227
3228
3229
3230 private static Object getInputAutoNumberRowValue(
3231 boolean enableInsert, ColumnImpl col, Object[] row)
3232 {
3233 if(!enableInsert) {
3234 return null;
3235 }
3236
3237 Object inRowValue = col.getRowValue(row);
3238 if((inRowValue == Column.KEEP_VALUE) || (inRowValue == Column.AUTO_NUMBER)) {
3239
3240 inRowValue = null;
3241 }
3242 return inRowValue;
3243 }
3244
3245
3246
3247
3248 private void restoreAutoNumbersFromAdd(Object[] row)
3249 {
3250 if(_autoNumColumns.isEmpty()) {
3251 return;
3252 }
3253
3254 for(ColumnImpl col : _autoNumColumns) {
3255
3256 col.getAutoNumberGenerator().restoreLast(col.getRowValue(row));
3257 }
3258 }
3259
3260 private static void padRowBuffer(ByteBuffer buffer, int minRowSize,
3261 int trailerSize)
3262 {
3263 int pos = buffer.position();
3264 if((pos + trailerSize) < minRowSize) {
3265
3266 int padSize = minRowSize - (pos + trailerSize);
3267 ByteUtil.clearRange(buffer, pos, pos + padSize);
3268 ByteUtil.forward(buffer, padSize);
3269 }
3270 }
3271
3272 @Override
3273 public int getRowCount() {
3274 return _rowCount;
3275 }
3276
3277 int getNextLongAutoNumber() {
3278
3279 return ++_lastLongAutoNumber;
3280 }
3281
3282 int getLastLongAutoNumber() {
3283
3284 return _lastLongAutoNumber;
3285 }
3286
3287 void adjustLongAutoNumber(int inLongAutoNumber) {
3288 if(inLongAutoNumber > _lastLongAutoNumber) {
3289 _lastLongAutoNumber = inLongAutoNumber;
3290 }
3291 }
3292
3293 void restoreLastLongAutoNumber(int lastLongAutoNumber) {
3294
3295 _lastLongAutoNumber = lastLongAutoNumber - 1;
3296 }
3297
3298 int getNextComplexTypeAutoNumber() {
3299
3300 return ++_lastComplexTypeAutoNumber;
3301 }
3302
3303 int getLastComplexTypeAutoNumber() {
3304
3305 return _lastComplexTypeAutoNumber;
3306 }
3307
3308 void adjustComplexTypeAutoNumber(int inComplexTypeAutoNumber) {
3309 if(inComplexTypeAutoNumber > _lastComplexTypeAutoNumber) {
3310 _lastComplexTypeAutoNumber = inComplexTypeAutoNumber;
3311 }
3312 }
3313
3314 void restoreLastComplexTypeAutoNumber(int lastComplexTypeAutoNumber) {
3315
3316 _lastComplexTypeAutoNumber = lastComplexTypeAutoNumber - 1;
3317 }
3318
3319 @Override
3320 public String toString() {
3321 return ToStringBuilder.builder(this)
3322 .append("type", (_tableType + (!isSystem() ? " (USER)" : " (SYSTEM)")))
3323 .append("name", _name)
3324 .append("rowCount", _rowCount)
3325 .append("columnCount", _columns.size())
3326 .append("indexCount(data)", _indexCount)
3327 .append("logicalIndexCount", _logicalIndexCount)
3328 .appendIfNotNull("validator", _rowValidator)
3329 .append("columns", _columns)
3330 .append("indexes", _indexes)
3331 .append("ownedPages", _ownedPages)
3332 .toString();
3333 }
3334
3335
3336
3337
3338
3339
3340 public String display() throws IOException {
3341 return display(Long.MAX_VALUE);
3342 }
3343
3344
3345
3346
3347
3348
3349
3350 public String display(long limit) throws IOException {
3351 reset();
3352 StringWriter rtn = new StringWriter();
3353 new ExportUtil.Builder(getDefaultCursor()).setDelimiter("\t").setHeader(true)
3354 .exportWriter(new BufferedWriter(rtn));
3355 return rtn.toString();
3356 }
3357
3358
3359
3360
3361
3362
3363
3364 public static int addDataPageRow(ByteBuffer dataPage,
3365 int rowSize,
3366 JetFormat format,
3367 int rowFlags)
3368 {
3369 int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
3370
3371
3372 short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
3373 dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
3374 rowSpaceUsage));
3375
3376
3377 short rowCount = dataPage.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
3378 dataPage.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
3379 (short) (rowCount + 1));
3380
3381
3382 short rowLocation = findRowEnd(dataPage, rowCount, format);
3383 rowLocation -= rowSize;
3384
3385
3386 dataPage.putShort(getRowStartOffset(rowCount, format),
3387 (short)(rowLocation | rowFlags));
3388
3389
3390 dataPage.position(rowLocation);
3391
3392 return rowCount;
3393 }
3394
3395
3396
3397
3398
3399 static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
3400 {
3401 int rowsOnPage = 0;
3402 if((rowBuffer != null) && (rowBuffer.get(0) == PageTypes.DATA)) {
3403 rowsOnPage = rowBuffer.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
3404 }
3405 return rowsOnPage;
3406 }
3407
3408
3409
3410
3411 private void requireValidRowId(RowIdImpl rowId) {
3412 if(!rowId.isValid()) {
3413 throw new IllegalArgumentException(withErrorContext(
3414 "Given rowId is invalid: " + rowId));
3415 }
3416 }
3417
3418
3419
3420
3421 private void requireNonDeletedRow(RowState rowState, RowIdImpl rowId)
3422 {
3423 if(!rowState.isValid()) {
3424 throw new IllegalArgumentException(withErrorContext(
3425 "Given rowId is invalid for this table: " + rowId));
3426 }
3427 if(rowState.isDeleted()) {
3428 throw new IllegalStateException(withErrorContext(
3429 "Row is deleted: " + rowId));
3430 }
3431 }
3432
3433
3434
3435
3436 public static boolean isDeletedRow(short rowStart) {
3437 return ((rowStart & DELETED_ROW_MASK) != 0);
3438 }
3439
3440
3441
3442
3443 public static boolean isOverflowRow(short rowStart) {
3444 return ((rowStart & OVERFLOW_ROW_MASK) != 0);
3445 }
3446
3447
3448
3449
3450 public static short cleanRowStart(short rowStart) {
3451 return (short)(rowStart & OFFSET_MASK);
3452 }
3453
3454
3455
3456
3457 public static short findRowStart(ByteBuffer buffer, int rowNum,
3458 JetFormat format)
3459 {
3460 return cleanRowStart(
3461 buffer.getShort(getRowStartOffset(rowNum, format)));
3462 }
3463
3464
3465
3466
3467 public static int getRowStartOffset(int rowNum, JetFormat format)
3468 {
3469 return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
3470 }
3471
3472
3473
3474
3475 public static short findRowEnd(ByteBuffer buffer, int rowNum,
3476 JetFormat format)
3477 {
3478 return (short)((rowNum == 0) ?
3479 format.PAGE_SIZE :
3480 cleanRowStart(
3481 buffer.getShort(getRowEndOffset(rowNum, format))));
3482 }
3483
3484
3485
3486
3487 public static int getRowEndOffset(int rowNum, JetFormat format)
3488 {
3489 return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * (rowNum - 1));
3490 }
3491
3492
3493
3494
3495 public static int getRowSpaceUsage(int rowSize, JetFormat format)
3496 {
3497 return rowSize + format.SIZE_ROW_LOCATION;
3498 }
3499
3500 private void initAutoNumberColumns() {
3501 for(ColumnImpl c : _columns) {
3502 if(c.isAutoNumber()) {
3503 _autoNumColumns.add(c);
3504 }
3505 }
3506 }
3507
3508 private void initCalculatedColumns() {
3509 for(ColumnImpl c : _columns) {
3510 if(c.isCalculated()) {
3511 _calcColEval.add(c);
3512 }
3513 }
3514 }
3515
3516 boolean isThisTable(Identifier identifier) {
3517 String collectionName = identifier.getCollectionName();
3518 return ((collectionName == null) ||
3519 collectionName.equalsIgnoreCase(getName()));
3520 }
3521
3522
3523
3524
3525
3526
3527 public static boolean rowFitsOnDataPage(
3528 int rowLength, ByteBuffer dataPage, JetFormat format)
3529 {
3530 int rowSpaceUsage = getRowSpaceUsage(rowLength, format);
3531 short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
3532 int rowsOnPage = getRowsOnDataPage(dataPage, format);
3533 return ((rowSpaceUsage <= freeSpaceInPage) &&
3534 (rowsOnPage < format.MAX_NUM_ROWS_ON_DATA_PAGE));
3535 }
3536
3537
3538
3539
3540
3541 static Object[] dupeRow(Object[] row, int newRowLength) {
3542 Object[] copy = new Object[newRowLength];
3543 System.arraycopy(row, 0, copy, 0, Math.min(row.length, newRowLength));
3544 return copy;
3545 }
3546
3547 String withErrorContext(String msg) {
3548 return withErrorContext(msg, getDatabase(), getName());
3549 }
3550
3551 private static String withErrorContext(String msg, DatabaseImpl db,
3552 String tableName) {
3553 return msg + " (Db=" + db.getName() + ";Table=" + tableName + ")";
3554 }
3555
3556
3557 private enum RowStatus {
3558 INIT, INVALID_PAGE, INVALID_ROW, VALID, DELETED, NORMAL, OVERFLOW;
3559 }
3560
3561
3562 private enum RowStateStatus {
3563 INIT, AT_HEADER, AT_FINAL;
3564 }
3565
3566
3567
3568
3569 protected static class WriteRowState
3570 {
3571 private int _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
3572
3573 public int getComplexAutoNumber() {
3574 return _complexAutoNumber;
3575 }
3576
3577 public void setComplexAutoNumber(int complexAutoNumber) {
3578 _complexAutoNumber = complexAutoNumber;
3579 }
3580
3581 public void resetAutoNumber() {
3582 _complexAutoNumber = ColumnImpl.INVALID_AUTO_NUMBER;
3583 }
3584 }
3585
3586
3587
3588
3589
3590 public final class RowState extends WriteRowState
3591 implements ErrorHandler.Location
3592 {
3593
3594 private final TempPageHolder _headerRowBufferH;
3595
3596 private RowIdImpl _headerRowId = RowIdImpl.FIRST_ROW_ID;
3597
3598 private int _rowsOnHeaderPage;
3599
3600 private RowStateStatus _status = RowStateStatus.INIT;
3601
3602 private RowStatus _rowStatus = RowStatus.INIT;
3603
3604 private final TempPageHolder _overflowRowBufferH =
3605 TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
3606
3607
3608 private ByteBuffer _finalRowBuffer;
3609
3610
3611 private RowIdImpl _finalRowId = null;
3612
3613 private boolean _haveRowValues;
3614
3615 private Object[] _rowValues;
3616
3617 private NullMask _nullMask;
3618
3619
3620
3621 private int _lastModCount;
3622
3623 private ErrorHandler _errorHandler;
3624
3625 private short[] _varColOffsets;
3626
3627 private RowState(TempBufferHolder.Type headerType) {
3628 _headerRowBufferH = TempPageHolder.newHolder(headerType);
3629 _rowValues = new Object[TableImpl.this.getColumnCount()];
3630 _lastModCount = TableImpl.this._modCount;
3631 }
3632
3633 @Override
3634 public TableImpl getTable() {
3635 return TableImpl.this;
3636 }
3637
3638 public ErrorHandler getErrorHandler() {
3639 return((_errorHandler != null) ? _errorHandler :
3640 getTable().getErrorHandler());
3641 }
3642
3643 public void setErrorHandler(ErrorHandler newErrorHandler) {
3644 _errorHandler = newErrorHandler;
3645 }
3646
3647 public void reset() {
3648 resetAutoNumber();
3649 _finalRowId = null;
3650 _finalRowBuffer = null;
3651 _rowsOnHeaderPage = 0;
3652 _status = RowStateStatus.INIT;
3653 _rowStatus = RowStatus.INIT;
3654 _varColOffsets = null;
3655 _nullMask = null;
3656 if(_haveRowValues) {
3657 Arrays.fill(_rowValues, null);
3658 _haveRowValues = false;
3659 }
3660 }
3661
3662 public boolean isUpToDate() {
3663 return(TableImpl.this._modCount == _lastModCount);
3664 }
3665
3666 private void checkForModification() {
3667 if(!isUpToDate()) {
3668 reset();
3669 _headerRowBufferH.invalidate();
3670 _overflowRowBufferH.invalidate();
3671 int colCount = TableImpl.this.getColumnCount();
3672 if(colCount != _rowValues.length) {
3673
3674 _rowValues = new Object[colCount];
3675 }
3676 _lastModCount = TableImpl.this._modCount;
3677 }
3678 }
3679
3680 private ByteBuffer getFinalPage()
3681 throws IOException
3682 {
3683 if(_finalRowBuffer == null) {
3684
3685 _finalRowBuffer = getHeaderPage();
3686 }
3687 return _finalRowBuffer;
3688 }
3689
3690 public RowIdImpl getFinalRowId() {
3691 if(_finalRowId == null) {
3692 _finalRowId = getHeaderRowId();
3693 }
3694 return _finalRowId;
3695 }
3696
3697 private void setRowStatus(RowStatus rowStatus) {
3698 _rowStatus = rowStatus;
3699 }
3700
3701 public boolean isValid() {
3702 return(_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
3703 }
3704
3705 public boolean isDeleted() {
3706 return(_rowStatus == RowStatus.DELETED);
3707 }
3708
3709 public boolean isOverflow() {
3710 return(_rowStatus == RowStatus.OVERFLOW);
3711 }
3712
3713 public boolean isHeaderPageNumberValid() {
3714 return(_rowStatus.ordinal() > RowStatus.INVALID_PAGE.ordinal());
3715 }
3716
3717 public boolean isHeaderRowNumberValid() {
3718 return(_rowStatus.ordinal() > RowStatus.INVALID_ROW.ordinal());
3719 }
3720
3721 private void setStatus(RowStateStatus status) {
3722 _status = status;
3723 }
3724
3725 public boolean isAtHeaderRow() {
3726 return(_status.ordinal() >= RowStateStatus.AT_HEADER.ordinal());
3727 }
3728
3729 public boolean isAtFinalRow() {
3730 return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
3731 }
3732
3733 private Object setRowCacheValue(int idx, Object value) {
3734 _haveRowValues = true;
3735 _rowValues[idx] = value;
3736 return value;
3737 }
3738
3739 private Object getRowCacheValue(int idx) {
3740 Object value = _rowValues[idx];
3741
3742
3743 return(ColumnImpl.isImmutableValue(value) ? value : null);
3744 }
3745
3746 public Object[] getRowCacheValues() {
3747 return dupeRow(_rowValues, _rowValues.length);
3748 }
3749
3750 public NullMask getNullMask(ByteBuffer rowBuffer) {
3751 if(_nullMask == null) {
3752 _nullMask = getRowNullMask(rowBuffer);
3753 }
3754 return _nullMask;
3755 }
3756
3757 private short[] getVarColOffsets() {
3758 return _varColOffsets;
3759 }
3760
3761 private void setVarColOffsets(short[] varColOffsets) {
3762 _varColOffsets = varColOffsets;
3763 }
3764
3765 public RowIdImpl getHeaderRowId() {
3766 return _headerRowId;
3767 }
3768
3769 public int getRowsOnHeaderPage() {
3770 return _rowsOnHeaderPage;
3771 }
3772
3773 private ByteBuffer getHeaderPage()
3774 throws IOException
3775 {
3776 checkForModification();
3777 return _headerRowBufferH.getPage(getPageChannel());
3778 }
3779
3780 private ByteBuffer setHeaderRow(RowIdImpl rowId)
3781 throws IOException
3782 {
3783 checkForModification();
3784
3785
3786 if(isAtHeaderRow() && (getHeaderRowId().equals(rowId))) {
3787 return(isValid() ? getHeaderPage() : null);
3788 }
3789
3790
3791 reset();
3792 _headerRowId = rowId;
3793 _finalRowId = rowId;
3794
3795 int pageNumber = rowId.getPageNumber();
3796 int rowNumber = rowId.getRowNumber();
3797 if((pageNumber < 0) || !_ownedPages.containsPageNumber(pageNumber)) {
3798 setRowStatus(RowStatus.INVALID_PAGE);
3799 return null;
3800 }
3801
3802 _finalRowBuffer = _headerRowBufferH.setPage(getPageChannel(),
3803 pageNumber);
3804 _rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer, getFormat());
3805
3806 if((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
3807 setRowStatus(RowStatus.INVALID_ROW);
3808 return null;
3809 }
3810
3811 setRowStatus(RowStatus.VALID);
3812 return _finalRowBuffer;
3813 }
3814
3815 private ByteBuffer setOverflowRow(RowIdImpl rowId)
3816 throws IOException
3817 {
3818
3819
3820 if(!isUpToDate()) {
3821 throw new IllegalStateException(getTable().withErrorContext(
3822 "Table modified while searching?"));
3823 }
3824 if(_rowStatus != RowStatus.OVERFLOW) {
3825 throw new IllegalStateException(getTable().withErrorContext(
3826 "Row is not an overflow row?"));
3827 }
3828 _finalRowId = rowId;
3829 _finalRowBuffer = _overflowRowBufferH.setPage(getPageChannel(),
3830 rowId.getPageNumber());
3831 return _finalRowBuffer;
3832 }
3833
3834 private Object handleRowError(ColumnImpl column, byte[] columnData,
3835 Exception error)
3836 throws IOException
3837 {
3838 return getErrorHandler().handleRowError(column, columnData,
3839 this, error);
3840 }
3841
3842 @Override
3843 public String toString() {
3844 return ToStringBuilder.valueBuilder(this)
3845 .append("headerRowId", _headerRowId)
3846 .append("finalRowId", _finalRowId)
3847 .toString();
3848 }
3849 }
3850
3851
3852
3853
3854
3855 private class CalcColEvaluator
3856 {
3857
3858
3859 private final List<ColumnImpl> _calcColumns = new ArrayList<>(1);
3860 private boolean _sorted;
3861
3862 public void add(ColumnImpl col) {
3863 if(!getDatabase().isEvaluateExpressions()) {
3864 return;
3865 }
3866 _calcColumns.add(col);
3867
3868 _sorted = false;
3869 }
3870
3871
3872
3873
3874
3875 public boolean willCalculate(ColumnImpl col) {
3876 return _calcColumns.contains(col);
3877 }
3878
3879 public void reSort() {
3880
3881 _sorted = false;
3882 }
3883
3884 public void calculate(Object[] row) throws IOException {
3885 if(!_sorted) {
3886 sortColumnsByDeps();
3887 _sorted = true;
3888 }
3889
3890 for(ColumnImpl col : _calcColumns) {
3891 Object rowValue = col.getCalculationContext().eval(row);
3892 col.setRowValue(row, rowValue);
3893 }
3894 }
3895
3896 private void sortColumnsByDeps() {
3897
3898
3899
3900
3901
3902
3903
3904
3905 (new TopoSorter<ColumnImpl>(_calcColumns, TopoSorter.REVERSE) {
3906 @Override
3907 protected void getDescendents(ColumnImpl from,
3908 List<ColumnImpl> descendents) {
3909
3910 Set<Identifier> identifiers = new LinkedHashSet<>();
3911 from.getCalculationContext().collectIdentifiers(identifiers);
3912
3913 for(Identifier identifier : identifiers) {
3914 if(isThisTable(identifier)) {
3915 String colName = identifier.getObjectName();
3916 for(ColumnImpl calcCol : _calcColumns) {
3917
3918 if(calcCol.getName().equalsIgnoreCase(colName)) {
3919 descendents.add(calcCol);
3920 }
3921 }
3922 }
3923 }
3924 }
3925 }).sort();
3926 }
3927 }
3928 }