JDocCoverage Report - 21.04.2006 22:02:51

Namemethod, %comment, %TODO@see
smallsql.database.Database2116,7%   (1754/8765)00

/* =============================================================
 * 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.]
 *
 * ---------------
 * Database.java
 * ---------------
 * Author: Volker Berlin
 * 
 */
package smallsql.database;

import java.util.*;
import java.io.*;
import java.sql.*;


final class Database {

    /*
    Es gibt nur eine Instance dieser Klasse pro Datenbank. Sie wird geschert zwischen
    allen Connections zu dieser Datenbank und allen Threads. Deshalb muß der Zugriff
    auf diese Klasse Thread sicher sein.

    Hier werden vor allem Table Definitionen und Locks gespeichert.
    */

    static private HashMap databases = new HashMap();

    final private HashMap tableViews = new HashMap();
    private String name;
	private File directory;
	private RandomAccessFile master;
	final private WeakHashMap connections = new WeakHashMap();


	/**
	 * Get a instance of the Database Class. If the Datbase with the given name is not open 
	 * then it will be open. 
	 */
    static Database getDatabase( String name, SSConnection con, boolean create ) throws SQLException{
    	if(name == null) return null;
        if(name.startsWith("file:"))
            name = name.substring(5);
    	if(File.separatorChar == '\\')
    		name = name.replace( '/', File.separatorChar);
    	else
    		name = name.replace( '\\', File.separatorChar);
        synchronized(databases){
            Database db = (Database)databases.get(name);
            if(db == null){
                if(create && !new File(name).isDirectory()){
                    CommandCreateDatabase command = new CommandCreateDatabase(con.log,name);
                    command.execute(con,null);
                }
                db = new Database(name);
                databases.put( name, db);
            }
			db.connections.put(con, null);
            return db;
        }
    }
    
    
    private static Database getDatabase(SSConnection con, String name) throws SQLException{
		return name == null ?
					con.getDatabase(false) :
					getDatabase( name, con, false );
    }
    

    private Database(String name ) throws SQLException{
        try{
	        this.name = name;
			directory = new File(name);
			if(!directory.isDirectory()){
                throw Utils.createSQLException("Database '" + name + "' does not exists.");
            }
			directory = directory.getAbsoluteFile();
			File file = new File( directory, Utils.MASTER_FILENAME);
			if(!file.exists())
				throw Utils.createSQLException("Directory '" + name + "' is not a SmallSQL database.");
			master = new RandomAccessFile( file, "rw");
        }catch(Exception e){
        	throw Utils.createSQLException(e);
        }
    }

    String getName(){
        return name;
    }
    
	
	

	/**
	 * Remove a connection from this database.
	 */
	static final void closeConnection(SSConnection con) throws SQLException{
		synchronized(databases){
			Iterator iterator = databases.values().iterator();
			while(iterator.hasNext()){
				Database database = (Database)iterator.next();
				WeakHashMap connections = database.connections;
				connections.remove(con);
				if(connections.size() == 0){
					try {
						database.close();
					} catch (Exception e) {
						throw Utils.createSQLException(e);
					}
				}
			}
		}
	}
	

	/**
	 * Close this Database and all tables and views.
	 */
	final void close() throws Exception{
		databases.remove(this.name);
		synchronized(tableViews){
			Iterator iterator = tableViews.values().iterator();
			while(iterator.hasNext()){
				TableView tableView = (TableView)iterator.next();
				tableView.close();
				iterator.remove();
			}
		}
		master.close();
	}
	
    static TableView getTableView(SSConnection con, String catalog, String tableName) throws SQLException{
    	return getDatabase( con, catalog).getTableView( con, tableName);
    }

