Menu

Outparams with variables don't work

2004-11-08
2013-04-25
  • Mark Niggemann

    Mark Niggemann - 2004-11-08

    I discovered a defect with outparam tags that use variables that are set in a param tag from the preceeding call tag. Here is an example of my test:

    <test name="running sp_TestKeyRequest">
    <call>
      <stmt>{? = call TestKeyRequest(?,?,?)}</stmt>
      <param id="1" type="INTEGER" inout="out">${status}</param>
      <param id="2" type="CHAR" inout="out">${keyVal}</param>
      <param id="3" type="INTEGER" inout="out">${errorCode}</param>
      <param id="4" type="VARCHAR" inout="out">${errorDesc}</param>
    </call>
    <result>
      <outparam id="1" type="INTEGER">0</outparam>
      <outparam id="2" type="CHAR">${keyVal}</outparam>
      <outparam id="3" type="INTEGER">0</outparam>
      <outparam id="4" type="VARCHAR">success</outparam>
    </result>
    </test>

    SQLUnit is failing on outparam id 2, but it doesn't fail nicely. A null pointer exception was generated in the assertOutParamEqual method but not caught by the TestHandler which would have ordinarily caught and would indicate which test failed (very bad). Since it was not caught, there was no indication which test had failed, but there would be an indication that a test had failed.

    I think I've found the root causes. The XMLUtils.getText method incorrectly returns a null value when symValue was a variable but was not found in the SymbolTable. I think that it was meant to return the text in this case which would be not null. If I am correct in my assumption, here is what the snippet of code should be in XMLUtils starting around line 87:

    --- XMLUtils.java    2004-11-08 12:21:06.081395600 -0600
    +++ XMLUtils.java.newer    2004-11-08 09:51:03.521921000 -0600
    @@ -87,7 +87,7 @@
             String text = element.getText();
             if (SymbolTable.isVariableName(text)) {
                 String symValue = SymbolTable.getValue(text);
    -            if (symValue == null) {
    +            if (symValue != null) {
                     return symValue;
                 } else {
                     return text;

    While this fixes the Null pointer exception, it still doesn't entirely solve the problem since the symbol table doesn't appear to be updated by the resultant out parameters from the call tag.

    I've figured out what needs to be changed and can submit patched files. SqlHandler and CallHandler need to change slightly.

    Here are the patches:

    --- SqlHandler.java    2004-11-08 12:44:24.375799400 -0600
    +++ SqlHandler.java.newer    2004-11-08 11:55:48.503568800 -0600
    @@ -145,10 +145,14 @@
                 try {
                     executeQuery(ps, result);
                 } catch (SQLException e) {
    +                clrOutputParameters(ps, params);
                     if (!conn.getAutoCommit()) { conn.rollback(); }
                     ConnectionRegistry.invalidate(connectionId);
                     result.resetAsException(
                         (new Integer(e.getErrorCode())).toString(), e.getMessage());
    +            } catch (Exception e) {
    +                clrOutputParameters(ps, params);
    +                throw e;
                 }
                 setOutputParameters(ps, params, result);
                 release(ps, conn);
    @@ -303,6 +307,19 @@
         }

         /**
    +     * Clears output parameter symbol table variables. This is a no-op
    +     * in the case of this handler, but will contain code for the CallHandler.
    +     * @param ps the Statement object.
    +     * @param params the array of Param objects.
    +     * @exception Exception if one is thrown by this method.
    +     */
    +    protected void clrOutputParameters(final Statement ps,
    +            final Param[] params)
    +            throws Exception {
    +        return;
    +    }
    +
    +    /**
          * Closes Statement and Connection objects and releases them.
          * @param ps the Statement object.
          * @param conn the Connection object.

    --- CallHandler.java    2004-11-08 12:44:09.703970900 -0600
    +++ CallHandler.java.newer    2004-11-08 12:08:37.317397100 -0600
    @@ -155,7 +155,7 @@
             for (int i = 0; i < params.length; i++) {
                 if (params[i].isOutParameter()) {
                     if (SymbolTable.isVariableName(params[i].getValue())) {
    -                    SymbolTable.setValue(SymbolTable.OUT_PARAM + i + "}",
    +                    SymbolTable.setValue(SymbolTable.OUT_PARAM + tcs + ":" + i + "}",
                             params[i].getValue());
                     }
                     int sqltype = params[i].getSQLType();
    @@ -214,16 +214,23 @@
                     outParam.setId(params[i].getId());
                     outParam.setName(params[i].getName());
                     outParam.setType(params[i].getType());
    +                String outParamSymbol = SymbolTable.removeSymbol(SymbolTable.OUT_PARAM + tcs + ":" + i + "}");
                     // get the value from the CallableStatement
                     Object value = tcs.getObject(i + 1);
                     if (value instanceof ResultSet) {
                         // value is a resultset
                         ResultSetBean rsb = new ResultSetBean((ResultSet) value, 1);
                         outParam.setValue(rsb);
    +                    if ( outParamSymbol != null ) {
    +                        SymbolTable.setObject(outParamSymbol, rsb);
    +                    }
                     } else {
                         // value is a String
                         IType type = TypeFactory.getInstance(params[i].getType());
                         outParam.setValue(type.toString(value));
    +                    if (outParamSymbol != null) {
    +                        SymbolTable.setValue(outParamSymbol,type.toString(value));
    +                    }
                     }
                     outParamList.add(outParam);
                 }
    @@ -234,4 +241,23 @@
             }
             result.setOutParams(ops);
         }
    +
    +    /**
    +     * Overrides the parent method to clear parameters into the
    +     * DatabaseResult object.
    +     * @param cs the Statement object.
    +     * @param params the array of Param objects.
    +     * @param result the DatabaseResult object.
    +     * @exception Exception if there was a problem setting the OUT params.
    +     */
    +    protected final void clrOutputParameters(final Statement cs,
    +            final Param[] params)
    +            throws Exception {
    +        CallableStatement tcs = (CallableStatement) cs;
    +        for (int i = 0; i < params.length; i++) {
    +            if (params[i].isOutParameter()) {
    +                SymbolTable.removeSymbol(SymbolTable.OUT_PARAM + tcs + ":" + i + "}");
    +           }
    +        }
    +    }
    }

     
    • Sujit Pal

      Sujit Pal - 2004-11-08

      Hi Mark,

      Yes, the XMLUtils#getText() problem is definitely a bug, thanks for pointing it out.

      BTW, here is a list of "rules" for variable substitution (this will need to be updated after your patch):
      http://sqlunit.sourceforge.net/c2606.html

      In this case it looks like ${keyVal} is being used as both an lvalue (when being assigned as an out parameter in the call) and as an rvalue (when being compared with the value returned from the procedure in the outparameter in the result tag).

      From what I understand from your patch, you are clearing the declared outparam values at the beginning of each test, which would allow variables to be overwritten per test when reuse is requested, right? This is an excellent idea, and makes the variable substitution more intuitive.

      If you can send me a patch (the formatting does not get carried over in email cut and paste), I can update the code and this can be made available in the mext release.

      Thanks very much,
      Sujit

       
      • Sujit Pal

        Sujit Pal - 2004-11-09

        Hi Mark,

        I put in a test case in the test/mock/variabletests.xml and the code changes you enumerated in your post. The test name="Reusing previously set variable". Its in CVS now, I still have to update the docs to remove the restriction that once you set a variable you can reset only with a set tag.

        Thanks for the patch, it will be in the next release.

        -sujit

         

Log in to post a comment.