11package software .amazon .jdbc .plugin .cache ;
22
3+ import org .checkerframework .checker .nullness .qual .Nullable ;
34import java .io .ByteArrayInputStream ;
45import java .io .ByteArrayOutputStream ;
56import java .io .InputStream ;
67import java .io .IOException ;
78import java .io .Reader ;
8- import java .io .Serializable ;
99import java .io .ObjectInputStream ;
1010import java .io .ObjectOutputStream ;
1111import java .math .BigDecimal ;
4242
4343public 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