Skip to content

Commit 85b7db6

Browse files
committed
Support getSQLXML() for CachedResultSet. Update CacheConnection for better error logging.
1 parent ce0e904 commit 85b7db6

File tree

5 files changed

+374
-11
lines changed

5 files changed

+374
-11
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ public class CacheConnection {
3535
private final String cacheRoServerAddr; // read-only cache server
3636
private MessageDigest msgHashDigest = null;
3737

38-
private static final int DEFAULT_POOL_SIZE = 10;
39-
private static final int DEFAULT_POOL_MAX_IDLE = 10;
38+
private static final int DEFAULT_POOL_SIZE = 20;
39+
private static final int DEFAULT_POOL_MAX_IDLE = 20;
4040
private static final int DEFAULT_POOL_MIN_IDLE = 0;
41-
private static final long DEFAULT_MAX_BORROW_WAIT_MS = 50;
41+
private static final long DEFAULT_MAX_BORROW_WAIT_MS = 100;
4242

4343
private static final ReentrantLock READ_LOCK = new ReentrantLock();
4444
private static final ReentrantLock WRITE_LOCK = new ReentrantLock();
@@ -233,7 +233,7 @@ public void writeToCache(String key, byte[] value, int expiry) {
233233
.whenComplete((result, exception) -> handleCompletedCacheWrite(finalConn, exception));
234234
} catch (Exception e) {
235235
// Failed to trigger the async write to the cache, return the cache connection to the pool as broken
236-
LOGGER.warning("Failed to write to cache: " + e.getMessage());
236+
LOGGER.warning("Unable to start writing to cache: " + e.getMessage());
237237
if (conn != null && writeConnectionPool != null) {
238238
try {
239239
returnConnectionBackToPool(conn, true, false);

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,12 @@ public CachedResultSet(final ResultSet resultSet) throws SQLException {
8989
while (resultSet.next()) {
9090
final CachedRow row = new CachedRow(numColumns);
9191
for (int i = 1; i <= numColumns; ++i) {
92-
row.put(i, resultSet.getObject(i));
92+
Object rowObj = resultSet.getObject(i);
93+
// For SQLXML object, convert into CachedSQLXML object that is serializable
94+
if (rowObj instanceof SQLXML) {
95+
rowObj = new CachedSQLXML(((SQLXML)rowObj).getString());
96+
}
97+
row.put(i, rowObj);
9398
}
9499
rows.add(row);
95100
}
@@ -1143,12 +1148,15 @@ public NClob getNClob(final String columnLabel) throws SQLException {
11431148

11441149
@Override
11451150
public SQLXML getSQLXML(final int columnIndex) throws SQLException {
1146-
throw new UnsupportedOperationException();
1151+
Object val = checkAndGetColumnValue(columnIndex);
1152+
if (val == null) return null;
1153+
if (val instanceof SQLXML) return (SQLXML) val;
1154+
return new CachedSQLXML(val.toString());
11471155
}
11481156

11491157
@Override
11501158
public SQLXML getSQLXML(final String columnLabel) throws SQLException {
1151-
throw new UnsupportedOperationException();
1159+
return getSQLXML(checkAndGetColumnIndex(columnLabel));
11521160
}
11531161

11541162
@Override
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package software.amazon.jdbc.plugin.cache;
2+
3+
import org.xml.sax.InputSource;
4+
import org.xml.sax.XMLReader;
5+
import org.xml.sax.helpers.XMLReaderFactory;
6+
import javax.xml.parsers.DocumentBuilder;
7+
import javax.xml.parsers.DocumentBuilderFactory;
8+
import javax.xml.stream.XMLInputFactory;
9+
import javax.xml.stream.XMLStreamReader;
10+
import javax.xml.transform.Result;
11+
import javax.xml.transform.Source;
12+
import javax.xml.transform.dom.DOMSource;
13+
import javax.xml.transform.sax.SAXSource;
14+
import javax.xml.transform.stax.StAXSource;
15+
import javax.xml.transform.stream.StreamSource;
16+
import java.io.ByteArrayInputStream;
17+
import java.io.InputStream;
18+
import java.io.OutputStream;
19+
import java.io.Reader;
20+
import java.io.Serializable;
21+
import java.io.StringReader;
22+
import java.io.Writer;
23+
import java.nio.charset.StandardCharsets;
24+
import java.sql.SQLException;
25+
import java.sql.SQLXML;
26+
27+
public class CachedSQLXML implements SQLXML, Serializable {
28+
private boolean freed;
29+
private String data;
30+
31+
public CachedSQLXML(String data) {
32+
this.data = data;
33+
this.freed = false;
34+
}
35+
36+
@Override
37+
public void free() throws SQLException {
38+
if (this.freed) return;
39+
this.data = null;
40+
this.freed = true;
41+
}
42+
43+
private void checkFreed() throws SQLException {
44+
if (this.freed) {
45+
throw new SQLException("This SQLXML object has already been freed.");
46+
}
47+
}
48+
49+
@Override
50+
public InputStream getBinaryStream() throws SQLException {
51+
checkFreed();
52+
if (this.data == null) return null;
53+
return new ByteArrayInputStream(this.data.getBytes(StandardCharsets.UTF_8));
54+
}
55+
56+
@Override
57+
public OutputStream setBinaryStream() throws SQLException {
58+
throw new UnsupportedOperationException();
59+
}
60+
61+
@Override
62+
public Reader getCharacterStream() throws SQLException {
63+
checkFreed();
64+
if (this.data == null) return null;
65+
return new StringReader(this.data);
66+
}
67+
68+
@Override
69+
public Writer setCharacterStream() throws SQLException {
70+
throw new UnsupportedOperationException();
71+
}
72+
73+
@Override
74+
public String getString() throws SQLException {
75+
checkFreed();
76+
return this.data;
77+
}
78+
79+
@Override
80+
public void setString(String value) throws SQLException {
81+
throw new UnsupportedOperationException();
82+
}
83+
84+
@Override
85+
public <T extends Source> T getSource(Class<T> sourceClass) throws SQLException {
86+
checkFreed();
87+
if (this.data == null) return null;
88+
89+
try {
90+
if (sourceClass == null || DOMSource.class.equals(sourceClass)) {
91+
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
92+
return (T) new DOMSource(builder.parse(new InputSource(new StringReader(data))));
93+
}
94+
95+
if (SAXSource.class.equals(sourceClass)) {
96+
XMLReader reader = XMLReaderFactory.createXMLReader();
97+
return sourceClass.cast(new SAXSource(reader, new InputSource(new StringReader(data))));
98+
}
99+
100+
if (StreamSource.class.equals(sourceClass)) {
101+
return sourceClass.cast(new StreamSource(new StringReader(data)));
102+
}
103+
104+
if (StAXSource.class.equals(sourceClass)) {
105+
XMLStreamReader xsr = XMLInputFactory.newFactory().createXMLStreamReader(new StringReader(data));
106+
return sourceClass.cast(new StAXSource(xsr));
107+
}
108+
throw new SQLException("Unsupported source class for XML data: " + sourceClass.getName());
109+
} catch (Exception e) {
110+
throw new SQLException("Unable to decode XML data.", e);
111+
}
112+
}
113+
114+
@Override
115+
public <T extends Result> T setResult(Class<T> resultClass) throws SQLException {
116+
throw new UnsupportedOperationException();
117+
}
118+
}

wrapper/src/test/java/software/amazon/jdbc/plugin/cache/CachedResultSetTest.java

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public class CachedResultSetTest {
2323
@Mock ResultSet mockResultSet;
2424
@Mock ResultSetMetaData mockResultSetMetadata;
2525
private AutoCloseable closeable;
26-
private static Calendar estCal = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
27-
private TimeZone defaultTimeZone = TimeZone.getDefault();
26+
private static final Calendar estCal = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
27+
private final TimeZone defaultTimeZone = TimeZone.getDefault();
2828

2929
// Column values: label, name, typeName, type, displaySize, precision, tableName,
3030
// scale, schemaName, isAutoIncrement, isCaseSensitive, isCurrency, isDefinitelyWritable,
@@ -42,7 +42,8 @@ public class CachedResultSetTest {
4242
{"fieldBigDecimal", "fieldBigDecimal", "BigDecimal", Types.DECIMAL, 10, 2, "table", 1, "public", false, false, false, false, 0, true, true, false, false},
4343
{"fieldDate", "fieldDate", "Date", Types.DATE, 10, 2, "table", 1, "public", false, false, false, false, 1, true, true, false, false},
4444
{"fieldTime", "fieldTime", "Time", Types.TIME, 10, 2, "table", 1, "public", false, false, false, false, 1, true, true, false, false},
45-
{"fieldDateTime", "fieldDateTime", "Timestamp", Types.TIMESTAMP, 10, 2, "table", 1, "public", false, false, false, false, 0, true, true, false, false}
45+
{"fieldDateTime", "fieldDateTime", "Timestamp", Types.TIMESTAMP, 10, 2, "table", 1, "public", false, false, false, false, 0, true, true, false, false},
46+
{"fieldSqlXml", "fieldSqlXml", "SqlXml", Types.SQLXML, 100, 1, "table", 1, "public", false, false, false, false, 0, true, true, false, false}
4647
};
4748

4849
private static final Object [][] testColumnValues = {
@@ -58,7 +59,8 @@ public class CachedResultSetTest {
5859
{new BigDecimal("15.33"), new BigDecimal("-12.45")},
5960
{Date.valueOf("2025-03-15"), Date.valueOf("1102-01-15")},
6061
{Time.valueOf("22:54:00"), Time.valueOf("01:10:00")},
61-
{Timestamp.valueOf("2025-03-15 22:54:00"), Timestamp.valueOf("1950-01-18 21:50:05")}
62+
{Timestamp.valueOf("2025-03-15 22:54:00"), Timestamp.valueOf("1950-01-18 21:50:05")},
63+
{new CachedSQLXML("<root><item>A</item></root>"), new CachedSQLXML("<root><element1>Value A</element1><element2>Value B</element2></root>")}
6264
};
6365

6466
private void mockGetMetadataFields(int column, int testMetadataCol) throws SQLException {
@@ -202,6 +204,12 @@ private void verifyDefaultRow(ResultSet rs, int row) throws SQLException {
202204
assertEquals(testColumnValues[12][row], rs.getTimestamp("fieldDateTime"));
203205
assertEquals(13, rs.findColumn("fieldDateTime"));
204206
assertFalse(rs.wasNull());
207+
String sqlXmlString = ((SQLXML)testColumnValues[13][row]).getString();
208+
assertEquals(sqlXmlString, rs.getSQLXML(14).getString()); // fieldSqlXml
209+
assertFalse(rs.wasNull());
210+
assertEquals(sqlXmlString, rs.getSQLXML("fieldSqlXml").getString());
211+
assertEquals(14, rs.findColumn("fieldSqlXml"));
212+
assertFalse(rs.wasNull());
205213
verifyNonexistingField(rs);
206214
}
207215

@@ -672,6 +680,61 @@ void test_get_URL() throws SQLException {
672680
assertTrue(cachedRs.wasNull());
673681
}
674682

683+
@Test
684+
void test_get_sql_xml() throws SQLException {
685+
String longXml =
686+
"<product>\n" +
687+
" <manufacturer>TechCorp</manufacturer>\n" +
688+
"<specs>\n" +
689+
" <cpu>Intel i7</cpu>\n" +
690+
" <ram>16GB</ram>\n" +
691+
" <storage>512GB SSD</storage>\n" +
692+
"</specs>\n" +
693+
" <price>1200.00</price>\n" +
694+
"</product>";
695+
SQLXML testXml = new CachedSQLXML("<book><title>PostgreSQL Guide</title><author>John Doe</author></book>");
696+
SQLXML testXml2 = new CachedSQLXML(longXml);
697+
SQLXML invalidXml = new CachedSQLXML("<root>A<blah>");
698+
// Setup single column with string metadata (URLs stored as strings)
699+
when(mockResultSet.getMetaData()).thenReturn(mockResultSetMetadata);
700+
when(mockResultSetMetadata.getColumnCount()).thenReturn(1);
701+
mockGetMetadataFields(1, 13);
702+
when(mockResultSet.getObject(1)).thenReturn(testXml, testXml2, invalidXml, "invalid-xml", null);
703+
when(mockResultSet.next()).thenReturn(true, true, true, true, true, false);
704+
705+
CachedResultSet cachedRs = new CachedResultSet(mockResultSet);
706+
707+
// Test actual SQLXML objects - both index and label versions
708+
assertTrue(cachedRs.next());
709+
assertEquals(testXml.getString(), cachedRs.getSQLXML(1).getString());
710+
assertFalse(cachedRs.wasNull());
711+
assertEquals(testXml.getString(), cachedRs.getSQLXML("fieldSqlXml").getString());
712+
713+
assertTrue(cachedRs.next());
714+
assertEquals(testXml2.getString(), cachedRs.getSQLXML(1).getString());
715+
assertFalse(cachedRs.wasNull());
716+
assertEquals(testXml2.getString(), cachedRs.getSQLXML("fieldSqlXml").getString());
717+
718+
assertTrue(cachedRs.next());
719+
assertEquals(invalidXml.getString(), cachedRs.getSQLXML(1).getString());
720+
assertFalse(cachedRs.wasNull());
721+
assertEquals(invalidXml.getString(), cachedRs.getSQLXML("fieldSqlXml").getString());
722+
723+
assertTrue(cachedRs.next());
724+
assertEquals("invalid-xml", cachedRs.getSQLXML(1).getString());
725+
assertEquals("invalid-xml", cachedRs.getSQLXML("fieldSqlXml").getString());
726+
assertFalse(cachedRs.wasNull());
727+
728+
assertTrue(cachedRs.next());
729+
assertNull(cachedRs.getSQLXML(1));
730+
assertTrue(cachedRs.wasNull());
731+
assertNull(cachedRs.getSQLXML("fieldSqlXml"));
732+
assertTrue(cachedRs.wasNull());
733+
734+
assertFalse(cachedRs.next());
735+
}
736+
737+
675738
@Test
676739
void test_get_object_with_index_and_type() throws SQLException {
677740
// Setup single column with string metadata (mixed data types)

0 commit comments

Comments
 (0)