DataTable.java :  » Database-DBMS » mckoi » com » mckoi » database » Java Open Source

Java Open Source » Database DBMS » mckoi 
mckoi » com » mckoi » database » DataTable.java
/**
 * com.mckoi.database.DataTable  08 Mar 1998
 *
 * Mckoi SQL Database ( http://www.mckoi.com/database )
 * Copyright (C) 2000, 2001, 2002  Diehl and Associates, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * Version 2 as published by the Free Software Foundation.
 *
 * This program 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 General Public License Version 2 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * Version 2 along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Change Log:
 * 
 * 
 */

package com.mckoi.database;

import com.mckoi.database.global.SQLTypes;
import com.mckoi.util.IntegerVector;
import com.mckoi.debug.*;
import java.math.BigDecimal;
import java.util.Vector;
import java.util.ArrayList;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.File;

/**
 * DataTable is a wrapper for a MutableTableDataSource that fits into the
 * query hierarchy level.  A DataTable represents a table within a
 * transaction.  Adding, removing rows to a DataTable will change the
 * contents only with the context of the transaction the table was created in.
 * <p>
 * @author Tobias Downer
 */

public final class DataTable extends DefaultDataTable {

  /**
   * The DatabaseConnection object that is the parent of this DataTable.
   */
  private DatabaseConnection connection;

  /**
   * A low level access to the underlying transactional data source.
   */
  private MutableTableDataSource data_source;


  /**
   * ------
   * NOTE: Following values are only kept for lock debugging reasons.  These
   *   is no technical reason why they shouldn't be removed.  They allow us
   *   to check that a data table is locked correctly when accesses are
   *   performed on it.
   * ------
   */

  final static boolean LOCK_DEBUG = true;

  /**
   * The number of read locks we have on this table.
   */
  private int debug_read_lock_count = 0;

  /**
   * The number of write locks we have on this table (this should only ever be
   * 0 or 1).
   */
  private int debug_write_lock_count = 0;





  /**
   * Cosntructs the data table.
   */
  DataTable(DatabaseConnection connection,
            MutableTableDataSource data_source) throws DatabaseException {
    super(connection.getDatabase());
    this.connection = connection;
    this.data_source = data_source;
  }

  /**
   * Convenience - used to log debug messages.
   */
  public final DebugLogger Debug() {
    return connection.getSystem().Debug();
  }

  /**
   * Overwritten from DefaultDataTable to do nothing.  All selectable
   * schemes are handled within the DataTableManager now.
   */
  protected void blankSelectableSchemes(int type) {
  }

  /**
   * Returns the SelectableScheme for the given column.
   * (Overridden from DefaultDataTable).  If the schemes are not in memory then
   * they are loaded now.  This will synchronize over the 'table_manager'
   * which will effectively block this table at the lowest layer until the
   * indices are loaded into memory.
   */
  protected SelectableScheme getRootColumnScheme(int column) {
    checkReadLock();  // Read op

    return data_source.getColumnScheme(column);
  }

  /**
   * We can declare a DataTable as a new type.  This means, instead of
   * referencing a column as 'Customer.CustomerID' we can change the 'Customer'
   * part to anything we wish such as 'C1'.
   */
  public ReferenceTable declareAs(TableName new_name) {
    return new ReferenceTable(this, new_name);
  }

  /**
   * Generates an empty RowData object for 'addRow'ing into the Table.
   * We must first call this method to retrieve a blank RowData object,
   * fill it in with the required information, and then call 'addRow'
   */
  public final RowData createRowDataObject(QueryContext context) {
    checkSafeOperation();  // safe op
    return new RowData(this);
  }

  /**
   * Returns the current row count.  This queries the DataTableManager for
   * the real value.
   */
  public int getRowCount() {
    checkReadLock();  // read op

    return data_source.getRowCount();
  }

  /**
   * Adds a given 'RowData' object to the table.  This should be used for
   * any rows added to the table.  The order that rows are added into a table
   * is not important.
   * <p>
   * This method performs some checking of the cells in the table.  It first
   * checks that all columns declared as 'not null' have a value that is not
   * null.  It then checks that a the added row will not cause any duplicates
   * in a column declared as unique.
   * <p>
   * It then uses the low level io manager to store the data.
   * <p>
   * SYNCHRONIZATION ISSUE: We are assuming this is running in a synchronized
   *   environment that is unable to add or alter rows in this object within
   *   the lifetime of this method.
   */
  public final void add(RowData row_data) throws DatabaseException {
    checkReadWriteLock();  // write op

    if (!row_data.isSameTable(this)) {
      throw new DatabaseException(
                     "Internal Error: Using RowData from different table");
    }

    // Checks passed, so add to table.
    addRow(row_data);

    // Perform a referential integrity check on any changes to the table.
    data_source.constraintIntegrityCheck();
  }

