You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TableCreator.java 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. Copyright (c) 2011 James Ahlborn
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. */
  16. package com.healthmarketscience.jackcess;
  17. import java.io.IOException;
  18. import java.nio.charset.Charset;
  19. import java.util.Collections;
  20. import java.util.EnumSet;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. /**
  27. * Helper class used to maintain state during table creation.
  28. *
  29. * @author James Ahlborn
  30. * @usage _advanced_class_
  31. */
  32. class TableCreator
  33. {
  34. private final Database _database;
  35. private final String _name;
  36. private final List<Column> _columns;
  37. private final List<IndexBuilder> _indexes;
  38. private final Map<IndexBuilder,IndexState> _indexStates =
  39. new HashMap<IndexBuilder,IndexState>();
  40. private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
  41. private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
  42. private int _indexCount;
  43. private int _logicalIndexCount;
  44. public TableCreator(Database database, String name, List<Column> columns,
  45. List<IndexBuilder> indexes) {
  46. _database = database;
  47. _name = name;
  48. _columns = columns;
  49. _indexes = ((indexes != null) ? indexes :
  50. Collections.<IndexBuilder>emptyList());
  51. }
  52. public JetFormat getFormat() {
  53. return _database.getFormat();
  54. }
  55. public PageChannel getPageChannel() {
  56. return _database.getPageChannel();
  57. }
  58. public Charset getCharset() {
  59. return _database.getCharset();
  60. }
  61. public int getTdefPageNumber() {
  62. return _tdefPageNumber;
  63. }
  64. public int getUmapPageNumber() {
  65. return _umapPageNumber;
  66. }
  67. public List<Column> getColumns() {
  68. return _columns;
  69. }
  70. public List<IndexBuilder> getIndexes() {
  71. return _indexes;
  72. }
  73. public boolean hasIndexes() {
  74. return !_indexes.isEmpty();
  75. }
  76. public int getIndexCount() {
  77. return _indexCount;
  78. }
  79. public int getLogicalIndexCount() {
  80. return _logicalIndexCount;
  81. }
  82. public IndexState getIndexState(IndexBuilder idx) {
  83. return _indexStates.get(idx);
  84. }
  85. public int reservePageNumber() throws IOException {
  86. return getPageChannel().allocateNewPage();
  87. }
  88. /**
  89. * Creates the table in the database.
  90. * @usage _advanced_method_
  91. */
  92. public void createTable() throws IOException {
  93. validate();
  94. if(hasIndexes()) {
  95. // sort out index numbers. for now, these values will always match
  96. // (until we support writing foreign key indexes)
  97. for(IndexBuilder idx : _indexes) {
  98. IndexState idxState = new IndexState();
  99. idxState.setIndexNumber(_logicalIndexCount++);
  100. idxState.setIndexDataNumber(_indexCount++);
  101. _indexStates.put(idx, idxState);
  102. }
  103. }
  104. // reserve some pages
  105. _tdefPageNumber = reservePageNumber();
  106. _umapPageNumber = reservePageNumber();
  107. //Write the tdef page to disk.
  108. Table.writeTableDefinition(this);
  109. // update the database with the new table info
  110. _database.addNewTable(_name, _tdefPageNumber, Database.TYPE_TABLE, null, null);
  111. }
  112. /**
  113. * Validates the new table information before attempting creation.
  114. */
  115. private void validate() {
  116. Database.validateIdentifierName(
  117. _name, getFormat().MAX_TABLE_NAME_LENGTH, "table");
  118. if((_columns == null) || _columns.isEmpty()) {
  119. throw new IllegalArgumentException(
  120. "Cannot create table with no columns");
  121. }
  122. if(_columns.size() > getFormat().MAX_COLUMNS_PER_TABLE) {
  123. throw new IllegalArgumentException(
  124. "Cannot create table with more than " +
  125. getFormat().MAX_COLUMNS_PER_TABLE + " columns");
  126. }
  127. Column.SortOrder dbSortOrder = null;
  128. try {
  129. dbSortOrder = _database.getDefaultSortOrder();
  130. } catch(IOException e) {
  131. // ignored, just use the jet format default
  132. }
  133. Set<String> colNames = new HashSet<String>();
  134. // next, validate the column definitions
  135. for(Column column : _columns) {
  136. // FIXME for now, we can't create complex columns
  137. if(column.getType() == DataType.COMPLEX_TYPE) {
  138. throw new UnsupportedOperationException(
  139. "Complex column creation is not yet implemented");
  140. }
  141. column.validate(getFormat());
  142. if(!colNames.add(column.getName().toUpperCase())) {
  143. throw new IllegalArgumentException("duplicate column name: " +
  144. column.getName());
  145. }
  146. // set the sort order to the db default (if unspecified)
  147. if(column.getType().isTextual() && (column.getTextSortOrder() == null)) {
  148. column.setTextSortOrder(dbSortOrder);
  149. }
  150. }
  151. List<Column> autoCols = Table.getAutoNumberColumns(_columns);
  152. if(autoCols.size() > 1) {
  153. // for most autonumber types, we can only have one of each type
  154. Set<DataType> autoTypes = EnumSet.noneOf(DataType.class);
  155. for(Column c : autoCols) {
  156. if(!c.getType().isMultipleAutoNumberAllowed() &&
  157. !autoTypes.add(c.getType())) {
  158. throw new IllegalArgumentException(
  159. "Can have at most one AutoNumber column of type " + c.getType() +
  160. " per table");
  161. }
  162. }
  163. }
  164. if(hasIndexes()) {
  165. // now, validate the indexes
  166. Set<String> idxNames = new HashSet<String>();
  167. boolean foundPk = false;
  168. for(IndexBuilder index : _indexes) {
  169. index.validate(colNames);
  170. if(!idxNames.add(index.getName().toUpperCase())) {
  171. throw new IllegalArgumentException("duplicate index name: " +
  172. index.getName());
  173. }
  174. if(index.isPrimaryKey()) {
  175. if(foundPk) {
  176. throw new IllegalArgumentException(
  177. "found second primary key index: " + index.getName());
  178. }
  179. foundPk = true;
  180. }
  181. }
  182. }
  183. }
  184. /**
  185. * Maintains additional state used during index creation.
  186. * @usage _advanced_class_
  187. */
  188. static final class IndexState
  189. {
  190. private int _indexNumber;
  191. private int _indexDataNumber;
  192. private byte _umapRowNumber;
  193. private int _umapPageNumber;
  194. private int _rootPageNumber;
  195. public int getIndexNumber() {
  196. return _indexNumber;
  197. }
  198. public void setIndexNumber(int newIndexNumber) {
  199. _indexNumber = newIndexNumber;
  200. }
  201. public int getIndexDataNumber() {
  202. return _indexDataNumber;
  203. }
  204. public void setIndexDataNumber(int newIndexDataNumber) {
  205. _indexDataNumber = newIndexDataNumber;
  206. }
  207. public byte getUmapRowNumber() {
  208. return _umapRowNumber;
  209. }
  210. public void setUmapRowNumber(byte newUmapRowNumber) {
  211. _umapRowNumber = newUmapRowNumber;
  212. }
  213. public int getUmapPageNumber() {
  214. return _umapPageNumber;
  215. }
  216. public void setUmapPageNumber(int newUmapPageNumber) {
  217. _umapPageNumber = newUmapPageNumber;
  218. }
  219. public int getRootPageNumber() {
  220. return _rootPageNumber;
  221. }
  222. public void setRootPageNumber(int newRootPageNumber) {
  223. _rootPageNumber = newRootPageNumber;
  224. }
  225. }
  226. }