SQL: test coverage for JdbcResultSet (#32813)

* Tests for JdbcResultSet
* Added VARCHAR conversion for different types
* Made error messages consistent: they now contain both the type that fails to be converted and the value itself
This commit is contained in:
Andrei Stefan 2018-08-31 16:12:01 +03:00 committed by GitHub
parent f6a570880c
commit 0c4b3162be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1621 additions and 108 deletions

View File

@ -133,72 +133,37 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
@Override
public boolean getBoolean(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? (Boolean) val : false;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a boolean", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Boolean.class) : false;
}
@Override
public byte getByte(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? ((Number) val).byteValue() : 0;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a byte", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Byte.class) : 0;
}
@Override
public short getShort(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? ((Number) val).shortValue() : 0;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a short", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Short.class) : 0;
}
@Override
public int getInt(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? ((Number) val).intValue() : 0;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to an int", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Integer.class) : 0;
}
@Override
public long getLong(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? ((Number) val).longValue() : 0;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a long", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Long.class) : 0;
}
@Override
public float getFloat(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? ((Number) val).floatValue() : 0;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a float", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Float.class) : 0;
}
@Override
public double getDouble(int columnIndex) throws SQLException {
Object val = column(columnIndex);
try {
return val != null ? ((Number) val).doubleValue() : 0;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a double", cce);
}
return column(columnIndex) != null ? getObject(columnIndex, Double.class) : 0;
}
@Override
@ -272,15 +237,29 @@ class JdbcResultSet implements ResultSet, JdbcWrapper {
@Override
public Date getDate(String columnLabel) throws SQLException {
// TODO: the error message in case the value in the column cannot be converted to a Date refers to a column index
// (for example - "unable to convert column 4 to a long") and not to the column name, which is a bit confusing.
// Should we reconsider this? Maybe by catching the exception here and rethrowing it with the columnLabel instead.
return getDate(column(columnLabel));
}
private Long dateTime(int columnIndex) throws SQLException {
Object val = column(columnIndex);
JDBCType type = cursor.columns().get(columnIndex - 1).type;
try {
// TODO: the B6 appendix of the jdbc spec does mention CHAR, VARCHAR, LONGVARCHAR, DATE, TIMESTAMP as supported
// jdbc types that should be handled by getDate and getTime methods. From all of those we support VARCHAR and
// TIMESTAMP. Should we consider the VARCHAR conversion as a later enhancement?
if (JDBCType.TIMESTAMP.equals(type)) {
// the cursor can return an Integer if the date-since-epoch is small enough, XContentParser (Jackson) will
// return the "smallest" data type for numbers when parsing
// TODO: this should probably be handled server side
return val == null ? null : ((Number) val).longValue();
};
return val == null ? null : (Long) val;
} catch (ClassCastException cce) {
throw new SQLException("unable to convert column " + columnIndex + " to a long", cce);
throw new SQLException(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Long", val, type.getName()), cce);
}
}

View File

@ -10,7 +10,6 @@ import org.elasticsearch.xpack.sql.type.DataType;
import java.sql.Date;
import java.sql.JDBCType;
import java.sql.SQLDataException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Time;
@ -56,9 +55,10 @@ final class TypeConverter {
}
private static final long DAY_IN_MILLIS = 60 * 60 * 24;
private static final long DAY_IN_MILLIS = 60 * 60 * 24 * 1000;
private static final Map<Class<?>, JDBCType> javaToJDBC;
static {
Map<Class<?>, JDBCType> aMap = Arrays.stream(DataType.values())
.filter(dataType -> dataType.javaClass() != null
@ -120,6 +120,7 @@ final class TypeConverter {
}
}
static long convertFromCalendarToUTC(long value, Calendar cal) {
if (cal == null) {
return value;
@ -143,11 +144,15 @@ final class TypeConverter {
return (T) convert(val, columnType);
}
if (type.isInstance(val)) {
// converting a Long to a Timestamp shouldn't be possible according to the spec,
// it feels a little brittle to check this scenario here and I don't particularly like it
// TODO: can we do any better or should we go over the spec and allow getLong(date) to be valid?
if (!(type == Long.class && columnType == JDBCType.TIMESTAMP) && type.isInstance(val)) {
try {
return type.cast(val);
} catch (ClassCastException cce) {
throw new SQLDataException("Unable to convert " + val.getClass().getName() + " to " + columnType, cce);
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a %s", val,
columnType.getName(), type.getName()), cce);
}
}
@ -205,7 +210,8 @@ final class TypeConverter {
if (type == OffsetDateTime.class) {
return (T) asOffsetDateTime(val, columnType);
}
throw new SQLException("Conversion from type [" + columnType + "] to [" + type.getName() + "] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a %s", val,
columnType.getName(), type.getName()));
}
/**
@ -336,8 +342,11 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return Boolean.valueOf(Integer.signum(((Number) val).intValue()) != 0);
case VARCHAR:
return Boolean.valueOf((String) val);
default:
throw new SQLException("Conversion from type [" + columnType + "] to [Boolean] not supported");
throw new SQLException(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Boolean", val, columnType.getName()));
}
}
@ -355,10 +364,16 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return safeToByte(safeToLong(((Number) val).doubleValue()));
case VARCHAR:
try {
return Byte.valueOf((String) val);
} catch (NumberFormatException e) {
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [VARCHAR] to a Byte", val), e);
}
default:
}
throw new SQLException("Conversion from type [" + columnType + "] to [Byte] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Byte", val, columnType.getName()));
}
private static Short asShort(Object val, JDBCType columnType) throws SQLException {
@ -374,10 +389,16 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return safeToShort(safeToLong(((Number) val).doubleValue()));
case VARCHAR:
try {
return Short.valueOf((String) val);
} catch (NumberFormatException e) {
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [VARCHAR] to a Short", val), e);
}
default:
}
throw new SQLException("Conversion from type [" + columnType + "] to [Short] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Short", val, columnType.getName()));
}
private static Integer asInteger(Object val, JDBCType columnType) throws SQLException {
@ -393,10 +414,18 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return safeToInt(safeToLong(((Number) val).doubleValue()));
case VARCHAR:
try {
return Integer.valueOf((String) val);
} catch (NumberFormatException e) {
throw new SQLException(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [VARCHAR] to an Integer", val), e);
}
default:
}
throw new SQLException("Conversion from type [" + columnType + "] to [Integer] not supported");
throw new SQLException(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to an Integer", val, columnType.getName()));
}
private static Long asLong(Object val, JDBCType columnType) throws SQLException {
@ -412,12 +441,21 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return safeToLong(((Number) val).doubleValue());
case TIMESTAMP:
return ((Number) val).longValue();
//TODO: should we support conversion to TIMESTAMP?
//The spec says that getLong() should support the following types conversions:
//TINYINT, SMALLINT, INTEGER, BIGINT, REAL, FLOAT, DOUBLE, DECIMAL, NUMERIC, BIT, BOOLEAN, CHAR, VARCHAR, LONGVARCHAR
//case TIMESTAMP:
// return ((Number) val).longValue();
case VARCHAR:
try {
return Long.valueOf((String) val);
} catch (NumberFormatException e) {
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [VARCHAR] to a Long", val), e);
}
default:
}
throw new SQLException("Conversion from type [" + columnType + "] to [Long] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Long", val, columnType.getName()));
}
private static Float asFloat(Object val, JDBCType columnType) throws SQLException {
@ -433,10 +471,16 @@ final class TypeConverter {
case FLOAT:
case DOUBLE:
return Float.valueOf((((float) ((Number) val).doubleValue())));
case VARCHAR:
try {
return Float.valueOf((String) val);
} catch (NumberFormatException e) {
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [VARCHAR] to a Float", val), e);
}
default:
}
throw new SQLException("Conversion from type [" + columnType + "] to [Float] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Float", val, columnType.getName()));
}
private static Double asDouble(Object val, JDBCType columnType) throws SQLException {
@ -451,32 +495,41 @@ final class TypeConverter {
case REAL:
case FLOAT:
case DOUBLE:
return Double.valueOf(((Number) val).doubleValue());
case VARCHAR:
try {
return Double.valueOf((String) val);
} catch (NumberFormatException e) {
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [VARCHAR] to a Double", val), e);
}
default:
}
throw new SQLException("Conversion from type [" + columnType + "] to [Double] not supported");
throw new SQLException(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Double", val, columnType.getName()));
}
private static Date asDate(Object val, JDBCType columnType) throws SQLException {
if (columnType == JDBCType.TIMESTAMP) {
return new Date(utcMillisRemoveTime(((Number) val).longValue()));
}
throw new SQLException("Conversion from type [" + columnType + "] to [Date] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Date", val, columnType.getName()));
}
private static Time asTime(Object val, JDBCType columnType) throws SQLException {
if (columnType == JDBCType.TIMESTAMP) {
return new Time(utcMillisRemoveDate(((Number) val).longValue()));
}
throw new SQLException("Conversion from type [" + columnType + "] to [Time] not supported");
throw new SQLException(format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Time", val, columnType.getName()));
}
private static Timestamp asTimestamp(Object val, JDBCType columnType) throws SQLException {
if (columnType == JDBCType.TIMESTAMP) {
return new Timestamp(((Number) val).longValue());
}
throw new SQLException("Conversion from type [" + columnType + "] to [Timestamp] not supported");
throw new SQLException(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [%s] to a Timestamp", val, columnType.getName()));
}
private static byte[] asByteArray(Object val, JDBCType columnType) {

View File

@ -25,6 +25,7 @@ import java.util.Date;
import java.util.Locale;
import java.util.Map;
import static java.lang.String.format;
import static java.sql.JDBCType.BIGINT;
import static java.sql.JDBCType.BOOLEAN;
import static java.sql.JDBCType.DOUBLE;
@ -68,7 +69,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, true, Types.TIMESTAMP));
assertEquals("Conversion from type [BOOLEAN] to [Timestamp] not supported", sqle.getMessage());
assertEquals("Unable to convert value [true] of type [BOOLEAN] to a Timestamp", sqle.getMessage());
}
public void testSettingStringValues() throws SQLException {
@ -92,7 +93,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, "foo bar", Types.INTEGER));
assertEquals("Conversion from type [VARCHAR] to [Integer] not supported", sqle.getMessage());
assertEquals("Unable to convert value [foo bar] of type [VARCHAR] to an Integer", sqle.getMessage());
}
public void testSettingByteTypeValues() throws SQLException {
@ -128,7 +129,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, (byte) 6, Types.TIMESTAMP));
assertEquals("Conversion from type [TINYINT] to [Timestamp] not supported", sqle.getMessage());
assertEquals("Unable to convert value [6] of type [TINYINT] to a Timestamp", sqle.getMessage());
}
public void testSettingShortTypeValues() throws SQLException {
@ -161,7 +162,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, (short) 6, Types.TIMESTAMP));
assertEquals("Conversion from type [SMALLINT] to [Timestamp] not supported", sqle.getMessage());
assertEquals("Unable to convert value [6] of type [SMALLINT] to a Timestamp", sqle.getMessage());
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, 256, Types.TINYINT));
assertEquals("Numeric " + 256 + " out of range", sqle.getMessage());
@ -195,7 +196,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
int someInt = randomInt();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someInt, Types.TIMESTAMP));
assertEquals("Conversion from type [INTEGER] to [Timestamp] not supported", sqle.getMessage());
assertEquals(format(Locale.ROOT, "Unable to convert value [%.128s] of type [INTEGER] to a Timestamp", someInt), sqle.getMessage());
Integer randomIntNotShort = randomIntBetween(32768, Integer.MAX_VALUE);
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, randomIntNotShort, Types.SMALLINT));
@ -236,7 +237,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
long someLong = randomLong();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someLong, Types.TIMESTAMP));
assertEquals("Conversion from type [BIGINT] to [Timestamp] not supported", sqle.getMessage());
assertEquals(format(Locale.ROOT, "Unable to convert value [%.128s] of type [BIGINT] to a Timestamp", someLong), sqle.getMessage());
Long randomLongNotShort = randomLongBetween(Integer.MAX_VALUE + 1, Long.MAX_VALUE);
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, randomLongNotShort, Types.INTEGER));
@ -277,7 +278,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
float someFloat = randomFloat();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someFloat, Types.TIMESTAMP));
assertEquals("Conversion from type [REAL] to [Timestamp] not supported", sqle.getMessage());
assertEquals(format(Locale.ROOT, "Unable to convert value [%.128s] of type [REAL] to a Timestamp", someFloat), sqle.getMessage());
Float floatNotInt = 5_155_000_000f;
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, floatNotInt, Types.INTEGER));
@ -316,7 +317,8 @@ public class JdbcPreparedStatementTests extends ESTestCase {
double someDouble = randomDouble();
SQLException sqle = expectThrows(SQLException.class, () -> jps.setObject(1, someDouble, Types.TIMESTAMP));
assertEquals("Conversion from type [DOUBLE] to [Timestamp] not supported", sqle.getMessage());
assertEquals(
format(Locale.ROOT, "Unable to convert value [%.128s] of type [DOUBLE] to a Timestamp", someDouble), sqle.getMessage());
Double doubleNotInt = 5_155_000_000d;
sqle = expectThrows(SQLException.class, () -> jps.setObject(1, doubleNotInt, Types.INTEGER));
@ -361,7 +363,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
public void testSettingTimestampValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch());
Timestamp someTimestamp = new Timestamp(randomLong());
jps.setTimestamp(1, someTimestamp);
assertEquals(someTimestamp.getTime(), ((Date)value(jps)).getTime());
assertEquals(TIMESTAMP, jdbcType(jps));
@ -372,7 +374,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
assertEquals(1456708675000L, convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
assertEquals(TIMESTAMP, jdbcType(jps));
long beforeEpochTime = -randomMillisSinceEpoch();
long beforeEpochTime = randomLongBetween(Long.MIN_VALUE, 0);
jps.setTimestamp(1, new Timestamp(beforeEpochTime), nonDefaultCal);
assertEquals(beforeEpochTime, convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
assertTrue(value(jps) instanceof java.util.Date);
@ -384,7 +386,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
public void testThrownExceptionsWhenSettingTimestampValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Timestamp someTimestamp = new Timestamp(randomMillisSinceEpoch());
Timestamp someTimestamp = new Timestamp(randomLong());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someTimestamp, Types.INTEGER));
assertEquals("Conversion from type java.sql.Timestamp to INTEGER not supported", sqle.getMessage());
@ -416,12 +418,12 @@ public class JdbcPreparedStatementTests extends ESTestCase {
public void testSettingSqlDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
java.sql.Date someSqlDate = new java.sql.Date(randomMillisSinceEpoch());
java.sql.Date someSqlDate = new java.sql.Date(randomLong());
jps.setDate(1, someSqlDate);
assertEquals(someSqlDate.getTime(), ((Date)value(jps)).getTime());
assertEquals(TIMESTAMP, jdbcType(jps));
someSqlDate = new java.sql.Date(randomMillisSinceEpoch());
someSqlDate = new java.sql.Date(randomLong());
Calendar nonDefaultCal = randomCalendar();
jps.setDate(1, someSqlDate, nonDefaultCal);
assertEquals(someSqlDate.getTime(), convertFromUTCtoCalendar(((Date)value(jps)), nonDefaultCal));
@ -435,17 +437,17 @@ public class JdbcPreparedStatementTests extends ESTestCase {
public void testThrownExceptionsWhenSettingSqlDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
java.sql.Date someSqlDate = new java.sql.Date(randomMillisSinceEpoch());
java.sql.Date someSqlDate = new java.sql.Date(randomLong());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class,
() -> jps.setObject(1, new java.sql.Date(randomMillisSinceEpoch()), Types.DOUBLE));
() -> jps.setObject(1, new java.sql.Date(randomLong()), Types.DOUBLE));
assertEquals("Conversion from type " + someSqlDate.getClass().getName() + " to DOUBLE not supported", sqle.getMessage());
}
public void testSettingCalendarValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Calendar someCalendar = randomCalendar();
someCalendar.setTimeInMillis(randomMillisSinceEpoch());
someCalendar.setTimeInMillis(randomLong());
jps.setObject(1, someCalendar);
assertEquals(someCalendar.getTime(), (Date) value(jps));
@ -472,7 +474,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
public void testSettingDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Date someDate = new Date(randomMillisSinceEpoch());
Date someDate = new Date(randomLong());
jps.setObject(1, someDate);
assertEquals(someDate, (Date) value(jps));
@ -486,7 +488,7 @@ public class JdbcPreparedStatementTests extends ESTestCase {
public void testThrownExceptionsWhenSettingDateValues() throws SQLException {
JdbcPreparedStatement jps = createJdbcPreparedStatement();
Date someDate = new Date(randomMillisSinceEpoch());
Date someDate = new Date(randomLong());
SQLException sqle = expectThrows(SQLFeatureNotSupportedException.class, () -> jps.setObject(1, someDate, Types.BIGINT));
assertEquals("Conversion from type " + someDate.getClass().getName() + " to BIGINT not supported", sqle.getMessage());
@ -549,10 +551,6 @@ public class JdbcPreparedStatementTests extends ESTestCase {
assertEquals("Conversion from type byte[] to DOUBLE not supported", sqle.getMessage());
}
private long randomMillisSinceEpoch() {
return randomLongBetween(0, System.currentTimeMillis());
}
private JdbcPreparedStatement createJdbcPreparedStatement() throws SQLException {
return new JdbcPreparedStatement(null, JdbcConfiguration.create("jdbc:es://l:1", null, 0), "?");
}

View File

@ -0,0 +1,16 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.qa.sql.nosecurity;
import org.elasticsearch.xpack.qa.sql.jdbc.ResultSetTestCase;
/*
* Integration testing class for "no security" (cluster running without the Security plugin,
* or the Security is disbled) scenario. Runs all tests in the base class.
*/
public class JdbcResultSetIT extends ResultSetTestCase {
}

View File

@ -25,7 +25,8 @@ public class SimpleExampleTestCase extends JdbcIntegrationTestCase {
assertEquals("Don Quixote", results.getString(1));
assertEquals(1072, results.getInt(2));
SQLException e = expectThrows(SQLException.class, () -> results.getInt(1));
assertTrue(e.getMessage(), e.getMessage().contains("unable to convert column 1 to an int"));
assertTrue(e.getMessage(),
e.getMessage().contains("Unable to convert value [Don Quixote] of type [VARCHAR] to an Integer"));
assertFalse(results.next());
}
// end::simple_example