Skip to content

Commit 504abb7

Browse files
committed
Support filtering in LibreOffice Base (1.5)
This is a backport of the PR duckdb#573 to `v1.5-variegata` stable branch. This PR adds support to column filters in [LibreOffice Base](https://www.libreoffice.org/discover/base/) table browser. It supports all non-composite JDBC types except the temporal types (`DATE`, `TIME`, `TIMESTAMP`). LibreOffice does not use parameters and generate SQL filters for temporal types using [escape sequences](https://docs.oracle.com/cd/E13157_01/wlevs/docs30/jdbc_drivers/sqlescape.html) (for example: `{d '2020-12-31'}`) that are not supported by DuckDB parser. Ref: duckdb#366 Ref: duckdblabs/duckdb-internal#5164
1 parent 4e3044c commit 504abb7

3 files changed

Lines changed: 96 additions & 10 deletions

File tree

src/main/java/org/duckdb/DuckDBDatabaseMetaData.java

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,9 @@
22

33
import static java.lang.System.lineSeparator;
44

5-
import java.sql.Connection;
6-
import java.sql.DatabaseMetaData;
7-
import java.sql.PreparedStatement;
8-
import java.sql.ResultSet;
9-
import java.sql.RowIdLifetime;
10-
import java.sql.SQLException;
11-
import java.sql.SQLFeatureNotSupportedException;
12-
import java.sql.Statement;
13-
import java.sql.Types;
5+
import java.sql.*;
146
import java.util.Arrays;
7+
import java.util.List;
158
import java.util.Map;
169
import java.util.stream.Collectors;
1710

