|
48 | 48 | import java.time.ZoneOffset; |
49 | 49 | import java.time.ZonedDateTime; |
50 | 50 | import java.util.List; |
| 51 | +import java.util.Map; |
| 52 | +import java.util.TreeMap; |
51 | 53 |
|
52 | 54 | /** |
53 | 55 | * Oracle Source implementation {@link org.apache.hadoop.mapreduce.lib.db.DBWritable} and |
@@ -151,24 +153,8 @@ protected void setFieldValue(StructuredRecord.Builder recordBuilder, Schema.Fiel |
151 | 153 | recordBuilder.set(field.getName(), attrValue.toString()); |
152 | 154 | return; |
153 | 155 | } |
154 | | - if ("oracle.sql.NCLOB".equals(attrClassName)) { |
155 | | - recordBuilder.set(field.getName(), attrValue.toString()); |
156 | | - return; |
157 | | - } |
158 | 156 | if (attrValue instanceof BigDecimal) { |
159 | | - BigDecimal bigDecimal = (BigDecimal) attrValue; |
160 | | - if (Schema.LogicalType.DECIMAL.equals(fieldSchema.getLogicalType())) { |
161 | | - recordBuilder.setDecimal(field.getName(), bigDecimal.setScale(fieldSchema.getScale(), RoundingMode.HALF_UP)); |
162 | | - return; |
163 | | - } |
164 | | - switch (fieldSchema.getType()) { |
165 | | - case DOUBLE: recordBuilder.set(field.getName(), bigDecimal.doubleValue()); break; |
166 | | - case FLOAT: recordBuilder.set(field.getName(), bigDecimal.floatValue()); break; |
167 | | - case INT: recordBuilder.set(field.getName(), bigDecimal.intValue()); break; |
168 | | - case LONG: recordBuilder.set(field.getName(), bigDecimal.longValue()); break; |
169 | | - case STRING: recordBuilder.set(field.getName(), bigDecimal.toString()); break; |
170 | | - default: recordBuilder.set(field.getName(), bigDecimal); |
171 | | - } |
| 157 | + populateDecimalValue(attrValue, fieldSchema, recordBuilder, field); |
172 | 158 | return; |
173 | 159 | } |
174 | 160 |
|
@@ -262,6 +248,34 @@ protected void writeNonNullToDB(PreparedStatement stmt, Schema fieldSchema, |
262 | 248 | } |
263 | 249 | } |
264 | 250 |
|
| 251 | + private void populateDecimalValue(Object attrValue, Schema fieldSchema, |
| 252 | + StructuredRecord.Builder recordBuilder, Schema.Field field) { |
| 253 | + BigDecimal bigDecimal = (BigDecimal) attrValue; |
| 254 | + if (Schema.LogicalType.DECIMAL.equals(fieldSchema.getLogicalType())) { |
| 255 | + recordBuilder.setDecimal(field.getName(), bigDecimal.setScale(fieldSchema.getScale(), RoundingMode.HALF_UP)); |
| 256 | + return; |
| 257 | + } |
| 258 | + switch (fieldSchema.getType()) { |
| 259 | + case DOUBLE: |
| 260 | + recordBuilder.set(field.getName(), bigDecimal.doubleValue()); |
| 261 | + break; |
| 262 | + case FLOAT: |
| 263 | + recordBuilder.set(field.getName(), bigDecimal.floatValue()); |
| 264 | + break; |
| 265 | + case INT: |
| 266 | + recordBuilder.set(field.getName(), bigDecimal.intValue()); |
| 267 | + break; |
| 268 | + case LONG: |
| 269 | + recordBuilder.set(field.getName(), bigDecimal.longValue()); |
| 270 | + break; |
| 271 | + case STRING: |
| 272 | + recordBuilder.set(field.getName(), bigDecimal.toString()); |
| 273 | + break; |
| 274 | + default: |
| 275 | + recordBuilder.set(field.getName(), bigDecimal); |
| 276 | + } |
| 277 | + } |
| 278 | + |
265 | 279 | /** |
266 | 280 | * Creates an instance of 'oracle.sql.TIMESTAMPTZ' which corresponds to the specified timestamp with time zone string. |
267 | 281 | * @param connection sql connection. |
@@ -474,20 +488,57 @@ private void handleOracleSpecificType(ResultSet resultSet, StructuredRecord.Buil |
474 | 488 | } |
475 | 489 | } |
476 | 490 |
|
| 491 | + /** |
| 492 | + * Converts a JDBC {@link Struct} into a {@link StructuredRecord} based on the provided schema. |
| 493 | + * |
| 494 | + * @param struct the SQL structured type containing the source data attributes |
| 495 | + * @param schema the target record schema defining the fields to map |
| 496 | + * @return a populated {@code StructuredRecord} instance |
| 497 | + * @throws SQLException if an error occurs reading the struct attributes or metadata |
| 498 | + */ |
477 | 499 | protected StructuredRecord convertStructToRecord(Struct struct, Schema schema) |
478 | 500 | throws SQLException { |
479 | | - Object[] attributes = struct.getAttributes(); |
480 | | - List<Schema.Field> fields = schema.getFields(); |
| 501 | + Map<String, Object> attributeMap = getAttributeMap(struct, schema); |
481 | 502 | StructuredRecord.Builder builder = StructuredRecord.builder(schema); |
482 | 503 |
|
483 | | - for (int index = 0; index < attributes.length; index++) { |
484 | | - Schema.Field field = fields.get(index); |
485 | | - Object attrValue = attributes[index]; |
| 504 | + for (Schema.Field field : schema.getFields()) { |
| 505 | + Object attrValue = attributeMap.get(field.getName()); |
486 | 506 | setFieldValue(builder, field, attrValue); |
487 | 507 | } |
488 | 508 | return builder.build(); |
489 | 509 | } |
490 | 510 |
|
| 511 | + /** |
| 512 | + * Extracts attributes from a {@link Struct} into a case-insensitive map indexed by column name. |
| 513 | + * Uses reflection to extract underlying metadata (e.g., from Oracle StructDescriptor). |
| 514 | + * |
| 515 | + * @param struct the source SQL structured type |
| 516 | + * @param schema the target schema used for context in error messages |
| 517 | + * @return a case-insensitive {@code Map} linking column names to their attribute values |
| 518 | + * @throws SQLException if metadata extraction fails or driver-specific methods are inaccessible |
| 519 | + */ |
| 520 | + private Map<String, Object> getAttributeMap(Struct struct, Schema schema) throws SQLException { |
| 521 | + Map<String, Object> attributeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); |
| 522 | + Object[] attributes = struct.getAttributes(); |
| 523 | + |
| 524 | + try { |
| 525 | + Object descriptor = struct.getClass().getMethod("getDescriptor").invoke(struct); |
| 526 | + ResultSetMetaData metaData = |
| 527 | + (ResultSetMetaData) descriptor.getClass().getMethod("getMetaData").invoke(descriptor); |
| 528 | + for (int i = 1; i <= metaData.getColumnCount() && (i - 1) < attributes.length; i++) { |
| 529 | + attributeMap.put(metaData.getColumnName(i), attributes[i - 1]); |
| 530 | + } |
| 531 | + } catch (SQLException | NoSuchMethodException e) { |
| 532 | + throw new SQLException(String.format("Failed to retrieve attribute metadata for Oracle STRUCT schema '%s': %s", |
| 533 | + schema.getRecordName(), e.getMessage()), e); |
| 534 | + } catch (InvocationTargetException | IllegalAccessException e) { |
| 535 | + throw new SQLException(String.format("Unable to retrieve attribute metadata for Oracle STRUCT schema '%s'. " |
| 536 | + + "Ensure the Oracle JDBC driver supports JDBC StructDescriptor metadata.", |
| 537 | + schema.getRecordName()), e); |
| 538 | + } |
| 539 | + return attributeMap; |
| 540 | + } |
| 541 | + |
491 | 542 | /** |
492 | 543 | * Get the scale set in Non-nullable schema associated with the schema |
493 | 544 | * */ |
|
0 commit comments