Bug #79561 NullPointerException when calling a fully qualified stored procedure
Submitted: 9 Dec 2015 3:47 Modified: 1 Apr 2017 0:57
Reporter: Davi Arnaut (OCA) Email Updates:
Status: In progress Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version:5.1.36, 5.1.37, 5.1.38 OS:Any
Assigned to: Filipe Silva CPU Architecture:Any
Tags: CallableStatement, quote, regression, stored procedure

[9 Dec 2015 3:47] Davi Arnaut
Description:
Calling a fully qualified stored procedure where only the database name
part is quoted results in a null pointer exception. This used to work in
early versions.

Additionally, calling registerOutParameter with an unknown parameter
name results in a NullPointerException. A SQLException should be thrown
instead, see JDBC API.

How to repeat:
mysql> CREATE PROCEDURE `test`.foo(OUT outp LONGTEXT) BEGIN SELECT "foo" INTO outp; END|
Query OK, 0 rows affected (0.00 sec)

[darnaut ~]$ javac CallFuncQuote.java
[darnaut ~]$ java -cp ".:$HOME/mysql-connector-java-5.1.14.jar" CallFuncQuote
outp = foo
[darnaut ~]$ java -cp ".:$HOME/mysql-connector-java-5.1.36.jar" CallFuncQuote
Exception in thread "main" java.lang.NullPointerException
	at com.mysql.jdbc.CallableStatement.getNamedParamIndex(CallableStatement.java:1441)
	at com.mysql.jdbc.CallableStatement.registerOutParameter(CallableStatement.java:1883)
	at CallFuncQuote.main(CallFuncQuote.java:15)

Suggested fix:
The StringUtils.splitDBdotName method assumes that if the database is
quoted, so will be the procedure name part. That is, it expects
`database`.`procname` whereas `database`.procname is also valid.
[9 Dec 2015 3:47] Davi Arnaut
Test case

Attachment: CallFuncQuote.java (application/octet-stream, text), 643 bytes.

[9 Dec 2015 5:44] Umesh Shastry
Hello Davi,

Thank you for the report and test case.
This seems to be broken from 5.1.16 onward.

Thanks,
Umesh
[9 Dec 2015 5:45] Umesh Shastry
// 5.1.38, 5.1.37, 5.1.36 - affected(broken since 5.1.16)

[umshastr@hod03]/export/umesh/server/binaries/mysql-advanced-5.6.27: java -cp '.:/home/umshastr/bugs/mysql-connector-java-5.1.38/mysql-connector-java-5.1.38-bin.jar' CallFuncQuote
Exception in thread "main" java.lang.NullPointerException
        at com.mysql.jdbc.CallableStatement.getNamedParamIndex(CallableStatement.java:1375)
        at com.mysql.jdbc.CallableStatement.registerOutParameter(CallableStatement.java:1817)
        at CallFuncQuote.main(CallFuncQuote.java:15)
        
[umshastr@hod03]/export/umesh/server/binaries/mysql-advanced-5.6.27: java -cp '.:/home/umshastr/bugs/mysql-connector-java-5.1.37/mysql-connector-java-5.1.37-bin.jar' CallFuncQuote
Exception in thread "main" java.lang.NullPointerException
        at com.mysql.jdbc.CallableStatement.getNamedParamIndex(CallableStatement.java:1375)
        at com.mysql.jdbc.CallableStatement.registerOutParameter(CallableStatement.java:1817)
        at CallFuncQuote.main(CallFuncQuote.java:15)

[umshastr@hod03]/export/umesh/server/binaries/mysql-advanced-5.6.27: java -cp '.:/home/umshastr/bugs/mysql-connector-java-5.1.36/mysql-connector-java-5.1.36-bin.jar' CallFuncQuote
Exception in thread "main" java.lang.NullPointerException
        at com.mysql.jdbc.CallableStatement.getNamedParamIndex(CallableStatement.java:1441)
        at com.mysql.jdbc.CallableStatement.registerOutParameter(CallableStatement.java:1883)
        at CallFuncQuote.main(CallFuncQuote.java:15)

[umshastr@hod03]/export/umesh/server/binaries/mysql-advanced-5.6.27: java -cp '.:/home/umshastr/bugs/mysql-connector-java-5.1.16/mysql-connector-java-5.1.16-bin.jar' CallFuncQuote
Exception in thread "main" java.lang.NullPointerException
        at com.mysql.jdbc.CallableStatement.getNamedParamIndex(CallableStatement.java:1486)
        at com.mysql.jdbc.CallableStatement.registerOutParameter(CallableStatement.java:1908)
        at CallFuncQuote.main(CallFuncQuote.java:15)

