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