Search in sources :

Example 6 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class TestVoltCompiler method testGoodCreateProcedureWithAllow.

public void testGoodCreateProcedureWithAllow() throws Exception {
    Database db = goodDDLAgainstSimpleSchema("create role r1;", "create procedure p1 allow r1 as select * from books;");
    Procedure proc = db.getProcedures().get("p1");
    assertNotNull(proc);
    CatalogMap<GroupRef> groups = proc.getAuthgroups();
    assertEquals(1, groups.size());
    assertNotNull(groups.get("r1"));
    db = goodDDLAgainstSimpleSchema("create role r1;", "create role r2;", "create procedure p1 allow r1, r2 as select * from books;");
    proc = db.getProcedures().get("p1");
    assertNotNull(proc);
    groups = proc.getAuthgroups();
    assertEquals(2, groups.size());
    assertNotNull(groups.get("r1"));
    assertNotNull(groups.get("r2"));
    db = goodDDLAgainstSimpleSchema("create role r1;", "create procedure allow r1 from class org.voltdb.compiler.procedures.AddBook;");
    proc = db.getProcedures().get("AddBook");
    assertNotNull(proc);
    groups = proc.getAuthgroups();
    assertEquals(1, groups.size());
    assertNotNull(groups.get("r1"));
    db = goodDDLAgainstSimpleSchema("create role r1;", "create role r2;", "create procedure allow r1,r2 from class org.voltdb.compiler.procedures.AddBook;");
    proc = db.getProcedures().get("AddBook");
    assertNotNull(proc);
    groups = proc.getAuthgroups();
    assertEquals(2, groups.size());
    assertNotNull(groups.get("r1"));
    assertNotNull(groups.get("r2"));
    db = goodDDLAgainstSimpleSchema("create role r1;", "create procedure allow r1,r1 from class org.voltdb.compiler.procedures.AddBook;");
    proc = db.getProcedures().get("AddBook");
    assertNotNull(proc);
    groups = proc.getAuthgroups();
    assertEquals(1, groups.size());
    assertNotNull(groups.get("r1"));
}
Also used : Database(org.voltdb.catalog.Database) Procedure(org.voltdb.catalog.Procedure) GroupRef(org.voltdb.catalog.GroupRef)

Example 7 with GroupRef

use of org.voltdb.catalog.GroupRef in project voltdb by VoltDB.

the class ProcedureCompiler method compileJavaProcedure.

