Package org.voltdb.compiler

Source Code of org.voltdb.compiler.ProcedureCompiler

/* This file is part of VoltDB.
* Copyright (C) 2008-2010 VoltDB L.L.C.
*
* VoltDB is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VoltDB is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VoltDB.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.voltdb.compiler;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import org.hsqldb.HSQLInterface;
import org.voltdb.ProcInfo;
import org.voltdb.ProcInfoData;
import org.voltdb.SQLStmt;
import org.voltdb.VoltMapReduceProcedure;
import org.voltdb.VoltProcedure;
import org.voltdb.VoltTable;
import org.voltdb.VoltType;
import org.voltdb.catalog.Catalog;
import org.voltdb.catalog.CatalogMap;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Group;
import org.voltdb.catalog.GroupRef;
import org.voltdb.catalog.ProcParameter;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.StmtParameter;
import org.voltdb.catalog.Table;
import org.voltdb.catalog.User;
import org.voltdb.catalog.UserRef;
import org.voltdb.compiler.VoltCompiler.ProcedureDescriptor;
import org.voltdb.compiler.VoltCompiler.VoltCompilerException;

import edu.brown.catalog.CatalogUtil;
import edu.brown.catalog.special.NullProcParameter;
import edu.brown.hstore.HStoreConstants;
import edu.brown.interfaces.Deferrable;
import edu.brown.interfaces.Prefetchable;
import edu.brown.utils.ClassUtil;

/**
* Compiles stored procedures into a given catalog, invoking the
* StatementCompiler as needed.
*/
public abstract class ProcedureCompiler {

    static void compile(VoltCompiler compiler, HSQLInterface hsql, DatabaseEstimates estimates, Catalog catalog, Database db, ProcedureDescriptor procedureDescriptor)
            throws VoltCompiler.VoltCompilerException {

        assert (compiler != null);
        assert (hsql != null);
        assert (estimates != null);

        if (procedureDescriptor.m_singleStmt == null)
            compileJavaProcedure(compiler, hsql, estimates, catalog, db, procedureDescriptor);
        else
            compileSingleStmtProcedure(compiler, hsql, estimates, catalog, db, procedureDescriptor);
    }

