All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.healthmarketscience.jackcess.impl.TableCreator Maven / Gradle / Ivy

/*
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.impl;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
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 creation.
 *
 * @author James Ahlborn
 * @usage _advanced_class_
 */
class TableCreator 
{
  private final DatabaseImpl _database;
  private final String _name;
  private final List _columns;
  private final List _indexes;
  private final Map _indexStates = 
    new IdentityHashMap();
  private final Map _columnStates = 
    new IdentityHashMap();
  private final List _lvalCols = new ArrayList();
  private int _tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
  private int _umapPageNumber = PageChannel.INVALID_PAGE_NUMBER;
  private int _indexCount;
  private int _logicalIndexCount;

  public TableCreator(DatabaseImpl database, String name, List columns,
                      List indexes) {
    _database = database;
    _name = name;
    _columns = columns;
    _indexes = ((indexes != null) ? indexes : 
                Collections.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 getColumns() {
    return _columns;
  }

  public List 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();
  }

  public ColumnState getColumnState(ColumnBuilder col) {
    return _columnStates.get(col);
  }

  public List getLongValueColumns() {
    return _lvalCols;
  }

  /**
   * Creates the table in the database.
   * @usage _advanced_method_
   */
  public void createTable() throws IOException {

    validate();

    // assign column numbers and do some assorted column bookkeeping
    short columnNumber = (short) 0;
    for(ColumnBuilder col : _columns) {
      col.setColumnNumber(columnNumber++);
      if(col.getType().isLongValue()) {
        _lvalCols.add(col);
        // only lval columns need extra state
        _columnStates.put(col, new ColumnState());
      }
    }

    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);
      }
    }

    getPageChannel().startWrite();
    try {
      
      // reserve some pages
      _tdefPageNumber = reservePageNumber();
      _umapPageNumber = reservePageNumber();
    
      //Write the tdef page to disk.
      TableImpl.writeTableDefinition(this);

      // update the database with the new table info
      _database.addNewTable(_name, _tdefPageNumber, DatabaseImpl.TYPE_TABLE, null, null);

    } finally {
      getPageChannel().finishWrite();
    }
  }

  /**
   * Validates the new table information before attempting creation.
   */
  private void validate() {

    DatabaseImpl.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");
    }
    
    ColumnImpl.SortOrder dbSortOrder = null;
    try {
      dbSortOrder = _database.getDefaultSortOrder();
    } catch(IOException e) {
      // ignored, just use the jet format default
    }

    Set colNames = new HashSet();
    // next, validate the column definitions
    for(ColumnBuilder 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 autoCols = getAutoNumberColumns();
    if(autoCols.size() > 1) {
      // for most autonumber types, we can only have one of each type
      Set autoTypes = EnumSet.noneOf(DataType.class);
      for(ColumnBuilder 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()) {

      if(_indexes.size() > getFormat().MAX_INDEXES_PER_TABLE) {
        throw new IllegalArgumentException(
            "Cannot create table with more than " +
            getFormat().MAX_INDEXES_PER_TABLE + " indexes");
      }

      // now, validate the indexes
      Set idxNames = new HashSet();
      boolean foundPk = false;
      for(IndexBuilder index : _indexes) {
        index.validate(colNames, getFormat());
        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;
        }
      }
    }
  }

  private List getAutoNumberColumns() 
  {
    List autoCols = new ArrayList(1);
    for(ColumnBuilder c : _columns) {
      if(c.isAutoNumber()) {
        autoCols.add(c);
      }
    }
    return autoCols;
  }

  /**
   * 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;
    }
  }
    
  /**
   * 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;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy