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