Skip to content

Commit 251a3d7

Browse files
authored
Merge pull request #2698 from ClickHouse/12/18/unknown_datatype_tests_and_improvements
[jdbc-v2, client-v2] Improve messages for unknown data types
2 parents b242955 + cfba24a commit 251a3d7

File tree

5 files changed

+189
-13
lines changed

5 files changed

+189
-13
lines changed

clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/ClickHouseStatementTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,12 +801,13 @@ public void testSimpleAggregateFunction() throws SQLException {
801801
try (ClickHouseConnection conn = newConnection(new Properties());
802802
ClickHouseStatement stmt = conn.createStatement();) {
803803
stmt.execute("drop table if exists test_simple_agg_func; "
804-
+ "CREATE TABLE test_simple_agg_func (x SimpleAggregateFunction(max, UInt64)) ENGINE=AggregatingMergeTree ORDER BY tuple(); "
805-
+ "INSERT INTO test_simple_agg_func VALUES(1)");
804+
+ "CREATE TABLE test_simple_agg_func (id UInt64, x SimpleAggregateFunction(max, UInt64)) ENGINE=AggregatingMergeTree ORDER BY tuple(id); "
805+
+ "INSERT INTO test_simple_agg_func VALUES(1, 1)");
806806

807807
try (ResultSet rs = stmt.executeQuery("select * from test_simple_agg_func")) {
808808
Assert.assertTrue(rs.next(), "Should have one row");
809809
Assert.assertEquals(rs.getLong(1), 1L);
810+
Assert.assertEquals(rs.getLong(2), 1L);
810811
Assert.assertFalse(rs.next(), "Should have only one row");
811812
}
812813
}

client-v2/src/main/java/com/clickhouse/client/api/internal/TableSchemaParser.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.clickhouse.client.api.internal;
22

3+
import com.clickhouse.client.api.ClientException;
34
import com.clickhouse.client.api.metadata.TableSchema;
45
import com.clickhouse.data.ClickHouseColumn;
56

