@@ -113,15 +113,22 @@ record = recordBuilder.build();
113113 @ Override
114114 protected void handleField (ResultSet resultSet , StructuredRecord .Builder recordBuilder , Schema .Field field ,
115115 int columnIndex , int sqlType , int sqlPrecision , int sqlScale ) throws SQLException {
116- if (OracleSourceSchemaReader . ORACLE_TYPES . contains (sqlType ) || sqlType == Types . NCLOB || sqlType == Types . STRUCT ) {
116+ if (isOracleSpecificType (sqlType )) {
117117 handleOracleSpecificType (resultSet , recordBuilder , field , columnIndex , sqlType , sqlPrecision , sqlScale );
118118 } else {
119119 setField (resultSet , recordBuilder , field , columnIndex , sqlType , sqlPrecision , sqlScale );
120120 }
121121 }
122122
123+ protected boolean isOracleSpecificType (int sqlType ) {
124+ return OracleSourceSchemaReader .ORACLE_TYPES .contains (sqlType )
125+ || sqlType == Types .NCLOB
126+ || sqlType == Types .STRUCT ;
127+ }
128+
123129 @ Override
124- protected void setFieldValue (StructuredRecord .Builder recordBuilder , Schema .Field field , Object attrValue )
130+ protected void populateRecordField (Connection connection , StructuredRecord .Builder recordBuilder ,
131+ Schema .Field field , Object attrValue )
125132 throws SQLException {
126133 if (attrValue == null ) {
127134 recordBuilder .set (field .getName (), null );
@@ -132,7 +139,7 @@ protected void setFieldValue(StructuredRecord.Builder recordBuilder, Schema.Fiel
132139 : field .getSchema ();
133140 String attrClassName = attrValue .getClass ().getName ();
134141 if (attrValue instanceof Struct ) {
135- recordBuilder .set (field .getName (), convertStructToRecord ((Struct ) attrValue , fieldSchema ));
142+ recordBuilder .set (field .getName (), convertStructToRecord ((Struct ) attrValue , fieldSchema , connection ));
136143 return ;
137144 }
138145 if (attrValue instanceof Clob ) {
@@ -154,42 +161,61 @@ protected void setFieldValue(StructuredRecord.Builder recordBuilder, Schema.Fiel
154161 return ;
155162 }
156163 if (attrValue instanceof BigDecimal ) {
157- populateDecimalValue ( attrValue , fieldSchema , recordBuilder , field );
164+ handleDecimalValue (( BigDecimal ) attrValue , fieldSchema , recordBuilder , field );
158165 return ;
159166 }
160-
161167 if (attrValue instanceof Timestamp ) {
162- Timestamp timestamp = (Timestamp ) attrValue ;
163- if (Schema .LogicalType .DATETIME .equals (fieldSchema .getLogicalType ())) {
164- recordBuilder .setDateTime (field .getName (), timestamp .toLocalDateTime ());
165- } else {
166- super .setFieldValue (recordBuilder , field , attrValue );
167- }
168+ handleTimestampValue ((Timestamp ) attrValue , fieldSchema , recordBuilder , field , connection );
168169 return ;
169170 }
170171 if (attrValue instanceof OffsetDateTime ) {
171- ZonedDateTime zonedDateTime = ((OffsetDateTime ) attrValue ).atZoneSameInstant (ZoneId .of ("UTC" ));
172-
173- if (fieldSchema .getLogicalType () != null &&
174- (Schema .LogicalType .TIMESTAMP_MICROS .equals (fieldSchema .getLogicalType ()) ||
175- Schema .LogicalType .TIMESTAMP_MILLIS .equals (fieldSchema .getLogicalType ()))) {
176- recordBuilder .setTimestamp (field .getName (), zonedDateTime );
177- } else {
178- recordBuilder .set (field .getName (), zonedDateTime .toString ());
179- }
172+ handleOffsetDateTimeValue ((OffsetDateTime ) attrValue , fieldSchema , recordBuilder , field );
173+ return ;
174+ }
175+ if (isBfileValue (attrValue , field .getName ())) {
176+ recordBuilder .set (field .getName (), getBfileBytes (attrValue , field .getName ()));
180177 return ;
181178 }
182179
180+ super .populateRecordField (connection , recordBuilder , field , attrValue );
181+ }
182+
183+ private void handleTimestampValue (Timestamp timestamp , Schema fieldSchema ,
184+ StructuredRecord .Builder recordBuilder , Schema .Field field ,
185+ Connection connection ) throws SQLException {
186+ if (Schema .LogicalType .DATETIME .equals (fieldSchema .getLogicalType ())) {
187+ recordBuilder .setDateTime (field .getName (), timestamp .toLocalDateTime ());
188+ } else {
189+ super .populateRecordField (connection , recordBuilder , field , timestamp );
190+ }
191+ }
192+
193+ private void handleOffsetDateTimeValue (OffsetDateTime offsetDateTime , Schema fieldSchema ,
194+ StructuredRecord .Builder recordBuilder , Schema .Field field ) {
195+ ZonedDateTime zonedDateTime = offsetDateTime .atZoneSameInstant (ZoneId .of ("UTC" ));
196+ if (fieldSchema .getLogicalType () != null &&
197+ (Schema .LogicalType .TIMESTAMP_MICROS .equals (fieldSchema .getLogicalType ()) ||
198+ Schema .LogicalType .TIMESTAMP_MILLIS .equals (fieldSchema .getLogicalType ()))) {
199+ recordBuilder .setTimestamp (field .getName (), zonedDateTime );
200+ } else if (Schema .LogicalType .DATETIME .equals (fieldSchema .getLogicalType ())) {
201+ LocalDateTime systemLocalDateTime = offsetDateTime .atZoneSameInstant (ZoneId .systemDefault ()).toLocalDateTime ();
202+ recordBuilder .setDateTime (field .getName (), systemLocalDateTime );
203+ } else {
204+ recordBuilder .set (field .getName (), zonedDateTime .toString ());
205+ }
206+ }
207+
208+ private boolean isBfileValue (Object attrValue , String fieldName ) throws SQLException {
183209 ClassLoader oracleLoader = attrValue .getClass ().getClassLoader ();
184- try {
185- if (oracleLoader != null && oracleLoader .loadClass ("oracle.jdbc.OracleBfile" ).isInstance (attrValue )) {
186- recordBuilder .set (field .getName (), getBfileBytes (attrValue , field .getName ()));
187- return ;
188- }
189- } catch (ClassNotFoundException e ) {
190- throw new RuntimeException (e );
210+ try {
211+ if (oracleLoader != null && oracleLoader .loadClass ("oracle.jdbc.OracleBfile" ).isInstance (attrValue )) {
212+ return true ;
191213 }
192- super .setFieldValue (recordBuilder , field , attrValue );
214+ } catch (ClassNotFoundException e ) {
215+ throw new SQLException (String .format ("Column '%s' is of type 'BFILE', which is not supported with " +
216+ "this version of the JDBC driver." , fieldName ), e );
217+ }
218+ return false ;
193219 }
194220
195221 @ Override
@@ -248,9 +274,8 @@ protected void writeNonNullToDB(PreparedStatement stmt, Schema fieldSchema,
248274 }
249275 }
250276
251- private void populateDecimalValue ( Object attrValue , Schema fieldSchema ,
277+ private void handleDecimalValue ( BigDecimal bigDecimal , Schema fieldSchema ,
252278 StructuredRecord .Builder recordBuilder , Schema .Field field ) {
253- BigDecimal bigDecimal = (BigDecimal ) attrValue ;
254279 if (Schema .LogicalType .DECIMAL .equals (fieldSchema .getLogicalType ())) {
255280 recordBuilder .setDecimal (field .getName (), bigDecimal .setScale (fieldSchema .getScale (), RoundingMode .HALF_UP ));
256281 return ;
@@ -269,7 +294,7 @@ private void populateDecimalValue(Object attrValue, Schema fieldSchema,
269294 recordBuilder .set (field .getName (), bigDecimal .longValue ());
270295 break ;
271296 case STRING :
272- recordBuilder .set (field .getName (), bigDecimal .toString ());
297+ recordBuilder .set (field .getName (), bigDecimal .toPlainString ());
273298 break ;
274299 default :
275300 recordBuilder .set (field .getName (), bigDecimal );
@@ -455,7 +480,10 @@ private void handleOracleSpecificType(ResultSet resultSet, StructuredRecord.Buil
455480 case Types .STRUCT :
456481 Struct structValue = (Struct ) resultSet .getObject (columnIndex );
457482 if (structValue != null ) {
458- recordBuilder .set (field .getName (), convertStructToRecord (structValue , nonNullSchema ));
483+ recordBuilder .set (field .getName (), convertStructToRecord (structValue , nonNullSchema ,
484+ resultSet .getStatement ().getConnection ()));
485+ } else {
486+ recordBuilder .set (field .getName (), null );
459487 }
460488 break ;
461489 case Types .DECIMAL :
@@ -493,17 +521,18 @@ private void handleOracleSpecificType(ResultSet resultSet, StructuredRecord.Buil
493521 *
494522 * @param struct the SQL structured type containing the source data attributes
495523 * @param schema the target record schema defining the fields to map
524+ * @param connection the database connection
496525 * @return a populated {@code StructuredRecord} instance
497526 * @throws SQLException if an error occurs reading the struct attributes or metadata
498527 */
499- protected StructuredRecord convertStructToRecord (Struct struct , Schema schema )
528+ protected StructuredRecord convertStructToRecord (Struct struct , Schema schema , Connection connection )
500529 throws SQLException {
501- Map <String , Object > attributeMap = getAttributeMap (struct , schema );
530+ Map <String , Object > attributeMap = getAttributeMap (struct , schema , connection );
502531 StructuredRecord .Builder builder = StructuredRecord .builder (schema );
503532
504533 for (Schema .Field field : schema .getFields ()) {
505534 Object attrValue = attributeMap .get (field .getName ());
506- setFieldValue ( builder , field , attrValue );
535+ populateRecordField ( connection , builder , field , attrValue );
507536 }
508537 return builder .build ();
509538 }
@@ -514,29 +543,28 @@ protected StructuredRecord convertStructToRecord(Struct struct, Schema schema)
514543 *
515544 * @param struct the source SQL structured type
516545 * @param schema the target schema used for context in error messages
546+ * @param connection the database connection
517547 * @return a case-insensitive {@code Map} linking column names to their attribute values
518548 * @throws SQLException if metadata extraction fails or driver-specific methods are inaccessible
519549 */
520- private Map <String , Object > getAttributeMap (Struct struct , Schema schema ) throws SQLException {
550+ protected Map <String , Object > getAttributeMap (Struct struct , Schema schema , Connection connection )
551+ throws SQLException {
521552 Map <String , Object > attributeMap = new TreeMap <>(String .CASE_INSENSITIVE_ORDER );
522553 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 ]);
554+ if (attributes != null ) {
555+ try {
556+ Object descriptor = struct .getClass ().getMethod ("getDescriptor" ).invoke (struct );
557+ ResultSetMetaData metaData =
558+ (ResultSetMetaData ) descriptor .getClass ().getMethod ("getMetaData" ).invoke (descriptor );
559+ for (int i = 1 ; i <= metaData .getColumnCount () && (i - 1 ) < attributes .length ; i ++) {
560+ attributeMap .put (metaData .getColumnName (i ), attributes [i - 1 ]);
561+ }
562+ } catch (Exception e ) {
563+ throw new SQLException (String .format ("Failed to retrieve attribute metadata for Oracle STRUCT schema '%s': %s" ,
564+ schema .getRecordName (), e .getMessage ()));
530565 }
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 );
538566 }
539- return attributeMap ;
567+ return attributeMap ;
540568 }
541569
542570 /**
0 commit comments