removed UI and added bound variable inspection

This commit is contained in:
Sander Hautvast 2018-07-10 21:57:10 +02:00
parent 14e759f7fb
commit 78f8d406f4
17 changed files with 193 additions and 423 deletions

View file

@ -19,10 +19,9 @@
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testapp/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testapp/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />

View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>perfix</groupId>
<artifactId>agent</artifactId>
<artifactId>perfix-agent</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>

View file

@ -40,11 +40,13 @@ public class Registry {
});
return sortedByTotal;
}
//work in progress
// public static Map<String, Set<Report>> getCallStack() {
// callstack.forEach((name,children)->{
//
// });
// }
//work in progress
public static Map<String, Set<Report>> getCallStack() {
callstack.forEach((name, children) -> {
});
return null;
}
}

View file

@ -7,7 +7,9 @@ import javassist.NotFoundException;
import perfix.MutableBoolean;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.List;
import static java.util.Arrays.stream;
@ -30,9 +32,14 @@ public class ClassInstrumentor extends Instrumentor {
}
}
@Override
public void instrumentCode(Instrumentation inst) {
inst.addTransformer((classLoader, resource, aClass, protectionDomain, uninstrumentedByteCode)
-> instrumentCodeForClass(includes, resource, uninstrumentedByteCode));
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
return instrumentCodeForClass(includes, className, classfileBuffer);
}
});
}
private byte[] instrumentCodeForClass(List<String> includes, String resource, byte[] uninstrumentedByteCode) {
@ -40,10 +47,10 @@ public class ClassInstrumentor extends Instrumentor {
try {
CtClass ctClass = getCtClassForResource(resource);
if (servletInstrumentor.isServletImpl(resource)){
if (servletInstrumentor.isServletImpl(resource)) {
return servletInstrumentor.instrumentServlet(ctClass, uninstrumentedByteCode);
}
if (jdbcInstrumentor.isJdbcStatementImpl(resource, ctClass)) {
return jdbcInstrumentor.instrumentJdbcStatement(ctClass, uninstrumentedByteCode);
}

View file

@ -8,23 +8,28 @@ import java.util.List;
public abstract class Instrumentor {
static final String JAVA_STRING = "java.lang.String";
static final String JAVA_HASHMAP = "java.util.HashMap";
static final String PERFIX_METHODINVOCATION_CLASS = "perfix.MethodInvocation";
static final String JAVASSIST_FIRST_ARGUMENT_NAME = "$1";
static final String JAVASSIST_RETURNVALUE = "$_";
final ClassPool classpool;
final List<String> includes;
CtClass perfixMethodInvocationClass;
CtClass stringClass;
protected CtClass stringClass;
protected CtClass hashMapClass;
protected CtClass perfixMethodInvocationClass;
Instrumentor(List<String> includes, ClassPool classPool) {
this.includes = includes;
this.classpool = classPool;
try {
perfixMethodInvocationClass = getCtClass(PERFIX_METHODINVOCATION_CLASS);
stringClass = classpool.get(JAVA_STRING);
hashMapClass = classPool.get(JAVA_HASHMAP);
} catch (NotFoundException e) {
e.printStackTrace();
//suppress TODO implement trace
}
}
@ -37,7 +42,8 @@ public abstract class Instrumentor {
return classInstrumentor;
}
public void instrumentCode(Instrumentation inst){}
public void instrumentCode(Instrumentation inst) {
}
void instrumentMethod(CtMethod methodToinstrument) {
instrumentMethod(methodToinstrument, "\"" + methodToinstrument.getLongName() + "\"");

View file

@ -3,7 +3,10 @@ package perfix.instrument;
import javassist.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static java.util.Arrays.stream;
@ -22,10 +25,15 @@ public class JdbcInstrumentor extends Instrumentor {
private static final String PERFIX_SQLSTATEMENT_FIELD = "_perfixSqlStatement";
private static final String PERFIX_SETSQL_METHOD = "setSqlForPerfix";
private static CtClass statementTextClass = null;
JdbcInstrumentor(List<String> includes, ClassPool classPool) {
super(includes, classPool);
try {
statementTextClass = classpool.get("perfix.instrument.StatementText");
} catch (NotFoundException e) {
e.printStackTrace();
}
}
/* Need to enhance interface to be able to set a statement (string) for perfix. */
@ -33,17 +41,20 @@ public class JdbcInstrumentor extends Instrumentor {
try {
preparedStatementInterface.getDeclaredMethod(PERFIX_SETSQL_METHOD);
} catch (NotFoundException e1) {
e1.printStackTrace();
try {
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementInterface);
preparedStatementInterface.addMethod(setSqlForPerfix);
} catch (CannotCompileException e2) {
e2.printStackTrace();
return uninstrumentedByteCode;
}
}
try {
return bytecode(preparedStatementInterface);
} catch (CannotCompileException | IOException e) {
e.printStackTrace();
return uninstrumentedByteCode;
}
}
@ -79,6 +90,7 @@ public class JdbcInstrumentor extends Instrumentor {
return bytecode(classToInstrument);
} catch (NotFoundException | CannotCompileException | IOException e) {
// suppress
e.printStackTrace();
}
return uninstrumentedByteCode;
}
@ -86,24 +98,53 @@ public class JdbcInstrumentor extends Instrumentor {
/* */
byte[] instrumentJdbcPreparedStatementImpl(CtClass preparedStatementClass, byte[] uninstrumentedByteCode) {
try {
addPerfixStatementField(preparedStatementClass);
addPerfixFields(preparedStatementClass);
addPerfixStatementSetter(preparedStatementClass);
// instrument execute to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTE_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD + ".toString()")
);
// instrument executeQuery to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEQUERY_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD + ".toString()")
);
// instrument executeUpdate to record query duration
stream(preparedStatementClass.getDeclaredMethods(JAVASQL_EXECUTEUPDATE_METHOD)).forEach(method ->
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD)
instrumentMethod(method, PERFIX_SQLSTATEMENT_FIELD + ".toString()")
);
getDeclaredMethods(preparedStatementClass, "setString", "setObject", "setDate", "setTime", "setTimestamp")
.forEach(method -> {
try {
method.insertBefore(PERFIX_SQLSTATEMENT_FIELD + ".set($1, \"\'\"+$2+\"\'\");");
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}
);
getDeclaredMethods(preparedStatementClass,
"setInt", "setFloat", "setDouble", "setBoolean", "setLong", "setShort")
.forEach(method -> {
try {
method.insertBefore(PERFIX_SQLSTATEMENT_FIELD + ".set($1, $2);");
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}
);
getDeclaredMethods(preparedStatementClass, "setNull")
.forEach(method -> {
try {
method.insertBefore(PERFIX_SQLSTATEMENT_FIELD + ".set($1, \"[NULL]\");");
} catch (CannotCompileException e) {
throw new RuntimeException(e);
}
}
);
return bytecode(preparedStatementClass);
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
@ -111,6 +152,21 @@ public class JdbcInstrumentor extends Instrumentor {
}
}
private Stream<CtMethod> getDeclaredMethods(CtClass preparedStatementClass, String... methodnames) {
List<CtMethod> methods = new ArrayList<>();
for (String methodname : methodnames) {
try {
methods.addAll(Arrays.asList(preparedStatementClass.getDeclaredMethods(methodname)));
} catch (NotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
return methods.stream();
}
byte[] instrumentJdbcStatement(CtClass classToInstrument, byte[] uninstrumentedByteCode) {
try {
@ -124,14 +180,15 @@ public class JdbcInstrumentor extends Instrumentor {
);
return bytecode(classToInstrument);
} catch (Exception e) {
e.printStackTrace();
return uninstrumentedByteCode;
}
}
private void addPerfixStatementField(CtClass preparedStatementClass) throws CannotCompileException {
private void addPerfixFields(CtClass preparedStatementClass) throws CannotCompileException, NotFoundException {
// add a String field that will contain the statement
CtField perfixSqlField = new CtField(stringClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
CtField perfixSqlField = new CtField(statementTextClass, PERFIX_SQLSTATEMENT_FIELD, preparedStatementClass);
perfixSqlField.setModifiers(Modifier.PRIVATE);
preparedStatementClass.addField(perfixSqlField);
}
@ -140,7 +197,7 @@ public class JdbcInstrumentor extends Instrumentor {
// add setter for the new field
CtMethod setSqlForPerfix = new CtMethod(CtClass.voidType, PERFIX_SETSQL_METHOD, new CtClass[]{stringClass}, preparedStatementImplClass);
setSqlForPerfix.setModifiers(Modifier.PUBLIC);
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "=" + JAVASSIST_FIRST_ARGUMENT_NAME + ";");
setSqlForPerfix.setBody(PERFIX_SQLSTATEMENT_FIELD + "= new perfix.instrument.StatementText(" + JAVASSIST_FIRST_ARGUMENT_NAME + ");");
preparedStatementImplClass.addMethod(setSqlForPerfix);
}

View file

@ -0,0 +1,67 @@
package perfix.instrument;
public class StatementText {
private static final char BOUNDVAR_MARK = '?';
private final Object[] vars;
private final StringBuilder sql;
public StatementText(String sql) {
vars = new Object[countVars(sql)];
this.sql = new StringBuilder(sql);
}
public void set(int index, Object value) {
if (index < 1 || index > vars.length + 1) {
throw new IndexOutOfBoundsException("" + index);
}
vars[index - 1] = value;
}
public void set(int index, int value){
set(index, Integer.toString(value));
}
public void set(int index, long value){
set(index, Long.toString(value));
}
public void set(int index, double value){
set(index, Double.toString(value));
}
public void set(int index, boolean value){
set(index, Boolean.toString(value));
}
public void set(int index, float value){
set(index, Float.toString(value));
}
public void set(int index, short value){
set(index, Short.toString(value));
}
@Override
public String toString() {
int found = 0;
for (int i = 0; i < sql.length(); i++) {
if (sql.charAt(i) == BOUNDVAR_MARK) {
sql.deleteCharAt(i);
sql.insert(i, vars[found++]);
}
}
return sql.toString();
}
private int countVars(String sql) {
int count = 0;
for (int i = 0; i < sql.length(); i++) {
if (sql.charAt(i) == BOUNDVAR_MARK) {
count++;
}
}
return count;
}
}

View file

@ -33,58 +33,34 @@ public class HTTPServer extends NanoHTTPD {
return perfixMetrics();
case "/callstack":
return perfixCallstack();
default:
return serveStaticContent(uri);
}
}
private Response serveStaticContent(String uri) {
if (uri.equals("/")) {
uri = "/index.html";
}
try {
InputStream stream = getClass().getResourceAsStream(uri);
if (stream == null) {
return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "resource not found");
}
return newFixedLengthResponse(Response.Status.OK, determineContentType(uri), readFile(stream));
} catch (IOException e) {
return newFixedLengthResponse(e.toString());
default: return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "NOT FOUND");
}
}
private Response perfixMetrics() {
try {
return newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values())));
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(new ArrayList<>(Registry.sortedMethodsByDuration().values()))));
} catch (Exception e) {
e.printStackTrace();
return newFixedLengthResponse(e.toString());
}
}
private Response addCors(Response response) {
response.addHeader("Access-Control-Allow-Origin","*");
response.addHeader("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept");
return response;
}
private Response perfixCallstack() {
try {
return newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack()));
return addCors(newFixedLengthResponse(Response.Status.OK, "application/json", Serializer.toJSONString(Registry.getCallStack())));
} catch (Exception e) {
e.printStackTrace();
return newFixedLengthResponse(e.toString());
}
}
private String readFile(InputStream stream) throws IOException {
int nbytes = stream.available();
byte[] bytes = new byte[nbytes];
stream.read(bytes);
return new String(bytes);
}
private String determineContentType(String uri) {
if (uri.endsWith(".js")) {
return "application/javascript";
} else if (uri.endsWith(".css")) {
return "text/css";
} else {
return "text/html";
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Perfix console</title>
<script src="/d3.js"></script>
<script src="/perfix.js"></script>
<link rel="stylesheet" type="text/css" href="/main.css">
</head>
<body class="datagrid">
<table></table>
</body>
<script>
function loadData() {
d3.json("/report").then(function (data) {
// render the table(s)
tabulate(data, ['name', 'invocations', 'totalDuration', 'average']);
});
}
loadData();
</script>
<button type="button" onclick="loadData()">refresh</button>
</html>

View file

@ -1,106 +0,0 @@
.datagrid table {
border-collapse: collapse;
text-align: left;
width: 100%;
}
.datagrid {
font: normal 12px/150% Arial, Helvetica, sans-serif;
background: #fff;
overflow: hidden;
border: 1px solid #006699;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.datagrid table td, .datagrid table th {
padding: 3px 10px;
}
.datagrid table thead th {
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F));
background: -moz-linear-gradient(center top, #006699 5%, #00557F 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#006699', endColorstr='#00557F');
background-color: #006699;
color: #ffffff;
font-size: 15px;
font-weight: bold;
border-left: 1px solid #0070A8;
}
.datagrid table thead th:first-child {
border: none;
}
.datagrid table tbody td {
color: #00496B;
border-left: 1px solid #E1EEF4;
font-size: 12px;
font-weight: normal;
}
.datagrid table tbody .alt td {
background: #E1EEF4;
color: #00496B;
}
.datagrid table tbody td:first-child {
border-left: none;
}
.datagrid table tbody tr:last-child td {
border-bottom: none;
}
.datagrid table tfoot td div {
border-top: 1px solid #006699;
background: #E1EEF4;
}
.datagrid table tfoot td {
padding: 0;
font-size: 12px
}
.datagrid table tfoot td div {
padding: 2px;
}
.datagrid table tfoot td ul {
margin: 0;
padding: 0;
list-style: none;
text-align: right;
}
.datagrid table tfoot li {
display: inline;
}
.datagrid table tfoot li a {
text-decoration: none;
display: inline-block;
padding: 2px 8px;
margin: 1px;
color: #FFFFFF;
border: 1px solid #006699;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
background: -webkit-gradient(linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F));
background: -moz-linear-gradient(center top, #006699 5%, #00557F 100%);
background-color: #006699;
}
.datagrid table tfoot ul.active, .datagrid table tfoot ul a:hover {
text-decoration: none;
border-color: #006699;
color: #FFFFFF;
background: none;
background-color: #00557F;
}
div.dhtmlx_window_active, div.dhx_modal_cover_dv {
position: fixed !important;
}

View file

@ -1,29 +0,0 @@
function tabulate(data, columns) {
d3.select('table').remove();
var table = d3.select('body').append('table')
var thead = table.append('thead')
var tbody = table.append('tbody');
thead.append('tr')
.selectAll('th')
.data(columns).enter()
.append('th')
.text(function (column) { return column; });
var rows = tbody.selectAll('tr')
.data(data)
.enter()
.append('tr');
rows.selectAll('td')
.data(function (row) {
return columns.map(function (column) {
return {column: column, value: row[column]};
});
})
.enter()
.append('td')
.text(function (d) { return d.value; });
return table;
}

View file

@ -1,44 +0,0 @@
[
{
"name": "testperfix.Main.main(java.lang.String[])",
"invocations": 1,
"totalDuration": 1414878875,
"average": 1.414878875E9
},
{
"name": "testperfix.Main.run()",
"invocations": 1,
"totalDuration": 1414756159,
"average": 1.414756159E9
},
{
"name": "testperfix.Main.someJdbcStatentMethod()",
"invocations": 1,
"totalDuration": 399009103,
"average": 3.99009103E8
},
{
"name": "select CURRENT_DATE() -- simple statement",
"invocations": 1,
"totalDuration": 99705675,
"average": 9.9705675E7
},
{
"name": "testperfix.Main.someJdbcPreparedStatementMethod()",
"invocations": 1,
"totalDuration": 5880308,
"average": 5880308.0
},
{
"name": "testperfix.Main.someOtherMethod()",
"invocations": 1,
"totalDuration": 1285359,
"average": 1285359.0
},
{
"name": "select CURRENT_DATE() -- prepared statement",
"invocations": 1,
"totalDuration": 98241,
"average": 98241.0
}
]

View file

@ -1,141 +0,0 @@
var m = [20, 120, 20, 120],
w = 1280 - m[1] - m[3],
h = 800 - m[0] - m[2],
i = 0,
root;
var tree = d3.layout.tree()
.size([h, w]);
var diagonal = d3.svg.diagonal()
.projection(function(d) { return [d.y, d.x]; });
var vis = d3.select("#body").append("svg:svg")
.attr("width", w + m[1] + m[3])
.attr("height", h + m[0] + m[2])
.append("svg:g")
.attr("transform", "translate(" + m[3] + "," + m[0] + ")");
d3.json("flare.json", function(json) {
root = json;
root.x0 = h / 2;
root.y0 = 0;
function toggleAll(d) {
if (d.children) {
d.children.forEach(toggleAll);
toggle(d);
}
}
// Initialize the display to show a few nodes.
root.children.forEach(toggleAll);
toggle(root.children[1]);
toggle(root.children[1].children[2]);
toggle(root.children[9]);
toggle(root.children[9].children[0]);
update(root);
});
function update(source) {
var duration = d3.event && d3.event.altKey ? 5000 : 500;
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse();
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 180; });
// Update the nodes…
var node = vis.selectAll("g.node")
.data(nodes, function(d) { return d.id || (d.id = ++i); });
// Enter any new nodes at the parent's previous position.
var nodeEnter = node.enter().append("svg:g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", function(d) { toggle(d); update(d); });
nodeEnter.append("svg:circle")
.attr("r", 1e-6)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("svg:text")
.attr("x", function(d) { return d.children || d._children ? -10 : 10; })
.attr("dy", ".35em")
.attr("text-anchor", function(d) { return d.children || d._children ? "end" : "start"; })
.text(function(d) { return d.name; })
.style("fill-opacity", 1e-6);
// Transition nodes to their new position.
var nodeUpdate = node.transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
nodeUpdate.select("circle")
.attr("r", 4.5)
.style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeUpdate.select("text")
.style("fill-opacity", 1);
// Transition exiting nodes to the parent's new position.
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) { return "translate(" + source.y + "," + source.x + ")"; })
.remove();
nodeExit.select("circle")
.attr("r", 1e-6);
nodeExit.select("text")
.style("fill-opacity", 1e-6);
// Update the links…
var link = vis.selectAll("path.link")
.data(tree.links(nodes), function(d) { return d.target.id; });
// Enter any new links at the parent's previous position.
link.enter().insert("svg:path", "g")
.attr("class", "link")
.attr("d", function(d) {
var o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
})
.transition()
.duration(duration)
.attr("d", diagonal);
// Transition links to their new position.
link.transition()
.duration(duration)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition()
.duration(duration)
.attr("d", function(d) {
var o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
})
.remove();
// Stash the old positions for transition.
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Toggle children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}

