JDocCoverage Report - 21.04.2006 22:02:51

Namemethod, %comment, %TODO@see
smallsql.database.CommandSelect3517,0%   (1749/8542)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.]
 *
 * ---------------
 * CommandSelect.java
 * ---------------
 * Author: Volker Berlin
 * 
 */
package smallsql.database;

import java.sql.*;


class CommandSelect extends Command{

    private DataSources from; // List of TableResult
	private Expression where;
    RowSource join;
    private Expressions groupBy;
    private Expression having;
    private Expressions orderBy;
    private boolean isAggregateFunction;
    private int maxRows;
    /** is set if the keyword DISTINCT is used */
    private boolean isDistinct; 

    CommandSelect(Logger log){
		super(log);
    }
    
	CommandSelect(Logger log, Expressions columnExpressions){
		super(log, columnExpressions);
	}
    
    
    boolean compile(SSConnection con) throws Exception{
        boolean needCompile = false;
        if(from != null){
            for(int i=0; i<from.size(); i++){
				DataSource fromEntry = from.get(i);
                needCompile |= fromEntry.init( con );
            }
        }

		if(join == null){
			join = new NoFromResult();
			from = new DataSources();
			needCompile = true;
		}
        if(!needCompile) return false;

        for(int i=0; i<columnExpressions.size(); i++){
            Expression col = columnExpressions.get(i);
            if(col.getAlias() == null){
                // für Ausdrücke automatische Namen vergeben
                col.setAlias("col" + (i+1));
            }

            if(col.getType() != Expression.NAME){
                compileLinkExpressionParams(col);
                continue;
            }

            ExpressionName expr = (ExpressionName)col;

            if("*".equals( expr.getName() )){
                String tableAlias = expr.getTableAlias();
                if(tableAlias != null){
                    // Syntax: tableAlias.*
                    int t=0;
                    for(; t<from.size(); t++){
						DataSource fromEntry = from.get(t);
                        if(tableAlias.equalsIgnoreCase( fromEntry.getAlias() )){
                            TableView table = fromEntry.getTableView();
                            columnExpressions.remove(i);
                            i = compileAdd_All_Table_Columns( fromEntry, table, i ) - 1;
                            break;
                        }
                    }
                    if(t==from.size()) throw Utils.createSQLException( "The column prefix '" + tableAlias + "' does not match with a table name or alias name used in this query" );
                }else{
                    // Syntax *
                    columnExpressions.remove(i);
                    for(int t=0; t<from.size(); t++){
						DataSource fromEntry = from.get(t);
                        TableView table = fromEntry.getTableView();
                        i = compileAdd_All_Table_Columns( fromEntry, table, i );
                    }
                    i--;
                }
            }else{
            	// not a * Syntax
                compileLinkExpressionName( expr );
            }

        }
        if(where != null) compileLinkExpression( where );
        if(having != null) compileLinkExpression( having );
        if(orderBy != null) {
            for(int i=0; i<orderBy.size(); i++){
            	compileLinkExpression( orderBy.get(i));
            }
        }
		if(groupBy != null){
			for(int i=0; i<groupBy.size(); i++){
				compileLinkExpression( groupBy.get(i) );
			}
		}

        if(join instanceof Join){
            compileJoin( (Join)join );
        }
        
        if(where != null){
        	join = new Where( join, where );
        }
        
		if(isGroupResult()) {
			GroupResult result = new GroupResult( this, join, groupBy, having, orderBy);
			//result.execute();
			join = result;
		}
		
		if(isDistinct){
			join = new Distinct( join, columnExpressions );
		}
		
		if(orderBy != null){
			join = new SortedResult( join, orderBy );
		}
		
		return true;
    }

    
    /**
     * If this ResultSet is use any type of grouping. This means that GroupResult need create and that
     * the ResultSet is not updataable. 
     */
    final boolean isGroupResult(){
    	return groupBy != null || having != null || isAggregateFunction;
    }
    
    
	/**
	 * Set the link between the Named Expression and the Table object
	 * in the condition.
	 * If there are cascade Joins then follow the tree with a recursion. 
	 */
    private void compileJoin( Join join ) throws Exception{
        if(join.condition != null) compileLinkExpressionParams( join.condition );
        if(join.left instanceof Join){
            compileJoin( (Join)join.left );
        }
        if(join.right instanceof Join){
            compileJoin( (Join)join.right );
        }
    }
    
    
    private void compileLinkExpression( Expression expr) throws Exception{
		if(expr.getType() == Expression.NAME)
			 compileLinkExpressionName( (ExpressionName)expr);
		else compileLinkExpressionParams( expr );
    }
    
    
    /**
     * setzt die Verbindung einer named Expresion zur Table und Spaltenindex
     * This means a colun name in the SQL statement is link to it table source
     */
    private void compileLinkExpressionName( ExpressionName expr ) throws Exception{
            String tableAlias = expr.getTableAlias();
            if(tableAlias != null){
                int t=0;
                for(; t<from.size(); t++){
					DataSource fromEntry = from.get(t);
                    if(tableAlias.equalsIgnoreCase( fromEntry.getAlias() )){
                        TableView table = fromEntry.getTableView();
                        int colIdx = table.findColumnIdx( expr.getName() );
                        if(colIdx>=0){
                            // Column gefunden und Table zur Expression setzen
                            expr.setFrom( fromEntry, colIdx, table );
                            break;
                        }else
                            throw Utils.createSQLException("Invalid column name '" + expr.getName() + "'.");
                    }
                }
                if(t==from.size()) throw Utils.createSQLException( "The column prefix '" + tableAlias + "' does not match with a table name or alias name used in this query" );
            }else{
                // column name ohne table name
                int t=0;
                for(; t<from.size(); t++){
					DataSource fromEntry = from.get(t);
                    TableView table = fromEntry.getTableView();
                    int colIdx = table.findColumnIdx( expr.getName() );
                    if(colIdx>=0){
                        // Column gefunden und Table zur Expression setzen
                        expr.setFrom( fromEntry, colIdx, table );
                        break;
                    }
                }
                if(t>=from.size()){
                    throw Utils.createSQLException("Invalid column name '" + expr.getName() + "'.");
                }
            }
            compileLinkExpressionParams(expr);
    }
    