    TableView getTableView(SSConnection con, String tableName) throws SQLException{
        synchronized(tableViews){
            TableView tableView = (TableView)tableViews.get(tableName);
            if(tableView == null){
                // FIXME hier sollt nur die eine Tabelle gelockt sein und nicht alle Tabellen, Tabelle lesen sollte auserhalb des globalen syn sein.
                tableView = TableView.load(con, this, tableName);
                tableViews.put( tableName, tableView);
            }
            return tableView;
        }
    }
    

	static void dropTable(SSConnection con, String catalog, String tableName) throws Exception{
		getDatabase( con, catalog).dropTable( con, tableName);
	}
	

    void dropTable(SSConnection con, String tableName) throws Exception{
        synchronized(tableViews){
            Table table = (Table)tableViews.get( tableName );
            if(table != null){
				tableViews.remove( tableName );
                table.drop(con);
            }else{
            	Table.drop( this, tableName );
            }
        }
    }
    

	static void dropView(SSConnection con, String catalog, String tableName) throws Exception{
		getDatabase( con, catalog).dropView(tableName);
	}
	

	void dropView(String viewName) throws Exception{
		synchronized(tableViews){
			Object view = tableViews.remove( viewName );
			if(view != null && !(view instanceof View))
				throw Utils.createSQLException("Cannot use DROP VIEW with '" + viewName + "' because it does not is a view.");
			
			View.drop( this, viewName );
		}
	}
    

    /**
     * @param con current Connections
     * @param name the name of the new Table
     * @param columns the column descriptions of the table
     * @param indexes the indexes of the new table
     * @throws Exception
     */
	void createTable(SSConnection con, String name, Columns columns, IndexDescriptions indexes) throws Exception{
        // createFile() can run only one Thread success (it is atomic)
        // Thats the create of the Table does not need in the Synchronized.
        Table table = new Table( this, con, name, columns, indexes);
        synchronized(tableViews){
            tableViews.put( name, table);
        }
    }


	void createView(String name, String sql) throws Exception{
		// createFile() can run only one Thread success (it is atomic)
		// Thats the create of the View does not need in the Synchronized.
		new View( this, name, sql);
	}


