in solr/solrj-streaming/src/java/org/apache/solr/client/solrj/io/stream/JDBCStream.java [389:615]
protected ResultSetValueSelector determineValueSelector(int columnIdx, ResultSetMetaData metadata)
throws SQLException {
final int columnNumber = columnIdx + 1; // cause it starts at 1
// Use getColumnLabel instead of getColumnName to make sure fields renamed with AS are picked up
// properly
final String columnName = metadata.getColumnLabel(columnNumber);
final int jdbcType = metadata.getColumnType(columnNumber);
final String className = metadata.getColumnClassName(columnNumber);
ResultSetValueSelector valueSelector = null;
// Directly supported types can be just directly returned - no conversion really necessary
if (directSupportedTypes.contains(className)) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Object obj = resultSet.getObject(columnNumber);
if (resultSet.wasNull()) {
return null;
}
if (obj instanceof String s) {
if (s.contains(sep)) {
s = s.substring(1);
return s.split(sep);
}
}
return obj;
}
@Override
public String getColumnName() {
return columnName;
}
};
}
// We're checking the Java class names because there are lots of SQL types across
// lots of database drivers that can be mapped to standard Java types. Basically,
// this makes it easier, and we don't have to worry about esoteric type names in the
// JDBC family of types
else if (Short.class.getName().equals(className)) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Short obj = resultSet.getShort(columnNumber);
if (resultSet.wasNull()) {
return null;
}
return obj.longValue();
}
@Override
public String getColumnName() {
return columnName;
}
};
} else if (Integer.class.getName().equals(className)) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Integer obj = resultSet.getInt(columnNumber);
if (resultSet.wasNull()) {
return null;
}
return obj.longValue();
}
@Override
public String getColumnName() {
return columnName;
}
};
} else if (Float.class.getName().equals(className)) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Float obj = resultSet.getFloat(columnNumber);
if (resultSet.wasNull()) {
return null;
}
return obj.doubleValue();
}
@Override
public String getColumnName() {
return columnName;
}
};
}
// Here we are switching to check against the SQL type because date/times are
// notorious for not being consistent. We don't know if the driver is mapping
// to a java.time.* type or some old-school type.
else if (jdbcType == Types.DATE) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Date sqlDate = resultSet.getDate(columnNumber);
return resultSet.wasNull() ? null : sqlDate.toString();
}
@Override
public String getColumnName() {
return columnName;
}
};
} else if (jdbcType == Types.TIME) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Time sqlTime = resultSet.getTime(columnNumber);
return resultSet.wasNull() ? null : sqlTime.toString();
}
@Override
public String getColumnName() {
return columnName;
}
};
} else if (jdbcType == Types.TIMESTAMP) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Timestamp sqlTimestamp = resultSet.getTimestamp(columnNumber);
return resultSet.wasNull() ? null : sqlTimestamp.toInstant().toString();
}
@Override
public String getColumnName() {
return columnName;
}
};
} else if (Object.class.getName().equals(className)) {
// Calcite SQL type ANY comes across as generic Object (for multi-valued fields)
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Object obj = resultSet.getObject(columnNumber);
return resultSet.wasNull() ? null : obj;
}
@Override
public String getColumnName() {
return columnName;
}
};
}
// Now we're going to start seeing if things are assignable from the returned type
// to a more general type - this allows us to cover cases where something we weren't
// explicitly expecting, but can handle, is being returned.
else {
Class<?> clazz;
try {
clazz = Class.forName(className, false, getClass().getClassLoader());
} catch (Exception e) {
throw new RuntimeException(e);
}
final int scale = metadata.getScale(columnNumber);
if (Number.class.isAssignableFrom(clazz)) {
if (scale > 0) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
BigDecimal bd = resultSet.getBigDecimal(columnNumber);
return resultSet.wasNull() ? null : bd.doubleValue();
}
@Override
public String getColumnName() {
return columnName;
}
};
} else {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
BigDecimal bd = resultSet.getBigDecimal(columnNumber);
return resultSet.wasNull() ? null : bd.longValue();
}
@Override
public String getColumnName() {
return columnName;
}
};
}
} else if (Clob.class.isAssignableFrom(clazz)) {
valueSelector =
new ResultSetValueSelector() {
@Override
public Object selectValue(ResultSet resultSet) throws SQLException {
Clob c = resultSet.getClob(columnNumber);
if (resultSet.wasNull()) {
return null;
}
long length = c.length();
int lengthInt = (int) length;
if (length != lengthInt) {
throw new SQLException(
String.format(
Locale.ROOT,
"Encountered a clob of length #%d in column '%s' (col #%d). Max supported length is #%d.",
length,
columnName,
columnNumber,
Integer.MAX_VALUE));
}
return c.getSubString(1, lengthInt);
}
@Override
public String getColumnName() {
return columnName;
}
};
}
}
return valueSelector;
}