Skip to content

Commit 2abea77

Browse files
committed
Not de-serialize cached responses until the column field gets accessed.
1 parent b7baf47 commit 2abea77

File tree

1 file changed

+72
-10
lines changed

1 file changed

+72
-10
lines changed

wrapper/src/main/java/software/amazon/jdbc/plugin/cache/CachedResultSet.java

Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package software.amazon.jdbc.plugin.cache;
22

3+
import org.checkerframework.checker.nullness.qual.Nullable;
34
import java.io.ByteArrayInputStream;
45
import java.io.ByteArrayOutputStream;
56
import java.io.InputStream;
67
import java.io.IOException;
78
import java.io.Reader;
8-
import java.io.Serializable;
99
import java.io.ObjectInputStream;
1010
import java.io.ObjectOutputStream;
1111
import java.math.BigDecimal;
@@ -42,23 +42,44 @@
4242

4343
public class CachedResultSet implements ResultSet {
4444

45-
public static class CachedRow implements Serializable {
45+
public static class CachedRow {
4646
private final Object[] rowData;
47+
final byte[] @Nullable [] rawData;
4748

4849
public CachedRow(int numColumns) {
4950
rowData = new Object[numColumns];
51+
rawData = new byte[numColumns][];
5052
}
5153

52-
public void put(final int columnIndex, final Object columnValue) throws SQLException {
54+
private void checkColumnIndex(final int columnIndex) throws SQLException {
5355
if (columnIndex < 1 || columnIndex > rowData.length) {
54-
throw new SQLException("Invalid Column Index when populating CachedRow: " + columnIndex);
56+
throw new SQLException("Invalid Column Index when operating CachedRow: " + columnIndex);
5557
}
58+
}
59+
60+
public void put(final int columnIndex, final Object columnValue) throws SQLException {
61+
checkColumnIndex(columnIndex);
5662
rowData[columnIndex-1] = columnValue;
5763
}
5864

65+
public void putRaw(final int columnIndex, final byte[] rawColumnValue) throws SQLException {
66+
checkColumnIndex(columnIndex);
67+
rawData[columnIndex-1] = rawColumnValue;
68+
}
69+
5970
public Object get(final int columnIndex) throws SQLException {
60-
if (columnIndex < 1 || columnIndex > rowData.length) {
61-
throw new SQLException("Invalid Column Index when getting CachedRow value: " + columnIndex);
71+
checkColumnIndex(columnIndex);
72+
// De-serialize the data object from raw bytes if needed.
73+
if (rowData[columnIndex-1] == null && rawData[columnIndex-1] != null) {
74+
try (ByteArrayInputStream bis = new ByteArrayInputStream(rawData[columnIndex - 1]);
75+
ObjectInputStream ois = new ObjectInputStream(bis)) {
76+
rowData[columnIndex - 1] = ois.readObject();
77+
rawData[columnIndex - 1] = null;
78+
} catch (ClassNotFoundException e) {
79+
throw new SQLException("ClassNotFoundException while de-serializing caching resultSet for column: " + columnIndex, e);
80+
} catch (IOException e) {
81+
throw new SQLException("IOException while de-serializing caching resultSet for column: " + columnIndex, e);
82+
}
6283
}
6384
return rowData[columnIndex - 1];
6485
}
@@ -73,6 +94,12 @@ public Object get(final int columnIndex) throws SQLException {
7394
private final HashMap<String, Integer> columnNames;
7495
private volatile boolean closed;
7596

97+
/**
98+
* Create a CachedResultSet out of the original ResultSet queried from the database.
99+
* @param resultSet The ResultSet queried from the underlying database (not a CachedResultSet).
100+
* @return CachedResultSet that captures the metadata and the rows of the input ResultSet.
101+
* @throws SQLException
102+
*/
76103
public CachedResultSet(final ResultSet resultSet) throws SQLException {
77104
ResultSetMetaData srcMetadata = resultSet.getMetaData();
78105
final int numColumns = srcMetadata.getColumnCount();
@@ -116,14 +143,28 @@ private CachedResultSet(final CachedResultSetMetaData md, final ArrayList<Cached
116143
wasNullFlag = false;
117144
}
118145

146+
// Serialize the content of metadata and data rows for the current CachedResultSet into a byte array
119147
public byte[] serializeIntoByteArray() throws SQLException {
120148
// Serialize the metadata and then the rows
121149
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
122150
ObjectOutputStream output = new ObjectOutputStream(baos)) {
123-
output.writeObject(this.metadata);
151+
output.writeObject(metadata);
124152
output.writeInt(rows.size());
153+
int numColumns = metadata.getColumnCount();
125154
while (this.next()) {
126-
output.writeObject(rows.get(currentRow));
155+
// serialize individual column fields in each row
156+
CachedRow row = rows.get(currentRow);
157+
for (int i = 0; i < numColumns; i++) {
158+
try (ByteArrayOutputStream objBytes = new ByteArrayOutputStream();
159+
ObjectOutputStream objStream = new ObjectOutputStream(objBytes)) {
160+
objStream.writeObject(row.get(i + 1));
161+
objStream.flush();
162+
byte[] dataByteArray = objBytes.toByteArray();
163+
int serializedLength = dataByteArray.length;
164+
output.writeInt(serializedLength);
165+
output.write(dataByteArray, 0, serializedLength);
166+
}
167+
}
127168
}
128169
output.flush();
129170
return baos.toByteArray();
@@ -132,13 +173,34 @@ public byte[] serializeIntoByteArray() throws SQLException {
132173
}
133174
}
134175

176+
/**
177+
* Form a ResultSet from the raw data from the cache server. Each of the column objects are stored as
178+
* raw bytes and the actual de-serialization into Java objects will happen lazily upon access later on.
179+
*/
135180
public static ResultSet deserializeFromByteArray(byte[] data) throws SQLException {
136-
try (ByteArrayInputStream bis = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bis)) {
181+
try (ByteArrayInputStream bis = new ByteArrayInputStream(data);
182+
ObjectInputStream ois = new ObjectInputStream(bis)) {
137183
CachedResultSetMetaData metadata = (CachedResultSetMetaData) ois.readObject();
138184
int numRows = ois.readInt();
185+
int numColumns = metadata.getColumnCount();
139186
ArrayList<CachedRow> resultRows = new ArrayList<>(numRows);
140187
for (int i = 0; i < numRows; i++) {
141-
resultRows.add((CachedRow) ois.readObject());
188+
// Store the raw bytes for each column object in CachedRow
189+
final CachedRow row = new CachedRow(numColumns);
190+
for(int j = 0; j < numColumns; j++) {
191+
int nextObjSize = ois.readInt(); // The size of the next serialized object in its raw bytes form
192+
byte[] objData = new byte[nextObjSize];
193+
int lengthRead = 0;
194+
while (lengthRead < nextObjSize) {
195+
int bytesRead = ois.read(objData, lengthRead, nextObjSize-lengthRead);
196+
if (bytesRead == -1) {
197+
throw new SQLException("End of stream reached when reading the data for CachedResultSet");
198+
}
199+
lengthRead += bytesRead;
200+
}
201+
row.putRaw(j+1, objData);
202+
}
203+
resultRows.add(row);
142204
}
143205
return new CachedResultSet(metadata, resultRows);
144206
} catch (ClassNotFoundException e) {

0 commit comments

Comments
 (0)