  /**
   * Adds an array of 'RowData' objects to the table.  This should be used for
   * adding a group of rows to the table.  The order that rows are added into
   * a table is not important.
   * <p>
   * This method performs some checking of the cells in the table.  It first
   * checks that all columns declared as 'not null' have a value that is not
   * null.  It then checks that a the added row will not cause any duplicates
   * in a column declared as unique.
   * <p>
   * It then uses the low level io manager to store the data.
   * <p>
   * SYNCHRONIZATION ISSUE: We are assuming this is running in a synchronized
   *   environment that is unable to add or alter rows in this object within
   *   the lifetime of this method.
   */
  public final void add(RowData[] row_data_arr) throws DatabaseException {
    checkReadWriteLock();  // write op
    
    for (int i = 0; i < row_data_arr.length; ++i) {
      RowData row_data = row_data_arr[i];
      if (!row_data.isSameTable(this)) {
        throw new DatabaseException(
                       "Internal Error: Using RowData from different table");
      }
      addRow(row_data);
    }
    
    // Perform a referential integrity check on any changes to the table.
    data_source.constraintIntegrityCheck();
  }

  /**
   * Adds a new row of data to the table.  First of all, this tells the
   * underlying database mechanism to add the data to this table.  It then
   * add the row information to each SelectableScheme.
   */
  private void addRow(RowData row) throws DatabaseException {

    // This table name (for event notification)
    TableName table_name = getTableName();

    // Fire the 'before' trigger for an insert on this table
    connection.fireTableEvent(new TableModificationEvent(connection, table_name,
                                                         row, true));

    // Add the row to the underlying file system
    int row_number = data_source.addRow(row);

    // Fire the 'after' trigger for an insert on this table
    connection.fireTableEvent(new TableModificationEvent(connection, table_name,
                                                         row, false));

    // NOTE: currently nothing being done with 'row_number' after it's added.
    //   The underlying table data source manages the row index.

  }

  /**
   * Removes the given row from the table.  This is called just before the
   * row is actually deleted.  The method is provided to allow for some
   * maintenance of any search structures such as B-Trees.  This is called
   * from the 'delete' method in Table.
   */
  private void removeRow(int row_number) throws DatabaseException {

    // This table name (for event notification)
    TableName table_name = getTableName();

    // Fire the 'before' trigger for the delete on this table
    connection.fireTableEvent(new TableModificationEvent(connection, table_name,
                                                         row_number, true));

    // Delete the row from the underlying database
    data_source.removeRow(row_number);

    // Fire the 'after' trigger for the delete on this table
    connection.fireTableEvent(new TableModificationEvent(connection, table_name,
                                                         row_number, false));

  }

  /**
   * Updates the given row with the given data in this table.  This method
   * will likely add the modified data to a new row and delete the old
   * version of the row.
   */
  private void updateRow(int row_number, RowData row)
                                                    throws DatabaseException {

    // This table name (for event notification)
    TableName table_name = getTableName();

    // Fire the 'before' trigger for the update on this table
    connection.fireTableEvent(
         new TableModificationEvent(connection, table_name,
                                    row_number, row, true));

    // Update the row in the underlying database
    data_source.updateRow(row_number, row);

    // Fire the 'after' trigger for the update on this table
    connection.fireTableEvent(
         new TableModificationEvent(connection, table_name,
                                    row_number, row, false));

  }

