JDocCoverage Report - 21.04.2006 22:02:51

Namemethod, %comment, %TODO@see
smallsql.database.Table1921,9%   (2312/8267)30

/* =============================================================
 * SmallSQL : a free Java DBMS library for the Java(tm) platform
 * =============================================================
 *
 * (C) Copyright 2004-2006, by Volker Berlin.
 *
 * Project Info:  http://www.smallsql.de/
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
 * USA.  
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
 * in the United States and other countries.]
 *
 * ---------------
 * Table.java
 * ---------------
 * Author: Volker Berlin
 * 
 */
package smallsql.database;

import java.io.*;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;


class Table extends TableView{
	
	private static final int INDEX = 1;

    final Database database;
    RandomAccessFile raFile; // file handle of the table
	private Lobs lobs; // file handle of lob data for this table
    long firstPage; // offset of the first page

	final private HashMap locks = new HashMap();
	private SSConnection tabLockConnection; // wenn gesetzt die Connection die ein LOCK_TAB hat
	private int tabLockCount;
	final private ArrayList locksInsert = new ArrayList(); // liste der LOCK_INSERT
	final private HashMap serializeConnections = new HashMap();
	final IndexDescriptions indexes;
	final ForeignKeys references = new ForeignKeys();


	/**
	 * Constructor for read existing tables.
	 */
    Table(SSConnection con, String name, RandomAccessFile raFile, long offset, int tableFormatVersion) throws Exception{
        super( name, new Columns() );
        this.database = con.getDatabase(false);
        this.raFile   = raFile;
		this.firstPage = offset;
		StoreImpl store = getStore(con, firstPage, SQLTokenizer.SELECT);
		int count = store.readInt();

		for(int i=0; i<count; i++){
			columns.add( store.readColumn(this, tableFormatVersion) );
		}
		indexes = new IndexDescriptions();
		
		// read additional informations
		int type;
		while((type = store.readInt()) != 0){
			int offsetInPage = store.getCurrentOffsetInPage();
			int size = store.readInt();
			switch(type){
				case INDEX:
					indexes.add( IndexDescription.load( database, this, store) );
					break;
			}
			store.setCurrentOffsetInPage(offsetInPage + size);
		}
		
		firstPage = store.getNextPagePos();
    }
    

    /**
     * Constructor for creating of new tables.
     */
    Table(Database database, SSConnection con, String name, Columns columns, IndexDescriptions indexes) throws Exception{
		super( name, columns );
		this.database = database;
		this.indexes = indexes;
		indexes.create( database, this );
        write(con);
    }
    
    /**
     * Constructor for extends class Lobs.
     */
    Table(Database database, String name){
    	super( name, null);
    	this.database = database;
		indexes = null;
    }

	/**
	 * Drop the Table. This method is static that the file does not need to load and also corrupt files can be dropped.
	 */ 
    static void drop(Database database, String name) throws Exception{
        boolean ok = new File( Utils.createTableViewFileName( database, name ) ).delete();
        if(!ok) throw Utils.createSQLException("Table '" + name + "' can't drop.");
    }
    
    
    /**
     * Drop a loaded table.
     *
     */
    void drop(SSConnection con) throws Exception{
		TableStorePage storePage = requestLock( con, SQLTokenizer.CREATE, -1 );
		if(storePage == null)
			throw Utils.createSQLException("Table '" + name + "' can't drop because is locked.");
		
		// remove the all commits that point to this table
		con.rollbackFile(raFile);
		close();
		if(lobs != null)
			lobs.drop(con);
		if(indexes != null)
			indexes.drop(database);
		boolean ok = getFile( database, name).delete();
		if(!ok) throw Utils.createSQLException("Table '" + name + "' can't drop.");
    }
    

    void close() throws Exception{
        raFile.close();
        raFile = null;
    }


    private void write(SSConnection con) throws Exception{
        raFile = createFile( database );
        firstPage = 8;
        StoreImpl store = getStore( con, firstPage, SQLTokenizer.CREATE);
        int count = columns.size();
        store.writeInt( count );
        for(int i=0; i<count; i++){
            store.writeColumn( this, columns.get(i) );
        }

		// write additional informations
		for(int i=0; i<indexes.size(); i++){
			IndexDescription indexDesc = indexes.get(i);
			store.writeInt( INDEX );
			int offsetStart = store.getCurrentOffsetInPage();
			store.setCurrentOffsetInPage( offsetStart + 4 ); // place holder for length
			
			// write the IndexDescription
			indexDesc.save(store);
			
			// write the length information
			int offsetEnd = store.getCurrentOffsetInPage();
			store.setCurrentOffsetInPage( offsetStart );
			store.writeInt( offsetEnd - offsetStart);
			store.setCurrentOffsetInPage( offsetEnd );
		}
		store.writeInt( 0 ); // no more additinal informations
		
		store.writeFinsh(con);
        firstPage = store.getNextPagePos();
    }
    

	void writeMagic(RandomAccessFile raFile) throws Exception{
		raFile.writeInt(MAGIC_TABLE);
		raFile.writeInt(TABLE_VIEW_VERSION);
	}
	