static void compileJavaProcedure(VoltCompiler compiler, HSQLInterface hsql, DatabaseEstimates estimates, Database db, ProcedureDescriptor procedureDescriptor, InMemoryJarfile jarOutput) throws VoltCompiler.VoltCompilerException {
    final String className = procedureDescriptor.m_className;
    // Load the class given the class name
    Class<?> procClass = procedureDescriptor.m_class;
    // get the short name of the class (no package)
    String shortName = deriveShortProcedureName(className);
    // add an entry to the catalog
    final Procedure procedure = db.getProcedures().add(shortName);
    for (String groupName : procedureDescriptor.m_authGroups) {
        final Group group = db.getGroups().get(groupName);
        if (group == null) {
            throw compiler.new VoltCompilerException("Procedure " + className + " allows access by a role " + 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.setDefaultproc(procedureDescriptor.m_builtInStmt);
    procedure.setHasjava(true);
    ProcedureAnnotation pa = (ProcedureAnnotation) procedure.getAnnotation();
    if (pa == null) {
        pa = new ProcedureAnnotation();
        procedure.setAnnotation(pa);
    }
    // get the annotation
    // first try to get one that has been passed from the compiler
    ProcInfoData info = compiler.getProcInfoOverride(shortName);
    // check if partition info was set in ddl
    ProcInfoData ddlInfo = null;
    if (procedureDescriptor.m_partitionString != null && !procedureDescriptor.m_partitionString.trim().isEmpty()) {
        ddlInfo = new ProcInfoData();
        ddlInfo.partitionInfo = procedureDescriptor.m_partitionString;
        ddlInfo.singlePartition = true;
    }
    // and create a ProcInfo.Data instance for it
    if (info == null) {
        info = new ProcInfoData();
        ProcInfo annotationInfo = procClass.getAnnotation(ProcInfo.class);
        // error out if partition info is present in both ddl and annotation
        if (annotationInfo != null) {
            if (ddlInfo != null) {
                String msg = "Procedure: " + shortName + " has partition properties defined both in ";
                msg += "class \"" + className + "\" and in the schema definition file(s)";
                throw compiler.new VoltCompilerException(msg);
            }
            // Prevent AutoGenerated DDL from including PARTITION PROCEDURE for this procedure.
            pa.classAnnotated = true;
            info.partitionInfo = annotationInfo.partitionInfo();
            info.singlePartition = annotationInfo.singlePartition();
        } else if (ddlInfo != null) {
            info = ddlInfo;
        }
    } else {
        pa.classAnnotated = true;
    }
    assert (info != null);
    // make sure multi-partition implies no partitioning info
    if (info.singlePartition == false) {
        if ((info.partitionInfo != null) && (info.partitionInfo.length() > 0)) {
            String msg = "Procedure: " + shortName + " is annotated as multi-partition";
            msg += " but partitionInfo has non-empty value: \"" + info.partitionInfo + "\"";
            throw compiler.new VoltCompilerException(msg);
        }
    }
    // if the procedure is non-transactional, then take this special path here
    if (VoltNonTransactionalProcedure.class.isAssignableFrom(procClass)) {
        compileNTProcedure(compiler, procClass, procedure, jarOutput);
        return;
    }
    // if still here, that means the procedure is transactional
    procedure.setTransactional(true);
    // track if there are any writer statements and/or sequential scans and/or an overlooked common partitioning parameter
    boolean procHasWriteStmts = false;
    boolean procHasSeqScans = false;
    // procWantsCommonPartitioning == true but commonPartitionExpression == null signifies a proc
    // for which the planner was requested to attempt to find an SP plan, but that was not possible
    // -- it had a replicated write or it had one or more partitioned reads that were not all
    // filtered by the same partition key value -- so it was planned as an MP proc.
    boolean procWantsCommonPartitioning = true;
    AbstractExpression commonPartitionExpression = null;
    String exampleSPstatement = null;
    Object exampleSPvalue = null;
    // iterate through the fields and get valid sql statements
    VoltProcedure procInstance;
    try {
        procInstance = (VoltProcedure) procClass.newInstance();
    } catch (InstantiationException e) {
        throw new RuntimeException("Error instantiating procedure \"%s\"" + procClass.getName(), e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Error instantiating procedure \"%s\"" + procClass.getName(), e);
    }
    Map<String, SQLStmt> stmtMap = getValidSQLStmts(compiler, procClass.getSimpleName(), procClass, procInstance, true);
    ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
    builder.putAll(stmtMap);
    // find the run() method and get the params
    Method procMethod = null;
    Method[] methods = procClass.getDeclaredMethods();
    for (final Method m : methods) {
        String name = m.getName();
        if (name.equals("run")) {
            assert (m.getDeclaringClass() == procClass);
            // if not null, then we've got more than one run method
            if (procMethod != null) {
                String msg = "Procedure: " + shortName + " has multiple public run(...) methods. ";
                msg += "Only a single run(...) method is supported.";
                throw compiler.new VoltCompilerException(msg);
            }
            if (Modifier.isPublic(m.getModifiers())) {
                // found it!
                procMethod = m;
            } else {
                compiler.addWarn("Procedure: " + shortName + " has non-public run(...) method.");
            }
        }
    }
    if (procMethod == null) {
        String msg = "Procedure: " + shortName + " has no run(...) method.";
        throw compiler.new VoltCompilerException(msg);
    }
    // check the return type of the run method
    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);
    }
    builder.put("@run", procMethod);
    Map<String, Object> fields = builder.build();
    // determine if proc is read or read-write by checking if the proc contains any write sql stmts
    boolean readWrite = false;
    for (Object field : fields.values()) {
        if (!(field instanceof SQLStmt))
            continue;
        SQLStmt stmt = (SQLStmt) field;
        QueryType qtype = QueryType.getFromSQL(stmt.getText());
        if (!qtype.isReadOnly()) {
            readWrite = true;
            break;
        }
    }
    // default to FASTER determinism mode, which may favor non-deterministic plans
    // but if it's a read-write proc, use a SAFER planning mode wrt determinism.
    final DeterminismMode detMode = readWrite ? DeterminismMode.SAFER : DeterminismMode.FASTER;
    for (Entry<String, Object> entry : fields.entrySet()) {
        if (!(entry.getValue() instanceof SQLStmt))
            continue;
        String stmtName = entry.getKey();
        SQLStmt stmt = (SQLStmt) entry.getValue();
        // add the statement to the catalog
        Statement catalogStmt = procedure.getStatements().add(stmtName);
        // compile the statement
        StatementPartitioning partitioning = info.singlePartition ? StatementPartitioning.forceSP() : StatementPartitioning.forceMP();
        boolean cacheHit = StatementCompiler.compileFromSqlTextAndUpdateCatalog(compiler, hsql, db, estimates, catalogStmt, stmt.getText(), stmt.getJoinOrder(), detMode, partitioning);
        // if this was a cache hit or specified single, don't worry about figuring out more partitioning
        if (partitioning.wasSpecifiedAsSingle() || cacheHit) {
            // Don't try to infer what's already been asserted.
            procWantsCommonPartitioning = false;
        // The planner does not currently attempt to second-guess a plan declared as single-partition, maybe some day.
        // In theory, the PartitioningForStatement would confirm the use of (only) a parameter as a partition key --
        // or if the partition key was determined to be some other constant (expression?) it might display an informational
        // message that the passed parameter is assumed to be equal to the hard-coded partition key constant (expression).
        // Validate any inferred statement partitioning given the statement's possible usage, until a contradiction is found.
        } else if (procWantsCommonPartitioning) {
            // conflict with the partitioning of prior statements.
            if (partitioning.getCountOfIndependentlyPartitionedTables() == 1) {
                AbstractExpression statementPartitionExpression = partitioning.singlePartitioningExpressionForReport();
                if (statementPartitionExpression != null) {
                    if (commonPartitionExpression == null) {
                        commonPartitionExpression = statementPartitionExpression;
                        exampleSPstatement = stmt.getText();
                        exampleSPvalue = partitioning.getInferredPartitioningValue();
                    } else if (commonPartitionExpression.equals(statementPartitionExpression) || (statementPartitionExpression instanceof ParameterValueExpression && commonPartitionExpression instanceof ParameterValueExpression)) {
                    // Any constant used for partitioning would have to be the same for all statements, but
                    // any statement parameter used for partitioning MIGHT come from the same proc parameter as
                    // any other statement's parameter used for partitioning.
                    } else {
                        // appears to be different partitioning for different statements
                        procWantsCommonPartitioning = false;
                    }
                } else {
                    // There is a statement with a partitioned table whose partitioning column is
                    // not equality filtered with a constant or param. Abandon all hope.
                    procWantsCommonPartitioning = false;
                }
            // Usually, replicated-only statements in a mix with others have no effect on the MP/SP decision
            } else if (partitioning.getCountOfPartitionedTables() == 0) {
                // but SP is strictly forbidden for DML, to maintain the consistency of the replicated data.
                if (partitioning.getIsReplicatedTableDML()) {
                    procWantsCommonPartitioning = false;
                }
            } else {
                // There is a statement with a partitioned table whose partitioning column is
                // not equality filtered with a constant or param. Abandon all hope.
                procWantsCommonPartitioning = false;
            }
        }
        // if a single stmt is not read only, then the proc is not read only
        if (catalogStmt.getReadonly() == false) {
            procHasWriteStmts = true;
        }
        if (catalogStmt.getSeqscancount() > 0) {
            procHasSeqScans = true;
        }
    }
    // MIGHT the planner have uncovered an overlooked opportunity to run all statements SP?
    if (procWantsCommonPartitioning && (commonPartitionExpression != null)) {
        String msg = null;
        if (commonPartitionExpression instanceof ParameterValueExpression) {
            msg = "This procedure might benefit from an @ProcInfo annotation designating parameter " + ((ParameterValueExpression) commonPartitionExpression).getParameterIndex() + " of statement '" + exampleSPstatement + "'";
        } else {
            String valueDescription = null;
            if (exampleSPvalue == null) {
                // Statements partitioned on a runtime constant. This is likely to be cryptic, but hopefully gets the idea across.
                valueDescription = "of " + commonPartitionExpression.explain("");
            } else {
                // A simple constant value COULD have been a parameter.
                valueDescription = exampleSPvalue.toString();
            }
            msg = "This procedure might benefit from an @ProcInfo annotation referencing an added parameter passed the value " + valueDescription;
        }
        compiler.addInfo(msg);
    }
    // set the read onlyness of a proc
    procedure.setReadonly(procHasWriteStmts == false);
    procedure.setHasseqscans(procHasSeqScans);
    checkForDeterminismWarnings(compiler, shortName, procedure, procHasWriteStmts);
    // set procedure parameter types
    CatalogMap<ProcParameter> params = procedure.getParameters();
    Class<?>[] paramTypes = procMethod.getParameterTypes();
    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);
        // boxed types are not supported parameters at this time
        if ((cls == Long.class) || (cls == Integer.class) || (cls == Short.class) || (cls == Byte.class) || (cls == Double.class) || (cls == Character.class) || (cls == Boolean.class)) {
            String msg = "Procedure: " + shortName + " has a parameter with a boxed type: ";
            msg += cls.getSimpleName();
            msg += ". Replace this parameter with the corresponding primitive type and the procedure may compile.";
            throw compiler.new VoltCompilerException(msg);
        } else if ((cls == Float.class) || (cls == float.class)) {
            String msg = "Procedure: " + shortName + " has a parameter with type: ";
            msg += cls.getSimpleName();
            msg += ". Replace this parameter type with double and the procedure may compile.";
            throw compiler.new VoltCompilerException(msg);
        }
        VoltType type;
        try {
            type = VoltType.typeFromClass(cls);
        } catch (VoltTypeException 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);
        } catch (RuntimeException e) {
            String msg = "Procedure: " + shortName + " unexpectedly failed a check on a parameter of type: ";
            msg += cls.getSimpleName();
            msg += " with error: ";
            msg += e.toString();
            throw compiler.new VoltCompilerException(msg);
        }
        param.setType(type.getValue());
    }
    // parse the procinfo
    procedure.setSinglepartition(info.singlePartition);
    if (info.singlePartition) {
        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, byte[].class };
        boolean found = false;
        for (Class<?> candidate : validPartitionClzzes) {
            if (partitionType == candidate)
                found = true;
        }
        if (!found) {
            String msg = "PartitionInfo parameter must be a String or Number for procedure: " + procedure.getClassname();
            throw compiler.new VoltCompilerException(msg);
        }
        VoltType columnType = VoltType.get((byte) procedure.getPartitioncolumn().getType());
        VoltType paramType = VoltType.typeFromClass(partitionType);
        if (!columnType.canExactlyRepresentAnyValueOf(paramType)) {
            String msg = "Type mismatch between partition column and partition parameter for procedure " + procedure.getClassname() + " may cause overflow or loss of precision.\nPartition column is type " + columnType + " and partition parameter is type " + paramType;
            throw compiler.new VoltCompilerException(msg);
        } else if (!paramType.canExactlyRepresentAnyValueOf(columnType)) {
            String msg = "Type mismatch between partition column and partition parameter for procedure " + procedure.getClassname() + " does not allow the full range of partition key values.\nPartition column is type " + columnType + " and partition parameter is type " + paramType;
            compiler.addWarn(msg);
        }
    }
    // put the compiled code for this procedure into the jarfile
    // need to find the outermost ancestor class for the procedure in the event
    // that it's actually an inner (or inner inner...) class.
    // addClassToJar recursively adds all the children, which should include this
    // class
    Class<?> ancestor = procClass;
    while (ancestor.getEnclosingClass() != null) {
        ancestor = ancestor.getEnclosingClass();
    }
    compiler.addClassToJar(jarOutput, ancestor);
}
Also used : Group(org.voltdb.catalog.Group) VoltProcedure(org.voltdb.VoltProcedure) VoltTypeException(org.voltdb.VoltTypeException) ProcedureAnnotation(org.voltdb.compilereport.ProcedureAnnotation) ProcInfoData(org.voltdb.ProcInfoData) StatementPartitioning(org.voltdb.planner.StatementPartitioning) VoltProcedure(org.voltdb.VoltProcedure) VoltNonTransactionalProcedure(org.voltdb.VoltNonTransactionalProcedure) Procedure(org.voltdb.catalog.Procedure) VoltCompilerException(org.voltdb.compiler.VoltCompiler.VoltCompilerException) ParameterValueExpression(org.voltdb.expressions.ParameterValueExpression) ProcParameter(org.voltdb.catalog.ProcParameter) ProcInfo(org.voltdb.ProcInfo) SQLStmt(org.voltdb.SQLStmt) Statement(org.voltdb.catalog.Statement) Method(java.lang.reflect.Method) VoltTable(org.voltdb.VoltTable) ImmutableMap(com.google_voltpatches.common.collect.ImmutableMap) AbstractExpression(org.voltdb.expressions.AbstractExpression) VoltType(org.voltdb.VoltType) GroupRef(org.voltdb.catalog.GroupRef) QueryType(org.voltdb.types.QueryType)