  /**
   * This is the public method for removing a given result set from this
   * table.  Given a Table object, this will remove from this table any row
   * that are in the given table.  The given Table must have this object as
   * its distant ancestor.  If it does not then it will throw an exception.
   * Examples: table.delete(table)           -- delete the entire table.
   *           table.delete(table.select( < some condition > ));
   * It returns the number of rows that were deleted.
   * <p>
   * <strong>INTERNAL NOTE:</strong> The 'table' parameter may be the result
   *   of joins.  This may cause the same row in this table to be referenced
   *   more than once.  We must make sure that we delete any given row only
   *   once by using the 'distinct' function.
   * <p>
   * 'limit' dictates how many rows will be deleted.  If 'limit' is less than
   * 0 then this indicates there is no limit.  Keep in mind that rows are
   * picked out from top to bottom in the 'table' object.  Normally the
   * input table will be the result of an un-ordered 'where' clause so using
   * a limit does not permit deletes in a deterministic manner.
   * <p>
   * ASSUMPTION: There are no duplicate rows in the input set.
   */
  public int delete(Table table, int limit) throws DatabaseException {
    checkReadWriteLock();  // write op

    IntegerVector row_set = new IntegerVector(table.getRowCount());
    RowEnumeration e = table.rowEnumeration();
    while (e.hasMoreRows()) {
      row_set.addInt(e.nextRowIndex());
    }
    e = null;

    // HACKY: Find the first column of this table in the search table.  This
    //   will allow us to generate a row set of only the rows in the search
    //   table.
    int first_column = table.findFieldName(getResolvedVariable(0));

    if (first_column == -1) {
      throw new DatabaseException("Search table does not contain any " +
                                  "reference to table being deleted from");
    }

    // Generate a row set that is in this tables domain.
    table.setToRowTableDomain(first_column, row_set, this);

    // row_set may contain duplicate row indices, therefore we must sort so
    // any duplicates are grouped and therefore easier to find.
    row_set.quickSort();

    // If limit less than zero then limit is whole set.
    if (limit < 0) {
      limit = Integer.MAX_VALUE;
    }

    // Remove each row in row set in turn.  Make sure we don't remove the
    // same row index twice.
    int len = Math.min(row_set.size(), limit);
    int last_removed = -1;
    int remove_count = 0;
    for (int i = 0; i < len; ++i) {
      int to_remove = row_set.intAt(i);
      if (to_remove < last_removed) {
        throw new DatabaseException(
          "Internal error: row sorting error or row_set not in the range > 0");
      }

      if (to_remove != last_removed) {
        removeRow(to_remove);
        last_removed = to_remove;
        ++remove_count;
      }

    }

    if (remove_count > 0) {
      // Perform a referential integrity check on any changes to the table.
      data_source.constraintIntegrityCheck();
    }

    return remove_count;
  }

  // Unlimited delete
  public int delete(Table table) throws DatabaseException {
    return delete(table, -1);
  }

  /**
   * Updates the table by applying the assignment operations over each row
   * that is found in the input 'table' set.  The input table must be a direct
   * child of this DataTable.
   * <p>
   * This operation assumes that there is a WRITE lock on this table.  A
   * WRITE lock means no other thread may access this table while the
   * operation is being performed.  (However, a result set may still be
   * downloading from this table).
   * <p>
   * 'limit' dictates how many rows will be updated.  If 'limit' is less than
   * 0 then this indicates there is no limit.  Keep in mind that rows are
   * picked out from top to bottom in the 'table' object.  Normally the
   * input table will be the result of an un-ordered 'where' clause so using
   * a limit does not permit updates in a deterministic manner.
   * <p>
   * Returns the number of rows updated in this table.
   * <p>
   * NOTE: We assume there are no duplicate rows to the root set from the
   *   given 'table'.
   */
  public final int update(QueryContext context,
                          Table table, Assignment[] assign_list, int limit)
                                                    throws DatabaseException {
    checkReadWriteLock();  // write op

    // Get the rows from the input table.
    IntegerVector row_set = new IntegerVector();
    RowEnumeration e = table.rowEnumeration();
    while (e.hasMoreRows()) {
      row_set.addInt(e.nextRowIndex());
    }
    e = null;

    // HACKY: Find the first column of this table in the search table.  This
    //   will allow us to generate a row set of only the rows in the search
    //   table.
    int first_column = table.findFieldName(getResolvedVariable(0));
    if (first_column == -1) {
      throw new DatabaseException("Search table does not contain any " +
                                  "reference to table being updated from");
    }

    // Convert the row_set to this table's domain.
    table.setToRowTableDomain(first_column, row_set, this);

    // NOTE: Assume there's no duplicate rows.

    RowData original_data = createRowDataObject(context);
    RowData row_data = createRowDataObject(context);

    // If limit less than zero then limit is whole set.
    if (limit < 0) {
      limit = Integer.MAX_VALUE;
    }

    // Update each row in row set in turn up to the limit.
    int len = Math.min(row_set.size(), limit);
    int update_count = 0;
    for (int i = 0; i < len; ++i) {
      int to_update = row_set.intAt(i);

      // Make a RowData object from this row (plus keep the original intact
      // incase we need to roll back to it).
      original_data.setFromRow(to_update);
      row_data.setFromRow(to_update);

      // Run each assignment on the RowData.
      for (int n = 0; n < assign_list.length; ++n) {
        Assignment assignment = assign_list[n];
        row_data.evaluate(assignment, context);
      }

      // Update the row
      updateRow(to_update, row_data);

      ++update_count;
    }

    if (update_count > 0) {
      // Perform a referential integrity check on any changes to the table.
      data_source.constraintIntegrityCheck();
    }

    return update_count;

  }

