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.util.ArrayList;
21 import java.util.Collections;
22 import java.util.EnumSet;
23 import java.util.HashSet;
24 import java.util.IdentityHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28
29 import com.healthmarketscience.jackcess.ColumnBuilder;
30 import com.healthmarketscience.jackcess.DataType;
31 import com.healthmarketscience.jackcess.IndexBuilder;
32 import com.healthmarketscience.jackcess.PropertyMap;
33 import com.healthmarketscience.jackcess.TableBuilder;
34
35
36
37
38
39
40
41 public class TableCreator extends TableMutator
42 {
43 private String _name;
44 private List<ColumnBuilder> _columns;
45 private List<IndexBuilder> _indexes;
46 private final List<IndexDataState> _indexDataStates =
47 new ArrayList<>();
48 private final Map<ColumnBuilder,ColumnState> _columnStates =
49 new IdentityHashMap<>();
50 private final List<ColumnBuilder> _lvalCols = new ArrayList<>();
51 private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
52 private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
53 private int _indexCount;
54 private int _logicalIndexCount;
55
56 public TableCreator(DatabaseImpl database) {
57 super(database);
58 }
59
60 public String getName() {
61 return _name;
62 }
63
64 @Override
65 String getTableName() {
66 return getName();
67 }
68
69 @Override
70 public int getTdefPageNumber() {
71 return _tdefPageNumber;
72 }
73
74 public int getUmapPageNumber() {
75 return _umapPageNumber;
76 }
77
78 public List<ColumnBuilder> getColumns() {
79 return _columns;
80 }
81
82 public List<IndexBuilder> getIndexes() {
83 return _indexes;
84 }
85
86 public boolean hasIndexes() {
87 return !_indexes.isEmpty();
88 }
89
90 public int getIndexCount() {
91 return _indexCount;
92 }
93
94 public int getLogicalIndexCount() {
95 return _logicalIndexCount;
96 }
97
98 @Override
99 public IndexDataState getIndexDataState(IndexBuilder idx) {
100 for(IndexDataState idxDataState : _indexDataStates) {
101 for(IndexBuilder curIdx : idxDataState.getIndexes()) {
102 if(idx == curIdx) {
103 return idxDataState;
104 }
105 }
106 }
107 throw new IllegalStateException(withErrorContext(
108 "could not find state for index"));
109 }
110
111 public List<IndexDataState> getIndexDataStates() {
112 return _indexDataStates;
113 }
114
115 @Override
116 public ColumnState getColumnState(ColumnBuilder col) {
117 return _columnStates.get(col);
118 }
119
120 private ColumnState getOrCreateColumnState(ColumnBuilder col) {
121
122
123 return _columnStates.computeIfAbsent(col, c -> new ColumnState());
124 }
125
126
127
128
129
130 void setExtraFlags(ColumnBuilder col, byte extraFlags) {
131 getOrCreateColumnState(col).setExtraFlags(extraFlags);
132 }
133
134 public List<ColumnBuilder> getLongValueColumns() {
135 return _lvalCols;
136 }
137
138 @Override
139 short getColumnNumber(String colName) {
140 for(ColumnBuilder col : _columns) {
141 if(col.getName().equalsIgnoreCase(colName)) {
142 return col.getColumnNumber();
143 }
144 }
145 return IndexData.COLUMN_UNUSED;
146 }
147
148 @Override
149 boolean isComplexColumn(String colName) {
150 for(ColumnBuilder col : _columns) {
151 if(col.getName().equalsIgnoreCase(colName)) {
152 return (col.getType() == DataType.COMPLEX_TYPE);
153 }
154 }
155 return false;
156 }
157
158
159
160
161
162
163 public short countNonLongVariableLength() {
164 short rtn = 0;
165 for (ColumnBuilder col : _columns) {
166 if (col.isVariableLength() && !col.getType().isLongValue()) {
167 rtn++;
168 }
169 }
170 return rtn;
171 }
172
173
174
175
176
177
178 public TableImpl createTable(TableBuilder table) throws IOException {
179 return createTable(table, 0, false);
180 }
181
182
183
184
185
186
187
188
189 TableImpl createTable(TableBuilder table, int catalogFlags,
190 boolean isInternal)
191 throws IOException
192 {
193 _name = table.getName();
194 _columns = addVersionHistoryColumn(table);
195 _indexes = table.getIndexes();
196 if(_indexes == null) {
197 _indexes = Collections.<IndexBuilder>emptyList();
198 }
199
200 validate();
201
202
203 short columnNumber = (short) 0;
204 for(ColumnBuilder col : _columns) {
205 col.setColumnNumber(columnNumber++);
206 if(col.getType().isLongValue()) {
207 _lvalCols.add(col);
208 }
209 if(col.getType().isLongValue() ||
210 (col.getType() == DataType.COMPLEX_TYPE)) {
211
212 getOrCreateColumnState(col);
213 }
214 }
215
216 if(hasIndexes()) {
217
218 for(IndexBuilder idx : _indexes) {
219 idx.setIndexNumber(_logicalIndexCount++);
220 findIndexDataState(idx);
221 }
222 }
223
224 if(!isInternal) {
225 getPageChannel().startExclusiveWrite();
226 } else {
227
228
229 getPageChannel().startWrite();
230 }
231 try {
232
233
234 _tdefPageNumber = reservePageNumber();
235 _umapPageNumber = reservePageNumber();
236
237
238
239 for(ColumnBuilder col : _columns) {
240 if(col.getComplexDesc() != null) {
241 ComplexColumnCreator.create(this, _name, _tdefPageNumber, col);
242 catalogFlags |= ComplexColumnCreator.CATALOG_FLAGS_HAS_COMPLEX;
243 }
244 }
245
246
247 TableImpl.writeTableDefinition(this);
248
249
250 getDatabase().addNewTable(_name, _tdefPageNumber, DatabaseImpl.TYPE_TABLE,
251 catalogFlags, null, null);
252
253
254
255 TableImpl newTable = getDatabase().getTable(_tdefPageNumber);
256
257
258 boolean addedProps = false;
259 Map<String,PropertyMap.Property> props = table.getProperties();
260 if(props != null) {
261 newTable.getProperties().putAll(props.values());
262 addedProps = true;
263 }
264 if(hasVersionHistory()) {
265
266 newTable.getProperties().put(PropertyMap.APPEND_ONLY_PROP,
267 DataType.BOOLEAN, Boolean.TRUE);
268 addedProps = true;
269 }
270 for(ColumnBuilder cb : _columns) {
271 Map<String,PropertyMap.Property> colProps = cb.getProperties();
272 if(colProps != null) {
273 newTable.getColumn(cb.getName()).getProperties()
274 .putAll(colProps.values());
275 addedProps = true;
276 }
277 }
278
279
280 if(addedProps) {
281 newTable.getProperties().save();
282 }
283
284 return newTable;
285
286 } finally {
287 getPageChannel().finishWrite();
288 }
289 }
290
291
292
293
294
295 private List<ColumnBuilder> addVersionHistoryColumn(TableBuilder table) {
296 List<ColumnBuilder> columns = table.getColumns();
297
298 ColumnBuilder historyCol = null;
299 for(ColumnBuilder col : columns) {
300 if(col.isAppendOnly()) {
301 if(historyCol != null) {
302 throw new IllegalArgumentException(withErrorContext(
303 "Only one memo column of a table can keep a version history, " +
304 "because every version history column has the same name"));
305 }
306 historyCol = ComplexColumnCreator.newVersionHistoryColumn(col);
307 }
308 }
309
310 if(historyCol == null) {
311 return columns;
312 }
313
314 columns = new ArrayList<>(columns);
315 columns.add(historyCol);
316 return columns;
317 }
318
319 private boolean hasVersionHistory() {
320 for(ColumnBuilder col : _columns) {
321 if(col.isAppendOnly()) {
322 return true;
323 }
324 }
325 return false;
326 }
327
328 private IndexDataState findIndexDataState(IndexBuilder idx) {
329
330
331
332 for(IndexDataState idxDataState : _indexDataStates) {
333 if(sameIndexData(idxDataState.getFirstIndex(), idx)) {
334 idxDataState.addIndex(idx);
335 return idxDataState;
336 }
337 }
338
339
340 IndexDataState idxDataState = new IndexDataState();
341 idxDataState.setIndexDataNumber(_indexCount++);
342 idxDataState.addIndex(idx);
343 _indexDataStates.add(idxDataState);
344 return idxDataState;
345 }
346
347
348
349
350 private void validate() throws IOException {
351
352 getDatabase().validateNewTableName(_name);
353
354 if((_columns == null) || _columns.isEmpty()) {
355 throw new IllegalArgumentException(withErrorContext(
356 "Cannot create table with no columns"));
357 }
358 if(_columns.size() > getFormat().MAX_COLUMNS_PER_TABLE) {
359 throw new IllegalArgumentException(withErrorContext(
360 "Cannot create table with more than " +
361 getFormat().MAX_COLUMNS_PER_TABLE + " columns"));
362 }
363
364 Set<String> colNames = new HashSet<>();
365
366 for(ColumnBuilder column : _columns) {
367 validateColumn(colNames, column);
368 }
369
370 List<ColumnBuilder> autoCols = getAutoNumberColumns();
371 if(autoCols.size() > 1) {
372
373 Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
374 for(ColumnBuilder c : autoCols) {
375 validateAutoNumberColumn(autoTypes, c);
376 }
377 }
378
379 if(hasIndexes()) {
380
381 if(_indexes.size() > getFormat().MAX_INDEXES_PER_TABLE) {
382 throw new IllegalArgumentException(withErrorContext(
383 "Cannot create table with more than " +
384 getFormat().MAX_INDEXES_PER_TABLE + " indexes"));
385 }
386
387
388 Set<String> idxNames = new HashSet<>();
389 boolean foundPk[] = new boolean[1];
390 for(IndexBuilder index : _indexes) {
391 validateIndex(colNames, idxNames, foundPk, index);
392 }
393 }
394 }
395
396 private List<ColumnBuilder> getAutoNumberColumns()
397 {
398 List<ColumnBuilder> autoCols = new ArrayList<>(1);
399 for(ColumnBuilder c : _columns) {
400 if(c.isAutoNumber()) {
401 autoCols.add(c);
402 }
403 }
404 return autoCols;
405 }
406
407 private static boolean sameIndexData(IndexBuilder/../../../com/healthmarketscience/jackcess/IndexBuilder.html#IndexBuilder">IndexBuilder idx1, IndexBuilder idx2) {
408
409
410 if(idx1.getFlags() != idx2.getFlags()) {
411 return false;
412 }
413
414 if(idx1.getColumns().size() != idx2.getColumns().size()) {
415 return false;
416 }
417
418 for(int i = 0; i < idx1.getColumns().size(); ++i) {
419 IndexBuilder.Column col1 = idx1.getColumns().get(i);
420 IndexBuilder.Column col2 = idx2.getColumns().get(i);
421
422 if(!sameIndexData(col1, col2)) {
423 return false;
424 }
425 }
426
427 return true;
428 }
429
430 private static boolean sameIndexData(
431 IndexBuilder.Column col1, IndexBuilder.Column col2) {
432 return (col1.getName().equals(col2.getName()) &&
433 (col1.getFlags() == col2.getFlags()));
434 }
435
436 @Override
437 protected String withErrorContext(String msg) {
438 return msg + "(Table=" + getName() + ")";
439 }
440 }