git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/mutateops@1006 f203690c-595d-4dc9-a70b-905162fa7fd2tags/jackcess-2.1.5
@@ -384,12 +384,12 @@ public class ColumnBuilder { | |||
* @usage _advanced_method_ | |||
*/ | |||
public void validate(JetFormat format) { | |||
if(getType() == null) { | |||
throw new IllegalArgumentException(withErrorContext("must have type")); | |||
} | |||
DatabaseImpl.validateIdentifierName( | |||
getName(), format.MAX_COLUMN_NAME_LENGTH, "column"); | |||
if(getType() == null) { | |||
throw new IllegalArgumentException(withErrorContext("must have type")); | |||
} | |||
if(getType().isUnsupported()) { | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot create column with unsupported type " + getType())); |
@@ -17,45 +17,91 @@ limitations under the License. | |||
package com.healthmarketscience.jackcess; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import com.healthmarketscience.jackcess.impl.DatabaseImpl; | |||
import com.healthmarketscience.jackcess.impl.RelationshipCreator; | |||
import com.healthmarketscience.jackcess.impl.RelationshipImpl; | |||
/** | |||
* Builder style class for constructing a {@link Relationship}, and, | |||
* optionally, the associated backing foreign key (if referential integrity | |||
* enforcement is enabled). | |||
* | |||
* @author James Ahlborn | |||
*/ | |||
public class RelationshipBuilder | |||
{ | |||
private static final int JOIN_FLAGS = | |||
RelationshipImpl.LEFT_OUTER_JOIN_FLAG | | |||
RelationshipImpl.RIGHT_OUTER_JOIN_FLAG; | |||
/** relationship flags (default to "don't enforce") */ | |||
private int _flags = RelationshipImpl.NO_REFERENTIAL_INTEGRITY_FLAG; | |||
private String _toTable; | |||
private String _fromTable; | |||
private List<String> _toCols = new ArrayList<String>(); | |||
private List<String> _fromCols = new ArrayList<String>(); | |||
// - primary table must have unique index | |||
// - primary table index name ".rC", ".rD"... | |||
// - secondary index name "<PTable><STable>" | |||
// - add <name>1, <name>2 after names to make unique (index names and | |||
// relationship names) | |||
public RelationshipBuilder() | |||
public RelationshipBuilder(String toTable, String fromTable) | |||
{ | |||
_toTable = toTable; | |||
_fromTable = fromTable; | |||
} | |||
/** | |||
* Adds a pair of columns to the relationship. | |||
*/ | |||
public RelationshipBuilder addColumns(String toCol, String fromCol) { | |||
_toCols.add(toCol); | |||
_fromCols.add(fromCol); | |||
return this; | |||
} | |||
/** | |||
* Enables referential integrity enforcement for this relationship. | |||
* | |||
* Note, this requires the "to" table to have an existing unique index on | |||
* the relevant columns. | |||
*/ | |||
public RelationshipBuilder setReferentialIntegrity() { | |||
return clearFlag(RelationshipImpl.NO_REFERENTIAL_INTEGRITY_FLAG); | |||
} | |||
/** | |||
* Enables deletes to be cascaded from the "to" table to the "from" table. | |||
* | |||
* Note, this requires referential integrity to be enforced. | |||
*/ | |||
public RelationshipBuilder setCascadeDeletes() { | |||
return setFlag(RelationshipImpl.CASCADE_DELETES_FLAG); | |||
} | |||
/** | |||
* Enables updates to be cascaded from the "to" table to the "from" table. | |||
* | |||
* Note, this requires referential integrity to be enforced. | |||
*/ | |||
public RelationshipBuilder setCascadeUpdates() { | |||
return setFlag(RelationshipImpl.CASCADE_UPDATES_FLAG); | |||
} | |||
/** | |||
* Enables deletes in the "to" table to be cascaded as "null" to the "from" | |||
* table. | |||
* | |||
* Note, this requires referential integrity to be enforced. | |||
*/ | |||
public RelationshipBuilder setCascadeNullOnDelete() { | |||
return setFlag(RelationshipImpl.CASCADE_NULL_FLAG); | |||
} | |||
/** | |||
* Sets the preferred join type for this relationship. | |||
*/ | |||
public RelationshipBuilder setJoinType(Relationship.JoinType joinType) { | |||
clearFlag(JOIN_FLAGS); | |||
switch(joinType) { | |||
case INNER: | |||
// nothing to do | |||
@@ -80,6 +126,22 @@ public class RelationshipBuilder | |||
return _flags; | |||
} | |||
public String getToTable() { | |||
return _toTable; | |||
} | |||
public String getFromTable() { | |||
return _fromTable; | |||
} | |||
public List<String> getToColumns() { | |||
return _toCols; | |||
} | |||
public List<String> getFromColumns() { | |||
return _fromCols; | |||
} | |||
/** | |||
* Creates a new Relationship in the given Database with the currently | |||
* configured attributes. | |||
@@ -87,10 +149,7 @@ public class RelationshipBuilder | |||
public Relationship toRelationship(Database db) | |||
throws IOException | |||
{ | |||
// FIXME writeme | |||
return null; | |||
return new RelationshipCreator((DatabaseImpl)db).createRelationship(this); | |||
} | |||
private RelationshipBuilder setFlag(int flagMask) { |
@@ -17,6 +17,7 @@ limitations under the License. | |||
package com.healthmarketscience.jackcess.impl; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Set; | |||
@@ -29,12 +30,23 @@ import com.healthmarketscience.jackcess.RelationshipBuilder; | |||
*/ | |||
public class RelationshipCreator extends DBMutator | |||
{ | |||
private final static int CASCADE_FLAGS = | |||
RelationshipImpl.CASCADE_DELETES_FLAG | | |||
RelationshipImpl.CASCADE_UPDATES_FLAG | | |||
RelationshipImpl.CASCADE_NULL_FLAG; | |||
private TableImpl _primaryTable; | |||
private TableImpl _secondaryTable; | |||
private RelationshipBuilder _relationship; | |||
private List<ColumnImpl> _primaryCols; | |||
private List<ColumnImpl> _secondaryCols; | |||
private int _flags; | |||
// - primary table must have unique index | |||
// - primary table index name ".rC", ".rD"... | |||
// - secondary index name "<PTable><STable>" | |||
// - add <name>1, <name>2 after names to make unique (index names and | |||
// relationship names) | |||
public RelationshipCreator(DatabaseImpl database) | |||
{ | |||
@@ -66,7 +78,7 @@ public class RelationshipCreator extends DBMutator | |||
throws IOException | |||
{ | |||
_relationship = relationship; | |||
validate(); | |||
getPageChannel().startExclusiveWrite(); | |||
@@ -85,22 +97,26 @@ public class RelationshipCreator extends DBMutator | |||
private void validate() throws IOException { | |||
_primaryTable = getDatabase().getTable(_relationship.getToTable()); | |||
_secondaryTable = getDatabase().getTable(_relationship.getToTable()); | |||
if((_primaryTable == null) || (_secondaryTable == null)) { | |||
throw new IllegalArgumentException( | |||
"Two tables are required in relationship"); | |||
} | |||
if(_primaryTable.getDatabase() != _secondaryTable.getDatabase()) { | |||
throw new IllegalArgumentException("Tables are not from same database"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Two valid tables are required in relationship")); | |||
} | |||
_primaryCols = getColumns(_primaryTable, _relationship.getToColumns()); | |||
_secondaryCols = getColumns(_secondaryTable, _relationship.getFromColumns()); | |||
if((_primaryCols == null) || (_primaryCols.isEmpty()) || | |||
(_secondaryCols == null) || (_secondaryCols.isEmpty())) { | |||
throw new IllegalArgumentException("Missing columns in relationship"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Missing columns in relationship")); | |||
} | |||
if(_primaryCols.size() != _secondaryCols.size()) { | |||
throw new IllegalArgumentException( | |||
"Must have same number of columns on each side of relationship"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Must have same number of columns on each side of relationship")); | |||
} | |||
for(int i = 0; i < _primaryCols.size(); ++i) { | |||
@@ -108,12 +124,18 @@ public class RelationshipCreator extends DBMutator | |||
ColumnImpl scol = _primaryCols.get(i); | |||
if(pcol.getType() != scol.getType()) { | |||
throw new IllegalArgumentException( | |||
"Matched columns must have the same data type"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Matched columns must have the same data type")); | |||
} | |||
} | |||
if(!_relationship.hasReferentialIntegrity()) { | |||
if((_relationship.getFlags() & CASCADE_FLAGS) != 0) { | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cascade flags cannot be enabled if referential integrity is not enforced")); | |||
} | |||
return; | |||
} | |||
@@ -123,7 +145,8 @@ public class RelationshipCreator extends DBMutator | |||
// - cols come from right tables, tables from right db | |||
// - (cols can be duped in index) | |||
// - cols have same data types | |||
// - if enforce, require unique index on primary (auto-create?), index on secondary | |||
// - if enforce, require unique index on primary, | |||
// - auto-create index on secondary | |||
// - advanced, check for enforce cycles? | |||
// - index must be ascending | |||
@@ -186,4 +209,38 @@ public class RelationshipCreator extends DBMutator | |||
} | |||
} | |||
} | |||
private static List<ColumnImpl> getColumns(TableImpl table, List<String> colNames) { | |||
List<ColumnImpl> cols = new ArrayList<ColumnImpl>(); | |||
for(String colName : colNames) { | |||
cols.add(table.getColumn(colName)); | |||
} | |||
return cols; | |||
} | |||
private static String getTableErrorContext( | |||
TableImpl table, List<ColumnImpl> cols, | |||
String tableName, List<String> colNames) { | |||
if(table != null) { | |||
tableName = table.getName(); | |||
} | |||
if(cols != null) { | |||
colNames = new ArrayList<String>(); | |||
for(ColumnImpl col : cols) { | |||
colNames.add(col.getName()); | |||
} | |||
} | |||
return CustomToStringStyle.valueBuilder(tableName) | |||
.append(null, cols) | |||
.toString(); | |||
} | |||
private String withErrorContext(String msg) { | |||
return msg + "(Rel=" + | |||
getTableErrorContext(_primaryTable, _primaryCols, _relationship.getToTable(), | |||
_relationship.getToColumns()) + " <- " + | |||
getTableErrorContext(_secondaryTable, _secondaryCols, _relationship.getFromTable(), | |||
_relationship.getFromColumns()) + ")"; | |||
} | |||
} |
@@ -104,7 +104,8 @@ public class TableCreator extends TableMutator | |||
} | |||
} | |||
} | |||
throw new IllegalStateException("could not find state for index"); | |||
throw new IllegalStateException(withErrorContext( | |||
"could not find state for index")); | |||
} | |||
public List<IndexDataState> getIndexDataStates() { | |||
@@ -251,13 +252,13 @@ public class TableCreator extends TableMutator | |||
getDatabase().validateNewTableName(_name); | |||
if((_columns == null) || _columns.isEmpty()) { | |||
throw new IllegalArgumentException( | |||
"Cannot create table with no columns"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot create table with no columns")); | |||
} | |||
if(_columns.size() > getFormat().MAX_COLUMNS_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot create table with more than " + | |||
getFormat().MAX_COLUMNS_PER_TABLE + " columns"); | |||
getFormat().MAX_COLUMNS_PER_TABLE + " columns")); | |||
} | |||
Set<String> colNames = new HashSet<String>(); | |||
@@ -278,9 +279,9 @@ public class TableCreator extends TableMutator | |||
if(hasIndexes()) { | |||
if(_indexes.size() > getFormat().MAX_INDEXES_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot create table with more than " + | |||
getFormat().MAX_INDEXES_PER_TABLE + " indexes"); | |||
getFormat().MAX_INDEXES_PER_TABLE + " indexes")); | |||
} | |||
// now, validate the indexes | |||
@@ -332,4 +333,7 @@ public class TableCreator extends TableMutator | |||
(col1.getFlags() == col2.getFlags())); | |||
} | |||
private String withErrorContext(String msg) { | |||
return msg + "(Table=" + getName() + ")"; | |||
} | |||
} |
@@ -173,12 +173,13 @@ public class TableUpdater extends TableMutator | |||
private void validateAddColumn() { | |||
if(_column == null) { | |||
throw new IllegalArgumentException("Cannot add column with no column"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot add column with no column")); | |||
} | |||
if((_table.getColumnCount() + 1) > getFormat().MAX_COLUMNS_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot add column to table with " + | |||
getFormat().MAX_COLUMNS_PER_TABLE + " columns"); | |||
getFormat().MAX_COLUMNS_PER_TABLE + " columns")); | |||
} | |||
Set<String> colNames = getColumnNames(); | |||
@@ -199,12 +200,13 @@ public class TableUpdater extends TableMutator | |||
private void validateAddIndex() { | |||
if(_index == null) { | |||
throw new IllegalArgumentException("Cannot add index with no index"); | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot add index with no index")); | |||
} | |||
if((_table.getLogicalIndexCount() + 1) > getFormat().MAX_INDEXES_PER_TABLE) { | |||
throw new IllegalArgumentException( | |||
throw new IllegalArgumentException(withErrorContext( | |||
"Cannot add index to table with " + | |||
getFormat().MAX_INDEXES_PER_TABLE + " indexes"); | |||
getFormat().MAX_INDEXES_PER_TABLE + " indexes")); | |||
} | |||
boolean foundPk[] = new boolean[1]; | |||
@@ -291,4 +293,14 @@ public class TableUpdater extends TableMutator | |||
((col1.getFlags() | ignoreColFlags) == | |||
(col2.getFlags() | ignoreColFlags))); | |||
} | |||
private String withErrorContext(String msg) { | |||
String objStr = ""; | |||
if(_column != null) { | |||
objStr = ";Column=" + _column.getName(); | |||
} else if(_index != null) { | |||
objStr = ";Index=" + _index.getName(); | |||
} | |||
return msg + "(Table=" + _table.getName() + objStr + ")"; | |||
} | |||
} |