Skip to content

Commit

Permalink
LibreOfficeBase ResultSet metadata issues
Browse files Browse the repository at this point in the history
Implement several ResultSetMetaData methods to be able be run as a
proper JDBC-driver inside Base tool.

Reworked SQL data types in relation with Tarantool NoSQL types as well
as JDBC types. A data conversations are left out of this commit.

This issue also addresses to tarantool/tarantool#3292 to be executed
in a dry-run mode. LibreOffice Base uses PreparedStatement.getMetadata()
without a real query execution to extract a metadata in advance. This
causes NPE in #198.

Follows on: #198
  • Loading branch information
nicktorwald committed Jun 27, 2019
1 parent b53e0ba commit 70e65fa
Show file tree
Hide file tree
Showing 14 changed files with 900 additions and 329 deletions.
4 changes: 3 additions & 1 deletion src/main/java/org/tarantool/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ public enum Key implements Callable<Integer> {
DATA(0x30),
ERROR(0x31),

SQL_FIELD_NAME(0),
SQL_FIELD_NAME(0x0),
SQL_FIELD_TYPE(0x1),

SQL_METADATA(0x32),
SQL_TEXT(0x40),
SQL_BIND(0x41),
Expand Down
45 changes: 39 additions & 6 deletions src/main/java/org/tarantool/SqlProtoUtils.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.tarantool;

import org.tarantool.jdbc.type.TarantoolSqlType;
import org.tarantool.jdbc.type.TarantoolType;
import org.tarantool.protocol.TarantoolPacket;

import java.util.ArrayList;
Expand Down Expand Up @@ -30,8 +32,11 @@ public static List<List<Object>> getSQLData(TarantoolPacket pack) {
public static List<SQLMetaData> getSQLMetadata(TarantoolPacket pack) {
List<Map<Integer, Object>> meta = (List<Map<Integer, Object>>) pack.getBody().get(Key.SQL_METADATA.getId());
List<SQLMetaData> values = new ArrayList<>(meta.size());
for (Map<Integer, Object> c : meta) {
values.add(new SQLMetaData((String) c.get(Key.SQL_FIELD_NAME.getId())));
for (Map<Integer, Object> item : meta) {
values.add(new SQLMetaData(
(String) item.get(Key.SQL_FIELD_NAME.getId()),
(String) item.get(Key.SQL_FIELD_TYPE.getId()))
);
}
return values;
}
Expand All @@ -46,21 +51,49 @@ public static Long getSqlRowCount(TarantoolPacket pack) {
}

public static class SQLMetaData {
protected String name;
private String name;
private TarantoolSqlType type;

public SQLMetaData(String name) {
/**
* Constructs new SQL metadata based on a raw Tarantool
* type.
*
* Tarantool returns a raw type instead of SQL one.
* This leads a type mapping ambiguity between raw and
* SQL types and a default SQL type will be chosen.
*
* @param name column name
* @param tarantoolType raw Tarantool type name
*
* @see TarantoolSqlType#getDefaultSqlType(TarantoolType)
*/
public SQLMetaData(String name, String tarantoolType) {
this(
name,
TarantoolSqlType.getDefaultSqlType(TarantoolType.of(tarantoolType))
);
}

public SQLMetaData(String name, TarantoolSqlType type) {
this.name = name;
this.type = type;
}

public String getName() {
return name;
}

public TarantoolSqlType getType() {
return type;
}

@Override
public String toString() {
return "SQLMetaData{" +
"name='" + name + '\'' +
'}';
"name='" + name + '\'' +
", type=" + type +
'}';
}

}
}
15 changes: 9 additions & 6 deletions src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import org.tarantool.SqlProtoUtils;
import org.tarantool.Version;
import org.tarantool.jdbc.type.TarantoolSqlType;
import org.tarantool.util.TupleTwo;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
Expand Down Expand Up @@ -1087,16 +1089,17 @@ public ResultSet getPseudoColumns(String catalog,
return asEmptyMetadataResultSet(DatabaseMetadataTable.PSEUDO_COLUMNS);
}

private ResultSet asMetadataResultSet(List<String> columnNames, List<List<Object>> rows) throws SQLException {
List<SqlProtoUtils.SQLMetaData> meta = columnNames.stream()
.map(SqlProtoUtils.SQLMetaData::new)
private ResultSet asMetadataResultSet(List<TupleTwo<String, TarantoolSqlType>> meta, List<List<Object>> rows)
throws SQLException {
List<SqlProtoUtils.SQLMetaData> sqlMeta = meta.stream()
.map(tuple -> new SqlProtoUtils.SQLMetaData(tuple.getFirst(), tuple.getSecond()))
.collect(Collectors.toList());
SQLResultHolder holder = SQLResultHolder.ofQuery(meta, rows);
SQLResultHolder holder = SQLResultHolder.ofQuery(sqlMeta, rows);
return createMetadataStatement().executeMetadata(holder);
}

private ResultSet asEmptyMetadataResultSet(List<String> columnNames) throws SQLException {
return asMetadataResultSet(columnNames, Collections.emptyList());
private ResultSet asEmptyMetadataResultSet(List<TupleTwo<String, TarantoolSqlType>> meta) throws SQLException {
return asMetadataResultSet(meta, Collections.emptyList());
}

@Override
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,13 @@ public void setArray(int parameterIndex, Array x) throws SQLException {

@Override
public ResultSetMetaData getMetaData() throws SQLException {
return getResultSet().getMetaData();
if (resultSet != null && !resultSet.isClosed()) {
return resultSet.getMetaData();
}
// it's required a support of dry-run mode to obtain
// a statement metadata without real query execution.
// see https://github.com/tarantool/tarantool/issues/3292
return null;
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/tarantool/jdbc/SQLResultSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public class SQLResultSet implements ResultSet {
private final int holdability;

public SQLResultSet(SQLResultHolder holder, TarantoolStatement ownerStatement) throws SQLException {
metaData = new SQLResultSetMetaData(holder.getSqlMetadata());
metaData = new SQLResultSetMetaData(holder.getSqlMetadata(), ownerStatement.getConnection().isReadOnly());
statement = ownerStatement;
scrollType = statement.getResultSetType();
concurrencyLevel = statement.getResultSetConcurrency();
Expand Down
91 changes: 67 additions & 24 deletions src/main/java/org/tarantool/jdbc/SQLResultSetMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLNonTransientException;
import java.sql.Types;
import java.util.List;

public class SQLResultSetMetaData implements ResultSetMetaData {

private final List<SqlProtoUtils.SQLMetaData> sqlMetadata;
private final boolean readOnly;

public SQLResultSetMetaData(List<SqlProtoUtils.SQLMetaData> sqlMetaData) {
public SQLResultSetMetaData(List<SqlProtoUtils.SQLMetaData> sqlMetaData, boolean readOnly) {
this.sqlMetadata = sqlMetaData;
this.readOnly = readOnly;
}

@Override
Expand All @@ -25,37 +25,59 @@ public int getColumnCount() throws SQLException {

@Override
public boolean isAutoIncrement(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
// extra flag or, at least table ID is required in meta
// to be able to fetch an the flag indirectly.
return false;
}

@Override
public boolean isCaseSensitive(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().isCaseSensitive();
}

/**
* {@inheritDoc}
* <p>
* All the types can be used in {@literal WHERE} clause.
*/
@Override
public boolean isSearchable(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return true;
}

/**
* {@inheritDoc}
* <p>
* Always {@literal false} because
* Tarantool does not have monetary types.
*/
@Override
public boolean isCurrency(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return false;
}

@Override
public int isNullable(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
// extra nullability flag or, at least table ID is required in meta
// to be able to fetch an the flag indirectly.
return ResultSetMetaData.columnNullableUnknown;
}

@Override
public boolean isSigned(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().isSigned();
}

@Override
public int getColumnDisplaySize(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().getDisplaySize();
}

@Override
Expand All @@ -64,6 +86,15 @@ public String getColumnLabel(int column) throws SQLException {
return sqlMetadata.get(column - 1).getName();
}

/**
* {@inheritDoc}
* <p>
* Name always has the same value as label
* because Tarantool does not differentiate
* column names and aliases.
*
* @see #getColumnLabel(int)
*/
@Override
public String getColumnName(int column) throws SQLException {
checkColumnIndex(column);
Expand All @@ -72,65 +103,77 @@ public String getColumnName(int column) throws SQLException {

@Override
public String getSchemaName(int column) throws SQLException {
return null;
checkColumnIndex(column);
return "";
}

@Override
public int getPrecision(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().getPrecision();
}


@Override
public int getScale(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().getScale();
}

@Override
public String getTableName(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
// extra table name or, at least table ID is required in meta
// to be able to fetch the table name.
return "";
}

@Override
public String getCatalogName(int column) throws SQLException {
return null;
checkColumnIndex(column);
return "";
}

@Override
public int getColumnType(int column) throws SQLException {
return Types.OTHER;
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().getJdbcType().getTypeNumber();
}

@Override
public String getColumnTypeName(int column) throws SQLException {
return "scalar";
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().getTypeName();
}

@Override
public boolean isReadOnly(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return readOnly;
}

@Override
public boolean isWritable(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
return !isReadOnly(column);
}

@Override
public boolean isDefinitelyWritable(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
return false;
}

@Override
public String getColumnClassName(int column) throws SQLException {
throw new SQLFeatureNotSupportedException();
checkColumnIndex(column);
return sqlMetadata.get(column - 1).getType().getJdbcType().getJavaType().getName();
}

@Override
public <T> T unwrap(Class<T> type) throws SQLException {
if (isWrapperFor(type)) {
return type.cast(this);
}
throw new SQLNonTransientException("ResultSetMetadata does not wrap " + type.getName());
throw new SQLNonTransientException("SQLResultSetMetadata does not wrap " + type.getName());
}

@Override
Expand All @@ -150,7 +193,7 @@ void checkColumnIndex(int columnIndex) throws SQLException {
@Override
public String toString() {
return "SQLResultSetMetaData{" +
"sqlMetadata=" + sqlMetadata +
'}';
"sqlMetadata=" + sqlMetadata +
'}';
}
}
Loading

0 comments on commit 70e65fa

Please sign in to comment.