11package com .getindata .connectors .http .internal .table .lookup ;
22
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 ;
57import javax .annotation .Nullable ;
68
79import lombok .extern .slf4j .Slf4j ;
1517import org .apache .flink .table .connector .source .LookupTableSource ;
1618import org .apache .flink .table .connector .source .abilities .SupportsLimitPushDown ;
1719import org .apache .flink .table .connector .source .abilities .SupportsProjectionPushDown ;
20+ import org .apache .flink .table .connector .source .abilities .SupportsReadingMetadata ;
1821import org .apache .flink .table .connector .source .lookup .AsyncLookupFunctionProvider ;
1922import org .apache .flink .table .connector .source .lookup .LookupFunctionProvider ;
2023import org .apache .flink .table .connector .source .lookup .PartialCachingAsyncLookupProvider ;
2124import org .apache .flink .table .connector .source .lookup .PartialCachingLookupProvider ;
2225import org .apache .flink .table .connector .source .lookup .cache .LookupCache ;
23- import org .apache .flink .table .data .RowData ;
26+ import org .apache .flink .table .data .* ;
2427import org .apache .flink .table .factories .DynamicTableFactory ;
2528import org .apache .flink .table .factories .FactoryUtil ;
2629import org .apache .flink .table .functions .AsyncLookupFunction ;
4245
4346@ Slf4j
4447public class HttpLookupTableSource
45- implements LookupTableSource , SupportsProjectionPushDown , SupportsLimitPushDown {
48+ implements LookupTableSource , SupportsReadingMetadata , SupportsProjectionPushDown , SupportsLimitPushDown {
4649
4750 private DataType physicalRowDataType ;
4851
@@ -54,6 +57,16 @@ public class HttpLookupTableSource
5457 @ Nullable
5558 private final LookupCache cache ;
5659
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+
5770 public HttpLookupTableSource (
5871 DataType physicalRowDataType ,
5972 HttpLookupConfig lookupConfig ,
@@ -111,13 +124,26 @@ protected LookupRuntimeProvider getLookupRuntimeProvider(LookupRow lookupRow,
111124 responseSchemaDecoder ,
112125 PollingClientFactory <RowData >
113126 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+ }
115139 HttpTableLookupFunction dataLookupFunction =
116140 new HttpTableLookupFunction (
117141 pollingClientFactory ,
118142 responseSchemaDecoder ,
119143 lookupRow ,
120- lookupConfig
144+ lookupConfig ,
145+ metadataConverters ,
146+ this .producedDataType
121147 );
122148 if (lookupConfig .isUseAsync ()) {
123149 AsyncLookupFunction asyncLookupFunction =
@@ -256,4 +282,100 @@ private LookupSchemaEntry<RowData> processRow(RowField rowField, int parentIndex
256282 RowData .createFieldGetter (type1 , parentIndex ));
257283 }
258284 }
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+ }
259380}
381+
0 commit comments