Skip to content

Commit

Permalink
MONDRIAN: limit size of memberlist arg to native CJ using
Browse files Browse the repository at this point in the history
mondrian.result.limit. Throw ResourceExceededException if
limit exceeded.

[git-p4: depot-paths = "//open/mondrian/": change = 11194]
  • Loading branch information
Rushan Chen committed Jun 19, 2008
1 parent 02fc833 commit c57e114
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 105 deletions.
31 changes: 31 additions & 0 deletions src/main/mondrian/olap/Util.java
Expand Up @@ -2329,6 +2329,37 @@ public static UserDefinedFunction createUdf(Class<?> udfClass) {

return udf;
}

/**
* Check the resultSize against the result limit setting. Throws
* LimitExceededDuringCrossjoin exception if limit exceeded.
*
* When it is called from RolapNativeSet.checkCrossJoin(), it is only
* possible to check the known input size, because the final CJ result
* will come from the DB(and will be checked against the limit when
* fetching from the JDBC result set, in SqlTupleReader.prepareTuples())
*
* @param resultSize
* @throws ResourceLimitExceededException
*/
public static void checkCJResultLimit(long resultSize) {
int resultLimit = MondrianProperties.instance().ResultLimit.get();

// Throw an exeption, if the size of the crossjoin exceeds the result
// limit.
//
if (resultLimit > 0 && resultLimit < resultSize) {
throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex(
resultSize, resultLimit);
}

// Throw an exception if the crossjoin exceeds a reasonable limit.
// (Yes, 4 billion is a reasonable limit.)
if (resultSize > Integer.MAX_VALUE) {
throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex(
resultSize, Integer.MAX_VALUE);
}
}
}

// End Util.java
24 changes: 5 additions & 19 deletions src/main/mondrian/olap/fun/CrossJoinFunDef.java
Expand Up @@ -18,7 +18,6 @@
import mondrian.olap.type.SetType;
import mondrian.olap.type.TupleType;
import mondrian.olap.type.Type;
import mondrian.resource.MondrianResource;
import mondrian.util.UnsupportedList;
import mondrian.rolap.*;

Expand Down Expand Up @@ -1925,26 +1924,13 @@ public static List<Member[]> crossJoin(
}
// Optimize nonempty(crossjoin(a,b)) ==
// nonempty(crossjoin(nonempty(a),nonempty(b))
long size = (long)list1.size() * (long)list2.size();
int resultLimit = MondrianProperties.instance().ResultLimit.get();

// Throw an exeption, if the size of the crossjoin exceeds the result
// limit.
//

// FIXME: If we're going to apply a NON EMPTY constraint later, it's
// possible that the ultimate result will be much smaller.
if (resultLimit > 0 && resultLimit < size) {
throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex(
size, resultLimit);
}

// Throw an exception if the crossjoin exceeds a reasonable limit.
// (Yes, 4 billion is a reasonable limit.)
if (size > Integer.MAX_VALUE) {
throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex(
size, Integer.MAX_VALUE);
}


long size = (long)list1.size() * (long)list2.size();
Util.checkCJResultLimit(size);

