|
1 | 1 | package com.clickhouse.client.api.data_formats; |
2 | 2 |
|
| 3 | +import com.clickhouse.client.api.ClientException; |
| 4 | +import com.clickhouse.client.api.data_formats.internal.NumberConverter; |
3 | 5 | import com.clickhouse.client.api.internal.SchemaUtils; |
4 | 6 | import com.clickhouse.client.api.metadata.TableSchema; |
5 | 7 | import com.clickhouse.data.ClickHouseColumn; |
|
9 | 11 | import com.clickhouse.data.value.ClickHouseGeoPolygonValue; |
10 | 12 | import com.clickhouse.data.value.ClickHouseGeoRingValue; |
11 | 13 |
|
| 14 | +import java.lang.reflect.Array; |
12 | 15 | import java.math.BigDecimal; |
13 | 16 | import java.math.BigInteger; |
14 | 17 | import java.net.Inet4Address; |
@@ -151,9 +154,21 @@ public double getDouble(String colName) { |
151 | 154 | @Override |
152 | 155 | public boolean getBoolean(String colName) { |
153 | 156 | Object val = currentRow.get(colName); |
154 | | - if (val instanceof Boolean) return (Boolean) val; |
155 | | - if (val instanceof Number) return ((Number) val).intValue() != 0; |
156 | | - return Boolean.parseBoolean(val.toString()); |
| 157 | + if (val instanceof Boolean) { |
| 158 | + return (Boolean) val; |
| 159 | + } |
| 160 | + if (val instanceof Number) { |
| 161 | + // Match AbstractBinaryFormatReader (SerializerUtils.convertToBoolean): |
| 162 | + // any non-zero integral value is true, zero is false. Fractional |
| 163 | + // values keep the same behavior because they are truncated by |
| 164 | + // longValue() before the zero check. |
| 165 | + return ((Number) val).longValue() != 0; |
| 166 | + } |
| 167 | + if (val == null) { |
| 168 | + throw new ClientException("Column '" + colName + "' has null value and cannot be converted to boolean"); |
| 169 | + } |
| 170 | + throw new ClientException("Cannot convert value of type " + val.getClass().getName() |
| 171 | + + " in column '" + colName + "' to boolean"); |
157 | 172 | } |
158 | 173 |
|
159 | 174 | @Override |
@@ -224,52 +239,150 @@ public ClickHouseGeoMultiPolygonValue getGeoMultiPolygon(String colName) { |
224 | 239 |
|
225 | 240 | @Override |
226 | 241 | public <T> List<T> getList(String colName) { |
227 | | - return (List<T>) currentRow.get(colName); |
| 242 | + Object val = currentRow.get(colName); |
| 243 | + if (val == null) { |
| 244 | + return null; |
| 245 | + } |
| 246 | + if (!(val instanceof List<?>)) { |
| 247 | + throw new ClientException("Column '" + colName + "' is not of array type (actual: " |
| 248 | + + val.getClass().getName() + ")"); |
| 249 | + } |
| 250 | + return (List<T>) val; |
228 | 251 | } |
229 | 252 |
|
230 | 253 | @Override |
231 | 254 | public byte[] getByteArray(String colName) { |
232 | | - throw new UnsupportedOperationException(); |
| 255 | + return getPrimitiveArray(colName, byte.class); |
233 | 256 | } |
234 | 257 |
|
235 | 258 | @Override |
236 | 259 | public int[] getIntArray(String colName) { |
237 | | - throw new UnsupportedOperationException(); |
| 260 | + return getPrimitiveArray(colName, int.class); |
238 | 261 | } |
239 | 262 |
|
240 | 263 | @Override |
241 | 264 | public long[] getLongArray(String colName) { |
242 | | - throw new UnsupportedOperationException(); |
| 265 | + return getPrimitiveArray(colName, long.class); |
243 | 266 | } |
244 | 267 |
|
245 | 268 | @Override |
246 | 269 | public float[] getFloatArray(String colName) { |
247 | | - throw new UnsupportedOperationException(); |
| 270 | + return getPrimitiveArray(colName, float.class); |
248 | 271 | } |
249 | 272 |
|
250 | 273 | @Override |
251 | 274 | public double[] getDoubleArray(String colName) { |
252 | | - throw new UnsupportedOperationException(); |
| 275 | + return getPrimitiveArray(colName, double.class); |
253 | 276 | } |
254 | 277 |
|
255 | 278 | @Override |
256 | 279 | public boolean[] getBooleanArray(String colName) { |
257 | | - throw new UnsupportedOperationException(); |
| 280 | + return getPrimitiveArray(colName, boolean.class); |
258 | 281 | } |
259 | 282 |
|
260 | 283 | @Override |
261 | 284 | public short[] getShortArray(String colName) { |
262 | | - throw new UnsupportedOperationException(); |
| 285 | + return getPrimitiveArray(colName, short.class); |
263 | 286 | } |
264 | 287 |
|
265 | 288 | @Override |
266 | 289 | public String[] getStringArray(String colName) { |
267 | | - throw new UnsupportedOperationException(); |
| 290 | + List<?> list = asArrayList(colName); |
| 291 | + if (list == null) { |
| 292 | + return null; |
| 293 | + } |
| 294 | + String[] out = new String[list.size()]; |
| 295 | + for (int i = 0; i < list.size(); i++) { |
| 296 | + Object el = list.get(i); |
| 297 | + out[i] = el == null ? null : el.toString(); |
| 298 | + } |
| 299 | + return out; |
268 | 300 | } |
269 | 301 |
|
270 | 302 | @Override |
271 | 303 | public Object[] getObjectArray(String colName) { |
272 | | - throw new UnsupportedOperationException(); |
| 304 | + List<?> list = asArrayList(colName); |
| 305 | + return list == null ? null : list.toArray(new Object[0]); |
| 306 | + } |
| 307 | + |
| 308 | + /** |
| 309 | + * Returns the value of the given column as a {@code List}, or {@code null} |
| 310 | + * if the value is missing. Throws {@link ClientException} when the column |
| 311 | + * exists but is not an array. |
| 312 | + */ |
| 313 | + private List<?> asArrayList(String colName) { |
| 314 | + Object val = currentRow.get(colName); |
| 315 | + if (val == null) { |
| 316 | + return null; |
| 317 | + } |
| 318 | + if (!(val instanceof List<?>)) { |
| 319 | + throw new ClientException("Column '" + colName + "' is not of array type (actual: " |
| 320 | + + val.getClass().getName() + ")"); |
| 321 | + } |
| 322 | + return (List<?>) val; |
| 323 | + } |
| 324 | + |
| 325 | + @SuppressWarnings("unchecked") |
| 326 | + private <T> T getPrimitiveArray(String colName, Class<?> componentType) { |
| 327 | + List<?> list = asArrayList(colName); |
| 328 | + if (list == null) { |
| 329 | + return null; |
| 330 | + } |
| 331 | + try { |
| 332 | + Object array = Array.newInstance(componentType, list.size()); |
| 333 | + for (int i = 0; i < list.size(); i++) { |
| 334 | + Object el = list.get(i); |
| 335 | + if (el == null) { |
| 336 | + throw new ClientException("Column '" + colName |
| 337 | + + "' contains a null element which cannot fit into an array of primitive " |
| 338 | + + componentType.getName()); |
| 339 | + } |
| 340 | + Array.set(array, i, coerceToComponent(el, componentType)); |
| 341 | + } |
| 342 | + return (T) array; |
| 343 | + } catch (ClassCastException | IllegalArgumentException e) { |
| 344 | + throw new ClientException("Value of column '" + colName |
| 345 | + + "' cannot be converted to an array of " + componentType.getName(), e); |
| 346 | + } |
| 347 | + } |
| 348 | + |
| 349 | + /** |
| 350 | + * Coerces a parsed JSON element to a boxed primitive type. JSON parsers |
| 351 | + * may materialize numeric array elements as different boxed types |
| 352 | + * (e.g. {@code Integer}, {@code Long}, {@code Double}, {@code BigDecimal}), |
| 353 | + * so element-level conversion is necessary before populating a typed |
| 354 | + * primitive array. |
| 355 | + */ |
| 356 | + private static Object coerceToComponent(Object value, Class<?> componentType) { |
| 357 | + if (componentType == byte.class) { |
| 358 | + return NumberConverter.toByte(value); |
| 359 | + } |
| 360 | + if (componentType == short.class) { |
| 361 | + return NumberConverter.toShort(value); |
| 362 | + } |
| 363 | + if (componentType == int.class) { |
| 364 | + return NumberConverter.toInt(value); |
| 365 | + } |
| 366 | + if (componentType == long.class) { |
| 367 | + return NumberConverter.toLong(value); |
| 368 | + } |
| 369 | + if (componentType == float.class) { |
| 370 | + return NumberConverter.toFloat(value); |
| 371 | + } |
| 372 | + if (componentType == double.class) { |
| 373 | + return NumberConverter.toDouble(value); |
| 374 | + } |
| 375 | + if (componentType == boolean.class) { |
| 376 | + if (value instanceof Boolean) { |
| 377 | + return value; |
| 378 | + } |
| 379 | + if (value instanceof Number) { |
| 380 | + return ((Number) value).longValue() != 0; |
| 381 | + } |
| 382 | + throw new IllegalArgumentException( |
| 383 | + "Cannot convert " + value.getClass().getName() + " to boolean array element"); |
| 384 | + } |
| 385 | + return value; |
273 | 386 | } |
274 | 387 |
|
275 | 388 | @Override |
|
0 commit comments