*
* 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; private Lobs lobs; long firstPage;
final private HashMap locks = new HashMap();
private SSConnection tabLockConnection; private int tabLockCount;
final private ArrayList locksInsert = new ArrayList(); final private HashMap serializeConnections = new HashMap();
final IndexDescriptions indexes;
final ForeignKeys references = new ForeignKeys();
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();
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();
}
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);
}
Table(Database database, String name){
super( name, null);
this.database = database;
indexes = null;
}
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.");
}
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.");
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) );
}
for(int i=0; i<indexes.size(); i++){
IndexDescription indexDesc = indexes.get(i);
store.writeInt( INDEX );
int offsetStart = store.getCurrentOffsetInPage();
store.setCurrentOffsetInPage( offsetStart + 4 );
indexDesc.save(store);
int offsetEnd = store.getCurrentOffsetInPage();
store.setCurrentOffsetInPage( offsetStart );
store.writeInt( offsetEnd - offsetStart);
store.setCurrentOffsetInPage( offsetEnd );
}
store.writeInt( 0 );
store.writeFinsh(con);
firstPage = store.getNextPagePos();
}
void writeMagic(RandomAccessFile raFile) throws Exception{
raFile.writeInt(MAGIC_TABLE);
raFile.writeInt(TABLE_VIEW_VERSION);
}
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{
return StoreImpl.recreateStore( this, storePage, pageOperation );
}
StoreImpl getStoreInsert( SSConnection con ) throws Exception{
TableStorePage storePage = requestLock( con, SQLTokenizer.INSERT, -1 );
return StoreImpl.createStore( this, storePage, SQLTokenizer.INSERT, -1 );
}
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 );
}
final long getFirstPage(){
return firstPage;
}
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; 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);
}
}
}
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:{
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++){
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(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); 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; 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:
return new TableStorePage( con, this, LOCK_INSERT, -1);
default:
throw new Error("pageOperation:"+pageOperation);
}
}
}
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); TableStorePage lockFirst;
TableStorePage lock = lockFirst = (TableStorePage)locks.get( pageKey );
while(lock != null){
if(lock.con != con) return null; if(lock.lockType < LOCK_WRITE){
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;
}
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){
if(lock == prev){
if(lock.nextLock == null){
locksInsert.remove(i--);
}else{
locksInsert.set( i, lock.nextLock );
}
}else{
prev.nextLock = lock.nextLock;
}
return;
}
prev = lock;
lock = lock.nextLock;
}
}
break;
case LOCK_READ:
case LOCK_WRITE:
Long pageKey = new Long(fileOffset); lock = (TableStorePage)locks.get( pageKey );
prev = lock;
while(lock != null){
if(lock == storePage){
if(lock == prev){
if(lock.nextLock == null){
locks.remove(pageKey);
}else{
locks.put( pageKey, lock.nextLock );
}
}else{
prev = lock.nextLock;
}
return;
}
prev = lock;
lock = lock.nextLock;
}
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();
}
}
}
}