Skip to content

Commit f58ff57

Browse files
Fixes out of bound issue when checking column name
1 parent 7f8c2ff commit f58ff57

File tree

5 files changed

+98
-28
lines changed

5 files changed

+98
-28
lines changed

src/main/java/com/qwazr/jdbc/cache/CachedPreparedStatement.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,13 @@
2626

2727
class CachedPreparedStatement<T extends PreparedStatement> extends CachedStatement<T> implements PreparedStatement {
2828

29-
private final String sql;
3029
protected final SortedMap<Integer, Object> parameters;
3130

3231
CachedPreparedStatement(final CachedConnection connection, final T backendStatement, final String sql,
3332
final int resultSetConcurrency, final int resultSetType, final int resultSetHoldability) {
3433
super(connection, backendStatement, resultSetConcurrency, resultSetType, resultSetHoldability);
3534
this.parameters = new TreeMap<>();
36-
this.sql = sql;
35+
this.executedSql = sql;
3736
}
3837

3938
CachedPreparedStatement(final CachedConnection connection, final T backendStatement, final String sql) {
@@ -42,7 +41,7 @@ class CachedPreparedStatement<T extends PreparedStatement> extends CachedStateme
4241

4342
@Override
4443
public ResultSet executeQuery() throws SQLException {
45-
final String cacheKey = ResultSetCache.getKey(sql, parameters);
44+
final String cacheKey = ResultSetCache.getKey(executedSql, parameters);
4645
return connection.resultSetCache.get(this, cacheKey, () -> backendStatement.executeQuery());
4746
}
4847

@@ -54,6 +53,13 @@ public int executeUpdate() throws SQLException {
5453
throw new SQLFeatureNotSupportedException();
5554
}
5655

56+
@Override
57+
public ResultSet getResultSet() throws SQLException {
58+
final String cacheKey = ResultSetCache.getKey(executedSql, parameters);
59+
return connection.resultSetCache
60+
.get(this, cacheKey, backendStatement == null ? null : backendStatement::getResultSet);
61+
}
62+
5763
@Override
5864
public void setNull(int parameterIndex, int sqlType) throws SQLException {
5965
if (backendStatement != null)
@@ -203,7 +209,7 @@ public boolean execute() throws SQLException {
203209
if (backendStatement != null)
204210
return backendStatement.execute();
205211
else
206-
throw new SQLFeatureNotSupportedException();
212+
return connection.resultSetCache.checkExists(ResultSetCache.getKey(executedSql, parameters));
207213
}
208214

209215
@Override

src/main/java/com/qwazr/jdbc/cache/CachedResultSet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class CachedResultSet implements ResultSet {
5757
this.columnNames = new HashMap<>();
5858
int i = 0;
5959
for (ResultSetWriter.ColumnDef column : metaData.columns)
60-
this.columnNames.put(column.label, i++);
60+
this.columnNames.put(column.label, ++i);
6161
readNext();
6262
} catch (IOException e) {
6363
try {

src/main/java/com/qwazr/jdbc/cache/CachedStatement.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ class CachedStatement<T extends Statement> implements Statement {
2929
private volatile int maxFieldSize;
3030
private volatile int maxRows;
3131
private volatile int queryTimeOut;
32-
private volatile CachedResultSet resultSet;
3332
private volatile int fetchDirection;
3433
private volatile int fetchSize;
3534
private volatile boolean closed;
3635
private volatile boolean poolable;
3736
private volatile boolean closeOnCompletion;
37+
protected volatile String executedSql;
3838

3939
CachedStatement(final CachedConnection connection, final T backendStatement, final int resultSetConcurrency,
4040
final int resultSetType, final int resultSetHoldability) {
@@ -46,12 +46,12 @@ class CachedStatement<T extends Statement> implements Statement {
4646
this.maxFieldSize = 0;
4747
this.maxRows = 0;
4848
this.queryTimeOut = 0;
49-
this.resultSet = null;
5049
this.fetchDirection = 0;
5150
this.fetchSize = 0;
5251
this.closed = false;
5352
this.poolable = false;
5453
this.closeOnCompletion = false;
54+
this.executedSql = null;
5555
}
5656

5757
CachedStatement(final CachedConnection connection, final T backendStatement) {
@@ -60,12 +60,15 @@ class CachedStatement<T extends Statement> implements Statement {
6060

6161
@Override
6262
public ResultSet executeQuery(String sql) throws SQLException {
63+
this.executedSql = sql;
6364
final String cacheKey = ResultSetCache.getKey(sql);
64-
return connection.resultSetCache.get(this, cacheKey, () -> backendStatement.executeQuery(sql));
65+
return connection.resultSetCache
66+
.get(this, cacheKey, backendStatement == null ? null : () -> backendStatement.executeQuery(sql));
6567
}
6668

6769
@Override
6870
public int executeUpdate(String sql) throws SQLException {
71+
this.executedSql = sql;
6972
if (backendStatement != null)
7073
return backendStatement.executeUpdate(sql);
7174
else
@@ -158,15 +161,18 @@ public void setCursorName(String name) throws SQLException {
158161

159162
@Override
160163
public boolean execute(String sql) throws SQLException {
164+
this.executedSql = sql;
161165
if (backendStatement != null)
162166
return backendStatement.execute(sql);
163167
else
164-
throw new SQLFeatureNotSupportedException();
168+
return connection.resultSetCache.checkExists(ResultSetCache.getKey(executedSql));
165169
}
166170

167171
@Override
168172
public ResultSet getResultSet() throws SQLException {
169-
return resultSet;
173+
final String cacheKey = ResultSetCache.getKey(executedSql);
174+
return connection.resultSetCache
175+
.get(this, cacheKey, backendStatement == null ? null : backendStatement::getResultSet);
170176
}
171177

172178
@Override
@@ -272,6 +278,7 @@ public ResultSet getGeneratedKeys() throws SQLException {
272278

273279
@Override
274280
public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
281+
executedSql = sql;
275282
if (backendStatement != null)
276283
return backendStatement.executeUpdate(sql, autoGeneratedKeys);
277284
else
@@ -280,6 +287,7 @@ public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException
280287

281288
@Override
282289
public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
290+
executedSql = sql;
283291
if (backendStatement != null)
284292
return backendStatement.executeUpdate(sql, columnIndexes);
285293
else
@@ -288,6 +296,7 @@ public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
288296

289297
@Override
290298
public int executeUpdate(String sql, String[] columnNames) throws SQLException {
299+
executedSql = sql;
291300
if (backendStatement != null)
292301
return backendStatement.executeUpdate(sql, columnNames);
293302
else
@@ -296,26 +305,29 @@ public int executeUpdate(String sql, String[] columnNames) throws SQLException {
296305

297306
@Override
298307
public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
308+
executedSql = sql;
299309
if (backendStatement != null)
300310
return backendStatement.execute(sql, autoGeneratedKeys);
301311
else
302-
throw new SQLFeatureNotSupportedException();
312+
return connection.resultSetCache.checkExists(ResultSetCache.getKey(executedSql));
303313
}
304314

305315
@Override
306316
public boolean execute(String sql, int[] columnIndexes) throws SQLException {
317+
executedSql = sql;
307318
if (backendStatement != null)
308319
return backendStatement.execute(sql, columnIndexes);
309320
else
310-
throw new SQLFeatureNotSupportedException();
321+
return connection.resultSetCache.checkExists(ResultSetCache.getKey(executedSql));
311322
}
312323

313324
@Override
314325
public boolean execute(String sql, String[] columnNames) throws SQLException {
326+
executedSql = sql;
315327
if (backendStatement != null)
316328
return backendStatement.execute(sql, columnNames);
317329
else
318-
throw new SQLFeatureNotSupportedException();
330+
return connection.resultSetCache.checkExists(ResultSetCache.getKey(executedSql));
319331
}
320332

321333
@Override

src/main/java/com/qwazr/jdbc/cache/ResultSetCache.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,44 @@ class ResultSetCache {
4343
this.cacheDirectory = cacheDirectory;
4444
}
4545

46-
CachedResultSet get(final CachedStatement statement, final String key, final Provider resultSetProvider)
46+
/**
47+
* Return the cached ResultSet for the given key.
48+
* If the entry does not exist the ResultSet is extracted by calling the given resultSetProvider.
49+
* If the entry does not exist and no resultSetProvider is given (null) an SQLException is thrown.
50+
*
51+
* @param statement the cached statement
52+
* @param key the generated key for this statement
53+
* @param resultSetProvider the optional result provider
54+
* @return the cached ResultSet
55+
* @throws SQLException
56+
*/
57+
final CachedResultSet get(final CachedStatement statement, final String key, final Provider resultSetProvider)
4758
throws SQLException {
4859
final Path resultSetPath = cacheDirectory.resolve(key);
4960
final ResultSet providedResultSet;
5061
if (!Files.exists(resultSetPath)) {
62+
if (resultSetProvider == null)
63+
throw new SQLException("No cache available");
5164
providedResultSet = resultSetProvider.provide();
5265
ResultSetWriter.write(resultSetPath, providedResultSet);
5366
}
5467
return new CachedResultSet(statement, resultSetPath);
5568
}
5669

70+
/**
71+
* Check if an entry is available for this key. If not, an SQLException is thrown.
72+
*
73+
* @param key the computed key
74+
* @return always true
75+
* @throws SQLException
76+
*/
77+
final boolean checkExists(final String key) throws SQLException {
78+
final Path resultSetPath = cacheDirectory.resolve(key);
79+
if (!Files.exists(resultSetPath))
80+
throw new SQLException("No cache available");
81+
return true;
82+
}
83+
5784
interface Provider {
5885

5986
ResultSet provide() throws SQLException;

src/test/java/DerbyTest.java

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -104,33 +104,53 @@ private void checkResultSet(ResultSet resultSet, Object[]... rows) throws SQLExc
104104

105105
final static String SQL_SIMPLE = "SELECT ID,NAME,CREATED FROM FIRSTTABLE";
106106

107+
private ResultSet executeQuery(Connection cnx) throws SQLException {
108+
final Statement stmt = cnx.createStatement();
109+
return stmt.executeQuery(SQL_SIMPLE);
110+
}
111+
107112
@Test
108113
public void test110TestSimpleStatement() throws SQLException {
109114
// First without the cache
110-
checkResultSet(cnxCacheDisable.createStatement().executeQuery(SQL_SIMPLE), ROWS);
115+
checkResultSet(executeQuery(cnxCacheDisable), ROWS);
111116
// Second the cache is written
112-
checkResultSet(cnxCacheEnable.createStatement().executeQuery(SQL_SIMPLE), ROWS);
117+
checkResultSet(executeQuery(cnxCacheEnable), ROWS);
113118
// Third the cache is read
114-
checkResultSet(cnxCacheEnable.createStatement().executeQuery(SQL_SIMPLE), ROWS);
119+
checkResultSet(executeQuery(cnxCacheEnable), ROWS);
115120
// Last, without the backend
116-
checkResultSet(cnxCacheNoBackend.createStatement().executeQuery(SQL_SIMPLE), ROWS);
121+
checkResultSet(executeQuery(cnxCacheNoBackend), ROWS);
117122
}
118123

119-
final static String SQL_PREP = "SELECT ID,NAME,CREATED FROM FIRSTTABLE WHERE ID = ? OR ID = ?";
124+
private ResultSet updateGetResultSet(Connection cnx) throws SQLException {
125+
final Statement stmt = cnx.createStatement();
126+
stmt.execute(SQL_SIMPLE);
127+
return stmt.getResultSet();
128+
}
120129

121130
@Test
122-
public void test110TestPreparedStatementWithoutCache() throws SQLException {
123-
final PreparedStatement stmt = cnxCacheDisable.prepareStatement(SQL_PREP);
131+
public void test110TestUpdateAndGetResultSet() throws SQLException {
132+
checkResultSet(updateGetResultSet(cnxCacheDisable), ROWS);
133+
checkResultSet(updateGetResultSet(cnxCacheEnable), ROWS);
134+
checkResultSet(updateGetResultSet(cnxCacheNoBackend), ROWS);
135+
}
136+
137+
final static String SQL_PREP = "SELECT ID,NAME,CREATED FROM FIRSTTABLE WHERE ID = ? OR ID = ?";
138+
139+
private PreparedStatement getPreparedStatement(Connection cnx) throws SQLException {
140+
final PreparedStatement stmt = cnx.prepareStatement(SQL_PREP);
124141
stmt.setInt(1, 10);
125142
stmt.setInt(2, 40);
126-
checkResultSet(stmt.executeQuery(), ROW1, ROW4);
143+
return stmt;
144+
}
145+
146+
@Test
147+
public void test110TestPreparedStatementWithoutCache() throws SQLException {
148+
checkResultSet(getPreparedStatement(cnxCacheDisable).executeQuery(), ROW1, ROW4);
127149
}
128150

129151
@Test
130152
public void test120TestPreparedStatementWithCache() throws SQLException {
131-
final PreparedStatement stmt = cnxCacheEnable.prepareStatement(SQL_PREP);
132-
stmt.setInt(1, 10);
133-
stmt.setInt(2, 40);
153+
final PreparedStatement stmt = getPreparedStatement(cnxCacheEnable);
134154
// First the cache is written
135155
checkResultSet(stmt.executeQuery(), ROW1, ROW4);
136156
// Second the cache is read
@@ -139,10 +159,15 @@ public void test120TestPreparedStatementWithCache() throws SQLException {
139159

140160
@Test
141161
public void test130TestPreparedStatementNoBackend() throws SQLException {
142-
final PreparedStatement stmt = cnxCacheNoBackend.prepareStatement(SQL_PREP);
143-
stmt.setInt(1, 10);
144-
stmt.setInt(2, 40);
162+
final PreparedStatement stmt = getPreparedStatement(cnxCacheNoBackend);
145163
checkResultSet(stmt.executeQuery(), ROW1, ROW4);
146164
}
147165

166+
@Test
167+
public void test135TestPreparedStatementGetResultSet() throws SQLException {
168+
final PreparedStatement stmt = getPreparedStatement(cnxCacheNoBackend);
169+
stmt.execute();
170+
checkResultSet(stmt.getResultSet(), ROW1, ROW4);
171+
}
172+
148173
}

0 commit comments

Comments
 (0)