    /*StoreImpl getStoreCreate( SSConnection con, long filePos ) throws Exception{
        return StoreImpl.createStore( con, raFile, SQLTokenizer.CREATE, filePos );
    }*/

    StoreImpl getStore( SSConnection con, long filePos, int pageOperation ) throws Exception{
		TableStorePage storePage = requestLock( con, pageOperation, filePos );
        return StoreImpl.createStore( this, storePage, pageOperation, filePos );
    }

    
	StoreImpl getStore( TableStorePage storePage, int pageOperation ) throws Exception{
		// is used for not commited INSERT pages, a new lock is not needed
		return StoreImpl.recreateStore( this, storePage, pageOperation );
	}
	
    /*StoreImpl getStoreUpdate( SSConnection con, long filePos ) throws Exception{
        return StoreImpl.createStore( con, raFile, SQLTokenizer.UPDATE, filePos );
    }

    StoreImpl getStoreDelete( SSConnection con, long filePos ) throws Exception{
        return StoreImpl.createStore( con, raFile, SQLTokenizer.DELETE, filePos );
    }*/
	

    StoreImpl getStoreInsert( SSConnection con ) throws Exception{
		TableStorePage storePage = requestLock( con, SQLTokenizer.INSERT, -1 );
        return StoreImpl.createStore( this, storePage, SQLTokenizer.INSERT, -1 );
    }
    
    
    /**
     * Create a Store that is not invoke in a transaction for copy of data.
     */
	StoreImpl getStoreTemp( SSConnection con ) throws Exception{
		TableStorePage storePage = new TableStorePage( con, this, LOCK_NONE, -2);
		return StoreImpl.createStore( this, storePage, SQLTokenizer.INSERT, -2 );
	}
        

	StoreImpl getLobStore(SSConnection con, long filePos, int pageOperation) throws Exception{
		if(lobs == null){
			lobs = new Lobs( this );
		}
		return lobs.getStore( con, filePos, pageOperation );
	}
    

	
	/**
	 * Return the file offset of the first page with data after the table declaration.
	 * This is equals to the first row.
	 */
    final long getFirstPage(){
        return firstPage;
    }


    /**
     * Return a list of Links to not commited rows. The list include only the rows that are visible for 
     * the current isolation level.
     */
    List getInserts(SSConnection con){
		synchronized(locks){
			ArrayList inserts = new ArrayList();
			if(con.isolationLevel <= Connection.TRANSACTION_READ_UNCOMMITTED){
				for(int i=0; i<locksInsert.size(); i++){
					TableStorePageInsert lock = (TableStorePageInsert)locksInsert.get(i);
					inserts.add(lock.getLink());
				}
			}else{
				for(int i=0; i<locksInsert.size(); i++){
					TableStorePageInsert lock = (TableStorePageInsert)locksInsert.get(i);
					if(lock.con == con)
						inserts.add(lock.getLink());
				}
			}
			return inserts;
		}    	
    }
    
    
    final private TableStorePage requestLock(SSConnection con, int pageOperation, long page) throws Exception{
    	synchronized(locks){
			long endTime = 0;
			while(true){
				TableStorePage storePage = requestLockImpl( con, pageOperation, page);
				if(storePage != null) 
					return storePage; // the normal case shoud be the fasted
				if(endTime == 0)
					endTime = System.currentTimeMillis() + 5000;
				long waitTime = endTime - System.currentTimeMillis();
				if(waitTime <= 0)
					throw Utils.createSQLException("Deadlock, can not create a lock on table '"+name+"'");
				locks.wait(waitTime);
			}
    	}
    }
    
