001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2004-2005 TONBELLER AG 008// Copyright (C) 2006-2013 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.olap.*; 014import mondrian.olap.fun.NonEmptyCrossJoinFunDef; 015import mondrian.rolap.sql.*; 016 017import java.util.*; 018 019/** 020 * Creates a {@link mondrian.olap.NativeEvaluator} that evaluates NON EMPTY 021 * CrossJoin in SQL. The generated SQL will join the dimension tables with 022 * the fact table and return all combinations that have a 023 * corresponding row in the fact table. The current context (slicer) is 024 * used for filtering (WHERE clause in SQL). This very effective computes 025 * queries like 026 * 027 * <pre> 028 * SELECT ... 029 * NON EMTPY Crossjoin( 030 * [product].[name].members, 031 * [customer].[name].members) ON ROWS 032 * FROM [Sales] 033 * WHERE ([store].[store #14]) 034 * </pre> 035 * 036 * where both, customer.name and product.name have many members, but the 037 * resulting crossjoin only has few. 038 * 039 * <p>The implementation currently can not handle sets containting 040 * parent/child hierarchies, ragged hierarchies, calculated members and 041 * the ALL member. Otherwise all 042 * 043 * @author av 044 * @since Nov 21, 2005 045 */ 046public class RolapNativeCrossJoin extends RolapNativeSet { 047 048 public RolapNativeCrossJoin() { 049 super.setEnabled( 050 MondrianProperties.instance().EnableNativeCrossJoin.get()); 051 } 052 053 /** 054 * Constraint that restricts the result to the current context. 055 * 056 * <p>If the current context contains calculated members, silently ignores 057 * them. This means means that too many members are returned, but this does 058 * not matter, because the {@link RolapConnection.NonEmptyResult} will 059 * filter out these later.</p> 060 */ 061 static class NonEmptyCrossJoinConstraint extends SetConstraint { 062 NonEmptyCrossJoinConstraint( 063 CrossJoinArg[] args, 064 RolapEvaluator evaluator) 065 { 066 // Cross join ignores calculated members, including the ones from 067 // the slicer. 068 super(args, evaluator, false); 069 } 070 071 public RolapMember findMember(Object key) { 072 for (CrossJoinArg arg : args) { 073 if (arg instanceof MemberListCrossJoinArg) { 074 final MemberListCrossJoinArg crossJoinArg = 075 (MemberListCrossJoinArg) arg; 076 final List<RolapMember> memberList = 077 crossJoinArg.getMembers(); 078 for (RolapMember rolapMember : memberList) { 079 if (key.equals(rolapMember.getKey())) { 080 return rolapMember; 081 } 082 } 083 } 084 } 085 return null; 086 } 087 } 088 089 protected boolean restrictMemberTypes() { 090 return false; 091 } 092 093 NativeEvaluator createEvaluator( 094 RolapEvaluator evaluator, 095 FunDef fun, 096 Exp[] args) 097 { 098 if (!isEnabled()) { 099 // native crossjoins were explicitly disabled, so no need 100 // to alert about not using them 101 return null; 102 } 103 RolapCube cube = evaluator.getCube(); 104 105 List<CrossJoinArg[]> allArgs = 106 crossJoinArgFactory() 107 .checkCrossJoin(evaluator, fun, args, false); 108 109 // checkCrossJoinArg returns a list of CrossJoinArg arrays. The first 110 // array is the CrossJoin dimensions. The second array, if any, 111 // contains additional constraints on the dimensions. If either the list 112 // or the first array is null, then native cross join is not feasible. 113 if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) { 114 // Something in the arguments to the crossjoin prevented 115 // native evaluation; may need to alert 116 alertCrossJoinNonNative( 117 evaluator, 118 fun, 119 "arguments not supported"); 120 return null; 121 } 122 123 CrossJoinArg[] cjArgs = allArgs.get(0); 124 125 // check if all CrossJoinArgs are "All" members or Calc members 126 // "All" members do not have relational expression, and Calc members 127 // in the input could produce incorrect results. 128 // 129 // If NECJ only has AllMembers, or if there is at least one CalcMember, 130 // then sql evaluation is not possible. 131 int countNonNativeInputArg = 0; 132 133 for (CrossJoinArg arg : cjArgs) { 134 if (arg instanceof MemberListCrossJoinArg) { 135 MemberListCrossJoinArg cjArg = 136 (MemberListCrossJoinArg)arg; 137 if (cjArg.hasAllMember() || cjArg.isEmptyCrossJoinArg()) { 138 ++countNonNativeInputArg; 139 } 140 if (cjArg.hasCalcMembers()) { 141 countNonNativeInputArg = cjArgs.length; 142 break; 143 } 144 } 145 } 146 147 if (countNonNativeInputArg == cjArgs.length) { 148 // If all inputs contain "All" members; or 149 // if all inputs are MemberListCrossJoinArg with empty member list 150 // content, then native evaluation is not feasible. 151 alertCrossJoinNonNative( 152 evaluator, 153 fun, 154 "either all arguments contain the ALL member, " 155 + "or empty member lists, or one has a calculated member"); 156 return null; 157 } 158 159 if (isPreferInterpreter(cjArgs, true)) { 160 // Native evaluation wouldn't buy us anything, so no 161 // need to alert 162 return null; 163 } 164 165 // Verify that args are valid 166 List<RolapLevel> levels = new ArrayList<RolapLevel>(); 167 for (CrossJoinArg cjArg : cjArgs) { 168 RolapLevel level = cjArg.getLevel(); 169 if (level != null) { 170 // Only add non null levels. These levels have real 171 // constraints. 172 levels.add(level); 173 } 174 } 175 176 if (cube.isVirtual() 177 && !evaluator.getQuery().nativeCrossJoinVirtualCube()) 178 { 179 // Something in the query at large (namely, some unsupported 180 // function on the [Measures] dimension) prevented native 181 // evaluation with virtual cubes; may need to alert 182 alertCrossJoinNonNative( 183 evaluator, 184 fun, 185 "not all functions on [Measures] dimension supported"); 186 return null; 187 } 188 189 if (!NonEmptyCrossJoinConstraint.isValidContext( 190 evaluator, 191 false, 192 levels.toArray(new RolapLevel[levels.size()]), 193 restrictMemberTypes())) 194 { 195 // Missing join conditions due to non-conforming dimensions 196 // meant native evaluation would have led to a true cross 197 // product, which we want to defer instead of pushing it down; 198 // so no need to alert 199 return null; 200 } 201 202 // join with fact table will always filter out those members 203 // that dont have a row in the fact table 204 if (!evaluator.isNonEmpty()) { 205 return null; 206 } 207 208 LOGGER.debug("using native crossjoin"); 209 210 // Create a new evaluation context, eliminating any outer context for 211 // the dimensions referenced by the inputs to the NECJ 212 // (otherwise, that outer context would be incorrectly intersected 213 // with the constraints from the inputs). 214 final int savepoint = evaluator.savepoint(); 215 216 try { 217 overrideContext(evaluator, cjArgs, null); 218 219 // Use the combined CrossJoinArg for the tuple constraint, 220 // which will be translated to the SQL WHERE clause. 221 CrossJoinArg[] cargs = combineArgs(allArgs); 222 223 // Now construct the TupleConstraint that contains both the CJ 224 // dimensions and the additional filter on them. It will make a 225 // copy of the evaluator. 226 TupleConstraint constraint = 227 buildConstraint(evaluator, fun, cargs); 228 229 // Use the just the CJ CrossJoiArg for the evaluator context, 230 // which will be translated to select list in sql. 231 final SchemaReader schemaReader = evaluator.getSchemaReader(); 232 return new SetEvaluator(cjArgs, schemaReader, constraint); 233 } finally { 234 evaluator.restore(savepoint); 235 } 236 } 237 238 CrossJoinArg[] combineArgs( 239 List<CrossJoinArg[]> allArgs) 240 { 241 CrossJoinArg[] cjArgs = allArgs.get(0); 242 if (allArgs.size() == 2) { 243 CrossJoinArg[] predicateArgs = allArgs.get(1); 244 if (predicateArgs != null) { 245 // Combine the CJ and the additional predicate args. 246 return Util.appendArrays(cjArgs, predicateArgs); 247 } 248 } 249 return cjArgs; 250 } 251 252 private TupleConstraint buildConstraint( 253 final RolapEvaluator evaluator, 254 final FunDef fun, 255 final CrossJoinArg[] cargs) 256 { 257 CrossJoinArg[] myArgs; 258 if (safeToConstrainByOtherAxes(fun)) { 259 myArgs = buildArgs(evaluator, cargs); 260 } else { 261 myArgs = cargs; 262 } 263 return new NonEmptyCrossJoinConstraint(myArgs, evaluator); 264 } 265 266 private CrossJoinArg[] buildArgs( 267 final RolapEvaluator evaluator, final CrossJoinArg[] cargs) 268 { 269 Set<CrossJoinArg> joinArgs = 270 crossJoinArgFactory().buildConstraintFromAllAxes(evaluator); 271 joinArgs.addAll(Arrays.asList(cargs)); 272 return joinArgs.toArray(new CrossJoinArg[joinArgs.size()]); 273 } 274 275 private boolean safeToConstrainByOtherAxes(final FunDef fun) { 276 return !(fun instanceof NonEmptyCrossJoinFunDef); 277 } 278 279 private void alertCrossJoinNonNative( 280 RolapEvaluator evaluator, 281 FunDef fun, 282 String reason) 283 { 284 if (!(fun instanceof NonEmptyCrossJoinFunDef)) { 285 // Only alert for an explicit NonEmptyCrossJoin, 286 // since query authors use that to indicate that 287 // they expect it to be "wicked fast" 288 return; 289 } 290 if (!evaluator.getQuery().shouldAlertForNonNative(fun)) { 291 return; 292 } 293 RolapUtil.alertNonNative("NonEmptyCrossJoin", reason); 294 } 295} 296 297// End RolapNativeCrossJoin.java 298