001 /* 002 // This software is subject to the terms of the Common Public License 003 // Agreement, available at the following URL: 004 // http://www.opensource.org/licenses/cpl.html. 005 // Copyright (C) 2004-2005 TONBELLER AG 006 // All Rights Reserved. 007 // You must accept the terms of that agreement to use this software. 008 */ 009 package mondrian.rolap; 010 011 import java.util.*; 012 import mondrian.olap.*; 013 import mondrian.olap.fun.*; 014 import mondrian.rolap.sql.TupleConstraint; 015 016 /** 017 * creates a {@link mondrian.olap.NativeEvaluator} that evaluates NON EMPTY 018 * CrossJoin in SQL. The generated SQL will join the dimension tables with 019 * the fact table and return all combinations that have a 020 * corresponding row in the fact table. The current context (slicer) is 021 * used for filtering (WHERE clause in SQL). This very effective computes 022 * queris like 023 * <pre> 024 * select ... 025 * NON EMTPY crossjoin([product].[name].members, [customer].[name].members) on rows 026 * froms [Sales] 027 * where ([store].[store #14]) 028 * </pre> 029 * where both, customer.name and product.name have many members, but the resulting 030 * crossjoin only has few. 031 * <p> 032 * The implementation currently can not handle sets containting 033 * parent/child hierarchies, ragged hierarchies, calculated members and 034 * the ALL member. Otherwise all 035 * 036 * @author av 037 * @since Nov 21, 2005 038 */ 039 public class RolapNativeCrossJoin extends RolapNativeSet { 040 041 public RolapNativeCrossJoin() { 042 super.setEnabled( 043 MondrianProperties.instance().EnableNativeCrossJoin.get()); 044 } 045 046 /** 047 * Constraint that restricts the result to the current context. 048 * 049 * <p>If the current context contains calculated members, silently ignores 050 * them. This means means that too many members are returned, but this does 051 * not matter, because the {@link RolapConnection.NonEmptyResult} will 052 * filter out these later.</p> 053 */ 054 static class NonEmptyCrossJoinConstraint extends SetConstraint { 055 NonEmptyCrossJoinConstraint( 056 CrossJoinArg[] args, 057 RolapEvaluator evaluator) 058 { 059 // Cross join ignores calculated members, including the ones from 060 // the slicer. 061 super(args, evaluator, false); 062 } 063 } 064 065 protected boolean restrictMemberTypes() { 066 return false; 067 } 068 069 NativeEvaluator createEvaluator( 070 RolapEvaluator evaluator, 071 FunDef fun, 072 Exp[] args) 073 { 074 if (!isEnabled()) { 075 // native crossjoins were explicitly disabled, so no need 076 // to alert about not using them 077 return null; 078 } 079 RolapCube cube = evaluator.getCube(); 080 081 CrossJoinArg[] cargs = checkCrossJoin(evaluator, fun, args); 082 083 if (cargs == null) { 084 // Something in the arguments to the crossjoin prevented 085 // native evaluation; may need to alert 086 alertCrossJoinNonNative( 087 evaluator, 088 fun, 089 "arguments not supported"); 090 return null; 091 } 092 093 // check if all CrossJoinArgs are "All" members or Calc members 094 // "All" members do not have relational expression, and Calc members 095 // in the input could produce incorrect results. 096 // 097 // If NECJ only has AllMembers, or if there is at least one CalcMember, 098 // then sql evaluation is not possible. 099 int countNonNativeInputArg = 0; 100 101 for (CrossJoinArg arg : cargs) { 102 if (arg instanceof MemberListCrossJoinArg) { 103 MemberListCrossJoinArg cjArg = 104 (MemberListCrossJoinArg)arg; 105 if (cjArg.hasAllMember() || cjArg.isEmptyCrossJoinArg()) { 106 ++countNonNativeInputArg; 107 } 108 if (cjArg.hasCalcMembers()) { 109 countNonNativeInputArg = cargs.length; 110 break; 111 } 112 } 113 } 114 115 if (countNonNativeInputArg == cargs.length) { 116 // If all inputs contain "All" members; or 117 // if all inputs are MemberListCrossJoinArg with empty member list 118 // content, then native evaluation is not feasible. 119 alertCrossJoinNonNative( 120 evaluator, 121 fun, 122 "either all arguments contain the ALL member, " + 123 "or empty member lists, or one has a calculated member"); 124 return null; 125 } 126 127 if (isPreferInterpreter(cargs, true)) { 128 // Native evaluation wouldn't buy us anything, so no 129 // need to alert 130 return null; 131 } 132 133 List<RolapLevel> levels = new ArrayList<RolapLevel>(); 134 135 for (int i = 0; i < cargs.length; i++) { 136 RolapLevel level = cargs[i].getLevel(); 137 if (level != null) { 138 // Only add non null levels. These levels have real 139 // constraints. 140 levels.add(level); 141 } 142 } 143 144 if ((cube.isVirtual() && 145 !evaluator.getQuery().nativeCrossJoinVirtualCube())) { 146 // Something in the query at large (namely, some unsupported 147 // function on the [Measures] dimension) prevented native 148 // evaluation with virtual cubes; may need to alert 149 alertCrossJoinNonNative( 150 evaluator, 151 fun, 152 "not all functions on [Measures] dimension supported"); 153 return null; 154 } 155 if (!NonEmptyCrossJoinConstraint.isValidContext( 156 evaluator, 157 false, 158 levels.toArray(new RolapLevel[levels.size()]))) { 159 // Missing join conditions due to non-conforming dimensions 160 // meant native evaluation would have led to a true cross 161 // product, which we want to defer instead of pushing it down; 162 // so no need to alert 163 return null; 164 } 165 166 // join with fact table will always filter out those members 167 // that dont have a row in the fact table 168 if (!evaluator.isNonEmpty()) { 169 return null; 170 } 171 172 LOGGER.debug("using native crossjoin"); 173 174 // Create a new evaluation context, eliminating any outer context for 175 // the dimensions referenced by the inputs to the NECJ 176 // (otherwise, that outer context would be incorrectly intersected 177 // with the constraints from the inputs). 178 evaluator = evaluator.push(); 179 180 Member[] evalMembers = evaluator.getMembers().clone(); 181 for (RolapLevel level : levels) { 182 RolapHierarchy hierarchy = level.getHierarchy(); 183 for (int i = 0; i < evalMembers.length; ++i) { 184 Dimension evalMemberDimension = 185 evalMembers[i].getHierarchy().getDimension(); 186 if (evalMemberDimension == hierarchy.getDimension()) { 187 evalMembers[i] = hierarchy.getAllMember(); 188 } 189 } 190 } 191 evaluator.setContext(evalMembers); 192 193 TupleConstraint constraint = new NonEmptyCrossJoinConstraint(cargs, evaluator); 194 SchemaReader schemaReader = evaluator.getSchemaReader(); 195 return new SetEvaluator(cargs, schemaReader, constraint); 196 } 197 198 private void alertCrossJoinNonNative( 199 RolapEvaluator evaluator, 200 FunDef fun, 201 String reason) 202 { 203 if (!(fun instanceof NonEmptyCrossJoinFunDef)) { 204 // Only alert for an explicit NonEmptyCrossJoin, 205 // since query authors use that to indicate that 206 // they expect it to be "wicked fast" 207 return; 208 } 209 if (!evaluator.getQuery().shouldAlertForNonNative(fun)) { 210 return; 211 } 212 RolapUtil.alertNonNative("NonEmptyCrossJoin", reason); 213 } 214 } 215 216 // End RolapNativeCrossJoin.java