1
1
package com .getindata .connectors .http .internal .table .lookup ;
2
2
3
- import java .util .ArrayList ;
4
- import java .util .List ;
3
+ import java .net .http .HttpResponse ;
4
+ import java .util .*;
5
+ import java .util .stream .Collectors ;
6
+ import java .util .stream .Stream ;
5
7
import javax .annotation .Nullable ;
6
8
7
9
import lombok .extern .slf4j .Slf4j ;
15
17
import org .apache .flink .table .connector .source .LookupTableSource ;
16
18
import org .apache .flink .table .connector .source .abilities .SupportsLimitPushDown ;
17
19
import org .apache .flink .table .connector .source .abilities .SupportsProjectionPushDown ;
20
+ import org .apache .flink .table .connector .source .abilities .SupportsReadingMetadata ;
18
21
import org .apache .flink .table .connector .source .lookup .AsyncLookupFunctionProvider ;
19
22
import org .apache .flink .table .connector .source .lookup .LookupFunctionProvider ;
20
23
import org .apache .flink .table .connector .source .lookup .PartialCachingAsyncLookupProvider ;
21
24
import org .apache .flink .table .connector .source .lookup .PartialCachingLookupProvider ;
22
25
import org .apache .flink .table .connector .source .lookup .cache .LookupCache ;
23
- import org .apache .flink .table .data .RowData ;
26
+ import org .apache .flink .table .data .* ;
24
27
import org .apache .flink .table .factories .DynamicTableFactory ;
25
28
import org .apache .flink .table .factories .FactoryUtil ;
26
29
import org .apache .flink .table .functions .AsyncLookupFunction ;
42
45
43
46
@ Slf4j
44
47
public class HttpLookupTableSource
45
- implements LookupTableSource , SupportsProjectionPushDown , SupportsLimitPushDown {
48
+ implements LookupTableSource , SupportsReadingMetadata , SupportsProjectionPushDown , SupportsLimitPushDown {
46
49
47
50
private DataType physicalRowDataType ;
48
51
@@ -54,6 +57,16 @@ public class HttpLookupTableSource
54
57
@ Nullable
55
58
private final LookupCache cache ;
56
59
60
+ // --------------------------------------------------------------------------------------------
61
+ // Mutable attributes
62
+ // --------------------------------------------------------------------------------------------
63
+
64
+ /** Data type that describes the final output of the source. */
65
+ protected DataType producedDataType ;
66
+
67
+ /** Metadata that is appended at the end of a physical source row. */
68
+ protected List <String > metadataKeys ;
69
+
57
70
public HttpLookupTableSource (
58
71
DataType physicalRowDataType ,
59
72
HttpLookupConfig lookupConfig ,
@@ -111,13 +124,26 @@ protected LookupRuntimeProvider getLookupRuntimeProvider(LookupRow lookupRow,
111
124
responseSchemaDecoder ,
112
125
PollingClientFactory <RowData >
113
126
pollingClientFactory ) {
114
-
127
+ MetadataConverter [] metadataConverters ={};
128
+ if (this .metadataKeys != null ) {
129
+ this .metadataKeys .stream ()
130
+ .map (
131
+ k ->
132
+ Stream .of (HttpLookupTableSource .ReadableMetadata .values ())
133
+ .filter (rm -> rm .key .equals (k ))
134
+ .findFirst ()
135
+ .orElseThrow (IllegalStateException ::new ))
136
+ .map (m -> m .converter )
137
+ .toArray (MetadataConverter []::new );
138
+ }
115
139
HttpTableLookupFunction dataLookupFunction =
116
140
new HttpTableLookupFunction (
117
141
pollingClientFactory ,
118
142
responseSchemaDecoder ,
119
143
lookupRow ,
120
- lookupConfig
144
+ lookupConfig ,
145
+ metadataConverters ,
146
+ this .producedDataType
121
147
);
122
148
if (lookupConfig .isUseAsync ()) {
123
149
AsyncLookupFunction asyncLookupFunction =
@@ -256,4 +282,100 @@ private LookupSchemaEntry<RowData> processRow(RowField rowField, int parentIndex
256
282
RowData .createFieldGetter (type1 , parentIndex ));
257
283
}
258
284
}
285
+
286
+ @ Override
287
+ public Map <String , DataType > listReadableMetadata () {
288
+ final Map <String , DataType > metadataMap = new LinkedHashMap <>();
289
+
290
+ decodingFormat .listReadableMetadata ()
291
+ .forEach ((key , value ) -> metadataMap .put (key , value ));
292
+
293
+ // according to convention, the order of the final row must be
294
+ // PHYSICAL + FORMAT METADATA + CONNECTOR METADATA
295
+ // where the format metadata has highest precedence
296
+ // add connector metadata
297
+ Stream .of (ReadableMetadata .values ()).forEachOrdered (m -> metadataMap .putIfAbsent (m .key , m .dataType ));
298
+ return metadataMap ;
299
+ }
300
+
301
+ @ Override
302
+ public void applyReadableMetadata (List <String > metadataKeys , DataType producedDataType ) {
303
+ // separate connector and format metadata
304
+ final List <String > connectorMetadataKeys = new ArrayList <>(metadataKeys );
305
+ final Map <String , DataType > formatMetadata = decodingFormat .listReadableMetadata ();
306
+ // store non connector keys and remove them from the connectorMetadataKeys.
307
+ List <String > formatMetadataKeys = new ArrayList <>();
308
+ Set <String > metadataKeysSet = metadataKeys .stream ().collect (Collectors .toSet ());
309
+ for (ReadableMetadata rm : ReadableMetadata .values ()) {
310
+ String metadataKeyToCheck = rm .name ();
311
+ if (!metadataKeysSet .contains (metadataKeyToCheck )) {
312
+ formatMetadataKeys .add (metadataKeyToCheck );
313
+ connectorMetadataKeys .remove (metadataKeyToCheck );
314
+ }
315
+ }
316
+ // push down format metadata keys
317
+ if (formatMetadata .size () > 0 ) {
318
+ final List <String > requestedFormatMetadataKeys =
319
+ formatMetadataKeys .stream ().collect (Collectors .toList ());
320
+ decodingFormat .applyReadableMetadata (requestedFormatMetadataKeys );
321
+ }
322
+ this .metadataKeys = connectorMetadataKeys ;
323
+ this .producedDataType = producedDataType ;
324
+ }
325
+ // --------------------------------------------------------------------------------------------
326
+ // Metadata handling
327
+ // --------------------------------------------------------------------------------------------
328
+ enum ReadableMetadata {
329
+ HTTP_ERROR_STRING (
330
+ "error_string" ,
331
+ DataTypes .STRING (),
332
+ new MetadataConverter () {
333
+ private static final long serialVersionUID = 1L ;
334
+ @ Override
335
+ public Object read (String msg , HttpResponse httpResponse ) {
336
+ return StringData .fromString (msg );
337
+ }
338
+ }),
339
+ HTTP_ERROR_CODE (
340
+ "error_code" ,
341
+ DataTypes .INT (),
342
+ new MetadataConverter () {
343
+ private static final long serialVersionUID = 1L ;
344
+ @ Override
345
+ public Object read (String msg , HttpResponse httpResponse ) {
346
+ return httpResponse != null ? httpResponse .statusCode ():null ;
347
+ }
348
+ }),
349
+ HTTP_HEADERS (
350
+ "error_headers" ,
351
+ DataTypes .MAP (DataTypes .STRING (), DataTypes .ARRAY (DataTypes .STRING ())),
352
+ new MetadataConverter () {
353
+ private static final long serialVersionUID = 1L ;
354
+ @ Override
355
+ public Object read (String msg , HttpResponse httpResponse ) {
356
+ if (httpResponse == null ) return null ;
357
+ Map <String , List <String >> httpHeadersMap = httpResponse .headers ().map ();
358
+ Map <StringData , ArrayData > stringDataMap = new HashMap <>();
359
+ for (String key : httpHeadersMap .keySet ()) {
360
+ List <StringData > strDataList = new ArrayList <>();
361
+ httpHeadersMap .get (key ).stream ()
362
+ .forEach ((c ) -> strDataList .add (StringData .fromString (c )));
363
+ stringDataMap .put (StringData .fromString (key ), new GenericArrayData (strDataList .toArray ()));
364
+ }
365
+ return new GenericMapData (stringDataMap );
366
+ }
367
+ });
368
+ final String key ;
369
+
370
+ final DataType dataType ;
371
+ final MetadataConverter converter ;
372
+
373
+ //TODO decide if we need a MetadataConverter
374
+ ReadableMetadata (String key , DataType dataType , MetadataConverter converter ) {
375
+ this .key = key ;
376
+ this .dataType = dataType ;
377
+ this .converter = converter ;
378
+ }
379
+ }
259
380
}
381
+
0 commit comments