git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/mutateops@1002 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.1.5
@@ -26,7 +26,7 @@ import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||
import com.healthmarketscience.jackcess.impl.JetFormat; | |||
import com.healthmarketscience.jackcess.impl.PropertyMapImpl; | |||
import com.healthmarketscience.jackcess.impl.TableImpl; | |||
import com.healthmarketscience.jackcess.impl.TableMutator; | |||
import com.healthmarketscience.jackcess.impl.TableUpdater; | |||
/** | |||
* Builder style class for constructing a {@link Column}. See {@link | |||
@@ -479,9 +479,8 @@ public class ColumnBuilder { | |||
* Adds a new Column to the given Table with the currently configured | |||
* attributes. | |||
*/ | |||
public Column addToTable(Table table) throws IOException | |||
{ | |||
return new TableMutator((TableImpl)table).addColumn(this); | |||
public Column addToTable(Table table) throws IOException { | |||
return new TableUpdater((TableImpl)table).addColumn(this); | |||
} | |||
private String withErrorContext(String msg) { |
@@ -27,7 +27,7 @@ import com.healthmarketscience.jackcess.impl.IndexData; | |||
import com.healthmarketscience.jackcess.impl.IndexImpl; | |||
import com.healthmarketscience.jackcess.impl.JetFormat; | |||
import com.healthmarketscience.jackcess.impl.TableImpl; | |||
import com.healthmarketscience.jackcess.impl.TableMutator; | |||
import com.healthmarketscience.jackcess.impl.TableUpdater; | |||
/** | |||
* Builder style class for constructing an {@link Index}. See {@link | |||
@@ -199,9 +199,8 @@ public class IndexBuilder | |||
* Adds a new Index to the given Table with the currently configured | |||
* attributes. | |||
*/ | |||
public Index addToTable(Table table) throws IOException | |||
{ | |||
return new TableMutator((TableImpl)table).addIndex(this); | |||
public Index addToTable(Table table) throws IOException { | |||
return new TableUpdater((TableImpl)table).addIndex(this); | |||
} | |||
private String withErrorContext(String msg) { |
@@ -27,6 +27,10 @@ import java.util.List; | |||
*/ | |||
public interface Relationship | |||
{ | |||
public enum JoinType { | |||
INNER, LEFT_OUTER, RIGHT_OUTER; | |||
} | |||
public String getName(); | |||
public Table getFromTable(); | |||
@@ -50,4 +54,6 @@ public interface Relationship | |||
public boolean isLeftOuterJoin(); | |||
public boolean isRightOuterJoin(); | |||
public JoinType getJoinType(); | |||
} |
@@ -28,6 +28,7 @@ import java.util.Set; | |||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||
import com.healthmarketscience.jackcess.impl.PropertyMapImpl; | |||
import com.healthmarketscience.jackcess.impl.TableCreator; | |||
/** | |||
* Builder style class for constructing a {@link Table}. | |||
@@ -116,6 +117,9 @@ public class TableBuilder { | |||
} | |||
} | |||
public String getName() { | |||
return _name; | |||
} | |||
/** | |||
* Adds a Column to the new table. | |||
@@ -140,6 +144,10 @@ public class TableBuilder { | |||
return this; | |||
} | |||
public List<ColumnBuilder> getColumns() { | |||
return _columns; | |||
} | |||
/** | |||
* Adds an IndexBuilder to the new table. | |||
*/ | |||
@@ -154,6 +162,22 @@ public class TableBuilder { | |||
return this; | |||
} | |||
/** | |||
* Adds the Indexes to the new table. | |||
*/ | |||
public TableBuilder addIndexes(Collection<? extends IndexBuilder> indexes) { | |||
if(indexes != null) { | |||
for(IndexBuilder col : indexes) { | |||
addIndex(col); | |||
} | |||
} | |||
return this; | |||
} | |||
public List<IndexBuilder> getIndexes() { | |||
return _indexes; | |||
} | |||
/** | |||
* Sets whether or not subsequently added columns will have their names | |||
* automatically escaped | |||
@@ -202,35 +226,16 @@ public class TableBuilder { | |||
return this; | |||
} | |||
public Map<String,PropertyMap.Property> getProperties() { | |||
return _props; | |||
} | |||
/** | |||
* Creates a new Table in the given Database with the currently configured | |||
* attributes. | |||
*/ | |||
public Table toTable(Database db) | |||
throws IOException | |||
{ | |||
((DatabaseImpl)db).createTable(_name, _columns, _indexes); | |||
Table table = db.getTable(_name); | |||
boolean addedProps = false; | |||
if(_props != null) { | |||
table.getProperties().putAll(_props.values()); | |||
addedProps = true; | |||
} | |||
for(ColumnBuilder cb : _columns) { | |||
Map<String,PropertyMap.Property> colProps = cb.getProperties(); | |||
if(colProps != null) { | |||
table.getColumn(cb.getName()).getProperties().putAll(colProps.values()); | |||
addedProps = true; | |||
} | |||
} | |||
// all table and column props are saved together | |||
if(addedProps) { | |||
table.getProperties().save(); | |||
} | |||
return table; | |||
public Table toTable(Database db) throws IOException { | |||
return new TableCreator(((DatabaseImpl)db)).createTable(this); | |||
} | |||
/** |
@@ -1590,10 +1590,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
} | |||
protected static void writeDefinition( | |||
DBMutator mutator, ColumnBuilder col, ByteBuffer buffer) | |||
TableMutator mutator, ColumnBuilder col, ByteBuffer buffer) | |||
throws IOException | |||
{ | |||
DBMutator.ColumnOffsets colOffsets = mutator.getColumnOffsets(); | |||
TableMutator.ColumnOffsets colOffsets = mutator.getColumnOffsets(); | |||
buffer.put(col.getType().getValue()); | |||
buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); //constant magic number | |||
@@ -1671,10 +1671,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { | |||
} | |||
protected static void writeColUsageMapDefinition( | |||
DBMutator creator, ColumnBuilder lvalCol, ByteBuffer buffer) | |||
TableMutator creator, ColumnBuilder lvalCol, ByteBuffer buffer) | |||
throws IOException | |||
{ | |||
DBMutator.ColumnState colState = creator.getColumnState(lvalCol); | |||
TableMutator.ColumnState colState = creator.getColumnState(lvalCol); | |||
buffer.putShort(lvalCol.getColumnNumber()); | |||
@@ -18,23 +18,16 @@ package com.healthmarketscience.jackcess.impl; | |||
import java.io.IOException; | |||
import java.nio.charset.Charset; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Set; | |||
import com.healthmarketscience.jackcess.ColumnBuilder; | |||
import com.healthmarketscience.jackcess.DataType; | |||
import com.healthmarketscience.jackcess.IndexBuilder; | |||
/** | |||
* Helper class used to maintain state during database mutation. | |||
* Common helper class used to maintain state during database mutation. | |||
* | |||
* @author James Ahlborn | |||
*/ | |||
abstract class DBMutator | |||
{ | |||
private final DatabaseImpl _database; | |||
private ColumnOffsets _colOffsets; | |||
protected DBMutator(DatabaseImpl database) { | |||
_database = database; | |||
@@ -60,75 +53,11 @@ abstract class DBMutator | |||
return getPageChannel().allocateNewPage(); | |||
} | |||
public void setColumnOffsets( | |||
int fixedOffset, int varOffset, int longVarOffset) { | |||
if(_colOffsets == null) { | |||
_colOffsets = new ColumnOffsets(); | |||
} | |||
_colOffsets.set(fixedOffset, varOffset, longVarOffset); | |||
} | |||
public ColumnOffsets getColumnOffsets() { | |||
return _colOffsets; | |||
} | |||
public static int calculateNameLength(String name) { | |||
return (name.length() * JetFormat.TEXT_FIELD_UNIT_SIZE) + 2; | |||
} | |||
protected void validateColumn(Set<String> colNames, ColumnBuilder column) { | |||
// FIXME for now, we can't create complex columns | |||
if(column.getType() == DataType.COMPLEX_TYPE) { | |||
throw new UnsupportedOperationException( | |||
"Complex column creation is not yet implemented"); | |||
} | |||
column.validate(getFormat()); | |||
if(!colNames.add(column.getName().toUpperCase())) { | |||
throw new IllegalArgumentException("duplicate column name: " + | |||
column.getName()); | |||
} | |||
setColumnSortOrder(column); | |||
} | |||
protected void validateIndex(Set<String> colNames, Set<String> idxNames, | |||
boolean[] foundPk, IndexBuilder index) { | |||
index.validate(colNames, getFormat()); | |||
if(!idxNames.add(index.getName().toUpperCase())) { | |||
throw new IllegalArgumentException("duplicate index name: " + | |||
index.getName()); | |||
} | |||
if(index.isPrimaryKey()) { | |||
if(foundPk[0]) { | |||
throw new IllegalArgumentException( | |||
"found second primary key index: " + index.getName()); | |||
} | |||
foundPk[0] = true; | |||
} | |||
} | |||
protected static void validateAutoNumberColumn(Set<DataType> autoTypes, | |||
ColumnBuilder column) | |||
{ | |||
if(!column.getType().isMultipleAutoNumberAllowed() && | |||
!autoTypes.add(column.getType())) { | |||
throw new IllegalArgumentException( | |||
"Can have at most one AutoNumber column of type " + column.getType() + | |||
" per table"); | |||
} | |||
} | |||
private void setColumnSortOrder(ColumnBuilder column) { | |||
// set the sort order to the db default (if unspecified) | |||
if(column.getType().isTextual() && (column.getTextSortOrder() == null)) { | |||
column.setTextSortOrder(getDbSortOrder()); | |||
} | |||
} | |||
private ColumnImpl.SortOrder getDbSortOrder() { | |||
protected ColumnImpl.SortOrder getDbSortOrder() { | |||
try { | |||
return _database.getDefaultSortOrder(); | |||
} catch(IOException e) { | |||
@@ -136,140 +65,4 @@ abstract class DBMutator | |||
} | |||
return null; | |||
} | |||
abstract String getTableName(); | |||
public abstract int getTdefPageNumber(); | |||
abstract short getColumnNumber(String colName); | |||
public abstract ColumnState getColumnState(ColumnBuilder col); | |||
public abstract IndexDataState getIndexDataState(IndexBuilder idx); | |||
/** | |||
* Maintains additional state used during column writing. | |||
* @usage _advanced_class_ | |||
*/ | |||
static final class ColumnOffsets | |||
{ | |||
private short _fixedOffset; | |||
private short _varOffset; | |||
private short _longVarOffset; | |||
public void set(int fixedOffset, int varOffset, int longVarOffset) { | |||
_fixedOffset = (short)fixedOffset; | |||
_varOffset = (short)varOffset; | |||
_longVarOffset = (short)longVarOffset; | |||
} | |||
public short getNextVariableOffset(ColumnBuilder col) { | |||
if(!col.getType().isLongValue()) { | |||
return _varOffset++; | |||
} | |||
return _longVarOffset++; | |||
} | |||
public short getNextFixedOffset(ColumnBuilder col) { | |||
short offset = _fixedOffset; | |||
_fixedOffset += col.getType().getFixedSize(col.getLength()); | |||
return offset; | |||
} | |||
} | |||
/** | |||
* Maintains additional state used during column creation. | |||
* @usage _advanced_class_ | |||
*/ | |||
static final class ColumnState | |||
{ | |||
private byte _umapOwnedRowNumber; | |||
private byte _umapFreeRowNumber; | |||
// we always put both usage maps on the same page | |||
private int _umapPageNumber; | |||
public byte getUmapOwnedRowNumber() { | |||
return _umapOwnedRowNumber; | |||
} | |||
public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) { | |||
_umapOwnedRowNumber = newUmapOwnedRowNumber; | |||
} | |||
public byte getUmapFreeRowNumber() { | |||
return _umapFreeRowNumber; | |||
} | |||
public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) { | |||
_umapFreeRowNumber = newUmapFreeRowNumber; | |||
} | |||
public int getUmapPageNumber() { | |||
return _umapPageNumber; | |||
} | |||
public void setUmapPageNumber(int newUmapPageNumber) { | |||
_umapPageNumber = newUmapPageNumber; | |||
} | |||
} | |||
/** | |||
* Maintains additional state used during index data creation. | |||
* @usage _advanced_class_ | |||
*/ | |||
static final class IndexDataState | |||
{ | |||
private final List<IndexBuilder> _indexes = new ArrayList<IndexBuilder>(); | |||
private int _indexDataNumber; | |||
private byte _umapRowNumber; | |||
private int _umapPageNumber; | |||
private int _rootPageNumber; | |||
public IndexBuilder getFirstIndex() { | |||
// all indexes which have the same backing IndexDataState will have | |||
// equivalent columns and flags. | |||
return _indexes.get(0); | |||
} | |||
public List<IndexBuilder> getIndexes() { | |||
return _indexes; | |||
} | |||
public void addIndex(IndexBuilder idx) { | |||
_indexes.add(idx); | |||
} | |||
public int getIndexDataNumber() { | |||
return _indexDataNumber; | |||
} | |||
public void setIndexDataNumber(int newIndexDataNumber) { | |||
_indexDataNumber = newIndexDataNumber; | |||
} | |||
public byte getUmapRowNumber() { | |||
return _umapRowNumber; | |||
} | |||
public void setUmapRowNumber(byte newUmapRowNumber) { | |||
_umapRowNumber = newUmapRowNumber; | |||
} | |||
public int getUmapPageNumber() { | |||
return _umapPageNumber; | |||
} | |||
public void setUmapPageNumber(int newUmapPageNumber) { | |||
_umapPageNumber = newUmapPageNumber; | |||
} | |||
public int getRootPageNumber() { | |||
return _rootPageNumber; | |||
} | |||
public void setRootPageNumber(int newRootPageNumber) { | |||
_rootPageNumber = newRootPageNumber; | |||
} | |||
} | |||
} |
@@ -60,6 +60,7 @@ import com.healthmarketscience.jackcess.Relationship; | |||
import com.healthmarketscience.jackcess.Row; | |||
import com.healthmarketscience.jackcess.RuntimeIOException; | |||
import com.healthmarketscience.jackcess.Table; | |||
import com.healthmarketscience.jackcess.TableBuilder; | |||
import com.healthmarketscience.jackcess.TableMetaData; | |||
import com.healthmarketscience.jackcess.impl.query.QueryImpl; | |||
import com.healthmarketscience.jackcess.query.Query; | |||
@@ -1035,12 +1036,10 @@ public class DatabaseImpl implements Database | |||
List<IndexBuilder> indexes) | |||
throws IOException | |||
{ | |||
if(lookupTable(name) != null) { | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot create table with name of existing table '" + name + "'")); | |||
} | |||
new TableCreator(this, name, columns, indexes).createTable(); | |||
new TableBuilder(name) | |||
.addColumns(columns) | |||
.addIndexes(indexes) | |||
.toTable(this); | |||
} | |||
public void createLinkedTable(String name, String linkedDbName, | |||
@@ -1582,6 +1581,15 @@ public class DatabaseImpl implements Database | |||
} | |||
_pageChannel.close(); | |||
} | |||
public void validateNewTableName(String name) throws IOException { | |||
if(lookupTable(name) != null) { | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot create table with name of existing table '" + name + "'")); | |||
} | |||
validateIdentifierName(name, getFormat().MAX_TABLE_NAME_LENGTH, "table"); | |||
} | |||
/** | |||
* Validates an identifier name. |
@@ -492,7 +492,7 @@ public class IndexData { | |||
* @param idxCount num indexes to write | |||
*/ | |||
protected static void writeRowCountDefinitions( | |||
DBMutator creator, ByteBuffer buffer, int idxCount) | |||
TableMutator creator, ByteBuffer buffer, int idxCount) | |||
{ | |||
// index row counts (empty data) | |||
ByteUtil.forward(buffer, (idxCount * | |||
@@ -510,7 +510,7 @@ public class IndexData { | |||
{ | |||
ByteBuffer rootPageBuffer = createRootPageBuffer(creator); | |||
for(DBMutator.IndexDataState idxDataState : creator.getIndexDataStates()) { | |||
for(TableMutator.IndexDataState idxDataState : creator.getIndexDataStates()) { | |||
writeDefinition(creator, buffer, idxDataState, rootPageBuffer); | |||
} | |||
} | |||
@@ -521,8 +521,8 @@ public class IndexData { | |||
* @param buffer Buffer to write to | |||
*/ | |||
protected static void writeDefinition( | |||
DBMutator creator, ByteBuffer buffer, | |||
DBMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer) | |||
TableMutator creator, ByteBuffer buffer, | |||
TableMutator.IndexDataState idxDataState, ByteBuffer rootPageBuffer) | |||
throws IOException | |||
{ | |||
if(rootPageBuffer == null) { | |||
@@ -573,7 +573,7 @@ public class IndexData { | |||
ByteUtil.forward(buffer, 5); // unknown | |||
} | |||
private static ByteBuffer createRootPageBuffer(DBMutator creator) | |||
private static ByteBuffer createRootPageBuffer(TableMutator creator) | |||
throws IOException | |||
{ | |||
ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer(); |
@@ -351,10 +351,10 @@ public class IndexImpl implements Index, Comparable<IndexImpl> | |||
} | |||
protected static void writeDefinition( | |||
DBMutator mutator, IndexBuilder idx, ByteBuffer buffer) | |||
TableMutator mutator, IndexBuilder idx, ByteBuffer buffer) | |||
throws IOException | |||
{ | |||
DBMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx); | |||
TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx); | |||
// write logical index information | |||
buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def |
@@ -33,20 +33,20 @@ public class RelationshipImpl implements Relationship | |||
{ | |||
/** flag indicating one-to-one relationship */ | |||
private static final int ONE_TO_ONE_FLAG = 0x00000001; | |||
public static final int ONE_TO_ONE_FLAG = 0x00000001; | |||
/** flag indicating no referential integrity */ | |||
private static final int NO_REFERENTIAL_INTEGRITY_FLAG = 0x00000002; | |||
public static final int NO_REFERENTIAL_INTEGRITY_FLAG = 0x00000002; | |||
/** flag indicating cascading updates (requires referential integrity) */ | |||
private static final int CASCADE_UPDATES_FLAG = 0x00000100; | |||
public static final int CASCADE_UPDATES_FLAG = 0x00000100; | |||
/** flag indicating cascading deletes (requires referential integrity) */ | |||
private static final int CASCADE_DELETES_FLAG = 0x00001000; | |||
public static final int CASCADE_DELETES_FLAG = 0x00001000; | |||
/** flag indicating cascading null on delete (requires referential | |||
integrity) */ | |||
private static final int CASCADE_NULL_FLAG = 0x00002000; | |||
public static final int CASCADE_NULL_FLAG = 0x00002000; | |||
/** flag indicating left outer join */ | |||
private static final int LEFT_OUTER_JOIN_FLAG = 0x01000000; | |||
public static final int LEFT_OUTER_JOIN_FLAG = 0x01000000; | |||
/** flag indicating right outer join */ | |||
private static final int RIGHT_OUTER_JOIN_FLAG = 0x02000000; | |||
public static final int RIGHT_OUTER_JOIN_FLAG = 0x02000000; | |||
/** the name of this relationship */ | |||
private final String _name; | |||
@@ -127,6 +127,15 @@ public class RelationshipImpl implements Relationship | |||
public boolean isRightOuterJoin() { | |||
return hasFlag(RIGHT_OUTER_JOIN_FLAG); | |||
} | |||
public JoinType getJoinType() { | |||
if(isLeftOuterJoin()) { | |||
return JoinType.LEFT_OUTER; | |||
} else if(isRightOuterJoin()) { | |||
return JoinType.RIGHT_OUTER; | |||
} | |||
return JoinType.INNER; | |||
} | |||
private boolean hasFlag(int flagMask) { | |||
return((getFlags() & flagMask) != 0); |
@@ -29,6 +29,8 @@ import java.util.Set; | |||
import com.healthmarketscience.jackcess.ColumnBuilder; | |||
import com.healthmarketscience.jackcess.DataType; | |||
import com.healthmarketscience.jackcess.IndexBuilder; | |||
import com.healthmarketscience.jackcess.PropertyMap; | |||
import com.healthmarketscience.jackcess.TableBuilder; | |||
/** | |||
* Helper class used to maintain state during table creation. | |||
@@ -36,11 +38,11 @@ import com.healthmarketscience.jackcess.IndexBuilder; | |||
* @author James Ahlborn | |||
* @usage _advanced_class_ | |||
*/ | |||
class TableCreator extends DBMutator | |||
public class TableCreator extends TableMutator | |||
{ | |||
private final String _name; | |||
private final List<ColumnBuilder> _columns; | |||
private final List<IndexBuilder> _indexes; | |||
private String _name; | |||
private List<ColumnBuilder> _columns; | |||
private List<IndexBuilder> _indexes; | |||
private final List<IndexDataState> _indexDataStates = | |||
new ArrayList<IndexDataState>(); | |||
private final Map<ColumnBuilder,ColumnState> _columnStates = | |||
@@ -51,13 +53,8 @@ class TableCreator extends DBMutator | |||
private int _indexCount; | |||
private int _logicalIndexCount; | |||
public TableCreator(DatabaseImpl database, String name, | |||
List<ColumnBuilder> columns, List<IndexBuilder> indexes) { | |||
public TableCreator(DatabaseImpl database) { | |||
super(database); | |||
_name = name; | |||
_columns = columns; | |||
_indexes = ((indexes != null) ? indexes : | |||
Collections.<IndexBuilder>emptyList()); | |||
} | |||
public String getName() { | |||
@@ -153,7 +150,14 @@ class TableCreator extends DBMutator | |||
* Creates the table in the database. | |||
* @usage _advanced_method_ | |||
*/ | |||
public void createTable() throws IOException { | |||
public TableImpl createTable(TableBuilder table) throws IOException { | |||
_name = table.getName(); | |||
_columns = table.getColumns(); | |||
_indexes = table.getIndexes(); | |||
if(_indexes == null) { | |||
_indexes = Collections.<IndexBuilder>emptyList(); | |||
} | |||
validate(); | |||
@@ -190,6 +194,31 @@ class TableCreator extends DBMutator | |||
getDatabase().addNewTable(_name, _tdefPageNumber, DatabaseImpl.TYPE_TABLE, | |||
null, null); | |||
TableImpl newTable = getDatabase().getTable(_name); | |||
// add any table properties | |||
boolean addedProps = false; | |||
Map<String,PropertyMap.Property> props = table.getProperties(); | |||
if(props != null) { | |||
newTable.getProperties().putAll(props.values()); | |||
addedProps = true; | |||
} | |||
for(ColumnBuilder cb : _columns) { | |||
Map<String,PropertyMap.Property> colProps = cb.getProperties(); | |||
if(colProps != null) { | |||
newTable.getColumn(cb.getName()).getProperties() | |||
.putAll(colProps.values()); | |||
addedProps = true; | |||
} | |||
} | |||
// all table and column props are saved together | |||
if(addedProps) { | |||
newTable.getProperties().save(); | |||
} | |||
return newTable; | |||
} finally { | |||
getPageChannel().finishWrite(); | |||
} | |||
@@ -217,10 +246,9 @@ class TableCreator extends DBMutator | |||
/** | |||
* Validates the new table information before attempting creation. | |||
*/ | |||
private void validate() { | |||
private void validate() throws IOException { | |||
DatabaseImpl.validateIdentifierName( | |||
_name, getFormat().MAX_TABLE_NAME_LENGTH, "table"); | |||
getDatabase().validateNewTableName(_name); | |||
if((_columns == null) || _columns.isEmpty()) { | |||
throw new IllegalArgumentException( |
@@ -1017,7 +1017,7 @@ public class TableImpl implements Table | |||
private static void writeTableDefinitionBuffer( | |||
ByteBuffer buffer, int tdefPageNumber, | |||
DBMutator mutator, List<Integer> reservedPages) | |||
TableMutator mutator, List<Integer> reservedPages) | |||
throws IOException | |||
{ | |||
buffer.rewind(); | |||
@@ -1099,10 +1099,10 @@ public class TableImpl implements Table | |||
} | |||
/** | |||
* Writes a column defined by the given TableMutator to this table. | |||
* Writes a column defined by the given TableUpdater to this table. | |||
* @usage _advanced_method_ | |||
*/ | |||
protected ColumnImpl mutateAddColumn(TableMutator mutator) throws IOException | |||
protected ColumnImpl mutateAddColumn(TableUpdater mutator) throws IOException | |||
{ | |||
ColumnBuilder column = mutator.getColumn(); | |||
JetFormat format = mutator.getFormat(); | |||
@@ -1183,7 +1183,7 @@ public class TableImpl implements Table | |||
// allocate usage maps for the long value col | |||
Map.Entry<Integer,Integer> umapInfo = addUsageMaps(2, null); | |||
System.out.println("FOO created umap " + umapInfo); | |||
DBMutator.ColumnState colState = mutator.getColumnState(column); | |||
TableMutator.ColumnState colState = mutator.getColumnState(column); | |||
colState.setUmapPageNumber(umapInfo.getKey()); | |||
byte rowNum = umapInfo.getValue().byteValue(); | |||
colState.setUmapOwnedRowNumber(rowNum); | |||
@@ -1281,10 +1281,10 @@ public class TableImpl implements Table | |||
} | |||
/** | |||
* Writes a index defined by the given TableMutator to this table. | |||
* Writes a index defined by the given TableUpdater to this table. | |||
* @usage _advanced_method_ | |||
*/ | |||
protected IndexData mutateAddIndexData(TableMutator mutator) throws IOException | |||
protected IndexData mutateAddIndexData(TableUpdater mutator) throws IOException | |||
{ | |||
IndexBuilder index = mutator.getIndex(); | |||
JetFormat format = mutator.getFormat(); | |||
@@ -1326,7 +1326,7 @@ public class TableImpl implements Table | |||
format.SIZE_INDEX_COLUMN_BLOCK)); | |||
// allocate usage maps and root page | |||
DBMutator.IndexDataState idxDataState = mutator.getIndexDataState(index); | |||
TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(index); | |||
int rootPageNumber = getPageChannel().allocateNewPage(); | |||
Map.Entry<Integer,Integer> umapInfo = addUsageMaps(1, rootPageNumber); | |||
System.out.println("FOO created umap " + umapInfo); | |||
@@ -1404,10 +1404,10 @@ public class TableImpl implements Table | |||
} | |||
/** | |||
* Writes a index defined by the given TableMutator to this table. | |||
* Writes a index defined by the given TableUpdater to this table. | |||
* @usage _advanced_method_ | |||
*/ | |||
protected IndexImpl mutateAddIndex(TableMutator mutator) throws IOException | |||
protected IndexImpl mutateAddIndex(TableUpdater mutator) throws IOException | |||
{ | |||
IndexBuilder index = mutator.getIndex(); | |||
JetFormat format = mutator.getFormat(); | |||
@@ -1490,7 +1490,7 @@ public class TableImpl implements Table | |||
return newIdx; | |||
} | |||
private void validateTableDefUpdate(TableMutator mutator, ByteBuffer tableBuffer) | |||
private void validateTableDefUpdate(TableUpdater mutator, ByteBuffer tableBuffer) | |||
throws IOException | |||
{ | |||
if(!mutator.validateUpdatedTdef(tableBuffer)) { | |||
@@ -1522,7 +1522,7 @@ public class TableImpl implements Table | |||
} | |||
private ByteBuffer loadCompleteTableDefinitionBufferForUpdate( | |||
TableMutator mutator) | |||
TableUpdater mutator) | |||
throws IOException | |||
{ | |||
// load complete table definition | |||
@@ -1750,7 +1750,7 @@ public class TableImpl implements Table | |||
// index umap | |||
int indexIdx = i - 2; | |||
DBMutator.IndexDataState idxDataState = | |||
TableMutator.IndexDataState idxDataState = | |||
creator.getIndexDataStates().get(indexIdx); | |||
// allocate root page for the index | |||
@@ -1774,7 +1774,7 @@ public class TableImpl implements Table | |||
lvalColIdx /= 2; | |||
ColumnBuilder lvalCol = lvalCols.get(lvalColIdx); | |||
DBMutator.ColumnState colState = | |||
TableMutator.ColumnState colState = | |||
creator.getColumnState(lvalCol); | |||
umapBuf.put(rowStart, UsageMap.MAP_TYPE_INLINE); |
@@ -16,11 +16,7 @@ limitations under the License. | |||
package com.healthmarketscience.jackcess.impl; | |||
import java.io.IOException; | |||
import java.nio.ByteBuffer; | |||
import java.util.ArrayList; | |||
import java.util.EnumSet; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
@@ -29,253 +25,215 @@ import com.healthmarketscience.jackcess.DataType; | |||
import com.healthmarketscience.jackcess.IndexBuilder; | |||
/** | |||
* Helper class used to maintain state during table mutation. | |||
* Common helper class used to maintain state during table mutation. | |||
* | |||
* @author James Ahlborn | |||
* @usage _advanced_class_ | |||
*/ | |||
public class TableMutator extends DBMutator | |||
public abstract class TableMutator extends DBMutator | |||
{ | |||
private final TableImpl _table; | |||
private ColumnBuilder _column; | |||
private IndexBuilder _index; | |||
private int _origTdefLen; | |||
private int _addedTdefLen; | |||
private List<Integer> _nextPages = new ArrayList<Integer>(1); | |||
private ColumnState _colState; | |||
private IndexDataState _idxDataState; | |||
public TableMutator(TableImpl table) { | |||
super(table.getDatabase()); | |||
_table = table; | |||
} | |||
private ColumnOffsets _colOffsets; | |||
public ColumnBuilder getColumn() { | |||
return _column; | |||
protected TableMutator(DatabaseImpl database) { | |||
super(database); | |||
} | |||
public IndexBuilder getIndex() { | |||
return _index; | |||
public void setColumnOffsets( | |||
int fixedOffset, int varOffset, int longVarOffset) { | |||
if(_colOffsets == null) { | |||
_colOffsets = new ColumnOffsets(); | |||
} | |||
_colOffsets.set(fixedOffset, varOffset, longVarOffset); | |||
} | |||
@Override | |||
String getTableName() { | |||
return _table.getName(); | |||
} | |||
@Override | |||
public int getTdefPageNumber() { | |||
return _table.getTableDefPageNumber(); | |||
public ColumnOffsets getColumnOffsets() { | |||
return _colOffsets; | |||
} | |||
@Override | |||
short getColumnNumber(String colName) { | |||
for(ColumnImpl col : _table.getColumns()) { | |||
if(col.getName().equalsIgnoreCase(colName)) { | |||
return col.getColumnNumber(); | |||
protected void validateColumn(Set<String> colNames, ColumnBuilder column) { | |||
// FIXME for now, we can't create complex columns | |||
if(column.getType() == DataType.COMPLEX_TYPE) { | |||
throw new UnsupportedOperationException( | |||
"Complex column creation is not yet implemented"); | |||
} | |||
column.validate(getFormat()); | |||
if(!colNames.add(column.getName().toUpperCase())) { | |||
throw new IllegalArgumentException("duplicate column name: " + | |||
column.getName()); | |||
} | |||
} | |||
return IndexData.COLUMN_UNUSED; | |||
} | |||
@Override | |||
public ColumnState getColumnState(ColumnBuilder col) { | |||
return ((col == _column) ? _colState : null); | |||
setColumnSortOrder(column); | |||
} | |||
@Override | |||
public IndexDataState getIndexDataState(IndexBuilder idx) { | |||
return ((idx == _index) ? _idxDataState : null); | |||
protected void validateIndex(Set<String> colNames, Set<String> idxNames, | |||
boolean[] foundPk, IndexBuilder index) { | |||
index.validate(colNames, getFormat()); | |||
if(!idxNames.add(index.getName().toUpperCase())) { | |||
throw new IllegalArgumentException("duplicate index name: " + | |||
index.getName()); | |||
} | |||
if(index.isPrimaryKey()) { | |||
if(foundPk[0]) { | |||
throw new IllegalArgumentException( | |||
"found second primary key index: " + index.getName()); | |||
} | |||
foundPk[0] = true; | |||
} | |||
} | |||
int getAddedTdefLen() { | |||
return _addedTdefLen; | |||
protected static void validateAutoNumberColumn(Set<DataType> autoTypes, | |||
ColumnBuilder column) | |||
{ | |||
if(!column.getType().isMultipleAutoNumberAllowed() && | |||
!autoTypes.add(column.getType())) { | |||
throw new IllegalArgumentException( | |||
"Can have at most one AutoNumber column of type " + column.getType() + | |||
" per table"); | |||
} | |||
} | |||
void addTdefLen(int add) { | |||
_addedTdefLen += add; | |||
private void setColumnSortOrder(ColumnBuilder column) { | |||
// set the sort order to the db default (if unspecified) | |||
if(column.getType().isTextual() && (column.getTextSortOrder() == null)) { | |||
column.setTextSortOrder(getDbSortOrder()); | |||
} | |||
} | |||
void setOrigTdefLen(int len) { | |||
_origTdefLen = len; | |||
} | |||
abstract String getTableName(); | |||
List<Integer> getNextPages() { | |||
return _nextPages; | |||
} | |||
public abstract int getTdefPageNumber(); | |||
void resetTdefInfo() { | |||
_addedTdefLen = 0; | |||
_origTdefLen = 0; | |||
_nextPages.clear(); | |||
} | |||
abstract short getColumnNumber(String colName); | |||
public abstract ColumnState getColumnState(ColumnBuilder col); | |||
public ColumnImpl addColumn(ColumnBuilder column) throws IOException { | |||
public abstract IndexDataState getIndexDataState(IndexBuilder idx); | |||
_column = column; | |||
/** | |||
* Maintains additional state used during column writing. | |||
* @usage _advanced_class_ | |||
*/ | |||
static final class ColumnOffsets | |||
{ | |||
private short _fixedOffset; | |||
private short _varOffset; | |||
private short _longVarOffset; | |||
validateAddColumn(); | |||
public void set(int fixedOffset, int varOffset, int longVarOffset) { | |||
_fixedOffset = (short)fixedOffset; | |||
_varOffset = (short)varOffset; | |||
_longVarOffset = (short)longVarOffset; | |||
} | |||
// assign column number and do some assorted column bookkeeping | |||
short columnNumber = (short)_table.getMaxColumnCount(); | |||
_column.setColumnNumber(columnNumber); | |||
if(_column.getType().isLongValue()) { | |||
_colState = new ColumnState(); | |||
public short getNextVariableOffset(ColumnBuilder col) { | |||
if(!col.getType().isLongValue()) { | |||
return _varOffset++; | |||
} | |||
return _longVarOffset++; | |||
} | |||
getPageChannel().startExclusiveWrite(); | |||
try { | |||
return _table.mutateAddColumn(this); | |||
} finally { | |||
getPageChannel().finishWrite(); | |||
public short getNextFixedOffset(ColumnBuilder col) { | |||
short offset = _fixedOffset; | |||
_fixedOffset += col.getType().getFixedSize(col.getLength()); | |||
return offset; | |||
} | |||
} | |||
public IndexImpl addIndex(IndexBuilder index) throws IOException { | |||
_index = index; | |||
validateAddIndex(); | |||
// assign index number and do some assorted index bookkeeping | |||
int indexNumber = _table.getLogicalIndexCount(); | |||
_index.setIndexNumber(indexNumber); | |||
// find backing index state | |||
findIndexDataState(); | |||
getPageChannel().startExclusiveWrite(); | |||
try { | |||
if(_idxDataState.getIndexDataNumber() == _table.getIndexCount()) { | |||
// we need a new backing index data | |||
_table.mutateAddIndexData(this); | |||
// we need to modify the table def again when adding the Index, so reset | |||
resetTdefInfo(); | |||
} | |||
return _table.mutateAddIndex(this); | |||
/** | |||
* Maintains additional state used during column creation. | |||
* @usage _advanced_class_ | |||
*/ | |||
static final class ColumnState | |||
{ | |||
private byte _umapOwnedRowNumber; | |||
private byte _umapFreeRowNumber; | |||
// we always put both usage maps on the same page | |||
private int _umapPageNumber; | |||
} finally { | |||
getPageChannel().finishWrite(); | |||
public byte getUmapOwnedRowNumber() { | |||
return _umapOwnedRowNumber; | |||
} | |||
} | |||
boolean validateUpdatedTdef(ByteBuffer tableBuffer) { | |||
// sanity check the updates | |||
return((_origTdefLen + _addedTdefLen) == tableBuffer.limit()); | |||
} | |||
public void setUmapOwnedRowNumber(byte newUmapOwnedRowNumber) { | |||
_umapOwnedRowNumber = newUmapOwnedRowNumber; | |||
} | |||
private void validateAddColumn() { | |||
public byte getUmapFreeRowNumber() { | |||
return _umapFreeRowNumber; | |||
} | |||
if(_column == null) { | |||
throw new IllegalArgumentException("Cannot add column with no column"); | |||
public void setUmapFreeRowNumber(byte newUmapFreeRowNumber) { | |||
_umapFreeRowNumber = newUmapFreeRowNumber; | |||
} | |||
if((_table.getColumnCount() + 1) > getFormat().MAX_COLUMNS_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
"Cannot add column to table with " + | |||
getFormat().MAX_COLUMNS_PER_TABLE + " columns"); | |||
public int getUmapPageNumber() { | |||
return _umapPageNumber; | |||
} | |||
Set<String> colNames = getColumnNames(); | |||
// next, validate the column definition | |||
validateColumn(colNames, _column); | |||
if(_column.isAutoNumber()) { | |||
// for most autonumber types, we can only have one of each type | |||
Set<DataType> autoTypes = EnumSet.noneOf(DataType.class); | |||
for(ColumnImpl column : _table.getAutoNumberColumns()) { | |||
autoTypes.add(column.getType()); | |||
} | |||
validateAutoNumberColumn(autoTypes, _column); | |||
public void setUmapPageNumber(int newUmapPageNumber) { | |||
_umapPageNumber = newUmapPageNumber; | |||
} | |||
} | |||
private void validateAddIndex() { | |||
if(_index == null) { | |||
throw new IllegalArgumentException("Cannot add index with no index"); | |||
/** | |||
* Maintains additional state used during index data creation. | |||
* @usage _advanced_class_ | |||
*/ | |||
static final class IndexDataState | |||
{ | |||
private final List<IndexBuilder> _indexes = new ArrayList<IndexBuilder>(); | |||
private int _indexDataNumber; | |||
private byte _umapRowNumber; | |||
private int _umapPageNumber; | |||
private int _rootPageNumber; | |||
public IndexBuilder getFirstIndex() { | |||
// all indexes which have the same backing IndexDataState will have | |||
// equivalent columns and flags. | |||
return _indexes.get(0); | |||
} | |||
if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
"Cannot add index to table with " + | |||
getFormat().MAX_INDEXES_PER_TABLE + " indexes"); | |||
} | |||
boolean foundPk[] = new boolean[1]; | |||
Set<String> idxNames = getIndexNames(foundPk); | |||
// next, validate the index definition | |||
validateIndex(getColumnNames(), idxNames, foundPk, _index); | |||
} | |||
private Set<String> getColumnNames() { | |||
Set<String> colNames = new HashSet<String>(); | |||
for(ColumnImpl column : _table.getColumns()) { | |||
colNames.add(column.getName().toUpperCase()); | |||
public List<IndexBuilder> getIndexes() { | |||
return _indexes; | |||
} | |||
return colNames; | |||
} | |||
private Set<String> getIndexNames(boolean[] foundPk) { | |||
Set<String> idxNames = new HashSet<String>(); | |||
for(IndexImpl index : _table.getIndexes()) { | |||
idxNames.add(index.getName().toUpperCase()); | |||
if(index.isPrimaryKey()) { | |||
foundPk[0] = true; | |||
} | |||
public void addIndex(IndexBuilder idx) { | |||
_indexes.add(idx); | |||
} | |||
return idxNames; | |||
} | |||
private void findIndexDataState() { | |||
public int getIndexDataNumber() { | |||
return _indexDataNumber; | |||
} | |||
_idxDataState = new IndexDataState(); | |||
_idxDataState.addIndex(_index); | |||
// search for an existing index which matches the given index (in terms of | |||
// the backing data) | |||
for(IndexData idxData : _table.getIndexDatas()) { | |||
if(sameIndexData(_index, idxData)) { | |||
_idxDataState.setIndexDataNumber(idxData.getIndexDataNumber()); | |||
return; | |||
} | |||
public void setIndexDataNumber(int newIndexDataNumber) { | |||
_indexDataNumber = newIndexDataNumber; | |||
} | |||
// no matches found, need new index data state | |||
_idxDataState.setIndexDataNumber(_table.getIndexCount()); | |||
} | |||
public byte getUmapRowNumber() { | |||
return _umapRowNumber; | |||
} | |||
private static boolean sameIndexData(IndexBuilder idx1, IndexData idx2) { | |||
// index data can be combined if flags match and columns (and col flags) | |||
// match | |||
if(idx1.getFlags() != idx2.getIndexFlags()) { | |||
return false; | |||
public void setUmapRowNumber(byte newUmapRowNumber) { | |||
_umapRowNumber = newUmapRowNumber; | |||
} | |||
if(idx1.getColumns().size() != idx2.getColumns().size()) { | |||
return false; | |||
public int getUmapPageNumber() { | |||
return _umapPageNumber; | |||
} | |||
for(int i = 0; i < idx1.getColumns().size(); ++i) { | |||
IndexBuilder.Column col1 = idx1.getColumns().get(i); | |||
IndexData.ColumnDescriptor col2 = idx2.getColumns().get(i); | |||
if(!sameIndexData(col1, col2)) { | |||
return false; | |||
} | |||
public void setUmapPageNumber(int newUmapPageNumber) { | |||
_umapPageNumber = newUmapPageNumber; | |||
} | |||
return true; | |||
} | |||
public int getRootPageNumber() { | |||
return _rootPageNumber; | |||
} | |||
private static boolean sameIndexData( | |||
IndexBuilder.Column col1, IndexData.ColumnDescriptor col2) { | |||
return (col1.getName().equals(col2.getName()) && | |||
(col1.getFlags() == col2.getFlags())); | |||
} | |||
public void setRootPageNumber(int newRootPageNumber) { | |||
_rootPageNumber = newRootPageNumber; | |||
} | |||
} | |||
} |
@@ -0,0 +1,281 @@ | |||
/* | |||
Copyright (c) 2016 James Ahlborn | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
*/ | |||
package com.healthmarketscience.jackcess.impl; | |||
import java.io.IOException; | |||
import java.nio.ByteBuffer; | |||
import java.util.ArrayList; | |||
import java.util.EnumSet; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import com.healthmarketscience.jackcess.ColumnBuilder; | |||
import com.healthmarketscience.jackcess.DataType; | |||
import com.healthmarketscience.jackcess.IndexBuilder; | |||
/** | |||
* Helper class used to maintain state during table mutation. | |||
* | |||
* @author James Ahlborn | |||
* @usage _advanced_class_ | |||
*/ | |||
public class TableUpdater extends TableMutator | |||
{ | |||
private final TableImpl _table; | |||
private ColumnBuilder _column; | |||
private IndexBuilder _index; | |||
private int _origTdefLen; | |||
private int _addedTdefLen; | |||
private List<Integer> _nextPages = new ArrayList<Integer>(1); | |||
private ColumnState _colState; | |||
private IndexDataState _idxDataState; | |||
public TableUpdater(TableImpl table) { | |||
super(table.getDatabase()); | |||
_table = table; | |||
} | |||
public ColumnBuilder getColumn() { | |||
return _column; | |||
} | |||
public IndexBuilder getIndex() { | |||
return _index; | |||
} | |||
@Override | |||
String getTableName() { | |||
return _table.getName(); | |||
} | |||
@Override | |||
public int getTdefPageNumber() { | |||
return _table.getTableDefPageNumber(); | |||
} | |||
@Override | |||
short getColumnNumber(String colName) { | |||
for(ColumnImpl col : _table.getColumns()) { | |||
if(col.getName().equalsIgnoreCase(colName)) { | |||
return col.getColumnNumber(); | |||
} | |||
} | |||
return IndexData.COLUMN_UNUSED; | |||
} | |||
@Override | |||
public ColumnState getColumnState(ColumnBuilder col) { | |||
return ((col == _column) ? _colState : null); | |||
} | |||
@Override | |||
public IndexDataState getIndexDataState(IndexBuilder idx) { | |||
return ((idx == _index) ? _idxDataState : null); | |||
} | |||
int getAddedTdefLen() { | |||
return _addedTdefLen; | |||
} | |||
void addTdefLen(int add) { | |||
_addedTdefLen += add; | |||
} | |||
void setOrigTdefLen(int len) { | |||
_origTdefLen = len; | |||
} | |||
List<Integer> getNextPages() { | |||
return _nextPages; | |||
} | |||
void resetTdefInfo() { | |||
_addedTdefLen = 0; | |||
_origTdefLen = 0; | |||
_nextPages.clear(); | |||
} | |||
public ColumnImpl addColumn(ColumnBuilder column) throws IOException { | |||
_column = column; | |||
validateAddColumn(); | |||
// assign column number and do some assorted column bookkeeping | |||
short columnNumber = (short)_table.getMaxColumnCount(); | |||
_column.setColumnNumber(columnNumber); | |||
if(_column.getType().isLongValue()) { | |||
_colState = new ColumnState(); | |||
} | |||
getPageChannel().startExclusiveWrite(); | |||
try { | |||
return _table.mutateAddColumn(this); | |||
} finally { | |||
getPageChannel().finishWrite(); | |||
} | |||
} | |||
public IndexImpl addIndex(IndexBuilder index) throws IOException { | |||
_index = index; | |||
validateAddIndex(); | |||
// assign index number and do some assorted index bookkeeping | |||
int indexNumber = _table.getLogicalIndexCount(); | |||
_index.setIndexNumber(indexNumber); | |||
// find backing index state | |||
findIndexDataState(); | |||
getPageChannel().startExclusiveWrite(); | |||
try { | |||
if(_idxDataState.getIndexDataNumber() == _table.getIndexCount()) { | |||
// we need a new backing index data | |||
_table.mutateAddIndexData(this); | |||
// we need to modify the table def again when adding the Index, so reset | |||
resetTdefInfo(); | |||
} | |||
return _table.mutateAddIndex(this); | |||
} finally { | |||
getPageChannel().finishWrite(); | |||
} | |||
} | |||
boolean validateUpdatedTdef(ByteBuffer tableBuffer) { | |||
// sanity check the updates | |||
return((_origTdefLen + _addedTdefLen) == tableBuffer.limit()); | |||
} | |||
private void validateAddColumn() { | |||
if(_column == null) { | |||
throw new IllegalArgumentException("Cannot add column with no column"); | |||
} | |||
if((_table.getColumnCount() + 1) > getFormat().MAX_COLUMNS_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
"Cannot add column to table with " + | |||
getFormat().MAX_COLUMNS_PER_TABLE + " columns"); | |||
} | |||
Set<String> colNames = getColumnNames(); | |||
// next, validate the column definition | |||
validateColumn(colNames, _column); | |||
if(_column.isAutoNumber()) { | |||
// for most autonumber types, we can only have one of each type | |||
Set<DataType> autoTypes = EnumSet.noneOf(DataType.class); | |||
for(ColumnImpl column : _table.getAutoNumberColumns()) { | |||
autoTypes.add(column.getType()); | |||
} | |||
validateAutoNumberColumn(autoTypes, _column); | |||
} | |||
} | |||
private void validateAddIndex() { | |||
if(_index == null) { | |||
throw new IllegalArgumentException("Cannot add index with no index"); | |||
} | |||
if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
"Cannot add index to table with " + | |||
getFormat().MAX_INDEXES_PER_TABLE + " indexes"); | |||
} | |||
boolean foundPk[] = new boolean[1]; | |||
Set<String> idxNames = getIndexNames(foundPk); | |||
// next, validate the index definition | |||
validateIndex(getColumnNames(), idxNames, foundPk, _index); | |||
} | |||
private Set<String> getColumnNames() { | |||
Set<String> colNames = new HashSet<String>(); | |||
for(ColumnImpl column : _table.getColumns()) { | |||
colNames.add(column.getName().toUpperCase()); | |||
} | |||
return colNames; | |||
} | |||
private Set<String> getIndexNames(boolean[] foundPk) { | |||
Set<String> idxNames = new HashSet<String>(); | |||
for(IndexImpl index : _table.getIndexes()) { | |||
idxNames.add(index.getName().toUpperCase()); | |||
if(index.isPrimaryKey()) { | |||
foundPk[0] = true; | |||
} | |||
} | |||
return idxNames; | |||
} | |||
private void findIndexDataState() { | |||
_idxDataState = new IndexDataState(); | |||
_idxDataState.addIndex(_index); | |||
// search for an existing index which matches the given index (in terms of | |||
// the backing data) | |||
for(IndexData idxData : _table.getIndexDatas()) { | |||
if(sameIndexData(_index, idxData)) { | |||
_idxDataState.setIndexDataNumber(idxData.getIndexDataNumber()); | |||
return; | |||
} | |||
} | |||
// no matches found, need new index data state | |||
_idxDataState.setIndexDataNumber(_table.getIndexCount()); | |||
} | |||
private static boolean sameIndexData(IndexBuilder idx1, IndexData idx2) { | |||
// index data can be combined if flags match and columns (and col flags) | |||
// match | |||
if(idx1.getFlags() != idx2.getIndexFlags()) { | |||
return false; | |||
} | |||
if(idx1.getColumns().size() != idx2.getColumns().size()) { | |||
return false; | |||
} | |||
for(int i = 0; i < idx1.getColumns().size(); ++i) { | |||
IndexBuilder.Column col1 = idx1.getColumns().get(i); | |||
IndexData.ColumnDescriptor col2 = idx2.getColumns().get(i); | |||
if(!sameIndexData(col1, col2)) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
private static boolean sameIndexData( | |||
IndexBuilder.Column col1, IndexData.ColumnDescriptor col2) { | |||
return (col1.getName().equals(col2.getName()) && | |||
(col1.getFlags() == col2.getFlags())); | |||
} | |||
} |