-- 5.1.14, 5.1.15 - not affected

[umshastr@hod03]/export/umesh/server/binaries/mysql-advanced-5.6.27: java -cp '.:/home/umshastr/bugs/mysql-connector-java-5.1.14/mysql-connector-java-5.1.14-bin.jar' CallFuncQuote
outp = foo
[umshastr@hod03]/export/umesh/server/binaries/mysql-advanced-5.6.27: java -cp '.:/home/umshastr/bugs/mysql-connector-java-5.1.15/mysql-connector-java-5.1.15-bin.jar' CallFuncQuote
outp = foo
[2 Jan 2016 12:45] Ramin Orujov
Dear Dav.
I've fixed this bug by adding a few lines in StringUtils.splitDBdotName method. Here is my fixed version.

public static List<String> splitDBdotName(String src, String cat, String quotId, boolean isNoBslashEscSet) {
        if ((src == null) || (src.equals("%"))) {
            return new ArrayList<String>();
        }

        boolean isQuoted = StringUtils.indexOfIgnoreCase(0, src, quotId) > -1;

        String retval = src;
        String tmpCat = cat;
        //I.e., what if database is named `MyDatabase 1.0.0`... thus trueDotIndex
        int trueDotIndex = -1;
        if (!" ".equals(quotId)) {
            //Presumably, if there is a database name attached and it contains dots, then it should be quoted so we first check for that
            if (isQuoted) {
                trueDotIndex = StringUtils.indexOfIgnoreCase(0, retval, quotId + "." + quotId);
                
                // bug #79516 only database name is quoted, 2nd part after dot is not quoted
                // http://bugs.mysql.com/bug.php?id=79561
                if(trueDotIndex == -1) {
                    trueDotIndex = StringUtils.indexOfIgnoreCase(0, retval, quotId + ".");
                }
            } else {
                // NOT quoted, fetch first DOT
                // ex: cStmt = this.conn.prepareCall("{call bug57022.procbug57022(?, ?)}");
                trueDotIndex = StringUtils.indexOfIgnoreCase(0, retval, ".");
            }
        } else {
            trueDotIndex = retval.indexOf(".");
        }

        List<String> retTokens = new ArrayList<String>(2);

        if (trueDotIndex != -1) {
            //There is a catalog attached
            if (isQuoted) {
                tmpCat = StringUtils.toString(StringUtils.stripEnclosure(retval.substring(0, trueDotIndex + 1).getBytes(), quotId, quotId));
                if (StringUtils.startsWithIgnoreCaseAndWs(tmpCat, quotId)) {
                    tmpCat = tmpCat.substring(1, tmpCat.length() - 1);
                }

                retval = retval.substring(trueDotIndex + 2);
                retval = StringUtils.toString(StringUtils.stripEnclosure(retval.getBytes(), quotId, quotId));
            } else {
                //NOT quoted, adjust indexOf
                tmpCat = retval.substring(0, trueDotIndex);
                retval = retval.substring(trueDotIndex + 1);
            }
        } else {
            //No catalog attached, strip retval and return
            retval = StringUtils.toString(StringUtils.stripEnclosure(retval.getBytes(), quotId, quotId));
        }

        retTokens.add(tmpCat);
        retTokens.add(retval);
        return retTokens;
    }
[2 Jan 2016 12:46] Ramin Orujov
Attached fixed version.

Attachment: StringUtils.java (text/x-java), 85.50 KiB.

[2 Jan 2016 13:22] Ramin Orujov
Here is my pull request
https://github.com/mysql/mysql-connector-j/pull/8
[2 Jan 2016 14:26] Ramin Orujov
Here is output from Davi's test case with fixed jar file.

raminorujov@ro--ubuntu:~/Downloads$ pwd
/home/raminorujov/Downloads
raminorujov@ro--ubuntu:~/Downloads$ cp ~/repo/ro-mysql-connector-j/mysql-connector-j/build/mysql-connector-java-5.1.38-SNAPSHOT/mysql-connector-java-5.1.38-SNAPSHOT-bin.jar .
raminorujov@ro--ubuntu:~/Downloads$ javac CallFuncQuote.java 
raminorujov@ro--ubuntu:~/Downloads$ java -cp .:mysql-connector-java-5.1.38-SNAPSHOT-bin.jar CallFuncQuote 
outp = foo
raminorujov@ro--ubuntu:~/Downloads$
[14 Mar 2016 12:26] Filipe Silva
Duplicate Bug#80722.