diff options
author | James Ahlborn <jtahlborn@yahoo.com> | 2011-11-17 04:39:41 +0000 |
---|---|---|
committer | James Ahlborn <jtahlborn@yahoo.com> | 2011-11-17 04:39:41 +0000 |
commit | 329d12ecc1367db2fad16c3ea953884c5c78c568 (patch) | |
tree | f0a63a8579bc5ab4db0f6e649c8d6e697521306c /src/java/com | |
parent | ede3f486415327a427e1a6df72983ce8c4a640fa (diff) | |
download | jackcess-329d12ecc1367db2fad16c3ea953884c5c78c568.tar.gz jackcess-329d12ecc1367db2fad16c3ea953884c5c78c568.zip |
refactor table creation
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@596 f203690c-595d-4dc9-a70b-905162fa7fd2
Diffstat (limited to 'src/java/com')
8 files changed, 356 insertions, 225 deletions
diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java index 847fd2b..32a67ce 100644 --- a/src/java/com/healthmarketscience/jackcess/Column.java +++ b/src/java/com/healthmarketscience/jackcess/Column.java @@ -1988,10 +1988,10 @@ public class Column implements Comparable<Column> { * @param columns List of Columns to write definitions for */ protected static void writeDefinitions( - ByteBuffer buffer, List<Column> columns, JetFormat format, - Charset charset) + TableCreator creator, ByteBuffer buffer) throws IOException { + List<Column> columns = creator.getColumns(); short columnNumber = (short) 0; short fixedOffset = (short) 0; short variableOffset = (short) 0; @@ -2021,7 +2021,7 @@ public class Column implements Comparable<Column> { if(col.getType().isTextual()) { // this will write 4 bytes (note we don't support writing dbs which // use the text code page) - writeSortOrder(buffer, col.getTextSortOrder(), format); + writeSortOrder(buffer, col.getTextSortOrder(), creator.getFormat()); } else { if(col.getType().getHasScalePrecision()) { buffer.put(col.getPrecision()); // numeric precision @@ -2054,11 +2054,11 @@ public class Column implements Comparable<Column> { columnNumber++; if (LOG.isDebugEnabled()) { LOG.debug("Creating new column def block\n" + ByteUtil.toHexString( - buffer, position, format.SIZE_COLUMN_DEF_BLOCK)); + buffer, position, creator.getFormat().SIZE_COLUMN_DEF_BLOCK)); } } for (Column col : columns) { - Table.writeName(buffer, col.getName(), charset); + Table.writeName(buffer, col.getName(), creator.getCharset()); } } diff --git a/src/java/com/healthmarketscience/jackcess/Database.java b/src/java/com/healthmarketscience/jackcess/Database.java index 4eb0b9b..4f2f753 100644 --- a/src/java/com/healthmarketscience/jackcess/Database.java +++ b/src/java/com/healthmarketscience/jackcess/Database.java @@ -47,10 +47,8 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; -import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -1226,100 +1224,24 @@ public class Database List<IndexBuilder> indexes) throws IOException { - validateIdentifierName(name, _format.MAX_TABLE_NAME_LENGTH, "table"); - - if(getTable(name) != null) { + if(lookupTable(name) != null) { throw new IllegalArgumentException( "Cannot create table with name of existing table"); } - - if(columns.isEmpty()) { - throw new IllegalArgumentException( - "Cannot create table with no columns"); - } - if(columns.size() > _format.MAX_COLUMNS_PER_TABLE) { - throw new IllegalArgumentException( - "Cannot create table with more than " + - _format.MAX_COLUMNS_PER_TABLE + " columns"); - } - - Column.SortOrder dbSortOrder = null; - try { - dbSortOrder = getDefaultSortOrder(); - } catch(IOException e) { - // ignored, just use the jet format default - } - - Set<String> colNames = new HashSet<String>(); - // next, validate the column definitions - for(Column column : columns) { - - // 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(_format); - if(!colNames.add(column.getName().toUpperCase())) { - throw new IllegalArgumentException("duplicate column name: " + - column.getName()); - } - // set the sort order to the db default (if unspecified) - if(column.getType().isTextual() && (column.getTextSortOrder() == null)) { - column.setTextSortOrder(dbSortOrder); - } - } - - List<Column> autoCols = Table.getAutoNumberColumns(columns); - if(autoCols.size() > 1) { - // for most autonumber types, we can only have one of each type - Set<DataType> autoTypes = EnumSet.noneOf(DataType.class); - for(Column c : autoCols) { - if(!c.getType().isMultipleAutoNumberAllowed() && - !autoTypes.add(c.getType())) { - throw new IllegalArgumentException( - "Can have at most one AutoNumber column of type " + c.getType() + - " per table"); - } - } - } + new TableCreator(this, name, columns, indexes).createTable(); + } - if(indexes == null) { - indexes = Collections.emptyList(); - } - if(!indexes.isEmpty()) { - // now, validate the indexes - Set<String> idxNames = new HashSet<String>(); - boolean foundPk = false; - for(IndexBuilder index : indexes) { - index.validate(colNames); - if(!idxNames.add(index.getName().toUpperCase())) { - throw new IllegalArgumentException("duplicate index name: " + - index.getName()); - } - if(index.isPrimaryKey()) { - if(foundPk) { - throw new IllegalArgumentException( - "found second primary key index: " + index.getName()); - } - foundPk = true; - } - } - } - - //Write the tdef page to disk. - int tdefPageNumber = Table.writeTableDefinition(columns, indexes, - _pageChannel, _format, - getCharset()); - + /** + * Adds a newly created table to the relevant internal database structures. + */ + void addNewTable(String name, int tdefPageNumber) throws IOException { //Add this table to our internal list. addTable(name, Integer.valueOf(tdefPageNumber)); //Add this table to system tables addToSystemCatalog(name, tdefPageNumber); - addToAccessControlEntries(tdefPageNumber); + addToAccessControlEntries(tdefPageNumber); } /** diff --git a/src/java/com/healthmarketscience/jackcess/ImportFilter.java b/src/java/com/healthmarketscience/jackcess/ImportFilter.java index 3cb1416..6bcc620 100644 --- a/src/java/com/healthmarketscience/jackcess/ImportFilter.java +++ b/src/java/com/healthmarketscience/jackcess/ImportFilter.java @@ -34,7 +34,7 @@ import java.util.List; /** * Interface which allows customization of the behavior of the - * <code>Database<</code> import/copy methods. + * <code>Database</code> import/copy methods. * * @author James Ahlborn */ diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 532b4e1..88b5237 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -29,7 +29,6 @@ package com.healthmarketscience.jackcess; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import java.util.Collections; import java.util.List; import java.util.Map; @@ -394,14 +393,15 @@ public class Index implements Comparable<Index> { * @param indexes List of IndexBuilders to write definitions for */ protected static void writeDefinitions( - ByteBuffer buffer, List<IndexBuilder> indexes, Charset charset) + TableCreator creator, ByteBuffer buffer) throws IOException { // write logical index information - for(IndexBuilder idx : indexes) { + for(IndexBuilder idx : creator.getIndexes()) { + TableCreator.IndexState idxState = creator.getIndexState(idx); buffer.putInt(Table.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def - buffer.putInt(idx.getIndexNumber()); // index num - buffer.putInt(idx.getIndexDataNumber()); // index data num + buffer.putInt(idxState.getIndexNumber()); // index num + buffer.putInt(idxState.getIndexDataNumber()); // index data num buffer.put((byte)0); // related table type buffer.putInt(INVALID_INDEX_NUMBER); // related index num buffer.putInt(0); // related table definition page number @@ -412,8 +412,8 @@ public class Index implements Comparable<Index> { } // write index names - for(IndexBuilder idx : indexes) { - Table.writeName(buffer, idx.getName(), charset); + for(IndexBuilder idx : creator.getIndexes()) { + Table.writeName(buffer, idx.getName(), creator.getCharset()); } } diff --git a/src/java/com/healthmarketscience/jackcess/IndexBuilder.java b/src/java/com/healthmarketscience/jackcess/IndexBuilder.java index 11fa556..07ddd77 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexBuilder.java +++ b/src/java/com/healthmarketscience/jackcess/IndexBuilder.java @@ -44,13 +44,6 @@ public class IndexBuilder /** the names and orderings of the indexed columns */ private final List<Column> _columns = new ArrayList<Column>(); - // used by table definition writing code - private int _indexNumber; - private int _indexDataNumber; - private byte _umapRowNumber; - private int _umapPageNumber; - private int _rootPageNumber; - public IndexBuilder(String name) { _name = name; } @@ -161,46 +154,6 @@ public class IndexBuilder } } - protected int getIndexNumber() { - return _indexNumber; - } - - protected void setIndexNumber(int newIndexNumber) { - _indexNumber = newIndexNumber; - } - - protected int getIndexDataNumber() { - return _indexDataNumber; - } - - protected void setIndexDataNumber(int newIndexDataNumber) { - _indexDataNumber = newIndexDataNumber; - } - - protected byte getUmapRowNumber() { - return _umapRowNumber; - } - - protected void setUmapRowNumber(byte newUmapRowNumber) { - _umapRowNumber = newUmapRowNumber; - } - - protected int getUmapPageNumber() { - return _umapPageNumber; - } - - protected void setUmapPageNumber(int newUmapPageNumber) { - _umapPageNumber = newUmapPageNumber; - } - - protected int getRootPageNumber() { - return _rootPageNumber; - } - - protected void setRootPageNumber(int newRootPageNumber) { - _rootPageNumber = newRootPageNumber; - } - /** * Information about a column in this index (name and ordering). */ diff --git a/src/java/com/healthmarketscience/jackcess/IndexData.java b/src/java/com/healthmarketscience/jackcess/IndexData.java index b979315..383bf24 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexData.java +++ b/src/java/com/healthmarketscience/jackcess/IndexData.java @@ -438,10 +438,11 @@ public abstract class IndexData { * @param indexes List of IndexBuilders to write definitions for */ protected static void writeRowCountDefinitions( - ByteBuffer buffer, int indexCount, JetFormat format) + TableCreator creator, ByteBuffer buffer) { // index row counts (empty data) - ByteUtil.forward(buffer, (indexCount * format.SIZE_INDEX_DEFINITION)); + ByteUtil.forward(buffer, (creator.getIndexCount() * + creator.getFormat().SIZE_INDEX_DEFINITION)); } /** @@ -450,15 +451,14 @@ public abstract class IndexData { * @param indexes List of IndexBuilders to write definitions for */ protected static void writeDefinitions( - ByteBuffer buffer, List<Column> columns, List<IndexBuilder> indexes, - int tdefPageNumber, PageChannel pageChannel, JetFormat format) + TableCreator creator, ByteBuffer buffer) throws IOException { - ByteBuffer rootPageBuffer = pageChannel.createPageBuffer(); + ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer(); writeDataPage(rootPageBuffer, SimpleIndexData.NEW_ROOT_DATA_PAGE, - tdefPageNumber, format); + creator.getTdefPageNumber(), creator.getFormat()); - for(IndexBuilder idx : indexes) { + for(IndexBuilder idx : creator.getIndexes()) { buffer.putInt(MAGIC_INDEX_NUMBER); // seemingly constant magic value // write column information (always MAX_COLUMNS entries) @@ -475,7 +475,7 @@ public abstract class IndexData { flags = idxCol.getFlags(); // find actual table column number - for(Column col : columns) { + for(Column col : creator.getColumns()) { if(col.getName().equalsIgnoreCase(idxCol.getName())) { columnNumber = col.getColumnNumber(); break; @@ -492,13 +492,16 @@ public abstract class IndexData { buffer.put(flags); // column flags (e.g. ordering) } - buffer.put(idx.getUmapRowNumber()); // umap row - ByteUtil.put3ByteInt(buffer, idx.getUmapPageNumber()); // umap page + TableCreator.IndexState idxState = creator.getIndexState(idx); + + buffer.put(idxState.getUmapRowNumber()); // umap row + ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); // umap page // write empty root index page - pageChannel.writePage(rootPageBuffer, idx.getRootPageNumber()); + creator.getPageChannel().writePage(rootPageBuffer, + idxState.getRootPageNumber()); - buffer.putInt(idx.getRootPageNumber()); + buffer.putInt(idxState.getRootPageNumber()); buffer.putInt(0); // unknown buffer.put(idx.getFlags()); // index flags (unique, etc.) ByteUtil.forward(buffer, 5); // unknown @@ -1007,7 +1010,8 @@ public abstract class IndexData { /** * Returns a new Entry of the correct type for the given data and page type. */ - private Entry newEntry(ByteBuffer buffer, int entryLength, boolean isLeaf) + private static Entry newEntry(ByteBuffer buffer, int entryLength, + boolean isLeaf) throws IOException { if(isLeaf) { diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index e11c7f9..ba7b675 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -945,52 +945,35 @@ public class Table } /** - * Writes a new table defined by the given columns and indexes to the - * database. - * @return the first page of the new table's definition + * Writes a new table defined by the given TableCreator to the database. * @usage _advanced_method_ */ - public static int writeTableDefinition( - List<Column> columns, List<IndexBuilder> indexes, - PageChannel pageChannel, JetFormat format, Charset charset) + protected static void writeTableDefinition(TableCreator creator) throws IOException { - int indexCount = 0; - int logicalIndexCount = 0; - if(!indexes.isEmpty()) { - // sort out index numbers. for now, these values will always match - // (until we support writing foreign key indexes) - for(IndexBuilder idx : indexes) { - idx.setIndexNumber(logicalIndexCount++); - idx.setIndexDataNumber(indexCount++); - } - } - - // allocate first table def page - int tdefPageNumber = pageChannel.allocateNewPage(); - // first, create the usage map page - int usageMapPageNumber = - createUsageMapDefinitionBuffer(indexes, pageChannel, format); + createUsageMapDefinitionBuffer(creator); // next, determine how big the table def will be (in case it will be more // than one page) - int idxDataLen = (indexCount * (format.SIZE_INDEX_DEFINITION + - format.SIZE_INDEX_COLUMN_BLOCK)) + - (logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK); + JetFormat format = creator.getFormat(); + int idxDataLen = (creator.getIndexCount() * + (format.SIZE_INDEX_DEFINITION + + format.SIZE_INDEX_COLUMN_BLOCK)) + + (creator.getLogicalIndexCount() * format.SIZE_INDEX_INFO_BLOCK); int totalTableDefSize = format.SIZE_TDEF_HEADER + - (format.SIZE_COLUMN_DEF_BLOCK * columns.size()) + idxDataLen + - format.SIZE_TDEF_TRAILER; + (format.SIZE_COLUMN_DEF_BLOCK * creator.getColumns().size()) + + idxDataLen + format.SIZE_TDEF_TRAILER; // total up the amount of space used by the column and index names (2 // bytes per char + 2 bytes for the length) - for(Column col : columns) { + for(Column col : creator.getColumns()) { int nameByteLen = (col.getName().length() * JetFormat.TEXT_FIELD_UNIT_SIZE); totalTableDefSize += nameByteLen + 2; } - for(IndexBuilder idx : indexes) { + for(IndexBuilder idx : creator.getIndexes()) { int nameByteLen = (idx.getName().length() * JetFormat.TEXT_FIELD_UNIT_SIZE); totalTableDefSize += nameByteLen + 2; @@ -998,25 +981,23 @@ public class Table // now, create the table definition - ByteBuffer buffer = pageChannel.createBuffer(Math.max(totalTableDefSize, - format.PAGE_SIZE)); - writeTableDefinitionHeader(buffer, columns, usageMapPageNumber, - totalTableDefSize, indexCount, - logicalIndexCount, format); + PageChannel pageChannel = creator.getPageChannel(); + ByteBuffer buffer = pageChannel .createBuffer(Math.max(totalTableDefSize, + format.PAGE_SIZE)); + writeTableDefinitionHeader(creator, buffer, totalTableDefSize); - if(indexCount > 0) { + if(creator.hasIndexes()) { // index row counts - IndexData.writeRowCountDefinitions(buffer, indexCount, format); + IndexData.writeRowCountDefinitions(creator, buffer); } // column definitions - Column.writeDefinitions(buffer, columns, format, charset); + Column.writeDefinitions(creator, buffer); - if(indexCount > 0) { + if(creator.hasIndexes()) { // index and index data definitions - IndexData.writeDefinitions(buffer, columns, indexes, tdefPageNumber, - pageChannel, format); - Index.writeDefinitions(buffer, indexes, charset); + IndexData.writeDefinitions(creator, buffer); + Index.writeDefinitions(creator, buffer); } //End of tabledef @@ -1030,7 +1011,7 @@ public class Table buffer.putShort(format.OFFSET_FREE_SPACE, (short)(buffer.remaining() - 8)); // overwrite page free space // Write the tdef page to disk. - pageChannel.writePage(buffer, tdefPageNumber); + pageChannel.writePage(buffer, creator.getTdefPageNumber()); } else { @@ -1047,7 +1028,7 @@ public class Table // this is the first page. note, the first page already has the // page header, so no need to write it here - nextTdefPageNumber = tdefPageNumber; + nextTdefPageNumber = creator.getTdefPageNumber(); } else { @@ -1077,8 +1058,6 @@ public class Table } } - - return tdefPageNumber; } /** @@ -1086,11 +1065,11 @@ public class Table * @param columns List of Columns in the table */ private static void writeTableDefinitionHeader( - ByteBuffer buffer, List<Column> columns, - int usageMapPageNumber, int totalTableDefSize, - int indexCount, int logicalIndexCount, JetFormat format) + TableCreator creator, ByteBuffer buffer, int totalTableDefSize) throws IOException { + List<Column> columns = creator.getColumns(); + //Start writing the tdef writeTablePageHeader(buffer); buffer.putInt(totalTableDefSize); //Length of table def @@ -1105,17 +1084,17 @@ public class Table buffer.putShort((short) columns.size()); //Max columns a row will have buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table buffer.putShort((short) columns.size()); //Number of columns in table - buffer.putInt(logicalIndexCount); //Number of logical indexes in table - buffer.putInt(indexCount); //Number of indexes in table + buffer.putInt(creator.getLogicalIndexCount()); //Number of logical indexes in table + buffer.putInt(creator.getIndexCount()); //Number of indexes in table buffer.put((byte) 0); //Usage map row number - ByteUtil.put3ByteInt(buffer, usageMapPageNumber); //Usage map page number + ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); //Usage map page number buffer.put((byte) 1); //Free map row number - ByteUtil.put3ByteInt(buffer, usageMapPageNumber); //Free map page number + ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); //Free map page number if (LOG.isDebugEnabled()) { int position = buffer.position(); buffer.rewind(); LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString( - buffer, format.SIZE_TDEF_HEADER)); + buffer, creator.getFormat().SIZE_TDEF_HEADER)); buffer.position(position); } } @@ -1149,13 +1128,13 @@ public class Table * row 0, the "pages with free space" map is in row 1. Index usage maps are * in subsequent rows. */ - private static int createUsageMapDefinitionBuffer( - List<IndexBuilder> indexes, PageChannel pageChannel, JetFormat format) + private static void createUsageMapDefinitionBuffer(TableCreator creator) throws IOException { // 2 table usage maps plus 1 for each index - int umapNum = 2 + indexes.size(); + int umapNum = 2 + creator.getIndexCount(); + JetFormat format = creator.getFormat(); int usageMapRowLength = format.OFFSET_USAGE_MAP_START + format.USAGE_MAP_TABLE_BYTE_LENGTH; int freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE @@ -1166,8 +1145,9 @@ public class Table throw new IOException("FIXME attempting to write too many indexes"); } - int umapPageNumber = pageChannel.allocateNewPage(); + int umapPageNumber = creator.getUmapPageNumber(); + PageChannel pageChannel = creator.getPageChannel(); ByteBuffer rtn = pageChannel.createPageBuffer(); rtn.put(PageTypes.DATA); rtn.put((byte) 0x1); //Unknown @@ -1190,19 +1170,20 @@ public class Table rowStart -= usageMapRowLength; } - if(!indexes.isEmpty()) { + if(creator.hasIndexes()) { - for(int i = 0; i < indexes.size(); ++i) { - IndexBuilder idx = indexes.get(i); + for(int i = 0; i < creator.getIndexes().size(); ++i) { + IndexBuilder idx = creator.getIndexes().get(i); // allocate root page for the index int rootPageNumber = pageChannel.allocateNewPage(); int umapRowNum = i + 2; // stash info for later use - idx.setRootPageNumber(rootPageNumber); - idx.setUmapRowNumber((byte)umapRowNum); - idx.setUmapPageNumber(umapPageNumber); + TableCreator.IndexState idxState = creator.getIndexState(idx); + idxState.setRootPageNumber(rootPageNumber); + idxState.setUmapRowNumber((byte)umapRowNum); + idxState.setUmapPageNumber(umapPageNumber); // index map definition, including initial root page rtn.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart); @@ -1211,12 +1192,10 @@ public class Table rtn.put(rowStart + 5, (byte)1); rowStart -= usageMapRowLength; - } + } } pageChannel.writePage(rtn, umapPageNumber); - - return umapPageNumber; } /** diff --git a/src/java/com/healthmarketscience/jackcess/TableCreator.java b/src/java/com/healthmarketscience/jackcess/TableCreator.java new file mode 100644 index 0000000..556227c --- /dev/null +++ b/src/java/com/healthmarketscience/jackcess/TableCreator.java @@ -0,0 +1,273 @@ +/* +Copyright (c) 2011 James Ahlborn + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +USA +*/ + +package com.healthmarketscience.jackcess; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Helper class used to maintain state during table creation. + * + * @author James Ahlborn + * @usage _advanced_class_ + */ +class TableCreator +{ + private final Database _database; + private final String _name; + private final List<Column> _columns; + private final List<IndexBuilder> _indexes; + private final Map<IndexBuilder,IndexState> _indexStates = + new HashMap<IndexBuilder,IndexState>(); + private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER; + private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER; + private int _indexCount; + private int _logicalIndexCount; + + public TableCreator(Database database, String name, List<Column> columns, + List<IndexBuilder> indexes) { + _database = database; + _name = name; + _columns = columns; + _indexes = ((indexes != null) ? indexes : + Collections.<IndexBuilder>emptyList()); + } + + public JetFormat getFormat() { + return _database.getFormat(); + } + + public PageChannel getPageChannel() { + return _database.getPageChannel(); + } + + public Charset getCharset() { + return _database.getCharset(); + } + + public int getTdefPageNumber() { + return _tdefPageNumber; + } + + public int getUmapPageNumber() { + return _umapPageNumber; + } + + public List<Column> getColumns() { + return _columns; + } + + public List<IndexBuilder> getIndexes() { + return _indexes; + } + + public boolean hasIndexes() { + return !_indexes.isEmpty(); + } + + public int getIndexCount() { + return _indexCount; + } + + public int getLogicalIndexCount() { + return _logicalIndexCount; + } + + public IndexState getIndexState(IndexBuilder idx) { + return _indexStates.get(idx); + } + + public int reservePageNumber() throws IOException { + return getPageChannel().allocateNewPage(); + } + + /** + * Creates the table in the database. + * @usage _advanced_method_ + */ + public void createTable() throws IOException { + + validate(); + + if(hasIndexes()) { + // sort out index numbers. for now, these values will always match + // (until we support writing foreign key indexes) + for(IndexBuilder idx : _indexes) { + IndexState idxState = new IndexState(); + idxState.setIndexNumber(_logicalIndexCount++); + idxState.setIndexDataNumber(_indexCount++); + _indexStates.put(idx, idxState); + } + } + + // reserve some pages + _tdefPageNumber = reservePageNumber(); + _umapPageNumber = reservePageNumber(); + + //Write the tdef page to disk. + Table.writeTableDefinition(this); + + // update the database with the new table info + _database.addNewTable(_name, _tdefPageNumber); + } + + /** + * Validates the new table information before attempting creation. + */ + private void validate() { + + Database.validateIdentifierName( + _name, getFormat().MAX_TABLE_NAME_LENGTH, "table"); + + if((_columns == null) || _columns.isEmpty()) { + throw new IllegalArgumentException( + "Cannot create table with no columns"); + } + if(_columns.size() > getFormat().MAX_COLUMNS_PER_TABLE) { + throw new IllegalArgumentException( + "Cannot create table with more than " + + getFormat().MAX_COLUMNS_PER_TABLE + " columns"); + } + + Column.SortOrder dbSortOrder = null; + try { + dbSortOrder = _database.getDefaultSortOrder(); + } catch(IOException e) { + // ignored, just use the jet format default + } + + Set<String> colNames = new HashSet<String>(); + // next, validate the column definitions + for(Column column : _columns) { + + // 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()); + } + + // set the sort order to the db default (if unspecified) + if(column.getType().isTextual() && (column.getTextSortOrder() == null)) { + column.setTextSortOrder(dbSortOrder); + } + } + + List<Column> autoCols = Table.getAutoNumberColumns(_columns); + if(autoCols.size() > 1) { + // for most autonumber types, we can only have one of each type + Set<DataType> autoTypes = EnumSet.noneOf(DataType.class); + for(Column c : autoCols) { + if(!c.getType().isMultipleAutoNumberAllowed() && + !autoTypes.add(c.getType())) { + throw new IllegalArgumentException( + "Can have at most one AutoNumber column of type " + c.getType() + + " per table"); + } + } + } + + if(hasIndexes()) { + // now, validate the indexes + Set<String> idxNames = new HashSet<String>(); + boolean foundPk = false; + for(IndexBuilder index : _indexes) { + index.validate(colNames); + if(!idxNames.add(index.getName().toUpperCase())) { + throw new IllegalArgumentException("duplicate index name: " + + index.getName()); + } + if(index.isPrimaryKey()) { + if(foundPk) { + throw new IllegalArgumentException( + "found second primary key index: " + index.getName()); + } + foundPk = true; + } + } + } + } + + /** + * Maintains additional state used during index creation. + * @usage _advanced_class_ + */ + static final class IndexState + { + private int _indexNumber; + private int _indexDataNumber; + private byte _umapRowNumber; + private int _umapPageNumber; + private int _rootPageNumber; + + public int getIndexNumber() { + return _indexNumber; + } + + public void setIndexNumber(int newIndexNumber) { + _indexNumber = newIndexNumber; + } + + 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; + } + + } +} |