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