@@ -10,13 +10,15 @@ import io.airbyte.cdk.load.command.DestinationCatalog
10
10
import io.airbyte.cdk.load.command.DestinationStream
11
11
import io.airbyte.cdk.load.data.AirbyteType
12
12
import io.airbyte.cdk.load.data.AirbyteValue
13
+ import io.airbyte.cdk.load.data.AirbyteValueCoercer
13
14
import io.airbyte.cdk.load.data.AirbyteValueDeepCoercingMapper
14
15
import io.airbyte.cdk.load.data.EnrichedAirbyteValue
15
16
import io.airbyte.cdk.load.data.FieldCategory
16
17
import io.airbyte.cdk.load.data.IntegerValue
17
18
import io.airbyte.cdk.load.data.NullValue
18
19
import io.airbyte.cdk.load.data.ObjectType
19
20
import io.airbyte.cdk.load.data.StringValue
21
+ import io.airbyte.cdk.load.data.TimestampTypeWithTimezone
20
22
import io.airbyte.cdk.load.data.TimestampWithTimezoneValue
21
23
import io.airbyte.cdk.load.data.json.toAirbyteValue
22
24
import io.airbyte.cdk.load.message.CheckpointMessage.Checkpoint
@@ -36,7 +38,9 @@ import io.airbyte.protocol.models.v0.AirbyteTraceMessage
36
38
import io.micronaut.context.annotation.Value
37
39
import jakarta.inject.Singleton
38
40
import java.math.BigInteger
41
+ import java.time.Instant
39
42
import java.time.OffsetDateTime
43
+ import java.time.ZoneOffset
40
44
import java.util.*
41
45
42
46
/* *
@@ -174,12 +178,26 @@ data class DestinationRecordAirbyteValue(
174
178
data class EnrichedDestinationRecordAirbyteValue (
175
179
val stream : DestinationStream .Descriptor ,
176
180
val declaredFields : Map <String , EnrichedAirbyteValue >,
177
- val airbyteMetaFields : Map <String , EnrichedAirbyteValue >,
178
181
val undeclaredFields : Map <String , JsonNode >,
179
182
val emittedAtMs : Long ,
180
183
val meta : Meta ? ,
181
184
val serializedSizeBytes : Long = 0L
182
- )
185
+ ) {
186
+ val airbyteMetaFields: Map <String , EnrichedAirbyteValue > by lazy {
187
+ mapOf (
188
+ " _airbyte_extracted_at" to
189
+ EnrichedAirbyteValue (
190
+ TimestampWithTimezoneValue (
191
+ OffsetDateTime .ofInstant(Instant .ofEpochMilli(emittedAtMs), ZoneOffset .UTC )
192
+ ),
193
+ TimestampTypeWithTimezone ,
194
+ name = " _airbyte_extracted_at" ,
195
+ FieldCategory .EXTRACTED_AT ,
196
+ ),
197
+ TODO (" all the other efields" ),
198
+ )
199
+ }
200
+ }
183
201
184
202
data class DestinationRecordRaw (
185
203
val stream : DestinationStream .Descriptor ,
@@ -215,7 +233,6 @@ data class DestinationRecordRaw(
215
233
}
216
234
217
235
val declaredFields = mutableMapOf<String , EnrichedAirbyteValue >()
218
- val airbyteMetaFields = mutableMapOf<String , EnrichedAirbyteValue >()
219
236
val undeclaredFields = mutableMapOf<String , JsonNode >()
220
237
221
238
// Process fields from the raw JSON
@@ -233,11 +250,22 @@ data class DestinationRecordRaw(
233
250
if (fieldValue.isNull) NullValue else fieldValue.toAirbyteValue()
234
251
declaredFields[fieldName] =
235
252
EnrichedAirbyteValue (
236
- value = airbyteValue,
237
- type = fieldType,
238
- name = fieldName,
239
- fieldCategory = FieldCategory .CLIENT_DATA
240
- )
253
+ value = airbyteValue,
254
+ type = fieldType,
255
+ name = fieldName,
256
+ fieldCategory = FieldCategory .CLIENT_DATA ,
257
+ )
258
+ .let {
259
+ val coercedValue = AirbyteValueCoercer .coerce(airbyteValue, schema)
260
+ if (coercedValue == null ) {
261
+ it.toNullified(
262
+ AirbyteRecordMessageMetaChange .Reason
263
+ .DESTINATION_SERIALIZATION_ERROR
264
+ )
265
+ } else {
266
+ it
267
+ }
268
+ }
241
269
}
242
270
else -> {
243
271
// Undeclared field (not in schema)
@@ -249,7 +277,6 @@ data class DestinationRecordRaw(
249
277
return EnrichedDestinationRecordAirbyteValue (
250
278
stream = stream,
251
279
declaredFields = declaredFields,
252
- airbyteMetaFields = airbyteMetaFields,
253
280
undeclaredFields = undeclaredFields,
254
281
emittedAtMs = rawData.record.emittedAt,
255
282
meta =
0 commit comments