001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/agg/OrPredicate.java#5 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2007-2008 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.rolap.agg; 011 012 import mondrian.rolap.StarPredicate; 013 import mondrian.rolap.RolapStar; 014 import mondrian.rolap.BitKey; 015 import mondrian.rolap.sql.SqlQuery; 016 017 import java.util.*; 018 019 /** 020 * Predicate which is the union of a list of predicates. It evaluates to 021 * true if any of the predicates evaluates to true. 022 * 023 * @see OrPredicate 024 * 025 * @author jhyde 026 * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/OrPredicate.java#5 $ 027 */ 028 public class OrPredicate extends ListPredicate { 029 030 public OrPredicate(List<StarPredicate> predicateList) { 031 super(predicateList); 032 } 033 034 public boolean evaluate(List<Object> valueList) { 035 // NOTE: If we know that every predicate in the list is a 036 // ValueColumnPredicate, we could optimize the evaluate method by 037 // building a value list at construction time. But it's a tradeoff, 038 // considering the extra time and space required. 039 for (StarPredicate childPredicate : children) { 040 if (childPredicate.evaluate(valueList)) { 041 return true; 042 } 043 } 044 return false; 045 } 046 047 public StarPredicate or(StarPredicate predicate) { 048 if (predicate instanceof OrPredicate && 049 predicate.getConstrainedColumnBitKey().equals(getConstrainedColumnBitKey())) { 050 // Do not collapse OrPredicates with different number of columns. 051 // Keeping them separate helps the SQL translation to IN-list. 052 ListPredicate that = (ListPredicate) predicate; 053 final List<StarPredicate> list = 054 new ArrayList<StarPredicate>(children); 055 list.addAll(that.children); 056 return new OrPredicate(list); 057 } else { 058 final List<StarPredicate> list = 059 new ArrayList<StarPredicate>(children); 060 list.add(predicate); 061 return new OrPredicate(list); 062 } 063 } 064 065 public StarPredicate and(StarPredicate predicate) { 066 List<StarPredicate> list = new ArrayList<StarPredicate>(); 067 list.add(this); 068 list.add(predicate); 069 return new AndPredicate(list); 070 } 071 072 /** 073 * Checks whether a predicate can be translated using an IN list, and groups 074 * predicates based on how many columns can be translated using IN list. If 075 * none of the columns can be made part of IN, the entire predicate will be 076 * translated using AND/OR. This method identifies all the columns that can 077 * be part of IN and and categorizes this predicate based on number of 078 * column values to use in the IN list. 079 * 080 * @param predicate predicate to analyze 081 * @param sqlQuery Query 082 * @param predicateMap the map containing predicates analyzed so far 083 */ 084 private void checkInListForPredicate( 085 StarPredicate predicate, 086 SqlQuery sqlQuery, 087 Map<BitKey, List<StarPredicate>> predicateMap) { 088 BitKey inListRHSBitKey; 089 090 if (predicate instanceof ValueColumnPredicate) { 091 // OR of column values from the same column 092 inListRHSBitKey = 093 ((ValueColumnPredicate) predicate).checkInList(columnBitKey); 094 } else if (predicate instanceof AndPredicate) { 095 // OR of ANDs over a set of values over the same column set 096 inListRHSBitKey = 097 ((AndPredicate) predicate).checkInList(sqlQuery, columnBitKey); 098 } else { 099 inListRHSBitKey = columnBitKey.emptyCopy(); 100 } 101 List<StarPredicate> predicateGroup = 102 predicateMap.get(inListRHSBitKey); 103 if (predicateGroup == null) { 104 predicateGroup = new ArrayList<StarPredicate> (); 105 predicateMap.put(inListRHSBitKey, predicateGroup); 106 } 107 predicateGroup.add(predicate); 108 } 109 110 private void checkInList( 111 SqlQuery sqlQuery, 112 Map<BitKey, List<StarPredicate>> predicateMap) 113 { 114 for (StarPredicate predicate : children) { 115 checkInListForPredicate(predicate, sqlQuery, predicateMap); 116 } 117 } 118 119 /** 120 * Translates a list of predicates over the same set of columns into sql 121 * using IN list where possible. 122 * 123 * @param sqlQuery Query 124 * @param buf buffer to build sql 125 * @param inListRHSBitKey which column positions are included in the IN predicate 126 * The non included positions corresponde to columns that are nulls. 127 * @param predicateList the list of predicates to translate. 128 */ 129 private void toInListSql( 130 SqlQuery sqlQuery, 131 StringBuilder buf, 132 BitKey inListRHSBitKey, 133 List<StarPredicate> predicateList) 134 { 135 // Make a col position to column map to aid search. 136 Map<Integer, RolapStar.Column> columnMap = 137 new HashMap<Integer, RolapStar.Column>(); 138 139 for (RolapStar.Column column : columns) { 140 columnMap.put(column.getBitPosition(), column); 141 } 142 143 buf.append("("); 144 // First generate nulls for the columns which will not be included 145 // in the IN list 146 147 boolean firstNullColumnPredicate = true; 148 for (Integer colPos : columnBitKey.andNot(inListRHSBitKey)) { 149 if (firstNullColumnPredicate) { 150 firstNullColumnPredicate = false; 151 } else { 152 buf.append(" and "); 153 } 154 String expr = columnMap.get(colPos).generateExprString(sqlQuery); 155 buf.append(expr); 156 buf.append(" is null"); 157 } 158 159 // Now the IN list part 160 if (inListRHSBitKey.isEmpty()) { 161 return; 162 } 163 164 if (firstNullColumnPredicate) { 165 firstNullColumnPredicate = false; 166 } else { 167 buf.append(" and "); 168 } 169 170 // First add the column names; 171 boolean multiInList = inListRHSBitKey.toBitSet().cardinality() > 1; 172 if (multiInList) { 173 // Multi-IN list 174 buf.append("("); 175 } 176 177 boolean firstColumn = true; 178 for (Integer colPos : inListRHSBitKey) { 179 if (firstColumn) { 180 firstColumn = false; 181 } else { 182 buf.append(", "); 183 } 184 String expr = columnMap.get(colPos).generateExprString(sqlQuery); 185 buf.append(expr); 186 } 187 if (multiInList) { 188 // Multi-IN list 189 buf.append(")"); 190 } 191 buf.append(" in ("); 192 193 boolean firstPredicate = true; 194 for (StarPredicate predicate : predicateList) { 195 if (firstPredicate) { 196 firstPredicate = false; 197 } else { 198 buf.append(", "); 199 } 200 201 if (predicate instanceof AndPredicate) { 202 ((AndPredicate)predicate).toInListSql(sqlQuery, buf, inListRHSBitKey); 203 } else { 204 assert (predicate instanceof ValueColumnPredicate); 205 ((ValueColumnPredicate)predicate).toInListSql(sqlQuery, buf); 206 } 207 } 208 buf.append(")"); 209 buf.append(")"); 210 } 211 212 public void toSql(SqlQuery sqlQuery, StringBuilder buf) { 213 // 214 // If possible, translate the predicate using IN lists. 215 // 216 // Two possibilities: 217 // (1) top-level can be directly tranlated to IN-list 218 // examples: 219 // (country IN (USA, Canada)) 220 // 221 // ((country, satte) in ((USA, CA), (USA, OR))) 222 // 223 // (2) child level can be translated to IN list: this allows IN list 224 // predicates generated such as: 225 // (country, state) IN ((USA, CA), (USA, OR)) 226 // OR 227 // (country, state, city) IN ((USA, CA, SF), (USA, OR, Portland)) 228 // 229 // The second case is handled by calling toSql on the children in 230 // super.toSql(). 231 // 232 Map<BitKey, List<StarPredicate>> predicateMap = 233 new HashMap<BitKey, List<StarPredicate>> (); 234 235 boolean first = true; 236 checkInList(sqlQuery, predicateMap); 237 buf.append("("); 238 239 for (BitKey columnKey : predicateMap.keySet()) { 240 List<StarPredicate> predList = predicateMap.get(columnKey); 241 if (columnKey.isEmpty() || predList.size() <= 1) { 242 // Not possible to have IN list, or only one predicate 243 // in the group. 244 for (StarPredicate pred : predList) { 245 if (first) { 246 first = false; 247 } else { 248 buf.append(" or "); 249 } 250 pred.toSql(sqlQuery, buf); 251 } 252 } else { 253 // Translate the rest 254 if (first) { 255 first = false; 256 } else { 257 buf.append(" or "); 258 } 259 toInListSql(sqlQuery, buf, columnKey, predList); 260 } 261 } 262 263 buf.append(")"); 264 } 265 266 protected String getOp() { 267 return "or"; 268 } 269 } 270 271 // End OrPredicate.java