    static void compileJavaProcedure(VoltCompiler compiler,
                                     HSQLInterface hsql,
                                     DatabaseEstimates estimates,
                                     Catalog catalog,
                                     Database db,
                                     ProcedureDescriptor procedureDescriptor)
            throws VoltCompiler.VoltCompilerException {

        final String className = procedureDescriptor.m_className;

        // Load the class given the class name
        Class<?> procClass = null;
        try {
            procClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            String msg = "Cannot load class for procedure: " + className;
            throw compiler.new VoltCompilerException(msg);
        }

        // get the short name of the class (no package)
        String[] parts = className.split("\\.");
        String shortName = parts[parts.length - 1];

        // add an entry to the catalog
        final Procedure procedure = db.getProcedures().add(shortName);
        procedure.setId(compiler.getNextProcedureId());
        for (String userName : procedureDescriptor.m_authUsers) {
            final User user = db.getUsers().get(userName);
            if (user == null) {
                throw compiler.new VoltCompilerException("Procedure " + className + " has a user " + userName + " that does not exist");
            }
            final UserRef userRef = procedure.getAuthusers().add(userName);
            userRef.setUser(user);
        }
        for (String groupName : procedureDescriptor.m_authGroups) {
            final Group group = db.getGroups().get(groupName);
            if (group == null) {
                throw compiler.new VoltCompilerException("Procedure " + className + " has a group " + groupName + " that does not exist");
            }
            final GroupRef groupRef = procedure.getAuthgroups().add(groupName);
            groupRef.setGroup(group);
        }
        procedure.setClassname(className);
        // sysprocs don't use the procedure compiler
        procedure.setSystemproc(false);
        procedure.setHasjava(true);

        // get the annotation
        // first try to get one that has been passed from the compiler
        ProcInfoData info = compiler.getProcInfoOverride(shortName);
        // then check for the usual one in the class itself
        // and create a ProcInfo.Data instance for it
        if (info == null) {
            info = new ProcInfoData();
            ProcInfo annotationInfo = procClass.getAnnotation(ProcInfo.class);
            if (annotationInfo != null) {
                info.partitionInfo = annotationInfo.partitionInfo();
                info.partitionParam = annotationInfo.partitionParam();
                info.singlePartition = annotationInfo.singlePartition();
                info.mapInputQuery = annotationInfo.mapInputQuery();
                // info.mapEmitTable = annotationInfo.mapEmitTable();
                info.reduceInputQuery = annotationInfo.reduceInputQuery();
                // info.reduceEmitTable = annotationInfo.reduceEmitTable();
            }
        }
        assert (info != null);

        VoltProcedure procInstance = null;
        try {
            procInstance = (VoltProcedure) procClass.newInstance();
        } catch (InstantiationException e1) {
            e1.printStackTrace();
        } catch (IllegalAccessException e1) {
            e1.printStackTrace();
        }

        // MapReduce!
        if (ClassUtil.getSuperClasses(procClass).contains(VoltMapReduceProcedure.class)) {
            procedure.setMapreduce(true);

            // The Map input query is required
            // The Reduce input query is optional
            if (info.mapInputQuery == null || info.mapInputQuery.isEmpty()) {
                String msg = "Procedure: " + shortName + " must include a mapInputQuery";
                throw compiler.new VoltCompilerException(msg);
            }

            Database catalog_db = CatalogUtil.getDatabase(procedure);
            VoltMapReduceProcedure<?> mrInstance = (VoltMapReduceProcedure<?>) procInstance;

            // Initialize the MapOutput table
            // Create an invocation of the VoltMapProcedure so that we can grab
            // the MapOutput's schema
            VoltTable.ColumnInfo[] schema = mrInstance.getMapOutputSchema();
            String tableMapOutput = "MAP_" + procedure.getName();
            Table catalog_tbl = catalog_db.getTables().add(tableMapOutput);
            assert (catalog_tbl != null);
            for (int i = 0; i < schema.length; i++) {
                Column catalog_col = catalog_tbl.getColumns().add(schema[i].getName());
                catalog_col.setIndex(i);
                catalog_col.setNullable(i > 0);
                catalog_col.setType(schema[i].getType().getValue());
                if (i == 0)
                    catalog_tbl.setPartitioncolumn(catalog_col);
            } // FOR
            catalog_tbl.setMapreduce(true);
            catalog_tbl.setIsreplicated(false);

            // Initialize the reduceOutput table
            VoltTable.ColumnInfo[] schema_reduceOutput = mrInstance.getReduceOutputSchema();
            String tableReduceOutput = "REDUCE_" + procedure.getName();
            catalog_tbl = catalog_db.getTables().add(tableReduceOutput);
            assert (catalog_tbl != null);
            for (int i = 0; i < schema_reduceOutput.length; i++) {
                Column catalog_col = catalog_tbl.getColumns().add(schema_reduceOutput[i].getName());
                catalog_col.setIndex(i);
                catalog_col.setNullable(i > 0);
                catalog_col.setType(schema_reduceOutput[i].getType().getValue());
                if (i == 0)
                    catalog_tbl.setPartitioncolumn(catalog_col);
            } // FOR
            catalog_tbl.setMapreduce(true);
            catalog_tbl.setIsreplicated(false);

            // Initialize the Procedure catalog object
            procedure.setMapinputquery(info.mapInputQuery);
            procedure.setMapemittable(tableMapOutput);
            procedure.setReduceemittable(tableReduceOutput);
            procedure.setReduceinputquery(info.reduceInputQuery);
        }

        // track if there are any writer stmts
        boolean procHasWriteStmts = false;

        // iterate through the fields and deal with
        Field[] fields = procClass.getFields();
        for (Field f : fields) {
            if (f.getType() == SQLStmt.class) {
                // String fieldName = f.getName();
                SQLStmt stmt = null;

                try {
                    stmt = (SQLStmt) f.get(procInstance);
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

                // add the statement to the catalog
                Statement catalogStmt = procedure.getStatements().add(f.getName());
               
                // compile the statement
                try {
                    StatementCompiler.compile(compiler, hsql, catalog, db, estimates, catalogStmt, stmt.getText(), info.singlePartition);
                } catch (VoltCompiler.VoltCompilerException e) {
                    e.printStackTrace();
                    String msg = shortName + "." + f.getName() + ": " + e.getMessage();
                    throw compiler.new VoltCompilerException(msg);
                }

                // If this Field has a Prefetchable annotation or the Statement was
                // identified as prefetchable in the project XML, then we will want to
                // set the "prefetchable" flag in the catalog for the Statement + Procedure
                if (f.getAnnotation(Prefetchable.class) != null ||
                    procedureDescriptor.m_prefetchable.contains(catalogStmt.getName())) {
                    catalogStmt.setPrefetchable(true);
                    procedure.setPrefetchable(true);
                }
                // If this Field has a Deferrable annotation or the Statement was
                // identified as deferrable in the project XML, then we will want to
                // set the "deferrable" flag in the catalog for the Statement + Procedure
                if (f.getAnnotation(Deferrable.class) != null) {
                    catalogStmt.setDeferrable(true);
                    procedure.setDeferrable(true);
                }

                // if a single stmt is not read only, then the proc is not read
                // only
                if (catalogStmt.getReadonly() == false)
                    procHasWriteStmts = true;
            }
        }

        // set the read onlyness of a proc
        procedure.setReadonly(procHasWriteStmts == false);

        Class<?>[] paramTypes = populateProcedureParameters(compiler, procClass, procedure);

        // parse the procinfo
        procedure.setSinglepartition(info.singlePartition);
        if (info.partitionInfo != null && info.partitionInfo.isEmpty() == false) {
            parsePartitionInfo(compiler, db, procedure, info.partitionInfo);
            if (procedure.getPartitionparameter() >= paramTypes.length) {
                String msg = "PartitionInfo parameter not a valid parameter for procedure: " + procedure.getClassname();
                throw compiler.new VoltCompilerException(msg);
            }

            // check the type of partition parameter meets our high standards
            Class<?> partitionType = paramTypes[procedure.getPartitionparameter()];
            Class<?>[] validPartitionClzzes = { Long.class, Integer.class, Short.class, Byte.class, long.class, int.class, short.class, byte.class, String.class };
            boolean found = false;
            for (Class<?> candidate : validPartitionClzzes) {
                if (partitionType == candidate)
                    found = true;
            }
            // assume on of the two tests above passes and one fails
            if (!found) {
                String msg = "PartitionInfo parameter must be a String or Number for procedure: " + procedure.getClassname();
                throw compiler.new VoltCompilerException(msg);
            }
        } else {
            procedure.setPartitionparameter(NullProcParameter.PARAM_IDX);
        }
       
        // ProcInfo.partitionParam overrides everything else
        if (info.partitionParam != -1) {
            if (info.partitionParam >= paramTypes.length || info.partitionParam < 0) {
                String msg = "PartitionInfo 'partitionParam' not a valid parameter for procedure: " + procedure.getClassname();
                throw compiler.new VoltCompilerException(msg);
            }
            procedure.setPartitionparameter(info.partitionParam);
        }

        // put the compiled code for this procedure into the jarfile
        // VoltCompiler.addClassToJar(procClass, compiler);
    }

    static Class<?>[] populateProcedureParameters(VoltCompiler compiler, Class<?> procClass, Procedure procedure) throws VoltCompiler.VoltCompilerException {

        final String[] parts = procedure.getClassname().split("\\.");
        final String shortName = parts[parts.length - 1];

        // find the run() method and get the params
        Method procMethod = null;
        Method[] methods = procClass.getMethods();

        // DONE(xin): Check to make sure that the queries defined in the the
        // mapInputQuery and the reduceInputQuery
        // exist in the procedure
        // DONE(xin): Check to make sure that the database includes the
        // map/reduce output tables

        // Database catalog_db =
        // edu.brown.catalog.CatalogUtil.getDatabase(procedure);
        // FIXME catalog_db.getTables().get(procedure.getMapemittable());

        boolean isMapReduce = procedure.getMapreduce();
        Statement mapStatement = null;
        if (isMapReduce) {
            String mapInputQuery = procedure.getMapinputquery();
            mapStatement = procedure.getStatements().get(mapInputQuery);
            if (mapStatement == null) {
                String msg = "Procedure: " + shortName + " uses undefined mapInputQuery '" + mapInputQuery + "'";
                throw compiler.new VoltCompilerException(msg);
            }

            String reduceInputQuery = procedure.getReduceinputquery();
            Statement reduceStatement = null;
            if (reduceInputQuery != null && reduceInputQuery.isEmpty() == false) {
                reduceStatement = procedure.getStatements().get(reduceInputQuery);
                if (reduceStatement == null) {
                    String msg = "Procedure: " + shortName + " uses undefined reduceInputQuery '" + reduceInputQuery + "'";
                    throw compiler.new VoltCompilerException(msg);
                }
            }
        }

        for (final Method m : methods) {
            String name = m.getName();
            if (name.equals("run")) {
                // if not null, then we've got more than one run method
                if (procMethod != null) {
                    String msg = "Procedure: " + shortName + " has multiple run(...) methods. ";
                    msg += "Only a single run(...) method is supported.";
                    throw compiler.new VoltCompilerException(msg);
                }
                // found it!
                procMethod = m;
            }

        }
        // check if there is run method

        if (procMethod == null) {
            String msg = "Procedure: " + shortName + " has no run(...) method.";
            throw compiler.new VoltCompilerException(msg);
        }

        if ((procMethod.getReturnType() != VoltTable[].class) && (procMethod.getReturnType() != VoltTable.class) && (procMethod.getReturnType() != long.class)
                && (procMethod.getReturnType() != Long.class)) {

            String msg = "Procedure: " + shortName + " has run(...) method that doesn't return long, Long, VoltTable or VoltTable[].";
            throw compiler.new VoltCompilerException(msg);
        }

        CatalogMap<ProcParameter> params = procedure.getParameters(); // procedure
                                                                      // parameters
        Class<?>[] paramTypes = null;

        // Set procedure parameter types from its run method parameters
        if (isMapReduce == false) {
            paramTypes = procMethod.getParameterTypes();// run method parameters
            for (int i = 0; i < paramTypes.length; i++) {
                Class<?> cls = paramTypes[i];
                ProcParameter param = params.add(String.valueOf(i));
                param.setIndex(i);

                // handle the case where the param is an array
                if (cls.isArray()) {
                    param.setIsarray(true);
                    cls = cls.getComponentType();
                } else
                    param.setIsarray(false);

                VoltType type;
                try {
                    type = VoltType.typeFromClass(cls);
                } catch (RuntimeException e) {
                    // handle the case where the type is invalid
                    String msg = "Procedure: " + shortName + " has a parameter with invalid type: ";
                    msg += cls.getSimpleName();
                    throw compiler.new VoltCompilerException(msg);
                }
                param.setType(type.getValue());
            } // FOR
        }
        // The input parameters to the MapInputQuery are the input parameters
        // for the Procedure
        else {
            paramTypes = new Class<?>[mapStatement.getParameters().size()];
            for (int i = 0; i < paramTypes.length; i++) {
                StmtParameter catalog_stmt_param = mapStatement.getParameters().get(i);
                assert (catalog_stmt_param != null);
                VoltType vtype = VoltType.get(catalog_stmt_param.getJavatype());
                paramTypes[i] = vtype.classFromType();

                ProcParameter catalog_proc_param = procedure.getParameters().add(catalog_stmt_param.getName());
                catalog_proc_param.setIndex(i);
                catalog_proc_param.setIsarray(false); // One day...
                catalog_proc_param.setType(vtype.getValue());
            } // FOR
        }
        return (paramTypes);
    }

    static void compileSingleStmtProcedure(VoltCompiler compiler, HSQLInterface hsql, DatabaseEstimates estimates, Catalog catalog, Database db, ProcedureDescriptor procedureDescriptor)
            throws VoltCompiler.VoltCompilerException {

        final String className = procedureDescriptor.m_className;
        if (className.indexOf('@') != -1) {
            throw compiler.new VoltCompilerException("User procedure names can't contain \"@\".");
        }

        // get the short name of the class (no package)
        String[] parts = className.split("\\.");
        String shortName = parts[parts.length - 1];

        // add an entry to the catalog
        final Procedure procedure = db.getProcedures().add(shortName);
        procedure.setId(compiler.getNextProcedureId());
        for (String userName : procedureDescriptor.m_authUsers) {
            final User user = db.getUsers().get(userName);
            if (user == null) {
                throw compiler.new VoltCompilerException("Procedure " + className + " has a user " + userName + " that does not exist");
            }
            final UserRef userRef = procedure.getAuthusers().add(userName);
            userRef.setUser(user);
        }
        for (String groupName : procedureDescriptor.m_authGroups) {
            final Group group = db.getGroups().get(groupName);
            if (group == null) {
                throw compiler.new VoltCompilerException("Procedure " + className + " has a group " + groupName + " that does not exist");
            }
            final GroupRef groupRef = procedure.getAuthgroups().add(groupName);
            groupRef.setGroup(group);
        }
        procedure.setClassname(className);
        // sysprocs don't use the procedure compiler
        procedure.setSystemproc(false);
        procedure.setHasjava(false);

        // get the annotation
        // first try to get one that has been passed from the compiler
        ProcInfoData info = compiler.getProcInfoOverride(shortName);
        // then check for the usual one in the class itself
        // and create a ProcInfo.Data instance for it
        if (info == null) {
            info = new ProcInfoData();
            if (procedureDescriptor.m_partitionString != null) {
                info.partitionInfo = procedureDescriptor.m_partitionString;
                info.singlePartition = true;
            }
        }
        assert (info != null);

        // ADD THE STATEMENT

        // add the statement to the catalog
        Statement catalogStmt = procedure.getStatements().add(HStoreConstants.ANON_STMT_NAME);

        // compile the statement
        StatementCompiler.compile(compiler, hsql, catalog, db, estimates, catalogStmt, procedureDescriptor.m_singleStmt, info.singlePartition);

        // if the single stmt is not read only, then the proc is not read only
        boolean procHasWriteStmts = (catalogStmt.getReadonly() == false);

        // set the read onlyness of a proc
        procedure.setReadonly(procHasWriteStmts == false);

        // set procedure parameter types
        CatalogMap<ProcParameter> params = procedure.getParameters();
        CatalogMap<StmtParameter> stmtParams = catalogStmt.getParameters();

        // set the procedure parameter types from the statement parameter types
        int i = 0;
        for (StmtParameter stmtParam : CatalogUtil.getSortedCatalogItems(stmtParams, "index")) {
            // name each parameter "param1", "param2", etc...
            ProcParameter procParam = params.add("param" + String.valueOf(i));
            procParam.setIndex(stmtParam.getIndex());
            procParam.setIsarray(false);
            procParam.setType(stmtParam.getJavatype());
            i++;
        }

        // parse the procinfo
        procedure.setSinglepartition(info.singlePartition);
        if (info.singlePartition) {
            parsePartitionInfo(compiler, db, procedure, info.partitionInfo);
            if (procedure.getPartitionparameter() >= params.size()) {
                String msg = "PartitionInfo parameter not a valid parameter for procedure: " + procedure.getClassname();
                throw compiler.new VoltCompilerException(msg);
            }
        }
    }

    /**
     * Determine which parameter is the partition indicator
     */
    static void parsePartitionInfo(VoltCompiler compiler, Database db, Procedure procedure, String info) throws VoltCompilerException {

        // assert(procedure.getSinglepartition() == true);

        // check this isn't empty
        if (info.length() == 0) {
            String msg = "Missing or Truncated PartitionInfo in attribute for procedure: " + procedure.getClassname();
            throw compiler.new VoltCompilerException(msg);
        }

        // split on the colon
        String[] parts = info.split(":");

        // if the colon doesn't split well, we have a problem
        if (parts.length != 2) {
            String msg = "Possibly invalid PartitionInfo in attribute for procedure: " + procedure.getClassname();
            throw compiler.new VoltCompilerException(msg);
        }

        // relabel the parts for code readability
        String columnInfo = parts[0].trim();
        int paramIndex = Integer.parseInt(parts[1].trim());

        int paramCount = procedure.getParameters().size();
        if ((paramIndex < 0) || (paramIndex >= paramCount)) {
            String msg = "PartitionInfo specifies invalid column for procedure: " + procedure.getClassname();
            throw compiler.new VoltCompilerException(msg);
        }

        // locate the parameter
        procedure.setPartitionparameter(paramIndex);

        // split the columninfo
        parts = columnInfo.split("\\.");
        if (parts.length != 2) {
            String msg = "Possibly invalid PartitionInfo in attribute for procedure: " + procedure.getClassname();
            throw compiler.new VoltCompilerException(msg);
        }

        // relabel the parts for code readability
        String tableName = parts[0].trim();
        String columnName = parts[1].trim();

        // locate the partition column
        CatalogMap<Table> tables = db.getTables();
        for (Table table : tables) {
            if (table.getTypeName().equalsIgnoreCase(tableName)) {
                CatalogMap<Column> columns = table.getColumns();
                for (Column column : columns) {
                    if (column.getTypeName().equalsIgnoreCase(columnName)) {
                        procedure.setPartitioncolumn(column);
                        return;
                    }
                }
            }
        }

        String msg = "Unable to locate partition column in PartitionInfo for procedure: " + procedure.getClassname();
        throw compiler.new VoltCompilerException(msg);
    }
}
TOP

Related Classes of org.voltdb.compiler.ProcedureCompiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.