Skip to content

Commit 30bda80

Browse files
authored
Merge pull request #770 from grantatspothero/grantnicholas/fixJdbcDriverFloatInfNaNHandling
3.1 jdbc driver broke float handling for nan/inf
2 parents 4acc1b3 + 0f5de18 commit 30bda80

4 files changed

Lines changed: 213 additions & 7 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package ru.yandex.clickhouse.response.parser;
2+
3+
import java.sql.SQLException;
4+
import java.util.TimeZone;
5+
6+
import ru.yandex.clickhouse.except.ClickHouseUnknownException;
7+
import ru.yandex.clickhouse.response.ByteFragment;
8+
import ru.yandex.clickhouse.response.ClickHouseColumnInfo;
9+
10+
final class ClickHouseFloatParser extends ClickHouseValueParser<Float> {
11+
12+
private static ClickHouseFloatParser instance;
13+
14+
static ClickHouseFloatParser getInstance() {
15+
if (instance == null) {
16+
instance = new ClickHouseFloatParser();
17+
}
18+
return instance;
19+
}
20+
21+
private ClickHouseFloatParser() {
22+
// prevent instantiation
23+
}
24+
25+
@Override
26+
public Float parse(ByteFragment value, ClickHouseColumnInfo columnInfo,
27+
TimeZone resultTimeZone) throws SQLException
28+
{
29+
if (value.isNull()) {
30+
return null;
31+
}
32+
if (value.isNaN()) {
33+
return Float.valueOf(Float.NaN);
34+
}
35+
String s = value.asString();
36+
switch (s) {
37+
case "+inf":
38+
case "inf":
39+
return Float.valueOf(Float.POSITIVE_INFINITY);
40+
case "-inf":
41+
return Float.valueOf(Float.NEGATIVE_INFINITY);
42+
default:
43+
try {
44+
return Float.valueOf(s);
45+
} catch (NumberFormatException nfe) {
46+
throw new ClickHouseUnknownException(
47+
"Error parsing '" + s + "' as Float",
48+
nfe);
49+
}
50+
}
51+
}
52+
53+
@Override
54+
protected Float getDefaultValue() {
55+
return Float.valueOf(0);
56+
}
57+
58+
}

