Hi,
I would like to report what seems like a bug in H2. I have a 'merge
into' prepared statement with a parameter in the insert sub-statement. I am
using the JDBC driver to send the statement. Following are the details.
First the table on which the query is being invoked:
CREATE TABLE MY_TEST_TABLE
(
ATTR1 VARCHAR2(128) NOT NULL,
ATTR2 VARCHAR2(50),
ATTR3 VARCHAR2(100),
ATTR4 VARCHAR2(100),
ATTR5 VARCHAR2(2000),
ATTR6 NUMBER(10),
ATTR7 TIMESTAMP(6),
ATTR8 VARCHAR2(10),
ATTR9 BLOB,
ATTR10 VARCHAR2(70),
ATTR11 TIMESTAMP,
ATTR12 TIMESTAMP(6),
ATTR13 VARCHAR2(200),
ATTR14 NUMBER(10),
ATTR15 NUMBER(10),
ATTR16 VARCHAR2(100),
ATTR17 VARCHAR2(128)
);
Following is the JAVA code that sends a 'merge into' update statement using
JDBC. Attached is a self-contained application code that runs the following
function.
static void bug_case (Connection conn)
{
System.out.
println ("Test for which we check the param count in isolation");
PreparedStatement statement = null;
try
{
statement =
conn.
prepareStatement
("MERGE INTO MY_TEST_TABLE D USING ( SELECT '1234' as ATTR1, 'TYPE1' as
ATTR2, 'FailType1' as ATTR4, 'Sample Details' as ATTR5, '1' as ATTR6,
'REPLAY' as ATTR8, 'abcd' as ATTR3 , '2' as MAX_ATTR6, 'null' as ATTR17
,null as ATTR7, null as ATTR9, null as ATTR10, null as ATTR11, null as
ATTR12, null as ATTR13, null as ATTR14 , null as ATTR15 FROM dual ) S ON
(D.ATTR1 = S.ATTR1 AND D.ATTR2 = S.ATTR2) WHEN MATCHED THEN UPDATE SET
D.ATTR6 = CASE WHEN S.ATTR6 < 0 THEN -1 ELSE D.ATTR6 + 1 END, D.ATTR8 =
CASE WHEN D.ATTR6 < S.MAX_ATTR6-1 THEN S.ATTR8 ELSE 'FAIL' END, D.ATTR7 =
sysdate, D.ATTR4 = S.ATTR4, D.ATTR5 = S.ATTR5, D.ATTR3 = S.ATTR3, D.ATTR17
= S.ATTR17 WHEN NOT MATCHED THEN INSERT (ATTR1, ATTR2, ATTR4, ATTR5, ATTR6,
ATTR7, ATTR8, ATTR3, ATTR9, ATTR17) VALUES (S.ATTR1, S.ATTR2, S.ATTR4,
S.ATTR5, S.ATTR6, sysdate, S.ATTR8,S.ATTR3,? ,S.ATTR17)");
Blob b1 = conn.createBlob ();
byte[]bytes = new byte[10];
for (int i = 0; i < 10; ++i)
bytes[i] = (byte) (2 * i);
b1.setBytes (1, bytes);
statement.setBlob (1, b1);
statement.executeUpdate ();
}
catch (Exception e)
{
System.err.println ("ERROR: " + e.toString ());
}
finally
{
try
{
if (statement != null)
{
statement.close ();
}
}
catch (SQLException e)
{
System.err.println ("SQL ERROR: " + e.toString ());
}
catch (Exception e)
{
System.err.println ("ERROR: " + e.toString ());
}
}
}
I get the following exception:
ERROR: org.h2.jdbc.JdbcSQLException: Invalid value "1" for parameter
"parameterIndex" [90008-197]
I have confirmed that the issue is not on the client side. The client-side
driver sends the same prepared statement to the H2 server and the server
sends back 0 as parameter count (as received by the function
void CommandRemote::prepare(SessionRemote s, boolean createParams)in the line
highlighted below. Here readInt returns 0 despite the parameter highlighted in
green above).
private void prepare(SessionRemote s, boolean createParams) {
id = s.getNextId();
for (int i = 0, count = 0; i < transferList.size(); i++) {
try {
Transfer transfer = transferList.get(i);
boolean v16 = s.getClientVersion() >=
Constants.TCP_PROTOCOL_VERSION_16;
if (createParams) {
s.traceOperation(v16 ? "SESSION_PREPARE_READ_PARAMS2"
: "SESSION_PREPARE_READ_PARAMS", id);
transfer.writeInt(
v16 ? SessionRemote.SESSION_PREPARE_READ_PARAMS2
: SessionRemote.SESSION_PREPARE_READ_PARAMS)
.writeInt(id).writeString(sql);
} else {
s.traceOperation("SESSION_PREPARE", id);
transfer.writeInt(SessionRemote.SESSION_PREPARE).
writeInt(id).writeString(sql);
}
s.done(transfer);
isQuery = transfer.readBoolean();
readonly = transfer.readBoolean();
cmdType = v16 && createParams ? transfer.readInt() : UNKNOWN;
int paramCount = transfer.readInt();
if (createParams) {
parameters.clear();
for (int j = 0; j < paramCount; j++) {
ParameterRemote p = new ParameterRemote(j);
p.readMetaData(transfer);
parameters.add(p);
}
}
} catch (IOException e) {
s.removeServer(e, i--, ++count);
}
}
}
Will much appreciate any feedback.
Best regards
Jay
--
You received this message because you are subscribed to the Google Groups "H2
Database" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/h2-database.
For more options, visit https://groups.google.com/d/optout.
import java.util.*;
import java.lang.*;
import java.io.*;
import org.apache.commons.dbcp.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.*;
import java.lang.System;
import java.sql.DriverManager;
public class H2Client
{
private static String DB_DRIVER = "org.h2.Driver";
private static String DB_CONNECTION = ""; //= "";
private static String DB_USER = ""; // = "sa";
private static String DB_PASSWORD = ""; // = "";
private static String DB_SCHEMA = "";
private static Connection connection = null;
public static Connection getDBConnection ()
{
if (connection == null)
try
{
Class.forName (DB_DRIVER);
try
{
connection =
DriverManager.getConnection (DB_CONNECTION, DB_USER, DB_PASSWORD);
if (connection != null)
{
System.out.println ("Connection to DB established");
return connection;
}
}
catch (SQLException ex)
{
System.out.println ("Connection to DB could not be established");
return null;
}
}
catch (ClassNotFoundException ex)
{
System.out.println ("Connection to DB could not be established");
return null;
}
return null;
}
public static void printUsage ()
{
System.out.
println
("Usage : executable -D driver -L connection-string -U username -P password @sqlfile1 @sqlfile2 ...");
}
public static void main (String[]argv)
{
if (argv.length == 0)
{
printUsage ();
return;
}
System.out.println (argv[0]);
String connectionString;
ArrayList < String > files = new ArrayList < String > ();
for (int i = 0; i < argv.length; ++i)
{
if (argv[i].equals ("-D"))
{
if (argv.length > (i + 1))
{
DB_DRIVER = argv[i + 1];
++i;
}
}
else if (argv[i].equals ("-C"))
{
if (argv.length > (i + 1))
{
DB_CONNECTION = argv[i + 1];
++i;
}
}
else if (argv[i].equals ("-L"))
{
if (argv.length > (i + 1))
{
String[]parts = argv[i + 1].split ("/");
System.out.println ("Number of parts = " + parts.length);
DB_USER = "sa"; // parts[0];
DB_PASSWORD = ""; //parts[1].substring(0, parts[1].length()-1);
String hostPort = parts[3];
String dbname = parts[4];
// Here is an example of the connection string "jdbc:h2:tcp://127.0.0.1:9092/test"
DB_CONNECTION =
"jdbc:h2:tcp://" + hostPort + "/" + dbname + ";MODE=Oracle";
if (parts[0] != "sa")
{
DB_SCHEMA = parts[0];
}
++i;
}
}
else if (argv[i].equals ("-S"))
{
if (argv.length > (i + 1))
{
DB_SCHEMA = argv[i + 1];
++i;
}
}
else if (argv[i].equals ("-U"))
{
if (argv.length > (i + 1))
{
DB_USER = argv[i + 1];
++i;
}
}
else if (argv[i].equals ("-P"))
{
if (argv.length > (i + 1))
{
DB_PASSWORD = argv[i + 1];
++i;
}
}
else if (argv[i].charAt (0) == '@')
{
files.add (argv[i].substring (1));
}
}
if (DB_CONNECTION.equals (""))
{
System.out.println ("Database connection was not specified");
printUsage ();
return;
}
if (DB_USER.equals (""))
{
System.out.println ("Username was not specified");
printUsage ();
return;
}
System.out.println ("DB Connection:<" + DB_CONNECTION + ">");
System.out.println ("Username :<" + DB_USER + ">");
System.out.println ("Password :<" + DB_PASSWORD + ">");
for (String file:files)
{
System.out.println ("Source file:" + file);
}
BasicDataSource basicDataSource = new BasicDataSource ();
basicDataSource.setDriverClassName (DB_DRIVER);
if (DB_SCHEMA == "")
{
basicDataSource.setUrl (DB_CONNECTION + ";SCHEMA_SEARCH_PATH=PUBLIC,INFORMATION_SCHEMA"); //jdbcSlabPasswordUtil.getServer());
}
else
{
basicDataSource.setUrl (DB_CONNECTION + ";SCHEMA=" + DB_SCHEMA +
";SCHEMA_SEARCH_PATH=PUBLIC,AME_CUSTODY_DBO,INFORMATION_SCHEMA");
}
basicDataSource.setUsername (DB_USER); //jdbcSlabPasswordUtil.getUsername());
basicDataSource.setPassword (DB_PASSWORD); //jdbcSlabPasswordUtil.getPassword());
basicDataSource.setInitialSize (10);
//basicDataSource.setMaxIdle(maxIdle);
//basicDataSource.setMaxActive(maxActive);
basicDataSource.setConnectionProperties ("v$session.program=DSJdbc");
run_tests (basicDataSource);
}
static void run_tests (BasicDataSource basicDataSource)
{
Connection conn = null;
Statement statement = null;
try
{
conn = basicDataSource.getConnection ();
bug_case (conn);
}
catch (Exception e)
{
System.err.println ("ERROR: " + e.toString ());
}
finally
{
try
{
if (conn != null)
{
conn.close ();
}
}
catch (SQLException e)
{
System.err.println ("SQL ERROR: " + e.toString ());
}
catch (Exception e)
{
System.err.println ("ERROR: " + e.toString ());
}
}
}
static void bug_case (Connection conn)
{
System.out.
println ("Test for which we check the param count in isolation");
PreparedStatement statement = null;
try
{
statement =
conn.
prepareStatement
("MERGE INTO MY_TEST_TABLE D USING ( SELECT '1234' as ATTR1, 'TYPE1' as ATTR2, 'FailType1' as ATTR4, 'Sample Details' as ATTR5, '1' as ATTR6, 'REPLAY' as ATTR8, 'abcd' as ATTR3 , '2' as MAX_ATTR6, 'null' as ATTR17 ,null as ATTR7, null as ATTR9, null as ATTR10, null as ATTR11, null as ATTR12, null as ATTR13, null as ATTR14 , null as ATTR15 FROM dual ) S ON (D.ATTR1 = S.ATTR1 AND D.ATTR2 = S.ATTR2) WHEN MATCHED THEN UPDATE SET D.ATTR6 = CASE WHEN S.ATTR6 < 0 THEN -1 ELSE D.ATTR6 + 1 END, D.ATTR8 = CASE WHEN D.ATTR6 < S.MAX_ATTR6-1 THEN S.ATTR8 ELSE 'FAIL' END, D.ATTR7 = sysdate, D.ATTR4 = S.ATTR4, D.ATTR5 = S.ATTR5, D.ATTR3 = S.ATTR3, D.ATTR17 = S.ATTR17 WHEN NOT MATCHED THEN INSERT (ATTR1, ATTR2, ATTR4, ATTR5, ATTR6, ATTR7, ATTR8, ATTR3, ATTR9, ATTR17) VALUES (S.ATTR1, S.ATTR2, S.ATTR4, S.ATTR5, S.ATTR6, sysdate, S.ATTR8,S.ATTR3,? ,S.ATTR17)");
Blob b1 = conn.createBlob ();
byte[]bytes = new byte[10];
for (int i = 0; i < 10; ++i)
bytes[i] = (byte) (2 * i);
b1.setBytes (1, bytes);
statement.setBlob (1, b1);
statement.executeUpdate ();
}
catch (Exception e)
{
System.err.println ("ERROR: " + e.toString ());
}
finally
{
try
{
if (statement != null)
{
statement.close ();
}
}
catch (SQLException e)
{
System.err.println ("SQL ERROR: " + e.toString ());
}
catch (Exception e)
{
System.err.println ("ERROR: " + e.toString ());
}
}
}
}