    private void compileLinkExpressionParams(Expression expr) throws Exception{
            // SubExpresion überprüfen
            Expression[] params = expr.getParams();
			isAggregateFunction = isAggregateFunction || expr.getType() >= Expression.GROUP_BEGIN;
            if(params != null){
                for(int k=0; k<params.length; k++){
                    Expression param = params[k];
					int type = param.getType();
					isAggregateFunction = isAggregateFunction || type >= Expression.GROUP_BEGIN;
                    if(type == Expression.NAME)
                         compileLinkExpressionName( (ExpressionName)param );
                    else compileLinkExpressionParams( param );
                }
            }
    }
    

    private final int compileAdd_All_Table_Columns( DataSource fromEntry, TableView table, int position){
        for(int k=0; k<table.columns.size(); k++){
            ExpressionName expr = new ExpressionName( table.columns.get(k).getName() );
            expr.setFrom( fromEntry, k, table );
            columnExpressions.add( position++, expr );
        }
        return position;
    }
    

    /**
     * The main method to execute this Command and create a ResultSet.
     */
    void executeImpl(SSConnection con, SSStatement st) throws Exception{
        compile(con);
        if((st.rsType == ResultSet.TYPE_SCROLL_INSENSITIVE || st.rsType == ResultSet.TYPE_SCROLL_SENSITIVE) &&
        	!join.isScrollable()){
        	join = new Scrollable(join);
        }
        join.execute();
        rs =  new SSResultSet( st, this );
    }
    
    
    /**
     * Is used from ResultSet.beforeFirst().
     *
     */
    void beforeFirst() throws Exception{
		join.beforeFirst();
    }
    
    
	/**
	 * Is used from ResultSet.isBeforeFirst().
	 */
	boolean isBeforeFirst() throws SQLException{
		return join.isBeforeFirst();
	}
    

	/**
	 * Is used from ResultSet.isFirst().
	 */
	boolean isFirst() throws SQLException{
		return join.isFirst();
	}
    

	/**
     * Is used from ResultSet.first().
     */
    boolean first() throws Exception{
		return join.first();
    }


	/**
	 * Is used from ResultSet.previous().
	 */
	boolean previous() throws Exception{
		return join.previous();
	}


	/**
	 * move to the next row.
	 * @return true if the next row valid
	 * @throws Exception
	 */
    boolean next() throws Exception{
        if(maxRows > 0 && join.getRow() > maxRows){
        	join.afterLast();
        	return false;
        }
		return join.next();
    }


	/**
	 * Is used from ResultSet.last().
	 */
	final boolean last() throws Exception{
		if(maxRows > 0){
			return join.absolute(maxRows);
		}
		return join.last();
	}


	/**
	 * Is used from ResultSet.afterLast().
	 */
	final void afterLast() throws Exception{
		join.afterLast();
	}


	/**
	 * Is used from ResultSet.isLast().
	 */
	boolean isLast() throws Exception{
		return join.isLast();
	}
    

	/**
	 * Is used from ResultSet.isAfterLast().
	 */
	boolean isAfterLast() throws SQLException{
		return join.isAfterLast();
	}
    

	/**
	 * Is used from ResultSet.absolute().
	 */
	final boolean absolute(int row) throws Exception{
		return join.absolute(row);
	}


	/**
	 * Is used from ResultSet.relative().
	 */
	final boolean relative(int rows) throws Exception{
		return join.relative(rows);
	}


	/**
	 * Is used from ResultSet.afterLast().
	 */
	final int getRow() throws Exception{
		int row = join.getRow();
		if(maxRows > 0 && row > maxRows) return 0;
		return row;
	}