View file

@ -0,0 +1,14 @@
import org.junit.Assert;
import org.junit.Test;
import perfix.instrument.StatementText;
public class StatementTextTest {
@Test
public void testReplace(){
StatementText b = new StatementText("select ? from ?");
b.set(1, "alpha");
b.set(2, "beta");
Assert.assertEquals("select alpha from beta",b.toString());
}
}

View file

@ -1,16 +0,0 @@
import org.junit.Test;
import perfix.MethodInvocation;
import perfix.Registry;
public class TestMethod {
@Test
public void testAddMethodToRegistry() {
MethodInvocation method = MethodInvocation.start("somename");
method.stop();
MethodInvocation method2 = MethodInvocation.start("somename");
method2.stop();
Registry.report(System.out);
}
}

View file

@ -42,8 +42,11 @@ public class App {
private static void someJdbcPreparedStatementMethod() {
try {
Connection connection = DriverManager.getConnection("jdbc:h2:mem:default", "sa", "");
PreparedStatement preparedStatement = connection.prepareStatement("select CURRENT_DATE() -- prepared statement");
Statement statement = connection.createStatement();
statement.execute("create table t (v varchar(2))");
statement.close();
PreparedStatement preparedStatement = connection.prepareStatement("select * from t where v=? -- prepared statement");
preparedStatement.setDate(1, new Date(new java.util.Date().getTime()));
preparedStatement.executeQuery();
connection.close();
} catch (SQLException e) {