| # | |
| # Licensed to the Apache Software Foundation (ASF) under one or more | |
| # contributor license agreements. See the NOTICE file distributed with | |
| # this work for additional information regarding copyright ownership. | |
| # The ASF licenses this file to You under the Apache License, Version 2.0 | |
| # (the "License"); you may not use this file except in compliance with | |
| # the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| """ Unbounded source transform for | |
| `Debezium <href="https://debezium.io/"/>`_. | |
| This transform is currently supported by Beam portable | |
| Flink, Spark, and Dataflow v2 runners. | |
| **Setup** | |
| Transform provided in this module is cross-language transform | |
| implemented in the Beam Java SDK. During the pipeline construction, Python SDK | |
| will connect to a Java expansion service to expand this transform. | |
| To facilitate this, a small amount of setup is needed before using this | |
| transform in a Beam Python pipeline. | |
| There are several ways to setup cross-language Debezium transform. | |
| * Option 1: use the default expansion service | |
| * Option 2: specify a custom expansion service | |
| See below for details regarding each of these options. | |
| *Option 1: Use the default expansion service* | |
| This is the recommended and easiest setup option for using Python Debezium | |
| transform. This option requires following pre-requisites | |
| before running the Beam pipeline. | |
| * Install Java runtime in the computer from where the pipeline is constructed | |
| and make sure that 'java' command is available. | |
| In this option, Python SDK will either download (for released Beam version) or | |
| build (when running from a Beam Git clone) a expansion service jar and use | |
| that to expand transforms. Currently Debezium transform use the | |
| 'beam-sdks-java-io-debezium-expansion-service' jar for this purpose. | |
| *Option 2: specify a custom expansion service* | |
| In this option, you startup your own expansion service and provide that as | |
| a parameter when using the transform provided in this module. | |
| This option requires following pre-requisites before running the Beam | |
| pipeline. | |
| * Startup your own expansion service. | |
| * Update your pipeline to provide the expansion service address when | |
| initiating Debezium transform provided in this module. | |
| Flink Users can use the built-in Expansion Service of the Flink Runner's | |
| Job Server. If you start Flink's Job Server, the expansion service will be | |
| started on port 8097. For a different address, please set the | |
| expansion_service parameter. | |
| **More information** | |
| For more information regarding cross-language transforms see: | |
| - https://beam.apache.org/roadmap/portability/ | |
| For more information specific to Flink runner see: | |
| - https://beam.apache.org/documentation/runners/flink/ | |
| """ | |
| # pytype: skip-file | |
| import json | |
| from enum import Enum | |
| from typing import NamedTuple | |
| from typing import Optional | |
| from apache_beam.transforms import Map | |
| from apache_beam.transforms import PTransform | |
| from apache_beam.transforms.external import BeamJarExpansionService | |
| from apache_beam.transforms.external import ExternalTransform | |
| from apache_beam.transforms.external import NamedTupleBasedPayloadBuilder | |
| __all__ = ['ReadFromDebezium', 'DriverClassName'] | |
| def default_io_expansion_service(): | |
| return BeamJarExpansionService( | |
| 'sdks:java:io:debezium:expansion-service:shadowJar') | |
| class DriverClassName(Enum): | |
| MYSQL = 'MySQL' | |
| POSTGRESQL = 'PostgreSQL' | |
| ORACLE = 'Oracle' | |
| DB2 = 'Db2' | |
| ReadFromDebeziumSchema = NamedTuple( | |
| 'ReadFromDebeziumSchema', | |
| [('connector_class', str), ('username', str), ('password', str), | |
| ('host', str), ('port', str), ('max_number_of_records', Optional[int]), | |
| ('connection_properties', list[str]), | |
| ('start_offset', Optional[list[str]]), | |
| ('offset_storage_path', Optional[str])]) | |
| class ReadFromDebezium(PTransform): | |
| """ | |
| An external PTransform which reads from Debezium and returns | |
| a Dictionary for each item in the specified database | |
| connection. | |
| Experimental; no backwards compatibility guarantees. | |
| """ | |
| URN = 'beam:transform:org.apache.beam:debezium_read:v1' | |
| def __init__( | |
| self, | |
| connector_class, | |
| username, | |
| password, | |
| host, | |
| port, | |
| max_number_of_records=None, | |
| connection_properties=None, | |
| start_offset=None, | |
| offset_storage_path=None, | |
| expansion_service=None): | |
| """ | |
| Initializes a read operation from Debezium. | |
| :param connector_class: name of the jdbc driver class | |
| :param username: database username | |
| :param password: database password | |
| :param host: database host | |
| :param port: database port | |
| :param max_number_of_records: maximum number of records | |
| to be fetched before stop. | |
| :param connection_properties: properties of the debezium | |
| connection passed as string | |
| with format [propertyName=property;]* | |
| :param start_offset: starting offset to resume the connector from | |
| a previously seen position. Provided as a list | |
| of "key=value" strings, where numeric values are | |
| encoded as their decimal string representation. | |
| Example for PostgreSQL:: | |
| start_offset=["lsn=28160840"] | |
| Example for MySQL:: | |
| start_offset=["file=binlog.000001", "pos=156"] | |
| Obtain the offset from the JSON output of a | |
| previous pipeline run (the "metadata" field | |
| contains connector-specific position info) or | |
| via ``SourceRecord.sourceOffset()`` in a custom | |
| Java SourceRecordMapper. | |
| :param offset_storage_path: path to a file where the connector offset | |
| is automatically saved after each checkpoint | |
| and loaded on pipeline startup, allowing the | |
| pipeline to resume from where it left off. | |
| Supports any filesystem available to the | |
| Beam runner (local, GCS, S3, etc.). | |
| Example:: | |
| offset_storage_path=( | |
| "gs://my-bucket/debezium/offset.json" | |
| ) | |
| When set, takes precedence over | |
| ``start_offset``. | |
| :param expansion_service: The address (host:port) | |
| of the ExpansionService. | |
| """ | |
| self.params = ReadFromDebeziumSchema( | |
| connector_class=connector_class.value, | |
| username=username, | |
| password=password, | |
| host=host, | |
| port=str(port), | |
| max_number_of_records=max_number_of_records, | |
| connection_properties=connection_properties, | |
| start_offset=start_offset, | |
| offset_storage_path=offset_storage_path) | |
| self.expansion_service = expansion_service or default_io_expansion_service() | |
| def expand(self, pbegin): | |
| return ( | |
| pbegin | ExternalTransform( | |
| self.URN, | |
| NamedTupleBasedPayloadBuilder(self.params), | |
| self.expansion_service, | |
| ).with_output_types(str) | |
| | 'JsonToDict' >> Map(json.loads).with_output_types(dict)) |