	final void updateRow(SSConnection con, Expression[] newRowSources) throws SQLException{
		int savepoint = con.getSavepoint();
		try{
			//loop through all tables of this ResultSet 
			for(int t=0; t<from.size(); t++){
				TableViewResult result = TableViewResult.getTableViewResult( from.get(t) );
				TableView table = result.getTableView();
				Columns tableColumns = table.columns;
				int count = tableColumns.size();
				
				// order the new Values after it position in the table
				Expression[] updateValues = new Expression[count];
				boolean isUpdateNeeded = false;
				for(int i=0; i<columnExpressions.size(); i++){
					Expression src = newRowSources[i];
					if(src != null && (!(src instanceof ExpressionValue) || !((ExpressionValue)src).isEmpty())){	
						Expression col = columnExpressions.get(i);
						if(!col.isDefinitelyWritable())
							throw Utils.createSQLException("Column " + i + " is read only.");
						ExpressionName exp = (ExpressionName)col;
						if(table == exp.getTable()){
							updateValues[exp.getColumnIndex()] = src;
							isUpdateNeeded = true;
							continue;
						}
					}
				}
				
				// save the new values if there are new value for this table
				if(isUpdateNeeded){
					result.updateRow(updateValues);
				}
			}
		}catch(Throwable e){
			con.rollback(savepoint);
			throw Utils.createSQLException(e);
		}finally{
			if(con.getAutoCommit()) con.commit();
		}
	}
	
	final void insertRow(SSConnection con, Expression[] newRowSources) throws SQLException{
		if(from.size() > 1)
			throw Utils.createSQLException("InsertRow not supported on joins.");
		if(from.size() == 0)
			throw Utils.createSQLException("InsertRow need a FROM expression.");
		
		int savepoint = con.getSavepoint();
		try{
			TableViewResult result = TableViewResult.getTableViewResult( from.get(0) );
			TableView table = result.getTableView();
			Columns tabColumns = table.columns;
			int count = tabColumns.size();
					
			// order the new Values after it position in the table
			Expression[] updateValues = new Expression[count];
			if(newRowSources != null){
				for(int i=0; i<columnExpressions.size(); i++){
					Expression src = newRowSources[i];
					if(src != null && (!(src instanceof ExpressionValue) || !((ExpressionValue)src).isEmpty())){	
						Expression rsColumn = columnExpressions.get(i); // Column of the ResultSet
						if(!rsColumn.isDefinitelyWritable())
							throw Utils.createSQLException("Column " + i + " is read only.");
						ExpressionName exp = (ExpressionName)rsColumn;
						if(table == exp.getTable()){
							updateValues[exp.getColumnIndex()] = src;
							continue;
						}
					}
					updateValues[i] = null;
				}
			}
					
			// save the new values if there are new value for this table
			result.insertRow(updateValues);
		}catch(Throwable e){
			con.rollback(savepoint);
			throw Utils.createSQLException(e);
		}finally{
			if(con.getAutoCommit()) con.commit();
		}
	}
	
	final void deleteRow(SSConnection con) throws SQLException{
		int savepoint = con.getSavepoint();
		try{
			if(from.size() > 1)
				throw Utils.createSQLException("DeleteRow not supported on joins.");
			if(from.size() == 0)
				throw Utils.createSQLException("DeleteRow need a FROM expression.");
			TableViewResult.getTableViewResult( from.get(0) ).deleteRow();
		}catch(Throwable e){
			con.rollback(savepoint);
			throw Utils.createSQLException(e);
		}finally{
			if(con.getAutoCommit()) con.commit();
		}
	}
	
	
	/**
	 * The returning index start at 0.
	 */
	public int findColumn(String columnName) throws SQLException {
		Expressions columns = columnExpressions;
		// FIXME performance
		for(int i=0; i<columns.size(); i++){
			if(columnName.equalsIgnoreCase(columns.get(i).getAlias()))
				return i;
		}
		throw Utils.createSQLException("Column '"+columnName+"' not found." );
	}
	
	
	/**
	 * Set if the keyword DISTINCT occur in the SELECT expession. 
	 */
	final void setDistinct(boolean distinct){
		this.isDistinct = distinct;
	}
	
	
	/**
	 * Set the RowSource expression from the FROM clause. 
	 * The Simples case is only a Table (TableResult)
	 */
    final void setSource(RowSource join){
        this.join = join;
    }

	/**
	 * List of all Tables and Views. 
	 * This is needed to replace the table aliases in the columnExpressions with the real soures.
	 */
    final void setFrom( DataSources from ){
        this.from = from;
    }

	/**
	 * Is used from CommandSelect, CommandDelete and CommandUpdate
	 * @param where
	 */
	final void setWhere( Expression where ){
		this.where = where;
	}

	final void setGroup(Expressions group){
        this.groupBy = group;
    }

	final void setHaving(Expression having){
        this.having = having;
    }

	final void setOrder(Expressions order){
        this.orderBy = order;
    }
    

	final void setMaxRows(int max){
		maxRows = max;
	}
}