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) 2001-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.rolap;
012
013import mondrian.calc.TupleList;
014import mondrian.olap.*;
015import mondrian.olap.fun.FunUtil;
016import mondrian.resource.MondrianResource;
017import mondrian.rolap.agg.AggregationManager;
018import mondrian.rolap.agg.CellRequest;
019import mondrian.rolap.aggmatcher.AggStar;
020import mondrian.rolap.sql.*;
021import mondrian.server.Locus;
022import mondrian.server.monitor.SqlStatementEvent;
023import mondrian.spi.Dialect;
024import mondrian.util.*;
025
026import org.eigenbase.util.property.StringProperty;
027
028import java.sql.*;
029import java.util.*;
030
031import javax.sql.DataSource;
032
033/**
034 * A <code>SqlMemberSource</code> reads members from a SQL database.
035 *
036 * <p>It's a good idea to put a {@link CacheMemberReader} on top of this.
037 *
038 * @author jhyde
039 * @since 21 December, 2001
040 */
041class SqlMemberSource
042    implements MemberReader, SqlTupleReader.MemberBuilder
043{
044    private final SqlConstraintFactory sqlConstraintFactory =
045        SqlConstraintFactory.instance();
046    private final RolapHierarchy hierarchy;
047    private final DataSource dataSource;
048    private MemberCache cache;
049    private int lastOrdinal = 0;
050    private boolean assignOrderKeys;
051    private Map<Object, Object> valuePool;
052
053    SqlMemberSource(RolapHierarchy hierarchy) {
054        this.hierarchy = hierarchy;
055        this.dataSource =
056            hierarchy.getRolapSchema().getInternalConnection().getDataSource();
057        assignOrderKeys =
058            MondrianProperties.instance().CompareSiblingsByOrderKey.get();
059        valuePool = ValuePoolFactoryFactory.getValuePoolFactory().create(this);
060    }
061
062    // implement MemberSource
063    public RolapHierarchy getHierarchy() {
064        return hierarchy;
065    }
066
067    // implement MemberSource
068    public boolean setCache(MemberCache cache) {
069        this.cache = cache;
070        return true; // yes, we support cache writeback
071    }
072
073    // implement MemberSource
074    public int getMemberCount() {
075        RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels();
076        int count = 0;
077        for (RolapLevel level : levels) {
078            count += getLevelMemberCount(level);
079        }
080        return count;
081    }
082
083    public RolapMember substitute(RolapMember member) {
084        return member;
085    }
086
087    public RolapMember desubstitute(RolapMember member) {
088        return member;
089    }
090
091    public RolapMember getMemberByKey(
092        RolapLevel level,
093        List<Comparable> keyValues)
094    {
095        if (level.isAll()) {
096            return null;
097        }
098        List<Dialect.Datatype> datatypeList = new ArrayList<Dialect.Datatype>();
099        List<MondrianDef.Expression> columnList =
100            new ArrayList<MondrianDef.Expression>();
101        for (RolapLevel x = level;; x = (RolapLevel) x.getParentLevel()) {
102            columnList.add(x.getKeyExp());
103            datatypeList.add(x.getDatatype());
104            if (x.isUnique()) {
105                break;
106            }
107        }
108        final List<RolapMember> list =
109            getMembersInLevel(
110                level,
111                new MemberKeyConstraint(
112                    columnList,
113                    datatypeList,
114                    keyValues));
115        switch (list.size()) {
116        case 0:
117            return null;
118        case 1:
119            return list.get(0);
120        default:
121            throw Util.newError(
122                "More than one member in level " + level + " with key "
123                + keyValues);
124        }
125    }
126
127    public RolapMember lookupMember(
128        List<Id.Segment> uniqueNameParts,
129        boolean failIfNotFound)
130    {
131        throw new UnsupportedOperationException();
132    }
133
134    public int getLevelMemberCount(RolapLevel level) {
135        if (level.isAll()) {
136            return 1;
137        }
138        return getMemberCount(level, dataSource);
139    }
140
141    private int getMemberCount(RolapLevel level, DataSource dataSource) {
142        boolean[] mustCount = new boolean[1];
143        String sql = makeLevelMemberCountSql(level, dataSource, mustCount);
144        final SqlStatement stmt =
145            RolapUtil.executeQuery(
146                dataSource,
147                sql,
148                new Locus(
149                    Locus.peek().execution,
150                    "SqlMemberSource.getLevelMemberCount",
151                    "while counting members of level '" + level));
152        try {
153            ResultSet resultSet = stmt.getResultSet();
154            int count;
155            if (! mustCount[0]) {
156                Util.assertTrue(resultSet.next());
157                ++stmt.rowCount;
158                count = resultSet.getInt(1);
159            } else {
160                // count distinct "manually"
161                ResultSetMetaData rmd = resultSet.getMetaData();
162                int nColumns = rmd.getColumnCount();
163                String[] colStrings = new String[nColumns];
164                count = 0;
165                while (resultSet.next()) {
166                    ++stmt.rowCount;
167                    boolean isEqual = true;
168                    for (int i = 0; i < nColumns; i++) {
169                        String colStr = resultSet.getString(i + 1);
170                        if (!Util.equals(colStr, colStrings[i])) {
171                            isEqual = false;
172                        }
173                        colStrings[i] = colStr;
174                    }
175                    if (!isEqual) {
176                        count++;
177                    }
178                }
179            }
180            return count;
181        } catch (SQLException e) {
182            throw stmt.handle(e);
183        } finally {
184            stmt.close();
185        }
186    }
187
188    /**
189     * Generates the SQL statement to count the members in
190     * <code>level</code>. For example, <blockquote>
191     *
192     * <pre>SELECT count(*) FROM (
193     *   SELECT DISTINCT "country", "state_province"
194     *   FROM "customer") AS "init"</pre>
195     *
196     * </blockquote> counts the non-leaf "state_province" level. MySQL
197     * doesn't allow SELECT-in-FROM, so we use the syntax<blockquote>
198     *
199     * <pre>SELECT count(DISTINCT "country", "state_province")
200     * FROM "customer"</pre>
201     *
202     * </blockquote>. The leaf level requires a different query:<blockquote>
203     *
204     * <pre>SELECT count(*) FROM "customer"</pre>
205     *
206     * </blockquote> counts the leaf "name" level of the "customer" hierarchy.
207     */
208    private String makeLevelMemberCountSql(
209        RolapLevel level,
210        DataSource dataSource,
211        boolean[] mustCount)
212    {
213        mustCount[0] = false;
214        SqlQuery sqlQuery =
215            SqlQuery.newQuery(
216                dataSource,
217                "while generating query to count members in level " + level);
218        int levelDepth = level.getDepth();
219        RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels();
220        if (levelDepth == levels.length) {
221            // "select count(*) from schema.customer"
222            sqlQuery.addSelect("count(*)", null);
223            hierarchy.addToFrom(sqlQuery, level.getKeyExp());
224            return sqlQuery.toString();
225        }
226        if (!sqlQuery.getDialect().allowsFromQuery()) {
227            List<String> columnList = new ArrayList<String>();
228            int columnCount = 0;
229            for (int i = levelDepth; i >= 0; i--) {
230                RolapLevel level2 = levels[i];
231                if (level2.isAll()) {
232                     continue;
233                }
234                if (columnCount > 0) {
235                    if (sqlQuery.getDialect().allowsCompoundCountDistinct()) {
236                        // no op.
237                    } else if (true) {
238                        // for databases where both SELECT-in-FROM and
239                        // COUNT DISTINCT do not work, we do not
240                        // generate any count and do the count
241                        // distinct "manually".
242                        mustCount[0] = true;
243                    }
244                }
245                hierarchy.addToFrom(sqlQuery, level2.getKeyExp());
246
247                String keyExp = level2.getKeyExp().getExpression(sqlQuery);
248                if (columnCount > 0
249                    && !sqlQuery.getDialect().allowsCompoundCountDistinct()
250                    && sqlQuery.getDialect().getDatabaseProduct()
251                    == Dialect.DatabaseProduct.SYBASE)
252                {
253                    keyExp = "convert(varchar, " + columnList + ")";
254                }
255                columnList.add(keyExp);
256
257                if (level2.isUnique()) {
258                    break; // no further qualification needed
259                }
260                ++columnCount;
261            }
262            if (mustCount[0]) {
263                for (String colDef : columnList) {
264                    final String exp =
265                        sqlQuery.getDialect().generateCountExpression(colDef);
266                    sqlQuery.addSelect(exp, null);
267                    sqlQuery.addOrderBy(exp, true, false, true);
268                }
269            } else {
270                int i = 0;
271                StringBuilder sb = new StringBuilder();
272                for (String colDef : columnList) {
273                    if (i > 0) {
274                        sb.append(", ");
275                    }
276                    sb.append(
277                        sqlQuery.getDialect()
278                            .generateCountExpression(colDef));
279                }
280                sqlQuery.addSelect(
281                    "count(DISTINCT " + sb.toString() + ")", null);
282            }
283            return sqlQuery.toString();
284
285        } else {
286            sqlQuery.setDistinct(true);
287            for (int i = levelDepth; i >= 0; i--) {
288                RolapLevel level2 = levels[i];
289                if (level2.isAll()) {
290                    continue;
291                }
292                MondrianDef.Expression keyExp = level2.getKeyExp();
293                hierarchy.addToFrom(sqlQuery, keyExp);
294                sqlQuery.addSelect(keyExp.getExpression(sqlQuery), null);
295                if (level2.isUnique()) {
296                    break; // no further qualification needed
297                }
298            }
299            SqlQuery outerQuery =
300                SqlQuery.newQuery(
301                    dataSource,
302                    "while generating query to count members in level "
303                    + level);
304            outerQuery.addSelect("count(*)", null);
305            // Note: the "init" is for Postgres, which requires
306            // FROM-queries to have an alias
307            boolean failIfExists = true;
308            outerQuery.addFrom(sqlQuery, "init", failIfExists);
309            return outerQuery.toString();
310        }
311    }
312
313
314    public List<RolapMember> getMembers() {
315        return getMembers(dataSource);
316    }
317
318    private List<RolapMember> getMembers(DataSource dataSource) {
319        Pair<String, List<SqlStatement.Type>> pair = makeKeysSql(dataSource);
320        final String sql = pair.left;
321        List<SqlStatement.Type> types = pair.right;
322        RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels();
323        SqlStatement stmt =
324            RolapUtil.executeQuery(
325                dataSource, sql, types, 0, 0,
326                new SqlStatement.StatementLocus(
327                    null,
328                    "SqlMemberSource.getMembers",
329                    "while building member cache",
330                    SqlStatementEvent.Purpose.TUPLES, 0),
331                -1, -1, null);
332        try {
333            final List<SqlStatement.Accessor> accessors = stmt.getAccessors();
334            List<RolapMember> list = new ArrayList<RolapMember>();
335            Map<MemberKey, RolapMember> map =
336                new HashMap<MemberKey, RolapMember>();
337            RolapMember root = null;
338            if (hierarchy.hasAll()) {
339                root = hierarchy.getAllMember();
340                list.add(root);
341            }
342
343            int limit = MondrianProperties.instance().ResultLimit.get();
344            ResultSet resultSet = stmt.getResultSet();
345            while (resultSet.next()) {
346                ++stmt.rowCount;
347                if (limit > 0 && limit < stmt.rowCount) {
348                    // result limit exceeded, throw an exception
349                    throw stmt.handle(
350                        MondrianResource.instance().MemberFetchLimitExceeded.ex(
351                            limit));
352                }
353
354                int column = 0;
355                RolapMember member = root;
356                for (RolapLevel level : levels) {
357                    if (level.isAll()) {
358                        continue;
359                    }
360                    Object value = accessors.get(column).get();
361                    if (value == null) {
362                        value = RolapUtil.sqlNullValue;
363                    }
364                    RolapMember parent = member;
365                    MemberKey key = new MemberKey(parent, value);
366                    member = map.get(key);
367                    if (member == null) {
368                        RolapMemberBase memberBase =
369                            new RolapMemberBase(parent, level, value);
370                        memberBase.setOrdinal(lastOrdinal++);
371                        member = memberBase;
372/*
373RME is this right
374                        if (level.getOrdinalExp() != level.getKeyExp()) {
375                            member.setOrdinal(lastOrdinal++);
376                        }
377*/
378                        if (value == RolapUtil.sqlNullValue) {
379                            addAsOldestSibling(list, member);
380                        } else {
381                            list.add(member);
382                        }
383                        map.put(key, member);
384                    }
385                    column++;
386
387                    // REVIEW jvs 20-Feb-2007:  What about caption?
388
389                    if (!level.getOrdinalExp().equals(level.getKeyExp())) {
390                        if (assignOrderKeys) {
391                            Object orderKey = accessors.get(column).get();
392                            setOrderKey((RolapMemberBase) member, orderKey);
393                        }
394                        column++;
395                    }
396
397                    Property[] properties = level.getProperties();
398                    for (Property property : properties) {
399                        // REVIEW emcdermid 9-Jul-2009:
400                        // Should we also look up the value in the
401                        // pool here, rather than setting it directly?
402                        // Presumably the value is already in the pool
403                        // as a result of makeMember().
404                        member.setProperty(
405                            property.getName(),
406                            accessors.get(column).get());
407                        column++;
408                    }
409                }
410            }
411
412            return list;
413        } catch (SQLException e) {
414            throw stmt.handle(e);
415        } finally {
416            stmt.close();
417        }
418    }
419
420    private void setOrderKey(RolapMemberBase member, Object orderKey) {
421        if ((orderKey != null) && !(orderKey instanceof Comparable)) {
422            orderKey = orderKey.toString();
423        }
424        member.setOrderKey((Comparable<?>) orderKey);
425    }
426
427    /**
428     * Adds <code>member</code> just before the first element in
429     * <code>list</code> which has the same parent.
430     */
431    private void addAsOldestSibling(
432        List<RolapMember> list,
433        RolapMember member)
434    {
435        int i = list.size();
436        while (--i >= 0) {
437            RolapMember sibling = list.get(i);
438            if (sibling.getParentMember() != member.getParentMember()) {
439                break;
440            }
441        }
442        list.add(i + 1, member);
443    }
444
445    private Pair<String, List<SqlStatement.Type>> makeKeysSql(
446        DataSource dataSource)
447    {
448        SqlQuery sqlQuery =
449            SqlQuery.newQuery(
450                dataSource,
451                "while generating query to retrieve members of " + hierarchy);
452        RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels();
453        for (RolapLevel level : levels) {
454            if (level.isAll()) {
455                continue;
456            }
457            MondrianDef.Expression exp = level.getKeyExp();
458            hierarchy.addToFrom(sqlQuery, exp);
459            String expString = exp.getExpression(sqlQuery);
460            sqlQuery.addSelectGroupBy(expString, null);
461            exp = level.getOrdinalExp();
462            hierarchy.addToFrom(sqlQuery, exp);
463            expString = exp.getExpression(sqlQuery);
464            sqlQuery.addOrderBy(expString, true, false, true);
465            if (!exp.equals(level.getKeyExp())) {
466                sqlQuery.addSelect(expString, null);
467            }
468
469            RolapProperty[] properties = level.getProperties();
470            for (RolapProperty property : properties) {
471                exp = property.getExp();
472                hierarchy.addToFrom(sqlQuery, exp);
473                expString = exp.getExpression(sqlQuery);
474                String alias = sqlQuery.addSelect(expString, null);
475                // Some dialects allow us to eliminate properties from the
476                // group by that are functionally dependent on the level value
477                if (!sqlQuery.getDialect().allowsSelectNotInGroupBy()
478                    || !property.dependsOnLevelValue())
479                {
480                    sqlQuery.addGroupBy(expString, alias);
481                }
482            }
483        }
484        return sqlQuery.toSqlAndTypes();
485    }
486
487    // implement MemberReader
488    public List<RolapMember> getMembersInLevel(
489        RolapLevel level)
490    {
491        TupleConstraint constraint =
492            sqlConstraintFactory.getLevelMembersConstraint(null);
493        return getMembersInLevel(level, constraint);
494    }
495
496    public List<RolapMember> getMembersInLevel(
497        RolapLevel level,
498        TupleConstraint constraint)
499    {
500        if (level.isAll()) {
501            return Collections.singletonList(hierarchy.getAllMember());
502        }
503        final TupleReader tupleReader =
504            level.getDimension().isHighCardinality()
505                ? new HighCardSqlTupleReader(constraint)
506                : new SqlTupleReader(constraint);
507        tupleReader.addLevelMembers(level, this, null);
508        final TupleList tupleList =
509            tupleReader.readMembers(dataSource, null, null);
510
511        assert tupleList.getArity() == 1;
512        return Util.cast(tupleList.slice(0));
513    }
514
515    public MemberCache getMemberCache() {
516        return cache;
517    }
518
519    public Object getMemberCacheLock() {
520        return cache;
521    }
522
523    // implement MemberSource
524    public List<RolapMember> getRootMembers() {
525        return getMembersInLevel((RolapLevel) hierarchy.getLevels()[0]);
526    }
527
528    /**
529     * Generates the SQL statement to access the children of
530     * <code>member</code>. For example, <blockquote>
531     *
532     * <pre>SELECT "city"
533     * FROM "customer"
534     * WHERE "country" = 'USA'
535     * AND "state_province" = 'BC'
536     * GROUP BY "city"</pre>
537     * </blockquote> retrieves the children of the member
538     * <code>[Canada].[BC]</code>.
539     * <p>Note that this method is never called in the context of
540     * virtual cubes, it is only called on regular cubes.
541     *
542     * <p>See also {@link SqlTupleReader#makeLevelMembersSql}.
543     */
544    Pair<String, List<SqlStatement.Type>> makeChildMemberSql(
545        RolapMember member,
546        DataSource dataSource,
547        MemberChildrenConstraint constraint)
548    {
549        SqlQuery sqlQuery =
550            SqlQuery.newQuery(
551                dataSource,
552                "while generating query to retrieve children of member "
553                    + member);
554
555        // If this is a non-empty constraint, it is more efficient to join to
556        // an aggregate table than to the fact table. See whether a suitable
557        // aggregate table exists.
558        AggStar aggStar = chooseAggStar(constraint, member);
559
560        // Create the condition, which is either the parent member or
561        // the full context (non empty).
562        constraint.addMemberConstraint(sqlQuery, null, aggStar, member);
563
564        RolapLevel level = (RolapLevel) member.getLevel().getChildLevel();
565
566        boolean levelCollapsed =
567            (aggStar != null)
568            && isLevelCollapsed(aggStar, (RolapCubeLevel)level);
569
570        boolean multipleCols =
571            SqlMemberSource.levelContainsMultipleColumns(level);
572
573        if (levelCollapsed && !multipleCols) {
574            // if this is a single column collapsed level, there is
575            // no need to join it with dimension tables
576            RolapStar.Column starColumn =
577                ((RolapCubeLevel) level).getStarKeyColumn();
578            int bitPos = starColumn.getBitPosition();
579            AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos);
580            String q = aggColumn.generateExprString(sqlQuery);
581            sqlQuery.addSelectGroupBy(q, starColumn.getInternalType());
582            sqlQuery.addOrderBy(q, true, false, true);
583            aggColumn.getTable().addToFrom(sqlQuery, false, true);
584            return sqlQuery.toSqlAndTypes();
585        }
586
587        hierarchy.addToFrom(sqlQuery, level.getKeyExp());
588        String q = level.getKeyExp().getExpression(sqlQuery);
589        sqlQuery.addSelectGroupBy(q, level.getInternalType());
590
591        // in non empty mode the level table must be joined to the fact
592        // table
593        constraint.addLevelConstraint(sqlQuery, null, aggStar, level);
594
595        if (levelCollapsed) {
596            // if this is a collapsed level, add a join between key and aggstar
597            RolapStar.Column starColumn =
598                ((RolapCubeLevel) level).getStarKeyColumn();
599            int bitPos = starColumn.getBitPosition();
600            AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos);
601            RolapStar.Condition condition =
602                new RolapStar.Condition(
603                    level.getKeyExp(),
604                    aggColumn.getExpression());
605            sqlQuery.addWhere(condition.toString(sqlQuery));
606            hierarchy.addToFromInverse(sqlQuery, level.getKeyExp());
607
608            // also may need to join parent levels to make selection unique
609            RolapCubeLevel parentLevel = (RolapCubeLevel)level.getParentLevel();
610            boolean isUnique = level.isUnique();
611            while (parentLevel != null && !parentLevel.isAll() && !isUnique) {
612                hierarchy.addToFromInverse(sqlQuery, parentLevel.getKeyExp());
613                starColumn = parentLevel.getStarKeyColumn();
614                bitPos = starColumn.getBitPosition();
615                aggColumn = aggStar.lookupColumn(bitPos);
616                condition =
617                    new RolapStar.Condition(
618                        parentLevel.getKeyExp(),
619                        aggColumn.getExpression());
620                sqlQuery.addWhere(condition.toString(sqlQuery));
621                parentLevel = parentLevel.getParentLevel();
622            }
623        }
624
625        if (level.hasCaptionColumn()) {
626            MondrianDef.Expression captionExp = level.getCaptionExp();
627            if (!levelCollapsed) {
628                hierarchy.addToFrom(sqlQuery, captionExp);
629            }
630            String captionSql = captionExp.getExpression(sqlQuery);
631            sqlQuery.addSelectGroupBy(captionSql, null);
632        }
633        if (!levelCollapsed) {
634            hierarchy.addToFrom(sqlQuery, level.getOrdinalExp());
635        }
636        String orderBy = level.getOrdinalExp().getExpression(sqlQuery);
637        sqlQuery.addOrderBy(orderBy, true, false, true);
638        if (!orderBy.equals(q)) {
639            sqlQuery.addSelectGroupBy(orderBy, null);
640        }
641
642        RolapProperty[] properties = level.getProperties();
643        for (RolapProperty property : properties) {
644            final MondrianDef.Expression exp = property.getExp();
645            if (!levelCollapsed) {
646                hierarchy.addToFrom(sqlQuery, exp);
647            }
648            final String s = exp.getExpression(sqlQuery);
649            String alias = sqlQuery.addSelect(s, null);
650            // Some dialects allow us to eliminate properties from the
651            // group by that are functionally dependent on the level value
652            if (!sqlQuery.getDialect().allowsSelectNotInGroupBy()
653                || !property.dependsOnLevelValue())
654            {
655                sqlQuery.addGroupBy(s, alias);
656            }
657        }
658        return sqlQuery.toSqlAndTypes();
659    }
660
661    private static AggStar chooseAggStar(
662        MemberChildrenConstraint constraint,
663        RolapMember member)
664    {
665        if (!MondrianProperties.instance().UseAggregates.get()
666                || !(constraint instanceof SqlContextConstraint))
667        {
668            return null;
669        }
670        SqlContextConstraint contextConstraint =
671                (SqlContextConstraint) constraint;
672        Evaluator evaluator = contextConstraint.getEvaluator();
673        RolapCube cube = (RolapCube) evaluator.getCube();
674        RolapStar star = cube.getStar();
675        final int starColumnCount = star.getColumnCount();
676        BitKey measureBitKey = BitKey.Factory.makeBitKey(starColumnCount);
677        BitKey levelBitKey = BitKey.Factory.makeBitKey(starColumnCount);
678
679        // Convert global ordinal to cube based ordinal (the 0th dimension
680        // is always [Measures])
681        final Member[] members = evaluator.getNonAllMembers();
682
683        // if measure is calculated, we can't continue
684        if (!(members[0] instanceof RolapBaseCubeMeasure)) {
685            return null;
686        }
687        RolapBaseCubeMeasure measure = (RolapBaseCubeMeasure)members[0];
688        // we need to do more than this!  we need the rolap star ordinal, not
689        // the rolap cube
690
691        int bitPosition =
692            ((RolapStar.Measure)measure.getStarMeasure()).getBitPosition();
693
694        // childLevel will always end up being a RolapCubeLevel, but the API
695        // calls into this method can be both shared RolapMembers and
696        // RolapCubeMembers so this cast is necessary for now. Also note that
697        // this method will never be called in the context of a virtual cube
698        // so baseCube isn't necessary for retrieving the correct column
699
700        // get the level using the current depth
701        RolapCubeLevel childLevel =
702            (RolapCubeLevel) member.getLevel().getChildLevel();
703
704        RolapStar.Column column = childLevel.getStarKeyColumn();
705
706        // set a bit for each level which is constrained in the context
707        final CellRequest request =
708            RolapAggregationManager.makeRequest(members);
709        if (request == null) {
710            // One or more calculated members. Cannot use agg table.
711            return null;
712        }
713        // TODO: RME why is this using the array of constrained columns
714        // from the CellRequest rather than just the constrained columns
715        // BitKey (method getConstrainedColumnsBitKey)?
716        RolapStar.Column[] columns = request.getConstrainedColumns();
717        for (RolapStar.Column column1 : columns) {
718            levelBitKey.set(column1.getBitPosition());
719        }
720
721        // set the masks
722        levelBitKey.set(column.getBitPosition());
723        measureBitKey.set(bitPosition);
724
725        // Set the bits for limited rollup members
726        RolapUtil.constraintBitkeyForLimitedMembers(
727            evaluator, members, cube, levelBitKey);
728
729        // find the aggstar using the masks
730        return AggregationManager.findAgg(
731            star, levelBitKey, measureBitKey, new boolean[] {false});
732    }
733
734    /**
735     * Determine if a level contains more than a single column for its
736     * data, such as an ordinal column or property column
737     *
738     * @param level the level to check
739     * @return true if multiple relational columns are involved in this level
740     */
741    public static boolean levelContainsMultipleColumns(RolapLevel level) {
742        if (level.isAll()) {
743            return false;
744        }
745        MondrianDef.Expression keyExp = level.getKeyExp();
746        MondrianDef.Expression ordinalExp = level.getOrdinalExp();
747        MondrianDef.Expression captionExp = level.getCaptionExp();
748
749        if (!keyExp.equals(ordinalExp)) {
750            return true;
751        }
752
753        if (captionExp != null && !keyExp.equals(captionExp)) {
754            return true;
755        }
756
757        RolapProperty[] properties = level.getProperties();
758        for (RolapProperty property : properties) {
759            if (!property.getExp().equals(keyExp)) {
760                return true;
761            }
762        }
763
764        return false;
765    }
766
767    /**
768     * Determine if the given aggregate table has the dimension level
769     * specified within in (AggStar.FactTable) it, aka collapsed,
770     * or associated with foreign keys (AggStar.DimTable)
771     *
772     * @param aggStar aggregate star if exists
773     * @param level level
774     * @return true if agg table has level or not
775     */
776    public static boolean isLevelCollapsed(
777        AggStar aggStar,
778        RolapCubeLevel level)
779    {
780        boolean levelCollapsed = false;
781        if (level.isAll()) {
782            return levelCollapsed;
783        }
784        RolapStar.Column starColumn = level.getStarKeyColumn();
785        int bitPos = starColumn.getBitPosition();
786        AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos);
787        if (aggColumn.getTable() instanceof AggStar.FactTable) {
788            levelCollapsed = true;
789        }
790        return levelCollapsed;
791    }
792
793    public void getMemberChildren(
794        List<RolapMember> parentMembers,
795        List<RolapMember> children)
796    {
797        MemberChildrenConstraint constraint =
798            sqlConstraintFactory.getMemberChildrenConstraint(null);
799        getMemberChildren(parentMembers, children, constraint);
800    }
801
802    public Map<? extends Member, Access> getMemberChildren(
803        List<RolapMember> parentMembers,
804        List<RolapMember> children,
805        MemberChildrenConstraint mcc)
806    {
807        // try to fetch all children at once
808        RolapLevel childLevel =
809            getCommonChildLevelForDescendants(parentMembers);
810        if (childLevel != null) {
811            TupleConstraint lmc =
812                sqlConstraintFactory.getDescendantsConstraint(
813                    parentMembers, mcc);
814            List<RolapMember> list =
815                getMembersInLevel(childLevel, lmc);
816            children.addAll(list);
817            return Util.toNullValuesMap(children);
818        }
819
820        // fetch them one by one
821        for (RolapMember parentMember : parentMembers) {
822            getMemberChildren(parentMember, children, mcc);
823        }
824        return Util.toNullValuesMap(children);
825    }
826
827    public void getMemberChildren(
828        RolapMember parentMember,
829        List<RolapMember> children)
830    {
831        MemberChildrenConstraint constraint =
832            sqlConstraintFactory.getMemberChildrenConstraint(null);
833        getMemberChildren(parentMember, children, constraint);
834    }
835
836    public Map<? extends Member, Access> getMemberChildren(
837        RolapMember parentMember,
838        List<RolapMember> children,
839        MemberChildrenConstraint constraint)
840    {
841        // allow parent child calculated members through
842        // this fixes the non closure parent child hierarchy bug
843        if (!parentMember.isAll()
844            && parentMember.isCalculated()
845            && !parentMember.getLevel().isParentChild())
846        {
847            return Util.toNullValuesMap((List)Collections.emptyList());
848        }
849        getMemberChildren2(parentMember, children, constraint);
850        return Util.toNullValuesMap(children);
851    }
852
853    /**
854     * If all parents belong to the same level and no parent/child is involved,
855     * returns that level; this indicates that all member children can be
856     * fetched at once. Otherwise returns null.
857     */
858    private RolapLevel getCommonChildLevelForDescendants(
859        List<RolapMember> parents)
860    {
861        // at least two members required
862        if (parents.size() < 2) {
863            return null;
864        }
865        RolapLevel parentLevel = null;
866        RolapLevel childLevel = null;
867        for (RolapMember member : parents) {
868            // we can not fetch children of calc members
869            if (member.isCalculated()) {
870                return null;
871            }
872            // first round?
873            if (parentLevel == null) {
874                parentLevel = member.getLevel();
875                // check for parent/child
876                if (parentLevel.isParentChild()) {
877                    return null;
878                }
879                childLevel = (RolapLevel) parentLevel.getChildLevel();
880                if (childLevel == null) {
881                    return null;
882                }
883                if (childLevel.isParentChild()) {
884                    return null;
885                }
886            } else if (parentLevel != member.getLevel()) {
887                return null;
888            }
889        }
890        return childLevel;
891    }
892
893    private void getMemberChildren2(
894        RolapMember parentMember,
895        List<RolapMember> children,
896        MemberChildrenConstraint constraint)
897    {
898        Pair<String, List<SqlStatement.Type>> pair;
899        boolean parentChild;
900        final RolapLevel parentLevel = parentMember.getLevel();
901        RolapLevel childLevel;
902        if (parentLevel.isParentChild()) {
903            pair = makeChildMemberSqlPC(parentMember);
904            parentChild = true;
905            childLevel = parentLevel;
906        } else {
907            childLevel = (RolapLevel) parentLevel.getChildLevel();
908            if (childLevel == null) {
909                // member is at last level, so can have no children
910                return;
911            }
912            if (childLevel.isParentChild()) {
913                pair = makeChildMemberSql_PCRoot(parentMember);
914                parentChild = true;
915            } else {
916                pair = makeChildMemberSql(parentMember, dataSource, constraint);
917                parentChild = false;
918            }
919        }
920        final String sql = pair.left;
921        final List<SqlStatement.Type> types = pair.right;
922        SqlStatement stmt =
923            RolapUtil.executeQuery(
924                dataSource, sql, types, 0, 0,
925                new SqlStatement.StatementLocus(
926                    Locus.peek().execution,
927                    "SqlMemberSource.getMemberChildren",
928                    "while building member cache",
929                    SqlStatementEvent.Purpose.TUPLES, 0),
930                -1, -1, null);
931        try {
932            int limit = MondrianProperties.instance().ResultLimit.get();
933            boolean checkCacheStatus = true;
934
935            final List<SqlStatement.Accessor> accessors = stmt.getAccessors();
936            ResultSet resultSet = stmt.getResultSet();
937            RolapMember parentMember2 = RolapUtil.strip(parentMember);
938            while (resultSet.next()) {
939                ++stmt.rowCount;
940                if (limit > 0 && limit < stmt.rowCount) {
941                    // result limit exceeded, throw an exception
942                    throw MondrianResource.instance().MemberFetchLimitExceeded
943                        .ex(limit);
944                }
945
946                Object value = accessors.get(0).get();
947                if (value == null) {
948                    value = RolapUtil.sqlNullValue;
949                }
950                Object captionValue;
951                int columnOffset = 1;
952                if (childLevel.hasCaptionColumn()) {
953                    // The columnOffset needs to take into account
954                    // the caption column if one exists
955                    captionValue = accessors.get(columnOffset++).get();
956                } else {
957                    captionValue = null;
958                }
959                Object key = cache.makeKey(parentMember2, value);
960                RolapMember member = cache.getMember(key, checkCacheStatus);
961                checkCacheStatus = false; /* Only check the first time */
962                if (member == null) {
963                    member =
964                        makeMember(
965                            parentMember2, childLevel, value, captionValue,
966                            parentChild, stmt, key, columnOffset);
967                }
968                if (value == RolapUtil.sqlNullValue) {
969                    children.toArray();
970                    addAsOldestSibling(children, member);
971                } else {
972                    children.add(member);
973                }
974            }
975        } catch (SQLException e) {
976            throw stmt.handle(e);
977        } finally {
978            stmt.close();
979        }
980    }
981
982    public RolapMember makeMember(
983        RolapMember parentMember,
984        RolapLevel childLevel,
985        Object value,
986        Object captionValue,
987        boolean parentChild,
988        SqlStatement stmt,
989        Object key,
990        int columnOffset)
991        throws SQLException
992    {
993        final RolapLevel rolapChildLevel;
994        if (childLevel instanceof RolapCubeLevel) {
995            rolapChildLevel = ((RolapCubeLevel) childLevel).getRolapLevel();
996        } else {
997            rolapChildLevel = childLevel;
998        }
999        RolapMemberBase member =
1000            new RolapMemberBase(parentMember, rolapChildLevel, value);
1001        if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) {
1002            member.setOrdinal(lastOrdinal++);
1003        }
1004        if (captionValue != null) {
1005            member.setCaption(captionValue.toString());
1006        }
1007        if (parentChild) {
1008            // Create a 'public' and a 'data' member. The public member is
1009            // calculated, and its value is the aggregation of the data member
1010            // and all of the children. The children and the data member belong
1011            // to the parent member; the data member does not have any
1012            // children.
1013            member =
1014                childLevel.hasClosedPeer()
1015                ? new RolapParentChildMember(
1016                    parentMember, rolapChildLevel, value, member)
1017                : new RolapParentChildMemberNoClosure(
1018                    parentMember, rolapChildLevel, value, member);
1019        }
1020        Property[] properties = childLevel.getProperties();
1021        final List<SqlStatement.Accessor> accessors = stmt.getAccessors();
1022        if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) {
1023            if (assignOrderKeys) {
1024                Object orderKey = accessors.get(columnOffset).get();
1025                setOrderKey(member, orderKey);
1026            }
1027            ++columnOffset;
1028        }
1029        for (int j = 0; j < properties.length; j++) {
1030            Property property = properties[j];
1031            member.setProperty(
1032                property.getName(),
1033                getPooledValue(accessors.get(columnOffset + j).get()));
1034        }
1035        cache.putMember(key, member);
1036        return member;
1037    }
1038
1039    public RolapMember allMember() {
1040        final RolapHierarchy rolapHierarchy =
1041            hierarchy instanceof RolapCubeHierarchy
1042                ? ((RolapCubeHierarchy) hierarchy).getRolapHierarchy()
1043                : hierarchy;
1044        return rolapHierarchy.getAllMember();
1045    }
1046
1047    /**
1048     * <p>Looks up an object (and if needed, stores it) in a cached value pool.
1049     * This permits us to reuse references to an existing object rather than
1050     * create new references to what are essentially duplicates.  The intent
1051     * is to allow the duplicate object to be garbage collected earlier, thus
1052     * keeping overall memory requirements down.</p>
1053     *
1054     * <p>If
1055     * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass}
1056     * is not set, then valuePool will be null and no attempt to cache the
1057     * value will be made.  The method will simply return the incoming
1058     * object reference.</p>
1059     *
1060     * @param incoming An object to look up.  Must be immutable in usage,
1061     *        even if not declared as such.
1062     * @return a reference to a cached object equal to the incoming object,
1063     *        or to the incoming object if either no cached object was found,
1064     *        or caching is disabled.
1065     */
1066    private Object getPooledValue(Object incoming) {
1067        if (valuePool == null) {
1068            return incoming;
1069        } else {
1070            Object ret = this.valuePool.get(incoming);
1071            if (ret != null) {
1072                return ret;
1073            } else {
1074                this.valuePool.put(incoming, incoming);
1075                return incoming;
1076            }
1077        }
1078    }
1079
1080    /**
1081     * Generates the SQL to find all root members of a parent-child hierarchy.
1082     * For example, <blockquote>
1083     *
1084     * <pre>SELECT "employee_id"
1085     * FROM "employee"
1086     * WHERE "supervisor_id" IS NULL
1087     * GROUP BY "employee_id"</pre>
1088     * </blockquote> retrieves the root members of the <code>[Employee]</code>
1089     * hierarchy.
1090     *
1091     * <p>Currently, parent-child hierarchies may have only one level (plus the
1092     * 'All' level).
1093     */
1094    private Pair<String, List<SqlStatement.Type>> makeChildMemberSql_PCRoot(
1095        RolapMember member)
1096    {
1097        SqlQuery sqlQuery =
1098            SqlQuery.newQuery(
1099                dataSource,
1100                "while generating query to retrieve children of parent/child "
1101                + "hierarchy member " + member);
1102        Util.assertTrue(
1103            member.isAll(),
1104            "In the current implementation, parent/child hierarchies must "
1105            + "have only one level (plus the 'All' level).");
1106
1107        RolapLevel level = (RolapLevel) member.getLevel().getChildLevel();
1108
1109        Util.assertTrue(!level.isAll(), "all level cannot be parent-child");
1110        Util.assertTrue(
1111            level.isUnique(), "parent-child level '"
1112                + level + "' must be unique");
1113
1114        hierarchy.addToFrom(sqlQuery, level.getParentExp());
1115        String parentId = level.getParentExp().getExpression(sqlQuery);
1116        StringBuilder condition = new StringBuilder(64);
1117        condition.append(parentId);
1118        if (level.getNullParentValue() == null
1119            || level.getNullParentValue().equalsIgnoreCase("NULL"))
1120        {
1121            condition.append(" IS NULL");
1122        } else {
1123            // Quote the value if it doesn't seem to be a number.
1124            try {
1125                Util.discard(Double.parseDouble(level.getNullParentValue()));
1126                condition.append(" = ");
1127                condition.append(level.getNullParentValue());
1128            } catch (NumberFormatException e) {
1129                condition.append(" = ");
1130                Util.singleQuoteString(level.getNullParentValue(), condition);
1131            }
1132        }
1133        sqlQuery.addWhere(condition.toString());
1134        hierarchy.addToFrom(sqlQuery, level.getKeyExp());
1135        String childId = level.getKeyExp().getExpression(sqlQuery);
1136        sqlQuery.addSelectGroupBy(childId, level.getInternalType());
1137        hierarchy.addToFrom(sqlQuery, level.getOrdinalExp());
1138        String orderBy = level.getOrdinalExp().getExpression(sqlQuery);
1139        sqlQuery.addOrderBy(orderBy, true, false, true);
1140        if (!orderBy.equals(childId)) {
1141            sqlQuery.addSelectGroupBy(orderBy, null);
1142        }
1143
1144        RolapProperty[] properties = level.getProperties();
1145        for (RolapProperty property : properties) {
1146            final MondrianDef.Expression exp = property.getExp();
1147            hierarchy.addToFrom(sqlQuery, exp);
1148            final String s = exp.getExpression(sqlQuery);
1149            String alias = sqlQuery.addSelect(s, null);
1150            // Some dialects allow us to eliminate properties from the group by
1151            // that are functionally dependent on the level value
1152            if (!sqlQuery.getDialect().allowsSelectNotInGroupBy()
1153                || !property.dependsOnLevelValue())
1154            {
1155                sqlQuery.addGroupBy(s, alias);
1156            }
1157        }
1158        return sqlQuery.toSqlAndTypes();
1159    }
1160
1161    /**
1162     * Generates the SQL statement to access the children of
1163     * <code>member</code> in a parent-child hierarchy. For example,
1164     * <blockquote>
1165     *
1166     * <pre>SELECT "employee_id"
1167     * FROM "employee"
1168     * WHERE "supervisor_id" = 5</pre>
1169     * </blockquote> retrieves the children of the member
1170     * <code>[Employee].[5]</code>.
1171     *
1172     * <p>See also {@link SqlTupleReader#makeLevelMembersSql}.
1173     */
1174    private Pair<String, List<SqlStatement.Type>> makeChildMemberSqlPC(
1175        RolapMember member)
1176    {
1177        SqlQuery sqlQuery =
1178            SqlQuery.newQuery(
1179                dataSource,
1180                "while generating query to retrieve children of "
1181                + "parent/child hierarchy member " + member);
1182        RolapLevel level = member.getLevel();
1183
1184        Util.assertTrue(!level.isAll(), "all level cannot be parent-child");
1185        Util.assertTrue(
1186            level.isUnique(),
1187            "parent-child level '" + level + "' must be "  + "unique");
1188
1189        hierarchy.addToFrom(sqlQuery, level.getParentExp());
1190        String parentId = level.getParentExp().getExpression(sqlQuery);
1191
1192        StringBuilder buf = new StringBuilder();
1193        sqlQuery.getDialect().quote(buf, member.getKey(), level.getDatatype());
1194        sqlQuery.addWhere(parentId, " = ", buf.toString());
1195
1196        hierarchy.addToFrom(sqlQuery, level.getKeyExp());
1197        String childId = level.getKeyExp().getExpression(sqlQuery);
1198        sqlQuery.addSelectGroupBy(childId, level.getInternalType());
1199        hierarchy.addToFrom(sqlQuery, level.getOrdinalExp());
1200        String orderBy = level.getOrdinalExp().getExpression(sqlQuery);
1201        sqlQuery.addOrderBy(orderBy, true, false, true);
1202        if (!orderBy.equals(childId)) {
1203            sqlQuery.addSelectGroupBy(orderBy, null);
1204        }
1205
1206        RolapProperty[] properties = level.getProperties();
1207        for (RolapProperty property : properties) {
1208            final MondrianDef.Expression exp = property.getExp();
1209            hierarchy.addToFrom(sqlQuery, exp);
1210            final String s = exp.getExpression(sqlQuery);
1211            String alias = sqlQuery.addSelect(s, null);
1212            // Some dialects allow us to eliminate properties from the group by
1213            // that are functionally dependent on the level value
1214            if (!sqlQuery.getDialect().allowsSelectNotInGroupBy()
1215                || !property.dependsOnLevelValue())
1216            {
1217                sqlQuery.addGroupBy(s, alias);
1218            }
1219        }
1220        return sqlQuery.toSqlAndTypes();
1221    }
1222
1223    // implement MemberReader
1224    public RolapMember getLeadMember(RolapMember member, int n) {
1225        throw new UnsupportedOperationException();
1226    }
1227
1228    public void getMemberRange(
1229        RolapLevel level,
1230        RolapMember startMember,
1231        RolapMember endMember,
1232        List<RolapMember> memberList)
1233    {
1234        throw new UnsupportedOperationException();
1235    }
1236
1237    public int compare(
1238        RolapMember m1,
1239        RolapMember m2,
1240        boolean siblingsAreEqual)
1241    {
1242        throw new UnsupportedOperationException();
1243    }
1244
1245
1246    public TupleReader.MemberBuilder getMemberBuilder() {
1247        return this;
1248    }
1249
1250    public RolapMember getDefaultMember() {
1251        // we expected the CacheMemberReader to implement this
1252        throw new UnsupportedOperationException();
1253    }
1254
1255    public RolapMember getMemberParent(RolapMember member) {
1256        throw new UnsupportedOperationException();
1257    }
1258
1259    // ~ -- Inner classes ------------------------------------------------------
1260
1261    /**
1262     * Member of a parent-child dimension which has a closure table.
1263     *
1264     * <p>When looking up cells, this member will automatically be converted
1265     * to a corresponding member of the auxiliary dimension which maps onto
1266     * the closure table.
1267     */
1268    private static class RolapParentChildMember extends RolapMemberBase {
1269        private final RolapMember dataMember;
1270        private int depth = 0;
1271
1272        public RolapParentChildMember(
1273            RolapMember parentMember,
1274            RolapLevel childLevel,
1275            Object value,
1276            RolapMember dataMember)
1277        {
1278            super(parentMember, childLevel, value);
1279            this.dataMember = dataMember;
1280            this.depth = (parentMember != null)
1281                ? parentMember.getDepth() + 1
1282                : 0;
1283        }
1284
1285        public Member getDataMember() {
1286            return dataMember;
1287        }
1288
1289        /**
1290         * @return the members's depth
1291         * @see mondrian.olap.Member#getDepth()
1292         */
1293        public int getDepth() {
1294            return depth;
1295        }
1296
1297        public int getOrdinal() {
1298            return dataMember.getOrdinal();
1299        }
1300    }
1301
1302    /**
1303     * Member of a parent-child dimension which has no closure table.
1304     *
1305     * <p>This member is calculated. When you ask for its value, it returns
1306     * an expression which aggregates the values of its child members.
1307     * This calculation is very inefficient, and we can only support
1308     * aggregatable measures ("count distinct" is non-aggregatable).
1309     * Unfortunately it's the best we can do without a closure table.
1310     */
1311    private static class RolapParentChildMemberNoClosure
1312        extends RolapParentChildMember
1313    {
1314
1315        public RolapParentChildMemberNoClosure(
1316            RolapMember parentMember,
1317            RolapLevel childLevel, Object value, RolapMember dataMember)
1318        {
1319            super(parentMember, childLevel, value, dataMember);
1320        }
1321
1322        protected boolean computeCalculated(final MemberType memberType) {
1323            return true;
1324        }
1325
1326        public Exp getExpression() {
1327            return getHierarchy().getAggregateChildrenExpression();
1328        }
1329    }
1330
1331    /**
1332     * <p>Interface definition for the pluggable factory used to decide
1333     * which implementation of {@link java.util.Map} to use to pool
1334     * reusable values.</p>
1335     */
1336    public interface ValuePoolFactory {
1337        /**
1338         * <p>Create a new {@link java.util.Map} to be used to pool values.
1339         * The value pool permits us to reuse references to existing objects
1340         * rather than create new references to what are essentially duplicates
1341         * of the same object.  The intent is to allow the duplicate object
1342         * to be garbage collected earlier, thus keeping overall memory
1343         * requirements down.</p>
1344         *
1345         * @param source The {@link SqlMemberSource} in which values are
1346         * being pooled.
1347         * @return a new value pool map
1348         */
1349        Map<Object, Object> create(SqlMemberSource source);
1350    }
1351
1352    /**
1353     * Default {@link mondrian.rolap.SqlMemberSource.ValuePoolFactory}
1354     * implementation, used if
1355     * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass}
1356     * is not set.
1357     */
1358    public static final class NullValuePoolFactory
1359        implements ValuePoolFactory
1360    {
1361        /**
1362         * {@inheritDoc}
1363         * <p>This version returns null, meaning that
1364         * by default values will not be pooled.</p>
1365         *
1366         * @param source {@inheritDoc}
1367         * @return {@inheritDoc}
1368         */
1369        public Map<Object, Object> create(SqlMemberSource source) {
1370            return null;
1371        }
1372    }
1373
1374    /**
1375     * <p>Creates the ValuePoolFactory which is in turn used
1376     * to create property-value maps for member properties.</p>
1377     *
1378     * <p>The name of the ValuePoolFactory is drawn from
1379     * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass}
1380     * in mondrian.properties.  If unset, it defaults to
1381     * {@link mondrian.rolap.SqlMemberSource.NullValuePoolFactory}. </p>
1382     */
1383    public static final class ValuePoolFactoryFactory
1384        extends ObjectFactory.Singleton<ValuePoolFactory>
1385    {
1386        /**
1387         * Single instance of the <code>ValuePoolFactoryFactory</code>.
1388         */
1389        private static final ValuePoolFactoryFactory factory;
1390        static {
1391            factory = new ValuePoolFactoryFactory();
1392        }
1393
1394        /**
1395         * Access the <code>ValuePoolFactory</code> instance.
1396         *
1397         * @return the <code>Map</code>.
1398         */
1399        public static ValuePoolFactory getValuePoolFactory() {
1400            return factory.getObject();
1401        }
1402
1403        /**
1404         * The constructor for the <code>ValuePoolFactoryFactory</code>.
1405         * This passes the <code>ValuePoolFactory</code> class to the
1406         * <code>ObjectFactory</code> base class.
1407         */
1408        private ValuePoolFactoryFactory() {
1409            super(ValuePoolFactory.class);
1410        }
1411
1412        protected StringProperty getStringProperty() {
1413            return MondrianProperties.instance()
1414               .SqlMemberSourceValuePoolFactoryClass;
1415        }
1416
1417        protected ValuePoolFactory getDefault(
1418            Class[] parameterTypes,
1419            Object[] parameterValues)
1420            throws CreationException
1421        {
1422            return new NullValuePoolFactory();
1423        }
1424    }
1425}
1426
1427// End SqlMemberSource.java