    /**
     * Request an page lock. If the rquest is valid then it return the StorePage. 
     * In the other case it return null.
     * @param page The fileOffset or -1 for a new page
     * @throws SQLException 
     */
	final private TableStorePage requestLockImpl(SSConnection con, int pageOperation, long page) throws SQLException{
		synchronized(locks){
			if(tabLockConnection != null && tabLockConnection != con) return null;
			switch(con.isolationLevel){
				case Connection.TRANSACTION_SERIALIZABLE:
					con.serializeCount++;
					serializeConnections.put( con, con);
					break;
			}
		
			switch(pageOperation){
				case SQLTokenizer.CREATE:{
						// first check if another connection has a lock bevore creating a table lock
						if(locks.size() > 0){
							Iterator values = locks.values().iterator();
							while(values.hasNext()){
								TableStorePage lock = (TableStorePage)values.next();
								if(lock.con != con) return null;
							}
						}
						for(int i=0; i<locksInsert.size(); i++){
							//the first StorePage in the linked list must be ever TableStorePageInsert
							TableStorePageInsert lock = (TableStorePageInsert)locksInsert.get(i);
							if(lock.con != con) return null;
						}
						if(serializeConnections.size() > 0){
							Iterator values = serializeConnections.values().iterator();
							while(values.hasNext()){
								TableStorePage lock = (TableStorePage)values.next();
								if(lock.con != con) return null;
							}
						}
						tabLockConnection = con;
						tabLockCount++;
						TableStorePage lock = new TableStorePage(con, this, LOCK_TAB, page);
						con.add(lock);
						return lock;
					}
				case SQLTokenizer.INSERT:{
						// if there are more as one Connection with a serializable lock then an INSERT is not valid
						if(serializeConnections.size() > 1) return null;
						if(serializeConnections.size() == 1 && serializeConnections.get(con) != null) return null;
						TableStorePageInsert lock = new TableStorePageInsert(con, this, LOCK_INSERT);
						locksInsert.add( lock );
						con.add(lock);
						return lock;
					}
				case SQLTokenizer.SELECT:{
						Long pageKey = new Long(page); //TODO performance
						TableStorePage lockFirst;
						TableStorePage lock = lockFirst = (TableStorePage)locks.get( pageKey );
						while(lock != null){
							if(lock.con == con || 
							   con.isolationLevel <= Connection.TRANSACTION_READ_UNCOMMITTED) return lock;
							if(lock.lockType == LOCK_WRITE) return null; // write lock of another Connection
							lock = lock.nextLock;
						}
						lock = new TableStorePage( con, this, LOCK_NONE, page);
						if(con.isolationLevel >= Connection.TRANSACTION_REPEATABLE_READ){
							lock.lockType = LOCK_READ;
							lock.nextLock = lockFirst;
							locks.put( pageKey, lock );
							con.add(lock);
						}
						return lock;							
					}
				case SQLTokenizer.LONGVARBINARY:
					// is used for written BLOB and CLOB
					// the difference to INSERT is that page descript the size of the byte buffer
					return new TableStorePage( con, this, LOCK_INSERT, -1);
				default:
					throw new Error("pageOperation:"+pageOperation);
			}
		}
	}
	
	
	/**
	 * Request a write lock for a page that is read.
	 * @throws SQLException 
	 */
	TableStorePage requestWriteLock(SSConnection con, TableStorePage readlock) throws SQLException{
		if(readlock.lockType == LOCK_INSERT){
			TableStorePage lock = new TableStorePage( con, this, LOCK_INSERT, -1);
			readlock.nextLock = lock;
			con.add(lock);
			return lock;									
		}
		Long pageKey = new Long(readlock.fileOffset); //TODO performance
		TableStorePage lockFirst;
		TableStorePage lock = lockFirst = (TableStorePage)locks.get( pageKey );
		while(lock != null){
			if(lock.con != con) return null; // there is allready any lock from another connection, we can not start write
			if(lock.lockType < LOCK_WRITE){
				// if there is only a read lock we can transfer it
				// this is requied for rollback to a savepoint
				lock.lockType = LOCK_WRITE;
				return lock;
			}
			lock = lock.nextLock;
		}
		lock = new TableStorePage( con, this, LOCK_WRITE, readlock.fileOffset);
		lock.nextLock = lockFirst;
		locks.put( pageKey, lock );
		con.add(lock);
		return lock;									
	}
	
	
	/**
	 * Remove the lock from this table.
	 */
	void freeLock(TableStorePage storePage){
		final int lockType = storePage.lockType;
		final long fileOffset = storePage.fileOffset;
		synchronized(locks){
			try{
				TableStorePage lock;
				TableStorePage prev;
				switch(lockType){
					case LOCK_INSERT:
						for(int i=0; i<locksInsert.size(); i++){
							prev = lock = (TableStorePage)locksInsert.get(i);
							while(lock != null){
								if(lock == storePage){
									//remove lock
									if(lock == prev){
										if(lock.nextLock == null){
											// the first lock is the only lock in the list
											locksInsert.remove(i--);
										}else{
											// only the first lock of the list is remove
											locksInsert.set( i, lock.nextLock );
										}
									}else{
										// a lock in the mid or end is removed
										prev.nextLock = lock.nextLock;
									}
									return;
								}
								prev = lock;
								lock = lock.nextLock;
							}
						}
						break;
					case LOCK_READ:
					case LOCK_WRITE:
						Long pageKey = new Long(fileOffset); //TODO performance
						lock = (TableStorePage)locks.get( pageKey );
						prev = lock;
						while(lock != null){
							if(lock == storePage){
								//lock entfernen
								if(lock == prev){
									if(lock.nextLock == null){
										// erste und einzige Lock in Liste
										locks.remove(pageKey);
									}else{
										// erste Lock in liste fällt weg
										locks.put( pageKey, lock.nextLock );
									}
								}else{
									// lock in mitte oder ende der Liste fällt weg
									prev = lock.nextLock;
								}
								return;
							}
							prev = lock;
							lock = lock.nextLock;
						}
						// Durchläufer kann auftreten, wenn eine Lock hochgestuft wurde und damit der type nicht stimmt
						break;
					case LOCK_TAB:
						assert storePage.con == tabLockConnection : "Internale Error with TabLock";
						if(--tabLockCount == 0) tabLockConnection = null;
						break;
					default:
						throw new Error();
				}
			}finally{
				locks.notifyAll();
			}
		}
	}

}