/* * Copyright (C) 2000 - 2008 TagServlet Ltd * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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 for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.naryx.tagfusion.cfm.queryofqueries; /** * This class represents a SQL select statement. * You can define the columns that will be selected using addSelectColumn() * and set the tables the select is from using setFromList() */ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import com.naryx.tagfusion.cfm.engine.catchDataFactory; import com.naryx.tagfusion.cfm.engine.cfData; import com.naryx.tagfusion.cfm.engine.cfQueryResultData; import com.naryx.tagfusion.cfm.engine.cfQueryResultSetMetaData; import com.naryx.tagfusion.cfm.engine.cfSession; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; public class unionStatement extends selectStatement{ private selectStatement statement1, statement2; private boolean all; unionStatement( selectStatement _statement1, selectStatement _statement2, boolean _all ){ super(null,true); statement1 = _statement1; statement2 = _statement2; all = _all; }// unionStatement() public selectStatement copy(){ unionStatement copy = new unionStatement( statement1.copy(), statement2.copy(), all ); copy.setOrderByList( this.orderByList ); return copy; } cfQueryResultData execute( cfSession _session, List<cfData> _pData ) throws cfmRunTimeException { cfQueryResultData result1 = statement1.execute( _session, _pData ); cfQueryResultData result2 = statement2.execute( _session, _pData ); // THROW EXCEPTION IF COL COUNTS DON"T MATCH if ( result1.getNoColumns() != result2.getNoColumns() ){ throw new cfmRunTimeException( catchDataFactory.generalException("errorCode.expressionError", "queryofqueries.invalidUnion", null ) ); } cfQueryResultData resultData; if ( all ){ resultData = unionAll( result1, result2 ); }else{ resultData = unionDistinct( result1, result2 ); } doOrderBy( resultData ); return resultData; }// execute() private void doOrderBy( cfQueryResultData _data ) throws cfmRunTimeException{ if ( orderByList == null ) { return; } List<orderByCol> orderByCopy = new ArrayList<orderByCol>( orderByList.size() ); for ( int j = 0; j < orderByList.size(); j++ ){ orderByCopy.add( orderByList.get(j).copy() ); } cfQueryResultSetMetaData queryMetaData = null; try { queryMetaData = ( (cfQueryResultSetMetaData) _data.getMetaData() ); } catch (SQLException e) { // shouldn't happen throw new cfmRunTimeException( catchDataFactory.generalException("errorCode.expressionError", "queryofqueries.badMetadata", new String[]{e.getMessage()} ) ); } String[] colNames = queryMetaData.getColumnNames(); // validate the order by list first int no_cols = colNames.length; for ( int i = 0; i < orderByCopy.size(); i++ ){ orderByCol col = orderByCopy.get( i ); if ( col.isIndex() ){ // if column is an index then check it's within the number of accessible columns if ( col.getIndex() > no_cols || col.getIndex() < 1 ){ // invalid index throw new cfmRunTimeException( catchDataFactory.generalException("errorCode.expressionError", "queryofqueries.invalidIndex", new String[]{col.getIndex()+""} ) ); } }else{ boolean found = false; // loop over the column names looking for a match for ( int j = 0; j < colNames.length; j++ ){ if ( colNames[j].equalsIgnoreCase( col.getColName() ) ){ found = true; break; } } if ( !found ){ throw new cfmRunTimeException( catchDataFactory.generalException("errorCode.expressionError", "queryofqueries.invalidIndex", new String[]{col.getColName()} ) ); } // if it wasn't found then check if it's in the lookup /*if ( !found ){ String actualColname = (String) _lookup.get( col.getColName() ); if ( actualColname != null ){ col.setColName( actualColname ); }else{ // invalid column name throw new cfmRunTimeException( catchDataFactory.generalException("errorCode.expressionError", "queryofqueries.invalidIndex", new String[]{col.getColName()} ) ); } }*/ } } _data.sort( orderByCopy ); } private static cfQueryResultData unionAll( cfQueryResultData _data1, cfQueryResultData _data2 ) { List<List<cfData>> tableRows2 = _data2.getTableRows(); List<List<cfData>> tableRows1 = _data1.getTableRows(); // reset the table rows of the first query to contain the rows of the second query also // it's safe to return the first query as it will not be reused since the context is // within a query of queries for ( int i = 0; i < tableRows2.size(); i++ ){ List<cfData> nextRow = tableRows2.get( i ); // since we're moving these to a different query we need to update the queryTableData for each cfData in the row for ( int j = 0; j < nextRow.size(); j++ ){ ( (cfData) nextRow.get( j ) ).setQueryTableData( tableRows1, j+1 ); } } _data1.getTableRows().addAll( tableRows2 ); return _data1; }// unionAll() private cfQueryResultData unionDistinct( cfQueryResultData _data1, cfQueryResultData _data2 ) { List<List<cfData>> tableRows1 = _data1.getTableRows(); List<List<cfData>> tableRows2 = _data2.getTableRows(); List<List<cfData>> resultRows = new ArrayList<List<cfData>>(); Comparator<List<cfData>> comp = new RowComparator(); // as cfmx does with a union, Collections.sort( tableRows1, comp ); addDistinct( resultRows, tableRows1, comp ); addDistinct( resultRows, tableRows2, comp ); // note the reuse of _data1; safe to do so since _data1 is within the context // of the qoq and will not be reused elsewhere _data1.setQueryData( resultRows ); return _data1; }// unionDistinct() /* * adds all the objects in the _from list to the _to list that * don't already exist there (equivalence test not instance test) */ private static void addDistinct( List<List<cfData>> _to, List<List<cfData>> _from, Comparator<List<cfData>> _comp ){ int fromSize = _from.size(); // DEAL WITH: this could throw an exception on the cfData.equals() // catch it here? or throw it up?? or does it throw an exception at all? // it might be caught within the _to.contains for ( int i = 0; i < fromSize; i++ ){ List<cfData> next = _from.get( i ); int find = Collections.binarySearch( _to, next, _comp ); if ( find < 0 ){ // since we're moving these to a different query we need to update the queryTableData for each cfData in the row for ( int j = 0; j < next.size(); j++ ){ ( (cfData) next.get( j ) ).setQueryTableData( _to, j+1 ); } _to.add( -1 * ( find + 1 ), next ); } } }// addDistinct() private static class RowComparator implements Comparator<List<cfData>> { public int compare( List<cfData> o1, List<cfData> o2 ) { return compareRows( o1, o2 ); } } private static int compareRows( List<cfData> _row1, List<cfData> _row2 ){ // INVARIANT: _row1.size() == _row2.size() for ( int i = 0; i < _row2.size(); i++ ){ int compareResult = cfData.compare( _row1.get(i), _row2.get(i) ); if ( compareResult != 0 ){ return compareResult; } } return 0; } }// unionStatement