@@ -23,7 +24,14 @@ public static TableSchema readTSKV(InputStream content, String table, String sql
2324
p.clear();
2425
if (!line.trim().isEmpty()) {
2526
p.load(new StringReader(line.replaceAll("\t", "\n")));
26-
ClickHouseColumn column = ClickHouseColumn.of(p.getProperty("name"), p.getProperty("type"));
27+
final String columnName = p.getProperty("name");
28+
final String columnType = p.getProperty("type");
29+
ClickHouseColumn column;
30+
try {
31+
column = ClickHouseColumn.of(columnName, columnType);
32+
} catch (IllegalArgumentException e) {
33+
throw new ClientException("Failed to parse column `"+ columnName + "` defined by type '" + columnType + "'", e);
34+
}
2735
String defaultType = p.getProperty("default_type");
2836
String defaultExpression = p.getProperty("default_expression");
2937
column.setHasDefault(defaultType != null && !defaultType.isEmpty());

client-v2/src/test/java/com/clickhouse/client/ParameterizedQueryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ void testStringParams(String paramValue) throws Exception {
240240
"SELECT " + column + " FROM " + table + " WHERE " + column + "='" + paramValue + "'").get();
241241
ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(r))
242242
{
243-
reader.next();
243+
Assert.assertNotNull(reader.next(), "No records were returned.");
244244
Assert.assertEquals(reader.getString(1), paramValue);
245245
}
246246
try (QueryResponse r = client.query(

client-v2/src/test/java/com/clickhouse/client/metadata/MetadataTests.java

Lines changed: 171 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
package com.clickhouse.client.metadata;
22

33
import com.clickhouse.client.BaseIntegrationTest;
4-
import com.clickhouse.client.ClickHouseClient;
5-
import com.clickhouse.client.ClickHouseConfig;
64
import com.clickhouse.client.ClickHouseNode;
7-
import com.clickhouse.client.ClickHouseNodeSelector;
85
import com.clickhouse.client.ClickHouseProtocol;
9-
import com.clickhouse.client.ClickHouseRequest;
10-
import com.clickhouse.client.ClickHouseResponse;
116
import com.clickhouse.client.ClickHouseServerForTest;
127
import com.clickhouse.client.api.Client;
8+
import com.clickhouse.client.api.command.CommandSettings;
139
import com.clickhouse.client.api.enums.Protocol;
1410
import com.clickhouse.client.api.internal.ServerSettings;
1511
import com.clickhouse.client.api.metadata.DefaultColumnToMethodMatchingStrategy;
1612
import com.clickhouse.client.api.metadata.TableSchema;
13+
import com.clickhouse.client.api.query.GenericRecord;
1714
import com.clickhouse.client.api.query.QuerySettings;
1815
import com.clickhouse.data.ClickHouseColumn;
1916
import com.clickhouse.data.ClickHouseDataType;
20-
import com.clickhouse.data.ClickHouseRecord;
17+
import com.clickhouse.data.ClickHouseVersion;
18+
import org.apache.commons.lang3.StringUtils;
2119
import org.testng.Assert;
2220
import org.testng.annotations.BeforeMethod;
2321
import org.testng.annotations.DataProvider;
2422
import org.testng.annotations.Test;
2523

26-
import java.util.Iterator;
24+
import java.util.HashSet;
2725
import java.util.List;
26+
import java.util.Set;
2827

2928
public class MetadataTests extends BaseIntegrationTest {
3029

@@ -106,6 +105,171 @@ public Object[][] testMatchingNormalizationData() {
106105
};
107106
}
108107

108+
@Test(groups = {"integration"})
109+
public void testCreateTableWithAllDataTypes() throws Exception {
110+
String tableName = "test_all_data_types";
111+
112+
// Query system.data_type_families to get all known types
113+
List<GenericRecord> dbTypes = client.queryAll("SELECT name, alias_to FROM system.data_type_families ORDER BY name");
114+
115+
// Types that cannot be used directly in CREATE TABLE columns
116+
Set<String> excludedTypes = new HashSet<>();
117+
excludedTypes.add("AggregateFunction");
118+
excludedTypes.add("SimpleAggregateFunction");
119+
excludedTypes.add("Nothing");
120+
excludedTypes.add("Nullable"); // Nullable is a wrapper, not a base type
121+
excludedTypes.add("LowCardinality"); // LowCardinality is a wrapper, not a base type
122+
excludedTypes.add("Enum"); // Enum is a base type, use Enum8 or Enum16 instead
123+
excludedTypes.add("Object"); // Deprecated and not used for while
124+
if (isCloud()) {
125+
excludedTypes.add("QBit"); // Due to env specific
126+
}
127+
128+
// Build column definitions
129+
StringBuilder createTableSql = new StringBuilder();
130+
createTableSql.append("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" (");
131+
132+
int columnIndex = 0;
133+
Set<String> addedTypes = new HashSet<>();
134+
135+
for (GenericRecord dbType : dbTypes) {
136+
String typeName = dbType.getString("name");
137+
String aliasTo = dbType.getString("alias_to");
138+
139+
// Use alias if available, otherwise use the name
140+
String actualType = StringUtils.isNotBlank(aliasTo) ? aliasTo : typeName;
141+
142+
// Skip excluded types and duplicates
143+
if (excludedTypes.contains(actualType) || addedTypes.contains(actualType)) {
144+
continue;
145+
}
146+
147+
// Generate column name and type definition
148+
String columnName = "col_" + columnIndex++;
149+
String columnType = getColumnTypeDefinition(actualType);
150+
151+
if (columnType != null) {
152+
createTableSql.append(columnName).append(" ").append(columnType).append(", ");
153+
addedTypes.add(actualType);
154+
}
155+
}
156+
157+
// Remove trailing comma and space
158+
if (createTableSql.length() > 0 && createTableSql.charAt(createTableSql.length() - 2) == ',') {
159+
createTableSql.setLength(createTableSql.length() - 2);
160+
}
161+
162+
createTableSql.append(") ENGINE = Memory");
163+
164+
// Create table with appropriate settings for experimental types
165+
CommandSettings commandSettings = new CommandSettings();
166+
// Allow Geometry type which may have variant ambiguity
167+
commandSettings.serverSetting("allow_suspicious_variant_types", "1");
168+
try {
169+
// Try to enable experimental types if version supports them
170+
if (isVersionMatch("[24.1,)")) {
171+
commandSettings.serverSetting("allow_experimental_variant_type", "1");
172+
}
173+
if (isVersionMatch("[24.8,)")) {
174+
commandSettings
175+
.serverSetting("allow_experimental_json_type", "1")
176+
.serverSetting("allow_experimental_dynamic_type", "1");
177+
}
178+
if (isVersionMatch("[25.5,)")) {
179+
commandSettings.serverSetting("enable_time_time64_type", "1");
180+
}
181+
if (isVersionMatch("[25.10,)") && !isCloud()) {
182+
commandSettings.serverSetting("allow_experimental_qbit_type", "1");
183+
}
184+
} catch (Exception e) {
185+
// If version check fails, continue without experimental settings
186+
}
187+
188+
try {
189+
client.execute("DROP TABLE IF EXISTS " + tableName).get().close();
190+
System.out.println(createTableSql);
191+
client.execute(createTableSql.toString(), commandSettings).get().close();
192+
193+
// Verify the schema
194+
TableSchema schema = client.getTableSchema(tableName);
195+
Assert.assertNotNull(schema, "Schema should not be null");
196+
Assert.assertEquals(schema.getTableName(), tableName);
197+
Assert.assertTrue(schema.getColumns().size() > 0, "Table should have at least one column");
198+
199+
// Verify that we have columns for the types we added
200+
// Some types might fail to create, so we check for at least 80% success rate
201+
Assert.assertTrue(schema.getColumns().size() >= addedTypes.size() * 0.8,
202+
"Expected at least 80% of types to be successfully created. Created: " + schema.getColumns().size() + ", Attempted: " + addedTypes.size());
203+
} finally {
204+
try {
205+
client.execute("DROP TABLE IF EXISTS " + tableName).get().close();
206+
} catch (Exception e) {
207+
// Ignore cleanup errors
208+
}
209+
}
210+
}
211+
212+
/**
213+
* Returns the column type definition for a given ClickHouse type name.
214+
* Returns null if the type cannot be used in CREATE TABLE.
215+
*/
216+
private String getColumnTypeDefinition(String typeName) {
217+
// Handle types that need parameters
218+
switch (typeName) {
219+
case "Decimal":
220+
return "Decimal(10, 2)";
221+
case "Decimal32":
222+
return "Decimal32(2)";
223+
case "Decimal64":
224+
return "Decimal64(3)";
225+
case "Decimal128":
226+
return "Decimal128(4)";
227+
case "Decimal256":
228+
return "Decimal256(5)";
229+
case "DateTime":
230+
return "DateTime";
231+
case "DateTime32":
232+
return "DateTime32";
233+
case "DateTime64":
234+
return "DateTime64(3)";
235+
case "FixedString":
236+
return "FixedString(10)";
237+
case "Enum":
238+
// Enum base type cannot be used without parameters, return null to skip it
239+
return null;
240+
case "Enum8":
241+
return "Enum8('a' = 1, 'b' = 2)";
242+
case "Enum16":
243+
return "Enum16('a' = 1, 'b' = 2)";
244+
case "Array":
245+
return "Array(String)";
246+
case "Map":
247+
return "Map(String, Int32)";
248+
case "Tuple":
249+
return "Tuple(String, Int32)";
250+
case "Nested":
251+
return "Nested(name String, value Int32)";
252+
case "Object":
253+
return "Object('json' String)";
254+
case "Variant":
255+
return "Variant(String, Int32)";
256+
case "QBit":
257+
// QBit requires two parameters: element type and number of elements
258+
return "QBit(Float32, 4)";
259+
case "Geometry":
260+
case "Geometry1":
261+
// Geometry type (requires allow_suspicious_variant_types = 1 setting)
262+
return "Geometry";
263+
default:
264+
// For simple types without parameters, return as-is
265+
return typeName;
266+
}
267+
}
268+
269+
public boolean isVersionMatch(String versionExpression) {
270+
List<GenericRecord> serverVersion = client.queryAll("SELECT version()");
271+
return ClickHouseVersion.of(serverVersion.get(0).getString(1)).check(versionExpression);
272+
}
109273
protected Client.Builder newClient() {
110274
ClickHouseNode node = getServer(ClickHouseProtocol.HTTP);
111275
boolean isSecure = isCloud();

jdbc-v2/src/main/java/com/clickhouse/jdbc/metadata/DatabaseMetaDataImpl.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,9 +1125,12 @@ public ResultSet getTypeInfo() throws SQLException {
11251125
if (type == null) {
11261126
try {
11271127
type = JdbcUtils.convertToSqlType(ClickHouseDataType.valueOf(typeName));
1128+
} catch (IllegalArgumentException e) {
1129+
log.error("Unknown type: " + typeName + ". Please check for a new version of the client.");
1130+
type = JDBCType.OTHER;
11281131
} catch (Exception e) {
1129-
log.error("Failed to convert column data type to SQL type: {}", typeName, e);
1130-
type = JDBCType.OTHER; // In case of error, return SQL type 0
1132+
log.error("Failed to get SQL type for type: " + typeName, e);
1133+
type = JDBCType.OTHER;
11311134
}
11321135
}
11331136

0 commit comments

Comments
 (0)