001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/fun/ExtractFunDef.java#2 $
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) 2007-2008 Julian Hyde
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.olap.fun;
011    
012    import mondrian.calc.*;
013    import mondrian.calc.impl.AbstractListCalc;
014    import mondrian.mdx.ResolvedFunCall;
015    import mondrian.mdx.DimensionExpr;
016    import mondrian.olap.*;
017    import mondrian.olap.type.*;
018    
019    import java.util.*;
020    
021    /**
022     * Definition of the <code>Extract</code> MDX function.
023     *
024     * <p>Syntax:
025     * <blockquote><code>Extract(&lt;Set&gt;, &lt;Dimension&gt;[,
026     * &lt;Dimension&gt;...])</code></blockquote>
027     *
028     * @author jhyde
029     * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/ExtractFunDef.java#2 $
030     * @since Jun 10, 2007
031     */
032    class ExtractFunDef extends FunDefBase {
033        static final ResolverBase Resolver = new ResolverBase(
034            "Extract",
035            "Extract(<Set>, <Dimension>[, <Dimension>...])",
036            "Returns a set of tuples from extracted dimension elements. The opposite of Crossjoin.",
037            Syntax.Function) {
038    
039            public FunDef resolve(
040                Exp[] args, Validator validator, int[] conversionCount) {
041                if (args.length < 2) {
042                    return null;
043                }
044                if (!validator.canConvert(
045                    args[0], Category.Set, conversionCount)) {
046                    return null;
047                }
048                for (int i = 1; i < args.length; ++i) {
049                    if (!validator.canConvert(
050                        args[i], Category.Dimension, conversionCount)) {
051                        return null;
052                    }
053                }
054    
055                // Find the dimensionality of the set expression.
056    
057                // Form a list of ordinals of the dimensions being extracted.
058                // For example, in
059                //   Extract(X.Members * Y.Members * Z.Members, Z, X)
060                // the dimension ordinals are X=0, Y=1, Z=2, and the extracted
061                // ordinals are {2, 0}.
062                //
063                // Each dimension extracted must exist in the LHS,
064                // and no dimension may be extracted more than once.
065                List<Integer> extractedOrdinals = new ArrayList<Integer>();
066                final List<Dimension> extractedDimensions = new ArrayList<Dimension>();
067                findExtractedDimensions(args, extractedDimensions, extractedOrdinals);
068                int[] parameterTypes = new int[args.length];
069                parameterTypes[0] = Category.Set;
070                Arrays.fill(parameterTypes, 1, parameterTypes.length, Category.Dimension);
071                return new ExtractFunDef(this, Category.Set, parameterTypes);
072            }
073        };
074    
075        private ExtractFunDef(
076            Resolver resolver, int returnType, int[] parameterTypes)
077        {
078            super(resolver, returnType, parameterTypes);
079        }
080    
081        public Type getResultType(Validator validator, Exp[] args) {
082            final List<Dimension> extractedDimensions =
083                new ArrayList<Dimension>();
084            final List<Integer> extractedOrdinals = new ArrayList<Integer>();
085            findExtractedDimensions(args, extractedDimensions, extractedOrdinals);
086            if (extractedDimensions.size() == 1) {
087                return new SetType(
088                    new MemberType(extractedDimensions.get(0), null, null, null));
089            } else {
090                List<Type> typeList = new ArrayList<Type>();
091                for (Dimension extractedDimension : extractedDimensions) {
092                    typeList.add(
093                        new MemberType(
094                            extractedDimension, null, null, null));
095                }
096                return new SetType(
097                    new TupleType(
098                        typeList.toArray(new Type[typeList.size()])));
099            }
100        }
101    
102        private static void findExtractedDimensions(
103            Exp[] args,
104            List<Dimension> extractedDimensions,
105            List<Integer> extractedOrdinals)
106        {
107            SetType type = (SetType) args[0].getType();
108            final List<Dimension> dimensions = new ArrayList<Dimension>();
109            if (type.getElementType() instanceof TupleType) {
110                for (Type elementType : ((TupleType) type
111                    .getElementType()).elementTypes) {
112                    Dimension dimension = elementType.getDimension();
113                    if (dimension == null) {
114                        throw new RuntimeException("dimension of argument not known");
115                    }
116                    dimensions.add(dimension);
117                }
118            } else {
119                Dimension dimension = type.getDimension();
120                if (dimension == null) {
121                    throw new RuntimeException("dimension of argument not known");
122                }
123                dimensions.add(dimension);
124            }
125    
126            for (int i = 1; i < args.length; i++) {
127                Exp arg = args[i];
128                if (arg instanceof DimensionExpr) {
129                    DimensionExpr dimensionExpr = (DimensionExpr) arg;
130                    final Dimension extractedDimension =
131                        dimensionExpr.getDimension();
132                    int ordinal = dimensions.indexOf(extractedDimension);
133                    if (ordinal == -1) {
134                        throw new RuntimeException(
135                            "dimension " +
136                                extractedDimension.getUniqueName() +
137                                " is not a dimension of the expression " + args[0]);
138                    }
139                    if (extractedOrdinals.indexOf(ordinal) >= 0) {
140                        throw new RuntimeException(
141                            "dimension " +
142                                extractedDimension.getUniqueName() +
143                                " is extracted more than once");
144                    }
145                    extractedOrdinals.add(ordinal);
146                    extractedDimensions.add(extractedDimension);
147                } else {
148                    throw new RuntimeException("not a constant dimension: " + arg);
149                }
150            }
151        }
152    
153        private static int[] toIntArray(List<Integer> integerList) {
154            final int[] ints = new int[integerList.size()];
155            for (int i = 0; i < ints.length; i++) {
156                ints[i] = integerList.get(i);
157            }
158            return ints;
159        }
160    
161        public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
162            List<Dimension> extractedDimensionList = new ArrayList<Dimension>();
163            List<Integer> extractedOrdinalList = new ArrayList<Integer>();
164            findExtractedDimensions(
165                call.getArgs(),
166                extractedDimensionList,
167                extractedOrdinalList);
168            Util.assertTrue(
169                extractedOrdinalList.size() == extractedDimensionList.size());
170            Exp arg = call.getArg(0);
171            final TupleListCalc listCalc =
172                (TupleListCalc) compiler.compileList(arg, false);
173            int inArity = ((SetType) arg.getType()).getArity();
174            final int outArity = extractedOrdinalList.size();
175            if (inArity == 1) {
176                // LHS is a set of members, RHS is the same dimension. Extract boils
177                // down to eliminating duplicate members.
178                Util.assertTrue(outArity == 1);
179                return new DistinctFunDef.CalcImpl(call, listCalc);
180            }
181            final int[] extractedOrdinals = toIntArray(extractedOrdinalList);
182            if (outArity == 1) {
183                return new AbstractListCalc(call, new Calc[] {listCalc}) {
184                    public List evaluateList(Evaluator evaluator) {
185                        List<Member> result = new ArrayList<Member>();
186                        List<Member[]> list = listCalc.evaluateTupleList(evaluator);
187                        Set<Member> emittedMembers = new HashSet<Member>();
188                        for (Member[] members : list) {
189                            Member outMember = members[extractedOrdinals[0]];
190                            if (emittedMembers.add(outMember)) {
191                                result.add(outMember);
192                            }
193                        }
194                        return result;
195                    }
196                };
197            } else {
198                return new AbstractListCalc(call, new Calc[] {listCalc}) {
199                    public List evaluateList(Evaluator evaluator) {
200                        List<Member[]> result = new ArrayList<Member[]>();
201                        List<Member[]> list = listCalc.evaluateTupleList(evaluator);
202                        Set<List<Member>> emittedTuples = new HashSet<List<Member>>();
203                        for (Member[] members : list) {
204                            Member[] outMembers = new Member[outArity];
205                            for (int i = 0; i < outMembers.length; i++) {
206                                outMembers[i] = members[extractedOrdinals[i]];
207                            }
208                            final List<Member> outTuple = Arrays.asList(outMembers);
209                            if (emittedTuples.add(outTuple)) {
210                                result.add(outMembers);
211                            }
212                        }
213                        return result;
214                    }
215                };
216            }
217        }
218    }
219    
220    // End ExtractFunDef.java