  /**
   * Returns the DataTableDef object for this table.  This object describes
   * how the table is made up.
   * <p>
   * <strong>NOTE:</strong> Do not keep references to this object.  The
   *   DataTableDef is invalidated when a table is closed.
   */
  public DataTableDef getDataTableDef() {
    checkSafeOperation();  // safe op

    return data_source.getDataTableDef();
  }

  /**
   * Returns the schema that this table is within.
   */
  public String getSchema() {
    checkSafeOperation();  // safe op

    return getDataTableDef().getSchema();
  }

  /**
   * Adds a DataTableListener to the DataTable objects at the root of this
   * table tree hierarchy.  If this table represents the join of a number of
   * tables then the DataTableListener is added to all the DataTable objects
   * at the root.
   * <p>
   * A DataTableListener is notified of all modifications to the raw entries
   * of the table.  This listener can be used for detecting changes in VIEWs,
   * for triggers or for caching of common queries.
   */
  public void addDataTableListener(DataTableListener listener) {
    // Currently we do nothing with this info.
  }

  /**
   * Removes a DataTableListener from the DataTable objects at the root of
   * this table tree hierarchy.  If this table represents the join of a
   * number of tables, then the DataTableListener is removed from all the
   * DataTable objects at the root.
   */
  public void removeDataTableListener(DataTableListener listener) {
    // Currently we do nothing with this info.
  }




  // -------- Methods implemented for DefaultDataTable --------

  /**
   * Given a set, this trickles down through the Table hierarchy resolving
   * the given row_set to a form that the given ancestor understands.
   * Say you give the set { 0, 1, 2, 3, 4, 5, 6 }, this function may check
   * down three levels and return a new 7 element set with the rows fully
   * resolved to the given ancestors domain.
   */
  void setToRowTableDomain(int column, IntegerVector row_set,
                           TableDataSource ancestor) {
    checkReadLock();  // read op

    if (ancestor != this && ancestor != data_source) {
      throw new RuntimeException("Method routed to incorrect table ancestor.");
    }
  }

  /**
   * Returns an object that represents the information in the given cell
   * in the table.  This can be used to obtain information about the given
   * table cells.
   */
  public TObject getCellContents(int column, int row) {
    checkSafeOperation();  // safe op

    return data_source.getCellContents(column, row);
  }

  /**
   * Returns an Enumeration of the rows in this table.
   * Each call to 'nextRowIndex' returns the next valid row index in the table.
   */
  public RowEnumeration rowEnumeration() {
    checkReadLock();  // read op

    return data_source.rowEnumeration();
  }


  /**
   * Locks the root table(s) of this table so that it is impossible to
   * overwrite the underlying rows that may appear in this table.
   * This is used when cells in the table need to be accessed 'outside' the
   * lock.  So we may have late access to cells in the table.
   * 'lock_key' is a given key that will also unlock the root table(s).
   * <p>
   * NOTE: This is nothing to do with the 'LockingMechanism' object.
   */
  public void lockRoot(int lock_key) {
    checkSafeOperation();  // safe op

    data_source.addRootLock();
  }

  /**
   * Unlocks the root tables so that the underlying rows may
   * once again be used if they are not locked and have been removed.  This
   * should be called some time after the rows have been locked.
   */
  public void unlockRoot(int lock_key) {
    checkSafeOperation();  // safe op

    data_source.removeRootLock();
  }

