001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2004-2005 TONBELLER AG
008// Copyright (C) 2006-2011 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.rolap.sql;
012
013import mondrian.olap.MondrianProperties;
014import mondrian.rolap.*;
015import mondrian.rolap.aggmatcher.AggStar;
016
017import java.util.ArrayList;
018import java.util.List;
019
020/**
021 * Represents an enumeration {member1, member2, ...}.
022 * All members must to the same level and are non-calculated.
023 */
024public class MemberListCrossJoinArg implements CrossJoinArg {
025    private final List<RolapMember> members;
026    private final RolapLevel level;
027    private final boolean restrictMemberTypes;
028    private final boolean hasCalcMembers;
029    private final boolean hasNonCalcMembers;
030    private final boolean hasAllMember;
031    private final boolean exclude;
032
033    private MemberListCrossJoinArg(
034        RolapLevel level,
035        List<RolapMember> members,
036        boolean restrictMemberTypes,
037        boolean hasCalcMembers,
038        boolean hasNonCalcMembers,
039        boolean hasAllMember,
040        boolean exclude)
041    {
042        this.level = level;
043        this.members = members;
044        this.restrictMemberTypes = restrictMemberTypes;
045        this.hasCalcMembers = hasCalcMembers;
046        this.hasNonCalcMembers = hasNonCalcMembers;
047        this.hasAllMember = hasAllMember;
048        this.exclude = exclude;
049    }
050
051    private static boolean isArgSizeSupported(
052        RolapEvaluator evaluator,
053        int argSize)
054    {
055        boolean argSizeNotSupported = false;
056
057        // Note: arg size 0 is accepted as valid CJ argument
058        // This is used to push down the "1 = 0" predicate
059        // into the emerging CJ so that the entire CJ can
060        // be natively evaluated.
061
062        // First check that the member list will not result in a predicate
063        // longer than the underlying DB could support.
064        if (argSize > MondrianProperties.instance().MaxConstraints.get()) {
065            argSizeNotSupported = true;
066        }
067
068        return !argSizeNotSupported;
069    }
070
071
072    /**
073     * Creates an instance of {@link CrossJoinArg},
074     * or returns null if the arguments are invalid. This method also
075     * records properties of the member list such as containing
076     * calc/non calc members, and containing the All member.
077     *
078     * <p>If restrictMemberTypes is set, then the resulting argument could
079     * contain calculated members. The newly created CrossJoinArg is marked
080     * appropriately for special handling downstream.
081     *
082     * <p>If restrictMemberTypes is false, then the resulting argument
083     * contains non-calculated members of the same level (after filtering
084     * out any null members).
085     *
086     * @param evaluator the current evaluator
087     * @param args members in the list
088     * @param restrictMemberTypes whether calculated members are allowed
089     * @param exclude Whether to exclude tuples that match the predicate
090     * @return MemberListCrossJoinArg if member list is well formed,
091     *   null if not.
092     */
093    public static CrossJoinArg create(
094        RolapEvaluator evaluator,
095        final List<RolapMember> args,
096        final boolean restrictMemberTypes,
097        boolean exclude)
098    {
099        // First check that the member list will not result in a predicate
100        // longer than the underlying DB could support.
101        if (!isArgSizeSupported(evaluator, args.size())) {
102            return null;
103        }
104
105        RolapLevel level = null;
106        RolapLevel nullLevel = null;
107        boolean hasCalcMembers = false;
108        boolean hasNonCalcMembers = false;
109
110        // Crossjoin Arg is an empty member list.
111        // This is used to push down the constant "false" condition to the
112        // native evaluator.
113        if (args.size() == 0) {
114            hasNonCalcMembers = true;
115        }
116        boolean hasAllMember = false;
117        for (RolapMember m : args) {
118            if (m.isNull()) {
119                // we're going to filter out null members anyway;
120                // don't choke on the fact that their level
121                // doesn't match that of others
122                nullLevel = m.getLevel();
123                continue;
124            }
125
126            // If "All" member, native evaluation is not possible
127            // because "All" member does not have a corresponding
128            // relational representation.
129            //
130            // "All" member is ignored during SQL generation.
131            // The complete MDX query can be evaluated natively only
132            // if there is non all member on at least one level;
133            // otherwise the generated SQL is an empty string.
134            // See SqlTupleReader.addLevelMemberSql()
135            //
136            if (m.isAll()) {
137                hasAllMember = true;
138            }
139
140            if (m.isCalculated() && !m.isParentChildLeaf()) {
141                if (restrictMemberTypes) {
142                    return null;
143                }
144                hasCalcMembers = true;
145            } else {
146                hasNonCalcMembers = true;
147            }
148            if (level == null) {
149                level = m.getLevel();
150            } else if (!level.equals(m.getLevel())) {
151                // Members should be on the same level.
152                return null;
153            }
154        }
155        if (level == null) {
156            // all members were null; use an arbitrary one of the
157            // null levels since the SQL predicate is going to always
158            // fail anyway
159            level = nullLevel;
160        }
161
162        // level will be null for an empty CJ input that is pushed down
163        // to the native evaluator.
164        // This case is not treated as a non-native input.
165        if ((level != null) && (!level.isSimple()
166            && !supportedParentChild(level, args)))
167        {
168            return null;
169        }
170        List<RolapMember> members = new ArrayList<RolapMember>();
171
172        for (RolapMember m : args) {
173            if (m.isNull()) {
174                // filter out null members
175                continue;
176            }
177            members.add(m);
178        }
179
180        return new MemberListCrossJoinArg(
181            level, members, restrictMemberTypes,
182            hasCalcMembers, hasNonCalcMembers, hasAllMember, exclude);
183    }
184
185    private static boolean supportedParentChild(
186        RolapLevel level, List<RolapMember> args)
187    {
188        if (level.isParentChild()) {
189            boolean allArgsLeaf = true;
190            for (RolapMember rolapMember : args) {
191            if (!rolapMember.isParentChildLeaf()) {
192                allArgsLeaf = false;
193                break;
194            }
195        }
196            return allArgsLeaf;
197        }
198        return false;
199    }
200
201    public RolapLevel getLevel() {
202        return level;
203    }
204
205    public List<RolapMember> getMembers() {
206        return members;
207    }
208
209    public boolean isPreferInterpreter(boolean joinArg) {
210        if (joinArg) {
211            // If this enumeration only contains calculated members,
212            // prefer non-native evaluation.
213            return hasCalcMembers && !hasNonCalcMembers;
214        } else {
215            // For non-join usage, always prefer non-native
216            // eval, since the members are already known.
217            return true;
218        }
219    }
220
221    public void addConstraint(
222        SqlQuery sqlQuery,
223        RolapCube baseCube,
224        AggStar aggStar)
225    {
226        SqlConstraintUtils.addMemberConstraint(
227            sqlQuery, baseCube, aggStar,
228            members, restrictMemberTypes, true, exclude);
229    }
230
231    /**
232     * Returns whether the input CJ arg is empty.
233     *
234     * <p>This is used to selectively push down empty input arg into the
235     * native evaluator.
236     *
237     * @return whether the input CJ arg is empty
238     */
239    public boolean isEmptyCrossJoinArg() {
240        return (level == null && members.size() == 0);
241    }
242
243    public boolean hasCalcMembers() {
244        return hasCalcMembers;
245    }
246
247    public boolean hasAllMember() {
248        return hasAllMember;
249    }
250
251    public int hashCode() {
252        int c = 12;
253        for (RolapMember member : members) {
254            c = 31 * c + member.hashCode();
255        }
256        if (restrictMemberTypes) {
257            c += 1;
258        }
259        if (exclude) {
260            c += 7;
261        }
262        return c;
263    }
264
265    public boolean equals(Object obj) {
266        if (!(obj instanceof MemberListCrossJoinArg)) {
267            return false;
268        }
269        MemberListCrossJoinArg that = (MemberListCrossJoinArg) obj;
270        if (this.restrictMemberTypes != that.restrictMemberTypes) {
271            return false;
272        }
273        if (this.exclude != that.exclude) {
274            return false;
275        }
276        for (int i = 0; i < members.size(); i++) {
277            if (this.members.get(i) != that.members.get(i)) {
278                return false;
279            }
280        }
281        return true;
282    }
283}
284
285// End MemberListCrossJoinArg.java