View Javadoc
1   /*
2   Copyright (c) 2016 James Ahlborn
3   
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7   
8       http://www.apache.org/licenses/LICENSE-2.0
9   
10  Unless required by applicable law or agreed to in writing, software
11  distributed under the License is distributed on an "AS IS" BASIS,
12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  See the License for the specific language governing permissions and
14  limitations under the License.
15  */
16  
17  package com.healthmarketscience.jackcess.impl;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.Set;
22  
23  import com.healthmarketscience.jackcess.ColumnBuilder;
24  import com.healthmarketscience.jackcess.DataType;
25  import com.healthmarketscience.jackcess.IndexBuilder;
26  
27  /**
28   * Common helper class used to maintain state during table mutation.
29   *
30   * @author James Ahlborn
31   * @usage _advanced_class_
32   */
33  public abstract class TableMutator extends DBMutator
34  {
35    private ColumnOffsets _colOffsets;
36  
37    protected TableMutator(DatabaseImpl database) {
38      super(database);
39    }
40  
41    public void setColumnOffsets(
42        int fixedOffset, int varOffset, int longVarOffset) {
43      if(_colOffsets == null) {
44        _colOffsets = new ColumnOffsets();
45      }
46      _colOffsets.set(fixedOffset, varOffset, longVarOffset);
47    }
48  
49    public ColumnOffsets getColumnOffsets() {
50      return _colOffsets;
51    }
52  
53    public IndexImpl.ForeignKeyReference getForeignKey(IndexBuilder idx) {
54      return null;
55    }
56  
57    protected void validateColumn(Set<String> colNames, ColumnBuilder column) {
58  
59        // FIXME for now, we can't create complex columns
60        if(column.getType() == DataType.COMPLEX_TYPE) {
61          throw new UnsupportedOperationException(withErrorContext(
62              "Complex column creation is not yet implemented"));
63        }
64  
65        column.validate(getFormat());
66        if(!colNames.add(DatabaseImpl.toLookupName(column.getName()))) {
67          throw new IllegalArgumentException(withErrorContext(
68              "duplicate column name: " + column.getName()));
69        }
70  
71        setColumnSortOrder(column);
72    }
73  
74    protected void validateIndex(Set<String> colNames, Set<String> idxNames,
75                                 boolean[] foundPk, IndexBuilder index) {
76  
77      index.validate(colNames, getFormat());
78      if(!idxNames.add(DatabaseImpl.toLookupName(index.getName()))) {
79        throw new IllegalArgumentException(withErrorContext(
80            "duplicate index name: " + index.getName()));
81      }
82      if(index.isPrimaryKey()) {
83        if(foundPk[0]) {
84          throw new IllegalArgumentException(withErrorContext(
85              "found second primary key index: " + index.getName()));
86        }
87        foundPk[0] = true;
88      } else if(index.getType() == IndexImpl.FOREIGN_KEY_INDEX_TYPE) {
89        if(getForeignKey(index) == null) {
90          throw new IllegalArgumentException(withErrorContext(
91              "missing foreign key info for " + index.getName()));
92        }
93      }
94    }
95  
96    protected void validateAutoNumberColumn(Set<DataType> autoTypes,
97                                            ColumnBuilder column)
98    {
99      if(!column.getType().isMultipleAutoNumberAllowed() &&
100        !autoTypes.add(column.getType())) {
101       throw new IllegalArgumentException(withErrorContext(
102           "Can have at most one AutoNumber column of type " + column.getType() +
103           " per table"));
104     }
105   }
106 
107   private void setColumnSortOrder(ColumnBuilder column) {
108       // set the sort order to the db default (if unspecified)
109       if(column.getType().isTextual() && (column.getTextSortOrder() == null)) {
110         column.setTextSortOrder(getDbSortOrder());
111       }
112   }
113 
114   abstract String getTableName();
115 
116   public abstract int getTdefPageNumber();
117 
118   abstract short getColumnNumber(String colName);
119 
120   /**
121    * @return {@code true} if the named column holds a complex type,
122    *         {@code false} if it does not or if there is no such column
123    */
124   abstract boolean isComplexColumn(String colName);
125 
126   public abstract ColumnState getColumnState(ColumnBuilder col);
127 
128   public abstract IndexDataState getIndexDataState(IndexBuilder idx);
129 
130   protected abstract String withErrorContext(String msg);
131 
132   /**
133    * Maintains additional state used during column writing.
134    * @usage _advanced_class_
135    */
136   static final class ColumnOffsets
137   {
138     private short _fixedOffset;
139     private short _varOffset;
140     private short _longVarOffset;
141 
142     public void set(int fixedOffset, int varOffset, int longVarOffset) {
143       _fixedOffset = (short)fixedOffset;
144       _varOffset = (short)varOffset;
145       _longVarOffset = (short)longVarOffset;
146     }
147 
148     public short getNextVariableOffset(ColumnBuilder col) {
149       if(!col.isVariableLength()) {
150         return _varOffset;
151       }
152       if(!col.getType().isLongValue()) {
153         return _varOffset++;
154       }
155       return _longVarOffset++;
156     }
157 
158     public short getNextFixedOffset(ColumnBuilder col) {
159       if(col.storeInNullMask()) {
160         // booleans are stored in null mask, not in fixed data section
161         return 0;
162       }
163       short offset = _fixedOffset;
164       _fixedOffset += col.getFixedDataSize();
165       return offset;
166     }
167   }
168 
169   /**
170    * Maintains additional state used during column creation.
171    * @usage _advanced_class_
172    */
173   static final class ColumnState
174   {
175     private byte _umapOwnedRowNumber;
176     private byte _umapFreeRowNumber;
177     // we always put both usage maps on the same page
178     private int _umapPageNumber;
179 
180     public byte getUmapOwnedRowNumber() {
181       return _umapOwnedRowNumber;
182     }
183 
184     public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) {
185       _umapOwnedRowNumber = newUmapOwnedRowNumber;
186     }
187 
188     public byte getUmapFreeRowNumber() {
189       return _umapFreeRowNumber;
190     }
191 
192     public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) {
193       _umapFreeRowNumber = newUmapFreeRowNumber;
194     }
195 
196     public int getUmapPageNumber() {
197       return _umapPageNumber;
198     }
199 
200     public void setUmapPageNumber(int newUmapPageNumber) {
201       _umapPageNumber = newUmapPageNumber;
202     }
203   }
204 
205   /**
206    * Maintains additional state used during index data creation.
207    * @usage _advanced_class_
208    */
209   static final class IndexDataState
210   {
211     private final List<IndexBuilder> _indexes = new ArrayList<>();
212     private int _indexDataNumber;
213     private byte _umapRowNumber;
214     private int _umapPageNumber;
215     private int _rootPageNumber;
216 
217     public IndexBuilder getFirstIndex() {
218       // all indexes which have the same backing IndexDataState will have
219       // equivalent columns and flags.
220       return _indexes.get(0);
221     }
222 
223     public List<IndexBuilder> getIndexes() {
224       return _indexes;
225     }
226 
227     public void addIndex(IndexBuilder idx) {
228       _indexes.add(idx);
229     }
230 
231     public int getIndexDataNumber() {
232       return _indexDataNumber;
233     }
234 
235     public void setIndexDataNumber(int newIndexDataNumber) {
236       _indexDataNumber = newIndexDataNumber;
237     }
238 
239     public byte getUmapRowNumber() {
240       return _umapRowNumber;
241     }
242 
243     public void setUmapRowNumber(byte newUmapRowNumber) {
244       _umapRowNumber = newUmapRowNumber;
245     }
246 
247     public int getUmapPageNumber() {
248       return _umapPageNumber;
249     }
250 
251     public void setUmapPageNumber(int newUmapPageNumber) {
252       _umapPageNumber = newUmapPageNumber;
253     }
254 
255     public int getRootPageNumber() {
256       return _rootPageNumber;
257     }
258 
259     public void setRootPageNumber(int newRootPageNumber) {
260       _rootPageNumber = newRootPageNumber;
261     }
262   }
263 }