  /**
   * Returns true if the table has its row roots locked (via the lockRoot(int)
   * method.
   */
  public boolean hasRootsLocked() {
    // There is no reason why we would need to know this information at
    // this level.
    // We need to deprecate this properly.
    throw new Error("hasRootsLocked is deprecated.");
  }





  // ------------ Lock debugging methods ----------

  /**
   * This is called by the 'Lock' class to notify this DataTable that a read/
   * write lock has been applied to this table.  This is for lock debugging
   * purposes only.
   */
  final void notifyAddRWLock(int lock_type) {
    if (LOCK_DEBUG) {
      if (lock_type == Lock.READ) {
        ++debug_read_lock_count;
      }
      else if (lock_type == Lock.WRITE) {
        ++debug_write_lock_count;
        if (debug_write_lock_count > 1) {
          throw new Error(">1 write lock on table " + getTableName());
        }
      }
      else {
        throw new Error("Unknown lock type: " + lock_type);
      }
    }
  }

  /**
   * This is called by the 'Lock' class to notify this DataTable that a read/
   * write lock has been released from this table.  This is for lock debugging
   * purposes only.
   */
  final void notifyReleaseRWLock(int lock_type) {
    if (LOCK_DEBUG) {
      if (lock_type == Lock.READ) {
        --debug_read_lock_count;
      }
      else if (lock_type == Lock.WRITE) {
        --debug_write_lock_count;
      }
      else {
        Debug().writeException(
                    new RuntimeException("Unknown lock type: " + lock_type));
      }
    }
  }

  /**
   * Returns true if the database is in exclusive mode.
   */
  private boolean isInExclusiveMode() {
    // Check the connection locking mechanism is in exclusive mode
    return connection.getLockingMechanism().isInExclusiveMode();
  }

  /**
   * Checks the database is in exclusive mode.
   */
  private void checkInExclusiveMode() {
    if (!isInExclusiveMode()) {
      Debug().writeException(new RuntimeException(
         "Performed exclusive operation on table and not in exclusive mode!"));
    }
  }

  /**
   * Check that we can safely read from this table.
   */
  private void checkReadLock() {
    if (LOCK_DEBUG) {
      // All 'sUSR' tables are given read access because they may only be
      // written under exclusive mode anyway.

      boolean is_internal_table =
                    getTableName().getSchema().equals(Database.SYSTEM_SCHEMA);

      if (!(is_internal_table ||
            debug_read_lock_count > 0 ||
            debug_write_lock_count > 0 ||
            isInExclusiveMode())) {

        System.err.println();
        System.err.print(" is_internal_table = " + is_internal_table);
        System.err.print(" debug_read_lock_count = " + debug_read_lock_count);
        System.err.print(" debug_write_lock_count = " + debug_write_lock_count);
        System.err.println(" isInExclusiveMode = " + isInExclusiveMode());

        Debug().writeException(new Error(
                    "Invalid read access on table '" + getTableName() + "'"));
      }
    }
  }

  /**
   * Check that we can safely read/write from this table.  This should catch
   * any synchronization concurrent issues.
   */
  private void checkReadWriteLock() {
    if (LOCK_DEBUG) {
      // We have to own exactly one write lock, or be in exclusive mode.
      if (!(debug_write_lock_count == 1 || isInExclusiveMode())) {
        Debug().writeException(
               new Error("Invalid read/write access on table '" +
                         getTableName() + "'"));
      }
    }
  }

  /**
   * Check that we can run a safe operation.
   */
  private void checkSafeOperation() {
    // no operation - nothing to check for...
  }



  // ---------- Overwritten to output debug info ----------
  // NOTE: These can all safely be commented out.

  public int getColumnCount() {
    checkSafeOperation();  // safe op

    return super.getColumnCount();
  }

  public Variable getResolvedVariable(int column) {
    checkSafeOperation();  // safe op

    return super.getResolvedVariable(column);
  }

  public int findFieldName(Variable v) {
    checkSafeOperation();  // safe op

    return super.findFieldName(v);
  }

  SelectableScheme getSelectableSchemeFor(int column, int original_column,
                                          Table table) {
    checkReadLock();  // read op

    return super.getSelectableSchemeFor(column, original_column, table);
  }

  RawTableInformation resolveToRawTable(RawTableInformation info) {
    checkReadLock();  // read op

    return super.resolveToRawTable(info);
  }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.