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