@@ -1109,7 +1102,62 @@ public ResultSet getCrossReference(String parentCatalog, String parentSchema, St
11091102

11101103
@Override
11111104
public ResultSet getTypeInfo() throws SQLException {
1112-
throw new SQLFeatureNotSupportedException("getTypeInfo");
1105+
List<TypeInfoEntry> entries = Arrays.asList(new TypeInfoEntry("BOOLEAN", Types.BIT, typePredBasic),
1106+
new TypeInfoEntry("TINYINT", Types.TINYINT, typePredBasic),
1107+
new TypeInfoEntry("SMALLINT", Types.SMALLINT, typePredBasic),
1108+
new TypeInfoEntry("INTEGER", Types.INTEGER, typePredBasic),
1109+
new TypeInfoEntry("BIGINT", Types.BIGINT, typePredBasic),
1110+
new TypeInfoEntry("BIGINT", Types.BIGINT, typePredBasic),
1111+
new TypeInfoEntry("FLOAT", Types.FLOAT, typePredBasic),
1112+
new TypeInfoEntry("REAL", Types.REAL, typePredBasic),
1113+
new TypeInfoEntry("DOUBLE", Types.DOUBLE, typePredBasic),
1114+
new TypeInfoEntry("DECIMAL", Types.NUMERIC, typePredBasic, 0, 38),
1115+
new TypeInfoEntry("DECIMAL", Types.DECIMAL, typePredBasic, 0, 38),
1116+
new TypeInfoEntry("VARCHAR", Types.CHAR, typePredChar),
1117+
new TypeInfoEntry("VARCHAR", Types.VARCHAR, typePredChar),
1118+
new TypeInfoEntry("VARCHAR", Types.LONGVARCHAR, typePredChar),
1119+
new TypeInfoEntry("DATE", Types.DATE, typePredBasic),
1120+
new TypeInfoEntry("TIME", Types.TIME, typePredBasic),
1121+
new TypeInfoEntry("TIMESTAMP", Types.TIMESTAMP, typePredBasic),
1122+
new TypeInfoEntry("BLOB", Types.BINARY, typePredChar),
1123+
new TypeInfoEntry("BLOB", Types.VARBINARY, typePredChar),
1124+
new TypeInfoEntry("BLOB", Types.LONGVARBINARY, typePredChar),
1125+
new TypeInfoEntry("NULL", Types.LONGVARBINARY, typePredBasic));
1126+
1127+
StringBuilder sb = new StringBuilder(QUERY_SB_DEFAULT_CAPACITY);
1128+
boolean first = true;
1129+
for (TypeInfoEntry en : entries) {
1130+
if (first) {
1131+
sb.append("SELECT").append(lineSeparator());
1132+
first = false;
1133+
} else {
1134+
sb.append("UNION ALL SELECT").append(lineSeparator());
1135+
}
1136+
sb.append(" '" + en.name + "'::VARCHAR AS TYPE_NAME").append(TRAILING_COMMA).append(lineSeparator());
1137+
sb.append(" " + en.sqlType + "::INTEGER AS DATA_TYPE").append(TRAILING_COMMA).append(lineSeparator());
1138+
sb.append(" 0::INTEGER AS PRECISION").append(TRAILING_COMMA).append(lineSeparator());
1139+
sb.append(" NULL::VARCHAR AS LITERAL_PREFIX").append(TRAILING_COMMA).append(lineSeparator());
1140+
sb.append(" NULL::VARCHAR AS LITERAL_SUFFIX").append(TRAILING_COMMA).append(lineSeparator());
1141+
sb.append(" NULL::VARCHAR AS CREATE_PARAMS").append(TRAILING_COMMA).append(lineSeparator());
1142+
sb.append(" " + typeNullableUnknown + "::SMALLINT AS NULLABLE")
1143+
.append(TRAILING_COMMA)
1144+
.append(lineSeparator());
1145+
sb.append(" TRUE::BOOL AS CASE_SENSITIVE").append(TRAILING_COMMA).append(lineSeparator());
1146+
sb.append(" " + en.searchable + "::SMALLINT AS SEARCHABLE").append(TRAILING_COMMA).append(lineSeparator());
1147+
sb.append(" FALSE::BOOL AS UNSIGNED_ATTRIBUTE").append(TRAILING_COMMA).append(lineSeparator());
1148+
sb.append(" FALSE::BOOL AS FIXED_PREC_SCALE").append(TRAILING_COMMA).append(lineSeparator());
1149+
sb.append(" FALSE::BOOL AS AUTO_INCREMENT").append(TRAILING_COMMA).append(lineSeparator());
1150+
sb.append(" NULL::VARCHAR AS LOCAL_TYPE_NAME").append(TRAILING_COMMA).append(lineSeparator());
1151+
sb.append(" 0::SMALLINT AS MINIMUM_SCALE").append(TRAILING_COMMA).append(lineSeparator());
1152+
sb.append(" 0::SMALLINT AS MAXIMUM_SCALE").append(TRAILING_COMMA).append(lineSeparator());
1153+
sb.append(" 0::INTEGER AS SQL_DATA_TYPE").append(TRAILING_COMMA).append(lineSeparator());
1154+
sb.append(" 0::INTEGER AS SQL_DATETIME_SUB").append(TRAILING_COMMA).append(lineSeparator());
1155+
sb.append(" 10::INTEGER AS NUM_PREC_RADIX").append(TRAILING_COMMA).append(lineSeparator());
1156+
}
1157+
1158+
PreparedStatement ps = conn.prepareStatement(sb.toString());
1159+
ps.closeOnCompletion();
1160+
return ps.executeQuery();
11131161
}
11141162

11151163
@Override
@@ -1492,4 +1540,28 @@ private static String nullPatternToWildcard(String pattern) {
14921540
}
14931541
return pattern;
14941542
}
1543+
1544+
private static class TypeInfoEntry {
1545+
final String name;
1546+
final int sqlType;
1547+
final int searchable;
1548+
final int precision;
1549+
final int scale;
1550+
1551+
private TypeInfoEntry(String name, int sqlType, int searchable) {
1552+
this.name = name;
1553+
this.sqlType = sqlType;
1554+
this.searchable = searchable;
1555+
this.precision = 0;
1556+
this.scale = 0;
1557+
}
1558+
1559+
public TypeInfoEntry(String name, int sqlType, int searchable, int precision, int scale) {
1560+
this.name = name;
1561+
this.sqlType = sqlType;
1562+
this.searchable = searchable;
1563+
this.precision = precision;
1564+
this.scale = scale;
1565+
}
1566+
}
14951567
}

src/main/java/org/duckdb/DuckDBDriver.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ public Connection connect(String url, Properties info) throws SQLException {
110110
// to be established.
111111
props.remove("path");
112112

113+
// LibreOffice Base adds this option with value 'simple'
114+
props.remove("Type");
115+
113116
// DuckLake connection
114117
if (pp.shortUrl.startsWith(DUCKLAKE_URL_PREFIX)) {
115118
setDefaultOptionValue(props, JDBC_PIN_DB, true);

src/test/java/org/duckdb/TestMetadata.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,4 +1015,15 @@ public static void test_medatada_cross_reference() throws Exception {
10151015
}
10161016
}
10171017
}
1018+
1019+
public static void test_metadata_type_info() throws Exception {
1020+
try (Connection conn = DriverManager.getConnection(JDBC_URL); ResultSet rs = conn.getMetaData().getTypeInfo()) {
1021+
// static table, not produced by engine, only checking the count
1022+
int count = 0;
1023+
while (rs.next()) {
1024+
count += 1;
1025+
}
1026+
assertEquals(count, 21);
1027+
}
1028+
}
10181029
}

0 commit comments

Comments
 (0)