Skip to content

Commit 05ce89d

Browse files
committed
LibreOfficeBase ResultSet metadata issues
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
1 parent b53e0ba commit 05ce89d

15 files changed

+902
-332
lines changed

Diff for: src/main/java/org/tarantool/Key.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ public enum Key implements Callable<Integer> {
2525
DATA(0x30),
2626
ERROR(0x31),
2727

28-
SQL_FIELD_NAME(0),
28+
SQL_FIELD_NAME(0x0),
29+
SQL_FIELD_TYPE(0x1),
30+
2931
SQL_METADATA(0x32),
3032
SQL_TEXT(0x40),
3133
SQL_BIND(0x41),

Diff for: src/main/java/org/tarantool/SqlProtoUtils.java

+39-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.tarantool;
22

3+
import org.tarantool.jdbc.type.TarantoolSqlType;
4+
import org.tarantool.jdbc.type.TarantoolType;
35
import org.tarantool.protocol.TarantoolPacket;
46

57
import java.util.ArrayList;
@@ -30,8 +32,11 @@ public static List<List<Object>> getSQLData(TarantoolPacket pack) {
3032
public static List<SQLMetaData> getSQLMetadata(TarantoolPacket pack) {
3133
List<Map<Integer, Object>> meta = (List<Map<Integer, Object>>) pack.getBody().get(Key.SQL_METADATA.getId());
3234
List<SQLMetaData> values = new ArrayList<>(meta.size());
33-
for (Map<Integer, Object> c : meta) {
34-
values.add(new SQLMetaData((String) c.get(Key.SQL_FIELD_NAME.getId())));
35+
for (Map<Integer, Object> item : meta) {
36+
values.add(new SQLMetaData(
37+
(String) item.get(Key.SQL_FIELD_NAME.getId()),
38+
(String) item.get(Key.SQL_FIELD_TYPE.getId()))
39+
);
3540
}
3641
return values;
3742
}
@@ -46,21 +51,49 @@ public static Long getSqlRowCount(TarantoolPacket pack) {
4651
}
4752

4853
public static class SQLMetaData {
49-
protected String name;
54+
private String name;
55+
private TarantoolSqlType type;
5056

51-
public SQLMetaData(String name) {
57+
/**
58+
* Constructs new SQL metadata based on a raw Tarantool
59+
* type.
60+
*
61+
* Tarantool returns a raw type instead of SQL one.
62+
* This leads a type mapping ambiguity between raw and
63+
* SQL types and a default SQL type will be chosen.
64+
*
65+
* @param name column name
66+
* @param tarantoolType raw Tarantool type name
67+
*
68+
* @see TarantoolSqlType#getDefaultSqlType(TarantoolType)
69+
*/
70+
public SQLMetaData(String name, String tarantoolType) {
71+
this(
72+
name,
73+
TarantoolSqlType.getDefaultSqlType(TarantoolType.of(tarantoolType))
74+
);
75+
}
76+
77+
public SQLMetaData(String name, TarantoolSqlType type) {
5278
this.name = name;
79+
this.type = type;
5380
}
5481

5582
public String getName() {
5683
return name;
5784
}
5885

86+
public TarantoolSqlType getType() {
87+
return type;
88+
}
89+
5990
@Override
6091
public String toString() {
6192
return "SQLMetaData{" +
62-
"name='" + name + '\'' +
63-
'}';
93+
"name='" + name + '\'' +
94+
", type=" + type +
95+
'}';
6496
}
97+
6598
}
6699
}

Diff for: src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java

+9-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import org.tarantool.SqlProtoUtils;
66
import org.tarantool.Version;
7+
import org.tarantool.jdbc.type.TarantoolSqlType;
8+
import org.tarantool.util.TupleTwo;
79

810
import java.sql.Connection;
911
import java.sql.DatabaseMetaData;
@@ -1087,16 +1089,17 @@ public ResultSet getPseudoColumns(String catalog,
10871089
return asEmptyMetadataResultSet(DatabaseMetadataTable.PSEUDO_COLUMNS);
10881090
}
10891091

1090-
private ResultSet asMetadataResultSet(List<String> columnNames, List<List<Object>> rows) throws SQLException {
1091-
List<SqlProtoUtils.SQLMetaData> meta = columnNames.stream()
1092-
.map(SqlProtoUtils.SQLMetaData::new)
1092+
private ResultSet asMetadataResultSet(List<TupleTwo<String, TarantoolSqlType>> meta, List<List<Object>> rows)
1093+
throws SQLException {
1094+
List<SqlProtoUtils.SQLMetaData> sqlMeta = meta.stream()
1095+
.map(tuple -> new SqlProtoUtils.SQLMetaData(tuple.getFirst(), tuple.getSecond()))
10931096
.collect(Collectors.toList());
1094-
SQLResultHolder holder = SQLResultHolder.ofQuery(meta, rows);
1097+
SQLResultHolder holder = SQLResultHolder.ofQuery(sqlMeta, rows);
10951098
return createMetadataStatement().executeMetadata(holder);
10961099
}
10971100

1098-
private ResultSet asEmptyMetadataResultSet(List<String> columnNames) throws SQLException {
1099-
return asMetadataResultSet(columnNames, Collections.emptyList());
1101+
private ResultSet asEmptyMetadataResultSet(List<TupleTwo<String, TarantoolSqlType>> meta) throws SQLException {
1102+
return asMetadataResultSet(meta, Collections.emptyList());
11001103
}
11011104

11021105
@Override

Diff for: src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,13 @@ public void setArray(int parameterIndex, Array x) throws SQLException {
313313

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

319325
@Override

Diff for: src/main/java/org/tarantool/jdbc/SQLResultSet.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class SQLResultSet implements ResultSet {
5454
private final int holdability;
5555

5656
public SQLResultSet(SQLResultHolder holder, TarantoolStatement ownerStatement) throws SQLException {
57-
metaData = new SQLResultSetMetaData(holder.getSqlMetadata());
57+
metaData = new SQLResultSetMetaData(holder.getSqlMetadata(), ownerStatement.getConnection().isReadOnly());
5858
statement = ownerStatement;
5959
scrollType = statement.getResultSetType();
6060
concurrencyLevel = statement.getResultSetConcurrency();

Diff for: src/main/java/org/tarantool/jdbc/SQLResultSetMetaData.java

+67-24
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55

66
import java.sql.ResultSetMetaData;
77
import java.sql.SQLException;
8-
import java.sql.SQLFeatureNotSupportedException;
98
import java.sql.SQLNonTransientException;
10-
import java.sql.Types;
119
import java.util.List;
1210

1311
public class SQLResultSetMetaData implements ResultSetMetaData {
1412

1513
private final List<SqlProtoUtils.SQLMetaData> sqlMetadata;
14+
private final boolean readOnly;
1615

17-
public SQLResultSetMetaData(List<SqlProtoUtils.SQLMetaData> sqlMetaData) {
16+
public SQLResultSetMetaData(List<SqlProtoUtils.SQLMetaData> sqlMetaData, boolean readOnly) {
1817
this.sqlMetadata = sqlMetaData;
18+
this.readOnly = readOnly;
1919
}
2020

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

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

3134
@Override
3235
public boolean isCaseSensitive(int column) throws SQLException {
33-
throw new SQLFeatureNotSupportedException();
36+
checkColumnIndex(column);
37+
return sqlMetadata.get(column - 1).getType().isCaseSensitive();
3438
}
3539

40+
/**
41+
* {@inheritDoc}
42+
* <p>
43+
* All the types can be used in {@literal WHERE} clause.
44+
*/
3645
@Override
3746
public boolean isSearchable(int column) throws SQLException {
38-
throw new SQLFeatureNotSupportedException();
47+
checkColumnIndex(column);
48+
return true;
3949
}
4050

51+
/**
52+
* {@inheritDoc}
53+
* <p>
54+
* Always {@literal false} because
55+
* Tarantool does not have monetary types.
56+
*/
4157
@Override
4258
public boolean isCurrency(int column) throws SQLException {
43-
throw new SQLFeatureNotSupportedException();
59+
checkColumnIndex(column);
60+
return false;
4461
}
4562

4663
@Override
4764
public int isNullable(int column) throws SQLException {
48-
throw new SQLFeatureNotSupportedException();
65+
checkColumnIndex(column);
66+
// extra nullability flag or, at least table ID is required in meta
67+
// to be able to fetch an the flag indirectly.
68+
return ResultSetMetaData.columnNullableUnknown;
4969
}
5070

5171
@Override
5272
public boolean isSigned(int column) throws SQLException {
53-
throw new SQLFeatureNotSupportedException();
73+
checkColumnIndex(column);
74+
return sqlMetadata.get(column - 1).getType().isSigned();
5475
}
5576

5677
@Override
5778
public int getColumnDisplaySize(int column) throws SQLException {
58-
throw new SQLFeatureNotSupportedException();
79+
checkColumnIndex(column);
80+
return sqlMetadata.get(column - 1).getType().getDisplaySize();
5981
}
6082

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

89+
/**
90+
* {@inheritDoc}
91+
* <p>
92+
* Name always has the same value as label
93+
* because Tarantool does not differentiate
94+
* column names and aliases.
95+
*
96+
* @see #getColumnLabel(int)
97+
*/
6798
@Override
6899
public String getColumnName(int column) throws SQLException {
69100
checkColumnIndex(column);
@@ -72,65 +103,77 @@ public String getColumnName(int column) throws SQLException {
72103

73104
@Override
74105
public String getSchemaName(int column) throws SQLException {
75-
return null;
106+
checkColumnIndex(column);
107+
return "";
76108
}
77109

78110
@Override
79111
public int getPrecision(int column) throws SQLException {
80-
throw new SQLFeatureNotSupportedException();
112+
checkColumnIndex(column);
113+
return sqlMetadata.get(column - 1).getType().getPrecision();
81114
}
82115

116+
83117
@Override
84118
public int getScale(int column) throws SQLException {
85-
throw new SQLFeatureNotSupportedException();
119+
checkColumnIndex(column);
120+
return sqlMetadata.get(column - 1).getType().getScale();
86121
}
87122

88123
@Override
89124
public String getTableName(int column) throws SQLException {
90-
throw new SQLFeatureNotSupportedException();
125+
checkColumnIndex(column);
126+
// extra table name or, at least table ID is required in meta
127+
// to be able to fetch the table name.
128+
return "";
91129
}
92130

93131
@Override
94132
public String getCatalogName(int column) throws SQLException {
95-
return null;
133+
checkColumnIndex(column);
134+
return "";
96135
}
97136

98137
@Override
99138
public int getColumnType(int column) throws SQLException {
100-
return Types.OTHER;
139+
checkColumnIndex(column);
140+
return sqlMetadata.get(column - 1).getType().getJdbcType().getTypeNumber();
101141
}
102142

103143
@Override
104144
public String getColumnTypeName(int column) throws SQLException {
105-
return "scalar";
145+
checkColumnIndex(column);
146+
return sqlMetadata.get(column - 1).getType().getTypeName();
106147
}
107148

108149
@Override
109150
public boolean isReadOnly(int column) throws SQLException {
110-
throw new SQLFeatureNotSupportedException();
151+
checkColumnIndex(column);
152+
return readOnly;
111153
}
112154

113155
@Override
114156
public boolean isWritable(int column) throws SQLException {
115-
throw new SQLFeatureNotSupportedException();
157+
return !isReadOnly(column);
116158
}
117159

118160
@Override
119161
public boolean isDefinitelyWritable(int column) throws SQLException {
120-
throw new SQLFeatureNotSupportedException();
162+
return false;
121163
}
122164

123165
@Override
124166
public String getColumnClassName(int column) throws SQLException {
125-
throw new SQLFeatureNotSupportedException();
167+
checkColumnIndex(column);
168+
return sqlMetadata.get(column - 1).getType().getJdbcType().getJavaType().getName();
126169
}
127170

128171
@Override
129172
public <T> T unwrap(Class<T> type) throws SQLException {
130173
if (isWrapperFor(type)) {
131174
return type.cast(this);
132175
}
133-
throw new SQLNonTransientException("ResultSetMetadata does not wrap " + type.getName());
176+
throw new SQLNonTransientException("SQLResultSetMetadata does not wrap " + type.getName());
134177
}
135178

136179
@Override
@@ -150,7 +193,7 @@ void checkColumnIndex(int columnIndex) throws SQLException {
150193
@Override
151194
public String toString() {
152195
return "SQLResultSetMetaData{" +
153-
"sqlMetadata=" + sqlMetadata +
154-
'}';
196+
"sqlMetadata=" + sqlMetadata +
197+
'}';
155198
}
156199
}

0 commit comments

Comments
 (0)