001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/SqlConstraintUtils.java#44 $ 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) 2004-2005 TONBELLER AG 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.rolap; 011 012 import java.util.*; 013 014 import mondrian.olap.Evaluator; 015 import mondrian.olap.Member; 016 import mondrian.olap.MondrianDef; 017 import mondrian.olap.MondrianProperties; 018 import mondrian.olap.Util; 019 import mondrian.rolap.agg.*; 020 import mondrian.rolap.sql.SqlQuery; 021 import mondrian.rolap.aggmatcher.AggStar; 022 import mondrian.util.FilteredIterableList; 023 024 /** 025 * Utility class used by implementations of {@link mondrian.rolap.sql.SqlConstraint}, 026 * used to generate constraints into {@link mondrian.rolap.sql.SqlQuery}. 027 * 028 * @author av 029 * @since Nov 21, 2005 030 * @version $Id: //open/mondrian/src/main/mondrian/rolap/SqlConstraintUtils.java#44 $ 031 */ 032 public class SqlConstraintUtils { 033 034 /** Utility class */ 035 private SqlConstraintUtils() { 036 } 037 038 /** 039 * For every restricting member in the current context, generates 040 * a WHERE condition and a join to the fact table. 041 * 042 * @param sqlQuery the query to modify 043 * @param aggStar Aggregate table, or null if query is against fact table 044 * @param restrictMemberTypes defines the behavior if the current context contains 045 * calculated members. 046 * If true, an exception is thrown. 047 * @param evaluator Evaluator 048 */ 049 public static void addContextConstraint( 050 SqlQuery sqlQuery, 051 AggStar aggStar, 052 Evaluator evaluator, 053 boolean restrictMemberTypes) { 054 // Add constraint using the current evaluator context 055 Member[] members = evaluator.getMembers(); 056 057 if (restrictMemberTypes) { 058 if (containsCalculatedMember(members)) { 059 throw Util.newInternal( 060 "can not restrict SQL to calculated Members"); 061 } 062 } else { 063 List<Member> memberList = 064 removeCalculatedMembers(Arrays.asList(members)); 065 memberList = removeDefaultMembers(memberList); 066 members = memberList.toArray(new Member[memberList.size()]); 067 } 068 069 final CellRequest request = 070 RolapAggregationManager.makeRequest(members); 071 if (request == null) { 072 if (restrictMemberTypes) { 073 throw Util.newInternal("CellRequest is null - why?"); 074 } 075 // One or more of the members was null or calculated, so the 076 // request is impossible to satisfy. 077 return; 078 } 079 RolapStar.Column[] columns = request.getConstrainedColumns(); 080 Object[] values = request.getSingleValues(); 081 int arity = columns.length; 082 // following code is similar to AbstractQuerySpec#nonDistinctGenerateSQL() 083 for (int i = 0; i < arity; i++) { 084 RolapStar.Column column = columns[i]; 085 086 String expr; 087 if (aggStar != null) { 088 int bitPos = column.getBitPosition(); 089 AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); 090 AggStar.Table table = aggColumn.getTable(); 091 table.addToFrom(sqlQuery, false, true); 092 093 expr = aggColumn.generateExprString(sqlQuery); 094 } else { 095 RolapStar.Table table = column.getTable(); 096 table.addToFrom(sqlQuery, false, true); 097 098 expr = column.generateExprString(sqlQuery); 099 } 100 101 final String value = String.valueOf(values[i]); 102 if (RolapUtil.mdxNullLiteral.equalsIgnoreCase(value)) { 103 sqlQuery.addWhere( 104 expr, 105 " is ", 106 RolapUtil.sqlNullLiteral); 107 } else { 108 if (column.getDatatype().isNumeric()) { 109 // make sure it can be parsed 110 Double.valueOf(value); 111 } 112 final StringBuilder buf = new StringBuilder(); 113 sqlQuery.getDialect().quote(buf, value, column.getDatatype()); 114 sqlQuery.addWhere( 115 expr, 116 " = ", 117 buf.toString()); 118 } 119 } 120 } 121 122 /** 123 * Removes the default members from an array. 124 * 125 * <p>This is required only if the default member is 126 * not the ALL member. The time dimension for example, has 1997 as default 127 * member. When we evaluate the query 128 * <pre> 129 * select NON EMPTY crossjoin( 130 * {[Time].[1998]}, [Customer].[All].children 131 * ) on columns 132 * from [sales] 133 * </pre> 134 * the <code>[Customer].[All].children</code> is evaluated with the default 135 * member <code>[Time].[1997]</code> in the evaluator context. This is wrong 136 * because the NON EMPTY must filter out Customers with no rows in the fact 137 * table for 1998 not 1997. So we do not restrict the time dimension and 138 * fetch all children. 139 * 140 * @param members Array of members 141 * @return Array of members with default members removed 142 */ 143 private static List<Member> removeDefaultMembers(List<Member> members) { 144 List<Member> result = new ArrayList<Member>(); 145 result.add(members.get(0)); // add the measure 146 for (int i = 1; i < members.size(); i++) { 147 Member m = members.get(i); 148 if (m.getHierarchy().getDefaultMember().equals(m)) { 149 continue; 150 } 151 result.add(m); 152 } 153 return result; 154 } 155 156 static List<Member> removeCalculatedMembers(List<Member> members) { 157 List<Member> result = new FilteredIterableList<Member>(members, 158 new FilteredIterableList.Filter<Member>() { 159 public boolean accept(final Member m) { 160 return !m.isCalculated(); 161 } 162 }); 163 return result; 164 } 165 166 public static boolean containsCalculatedMember(Member[] members) { 167 for (Member member : members) { 168 if (member.isCalculated()) { 169 return true; 170 } 171 } 172 return false; 173 } 174 175 /** 176 * Ensures that the table of <code>level</code> is joined to the fact 177 * table 178 * 179 * @param sqlQuery sql query under construction 180 * @param aggStar 181 * @param e evaluator corresponding to query 182 * @param level level to be added to query 183 */ 184 public static void joinLevelTableToFactTable( 185 SqlQuery sqlQuery, 186 RolapCube baseCube, 187 AggStar aggStar, 188 Evaluator e, 189 RolapCubeLevel level) 190 { 191 RolapStar.Column starColumn = level.getBaseStarKeyColumn(baseCube); 192 if (aggStar != null) { 193 int bitPos = starColumn.getBitPosition(); 194 AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); 195 AggStar.Table table = aggColumn.getTable(); 196 table.addToFrom(sqlQuery, false, true); 197 } else { 198 RolapStar.Table table = starColumn.getTable(); 199 assert table != null; 200 table.addToFrom(sqlQuery, false, true); 201 } 202 } 203 204 /** 205 * Creates a "WHERE parent = value" constraint. 206 * 207 * @param sqlQuery the query to modify 208 * @param baseCube base cube if virtual 209 * @param aggStar Definition of the aggregate table, or null 210 * @param parent the list of parent members 211 * @param restrictMemberTypes defines the behavior if <code>parent</code> 212 * is a calculated member. If true, an exception is thrown 213 */ 214 public static void addMemberConstraint( 215 SqlQuery sqlQuery, 216 RolapCube baseCube, 217 AggStar aggStar, 218 RolapMember parent, 219 boolean restrictMemberTypes) 220 { 221 List<RolapMember> list = Collections.singletonList(parent); 222 addMemberConstraint( 223 sqlQuery, baseCube, aggStar, list, restrictMemberTypes, false); 224 } 225 226 /** 227 * Creates a "WHERE exp IN (...)" condition containing the values 228 * of all parents. All parents must belong to the same level. 229 * 230 * <p>If this constraint is part of a native cross join, there are 231 * multiple constraining members, and the members comprise the cross 232 * product of all unique member keys referenced at each level, then 233 * generating IN expressions would result in incorrect results. In that 234 * case, "WHERE ((level1 = val1a AND level2 = val2a AND ...) 235 * OR (level1 = val1b AND level2 = val2b AND ...) OR ..." is generated 236 * instead. 237 * 238 * @param sqlQuery the query to modify 239 * @param baseCube base cube if virtual 240 * @param aggStar (not used) 241 * @param members the list of members for this constraint 242 * @param restrictMemberTypes defines the behavior if <code>parents</code> 243 * contains calculated members. 244 * If true, and one of the members is calculated, an exception is thrown. 245 * @param crossJoin true if constraint is being generated as part of 246 * a native crossjoin 247 */ 248 public static void addMemberConstraint( 249 SqlQuery sqlQuery, 250 RolapCube baseCube, 251 AggStar aggStar, 252 List<RolapMember> members, 253 boolean restrictMemberTypes, 254 boolean crossJoin) 255 { 256 if (members.size() == 0) { 257 // Generate a predicate which is always false in order to produce 258 // the empty set. It would be smarter to avoid executing SQL at 259 // all in this case, but doing it this way avoid special-case 260 // evaluation code. 261 sqlQuery.addWhere("(1 = 0)"); 262 return; 263 } 264 265 // Find out the first(lowest) unqiue parent level. 266 // Only need to compare members up to that level. 267 RolapMember member = members.get(0); 268 RolapLevel memberLevel = member.getLevel(); 269 RolapMember firstUniqueParent = member; 270 RolapLevel firstUniqueParentLevel = null; 271 for (; firstUniqueParent != null && 272 !firstUniqueParent.getLevel().isUnique(); 273 firstUniqueParent = firstUniqueParent.getParentMember()) { 274 } 275 276 if (firstUniqueParent != null) { 277 // There's a unique parent along the hiearchy 278 firstUniqueParentLevel = firstUniqueParent.getLevel(); 279 } 280 281 String condition = "("; 282 283 // If this constraint is part of a native cross join and there 284 // are multiple values for the parent members, then we can't 285 // use single value IN clauses 286 if (crossJoin && 287 !memberLevel.isUnique() && !membersAreCrossProduct(members)) { 288 assert (member.getParentMember() != null); 289 condition += 290 constrainMultiLevelMembers( 291 sqlQuery, 292 baseCube, 293 members, 294 firstUniqueParentLevel, 295 restrictMemberTypes); 296 } else { 297 condition += 298 generateSingleValueInExpr( 299 sqlQuery, 300 baseCube, 301 members, 302 firstUniqueParentLevel, 303 restrictMemberTypes); 304 } 305 306 if (condition.length() > 1) { 307 // condition is not empty 308 condition += ")"; 309 sqlQuery.addWhere(condition); 310 } 311 return; 312 } 313 314 private static StarColumnPredicate getColumnPredicates( 315 RolapStar.Column column, 316 Collection<RolapMember> members) 317 { 318 switch (members.size()) { 319 case 0: 320 return new LiteralStarPredicate(column, false); 321 case 1: 322 return new MemberColumnPredicate(column, members.iterator().next()); 323 default: 324 List<StarColumnPredicate> predicateList = 325 new ArrayList<StarColumnPredicate>(); 326 for (RolapMember member : members) { 327 predicateList.add(new MemberColumnPredicate(column, member)); 328 } 329 return new ListColumnPredicate(column, predicateList); 330 } 331 } 332 333 private static LinkedHashSet<RolapMember> getUniqueParentMembers( 334 Collection<RolapMember> members) 335 { 336 LinkedHashSet<RolapMember> set = new LinkedHashSet<RolapMember>(); 337 for (RolapMember m : members) { 338 m = m.getParentMember(); 339 if (m != null) { 340 set.add(m); 341 } 342 } 343 return set; 344 } 345 346 /** 347 * Adds to the where clause of a query expression matching a specified 348 * list of members 349 * 350 * @param sqlQuery query containing the where clause 351 * @param baseCube base cube if virtual 352 * @param members list of constraining members 353 * @param fromLevel lowest parent level that is unique 354 * @param restrictMemberTypes defines the behavior when calculated members are present 355 * 356 * @return a non-empty String if SQL is generated for the multi-level member list. 357 */ 358 private static String constrainMultiLevelMembers( 359 SqlQuery sqlQuery, 360 RolapCube baseCube, 361 List<RolapMember> members, 362 RolapLevel fromLevel, 363 boolean restrictMemberTypes) 364 { 365 // Use LinkedHashMap so that keySet() is deterministic. 366 Map<RolapMember, List<RolapMember>> parentChildrenMap = 367 new LinkedHashMap<RolapMember, List<RolapMember>>(); 368 String condition = ""; 369 370 // First try to generate IN list for all members 371 if (sqlQuery.getDialect().supportsMultiValueInExpr()) { 372 condition += 373 generateMultiValueInExpr( 374 sqlQuery, 375 baseCube, 376 members, 377 fromLevel, 378 restrictMemberTypes, 379 parentChildrenMap); 380 381 // The members list might contain NULL values in the member levels. 382 // 383 // e.g. 384 // [USA].[CA].[San Jose] 385 // [null].[null].[San Francisco] 386 // [null].[null].[Los Angeles] 387 // [null].[CA].[San Diego] 388 // [null].[CA].[Sacramento] 389 // 390 // Pick out such members to generate SQL later. 391 // These members are organized in a map that maps the parant levels 392 // containing NULL to all its children members in the list. e.g. the 393 // member list above becomes the following map, after SQL is generated 394 // for [USA].[CA].[San Jose] in the call above. 395 // 396 // [null].[null]->([San Francisco], [Los Angeles]) 397 // [null]->([CA].[San Diego], [CA].[Sacramento]) 398 // 399 if (parentChildrenMap.isEmpty()) { 400 return condition; 401 } 402 } else { 403 // Multi-value IN list not supported 404 // Classify members into List that share the same parent. 405 // 406 // Using the same example as above, the resulting map will be 407 // [USA].[CA]->[San Jose] 408 // [null].[null]->([San Francisco], [Los Angesles]) 409 // [null].[CA]->([San Diego],[Sacramento]) 410 // 411 // The idea is to be able to "compress" the original member list into 412 // groups that can use single value IN list for part of the comparison 413 // that does not involve NULLs. 414 // 415 for (RolapMember m : members) { 416 if (m.isCalculated()) { 417 if (restrictMemberTypes) { 418 throw Util.newInternal("addMemberConstraint: cannot " + 419 "restrict SQL to calculated member :" + m); 420 } 421 continue; 422 } 423 RolapMember p = m.getParentMember(); 424 List<RolapMember> childrenList = parentChildrenMap.get(p); 425 if (childrenList == null) { 426 childrenList = new ArrayList<RolapMember>(); 427 parentChildrenMap.put(p, childrenList); 428 } 429 childrenList.add(m); 430 } 431 } 432 433 // Now we try to generate predicates for the remaining 434 // parent-children group. 435 436 // Note that NULLs are not used to enforce uniqueness 437 // so we ignore the fromLevel here. 438 boolean firstParent = true; 439 440 if (condition.length() > 0) { 441 // Some members have already been translated into IN list. 442 firstParent = false; 443 } 444 445 RolapLevel memberLevel = members.get(0).getLevel(); 446 447 // The children for each parent are turned into IN list so they 448 // should not contain null. 449 for (RolapMember p : parentChildrenMap.keySet()) { 450 if (!firstParent) { 451 condition += " or "; 452 } 453 454 condition += "("; 455 456 // First generate ANDs for all members in the parent lineage of this parent-children group 457 boolean firstLevel = true; 458 for (RolapMember gp = p; gp != null; gp = gp.getParentMember()) { 459 if (gp.isAll()) { 460 // Ignore All member 461 // Get the next parent 462 continue; 463 } 464 465 RolapLevel level = gp.getLevel(); 466 467 // add the level to the FROM clause if this is the 468 // first parent-children group we're generating sql for 469 if (firstParent) { 470 RolapHierarchy hierarchy = 471 (RolapHierarchy) level.getHierarchy(); 472 473 // this method can be called within the context of shared 474 // members, outside of the normal rolap star, therefore 475 // we need to check the level to see if it is a shared or 476 // cube level. 477 478 RolapStar.Column column = null; 479 if (level instanceof RolapCubeLevel) { 480 column = 481 ((RolapCubeLevel)level). 482 getBaseStarKeyColumn(baseCube); 483 } 484 485 if (column != null) { 486 RolapStar.Table targetTable = column.getTable(); 487 hierarchy.addToFrom(sqlQuery, targetTable); 488 } else { 489 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 490 } 491 } 492 493 if (!firstLevel) { 494 condition += " and "; 495 } else { 496 firstLevel = false; 497 } 498 499 condition += constrainLevel( 500 level, 501 sqlQuery, 502 baseCube, 503 getColumnValue( 504 gp.getKey(), 505 sqlQuery.getDialect(), 506 level.getDatatype()), 507 false); 508 if (gp.getLevel() == fromLevel) { 509 // SQL is completely generated for this parent 510 break; 511 } 512 } 513 firstParent = false; 514 515 // Next, generate children for this parent-children group 516 List<RolapMember> children = parentChildrenMap.get(p); 517 518 // If no children to be generated for this parent then we are done 519 if (!children.isEmpty()) { 520 Map<RolapMember, List<RolapMember>> tmpParentChildrenMap = 521 new HashMap<RolapMember, List<RolapMember>>(); 522 523 condition += " and "; 524 525 RolapLevel childrenLevel = 526 (RolapLevel)(p.getLevel().getChildLevel()); 527 528 if (sqlQuery.getDialect().supportsMultiValueInExpr() && 529 childrenLevel != memberLevel) { 530 // Multi-level children and multi-value IN list supported 531 condition += 532 generateMultiValueInExpr( 533 sqlQuery, 534 baseCube, 535 children, 536 childrenLevel, 537 restrictMemberTypes, 538 tmpParentChildrenMap); 539 assert (tmpParentChildrenMap.isEmpty()); 540 } else { 541 // Can only be single level children 542 // If multi-value IN list not supported, children will be on 543 // the same level as members list. Only single value IN list 544 // needs to be generated for this case. 545 assert (childrenLevel == memberLevel); 546 condition += 547 generateSingleValueInExpr( 548 sqlQuery, 549 baseCube, 550 children, 551 childrenLevel, 552 restrictMemberTypes); 553 } 554 } 555 // SQL is complete for this parent-children group. 556 condition += ")"; 557 } 558 559 return condition; 560 } 561 562 /** 563 * @param members list of members 564 * 565 * @return true if the members comprise the cross product of all unique 566 * member keys referenced at each level 567 */ 568 private static boolean membersAreCrossProduct(List<RolapMember> members) 569 { 570 int crossProdSize = getNumUniqueMemberKeys(members); 571 for (Collection<RolapMember> parents = getUniqueParentMembers(members); 572 !parents.isEmpty(); parents = getUniqueParentMembers(parents)) 573 { 574 crossProdSize *= parents.size(); 575 } 576 return (crossProdSize == members.size()); 577 } 578 579 /** 580 * @param members list of members 581 * 582 * @return number of unique member keys in a list of members 583 */ 584 private static int getNumUniqueMemberKeys(List<RolapMember> members) 585 { 586 final HashSet<Object> set = new HashSet<Object>(); 587 for (RolapMember m : members) { 588 set.add(m.getKey()); 589 } 590 return set.size(); 591 } 592 593 /** 594 * @param key key corresponding to a member 595 * @param dialect sql dialect being used 596 * @param datatype data type of the member 597 * 598 * @return string value corresponding to the member 599 */ 600 private static String getColumnValue( 601 Object key, 602 SqlQuery.Dialect dialect, 603 SqlQuery.Datatype datatype) 604 { 605 if (key != RolapUtil.sqlNullValue) { 606 return key.toString(); 607 } else { 608 return RolapUtil.mdxNullLiteral; 609 } 610 } 611 612 /** 613 * Generates a sql expression constraining a level by some value 614 * 615 * @param level the level 616 * @param query the query that the sql expression will be added to 617 * @param baseCube base cube for virtual levels 618 * @param columnValue value constraining the level 619 * @param caseSensitive if true, need to handle case sensitivity of the 620 * member value 621 * 622 * @return generated string corresponding to the expression 623 */ 624 public static String constrainLevel( 625 RolapLevel level, 626 SqlQuery query, 627 RolapCube baseCube, 628 String columnValue, 629 boolean caseSensitive) 630 { 631 632 // this method can be called within the context of shared members, 633 // outside of the normal rolap star, therefore we need to 634 // check the level to see if it is a shared or cube level. 635 636 RolapStar.Column column = null; 637 if (level instanceof RolapCubeLevel) { 638 column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); 639 } 640 641 String columnString; 642 SqlQuery.Datatype datatype; 643 if (column != null) { 644 if (column.getNameColumn() == null) { 645 datatype = level.getDatatype(); 646 } else { 647 column = column.getNameColumn(); 648 // The schema doesn't specify the datatype of the name column, but 649 // we presume that it is a string. 650 datatype = SqlQuery.Datatype.String; 651 } 652 columnString = column.generateExprString(query); 653 } else { 654 MondrianDef.Expression exp = level.getNameExp(); 655 if (exp == null) { 656 exp = level.getKeyExp(); 657 datatype = level.getDatatype(); 658 } else { 659 // The schema doesn't specify the datatype of the name column, but 660 // we presume that it is a string. 661 datatype = SqlQuery.Datatype.String; 662 } 663 columnString = exp.getExpression(query); 664 } 665 666 String constraint; 667 668 if (RolapUtil.mdxNullLiteral.equalsIgnoreCase(columnValue)) { 669 constraint = columnString + " is " + RolapUtil.sqlNullLiteral; 670 } else { 671 if (datatype.isNumeric()) { 672 // make sure it can be parsed 673 Double.valueOf(columnValue); 674 } 675 final StringBuilder buf = new StringBuilder(); 676 query.getDialect().quote(buf, columnValue, datatype); 677 String value = buf.toString(); 678 if (caseSensitive && datatype == SqlQuery.Datatype.String) { 679 // Some databases (like DB2) compare case-sensitive. We convert 680 // the value to upper-case in the DBMS (e.g. UPPER('Foo')) 681 // rather than in Java (e.g. 'FOO') in case the DBMS is running 682 // a different locale. 683 if (!MondrianProperties.instance().CaseSensitive.get()) { 684 columnString = query.getDialect().toUpper(columnString); 685 value = query.getDialect().toUpper(value); 686 } 687 } 688 689 constraint = columnString + " = " + value; 690 } 691 692 return constraint; 693 } 694 695 /** 696 * Generates a multi-value IN expression corresponding to a list of 697 * member expressions, and adds the expression to the WHERE clause 698 * of a query, provided the member values are all non-null 699 * 700 * @param sqlQuery query containing the where clause 701 * @param baseCube base cube if virtual 702 * @param members list of constraining members 703 * @param fromLevel lowest parent level that is unique 704 * @param restrictMemberTypes defines the behavior when calculated members are present 705 * @param parentWithNullToChildrenMap upon return this map contains members 706 * that have Null values in its (parent) levels 707 * @return a non-empty String if multi-value IN list was generated for some members. 708 */ 709 private static String generateMultiValueInExpr( 710 SqlQuery sqlQuery, 711 RolapCube baseCube, 712 List<RolapMember> members, 713 RolapLevel fromLevel, 714 boolean restrictMemberTypes, 715 Map<RolapMember, List<RolapMember>> parentWithNullToChildrenMap) 716 { 717 final StringBuilder columnBuf = new StringBuilder(); 718 final StringBuilder valueBuf = new StringBuilder(); 719 720 columnBuf.append("("); 721 722 // generate the left-hand side of the IN expression 723 boolean isFirstLevelInMultiple = true; 724 for (RolapMember m = members.get(0); m != null; m = m.getParentMember()) { 725 if (m.isAll()) { 726 continue; 727 } 728 RolapLevel level = m.getLevel(); 729 RolapHierarchy hierarchy = (RolapHierarchy) level.getHierarchy(); 730 731 // this method can be called within the context of shared members, 732 // outside of the normal rolap star, therefore we need to 733 // check the level to see if it is a shared or cube level. 734 735 RolapStar.Column column = null; 736 if (level instanceof RolapCubeLevel) { 737 column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); 738 } 739 740 String columnString = null; 741 if (column != null) { 742 RolapStar.Table targetTable = column.getTable(); 743 hierarchy.addToFrom(sqlQuery, targetTable); 744 745 RolapStar.Column nameColumn = column.getNameColumn(); 746 if (nameColumn == null) { 747 nameColumn = column; 748 } 749 columnString = nameColumn.generateExprString(sqlQuery); 750 } else { 751 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 752 753 MondrianDef.Expression nameExp = level.getNameExp(); 754 if (nameExp == null) { 755 nameExp = level.getKeyExp(); 756 } 757 columnString = nameExp.getExpression(sqlQuery); 758 } 759 760 if (!isFirstLevelInMultiple) { 761 columnBuf.append(","); 762 } else { 763 isFirstLevelInMultiple = false; 764 } 765 766 columnBuf.append(columnString); 767 768 // Only needs to compare up to the first(lowest) unique level. 769 if (m.getLevel() == fromLevel) { 770 break; 771 } 772 } 773 774 columnBuf.append(")"); 775 776 // generate the RHS of the IN predicate 777 valueBuf.append("("); 778 boolean isFirstMember = true; 779 String memberString; 780 for (RolapMember m : members) { 781 if (m.isCalculated()) { 782 if (restrictMemberTypes) { 783 throw Util.newInternal("addMemberConstraint: cannot " + 784 "restrict SQL to calculated member :" + m); 785 } 786 continue; 787 } 788 789 isFirstLevelInMultiple = true; 790 memberString = "("; 791 792 boolean containsNull = false; 793 for (RolapMember p = m; p != null; p = p.getParentMember()) { 794 795 if (p.isAll()) { 796 // Ignore the ALL level. 797 // Generate SQL condition for the next level 798 continue; 799 } 800 RolapLevel level = p.getLevel(); 801 802 String value = getColumnValue( 803 p.getKey(), 804 sqlQuery.getDialect(), 805 level.getDatatype()); 806 807 // If parent at a level is NULL, record this parent and all 808 // its children(if there's any) 809 if (RolapUtil.mdxNullLiteral.equalsIgnoreCase(value)) { 810 // Add to the nullParent map 811 List<RolapMember> childrenList = 812 parentWithNullToChildrenMap.get(p); 813 if (childrenList == null) { 814 childrenList = new ArrayList<RolapMember>(); 815 parentWithNullToChildrenMap.put(p, childrenList); 816 } 817 818 // If p has children 819 if (m != p) { 820 childrenList.add(m); 821 } 822 823 // Skip generating condition for this parent 824 containsNull = true; 825 break; 826 } 827 828 if (isFirstLevelInMultiple) { 829 isFirstLevelInMultiple = false; 830 } else { 831 memberString += ","; 832 } 833 834 final StringBuilder buf = new StringBuilder(); 835 sqlQuery.getDialect().quote(buf, value, level.getDatatype()); 836 memberString += buf.toString(); 837 838 // Only needs to compare up to the first(lowest) unique level. 839 if (p.getLevel() == fromLevel) { 840 break; 841 } 842 } 843 844 // now check if sql string is sucessfully generated for this member 845 // If parent levels do not contain NULL then SQL must have been generated 846 //successfully. 847 if (!containsNull) { 848 memberString += ")"; 849 if (!isFirstMember) { 850 valueBuf.append(","); 851 } 852 valueBuf.append(memberString); 853 isFirstMember = false; 854 } 855 } 856 857 String condition = ""; 858 if (!isFirstMember) { 859 // SQLs are generated for some members. 860 valueBuf.append(")"); 861 condition += columnBuf.toString() + " in " + valueBuf.toString(); 862 } 863 864 return condition; 865 } 866 867 /** 868 * Generates a multi-value IN expression corresponding to a list of 869 * member expressions, and adds the expression to the WHERE clause 870 * of a query, provided the member values are all non-null 871 * 872 * @param sqlQuery query containing the where clause 873 * @param baseCube base cube if virtual 874 * @param members list of constraining members 875 * @param fromLevel lowest parent level that is unique 876 * @param restrictMemberTypes defines the behavior when calculated members are present 877 * @return a non-empty String if IN list was generated for the members. 878 */ 879 private static String generateSingleValueInExpr( 880 SqlQuery sqlQuery, 881 RolapCube baseCube, 882 List<RolapMember> members, 883 RolapLevel fromLevel, 884 boolean restrictMemberTypes) 885 { 886 int maxConstraints = 887 MondrianProperties.instance().MaxConstraints.get(); 888 SqlQuery.Dialect dialect = sqlQuery.getDialect(); 889 890 String condition = ""; 891 boolean firstLevel = true; 892 for (Collection<RolapMember> c = members; 893 !c.isEmpty(); 894 c = getUniqueParentMembers(c)) 895 { 896 RolapMember m = c.iterator().next(); 897 if (m.isAll()) { 898 continue; 899 } 900 if (m.isCalculated()) { 901 if (restrictMemberTypes) { 902 throw Util.newInternal("addMemberConstraint: cannot " + 903 "restrict SQL to calculated member :" + m); 904 } 905 continue; 906 } 907 RolapLevel level = m.getLevel(); 908 RolapHierarchy hierarchy = level.getHierarchy(); 909 910 // this method can be called within the context of shared members, 911 // outside of the normal rolap star, therefore we need to 912 // check the level to see if it is a shared or cube level. 913 914 RolapStar.Column column = null; 915 if (level instanceof RolapCubeLevel) { 916 column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); 917 } 918 919 StarColumnPredicate cc = getColumnPredicates(column, c); 920 String q = null; 921 if (column != null) { 922 RolapStar.Table targetTable = column.getTable(); 923 hierarchy.addToFrom(sqlQuery, targetTable); 924 q = column.generateExprString(sqlQuery); 925 } else { 926 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 927 q = level.getKeyExp().getExpression(sqlQuery); 928 } 929 930 if (!dialect.supportsUnlimitedValueList() && 931 cc instanceof ListColumnPredicate && 932 ((ListColumnPredicate) cc).getPredicates().size() > 933 maxConstraints) 934 { 935 // Simply get them all, do not create where-clause. 936 // Below are two alternative approaches (and code). They 937 // both have problems. 938 } else { 939 final String where = 940 RolapStar.Column.createInExpr( 941 q, cc, level.getDatatype(), sqlQuery); 942 if (!where.equals("true")) { 943 if (!firstLevel) { 944 condition += " and "; 945 } else { 946 firstLevel = false; 947 } 948 condition += where; 949 } 950 } 951 952 if (m.getLevel().isUnique() || m.getLevel() == fromLevel) { 953 break; // no further qualification needed 954 } 955 } 956 return condition; 957 } 958 } 959 960 // End SqlConstraintUtils.java