Aggregations

GroupRef (org.voltdb.catalog.GroupRef)7 Group (org.voltdb.catalog.Group)3 ProcParameter (org.voltdb.catalog.ProcParameter)3 Procedure (org.voltdb.catalog.Procedure)3 Statement (org.voltdb.catalog.Statement)3 ProcInfoData (org.voltdb.ProcInfoData)2 VoltNonTransactionalProcedure (org.voltdb.VoltNonTransactionalProcedure)2 VoltProcedure (org.voltdb.VoltProcedure)2 VoltTable (org.voltdb.VoltTable)2 VoltTypeException (org.voltdb.VoltTypeException)2 Database (org.voltdb.catalog.Database)2 VoltCompilerException (org.voltdb.compiler.VoltCompiler.VoltCompilerException)2 ProcedureAnnotation (org.voltdb.compilereport.ProcedureAnnotation)2 AbstractExpression (org.voltdb.expressions.AbstractExpression)2 ParameterValueExpression (org.voltdb.expressions.ParameterValueExpression)2 StatementPartitioning (org.voltdb.planner.StatementPartitioning)2 ImmutableMap (com.google_voltpatches.common.collect.ImmutableMap)1 Method (java.lang.reflect.Method)1 SecureRandom (java.security.SecureRandom)1 ArrayList (java.util.ArrayList)1