MAX_RETRY_TIMES = ConfigOptions.key("max-retry-times").intType().defaultValue(3);
28 |
29 | private int maxRetryTimes;
30 | private String host;
31 | private Integer port;
32 | private String password;
33 | private String delimiter;
34 | private Boolean async;
35 | private Long cacheSize;
36 | private Long cacheExpireMs;
37 | private String tableName;
38 | private String primaryKey;
39 |
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/java/com/hiscat/flink/custrom/format/json/JsonRowDataDeserializationSchema.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.custrom.format.json;
2 |
3 | import org.apache.flink.annotation.Internal;
4 | import org.apache.flink.api.common.serialization.DeserializationSchema;
5 | import org.apache.flink.api.common.typeinfo.TypeInformation;
6 | import org.apache.flink.formats.common.TimestampFormat;
7 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.json.JsonReadFeature;
8 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.DeserializationFeature;
9 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.JsonNode;
10 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
11 | import org.apache.flink.table.data.RowData;
12 | import org.apache.flink.table.types.logical.DecimalType;
13 | import org.apache.flink.table.types.logical.RowType;
14 | import org.apache.flink.table.types.logical.utils.LogicalTypeChecks;
15 |
16 | import javax.annotation.Nullable;
17 | import java.io.IOException;
18 | import java.util.Objects;
19 |
20 | import static java.lang.String.format;
21 | import static org.apache.flink.util.Preconditions.checkNotNull;
22 |
23 | /**
24 | * Deserialization schema from JSON to Flink Table/SQL internal data structure {@link RowData}.
25 | *
26 | * Deserializes a byte[]
message as a JSON object and reads the specified fields.
27 | *
28 | *
Failures during deserialization are forwarded as wrapped IOExceptions.
29 | */
30 | @Internal
31 | public class JsonRowDataDeserializationSchema implements DeserializationSchema {
32 | private static final long serialVersionUID = 1L;
33 |
34 | /**
35 | * Flag indicating whether to fail if a field is missing.
36 | */
37 | private final boolean failOnMissingField;
38 |
39 | /**
40 | * Flag indicating whether to ignore invalid fields/rows (default: throw an exception).
41 | */
42 | private final boolean ignoreParseErrors;
43 |
44 | /**
45 | * TypeInformation of the produced {@link RowData}.
46 | */
47 | private final TypeInformation resultTypeInfo;
48 |
49 | /**
50 | * Runtime converter that converts {@link JsonNode}s into objects of Flink SQL internal data
51 | * structures.
52 | */
53 | private final JsonToRowDataConverters.JsonToRowDataConverter runtimeConverter;
54 |
55 | /**
56 | * Object mapper for parsing the JSON.
57 | */
58 | private final ObjectMapper objectMapper = new ObjectMapper();
59 |
60 | /**
61 | * Timestamp format specification which is used to parse timestamp.
62 | */
63 | private final TimestampFormat timestampFormat;
64 |
65 | public JsonRowDataDeserializationSchema(
66 | RowType rowType,
67 | TypeInformation resultTypeInfo,
68 | boolean failOnMissingField,
69 | boolean ignoreParseErrors,
70 | TimestampFormat timestampFormat) {
71 | if (ignoreParseErrors && failOnMissingField) {
72 | throw new IllegalArgumentException(
73 | "JSON format doesn't support failOnMissingField and ignoreParseErrors are both enabled.");
74 | }
75 | this.resultTypeInfo = checkNotNull(resultTypeInfo);
76 | this.failOnMissingField = failOnMissingField;
77 | this.ignoreParseErrors = ignoreParseErrors;
78 | this.runtimeConverter =
79 | new JsonToRowDataConverters(failOnMissingField, ignoreParseErrors, timestampFormat)
80 | .createConverter(checkNotNull(rowType));
81 | this.timestampFormat = timestampFormat;
82 | boolean hasDecimalType =
83 | LogicalTypeChecks.hasNested(rowType, t -> t instanceof DecimalType);
84 | if (hasDecimalType) {
85 | objectMapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS);
86 | }
87 | objectMapper.configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature(), true);
88 | }
89 |
90 | @Override
91 | public RowData deserialize(@Nullable byte[] message) throws IOException {
92 | if (message == null) {
93 | return null;
94 | }
95 | try {
96 | return convertToRowData(deserializeToJsonNode(message));
97 | } catch (Throwable t) {
98 | if (ignoreParseErrors) {
99 | return null;
100 | }
101 | throw new IOException(
102 | format("Failed to deserialize JSON '%s'.", new String(message)), t);
103 | }
104 | }
105 |
106 | public JsonNode deserializeToJsonNode(byte[] message) throws IOException {
107 | return objectMapper.readTree(message);
108 | }
109 |
110 | public RowData convertToRowData(JsonNode message) {
111 | return (RowData) runtimeConverter.convert(message);
112 | }
113 |
114 | @Override
115 | public boolean isEndOfStream(RowData nextElement) {
116 | return false;
117 | }
118 |
119 | @Override
120 | public TypeInformation getProducedType() {
121 | return resultTypeInfo;
122 | }
123 |
124 | @Override
125 | public boolean equals(Object o) {
126 | if (this == o) {
127 | return true;
128 | }
129 | if (o == null || getClass() != o.getClass()) {
130 | return false;
131 | }
132 | JsonRowDataDeserializationSchema that = (JsonRowDataDeserializationSchema) o;
133 | return failOnMissingField == that.failOnMissingField
134 | && ignoreParseErrors == that.ignoreParseErrors
135 | && resultTypeInfo.equals(that.resultTypeInfo)
136 | && timestampFormat.equals(that.timestampFormat);
137 | }
138 |
139 | @Override
140 | public int hashCode() {
141 | return Objects.hash(failOnMissingField, ignoreParseErrors, resultTypeInfo, timestampFormat);
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/java/com/hiscat/flink/custrom/format/json/JsonRowDataSerializationSchema.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.custrom.format.json;
2 |
3 | import org.apache.flink.annotation.Internal;
4 | import org.apache.flink.api.common.serialization.SerializationSchema;
5 | import org.apache.flink.formats.common.TimestampFormat;
6 | import org.apache.flink.formats.json.JsonFormatOptions;
7 | import org.apache.flink.formats.json.JsonRowDataDeserializationSchema;
8 | import org.apache.flink.formats.json.RowDataToJsonConverters;
9 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.core.JsonGenerator;
10 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.ObjectMapper;
11 | import org.apache.flink.shaded.jackson2.com.fasterxml.jackson.databind.node.ObjectNode;
12 | import org.apache.flink.table.data.RowData;
13 | import org.apache.flink.table.types.logical.RowType;
14 |
15 | import java.util.Objects;
16 |
17 | /**
18 | * Serialization schema that serializes an object of Flink internal data structure into a JSON
19 | * bytes.
20 | *
21 | * Serializes the input Flink object into a JSON string and converts it into byte[]
.
22 | *
23 | *
Result byte[]
messages can be deserialized using {@link
24 | * JsonRowDataDeserializationSchema}.
25 | */
26 | @Internal
27 | public class JsonRowDataSerializationSchema implements SerializationSchema {
28 | private static final long serialVersionUID = 1L;
29 |
30 | /** RowType to generate the runtime converter. */
31 | private final RowType rowType;
32 |
33 | /** The converter that converts internal data formats to JsonNode. */
34 | private final RowDataToJsonConverters.RowDataToJsonConverter runtimeConverter;
35 |
36 | /** Object mapper that is used to create output JSON objects. */
37 | private final ObjectMapper mapper = new ObjectMapper();
38 |
39 | /** Reusable object node. */
40 | private transient ObjectNode node;
41 |
42 | /** Timestamp format specification which is used to parse timestamp. */
43 | private final TimestampFormat timestampFormat;
44 |
45 | /** The handling mode when serializing null keys for map data. */
46 | private final JsonFormatOptions.MapNullKeyMode mapNullKeyMode;
47 |
48 | /** The string literal when handling mode for map null key LITERAL. */
49 | private final String mapNullKeyLiteral;
50 |
51 | /** Flag indicating whether to serialize all decimals as plain numbers. */
52 | private final boolean encodeDecimalAsPlainNumber;
53 |
54 | public JsonRowDataSerializationSchema(
55 | RowType rowType,
56 | TimestampFormat timestampFormat,
57 | JsonFormatOptions.MapNullKeyMode mapNullKeyMode,
58 | String mapNullKeyLiteral,
59 | boolean encodeDecimalAsPlainNumber) {
60 | this.rowType = rowType;
61 | this.timestampFormat = timestampFormat;
62 | this.mapNullKeyMode = mapNullKeyMode;
63 | this.mapNullKeyLiteral = mapNullKeyLiteral;
64 | this.encodeDecimalAsPlainNumber = encodeDecimalAsPlainNumber;
65 | this.runtimeConverter =
66 | new RowDataToJsonConverters(timestampFormat, mapNullKeyMode, mapNullKeyLiteral)
67 | .createConverter(rowType);
68 |
69 | mapper.configure(
70 | JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, encodeDecimalAsPlainNumber);
71 | }
72 |
73 | @Override
74 | public byte[] serialize(RowData row) {
75 | if (node == null) {
76 | node = mapper.createObjectNode();
77 | }
78 |
79 | try {
80 | runtimeConverter.convert(mapper, node, row);
81 | return mapper.writeValueAsBytes(node);
82 | } catch (Throwable t) {
83 | throw new RuntimeException("Could not serialize row '" + row + "'. ", t);
84 | }
85 | }
86 |
87 | @Override
88 | public boolean equals(Object o) {
89 | if (this == o) {
90 | return true;
91 | }
92 | if (o == null || getClass() != o.getClass()) {
93 | return false;
94 | }
95 | JsonRowDataSerializationSchema that = (JsonRowDataSerializationSchema) o;
96 | return rowType.equals(that.rowType)
97 | && timestampFormat.equals(that.timestampFormat)
98 | && mapNullKeyMode.equals(that.mapNullKeyMode)
99 | && mapNullKeyLiteral.equals(that.mapNullKeyLiteral)
100 | && encodeDecimalAsPlainNumber == that.encodeDecimalAsPlainNumber;
101 | }
102 |
103 | @Override
104 | public int hashCode() {
105 | return Objects.hash(
106 | rowType,
107 | timestampFormat,
108 | mapNullKeyMode,
109 | mapNullKeyLiteral,
110 | encodeDecimalAsPlainNumber);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/java/com/hiscat/flink/custrom/format/json/TimeFormats.java:
--------------------------------------------------------------------------------
1 |
2 | package com.hiscat.flink.custrom.format.json;
3 |
4 | import java.time.format.DateTimeFormatter;
5 | import java.time.format.DateTimeFormatterBuilder;
6 | import java.time.temporal.ChronoField;
7 |
8 | /**
9 | * Time formats and timestamp formats respecting the RFC3339 specification, ISO-8601 specification
10 | * and SQL specification.
11 | */
12 | class TimeFormats {
13 |
14 | /** Formatter for RFC 3339-compliant string representation of a time value. */
15 | static final DateTimeFormatter RFC3339_TIME_FORMAT =
16 | new DateTimeFormatterBuilder()
17 | .appendPattern("HH:mm:ss")
18 | .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
19 | .appendPattern("'Z'")
20 | .toFormatter();
21 |
22 | /**
23 | * Formatter for RFC 3339-compliant string representation of a timestamp value (with UTC
24 | * timezone).
25 | */
26 | static final DateTimeFormatter RFC3339_TIMESTAMP_FORMAT =
27 | new DateTimeFormatterBuilder()
28 | .append(DateTimeFormatter.ISO_LOCAL_DATE)
29 | .appendLiteral('T')
30 | .append(RFC3339_TIME_FORMAT)
31 | .toFormatter();
32 |
33 | /** Formatter for ISO8601 string representation of a timestamp value (without UTC timezone). */
34 | static final DateTimeFormatter ISO8601_TIMESTAMP_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
35 |
36 | /** Formatter for ISO8601 string representation of a timestamp value (with UTC timezone). */
37 | static final DateTimeFormatter ISO8601_TIMESTAMP_WITH_LOCAL_TIMEZONE_FORMAT =
38 | new DateTimeFormatterBuilder()
39 | .append(DateTimeFormatter.ISO_LOCAL_DATE)
40 | .appendLiteral('T')
41 | .append(DateTimeFormatter.ISO_LOCAL_TIME)
42 | .appendPattern("'Z'")
43 | .toFormatter();
44 |
45 | /** Formatter for SQL string representation of a time value. */
46 | static final DateTimeFormatter SQL_TIME_FORMAT =
47 | new DateTimeFormatterBuilder()
48 | .appendPattern("HH:mm:ss")
49 | .appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
50 | .toFormatter();
51 |
52 | /** Formatter for SQL string representation of a timestamp value (without UTC timezone). */
53 | static final DateTimeFormatter SQL_TIMESTAMP_FORMAT =
54 | new DateTimeFormatterBuilder()
55 | .append(DateTimeFormatter.ISO_LOCAL_DATE)
56 | .appendLiteral(' ')
57 | .append(SQL_TIME_FORMAT)
58 | .toFormatter();
59 |
60 | /** Formatter for SQL string representation of a timestamp value (with UTC timezone). */
61 | static final DateTimeFormatter SQL_TIMESTAMP_WITH_LOCAL_TIMEZONE_FORMAT =
62 | new DateTimeFormatterBuilder()
63 | .append(DateTimeFormatter.ISO_LOCAL_DATE)
64 | .appendLiteral(' ')
65 | .append(SQL_TIME_FORMAT)
66 | .appendPattern("'Z'")
67 | .toFormatter();
68 |
69 | private TimeFormats() {}
70 | }
71 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/java/com/hiscat/flink/custrom/format/json/ogg/OggJsonFormatFactory.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.custrom.format.json.ogg;
2 |
3 | import com.hiscat.flink.custrom.format.json.JsonOptions;
4 | import org.apache.flink.api.common.serialization.DeserializationSchema;
5 | import org.apache.flink.api.common.serialization.SerializationSchema;
6 | import org.apache.flink.configuration.ConfigOption;
7 | import org.apache.flink.configuration.ReadableConfig;
8 | import org.apache.flink.formats.common.TimestampFormat;
9 | import org.apache.flink.formats.json.JsonFormatOptions;
10 | import org.apache.flink.table.connector.ChangelogMode;
11 | import org.apache.flink.table.connector.format.DecodingFormat;
12 | import org.apache.flink.table.connector.format.EncodingFormat;
13 | import org.apache.flink.table.connector.sink.DynamicTableSink;
14 | import org.apache.flink.table.data.RowData;
15 | import org.apache.flink.table.factories.DeserializationFormatFactory;
16 | import org.apache.flink.table.factories.DynamicTableFactory;
17 | import org.apache.flink.table.factories.FactoryUtil;
18 | import org.apache.flink.table.factories.SerializationFormatFactory;
19 | import org.apache.flink.table.types.DataType;
20 | import org.apache.flink.table.types.logical.RowType;
21 | import org.apache.flink.types.RowKind;
22 |
23 | import java.util.Collections;
24 | import java.util.HashSet;
25 | import java.util.Set;
26 |
27 | import static com.hiscat.flink.custrom.format.json.JsonOptions.ENCODE_DECIMAL_AS_PLAIN_NUMBER;
28 | import static com.hiscat.flink.custrom.format.json.ogg.OggJsonOptions.*;
29 |
30 | /**
31 | * Format factory for providing configured instances of OGG JSON to RowData {@link
32 | * DeserializationSchema}.
33 | */
34 | public class OggJsonFormatFactory
35 | implements DeserializationFormatFactory, SerializationFormatFactory {
36 |
37 | public static final String IDENTIFIER = "ogg-json";
38 |
39 | @Override
40 | public DecodingFormat> createDecodingFormat(
41 | DynamicTableFactory.Context context, ReadableConfig formatOptions) {
42 | FactoryUtil.validateFactoryOptions(this, formatOptions);
43 | validateDecodingFormatOptions(formatOptions);
44 |
45 | final String table = formatOptions.getOptional(TABLE_INCLUDE).orElse(null);
46 | final String type = formatOptions.getOptional(OP_TYPE_INCLUDE).orElse(null);
47 | final boolean ignoreParseErrors = formatOptions.get(IGNORE_PARSE_ERRORS);
48 | final TimestampFormat timestampFormat = JsonOptions.getTimestampFormat(formatOptions);
49 |
50 | return new OggJsonDecodingFormat(table, type, ignoreParseErrors, timestampFormat);
51 | }
52 |
53 | @Override
54 | public EncodingFormat> createEncodingFormat(
55 | DynamicTableFactory.Context context, ReadableConfig formatOptions) {
56 |
57 | FactoryUtil.validateFactoryOptions(this, formatOptions);
58 | validateEncodingFormatOptions(formatOptions);
59 |
60 | TimestampFormat timestampFormat = JsonOptions.getTimestampFormat(formatOptions);
61 |
62 | final JsonFormatOptions.MapNullKeyMode mapNullKeyMode = JsonFormatOptions.MapNullKeyMode.valueOf(formatOptions.get(JSON_MAP_NULL_KEY_LITERAL));
63 | String mapNullKeyLiteral = formatOptions.get(JSON_MAP_NULL_KEY_LITERAL);
64 |
65 | final boolean encodeDecimalAsPlainNumber = formatOptions.get(ENCODE_DECIMAL_AS_PLAIN_NUMBER);
66 |
67 | return new EncodingFormat>() {
68 | @Override
69 | public ChangelogMode getChangelogMode() {
70 | return ChangelogMode.newBuilder()
71 | .addContainedKind(RowKind.INSERT)
72 | .addContainedKind(RowKind.UPDATE_BEFORE)
73 | .addContainedKind(RowKind.UPDATE_AFTER)
74 | .addContainedKind(RowKind.DELETE)
75 | .build();
76 | }
77 |
78 | @Override
79 | public SerializationSchema createRuntimeEncoder(
80 | DynamicTableSink.Context context, DataType consumedDataType) {
81 | final RowType rowType = (RowType) consumedDataType.getLogicalType();
82 |
83 | return new OggJsonSerializationSchema(
84 | rowType,
85 | timestampFormat,
86 | mapNullKeyMode,
87 | mapNullKeyLiteral,
88 | encodeDecimalAsPlainNumber);
89 | }
90 | };
91 | }
92 |
93 | @Override
94 | public String factoryIdentifier() {
95 | return IDENTIFIER;
96 | }
97 |
98 | @Override
99 | public Set> requiredOptions() {
100 | return Collections.emptySet();
101 | }
102 |
103 | @Override
104 | public Set> optionalOptions() {
105 | Set> options = new HashSet<>();
106 | options.add(IGNORE_PARSE_ERRORS);
107 | options.add(TIMESTAMP_FORMAT);
108 | options.add(TABLE_INCLUDE);
109 | options.add(OP_TYPE_INCLUDE);
110 | options.add(JSON_MAP_NULL_KEY_MODE);
111 | options.add(JSON_MAP_NULL_KEY_LITERAL);
112 | options.add(ENCODE_DECIMAL_AS_PLAIN_NUMBER);
113 | return options;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/java/com/hiscat/flink/custrom/format/json/ogg/OggJsonOptions.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.custrom.format.json.ogg;
2 |
3 | import com.hiscat.flink.custrom.format.json.JsonOptions;
4 | import org.apache.flink.configuration.ConfigOption;
5 | import org.apache.flink.configuration.ConfigOptions;
6 | import org.apache.flink.configuration.ReadableConfig;
7 |
8 | /** Option utils for canal-json format. */
9 | public class OggJsonOptions {
10 |
11 | public static final ConfigOption IGNORE_PARSE_ERRORS = JsonOptions.IGNORE_PARSE_ERRORS;
12 |
13 | public static final ConfigOption TIMESTAMP_FORMAT = JsonOptions.TIMESTAMP_FORMAT;
14 |
15 | public static final ConfigOption JSON_MAP_NULL_KEY_MODE = JsonOptions.MAP_NULL_KEY_MODE;
16 |
17 | public static final ConfigOption JSON_MAP_NULL_KEY_LITERAL =
18 | JsonOptions.MAP_NULL_KEY_LITERAL;
19 |
20 | public static final ConfigOption TABLE_INCLUDE =
21 | ConfigOptions.key("table.include")
22 | .stringType()
23 | .noDefaultValue()
24 | .withDescription(
25 | "An optional regular expression to only read the specific tables changelog rows by regular matching the \"table\" meta field in the Canal record."
26 | + "The pattern string is compatible with Java's Pattern.");
27 |
28 |
29 | public static final ConfigOption OP_TYPE_INCLUDE =
30 | ConfigOptions.key("op-type.include")
31 | .stringType()
32 | .noDefaultValue()
33 | .withDescription("dml operation type values is I(INSERT),U(UPDATE),D(DELETE),T(TRUNCATE)");
34 |
35 | // --------------------------------------------------------------------------------------------
36 | // Validation
37 | // --------------------------------------------------------------------------------------------
38 |
39 | /** Validator for canal decoding format. */
40 | public static void validateDecodingFormatOptions(ReadableConfig tableOptions) {
41 | JsonOptions.validateDecodingFormatOptions(tableOptions);
42 | }
43 |
44 | /** Validator for canal encoding format. */
45 | public static void validateEncodingFormatOptions(ReadableConfig tableOptions) {
46 | JsonOptions.validateEncodingFormatOptions(tableOptions);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/java/com/hiscat/flink/custrom/format/json/ogg/OggJsonSerializationSchema.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.custrom.format.json.ogg;
2 |
3 | import com.hiscat.flink.custrom.format.json.JsonRowDataSerializationSchema;
4 | import org.apache.flink.api.common.serialization.SerializationSchema;
5 | import org.apache.flink.formats.common.TimestampFormat;
6 | import org.apache.flink.formats.json.JsonFormatOptions;
7 | import org.apache.flink.table.api.DataTypes;
8 | import org.apache.flink.table.data.*;
9 | import org.apache.flink.table.types.DataType;
10 | import org.apache.flink.table.types.logical.RowType;
11 | import org.apache.flink.types.RowKind;
12 |
13 | import java.util.Objects;
14 |
15 | import static org.apache.flink.table.types.utils.TypeConversions.fromLogicalToDataType;
16 |
17 | /**
18 | * Serialization schema that serializes an object of Flink Table/SQL internal data structure {@link
19 | * RowData} into a Canal JSON bytes.
20 | *
21 | * @see Alibaba Canal
22 | */
23 | public class OggJsonSerializationSchema implements SerializationSchema {
24 |
25 | private static final long serialVersionUID = 1L;
26 |
27 | private static final StringData OP_INSERT = StringData.fromString("I");
28 | private static final StringData OP_DELETE = StringData.fromString("D");
29 |
30 | private transient GenericRowData reuse;
31 |
32 | /**
33 | * The serializer to serialize Canal JSON data.
34 | */
35 | private final JsonRowDataSerializationSchema jsonSerializer;
36 |
37 | public OggJsonSerializationSchema(
38 | RowType rowType,
39 | TimestampFormat timestampFormat,
40 | JsonFormatOptions.MapNullKeyMode mapNullKeyMode,
41 | String mapNullKeyLiteral,
42 | boolean encodeDecimalAsPlainNumber) {
43 | jsonSerializer =
44 | new JsonRowDataSerializationSchema(
45 | createJsonRowType(fromLogicalToDataType(rowType)),
46 | timestampFormat,
47 | mapNullKeyMode,
48 | mapNullKeyLiteral,
49 | encodeDecimalAsPlainNumber);
50 | }
51 |
52 | @Override
53 | public void open(InitializationContext context) {
54 | reuse = new GenericRowData(2);
55 | }
56 |
57 | @Override
58 | public byte[] serialize(RowData row) {
59 | try {
60 | StringData opType = rowKind2String(row.getRowKind());
61 | ArrayData arrayData = new GenericArrayData(new RowData[]{row});
62 | reuse.setField(0, arrayData);
63 | reuse.setField(1, opType);
64 | return jsonSerializer.serialize(reuse);
65 | } catch (Throwable t) {
66 | throw new RuntimeException("Could not serialize row '" + row + "'.", t);
67 | }
68 | }
69 |
70 | private StringData rowKind2String(RowKind rowKind) {
71 | switch (rowKind) {
72 | case INSERT:
73 | case UPDATE_AFTER:
74 | return OP_INSERT;
75 | case UPDATE_BEFORE:
76 | case DELETE:
77 | return OP_DELETE;
78 | default:
79 | throw new UnsupportedOperationException(
80 | "Unsupported operation '" + rowKind + "' for row kind.");
81 | }
82 | }
83 |
84 | @Override
85 | public boolean equals(Object o) {
86 | if (this == o) {
87 | return true;
88 | }
89 | if (o == null || getClass() != o.getClass()) {
90 | return false;
91 | }
92 | OggJsonSerializationSchema that = (OggJsonSerializationSchema) o;
93 | return Objects.equals(jsonSerializer, that.jsonSerializer);
94 | }
95 |
96 | @Override
97 | public int hashCode() {
98 | return Objects.hash(jsonSerializer);
99 | }
100 |
101 | private static RowType createJsonRowType(DataType databaseSchema) {
102 | // Canal JSON contains other information, e.g. "database", "ts"
103 | // but we don't need them
104 | // and we don't need "old" , because can not support UPDATE_BEFORE,UPDATE_AFTER
105 | return (RowType)
106 | DataTypes.ROW(
107 | DataTypes.FIELD("before", databaseSchema),
108 | DataTypes.FIELD("after", databaseSchema),
109 | DataTypes.FIELD("op_type", DataTypes.STRING()))
110 | .getLogicalType();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/flink-custom-connector/src/main/resources/META-INF/services/org.apache.flink.table.factories.Factory:
--------------------------------------------------------------------------------
1 | com.hiscat.flink.custrom.format.json.ogg.OggJsonFormatFactory
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/AutoServiceDiscoveryPrometheusReporter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | */
18 |
19 | package com.hiscat.flink.prometheus;
20 |
21 | import com.hiscat.flink.prometheus.sd.ServiceDiscovery;
22 | import com.hiscat.flink.prometheus.sd.ServiceDiscoveryLoader;
23 | import com.sun.net.httpserver.HttpServer;
24 | import io.prometheus.client.exporter.HTTPServer;
25 | import org.apache.flink.annotation.PublicEvolving;
26 | import org.apache.flink.metrics.Metric;
27 | import org.apache.flink.metrics.MetricConfig;
28 | import org.apache.flink.metrics.prometheus.AbstractPrometheusReporter;
29 | import org.apache.flink.metrics.reporter.InstantiateViaFactory;
30 | import org.apache.flink.metrics.reporter.MetricReporter;
31 |
32 | import java.io.IOException;
33 | import java.lang.reflect.Field;
34 | import java.net.InetSocketAddress;
35 | import java.util.Iterator;
36 |
37 | import static com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryOptions.SD_IDENTIFIER;
38 |
39 |
40 | /**
41 | * {@link MetricReporter} that exports {@link Metric Metrics} via Prometheus.
42 | */
43 | @PublicEvolving
44 | @InstantiateViaFactory(
45 | factoryClassName = "com.hiscat.flink.prometheus.AtuoServiceDiscoveryPrometheusReporterFactory")
46 | public class AutoServiceDiscoveryPrometheusReporter extends AbstractPrometheusReporter {
47 |
48 | private HTTPServer httpServer;
49 | private ServiceDiscovery sd;
50 |
51 |
52 | @Override
53 | public void open(MetricConfig config) {
54 | super.open(config);
55 | sd = ServiceDiscoveryLoader
56 | .load(config.getString(SD_IDENTIFIER.key(), SD_IDENTIFIER.defaultValue()), this.getClass().getClassLoader());
57 | sd.register(getAddress(), config);
58 | }
59 |
60 | private InetSocketAddress getAddress() {
61 | try {
62 | Field server = this.httpServer.getClass().getDeclaredField("server");
63 | server.setAccessible(true);
64 | return ((HttpServer) server.get(this.httpServer)).getAddress();
65 | } catch (IllegalAccessException | NoSuchFieldException e) {
66 | throw new RuntimeException(e);
67 | }
68 | }
69 |
70 | AutoServiceDiscoveryPrometheusReporter(Iterator ports) {
71 | while (ports.hasNext()) {
72 | int port = ports.next();
73 | try {
74 | // internally accesses CollectorRegistry.defaultRegistry
75 | httpServer = new HTTPServer(port);
76 | log.info("Started PrometheusReporter HTTP server on port {}.", port);
77 | break;
78 | } catch (IOException ioe) { // assume port conflict
79 | log.debug("Could not start PrometheusReporter HTTP server on port {}.", port, ioe);
80 | }
81 | }
82 | if (httpServer == null) {
83 | throw new RuntimeException(
84 | "Could not start PrometheusReporter HTTP server on any configured port. Ports: "
85 | + ports);
86 | }
87 | }
88 |
89 | @Override
90 | public void close() {
91 | if (httpServer != null) {
92 | httpServer.stop();
93 | }
94 |
95 | sd.close();
96 |
97 | super.close();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/AutoServiceDiscoveryPrometheusReporterFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package com.hiscat.flink.prometheus;
19 |
20 | import org.apache.flink.metrics.MetricConfig;
21 | import org.apache.flink.metrics.reporter.InterceptInstantiationViaReflection;
22 | import org.apache.flink.metrics.reporter.MetricReporterFactory;
23 | import org.apache.flink.util.NetUtils;
24 |
25 | import java.util.Iterator;
26 | import java.util.Properties;
27 |
28 | /** {@link MetricReporterFactory} for {@link AutoServiceDiscoveryPrometheusReporter}. */
29 | @InterceptInstantiationViaReflection(
30 | reporterClassName = "com.hiscat.flink.prometheus.AtuoServiceDiscoveryPrometheusReporter")
31 | public class AutoServiceDiscoveryPrometheusReporterFactory implements MetricReporterFactory {
32 |
33 | static final String ARG_PORT = "port";
34 | private static final String DEFAULT_PORT = "9249";
35 |
36 | @Override
37 | public AutoServiceDiscoveryPrometheusReporter createMetricReporter(Properties properties) {
38 | MetricConfig metricConfig = (MetricConfig) properties;
39 | String portsConfig = metricConfig.getString(ARG_PORT, DEFAULT_PORT);
40 | Iterator ports = NetUtils.getPortRangeFromString(portsConfig);
41 |
42 | return new AutoServiceDiscoveryPrometheusReporter(ports);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/BaseZookeeperServiceDiscovery.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 |
4 |
5 | import org.apache.flink.shaded.curator5.org.apache.curator.RetryPolicy;
6 | import org.apache.flink.shaded.curator5.org.apache.curator.framework.CuratorFramework;
7 | import org.apache.flink.shaded.curator5.org.apache.curator.framework.CuratorFrameworkFactory;
8 | import org.apache.flink.shaded.curator5.org.apache.curator.retry.ExponentialBackoffRetry;
9 | import org.apache.flink.shaded.zookeeper3.org.apache.zookeeper.CreateMode;
10 | import org.apache.flink.shaded.zookeeper3.org.apache.zookeeper.data.Stat;
11 |
12 | import java.net.*;
13 | import java.nio.charset.StandardCharsets;
14 | import java.util.Enumeration;
15 | import java.util.Properties;
16 |
17 | import static com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryOptions.ZK_QUORUM;
18 |
19 | public abstract class BaseZookeeperServiceDiscovery implements ServiceDiscovery {
20 |
21 | private CuratorFramework client;
22 |
23 | @Override
24 | public void register(InetSocketAddress address, Properties properties) {
25 | initClient(properties);
26 | registerNode(address, properties);
27 | }
28 |
29 | private void initClient(Properties properties) {
30 | RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 100);
31 | client = CuratorFrameworkFactory.newClient(properties.getProperty(ZK_QUORUM.key()), retryPolicy);
32 | client.start();
33 | }
34 |
35 |
36 | private void registerNode(InetSocketAddress address, Properties properties) {
37 | try {
38 | String path = makePath(properties);
39 | Stat stat = client.checkExists().forPath(path);
40 | if (stat != null) {
41 | client.setData().forPath(path, makeServerSetData(address));
42 | return;
43 | }
44 |
45 | client
46 | .create()
47 | .creatingParentsIfNeeded()
48 | .withMode(getCreateMode())
49 | .forPath(path, makeServerSetData(address));
50 | } catch (Exception e) {
51 | throw new RuntimeException(e);
52 | }
53 | }
54 |
55 | protected abstract String makePath(Properties properties);
56 |
57 | protected abstract CreateMode getCreateMode();
58 |
59 | private byte[] makeServerSetData(InetSocketAddress address) {
60 | String jsonFormat = "{\"serviceEndpoint\":{\"host\":\"%s\",\"port\":%d},\"additionalEndpoints\":{},\"status\":\"ALIVE\"}\n";
61 | try {
62 | return String.format(jsonFormat, getIpAddress(),
63 | address.getPort()).getBytes(StandardCharsets.UTF_8);
64 | } catch (SocketException | UnknownHostException e) {
65 | throw new RuntimeException(e);
66 | }
67 | }
68 |
69 | private String getIpAddress() throws SocketException, UnknownHostException {
70 | Enumeration enumeration = NetworkInterface.getNetworkInterfaces();
71 | while (enumeration.hasMoreElements()) {
72 | NetworkInterface network = enumeration.nextElement();
73 | if (network.isVirtual() || !network.isUp()) {
74 | continue;
75 | }
76 | Enumeration addresses = network.getInetAddresses();
77 | while (addresses.hasMoreElements()) {
78 | InetAddress address = addresses.nextElement();
79 | if (address.isLoopbackAddress()) {
80 | continue;
81 | }
82 | if (address.isSiteLocalAddress()) {
83 | return address.getHostAddress();
84 | }
85 |
86 | }
87 | }
88 | return InetAddress.getLocalHost().getHostAddress();
89 | }
90 |
91 | @Override
92 | public void close() {
93 | client.close();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/JobManagerZookeeperServiceDiscovery.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 |
4 |
5 | import org.apache.flink.shaded.zookeeper3.org.apache.zookeeper.CreateMode;
6 |
7 | import java.util.Properties;
8 |
9 | import static com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryOptions.JM_PATH;
10 | import static com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryOptions.JOB_NAME;
11 |
12 |
13 | public class JobManagerZookeeperServiceDiscovery extends BaseZookeeperServiceDiscovery{
14 | @Override
15 | protected String makePath(Properties properties) {
16 | return properties.getProperty(JM_PATH.key()) + "/" + System.getProperty(JOB_NAME.key());
17 | }
18 |
19 | @Override
20 | protected CreateMode getCreateMode() {
21 | return CreateMode.EPHEMERAL;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/ServiceDiscovery.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 | import java.net.InetSocketAddress;
4 | import java.util.Properties;
5 |
6 | public interface ServiceDiscovery {
7 | void register(InetSocketAddress address, Properties properties);
8 |
9 | void close();
10 | }
11 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/ServiceDiscoveryFactory.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 | public interface ServiceDiscoveryFactory {
4 | String identifier();
5 |
6 | ServiceDiscovery create();
7 | }
8 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/ServiceDiscoveryLoader.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 | import java.util.ServiceConfigurationError;
6 | import java.util.ServiceLoader;
7 | import java.util.stream.Collectors;
8 |
9 | import static java.util.stream.Collectors.toList;
10 |
11 | public class ServiceDiscoveryLoader {
12 | public static ServiceDiscovery load(String identifier, ClassLoader classLoader) {
13 | List factories = discoverFactories(classLoader);
14 | checkNotFoundFactory(factories);
15 | List matches = factories.stream().filter(sd -> sd.identifier().equals(identifier)).collect(toList());
16 | checkMatchesFactories(identifier, factories, matches);
17 | return matches.get(0).create();
18 | }
19 |
20 | private static List discoverFactories(ClassLoader classLoader) {
21 | try {
22 | final List result = new LinkedList<>();
23 | ServiceLoader.load(ServiceDiscoveryFactory.class, classLoader)
24 | .iterator()
25 | .forEachRemaining(result::add);
26 | return result;
27 | } catch (ServiceConfigurationError e) {
28 | throw new RuntimeException(
29 | "Could not load service provider for service discovery factory.", e);
30 | }
31 | }
32 |
33 | private static void checkNotFoundFactory(List factories) {
34 | if (factories.isEmpty()) {
35 | throw new IllegalStateException(
36 | String.format(
37 | "Could not find any service discovery factories that implement '%s' in the classpath.",
38 | ServiceDiscoveryFactory.class.getName()));
39 | }
40 | }
41 |
42 | private static void checkMatchesFactories(String identifier, List factories, List matches) {
43 | checkNotFoundMatchedFactory(identifier, factories, matches);
44 |
45 | checkFindMultipleFactories(identifier, matches);
46 | }
47 |
48 | private static void checkNotFoundMatchedFactory(String identifier, List factories, List matches) {
49 | if (matches.isEmpty()) {
50 | throw new IllegalStateException(
51 | String.format(
52 | "Could not find any service discovery factory that can handle identifier '%s' that implements '%s' in the classpath.\n\n"
53 | + "Available factories are:\n\n"
54 | + "%s",
55 | identifier,
56 | ServiceDiscoveryFactory.class.getName(),
57 | factories.stream()
58 | .map(f -> f.getClass().getName())
59 | .distinct()
60 | .sorted()
61 | .collect(Collectors.joining("\n"))));
62 | }
63 | }
64 |
65 | private static void checkFindMultipleFactories(String identifier, List matches) {
66 | if (matches.size() > 1) {
67 | throw new IllegalStateException(
68 | String.format(
69 | "Multiple service discovery factories can handle identifier '%s' that implement '%s' found in the classpath.\n\n"
70 | + "Ambiguous factory classes are:\n\n"
71 | + "%s",
72 | identifier,
73 | ServiceDiscoveryFactory.class.getName(),
74 | matches.stream()
75 | .map(f -> f.getClass().getName())
76 | .sorted()
77 | .collect(Collectors.joining("\n"))));
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/TaskManagerZookeeperServiceDiscovery.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 | import org.apache.flink.shaded.zookeeper3.org.apache.zookeeper.CreateMode;
4 |
5 | import java.util.Properties;
6 |
7 | import static com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryOptions.*;
8 |
9 | public class TaskManagerZookeeperServiceDiscovery extends BaseZookeeperServiceDiscovery {
10 |
11 |
12 | @Override
13 | protected String makePath(Properties properties) {
14 | return properties.getProperty(TM_PATH.key()) + "/"
15 | + System.getProperty(JOB_NAME.key())
16 | + "_"
17 | + System.getProperty(CONTAINER_ID.key());
18 | }
19 |
20 | @Override
21 | protected CreateMode getCreateMode() {
22 | return CreateMode.EPHEMERAL_SEQUENTIAL;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/ZookeeperServiceDiscoveryFactory.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 | import static com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryOptions.ROLE;
4 |
5 | public class ZookeeperServiceDiscoveryFactory implements ServiceDiscoveryFactory {
6 | @Override
7 | public String identifier() {
8 | return "zookeeper";
9 | }
10 |
11 | @Override
12 | public ServiceDiscovery create() {
13 | if (currentNodeIsJobManager()) {
14 | return new JobManagerZookeeperServiceDiscovery();
15 | }
16 | return new TaskManagerZookeeperServiceDiscovery();
17 | }
18 |
19 | private static boolean currentNodeIsJobManager() {
20 | return "jm".equals(System.getProperty(ROLE.key()));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/java/com/hiscat/flink/prometheus/sd/ZookeeperServiceDiscoveryOptions.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.prometheus.sd;
2 |
3 | import org.apache.flink.configuration.ConfigOption;
4 | import org.apache.flink.configuration.ConfigOptions;
5 |
6 | public class ZookeeperServiceDiscoveryOptions {
7 | public static final ConfigOption TM_PATH = ConfigOptions.key("tm.path")
8 | .stringType()
9 | .noDefaultValue()
10 | .withDescription("zk tm path");
11 | public static final ConfigOption JM_PATH = ConfigOptions.key("jm.path")
12 | .stringType()
13 | .noDefaultValue()
14 | .withDescription("zk jm path");
15 | public static final ConfigOption SD_IDENTIFIER = ConfigOptions.key("sd.identifier")
16 | .stringType()
17 | .defaultValue("none")
18 | .withDescription("service discovery identifier ");
19 |
20 | public static final ConfigOption ZK_QUORUM = ConfigOptions.key("zk.quorum")
21 | .stringType()
22 | .noDefaultValue()
23 | .withDescription("service discovery zk quorum ");
24 |
25 | // ================== cli option ==========================
26 | public static final ConfigOption CONTAINER_ID = ConfigOptions.key("container_id")
27 | .stringType()
28 | .noDefaultValue()
29 | .withDescription("yarn container_id");
30 |
31 | public static final ConfigOption ROLE = ConfigOptions.key("role")
32 | .stringType()
33 | .noDefaultValue()
34 | .withDescription("env variable role");
35 | public static final ConfigOption JOB_NAME = ConfigOptions.key("jobName")
36 | .stringType()
37 | .noDefaultValue()
38 | .withDescription("job name ");
39 | }
40 |
--------------------------------------------------------------------------------
/flink-prometheus/src/main/resources/META-INF/services/com.hiscat.flink.prometheus.sd.ServiceDiscoveryFactory:
--------------------------------------------------------------------------------
1 | com.hiscat.flink.prometheus.sd.ZookeeperServiceDiscoveryFactory
--------------------------------------------------------------------------------
/flink-prometheus/src/main/resources/META-INF/services/org.apache.flink.metrics.reporter.MetricReporterFactory:
--------------------------------------------------------------------------------
1 | # Licensed to the Apache Software Foundation (ASF) under one or more
2 | # contributor license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright ownership.
4 | # The ASF licenses this file to You under the Apache License, Version 2.0
5 | # (the "License"); you may not use this file except in compliance with
6 | # the License. You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 |
16 | com.hiscat.flink.prometheus.AutoServiceDiscoveryPrometheusReporterFactory
17 |
--------------------------------------------------------------------------------
/flink-sql-submitter/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.hiscat.flink
8 | flink-sql-submitter
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 8
13 | 8
14 | 2.11
15 | 1.13.1
16 | 1.3.1
17 | 1.18.20
18 | 2.17.0
19 |
20 |
21 |
22 | org.apache.flink
23 | flink-clients_${scala.binary.version}
24 | provided
25 | ${flink.version}
26 |
27 |
28 | org.apache.flink
29 | flink-table-planner_${scala.binary.version}
30 | provided
31 | ${flink.version}
32 |
33 |
34 |
35 |
36 | org.apache.flink
37 | flink-sql-client_2.11
38 | ${flink.version}
39 | provided
40 |
41 |
42 | org.apache.flink
43 | flink-python_2.11
44 | ${flink.version}
45 | provided
46 |
47 |
48 | org.apache.flink
49 | flink-runtime-web_2.11
50 | ${flink.version}
51 | provided
52 |
53 |
54 | org.apache.flink
55 | flink-yarn_2.11
56 | ${flink.version}
57 | provided
58 |
59 |
60 |
61 | org.projectlombok
62 | lombok
63 | ${lombok.version}
64 | provided
65 |
66 |
67 | commons-cli
68 | commons-cli
69 | ${commons-cli.version}
70 |
71 |
72 |
73 |
74 | org.apache.logging.log4j
75 | log4j-api
76 | ${log4j.version}
77 | provided
78 |
79 |
80 | org.apache.logging.log4j
81 | log4j-core
82 | ${log4j.version}
83 | provided
84 |
85 |
86 | org.apache.logging.log4j
87 | log4j-slf4j-impl
88 | ${log4j.version}
89 | provided
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/flink-sql-submitter/src/main/java/com/hiscat/flink/SqlSubmitter.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink;
2 |
3 | import com.hiscat.flink.cli.CliStatementSplitter;
4 | import com.hiscat.flink.cli.SqlRunnerOptions;
5 | import com.hiscat.flink.function.CallContext;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.apache.commons.cli.ParseException;
8 | import org.apache.commons.io.IOUtils;
9 | import org.apache.flink.api.java.utils.ParameterTool;
10 | import org.apache.flink.core.fs.FileSystem;
11 | import org.apache.flink.core.fs.Path;
12 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
13 | import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
14 | import org.apache.flink.table.api.bridge.java.internal.StreamTableEnvironmentImpl;
15 | import org.apache.flink.table.delegation.Parser;
16 | import org.apache.flink.table.operations.CatalogSinkModifyOperation;
17 | import org.apache.flink.table.operations.ModifyOperation;
18 | import org.apache.flink.table.operations.Operation;
19 | import org.apache.flink.table.operations.command.SetOperation;
20 |
21 | import java.io.IOException;
22 | import java.net.URI;
23 | import java.nio.charset.StandardCharsets;
24 | import java.util.ArrayList;
25 | import java.util.List;
26 | import java.util.function.Consumer;
27 |
28 | @Slf4j
29 | public class SqlSubmitter {
30 |
31 | public static void main(String[] args)
32 | throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException,
33 | ParseException {
34 | final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
35 | final StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);
36 |
37 | final Parser parser = ((StreamTableEnvironmentImpl) tEnv).getParser();
38 | List dml = new ArrayList<>();
39 |
40 | for (String stmt :
41 | CliStatementSplitter.splitContent(getSql(SqlRunnerOptions.parseFromArgs(args)))) {
42 | stmt = stmt.trim();
43 | if (stmt.endsWith(";")) {
44 | stmt = stmt.substring(0, stmt.length() - 1).trim();
45 | }
46 |
47 | if (stmt.trim().isEmpty()) {
48 | continue;
49 | }
50 | if (stmt.startsWith("call")) {
51 | call(
52 | stmt,
53 | CallContext.builder().args(ParameterTool.fromArgs(args)).env(env).tEnv(tEnv).build());
54 | continue;
55 | }
56 | final Operation operation = parser.parse(stmt).get(0);
57 | if (operation instanceof SetOperation) {
58 | // SET
59 | tEnv.getConfig()
60 | .getConfiguration()
61 | .setString(
62 | ((SetOperation) operation)
63 | .getKey()
64 | .orElseThrow(() -> new RuntimeException("set key not exist")),
65 | ((SetOperation) operation)
66 | .getValue()
67 | .orElseThrow(() -> new RuntimeException("set value not exist")));
68 | } else if (operation instanceof CatalogSinkModifyOperation) {
69 | // INSERT INTO/OVERWRITE
70 | dml.add((ModifyOperation) operation);
71 | } else {
72 | // fallback to default implementation
73 | ((StreamTableEnvironmentImpl) tEnv).executeInternal(operation);
74 | }
75 | }
76 | if (!dml.isEmpty()) {
77 | ((StreamTableEnvironmentImpl) tEnv).executeInternal(dml);
78 | }
79 | }
80 |
81 | private static String getSql(SqlRunnerOptions options) throws IOException {
82 | if (options.getSqlFile().startsWith("classpath:")) {
83 | return IOUtils.toString(options.getSqlFileInputStreamFromClasspath(), StandardCharsets.UTF_8);
84 | }
85 | URI uri = URI.create(options.getSqlFile());
86 | FileSystem fs = FileSystem.get(uri);
87 | return IOUtils.toString(fs.open(new Path(uri)), StandardCharsets.UTF_8);
88 | }
89 |
90 | private static void call(final String stmt, final CallContext context)
91 | throws ClassNotFoundException, InstantiationException, IllegalAccessException {
92 | //noinspection unchecked
93 | ((Consumer) Class.forName(stmt.split(" ")[1]).newInstance()).accept(context);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/flink-sql-submitter/src/main/java/com/hiscat/flink/cli/CliStatementSplitter.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.cli;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.stream.Collectors;
6 |
7 | /**
8 | * Because we needn't include sql-client dependency. So we need copy this class
9 | *
10 | * Line splitter to determine whether the submitted line is complete. It also offers to split the
11 | * submitted content into multiple statements.
12 | *
13 | * This is a simple splitter. It just split the line in context-unrelated way, e.g. it fails to
14 | * parse line "';\n'"
15 | */
16 | public class CliStatementSplitter {
17 |
18 | private static final String MASK = "--.*$";
19 | private static final String BEGINNING_MASK = "^(\\s)*--.*$";
20 |
21 | public static boolean isStatementComplete(String statement) {
22 | String[] lines = statement.split("\n");
23 | // fix input statement is "\n"
24 | if (lines.length == 0) {
25 | return false;
26 | } else {
27 | return isEndOfStatement(lines[lines.length - 1]);
28 | }
29 | }
30 |
31 | public static List splitContent(String content) {
32 | List statements = new ArrayList<>();
33 | List buffer = new ArrayList<>();
34 |
35 | for (String line : content.split("\n")) {
36 | if (isEndOfStatement(line)) {
37 | buffer.add(line);
38 | statements.add(normalize(buffer));
39 | buffer.clear();
40 | } else {
41 | buffer.add(line);
42 | }
43 | }
44 | if (!buffer.isEmpty()) {
45 | statements.add(normalize(buffer));
46 | }
47 | return statements;
48 | }
49 |
50 | private static String normalize(List buffer) {
51 | // remove comment lines
52 | return buffer.stream()
53 | .map(statementLine -> statementLine.replaceAll(BEGINNING_MASK, ""))
54 | .collect(Collectors.joining("\n"));
55 | }
56 |
57 | private static boolean isEndOfStatement(String line) {
58 | return line.replaceAll(MASK, "").trim().endsWith(";");
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/flink-sql-submitter/src/main/java/com/hiscat/flink/cli/SqlRunnerOptions.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.cli;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 | import org.apache.commons.cli.*;
8 |
9 | import java.io.InputStream;
10 |
11 | @Data
12 | @Builder
13 | @NoArgsConstructor
14 | @AllArgsConstructor
15 | public class SqlRunnerOptions {
16 |
17 | public static final Option OPTION_FILE =
18 | Option.builder("f")
19 | .required(false)
20 | .longOpt("file")
21 | .numberOfArgs(1)
22 | .argName("script file")
23 | .desc(
24 | "Script file that should be executed. In this mode, "
25 | + "the client will not open an interactive terminal.")
26 | .build();
27 |
28 | private String sqlFile;
29 |
30 | public static SqlRunnerOptions parseFromArgs(String[] args) throws ParseException {
31 | final CommandLine cli =
32 | new DefaultParser().parse(new Options().addOption(OPTION_FILE), args, true);
33 |
34 | return SqlRunnerOptions.builder().sqlFile(cli.getOptionValue(OPTION_FILE.getOpt())).build();
35 | }
36 |
37 | public InputStream getSqlFileInputStreamFromClasspath() {
38 | return this.getClass()
39 | .getResourceAsStream(String.format("%s", sqlFile.replace("classpath:", "")));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/flink-sql-submitter/src/main/java/com/hiscat/flink/function/CallContext.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink.function;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.NoArgsConstructor;
7 | import org.apache.flink.api.java.utils.ParameterTool;
8 | import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
9 | import org.apache.flink.table.api.bridge.java.StreamTableEnvironment;
10 |
11 | @Data
12 | @AllArgsConstructor
13 | @NoArgsConstructor
14 | @Builder
15 | public class CallContext {
16 | private StreamExecutionEnvironment env;
17 | private StreamTableEnvironment tEnv;
18 | private ParameterTool args;
19 | }
20 |
--------------------------------------------------------------------------------
/flink-udf/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.hiscat.flink
8 | flink-udf
9 | 1.0-SNAPSHOT
10 |
11 |
12 | 8
13 | 8
14 | 1.13.1
15 | 31.0.1-jre
16 |
17 |
18 |
19 | org.apache.flink
20 | flink-table-planner-blink_2.11
21 | ${flink.version}
22 | provided
23 |
24 |
25 | com.google.guava
26 | guava
27 | ${guava.version}
28 | provided
29 |
30 |
31 |
--------------------------------------------------------------------------------
/flink-udf/src/main/java/com/hiscat/flink/Millisecond2LocalDateTimeString.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink;
2 |
3 | import java.time.Instant;
4 | import java.time.ZoneId;
5 | import org.apache.flink.table.annotation.DataTypeHint;
6 | import org.apache.flink.table.functions.ScalarFunction;
7 |
8 | @SuppressWarnings("unused")
9 | public class Millisecond2LocalDateTimeString extends ScalarFunction {
10 |
11 | public String eval(@DataTypeHint("BIGINT") Long mill ) {
12 | return Instant.ofEpochMilli(mill).atZone(ZoneId.systemDefault()).toLocalDateTime().toString();
13 | }
14 |
15 | public static void main(String[] args) {
16 | System.out.println(Instant.ofEpochMilli(0).atZone(ZoneId.systemDefault()).toLocalDateTime());
17 | //
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/flink-udf/src/main/java/com/hiscat/flink/TopicGetter.java:
--------------------------------------------------------------------------------
1 | package com.hiscat.flink;
2 |
3 | import static java.util.stream.Collectors.toList;
4 |
5 | import com.google.common.cache.Cache;
6 | import com.google.common.cache.CacheBuilder;
7 | import java.time.Duration;
8 | import java.util.List;
9 | import java.util.Map;
10 | import java.util.regex.Pattern;
11 | import org.apache.flink.table.functions.FunctionContext;
12 | import org.apache.flink.table.functions.ScalarFunction;
13 |
14 | public class TopicGetter extends ScalarFunction {
15 | private final List patterns;
16 | private transient Cache cache;
17 | private final Map tableTopicMapping;
18 | private final long cacheSize;
19 | private final Duration cacheExpiredMs;
20 |
21 | @Override
22 | public void open(FunctionContext context) {
23 | this.cache =
24 | CacheBuilder.newBuilder()
25 | .maximumSize(this.cacheSize)
26 | .expireAfterAccess(this.cacheExpiredMs)
27 | .build();
28 | }
29 |
30 | public TopicGetter(
31 | Map tableTopicMapping, long cacheSize, Duration cacheExpiredMs) {
32 | this.patterns = tableTopicMapping.keySet().stream().map(Pattern::compile).collect(toList());
33 | this.tableTopicMapping = tableTopicMapping;
34 | this.cacheSize = cacheSize;
35 | this.cacheExpiredMs = cacheExpiredMs;
36 | }
37 |
38 | public String eval(String db, String table) {
39 | String identifier = String.format("%s.%s", db, table);
40 | String topic = cache.getIfPresent(identifier);
41 | if (topic != null) {
42 | if (topic.isEmpty()) {
43 | return null;
44 | }
45 | return topic;
46 | }
47 | topic =
48 | patterns.stream()
49 | .filter(p -> p.matcher(identifier).matches())
50 | .findFirst()
51 | .map(p -> tableTopicMapping.get(p.pattern()))
52 | .orElse("");
53 | cache.put(identifier, topic);
54 | if (topic.isEmpty()) {
55 | return null;
56 | }
57 | return topic;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/image/how-to-run-sql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MyLanPangzi/flink-demo/369eba5cc59e02f723ee6812402104525718ab3f/image/how-to-run-sql.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.hiscat
8 | flink-demo
9 | 1.0-SNAPSHOT
10 | flink-demo
11 | pom
12 |
13 | flink-custom-connector
14 | flink-sql-submitter
15 | flink-prometheus
16 |
17 |
18 |
19 | 8
20 | 8
21 |
22 | 1.14.0
23 | 2.11
24 | 2.17.0
25 | 1.18.20
26 |
27 |
28 |
29 | org.projectlombok
30 | lombok
31 | ${lombok.version}
32 |
33 |
34 |
35 |
36 |
37 | org.apache.flink
38 | flink-clients_${scala.binary.version}
39 | ${flink.version}
40 |
41 |
42 | org.apache.flink
43 | flink-table-planner_${scala.binary.version}
44 | ${flink.version}
45 |
46 |
47 |
48 | org.apache.flink
49 | flink-json
50 | ${flink.version}
51 |
52 |
53 |
54 |
55 | org.apache.flink
56 | flink-sql-client_2.11
57 | ${flink.version}
58 | provided
59 |
60 |
61 | org.apache.flink
62 | flink-python_2.11
63 | ${flink.version}
64 | provided
65 |
66 |
67 | org.apache.flink
68 | flink-runtime-web_2.11
69 | ${flink.version}
70 | provided
71 |
72 |
73 | org.apache.flink
74 | flink-yarn_2.11
75 | ${flink.version}
76 | provided
77 |
78 |
79 |
80 |
81 | org.apache.flink
82 | flink-connector-kafka_2.11
83 | ${flink.version}
84 | provided
85 |
86 |
87 | org.apache.flink
88 | flink-connector-jdbc_2.11
89 | ${flink.version}
90 | provided
91 |
92 |
93 | com.ververica
94 | flink-connector-mysql-cdc
95 | ${flink-connector-mysql-cdc.version}
96 |
97 |
98 | org.apache.flink
99 | flink-connector-hive_2.11
100 | ${flink.version}
101 | provided
102 |
103 |
104 |
105 |
106 | org.apache.logging.log4j
107 | log4j-api
108 | ${log4j.version}
109 |
110 |
111 | org.apache.logging.log4j
112 | log4j-core
113 | ${log4j.version}
114 |
115 |
116 | org.apache.logging.log4j
117 | log4j-slf4j-impl
118 | ${log4j.version}
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------