@@ -46,6 +46,7 @@ import io.micronaut.context.annotation.Value
46
46
import jakarta.inject.Singleton
47
47
import java.math.BigInteger
48
48
import java.time.OffsetDateTime
49
+ import java.util.SequencedMap
49
50
import java.util.UUID
50
51
51
52
/* *
@@ -148,8 +149,8 @@ data class Meta(
148
149
TimestampWithTimezoneValue (
149
150
OffsetDateTime .parse(
150
151
value,
151
- AirbyteValueDeepCoercingMapper .DATE_TIME_FORMATTER
152
- )
152
+ AirbyteValueDeepCoercingMapper .DATE_TIME_FORMATTER ,
153
+ ),
153
154
)
154
155
}
155
156
}
@@ -158,7 +159,7 @@ data class Meta(
158
159
COLUMN_NAME_DATA -> toObjectValue(value.deserializeToNode())
159
160
else ->
160
161
throw NotImplementedError (
161
- " Column name $metaColumnName is not yet supported. This is probably a bug."
162
+ " Column name $metaColumnName is not yet supported. This is probably a bug." ,
162
163
)
163
164
}
164
165
}
@@ -196,9 +197,9 @@ data class DestinationRecord(
196
197
message.record.emittedAt,
197
198
Meta (
198
199
message.record.meta?.changes?.map { Meta .Change (it.field, it.change, it.reason) }
199
- ? : emptyList()
200
+ ? : emptyList(),
200
201
),
201
- serialized.length.toLong()
202
+ serialized.length.toLong(),
202
203
)
203
204
}
204
205
fun asDestinationRecordRaw (): DestinationRecordRaw {
@@ -223,8 +224,8 @@ data class DestinationRecordAirbyteValue(
223
224
224
225
data class EnrichedDestinationRecordAirbyteValue (
225
226
val stream : DestinationStream ,
226
- val declaredFields : Map <String , EnrichedAirbyteValue >,
227
- val undeclaredFields : Map <String , JsonNode >,
227
+ val declaredFields : LinkedHashMap <String , EnrichedAirbyteValue >,
228
+ val undeclaredFields : LinkedHashMap <String , JsonNode >,
228
229
val emittedAtMs : Long ,
229
230
/* *
230
231
* The airbyte_meta field as received by the destination connector. Note that this field is NOT
@@ -305,9 +306,9 @@ data class DestinationRecordRaw(
305
306
rawData.record.emittedAt,
306
307
Meta (
307
308
rawData.record.meta?.changes?.map { Meta .Change (it.field, it.change, it.reason) }
308
- ? : emptyList()
309
+ ? : emptyList(),
309
310
),
310
- serialized.length.toLong()
311
+ serialized.length.toLong(),
311
312
)
312
313
}
313
314
@@ -321,45 +322,42 @@ data class DestinationRecordRaw(
321
322
fun asEnrichedDestinationRecordAirbyteValue (): EnrichedDestinationRecordAirbyteValue {
322
323
val rawJson = asRawJson()
323
324
324
- // Get the set of field names defined in the schema
325
- val schemaFields =
325
+ // Get the fields from the schema
326
+ val schemaFields: SequencedMap < String , FieldType > =
326
327
when (schema) {
327
- is ObjectType -> schema.properties.keys
328
- else -> emptySet ()
328
+ is ObjectType -> schema.properties
329
+ else -> linkedMapOf ()
329
330
}
330
331
331
- val declaredFields = mutableMapOf <String , EnrichedAirbyteValue >()
332
- val undeclaredFields = mutableMapOf <String , JsonNode >()
332
+ val declaredFields = LinkedHashMap <String , EnrichedAirbyteValue >()
333
+ val undeclaredFields = LinkedHashMap <String , JsonNode >()
333
334
334
- // Process fields from the raw JSON
335
- rawJson.fields().forEach { (fieldName, fieldValue) ->
336
- when {
337
- schemaFields.contains(fieldName) -> {
338
- // Declared field (exists in schema)
339
- val fieldType =
340
- (schema as ObjectType ).properties[fieldName]?.type
341
- ? : throw IllegalStateException (
342
- " Field '$fieldName ' exists in schema keys but not in properties"
343
- )
344
-
345
- val enrichedValue =
346
- EnrichedAirbyteValue (
347
- abValue = NullValue ,
348
- type = fieldType,
349
- name = fieldName,
350
- airbyteMetaField = null ,
351
- )
352
- AirbyteValueCoercer .coerce(fieldValue.toAirbyteValue(), fieldType)?.let {
353
- enrichedValue.abValue = it
354
- }
355
- ? : enrichedValue.nullify(Reason .DESTINATION_SERIALIZATION_ERROR )
335
+ // Process fields from the raw JSON.
336
+ // First, get the declared fields, in the order defined by the catalog
337
+ schemaFields.forEach { (fieldName, fieldType) ->
338
+ if (! rawJson.has(fieldName)) {
339
+ return @forEach
340
+ }
356
341
357
- declaredFields[fieldName] = enrichedValue
358
- }
359
- else -> {
360
- // Undeclared field (not in schema)
361
- undeclaredFields[fieldName] = fieldValue
362
- }
342
+ val fieldValue = rawJson[fieldName]
343
+ val enrichedValue =
344
+ EnrichedAirbyteValue (
345
+ abValue = NullValue ,
346
+ type = fieldType.type,
347
+ name = fieldName,
348
+ airbyteMetaField = null ,
349
+ )
350
+ AirbyteValueCoercer .coerce(fieldValue.toAirbyteValue(), fieldType.type)?.let {
351
+ enrichedValue.abValue = it
352
+ }
353
+ ? : enrichedValue.nullify(Reason .DESTINATION_SERIALIZATION_ERROR )
354
+
355
+ declaredFields[fieldName] = enrichedValue
356
+ }
357
+ // Then, get the undeclared fields
358
+ rawJson.fields().forEach { (fieldName, fieldValue) ->
359
+ if (! schemaFields.contains(fieldName)) {
360
+ undeclaredFields[fieldName] = fieldValue
363
361
}
364
362
}
365
363
@@ -407,7 +405,7 @@ data class DestinationFile(
407
405
bytes = null ,
408
406
fileRelativePath = null ,
409
407
modified = null ,
410
- sourceFileUrl = null
408
+ sourceFileUrl = null ,
411
409
)
412
410
413
411
@get:JsonProperty(" file_url" )
@@ -453,7 +451,7 @@ data class DestinationFile(
453
451
.withStream(stream.descriptor.name)
454
452
.withNamespace(stream.descriptor.namespace)
455
453
.withEmittedAt(emittedAtMs)
456
- .withAdditionalProperty(" file" , file)
454
+ .withAdditionalProperty(" file" , file),
457
455
)
458
456
}
459
457
}
@@ -472,8 +470,8 @@ private fun statusToProtocolMessage(
472
470
.withStreamStatus(
473
471
AirbyteStreamStatusTraceMessage ()
474
472
.withStreamDescriptor(stream.asProtocolObject())
475
- .withStatus(status)
476
- )
473
+ .withStatus(status),
474
+ ),
477
475
)
478
476
479
477
data class DestinationRecordStreamComplete (
@@ -558,7 +556,7 @@ data class StreamCheckpoint(
558
556
) : this (
559
557
Checkpoint (
560
558
DestinationStream .Descriptor (streamNamespace, streamName),
561
- state = blob.deserializeToNode()
559
+ state = blob.deserializeToNode(),
562
560
),
563
561
Stats (sourceRecordCount),
564
562
destinationRecordCount?.let { Stats (it) },
@@ -604,7 +602,7 @@ data class GlobalCheckpoint(
604
602
.withGlobal(
605
603
AirbyteGlobalState ()
606
604
.withSharedState(state)
607
- .withStreamStates(checkpoints.map { it.asProtocolObject() })
605
+ .withStreamStates(checkpoints.map { it.asProtocolObject() }),
608
606
)
609
607
decorateStateMessage(stateMessage)
610
608
return AirbyteMessage ().withType(AirbyteMessage .Type .STATE ).withState(stateMessage)
@@ -617,7 +615,7 @@ data object Undefined : DestinationMessage {
617
615
// Arguably we could accept the raw message in the constructor?
618
616
// But that seems weird - when would we ever want to reemit that message?
619
617
throw NotImplementedError (
620
- " Unrecognized messages cannot be safely converted back to a protocol object."
618
+ " Unrecognized messages cannot be safely converted back to a protocol object." ,
621
619
)
622
620
}
623
621
}
@@ -641,7 +639,7 @@ class DestinationMessageFactory(
641
639
is Long -> it
642
640
else ->
643
641
throw IllegalArgumentException (
644
- " Unexpected value for $name : $it (${it::class .qualifiedName} )"
642
+ " Unexpected value for $name : $it (${it::class .qualifiedName} )" ,
645
643
)
646
644
}
647
645
}
@@ -671,12 +669,12 @@ class DestinationMessageFactory(
671
669
fileRelativePath = fileMessage[" file_relative_path" ] as String? ,
672
670
modified =
673
671
toLong(fileMessage[" modified" ], " message.record.modified" ),
674
- sourceFileUrl = fileMessage[" source_file_url" ] as String?
675
- )
672
+ sourceFileUrl = fileMessage[" source_file_url" ] as String? ,
673
+ ),
676
674
)
677
675
} catch (e: Exception ) {
678
676
throw IllegalArgumentException (
679
- " Failed to construct file message: ${e.message} "
677
+ " Failed to construct file message: ${e.message} " ,
680
678
)
681
679
}
682
680
} else {
@@ -699,24 +697,24 @@ class DestinationMessageFactory(
699
697
if (fileTransferEnabled) {
700
698
DestinationFileStreamComplete (
701
699
stream,
702
- message.trace.emittedAt?.toLong() ? : 0L
700
+ message.trace.emittedAt?.toLong() ? : 0L ,
703
701
)
704
702
} else {
705
703
DestinationRecordStreamComplete (
706
704
stream,
707
- message.trace.emittedAt?.toLong() ? : 0L
705
+ message.trace.emittedAt?.toLong() ? : 0L ,
708
706
)
709
707
}
710
708
AirbyteStreamStatus .INCOMPLETE ->
711
709
if (fileTransferEnabled) {
712
710
DestinationFileStreamIncomplete (
713
711
stream,
714
- message.trace.emittedAt?.toLong() ? : 0L
712
+ message.trace.emittedAt?.toLong() ? : 0L ,
715
713
)
716
714
} else {
717
715
DestinationRecordStreamIncomplete (
718
716
stream,
719
- message.trace.emittedAt?.toLong() ? : 0L
717
+ message.trace.emittedAt?.toLong() ? : 0L ,
720
718
)
721
719
}
722
720
else -> Undefined
@@ -763,7 +761,7 @@ class DestinationMessageFactory(
763
761
val descriptor = streamState.streamDescriptor
764
762
return Checkpoint (
765
763
stream = DestinationStream .Descriptor (descriptor.namespace, descriptor.name),
766
- state = runCatching { streamState.streamState }.getOrNull()
764
+ state = runCatching { streamState.streamState }.getOrNull(),
767
765
)
768
766
}
769
767
}
0 commit comments