    /**
     * Create a list of all available Databases from the point of the current 
     * Database or current working directory
     * @param database - current database
     * @return
     */
    static Object[][] getCatalogs(Database database){
    	List catalogs = new ArrayList();
    	File baseDir = (database != null) ?
    					database.directory.getParentFile() :
						new File(".");
		File dirs[] = baseDir.listFiles();
		if(dirs != null)
			for(int i=0; i<dirs.length; i++){
				if(dirs[i].isDirectory()){
					if(new File(dirs[i], Utils.MASTER_FILENAME).exists()){
						Object[] catalog = new Object[1];
						catalog[0] = dirs[i].getPath();
						catalogs.add(catalog);
					}
				}
			}
		Object[][] result = new Object[catalogs.size()][];
		catalogs.toArray(result);
		return result;
    }
	
    
	Strings getTables(String tablePattern){
		Strings list = new Strings();
		File dirs[] = directory.listFiles();    
		if(dirs != null)
			if(tablePattern == null) tablePattern = "%"; 
			tablePattern += Utils.TABLE_VIEW_EXTENTION;
			for(int i=0; i<dirs.length; i++){
				String name = dirs[i].getName();
				if(Utils.like(name, tablePattern)){
					list.add(name.substring( 0, name.length()-Utils.TABLE_VIEW_EXTENTION.length() ));
				}
			}
    	return list;
    }
	
    
    Object[][] getColumns( SSConnection con, String tablePattern, String colPattern) throws Exception{
    	List rows = new ArrayList();
		Strings tables = getTables(tablePattern);
    	for(int i=0; i<tables.size(); i++){
    		String tableName = tables.get(i);
			try{
	    		TableView tab = getTableView( con, tableName);
	    		Columns cols = tab.columns;
	    		for(int c=0; c<cols.size(); c++){
	    			Column col = cols.get(c);
					Object[] row = new Object[18];
					row[0] = getName(); 			//TABLE_CAT
								   					//TABLE_SCHEM
					row[2] = tableName;				//TABLE_NAME	
					row[3] = col.getName();			//COLUMN_NAME	
					row[4] = Utils.getShort( SQLTokenizer.getSQLDataType( col.getDataType() )); //DATA_TYPE  
					row[5] = SQLTokenizer.getKeyWord( col.getDataType() );	//TYPE_NAME
					row[6] = Utils.getInteger(col.getColumnSize());//COLUMN_SIZE
													//BUFFER_LENGTH
					row[8] = Utils.getInteger(col.getScale());//DECIMAL_DIGITS
					row[9] = Utils.getInteger(10);		//NUM_PREC_RADIX
					row[10]= Utils.getInteger(col.isNullable() ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls); //NULLABLE
													//REMARKS
					row[12]= col.getDefaultDefinition(); //COLUMN_DEF
													//SQL_DATA_TYPE
													//SQL_DATETIME_SUB
					row[15]= row[6];				//CHAR_OCTET_LENGTH
					row[16]= Utils.getInteger(i); 	//ORDINAL_POSITION		
					row[17]= col.isNullable() ? "YES" : "NO"; //IS_NULLABLE					
					rows.add(row);
	    		}
			}catch(Exception e){
				//invalid Tables and View will not show 
			}
    	}
		Object[][] result = new Object[rows.size()][];
		rows.toArray(result);
		return result;
    }
	
	
	Object[][] getReferenceKeys(SSConnection con, String pkTable, String fkTable) throws SQLException{
		List rows = new ArrayList();
		Strings tables = (pkTable != null) ? getTables(pkTable) : getTables(fkTable);
		for(int t=0; t<tables.size(); t++){
    		String tableName = tables.get(t);
    		TableView tab = getTableView( con, tableName);
			if(!(tab instanceof Table)) continue;
			ForeignKeys references = ((Table)tab).references;
			for(int i=0; i<references.size(); i++){
				ForeignKey foreignKey = references.get(i);
				IndexDescription pk = foreignKey.pk;
				IndexDescription fk = foreignKey.fk;
				if((pkTable == null || pkTable.equals(foreignKey.pkTable)) &&
				   (fkTable == null || fkTable.equals(foreignKey.fkTable))){
					Strings columnsPk = pk.getColumns();
					Strings columnsFk = fk.getColumns();
					for(int c=0; c<columnsPk.size(); c++){
						Object[] row = new Object[14];
						row[0] = getName();				//PKTABLE_CAT
														//PKTABLE_SCHEM
						row[2] = foreignKey.pkTable;	//PKTABLE_NAME
						row[3] = columnsPk.get(c);		//PKCOLUMN_NAME
						row[4] = getName();				//FKTABLE_CAT
														//FKTABLE_SCHEM
						row[6] = foreignKey.fkTable;	//FKTABLE_NAME
						row[7] = columnsFk.get(c);		//FKCOLUMN_NAME
						row[8] = Utils.getShort(c+1);	//KEY_SEQ
						row[9] = Utils.getShort(foreignKey.updateRule);//UPDATE_RULE
						row[10]= Utils.getShort(foreignKey.deleteRule); //DELETE_RULE
						row[11]= fk.getName();	//FK_NAME
						row[12]= pk.getName();	//PK_NAME
						row[13]= Utils.getShort(DatabaseMetaData.importedKeyNotDeferrable); //DEFERRABILITY
						rows.add(row);
					}
				}
			}
		}
		Object[][] result = new Object[rows.size()][];
		rows.toArray(result);
		return result;		
	}
	
	
	Object[][] getBestRowIdentifier(SSConnection con, String table) throws SQLException{
		List rows = new ArrayList();
		Strings tables = getTables(table);
		for(int t=0; t<tables.size(); t++){
    		String tableName = tables.get(t);
    		TableView tab = getTableView( con, tableName);
			if(!(tab instanceof Table)) continue;
			IndexDescriptions indexes = ((Table)tab).indexes;
			for(int i=0; i<indexes.size(); i++){
				IndexDescription index = indexes.get(i);
				if(index.isUnique()){
					Strings columns = index.getColumns();
					for(int c=0; c<columns.size(); c++){
						String columnName = columns.get(c);
						Column column = tab.findColumn(columnName);
						Object[] row = new Object[8];
						row[0] = Utils.getShort(DatabaseMetaData.bestRowTransaction);//SCOPE
						row[1] = columnName;			//COLUMN_NAME
						final int dataType = column.getDataType();
						row[2] = Utils.getInteger(dataType);//DATA_TYPE
						row[3] = SQLTokenizer.getKeyWord(dataType);//TYPE_NAME
						row[4] = Utils.getInteger(column.getPrecision());	//COLUMN_SIZE
														//BUFFER_LENGTH
						row[6] = Utils.getInteger(column.getScale());		//DECIMAL_DIGITS
						row[7] = Utils.getShort(DatabaseMetaData.bestRowNotPseudo);//PSEUDO_COLUMN
						rows.add(row);
					}
				}
			}
		}
		Object[][] result = new Object[rows.size()][];
		rows.toArray(result);
		return result;		
	}

	
	Object[][] getPrimaryKeys(SSConnection con, String table) throws SQLException{
		List rows = new ArrayList();
		Strings tables = getTables(table);
		for(int t=0; t<tables.size(); t++){
    		String tableName = tables.get(t);
    		TableView tab = getTableView( con, tableName);
			if(!(tab instanceof Table)) continue;
			IndexDescriptions indexes = ((Table)tab).indexes;
			for(int i=0; i<indexes.size(); i++){
				IndexDescription index = indexes.get(i);
				if(index.isPrimary()){
					Strings columns = index.getColumns();
					for(int c=0; c<columns.size(); c++){
						Object[] row = new Object[6];
						row[0] = getName(); 			//TABLE_CAT
														//TABLE_SCHEM
						row[2] = tableName;				//TABLE_NAME
						row[3] = columns.get(c);		//COLUMN_NAME
						row[4] = Utils.getShort(c+1);	//KEY_SEQ
						row[5] = index.getName();		//PK_NAME
						rows.add(row);
					}
				}
			}
		}
		Object[][] result = new Object[rows.size()][];
		rows.toArray(result);
		return result;		
	}
	
	
	Object[][] getIndexInfo( SSConnection con, String table, boolean unique) throws SQLException {
		List rows = new ArrayList();
		Strings tables = getTables(table);
		Short type = Utils.getShort( DatabaseMetaData.tableIndexOther );
		for(int t=0; t<tables.size(); t++){
    		String tableName = tables.get(t);
    		TableView tab = getTableView( con, tableName);
			if(!(tab instanceof Table)) continue;
			IndexDescriptions indexes = ((Table)tab).indexes;
			for(int i=0; i<indexes.size(); i++){
				IndexDescription index = indexes.get(i);
				Strings columns = index.getColumns();
				for(int c=0; c<columns.size(); c++){
					Object[] row = new Object[13];
					row[0] = getName(); 			//TABLE_CAT
													//TABLE_SCHEM
					row[2] = tableName;				//TABLE_NAME
					row[3] = Boolean.valueOf(!index.isUnique());//NON_UNIQUE
													//INDEX_QUALIFIER
					row[5] = index.getName();		//INDEX_NAME
					row[6] = type;					//TYPE
					row[7] = Utils.getShort(c+1);	//ORDINAL_POSITION
					row[8] = columns.get(c);		//COLUMN_NAME
													//ASC_OR_DESC
													//CARDINALITY
													//PAGES
													//FILTER_CONDITION
					rows.add(row);
				}
			}
    	}
		Object[][] result = new Object[rows.size()][];
		rows.toArray(result);
		return result;
	}
}