Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,19 @@ private Value getCurrentValue(int columnIndex) throws SQLException {
return val;
}

Value valueForMetadata(int columnIndex) {
int rowIndex = currentRowIndex < 0 ? 0 : currentRowIndex;
if (rowIndex >= rows.size()) {
return null;
}
Row row = rows.get(rowIndex);
List<Value> values = row.values();
if (values == null || columnIndex < 1 || columnIndex > values.size()) {
return null;
}
return values.get(columnIndex - 1);
}

@Override
public boolean next() throws SQLException {
checkClosed();
Expand Down Expand Up @@ -554,7 +567,7 @@ public Object getObject(String columnLabel) throws SQLException {
public ResultSetMetaData getMetaData() throws SQLException {
checkClosed();
if (metadata == null) {
metadata = new LibsqlResultSetMetaData(cols, rows, sql, connection);
metadata = new LibsqlResultSetMetaData(cols, rows, sql, connection, this);
}
return metadata;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,26 @@ public class LibsqlResultSetMetaData implements ResultSetMetaData {
private final List<Row> rows;
private final String sql;
private final LibsqlConnection connection;
private final LibsqlResultSet resultSet;

private boolean tableContextResolved;
private TableInfo tableContext;

public LibsqlResultSetMetaData(List<Col> cols) {
this(cols, List.of(), null, null);
this(cols, List.of(), null, null, null);
}

LibsqlResultSetMetaData(List<Col> cols, List<Row> rows, String sql, LibsqlConnection connection) {
this(cols, rows, sql, connection, null);
}

LibsqlResultSetMetaData(
List<Col> cols, List<Row> rows, String sql, LibsqlConnection connection, LibsqlResultSet resultSet) {
this.cols = cols != null ? cols : List.of();
this.rows = rows != null ? rows : List.of();
this.sql = sql;
this.connection = connection;
this.resultSet = resultSet;
}

@Override
Expand Down Expand Up @@ -204,28 +211,92 @@ public String getCatalogName(int column) throws SQLException {
public int getColumnType(int column) throws SQLException {
getCol(column);
String typeName = getColumnTypeName(column);
Value sample = sampleValue(column);
Value value = runtimeValue(column);
SampleType valueType = storageType(value);

return switch (typeName) {
case "BOOLEAN" -> Types.BOOLEAN;
case "TINYINT" -> Types.TINYINT;
case "SMALLINT", "INT2" -> Types.SMALLINT;
case "BIGINT", "INT8", "UNSIGNED BIG INT" -> Types.BIGINT;
case "DATE" -> Types.DATE;
case "DATETIME", "TIMESTAMP" -> Types.TIMESTAMP;
case "INT", "INTEGER", "MEDIUMINT" -> integerType(column);
case "DECIMAL" -> Types.DECIMAL;
case "DOUBLE", "DOUBLE PRECISION" -> Types.DOUBLE;
case "NUMERIC" -> Types.NUMERIC;
case "REAL" -> Types.REAL;
case "FLOAT" -> Types.FLOAT;
case "CHARACTER", "NCHAR", "NATIVE CHARACTER", "CHAR" -> Types.CHAR;
case "CLOB" -> Types.CLOB;
case "VARCHAR", "VARYING CHARACTER", "NVARCHAR", "TEXT" -> Types.VARCHAR;
case "BINARY" -> Types.BINARY;
case "BLOB" -> Types.BLOB;
default -> inferTypeFromValue(sample);
};
if (valueType == SampleType.INTEGER || valueType == SampleType.NULL) {
if ("BOOLEAN".equals(typeName)) {
return Types.BOOLEAN;
}
if ("TINYINT".equals(typeName)) {
return Types.TINYINT;
}
if ("SMALLINT".equals(typeName) || "INT2".equals(typeName)) {
return Types.SMALLINT;
}
if ("BIGINT".equals(typeName)
|| "INT8".equals(typeName)
|| "UNSIGNED BIG INT".equals(typeName)) {
return Types.BIGINT;
}
if ("DATE".equals(typeName)) {
return Types.DATE;
}
if ("DATETIME".equals(typeName) || "TIMESTAMP".equals(typeName)) {
return Types.TIMESTAMP;
}
if (valueType == SampleType.INTEGER
|| "INT".equals(typeName)
|| "INTEGER".equals(typeName)
|| "MEDIUMINT".equals(typeName)) {
long val = integer(value);
return val > Integer.MAX_VALUE || val < Integer.MIN_VALUE ? Types.BIGINT : Types.INTEGER;
}
}

if (valueType == SampleType.FLOAT || valueType == SampleType.NULL) {
if ("DECIMAL".equals(typeName)) {
return Types.DECIMAL;
}
if ("DOUBLE".equals(typeName) || "DOUBLE PRECISION".equals(typeName)) {
return Types.DOUBLE;
}
if ("NUMERIC".equals(typeName)) {
return Types.NUMERIC;
}
if ("REAL".equals(typeName)) {
return Types.REAL;
}
if (valueType == SampleType.FLOAT || "FLOAT".equals(typeName)) {
return Types.FLOAT;
}
}

if (valueType == SampleType.TEXT || valueType == SampleType.NULL) {
if ("CHARACTER".equals(typeName)
|| "NCHAR".equals(typeName)
|| "NATIVE CHARACTER".equals(typeName)
|| "CHAR".equals(typeName)) {
return Types.CHAR;
}
if ("CLOB".equals(typeName)) {
return Types.CLOB;
}
if ("DATE".equals(typeName)) {
return Types.DATE;
}
if ("DATETIME".equals(typeName) || "TIMESTAMP".equals(typeName)) {
return Types.TIMESTAMP;
}
if (valueType == SampleType.TEXT
|| "VARCHAR".equals(typeName)
|| "VARYING CHARACTER".equals(typeName)
|| "NVARCHAR".equals(typeName)
|| "TEXT".equals(typeName)) {
return Types.VARCHAR;
}
}

if (valueType == SampleType.BLOB || valueType == SampleType.NULL) {
if ("BINARY".equals(typeName)) {
return Types.BINARY;
}
if (valueType == SampleType.BLOB || "BLOB".equals(typeName)) {
return Types.BLOB;
}
}

return Types.NUMERIC;
}

@Override
Expand Down Expand Up @@ -346,6 +417,13 @@ private String columnName(int column) throws SQLException {
return name != null ? name.toLowerCase(Locale.ROOT) : "";
}

private Value runtimeValue(int column) {
if (resultSet != null) {
return resultSet.valueForMetadata(column);
}
return sampleValue(column);
}

private Value sampleValue(int column) {
for (Row row : rows) {
List<Value> values = row.values();
Expand All @@ -361,17 +439,20 @@ private Value sampleValue(int column) {
}

private SampleType sampleType(int column) {
Value sample = sampleValue(column);
if (sample instanceof Value.Integer) {
return storageType(sampleValue(column));
}

private static SampleType storageType(Value value) {
if (value instanceof Value.Integer) {
return SampleType.INTEGER;
}
if (sample instanceof Value.Float) {
if (value instanceof Value.Float) {
return SampleType.FLOAT;
}
if (sample instanceof Value.Blob) {
if (value instanceof Value.Blob) {
return SampleType.BLOB;
}
if (sample instanceof Value.Text) {
if (value instanceof Value.Text) {
return SampleType.TEXT;
}
return SampleType.NULL;
Expand All @@ -383,50 +464,6 @@ private static boolean isIntInRange(Value value) {
&& i.value() <= Integer.MAX_VALUE;
}

private int integerType(int column) {
Value firstNonInteger = null;
boolean outsideIntRange = false;
for (Row row : rows) {
List<Value> values = row.values();
if (values == null || column < 1 || column > values.size()) {
continue;
}
Value value = values.get(column - 1);
if (value == null || value instanceof Value.Null) {
continue;
}
if (value instanceof Value.Integer) {
if (!isIntInRange(value)) {
outsideIntRange = true;
}
} else if (firstNonInteger == null) {
firstNonInteger = value;
}
}
if (firstNonInteger != null) {
return inferTypeFromValue(firstNonInteger);
}
return outsideIntRange ? Types.BIGINT : Types.INTEGER;
}

private static int inferTypeFromValue(Value sample) {
if (sample instanceof Value.Integer i) {
return i.value() >= Integer.MIN_VALUE && i.value() <= Integer.MAX_VALUE
? Types.INTEGER
: Types.BIGINT;
}
if (sample instanceof Value.Float) {
return Types.FLOAT;
}
if (sample instanceof Value.Text) {
return Types.VARCHAR;
}
if (sample instanceof Value.Blob) {
return Types.BLOB;
}
return Types.NUMERIC;
}

private TableInfo resolveTableContext() throws SQLException {
if (!tableContextResolved) {
TableInfo resolved = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,101 @@ void usesRuntimeValueMappingForNonIntegerValuesInIntegerColumn() throws SQLExcep
assertThat(metadata.getColumnType(1)).isEqualTo(Types.VARCHAR);
}

@Test
void usesRuntimeStorageClassForDeclaredTypes() throws SQLException {
StmtResult result = new StmtResult(
List.of(
new Col("bi", "BIGINT"),
new Col("bo", "BOOLEAN"),
new Col("num", "NUMERIC"),
new Col("dec", "DECIMAL")),
List.of(new Row(List.of(Value.of(1.5), Value.of("true"), Value.of(7L), Value.of(8L)))),
0,
null);
LibsqlResultSet resultSet = new LibsqlResultSet(null, result);
ResultSetMetaData metadata = resultSet.getMetaData();

assertRuntimeTypes(metadata);

assertThat(resultSet.next()).isTrue();
assertRuntimeTypes(metadata);
}

@Test
void usesDeclaredTypesWhenRuntimeValueIsNull() throws SQLException {
StmtResult result = new StmtResult(
List.of(
new Col("bi", "BIGINT"),
new Col("bo", "BOOLEAN"),
new Col("num", "NUMERIC"),
new Col("dec", "DECIMAL")),
List.of(new Row(List.of(Value.ofNull(), Value.ofNull(), Value.ofNull(), Value.ofNull()))),
0,
null);
LibsqlResultSet resultSet = new LibsqlResultSet(null, result);
ResultSetMetaData metadata = resultSet.getMetaData();

assertDeclaredTypes(metadata);

assertThat(resultSet.next()).isTrue();
assertDeclaredTypes(metadata);
}

@Test
void usesDeclaredTypesForEmptyResultSet() throws SQLException {
StmtResult result = new StmtResult(
List.of(
new Col("bi", "BIGINT"),
new Col("bo", "BOOLEAN"),
new Col("num", "NUMERIC"),
new Col("dec", "DECIMAL")),
List.of(),
0,
null);
LibsqlResultSet resultSet = new LibsqlResultSet(null, result);
ResultSetMetaData metadata = resultSet.getMetaData();

assertDeclaredTypes(metadata);
assertThat(resultSet.next()).isFalse();
assertDeclaredTypes(metadata);
}

@Test
void reflectsStorageClassChangesBetweenRows() throws SQLException {
StmtResult result = new StmtResult(
List.of(new Col("bi", "BIGINT")),
List.of(new Row(List.of(Value.of(1.5))), new Row(List.of(Value.of(2L)))),
0,
null);
LibsqlResultSet resultSet = new LibsqlResultSet(null, result);
ResultSetMetaData metadata = resultSet.getMetaData();

assertThat(metadata.getColumnType(1)).isEqualTo(Types.FLOAT);

assertThat(resultSet.next()).isTrue();
assertThat(metadata.getColumnType(1)).isEqualTo(Types.FLOAT);

assertThat(resultSet.next()).isTrue();
assertThat(metadata.getColumnType(1)).isEqualTo(Types.BIGINT);

assertThat(resultSet.next()).isFalse();
assertThat(metadata.getColumnType(1)).isEqualTo(Types.BIGINT);
}

private static void assertRuntimeTypes(ResultSetMetaData metadata) throws SQLException {
assertThat(metadata.getColumnType(1)).isEqualTo(Types.FLOAT);
assertThat(metadata.getColumnType(2)).isEqualTo(Types.VARCHAR);
assertThat(metadata.getColumnType(3)).isEqualTo(Types.INTEGER);
assertThat(metadata.getColumnType(4)).isEqualTo(Types.INTEGER);
}

private static void assertDeclaredTypes(ResultSetMetaData metadata) throws SQLException {
assertThat(metadata.getColumnType(1)).isEqualTo(Types.BIGINT);
assertThat(metadata.getColumnType(2)).isEqualTo(Types.BOOLEAN);
assertThat(metadata.getColumnType(3)).isEqualTo(Types.NUMERIC);
assertThat(metadata.getColumnType(4)).isEqualTo(Types.DECIMAL);
}

@Test
void leavesAmbiguousTableContextUnknown() throws SQLException {
LibsqlResultSetMetaData metadata = new LibsqlResultSetMetaData(
Expand Down
Loading