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   public abstract ColumnState getColumnState(ColumnBuilder col);
121 
122   public abstract IndexDataState getIndexDataState(IndexBuilder idx);
123 
124   protected abstract String withErrorContext(String msg);
125 
126   /**
127    * Maintains additional state used during column writing.
128    * @usage _advanced_class_
129    */
130   static final class ColumnOffsets
131   {
132     private short _fixedOffset;
133     private short _varOffset;
134     private short _longVarOffset;
135 
136     public void set(int fixedOffset, int varOffset, int longVarOffset) {
137       _fixedOffset = (short)fixedOffset;
138       _varOffset = (short)varOffset;
139       _longVarOffset = (short)longVarOffset;
140     }
141 
142     public short getNextVariableOffset(ColumnBuilder col) {
143       if(!col.isVariableLength()) {
144         return _varOffset;
145       }
146       if(!col.getType().isLongValue()) {
147         return _varOffset++;
148       }
149       return _longVarOffset++;
150     }
151 
152     public short getNextFixedOffset(ColumnBuilder col) {
153       if(col.storeInNullMask()) {
154         // booleans are stored in null mask, not in fixed data section
155         return 0;
156       }
157       short offset = _fixedOffset;
158       _fixedOffset += col.getFixedDataSize();
159       return offset;
160     }
161   }
162 
163   /**
164    * Maintains additional state used during column creation.
165    * @usage _advanced_class_
166    */
167   static final class ColumnState
168   {
169     private byte _umapOwnedRowNumber;
170     private byte _umapFreeRowNumber;
171     // we always put both usage maps on the same page
172     private int _umapPageNumber;
173 
174     public byte getUmapOwnedRowNumber() {
175       return _umapOwnedRowNumber;
176     }
177 
178     public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) {
179       _umapOwnedRowNumber = newUmapOwnedRowNumber;
180     }
181 
182     public byte getUmapFreeRowNumber() {
183       return _umapFreeRowNumber;
184     }
185 
186     public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) {
187       _umapFreeRowNumber = newUmapFreeRowNumber;
188     }
189 
190     public int getUmapPageNumber() {
191       return _umapPageNumber;
192     }
193 
194     public void setUmapPageNumber(int newUmapPageNumber) {
195       _umapPageNumber = newUmapPageNumber;
196     }
197   }
198 
199   /**
200    * Maintains additional state used during index data creation.
201    * @usage _advanced_class_
202    */
203   static final class IndexDataState
204   {
205     private final List<IndexBuilder> _indexes = new ArrayList<IndexBuilder>();
206     private int _indexDataNumber;
207     private byte _umapRowNumber;
208     private int _umapPageNumber;
209     private int _rootPageNumber;
210 
211     public IndexBuilder getFirstIndex() {
212       // all indexes which have the same backing IndexDataState will have
213       // equivalent columns and flags.
214       return _indexes.get(0);
215     }
216 
217     public List<IndexBuilder> getIndexes() {
218       return _indexes;
219     }
220 
221     public void addIndex(IndexBuilder idx) {
222       _indexes.add(idx);
223     }
224 
225     public int getIndexDataNumber() {
226       return _indexDataNumber;
227     }
228 
229     public void setIndexDataNumber(int newIndexDataNumber) {
230       _indexDataNumber = newIndexDataNumber;
231     }
232 
233     public byte getUmapRowNumber() {
234       return _umapRowNumber;
235     }
236 
237     public void setUmapRowNumber(byte newUmapRowNumber) {
238       _umapRowNumber = newUmapRowNumber;
239     }
240 
241     public int getUmapPageNumber() {
242       return _umapPageNumber;
243     }
244 
245     public void setUmapPageNumber(int newUmapPageNumber) {
246       _umapPageNumber = newUmapPageNumber;
247     }
248 
249     public int getRootPageNumber() {
250       return _rootPageNumber;
251     }
252 
253     public void setRootPageNumber(int newRootPageNumber) {
254       _rootPageNumber = newRootPageNumber;
255     }
256   }
257 }