// Now we can safely cast size to an integer. It still might be very
// large - which means we're allocating a huge array which we might
// pare down later by applying NON EMPTY constraints - which is a
Expand Down
151 changes: 67 additions & 84 deletions src/main/mondrian/rolap/RolapNativeSet.java
Expand Up @@ -42,7 +42,7 @@ public abstract class RolapNativeSet extends RolapNative {

private SmartCache<Object, List<List<RolapMember>>> cache =
new SoftSmartCache<Object, List<List<RolapMember>>>();

/**
* Returns whether certain member types(e.g. calculated members) should
* disable native SQL evaluation for expressions containing them.
Expand Down Expand Up @@ -360,93 +360,43 @@ private MemberListCrossJoinArg(
this.hasAllMember = hasAllMember;
}

/**
* Creates an instance of {@link RolapNativeSet.CrossJoinArg}.
*
* @param args members in the list
* @param restrictMemberTypes whether calculated members are allowed
* @return MemberListCrossJoinArg if member list is well formed,
* NULL if not.
*/
static CrossJoinArg create(Exp[] args, boolean restrictMemberTypes) {
if (args.length == 0) {
return null;
}

List<RolapMember> memberList = new ArrayList<RolapMember>();
for (int i = 0; i < args.length; i++) {
if (!(args[i] instanceof MemberExpr)) {
return null;
}
memberList.add((RolapMember)
(((MemberExpr)args[i]).getMember()));
}

return create(memberList, restrictMemberTypes);
}

/**
* Creates an instance of {@link RolapNativeSet.CrossJoinArg}.
*
* @param args members in the list
* @param restrictMemberTypes whether calculated members are allowed
* @return MemberListCrossJoinArg if member list is well formed,
* NULL if not.
*
static CrossJoinArg create(List args, boolean restrictMemberTypes) {
if (args.isEmpty()) {
return null;
}
List<RolapMember> memberList = new RolapMember[args.size()];
for (int i = 0; i < args.size(); i++) {
if (!(args.get(i) instanceof RolapMember)) {
return null;
}
memberList[i] = (RolapMember) args.get(i);
}
return create(memberList, restrictMemberTypes);
}
*/

/**
* Creates an instance of {@link RolapNativeSet.CrossJoinArg},
* or returns null if the arguments are invalid. This method also
* records properties of the member list such as containing
* calc/non calc members, and containing the All member.
*
* <p>To be valid, the arguments must be non-calculated members of the
* same level (after filtering out any null members). There must be at
* least one member to begin with (may be null). If all members are
* nulls, then the result is a valid empty predicate.
*
* <p>REVIEW jvs 12-May-2007: but according to the code, if
* strict is false, then the argument is valid even if calculated
* members are presented (and then it's flagged appropriately
* for special handling downstream).
* <p>If restrictMemberTypes is set, then the resulting argument could
* contain calculated members. The newly created CrossJoinArg is marked
* appropriately for special handling downstream.
*
* <p>If restrictMemberTypes is false, then the resulting argument
* contains non-calculated members of the same level (after filtering
* out any null members).
*
* @param evaluator the current evaluator
* @param args members in the list
* @param restrictMemberTypes whether calculated members are allowed
* @return MemberListCrossJoinArg if member list is well formed,
* NULL if not.
*/
static CrossJoinArg create(
RolapEvaluator evaluator,
final List<RolapMember> args,
final boolean restrictMemberTypes)
{
if (args.isEmpty()) {
// First check that the member list will not result in a predicate
// longer than the underlying DB could support.
if (!isArgSizeSupported(evaluator, args.size())) {
return null;
}

RolapLevel level = null;
RolapLevel nullLevel = null;
boolean hasCalcMembers = false;
boolean hasNonCalcMembers = false;
boolean hasAllMember = false;

// First check that the member list will not result in a predicate
// longer than the underlying DB could support.
if (args.size() > MondrianProperties.instance()
.MaxConstraints.get()) {
return null;
}

int nNullMembers = 0;
try {
for (RolapMember m : args) {
Expand Down Expand Up @@ -707,28 +657,54 @@ protected CrossJoinArg checkMemberChildren(
return new DescendantsCrossJoinArg(level, member);
}

private static boolean isArgSizeSupported(
RolapEvaluator evaluator,
int argSize) {
boolean argSizeNotSupported = false;

if (argSize == 0) {
argSizeNotSupported = true;
}

// First check that the member list will not result in a predicate
// longer than the underlying DB could support.
if (!evaluator.getDialect().supportsUnlimitedValueList() &&
argSize > MondrianProperties.instance().MaxConstraints.get()) {
argSizeNotSupported = true;
}

return (!argSizeNotSupported);
}

/**
* Checks for a set constructor, <code>{member1, member2,
* &#46;&#46;&#46;}</code> that does not contain calculated members.
*
* @return an {@link CrossJoinArg} instance describing the enumeration,
* or null if <code>fun</code> represents something else.
*/
protected CrossJoinArg checkEnumeration(FunDef fun, Exp[] args) {
if (!"{}".equalsIgnoreCase(fun.getName())) {
protected CrossJoinArg checkEnumeration(RolapEvaluator evaluator,
FunDef fun, Exp[] args) {
// Return null if not the expected funciton name or input size.
if (!"{}".equalsIgnoreCase(fun.getName()) ||
!isArgSizeSupported(evaluator, args.length)) {
return null;
}
// also returns null if any member is calculated

List<RolapMember> memberList = new ArrayList<RolapMember>();
for (int i = 0; i < args.length; ++i) {
if (!(args[i] instanceof MemberExpr) ||
((MemberExpr) args[i]).getMember().isCalculated()) {
// also returns null if any member is calculated
return null;
}
}
return MemberListCrossJoinArg.create(args, restrictMemberTypes());
memberList.add((RolapMember)
(((MemberExpr)args[i]).getMember()));
}

return MemberListCrossJoinArg.create(evaluator, memberList, restrictMemberTypes());
}


/**
* Checks for <code>CrossJoin(&lt;set1&gt;, &lt;set2&gt)</code>, where
* set1 and set2 are one of
Expand Down Expand Up @@ -760,16 +736,18 @@ protected CrossJoinArg[] checkCrossJoin(
// MemberListCrossJoinArg.
// If either the inputs can be natively evaluated, or the result list
CrossJoinArg[] arg0 = checkCrossJoinArg(evaluator, args[0]);
ListCalc listCalc0;
List<RolapMember> list0 = null;
if (arg0 == null) {
if (MondrianProperties.instance().ExpandNonNative.get()) {
ListCalc listCalc0 = compiler.compileList(args[0]);
/*
List<RolapMember> list0 =
Util.cast(listCalc0.evaluateMemberList(evaluator));
*/
List<RolapMember> list0 = listCalc0.evaluateList(evaluator);
listCalc0 = compiler.compileList(args[0]);
list0 = Util.cast(listCalc0.evaluateList(evaluator));
// Prevent the case when the second argument size is too large
if (list0 != null) {
Util.checkCJResultLimit(list0.size());
}
CrossJoinArg arg =
MemberListCrossJoinArg.create(list0, restrictMemberTypes());
MemberListCrossJoinArg.create(evaluator, list0, restrictMemberTypes());
if (arg != null) {
arg0 = new CrossJoinArg[] {arg};
} else {
Expand All @@ -787,8 +765,13 @@ protected CrossJoinArg[] checkCrossJoin(
(MemberListCalc) compiler.compileList(args[1]);
List<RolapMember> list1 =
Util.cast(listCalc1.evaluateMemberList(evaluator));
// Prevent the case when the second argument size is too large
if (list1 != null) {
Util.checkCJResultLimit(list1.size());
}

CrossJoinArg arg =
MemberListCrossJoinArg.create(list1, restrictMemberTypes());
MemberListCrossJoinArg.create(evaluator, list1, restrictMemberTypes());
if (arg != null) {
arg1 = new CrossJoinArg[] {arg};
} else {
Expand Down Expand Up @@ -837,7 +820,7 @@ protected CrossJoinArg[] checkCrossJoinArg(
if (arg != null) {
return new CrossJoinArg[] {arg};
}
arg = checkEnumeration(fun, args);
arg = checkEnumeration(evaluator, fun, args);
if (arg != null) {
return new CrossJoinArg[] {arg};
}
Expand Down
6 changes: 4 additions & 2 deletions src/main/mondrian/rolap/SqlConstraintUtils.java
Expand Up @@ -884,7 +884,8 @@ private static String generateSingleValueInExpr(
{
int maxConstraints =
MondrianProperties.instance().MaxConstraints.get();

SqlQuery.Dialect dialect = sqlQuery.getDialect();

String condition = "";
boolean firstLevel = true;
for (Collection<RolapMember> c = members;
Expand Down Expand Up @@ -925,7 +926,8 @@ private static String generateSingleValueInExpr(
q = level.getKeyExp().getExpression(sqlQuery);
}

if (cc instanceof ListColumnPredicate &&
if (!dialect.supportsUnlimitedValueList() &&
cc instanceof ListColumnPredicate &&
((ListColumnPredicate) cc).getPredicates().size() >
maxConstraints)
{
Expand Down
36 changes: 36 additions & 0 deletions testsrc/main/mondrian/rolap/NonEmptyTest.java
Expand Up @@ -529,6 +529,42 @@ public void testExpandOneNonNativeInput() {
MondrianProperties.instance().EnableNativeCrossJoin.set(origNativeCrossJoin);
}

/**
* Check that the ExpandNonNative does not create Joins with input lists
* containing large number of members.
*
*/
public void testExpandNonNativeResourceLimitFailure() {
String query =
"select " +
"NonEmptyCrossJoin({[Gender].Children, [Gender].[F]}, {[Store].Children, [Store].[Mexico]}) on rows " +
"from [Sales]";

boolean origExpandNonNative =
MondrianProperties.instance().ExpandNonNative.get();
boolean origNativeCrossJoin =
MondrianProperties.instance().EnableNativeCrossJoin.get();
int origResultLimit =
MondrianProperties.instance().ResultLimit.get();

MondrianProperties.instance().ExpandNonNative.set(true);
MondrianProperties.instance().EnableNativeCrossJoin.set(true);
MondrianProperties.instance().ResultLimit.set(2);

try {
executeQuery(query);
fail("Expected error did not occur");
} catch (Throwable e) {
String expectedErrorMsg =
"Mondrian Error:Size of CrossJoin result (3) exceeded limit (2)";
assertEquals(expectedErrorMsg, e.getMessage());
} finally {
MondrianProperties.instance().ExpandNonNative.set(origExpandNonNative);
MondrianProperties.instance().EnableNativeCrossJoin.set(origNativeCrossJoin);
MondrianProperties.instance().ResultLimit.set(origResultLimit);
}
}

/**
* Verify that the presence of All member in all the inputs disables native
* evaluation, even when ExpandNonNative is true.
Expand Down

0 comments on commit c57e114

Please sign in to comment.