clickhouse-jdbc/src/main/java/ru/yandex/clickhouse/response/parser/ClickHouseValueParser.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,7 @@ public abstract class ClickHouseValueParser<T> {
4141
Boolean.FALSE);
4242
register(Date.class, ClickHouseSQLDateParser.getInstance());
4343
register(Double.class, ClickHouseDoubleParser.getInstance());
44-
register(Float.class,
45-
Float::valueOf,
46-
Float.valueOf(0f),
47-
Float.valueOf(Float.NaN));
44+
register(Float.class, ClickHouseFloatParser.getInstance());
4845
register(Instant.class, ClickHouseInstantParser.getInstance());
4946
register(Integer.class, Integer::decode, Integer.valueOf(0));
5047
register(LocalDate.class, ClickHouseLocalDateParser.getInstance());
@@ -152,8 +149,8 @@ public static final double parseDouble(ByteFragment value, ClickHouseColumnInfo
152149
public static final float parseFloat(ByteFragment value, ClickHouseColumnInfo columnInfo)
153150
throws SQLException
154151
{
155-
Double d = getParser(Double.class).parse(value, columnInfo, null);
156-
return d != null ? d.floatValue() : 0.0f;
152+
Float f = getParser(Float.class).parse(value, columnInfo, null);
153+
return f != null ? f.floatValue() : 0.0f;
157154
}
158155

159156
/**

clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/ClickHouseResultSetTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ public void withTotals() throws Exception {
167167
assertEquals(70511139L, rs.getLong(2));
168168
}
169169

170+
@Test
170171
public void withTotalsAndEmptyStrings() throws Exception {
171172
String response = "SiteName\tCountry\n" +
172173
"String\tString\n" +
@@ -266,6 +267,61 @@ public void withTotalsSingleIntColumn() throws Exception {
266267
assertEquals(0L, rs.getLong(1));
267268
}
268269

270+
@Test
271+
public void withTotalsSingleFloatColumn() throws Exception {
272+
String response =
273+
"Code\n"
274+
+ "Float32\n"
275+
+ "1.0\n"
276+
+ "NaN\n"
277+
+ "nan\n"
278+
+ "Infinity\n"
279+
+ "+Infinity\n"
280+
+ "-Infinity\n"
281+
+ "inf\n"
282+
+ "+inf\n"
283+
+ "-inf\n"
284+
+ "\n" // with totals separator row
285+
+ "0"; // with totals values row
286+
287+
ByteArrayInputStream is = new ByteArrayInputStream(response.getBytes("UTF-8"));
288+
289+
ClickHouseResultSet rs = buildResultSet(is, 1024, "db", "table", true, null, null, props);
290+
291+
rs.next();
292+
assertEquals(1f, rs.getObject(1));
293+
294+
rs.next();
295+
assertTrue(Float.isNaN((Float) rs.getObject(1)));
296+
297+
rs.next();
298+
assertTrue(Float.isNaN((Float) rs.getObject(1)));
299+
300+
rs.next();
301+
assertEquals(Float.POSITIVE_INFINITY, rs.getObject(1));
302+
303+
rs.next();
304+
assertEquals(Float.POSITIVE_INFINITY, rs.getObject(1));
305+
306+
rs.next();
307+
assertEquals(Float.NEGATIVE_INFINITY, rs.getObject(1));
308+
309+
rs.next();
310+
assertEquals(Float.POSITIVE_INFINITY, rs.getObject(1));
311+
312+
rs.next();
313+
assertEquals(Float.POSITIVE_INFINITY, rs.getObject(1));
314+
315+
rs.next();
316+
assertEquals(Float.NEGATIVE_INFINITY, rs.getObject(1));
317+
318+
assertFalse(rs.next());
319+
320+
rs.getTotals();
321+
assertEquals(0L, rs.getLong(1));
322+
}
323+
324+
269325
@Test(groups = "unit")
270326
public void withTotalsSingleNullableColumn() throws Exception {
271327
String response =

clickhouse-jdbc/src/test/java/ru/yandex/clickhouse/response/parser/ClickHouseValueParserTest.java

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package ru.yandex.clickhouse.response.parser;
22

33
import java.sql.SQLException;
4-
4+
import org.testng.annotations.DataProvider;
55
import org.testng.annotations.Test;
66

77
import ru.yandex.clickhouse.response.ByteFragment;
@@ -13,6 +13,40 @@
1313
import static org.testng.Assert.fail;
1414

1515
public class ClickHouseValueParserTest {
16+
/**
17+
* Generates test data for floats.
18+
*/
19+
@DataProvider(name = "float_test_data")
20+
public Object[][] floatTestData() {
21+
return new Object [][] {
22+
{"100.0", 100.0f},
23+
{"NaN", Float.NaN},
24+
{"Infinity", Float.POSITIVE_INFINITY},
25+
{"+Infinity", Float.POSITIVE_INFINITY},
26+
{"-Infinity", Float.NEGATIVE_INFINITY},
27+
{"nan", Float.NaN},
28+
{"inf", Float.POSITIVE_INFINITY},
29+
{"+inf", Float.POSITIVE_INFINITY},
30+
{"-inf", Float.NEGATIVE_INFINITY}
31+
};
32+
}
33+
34+
/**
35+
* Generates test data for doubles.
36+
*/
37+
@DataProvider(name = "double_test_data")
38+
public Object[][] doubleTestData() {
39+
return new Object [][] {
40+
{"100.0", 100.0d},
41+
{"Infinity", Double.POSITIVE_INFINITY},
42+
{"+Infinity", Double.POSITIVE_INFINITY},
43+
{"-Infinity", Double.NEGATIVE_INFINITY},
44+
{"nan", Double.NaN},
45+
{"inf", Double.POSITIVE_INFINITY},
46+
{"+inf", Double.POSITIVE_INFINITY},
47+
{"-inf", Double.NEGATIVE_INFINITY}
48+
};
49+
}
1650

1751
@Test(groups = "unit")
1852
public void testParseInt() throws Exception {
@@ -161,4 +195,65 @@ public void testParseBoolean() throws SQLException {
161195
assertFalse(ClickHouseValueParser.parseBoolean(ByteFragment.fromString(" true"), columnInfo));
162196
}
163197

198+
@Test (dataProvider = "float_test_data")
199+
public void testParseFloat(String byteFragmentString, Float expectedValue) throws SQLException {
200+
ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Float32", "columnName", null);
201+
float floatDelta = 0.001f;
202+
if (expectedValue.isNaN()) {
203+
assertTrue(Float.isNaN(ClickHouseValueParser.parseFloat(
204+
ByteFragment.fromString(byteFragmentString), columnInfo)
205+
));
206+
} else {
207+
assertEquals(ClickHouseValueParser.parseFloat(
208+
ByteFragment.fromString(byteFragmentString), columnInfo), expectedValue, floatDelta
209+
);
210+
}
211+
}
212+
213+
@Test (dataProvider = "double_test_data")
214+
public void testParseDouble(String byteFragmentString, Double expectedValue) throws SQLException {
215+
ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Float64", "columnName", null);
216+
double doubleDelta = 0.001;
217+
if (expectedValue.isNaN()) {
218+
assertTrue(Double.isNaN(ClickHouseValueParser.parseDouble(
219+
ByteFragment.fromString(byteFragmentString), columnInfo)
220+
));
221+
} else {
222+
assertEquals(ClickHouseValueParser.parseDouble(
223+
ByteFragment.fromString(byteFragmentString), columnInfo), expectedValue, doubleDelta
224+
);
225+
}
226+
}
227+
228+
@Test (dataProvider = "float_test_data")
229+
public void testGetParserFloat(String byteFragmentString, Float expectedValue) throws SQLException {
230+
ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Float32", "columnName", null);
231+
float floatDelta = 0.001f;
232+
233+
if (expectedValue.isNaN()) {
234+
assertTrue(Float.isNaN(ClickHouseValueParser.getParser(Float.class).parse(
235+
ByteFragment.fromString(byteFragmentString), columnInfo, null)
236+
));
237+
} else {
238+
assertEquals(ClickHouseValueParser.getParser(Float.class).parse(
239+
ByteFragment.fromString(byteFragmentString), columnInfo, null), expectedValue, floatDelta
240+
);
241+
}
242+
}
243+
244+
@Test (dataProvider = "double_test_data")
245+
public void testGetParserDouble(String byteFragmentString, Double expectedValue) throws SQLException {
246+
ClickHouseColumnInfo columnInfo = ClickHouseColumnInfo.parse("Float64", "columnName", null);
247+
double doubleDelta = 0.001d;
248+
249+
if (expectedValue.isNaN()) {
250+
assertTrue(Double.isNaN(ClickHouseValueParser.getParser(Double.class).parse(
251+
ByteFragment.fromString(byteFragmentString), columnInfo, null)
252+
));
253+
} else {
254+
assertEquals(ClickHouseValueParser.getParser(Double.class).parse(
255+
ByteFragment.fromString(byteFragmentString), columnInfo, null), expectedValue, doubleDelta
256+
);
257+
}
258+
}
164259
}

0 commit comments

Comments
 (0)