/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.pig.newplan.logical.expression; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.pig.EvalFunc; import org.apache.pig.FuncSpec; import org.apache.pig.builtin.Nondeterministic; import org.apache.pig.data.DataType; import org.apache.pig.impl.PigContext; import org.apache.pig.impl.logicalLayer.FrontendException; import org.apache.pig.impl.logicalLayer.schema.Schema; import org.apache.pig.newplan.Operator; import org.apache.pig.newplan.OperatorPlan; import org.apache.pig.newplan.PlanVisitor; import org.apache.pig.newplan.logical.Util; import org.apache.pig.newplan.logical.relational.LogicalSchema; import org.apache.pig.parser.SourceLocation; public class UserFuncExpression extends LogicalExpression { private FuncSpec mFuncSpec; private EvalFunc<?> ef = null; //this represents whether the function was instantiate via a DEFINE statement or not private boolean viaDefine=false; public UserFuncExpression(OperatorPlan plan, FuncSpec funcSpec) { super("UserFunc", plan); mFuncSpec = funcSpec; plan.add(this); } public UserFuncExpression(OperatorPlan plan, FuncSpec funcSpec, List<LogicalExpression> args) { this( plan, funcSpec ); for( LogicalExpression arg : args ) { plan.connect( this, arg ); } } public UserFuncExpression(OperatorPlan plan, FuncSpec funcSpec, boolean viaDefine) { this( plan, funcSpec); this.viaDefine = viaDefine; } public UserFuncExpression(OperatorPlan plan, FuncSpec funcSpec, List<LogicalExpression> args, boolean viaDefine) { this( plan, funcSpec, args ); this.viaDefine = viaDefine; } public FuncSpec getFuncSpec() { return mFuncSpec; } @Override public void accept(PlanVisitor v) throws FrontendException { if (!(v instanceof LogicalExpressionVisitor)) { throw new FrontendException("Expected LogicalExpressionVisitor", 2222); } ((LogicalExpressionVisitor)v).visit(this); } @Override public boolean isEqual(Operator other) throws FrontendException { //For the purpose of optimization rules (specially LogicalExpressionSimplifier) // a non deterministic udf is not equal to another. So returning false for //such cases. // Note that the function is also invoked by implementations of OperatorPlan.isEqual // that function is called from test cases to compare logical plans, and // it will return false even if the plans are clones. if(!this.isDeterministic()) return false; if( other instanceof UserFuncExpression ) { UserFuncExpression exp = (UserFuncExpression)other; if (!mFuncSpec.equals(exp.mFuncSpec )) return false; List<Operator> mySuccs = getPlan().getSuccessors(this); List<Operator> theirSuccs = other.getPlan().getSuccessors(other); if(mySuccs == null || theirSuccs == null){ if(mySuccs == null && theirSuccs == null){ return true; }else{ //only one of the udfs has null successors return false; } } if (mySuccs.size()!=theirSuccs.size()) return false; for (int i=0;i<mySuccs.size();i++) { if (!mySuccs.get(i).isEqual(theirSuccs.get(i))) return false; } return true; } else { return false; } } public boolean isDeterministic() throws FrontendException{ Class<?> udfClass; try { udfClass = PigContext.resolveClassName(getFuncSpec().getClassName()); }catch(IOException ioe) { throw new FrontendException("Cannot instantiate: " + getFuncSpec(), ioe) ; } if (udfClass.getAnnotation(Nondeterministic.class) == null) { return true; } return false; } public List<LogicalExpression> getArguments() throws FrontendException { List<Operator> successors = null; List<LogicalExpression> args = new ArrayList<LogicalExpression>(); // try { successors = plan.getSuccessors(this); if(successors == null) return args; for(Operator lo : successors){ args.add((LogicalExpression)lo); } // } catch (FrontendException e) { // return args; // } return args; } /** * @param funcSpec the FuncSpec to set */ public void setFuncSpec(FuncSpec funcSpec) { mFuncSpec = funcSpec; ef = (EvalFunc<?>) PigContext.instantiateFuncFromSpec(mFuncSpec); } @Override public LogicalSchema.LogicalFieldSchema getFieldSchema() throws FrontendException { if (fieldSchema!=null) return fieldSchema; LogicalSchema inputSchema = new LogicalSchema(); List<Operator> succs = plan.getSuccessors(this); if (succs!=null) { for(Operator lo : succs){ if (((LogicalExpression)lo).getFieldSchema()==null) { inputSchema = null; break; } inputSchema.addField(((LogicalExpression)lo).getFieldSchema()); } } // Since ef only set one time, we never change its value, so we can optimize it by instantiate only once. // This significantly optimize the performance of frontend (PIG-1738) if (ef==null) ef = (EvalFunc<?>) PigContext.instantiateFuncFromSpec(mFuncSpec); Schema udfSchema = ef.outputSchema(Util.translateSchema(inputSchema)); if (udfSchema != null) { Schema.FieldSchema fs; if(udfSchema.size() == 0) { fs = new Schema.FieldSchema(null, null, DataType.findType(ef.getReturnType())); } else if(udfSchema.size() == 1) { fs = new Schema.FieldSchema(udfSchema.getField(0)); } else { fs = new Schema.FieldSchema(null, udfSchema, DataType.TUPLE); } fieldSchema = Util.translateFieldSchema(fs); fieldSchema.normalize(); } else { fieldSchema = new LogicalSchema.LogicalFieldSchema(null, null, DataType.findType(ef.getReturnType())); } uidOnlyFieldSchema = fieldSchema.mergeUid(uidOnlyFieldSchema); return fieldSchema; } @Override public LogicalExpression deepCopy(LogicalExpressionPlan lgExpPlan) throws FrontendException { UserFuncExpression copy = null; try { copy = new UserFuncExpression( lgExpPlan, this.getFuncSpec().clone(), viaDefine); // Deep copy the input expressions. List<Operator> inputs = plan.getSuccessors( this ); if( inputs != null ) { for( Operator op : inputs ) { LogicalExpression input = (LogicalExpression)op; LogicalExpression inputCopy = input.deepCopy( lgExpPlan ); lgExpPlan.add( inputCopy ); lgExpPlan.connect( copy, inputCopy ); } } } catch(CloneNotSupportedException e) { e.printStackTrace(); } copy.setLocation( new SourceLocation( location ) ); return copy; } public String toString() { StringBuilder msg = new StringBuilder(); msg.append("(Name: " + name + "(" + getFuncSpec() + ")" + " Type: "); if (fieldSchema!=null) msg.append(DataType.findTypeName(fieldSchema.type)); else msg.append("null"); msg.append(" Uid: "); if (fieldSchema!=null) msg.append(fieldSchema.uid); else msg.append("null"); msg.append(")"); return msg.toString(); } public boolean isViaDefine() { return viaDefine; } }