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.IOException;
20 import java.nio.ByteBuffer;
21 import java.util.ArrayList;
22 import java.util.EnumSet;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26
27 import com.healthmarketscience.jackcess.ColumnBuilder;
28 import com.healthmarketscience.jackcess.DataType;
29 import com.healthmarketscience.jackcess.IndexBuilder;
30 import com.healthmarketscience.jackcess.PropertyMap;
31
32
33
34
35
36
37
38 public class TableUpdater extends TableMutator
39 {
40 private final TableImpl _table;
41
42 private ColumnBuilder _column;
43 private IndexBuilder _index;
44 private int _origTdefLen;
45 private int _addedTdefLen;
46 private final List<Integer> _nextPages = new ArrayList<>(1);
47 private ColumnState _colState;
48 private IndexDataState _idxDataState;
49 private IndexImpl.ForeignKeyReference _fkReference;
50
51 public TableUpdater(TableImpl table) {
52 super(table.getDatabase());
53 _table = table;
54 }
55
56 public ColumnBuilder getColumn() {
57 return _column;
58 }
59
60 public IndexBuilder getIndex() {
61 return _index;
62 }
63
64 @Override
65 String getTableName() {
66 return _table.getName();
67 }
68
69 @Override
70 public int getTdefPageNumber() {
71 return _table.getTableDefPageNumber();
72 }
73
74 @Override
75 short getColumnNumber(String colName) {
76 for(ColumnImpl col : _table.getColumns()) {
77 if(col.getName().equalsIgnoreCase(colName)) {
78 return col.getColumnNumber();
79 }
80 }
81 return IndexData.COLUMN_UNUSED;
82 }
83
84 @Override
85 boolean isComplexColumn(String colName) {
86 for(ColumnImpl col : _table.getColumns()) {
87 if(col.getName().equalsIgnoreCase(colName)) {
88 return (col.getType() == DataType.COMPLEX_TYPE);
89 }
90 }
91 return false;
92 }
93
94 @Override
95 public ColumnState getColumnState(ColumnBuilder col) {
96 return ((col == _column) ? _colState : null);
97 }
98
99 @Override
100 public IndexDataState getIndexDataState(IndexBuilder idx) {
101 return ((idx == _index) ? _idxDataState : null);
102 }
103
104 void setForeignKey(IndexImpl.ForeignKeyReference fkReference) {
105 _fkReference = fkReference;
106 }
107
108 @Override
109 public IndexImpl.ForeignKeyReference getForeignKey(IndexBuilder idx) {
110 return ((idx == _index) ? _fkReference : null);
111 }
112
113 int getAddedTdefLen() {
114 return _addedTdefLen;
115 }
116
117 void addTdefLen(int add) {
118 _addedTdefLen += add;
119 }
120
121 void setOrigTdefLen(int len) {
122 _origTdefLen = len;
123 }
124
125 List<Integer> getNextPages() {
126 return _nextPages;
127 }
128
129 void resetTdefInfo() {
130 _addedTdefLen = 0;
131 _origTdefLen = 0;
132 _nextPages.clear();
133 }
134
135 public ColumnImpl addColumn(ColumnBuilder column) throws IOException {
136 return addColumn(column, false);
137 }
138
139
140
141
142
143 private ColumnImpl addColumn(ColumnBuilder column, boolean isInternal)
144 throws IOException
145 {
146 _column = column;
147
148 validateAddColumn();
149
150
151 short columnNumber = (short)_table.getMaxColumnCount();
152 _column.setColumnNumber(columnNumber);
153 _column.setColumnId(getNextColumnId());
154 if(_column.getType().isLongValue() ||
155 (_column.getType() == DataType.COMPLEX_TYPE)) {
156
157 _colState = new ColumnState();
158 }
159
160 if(!isInternal) {
161 getPageChannel().startExclusiveWrite();
162 } else {
163
164
165 getPageChannel().startWrite();
166 }
167 try {
168
169 if(_column.getComplexDesc() != null) {
170
171
172 ComplexColumnCreator.create(this, _table.getName(),
173 _table.getTableDefPageNumber(), _column);
174 }
175
176 ColumnImpl newCol = _table.mutateAddColumn(this);
177
178 if(_column.getComplexDesc() != null) {
179 getDatabase().addToSystemCatalogFlags(
180 _table.getTableDefPageNumber(),
181 ComplexColumnCreator.CATALOG_FLAGS_HAS_COMPLEX);
182 }
183
184 if(_column.isAppendOnly()) {
185
186 new TableUpdater(_table).addColumn(
187 ComplexColumnCreator.newVersionHistoryColumn(_column), true);
188 PropertyMap tableProps = _table.getProperties();
189 tableProps.put(PropertyMap.APPEND_ONLY_PROP, DataType.BOOLEAN,
190 Boolean.TRUE);
191 tableProps.save();
192 }
193
194 return newCol;
195
196 } finally {
197 getPageChannel().finishWrite();
198 }
199 }
200
201
202
203
204
205
206
207
208 private short getNextColumnId() {
209 short maxId = -1;
210 for(ColumnImpl col : _table.getColumns()) {
211 if(col.getColumnId() > maxId) {
212 maxId = col.getColumnId();
213 }
214 }
215 return (short)(maxId + 1);
216 }
217
218 public IndexImpl addIndex(IndexBuilder index) throws IOException {
219 return addIndex(index, false, (byte)0, (byte)0);
220 }
221
222 IndexImpl addIndex(IndexBuilder index, boolean isInternal, byte ignoreIdxFlags,
223 byte ignoreColFlags)
224 throws IOException
225 {
226 _index = index;
227
228 if(!isInternal) {
229 validateAddIndex();
230 }
231
232
233 int indexNumber = _table.getLogicalIndexCount();
234 _index.setIndexNumber(indexNumber);
235
236
237 initIndexDataState(ignoreIdxFlags, ignoreColFlags);
238
239 if(!isInternal) {
240 getPageChannel().startExclusiveWrite();
241 } else {
242
243
244 getPageChannel().startWrite();
245 }
246 try {
247
248 if(_idxDataState.getIndexDataNumber() == _table.getIndexCount()) {
249
250 _table.mutateAddIndexData(this);
251
252
253 resetTdefInfo();
254 }
255
256 return _table.mutateAddIndex(this);
257
258 } finally {
259 getPageChannel().finishWrite();
260 }
261 }
262
263 boolean validateUpdatedTdef(ByteBuffer tableBuffer) {
264
265 return((_origTdefLen + _addedTdefLen) == tableBuffer.limit());
266 }
267
268 private void validateAddColumn() {
269
270 if(_column == null) {
271 throw new IllegalArgumentException(withErrorContext(
272 "Cannot add column with no column"));
273 }
274 if((_table.getColumnCount() + 1) > getFormat().MAX_COLUMNS_PER_TABLE) {
275 throw new IllegalArgumentException(withErrorContext(
276 "Cannot add column to table with " +
277 getFormat().MAX_COLUMNS_PER_TABLE + " columns"));
278 }
279
280 Set<String> colNames = getColumnNames();
281
282 validateColumn(colNames, _column);
283
284 if(_column.isAppendOnly() &&
285 colNames.contains(DatabaseImpl.toLookupName(
286 ComplexColumnCreator.VERSION_HISTORY_COL_NAME))) {
287 throw new IllegalArgumentException(withErrorContext(
288 "Only one memo column of a table can keep a version history, " +
289 "because every version history column has the same name"));
290 }
291
292 if(_column.isAutoNumber()) {
293
294 Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
295 for(ColumnImpl column : _table.getAutoNumberColumns()) {
296 autoTypes.add(column.getType());
297 }
298
299 validateAutoNumberColumn(autoTypes, _column);
300 }
301 }
302
303 private void validateAddIndex() {
304
305 if(_index == null) {
306 throw new IllegalArgumentException(withErrorContext(
307 "Cannot add index with no index"));
308 }
309 if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) {
310 throw new IllegalArgumentException(withErrorContext(
311 "Cannot add index to table with " +
312 getFormat().MAX_INDEXES_PER_TABLE + " indexes"));
313 }
314
315 boolean foundPk[] = new boolean[1];
316 Set<String> idxNames = getIndexNames(_table, foundPk);
317
318 validateIndex(getColumnNames(), idxNames, foundPk, _index);
319 }
320
321 private Set<String> getColumnNames() {
322 Set<String> colNames = new HashSet<>();
323 for(ColumnImpl column : _table.getColumns()) {
324 colNames.add(DatabaseImpl.toLookupName(column.getName()));
325 }
326 return colNames;
327 }
328
329 static Set<String> getIndexNames(TableImpl table, boolean[] foundPk) {
330 Set<String> idxNames = new HashSet<>();
331 for(IndexImpl index : table.getIndexes()) {
332 idxNames.add(DatabaseImpl.toLookupName(index.getName()));
333 if(index.isPrimaryKey() && (foundPk != null)) {
334 foundPk[0] = true;
335 }
336 }
337 return idxNames;
338 }
339
340 private void initIndexDataState(byte ignoreIdxFlags, byte ignoreColFlags) {
341
342 _idxDataState = new IndexDataState();
343 _idxDataState.addIndex(_index);
344
345
346
347 IndexData idxData = findIndexData(
348 _index, _table, ignoreIdxFlags, ignoreColFlags);
349
350 int idxDataNumber = ((idxData != null) ?
351 idxData.getIndexDataNumber() :
352 _table.getIndexCount());
353
354 _idxDataState.setIndexDataNumber(idxDataNumber);
355 }
356
357 static IndexData findIndexData(IndexBuilder idx, TableImpl table,
358 byte ignoreIdxFlags, byte ignoreColFlags)
359 {
360 for(IndexData idxData : table.getIndexDatas()) {
361 if(sameIndexData(idx, idxData, ignoreIdxFlags, ignoreColFlags)) {
362 return idxData;
363 }
364 }
365 return null;
366 }
367
368 private static boolean sameIndexData(IndexBuilder idx1, IndexData idx2,
369 byte ignoreIdxFlags, byte ignoreColFlags) {
370
371
372 if((idx1.getFlags() | ignoreIdxFlags) !=
373 (idx2.getIndexFlags() | ignoreIdxFlags)) {
374 return false;
375 }
376
377 if(idx1.getColumns().size() != idx2.getColumnCount()) {
378 return false;
379 }
380
381 for(int i = 0; i < idx1.getColumns().size(); ++i) {
382 IndexBuilder.Column col1 = idx1.getColumns().get(i);
383 IndexData.ColumnDescriptor col2 = idx2.getColumns().get(i);
384
385 if(!sameIndexData(col1, col2, ignoreColFlags)) {
386 return false;
387 }
388 }
389
390 return true;
391 }
392
393 private static boolean sameIndexData(
394 IndexBuilder.Column col1, IndexData.ColumnDescriptor col2,
395 int ignoreColFlags) {
396 return (col1.getName().equals(col2.getName()) &&
397 ((col1.getFlags() | ignoreColFlags) ==
398 (col2.getFlags() | ignoreColFlags)));
399 }
400
401 @Override
402 protected String withErrorContext(String msg) {
403 String objStr = "";
404 if(_column != null) {
405 objStr = ";Column=" + _column.getName();
406 } else if(_index != null) {
407 objStr = ";Index=" + _index.getName();
408 }
409 return msg + "(Table=" + _table.getName() + objStr + ")";
410 }
411 }