diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 6afc981bed0..f75a867b4c3 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -19,11 +19,19 @@ name: Backend on: workflow_call: + inputs: + TEST_IN_PR: + required: false + type: string + default: 'true' concurrency: group: backend-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +env: + TEST_IN_PR: ${{ inputs.TEST_IN_PR }} + jobs: license-header: name: License header @@ -50,7 +58,7 @@ jobs: dead-link: name: Dead links runs-on: ubuntu-latest - timeout-minutes: 120 + timeout-minutes: 150 steps: - uses: actions/checkout@v2 - run: sudo npm install -g markdown-link-check@3.8.7 @@ -77,6 +85,8 @@ jobs: outputs: api: ${{ steps.filter.outputs.api }} engine: ${{ steps.filter.outputs.engine }} + engine-e2e: ${{ steps.filter.outputs.engine-e2e }} + docs: ${{ steps.filter.outputs.docs }} ut-modules: ${{ steps.ut-modules.outputs.modules }} it-modules: ${{ steps.it-modules.outputs.modules }} steps: @@ -125,13 +135,19 @@ jobs: echo "deleted-poms=$true_or_false" >> $GITHUB_OUTPUT echo "deleted-poms_files=$file_list" >> $GITHUB_OUTPUT + doc_files=`python tools/update_modules_check/check_file_updates.py ua $workspace apache/dev origin/$current_branch "docs/**"` + true_or_false=${doc_files%%$'\n'*} + file_list=${doc_files#*$'\n'} + echo "docs=$true_or_false" >> $GITHUB_OUTPUT + echo "docs_files=$file_list" >> $GITHUB_OUTPUT + engine_e2e_files=`python tools/update_modules_check/check_file_updates.py ua $workspace apache/dev origin/$current_branch "seatunnel-e2e/seatunnel-engine-e2e/**"` true_or_false=${engine_e2e_files%%$'\n'*} file_list=${engine_e2e_files#*$'\n'} echo "engine-e2e=$true_or_false" >> $GITHUB_OUTPUT echo "engine-e2e_files=$file_list" >> $GITHUB_OUTPUT - api_files=`python tools/update_modules_check/check_file_updates.py ua $workspace apache/dev origin/$current_branch "seatunnel-api/**" "seatunnel-common/**" "seatunnel-config/**" "seatunnel-engine/**" "seatunnel-core/**" "seatunnel-e2e/seatunnel-e2e-common/**" "seatunnel-formats/**" "seatunnel-plugin-discovery/**" "seatunnel-transforms-v2/**" "seatunnel-translation/**" "seatunnel-e2e/seatunnel-transforms-v2-e2e/**" "pom.xml" "**/workflows/**" "tools/**" "seatunnel-dist/**"` + api_files=`python tools/update_modules_check/check_file_updates.py ua $workspace apache/dev origin/$current_branch "seatunnel-api/**" "seatunnel-common/**" "seatunnel-config/**" "seatunnel-core/**" "seatunnel-e2e/seatunnel-e2e-common/**" "seatunnel-formats/**" "seatunnel-plugin-discovery/**" "seatunnel-transforms-v2/**" "seatunnel-translation/**" "seatunnel-e2e/seatunnel-transforms-v2-e2e/**" "pom.xml" "**/workflows/**" "tools/**" "seatunnel-dist/**"` true_or_false=${api_files%%$'\n'*} file_list=${api_files#*$'\n'} if [[ $repository_owner == 'apache' ]];then @@ -218,7 +234,7 @@ jobs: - name: Make integration test modules id: it-modules timeout-minutes: 60 - if: ${{ steps.filter.outputs.api == 'false' && (steps.engine-modules.outputs.modules != '' || steps.cv2-modules.outputs.modules != '' || steps.cv2-e2e-modules.outputs.modules != '' || steps.cv2-flink-e2e-modules.outputs.modules != '' || steps.cv2-spark-e2e-modules.outputs.modules != '' || steps.engine-e2e-modules.outputs.modules != '') }} + if: ${{ steps.filter.outputs.api == 'false' && (steps.engine-modules.outputs.modules != '' || steps.cv2-modules.outputs.modules != '' || steps.cv2-e2e-modules.outputs.modules != '' || steps.cv2-flink-e2e-modules.outputs.modules != '' || steps.cv2-spark-e2e-modules.outputs.modules != '') }} run: | modules='${{ steps.cv2-e2e-modules.outputs.modules }}${{ steps.cv2-flink-e2e-modules.outputs.modules }}${{ steps.cv2-spark-e2e-modules.outputs.modules }}${{ steps.engine-e2e-modules.outputs.modules }}${{ steps.engine-modules.outputs.modules }}${{ steps.cv2-modules.outputs.modules }}' modules=${modules: 1} @@ -264,10 +280,69 @@ jobs: max_attempts: 3 retry_on: error command: | - ./mvnw -B -q install -DskipTests -D"maven.test.skip"=true -D"maven.javadoc.skip"=true -D"license.skipAddThirdParty" + ./mvnw -B install -DskipTests -D"maven.test.skip"=true -D"maven.javadoc.skip"=true -D"license.skipAddThirdParty" - name: Check Dependencies Licenses run: tools/dependencies/checkLicense.sh + document: + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.docs == 'true' + needs: [ changes, sanity-check ] + name: Build website + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout PR + uses: actions/checkout@v3 + with: + path: seatunnel-pr + - name: Checkout website repo + uses: actions/checkout@v3 + with: + repository: apache/seatunnel-website + path: seatunnel-website + - name: Sync PR changes to website + run: | + bash seatunnel-pr/tools/documents/sync.sh seatunnel-pr seatunnel-website + - uses: actions/setup-node@v2 + with: + node-version: 16.19.0 + - name: Run docusaurus build + run: | + cd seatunnel-website + npm set strict-ssl false + npm install + npm run build + + seatunnel-ui: + if: needs.changes.outputs.api == 'true' + needs: [ changes, sanity-check ] + name: Build SeaTunnel UI + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout PR + uses: actions/checkout@v3 + + - uses: actions/setup-node@v2 + with: + node-version: 20.x + + - name: Install Dependencies and Check Code Style + run: | + cd seatunnel-engine/seatunnel-engine-ui/ + npm install + npm run lint + + - name: Run unit tests + run: | + cd seatunnel-engine/seatunnel-engine-ui/ + npm run test:unit + + - name: Build SeaTunnel UI + run: | + cd seatunnel-engine/seatunnel-engine-ui/ + npm run build + unit-test: needs: [ changes, sanity-check ] if: needs.changes.outputs.api == 'true' || (needs.changes.outputs.api == 'false' && needs.changes.outputs.ut-modules != '') @@ -287,7 +362,7 @@ jobs: cache: 'maven' - name: run all modules unit test run: | - ./mvnw -B -T 1 clean verify -D"maven.test.skip"=false -D"license.skipAddThirdParty"=true --no-snapshot-updates + ./mvnw -B -T 1 clean verify -DskipUT=false -DskipIT=true -D"license.skipAddThirdParty"=true --no-snapshot-updates env: MAVEN_OPTS: -Xmx4096m @@ -299,7 +374,7 @@ jobs: matrix: java: [ '8', '11' ] os: [ 'ubuntu-latest' ] - timeout-minutes: 120 + timeout-minutes: 180 steps: - uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} @@ -311,7 +386,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-1) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 0` if [ ! -z $sub_modules ]; then @@ -330,7 +404,7 @@ jobs: matrix: java: [ '8', '11' ] os: [ 'ubuntu-latest' ] - timeout-minutes: 120 + timeout-minutes: 150 steps: - uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} @@ -342,7 +416,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-2) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 1` if [ ! -z $sub_modules ]; then @@ -351,7 +424,7 @@ jobs: echo "sub modules is empty, skipping" fi env: - MAVEN_OPTS: -Xmx2048m + MAVEN_OPTS: -Xmx4096m updated-modules-integration-test-part-3: needs: [ changes, sanity-check ] @@ -373,7 +446,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-3) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 2` if [ ! -z $sub_modules ]; then @@ -392,7 +464,7 @@ jobs: matrix: java: [ '8', '11' ] os: [ 'ubuntu-latest' ] - timeout-minutes: 120 + timeout-minutes: 150 steps: - uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} @@ -404,7 +476,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-4) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 3` if [ ! -z $sub_modules ]; then @@ -413,7 +484,7 @@ jobs: echo "sub modules is empty, skipping" fi env: - MAVEN_OPTS: -Xmx2048m + MAVEN_OPTS: -Xmx4096m updated-modules-integration-test-part-5: needs: [ changes, sanity-check ] if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' @@ -434,7 +505,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-5) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 4` if [ ! -z $sub_modules ]; then @@ -464,7 +534,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-6) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 5` if [ ! -z $sub_modules ]; then @@ -494,7 +563,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-7) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 6` if [ ! -z $sub_modules ]; then @@ -525,7 +593,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run updated modules integration test (part-8) - if: needs.changes.outputs.api == 'false' && needs.changes.outputs.it-modules != '' run: | sub_modules=`python tools/update_modules_check/update_modules_check.py sub_update_it_module ${{needs.changes.outputs.it-modules}} 8 7` if [ ! -z $sub_modules ]; then @@ -538,7 +605,7 @@ jobs: engine-v2-it: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' || needs.changes.outputs.engine-e2e == 'true' runs-on: ${{ matrix.os }} strategy: matrix: @@ -556,7 +623,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run seatunnel zeta integration test - if: needs.changes.outputs.api == 'true' run: | ./mvnw -T 1 -B verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-seatunnel-e2e-base,:connector-console-seatunnel-e2e -am -Pci env: @@ -596,8 +662,11 @@ jobs: KUBECONFIG: /etc/rancher/k3s/k3s.yaml transform-v2-it-part-1: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -614,7 +683,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run transform-v2 integration test (part-1) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :seatunnel-transforms-v2-e2e-part-1 -am -Pci env: @@ -622,8 +690,11 @@ jobs: transform-v2-it-part-2: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -640,7 +711,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run transform-v2 integration test (part-2) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :seatunnel-transforms-v2-e2e-part-2 -am -Pci env: @@ -648,8 +718,11 @@ jobs: all-connectors-it-1: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -666,7 +739,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-1) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -677,8 +749,11 @@ jobs: all-connectors-it-2: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -695,7 +770,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-2) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -706,8 +780,11 @@ jobs: all-connectors-it-3: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -724,7 +801,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-3) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -735,8 +811,11 @@ jobs: all-connectors-it-4: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -753,7 +832,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-4) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -764,8 +842,11 @@ jobs: all-connectors-it-5: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -782,7 +863,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-5) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -793,8 +873,11 @@ jobs: all-connectors-it-6: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -811,7 +894,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-6) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -822,8 +904,11 @@ jobs: all-connectors-it-7: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -840,7 +925,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run connector-v2 integration test (part-7) - if: needs.changes.outputs.api == 'true' run: | ./mvnw help:evaluate -Dexpression=project.modules -q -DforceStdout -pl :seatunnel-connector-v2-e2e >> /tmp/sub_module.txt sub_modules=`python tools/update_modules_check/update_modules_check.py sub /tmp/sub_module.txt` @@ -851,8 +935,11 @@ jobs: jdbc-connectors-it-part-1: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -869,7 +956,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-1) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-1 -am -Pci env: @@ -877,8 +963,11 @@ jobs: jdbc-connectors-it-part-2: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -895,7 +984,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-2) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-2 -am -Pci env: @@ -903,8 +991,11 @@ jobs: jdbc-connectors-it-part-3: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -921,7 +1012,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-3) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-3 -am -Pci env: @@ -929,8 +1019,11 @@ jobs: jdbc-connectors-it-part-4: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -947,7 +1040,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-4) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-4 -am -Pci env: @@ -955,8 +1047,11 @@ jobs: jdbc-connectors-it-part-5: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -973,7 +1068,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-5) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-5 -am -Pci env: @@ -981,8 +1075,11 @@ jobs: jdbc-connectors-it-part-6: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -999,7 +1096,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-6) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-6 -am -Pci env: @@ -1007,8 +1103,11 @@ jobs: jdbc-connectors-it-part-7: needs: [ changes, sanity-check ] - if: needs.changes.outputs.api == 'true' + if: needs.changes.outputs.api == 'true' || needs.changes.outputs.engine == 'true' runs-on: ${{ matrix.os }} + env: + RUN_ALL_CONTAINER: ${{ needs.changes.outputs.api }} + RUN_ZETA_CONTAINER: ${{ needs.changes.outputs.engine }} strategy: matrix: java: [ '8', '11' ] @@ -1025,7 +1124,6 @@ jobs: - name: free disk space run: tools/github/free_disk_space.sh - name: run jdbc connectors integration test (part-7) - if: needs.changes.outputs.api == 'true' run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-jdbc-e2e-part-7 -am -Pci env: @@ -1039,7 +1137,7 @@ jobs: matrix: java: [ '8', '11' ] os: [ 'ubuntu-latest' ] - timeout-minutes: 120 + timeout-minutes: 30 steps: - uses: actions/checkout@v2 - name: Set up JDK ${{ matrix.java }} @@ -1205,3 +1303,78 @@ jobs: - name: run oracle cdc connector integration test run: | ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-cdc-oracle-e2e -am -Pci + + connector-file-local-it: + needs: [ changes, sanity-check ] + if: needs.changes.outputs.api == 'true' || contains(needs.changes.outputs.it-modules, 'connector-file-local-e2e') + runs-on: ${{ matrix.os }} + strategy: + matrix: + java: [ '8', '11' ] + os: [ 'ubuntu-latest' ] + timeout-minutes: 120 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: 'maven' + - name: free disk space + run: tools/github/free_disk_space.sh + - name: run file local connector integration test + run: | + ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-file-local-e2e -am -Pci + env: + MAVEN_OPTS: -Xmx4096m + + connector-file-sftp-it: + needs: [ changes, sanity-check ] + if: needs.changes.outputs.api == 'true' || contains(needs.changes.outputs.it-modules, 'connector-file-sftp-e2e') + runs-on: ${{ matrix.os }} + strategy: + matrix: + java: [ '8', '11' ] + os: [ 'ubuntu-latest' ] + timeout-minutes: 120 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: 'maven' + - name: free disk space + run: tools/github/free_disk_space.sh + - name: run file sftp connector integration test + run: | + ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-file-sftp-e2e -am -Pci + env: + MAVEN_OPTS: -Xmx4096m + + connector-redis-it: + needs: [ changes, sanity-check ] + if: needs.changes.outputs.api == 'true' || contains(needs.changes.outputs.it-modules, 'connector-redis-e2e') + runs-on: ${{ matrix.os }} + strategy: + matrix: + java: [ '8', '11' ] + os: [ 'ubuntu-latest' ] + timeout-minutes: 120 + steps: + - uses: actions/checkout@v2 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: 'maven' + - name: free disk space + run: tools/github/free_disk_space.sh + - name: run redis connector integration test + run: | + ./mvnw -B -T 1 verify -DskipUT=true -DskipIT=false -D"license.skipAddThirdParty"=true --no-snapshot-updates -pl :connector-redis-e2e -am -Pci + env: + MAVEN_OPTS: -Xmx4096m diff --git a/.github/workflows/documents.yml b/.github/workflows/documents.yml deleted file mode 100644 index 61d064f0109..00000000000 --- a/.github/workflows/documents.yml +++ /dev/null @@ -1,66 +0,0 @@ -# -# 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. -# - -name: Documents - -on: - pull_request: - paths: - - 'docs/**' - -jobs: - build: - name: Build website - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: Checkout PR - uses: actions/checkout@v3 - with: - path: seatunnel-pr - - - name: Checkout website repo - uses: actions/checkout@v3 - with: - repository: apache/seatunnel-website - path: seatunnel-website - - - name: Sync PR changes to website - run: | - bash seatunnel-pr/tools/documents/sync.sh seatunnel-pr seatunnel-website - - - uses: actions/setup-node@v2 - with: - node-version: 16.19.0 - - - name: Run docusaurus build - run: | - cd seatunnel-website - npm set strict-ssl false - npm install - npm run build - - code-style: - name: Code style - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Check code style - run: ./mvnw --batch-mode --quiet --no-snapshot-updates clean spotless:check diff --git a/.github/workflows/labeler/label-scope-conf.yml b/.github/workflows/labeler/label-scope-conf.yml index b417d53e72a..b825fb9e58b 100644 --- a/.github/workflows/labeler/label-scope-conf.yml +++ b/.github/workflows/labeler/label-scope-conf.yml @@ -132,6 +132,11 @@ http: - changed-files: - any-glob-to-any-file: seatunnel-connectors-v2/connector-http/** - all-globs-to-all-files: '!seatunnel-connectors-v2/connector-!(http)/**' +prometheus: + - all: + - changed-files: + - any-glob-to-any-file: seatunnel-connectors-v2/connector-prometheus/** + - all-globs-to-all-files: '!seatunnel-connectors-v2/connector-!(prometheus)/**' hudi: - all: - changed-files: diff --git a/.github/workflows/notify_test_workflow.yml b/.github/workflows/notify_test_workflow.yml index 58d1b5f2bef..797d067257a 100644 --- a/.github/workflows/notify_test_workflow.yml +++ b/.github/workflows/notify_test_workflow.yml @@ -80,7 +80,7 @@ jobs: status = 'completed' const conclusion = 'action_required' - github.rest.checks.create({ + await github.rest.checks.create({ owner: context.repo.owner, repo: context.repo.repo, name: name, diff --git a/.github/workflows/publish-docker.yaml b/.github/workflows/publish-docker.yaml index 111aabf8511..5fb86b7bc36 100644 --- a/.github/workflows/publish-docker.yaml +++ b/.github/workflows/publish-docker.yaml @@ -76,6 +76,7 @@ jobs: -D"docker.build.skip"=false \ -D"docker.verify.skip"=false \ -D"docker.push.skip"=false \ + -D"skip.spotless"=true \ -Dmaven.deploy.skip \ --no-snapshot-updates \ -Pdocker,seatunnel \ No newline at end of file diff --git a/.github/workflows/schedule_backend.yml b/.github/workflows/schedule_backend.yml index 503a3866df9..9a6aa1cb90a 100644 --- a/.github/workflows/schedule_backend.yml +++ b/.github/workflows/schedule_backend.yml @@ -18,128 +18,17 @@ name: Schedule Backend on: schedule: - - cron: '0 0 03 * *' + - cron: '0 16 * * *' concurrency: - group: backend-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: schedule-backend-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: false jobs: - license-header: - if: github.repository == 'apache/seatunnel' - name: License header - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Check license header - uses: apache/skywalking-eyes@985866ce7e324454f61e22eb2db2e998db09d6f3 - - code-style: - if: github.repository == 'apache/seatunnel' - name: Code style - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Check code style - run: ./mvnw --batch-mode --quiet --no-snapshot-updates clean spotless:check - - dead-link: - if: github.repository == 'apache/seatunnel' - name: Dead links - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v2 - - run: sudo npm install -g markdown-link-check@3.8.7 - - run: | - for file in $(find . -name "*.md"); do - markdown-link-check -c .dlc.json -q "$file" - done - - sanity-check: - if: github.repository == 'apache/seatunnel' - name: Sanity check results - needs: [ license-header, code-style, dead-link ] - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Check results - run: | - [[ ${{ needs.license-header.result }} == 'success' ]] || exit 1; - [[ ${{ needs.code-style.result }} == 'success' ]] || exit 1; - [[ ${{ needs.dead-link.result }} == 'success' ]] || exit 1; - - dependency-license: - name: Dependency licenses - needs: [ sanity-check ] - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '8' - cache: 'maven' - - name: Install - run: >- - ./mvnw -B -q install -DskipTests - -D"maven.test.skip"=true - -D"maven.javadoc.skip"=true - -D"scalastyle.skip"=true - -D"license.skipAddThirdParty" - - name: Check Dependencies Licenses - run: tools/dependencies/checkLicense.sh - - unit-test: - needs: [ sanity-check ] - runs-on: ${{ matrix.os }} - strategy: - matrix: - java: [ '8', '11' ] - os: [ 'ubuntu-latest', 'windows-latest' ] - timeout-minutes: 90 - steps: - - uses: actions/checkout@v2 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 - with: - java-version: ${{ matrix.java }} - distribution: 'temurin' - cache: 'maven' - - name: run all modules unit test - run: | - ./mvnw -B -T 1C clean verify -D"maven.test.skip"=false -D"scalastyle.skip"=true -D"license.skipAddThirdParty"=true --no-snapshot-updates - env: - MAVEN_OPTS: -Xmx2048m - - integration-test: - needs: [ sanity-check ] - runs-on: ${{ matrix.os }} - strategy: - matrix: - java: [ '8', '11' ] - os: [ 'ubuntu-latest' ] - timeout-minutes: 90 - steps: - - uses: actions/checkout@v2 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v3 - with: - java-version: ${{ matrix.java }} - distribution: 'temurin' - cache: 'maven' - - name: run all modules integration test - run: | - ./mvnw -T 1C -B verify -DskipUT=true -DskipIT=false -D"scalastyle.skip"=true -D"license.skipAddThirdParty"=true --no-snapshot-updates - env: - MAVEN_OPTS: -Xmx2048m - + call-build-and-test: + permissions: + packages: write + name: Run + uses: ./.github/workflows/backend.yml + with: + TEST_IN_PR: false diff --git a/.gitignore b/.gitignore index c8732a4acfd..204a966b8a1 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,8 @@ seatunnel-examples /lib/* version.properties +node/ + +dist/ + +seatunnel-engine/seatunnel-engine-server/**/ui/* \ No newline at end of file diff --git a/README.md b/README.md index 2f15fd2209e..27cb1da56a3 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,8 @@ Companies and organizations worldwide use SeaTunnel for research, production, an ### 1. How do I install SeaTunnel? -Follow the [Installation Guide](https://seatunnel.apache.org/docs/2.3.3/start-v2/locally/deployment/) on our website to get started. +Follow the [Installation Guide](https://seatunnel.apache.org/docs/start-v2/locally/deployment/) on our website to get +started. ### 2. How can I contribute to SeaTunnel? @@ -143,6 +144,7 @@ Yes, SeaTunnel is available under the Apache 2.0 License, allowing commercial us Our [Official Documentation](https://seatunnel.apache.org/docs) includes detailed guides and tutorials to help you get started. -### 7. Is there a community or support channel? +### 6. Is there a community or support channel? Join our Slack community for support and discussions: [SeaTunnel Slack](https://s.apache.org/seatunnel-slack). +More information, please refer to [FAQ](https://seatunnel.apache.org/docs/faq). diff --git a/bin/install-plugin.cmd b/bin/install-plugin.cmd index be82e001bd8..2fe2a340f9a 100644 --- a/bin/install-plugin.cmd +++ b/bin/install-plugin.cmd @@ -22,8 +22,8 @@ REM Get seatunnel home set "SEATUNNEL_HOME=%~dp0..\" echo Set SEATUNNEL_HOME to [%SEATUNNEL_HOME%] -REM Connector default version is 2.3.8, you can also choose a custom version. eg: 2.3.8: install-plugin.bat 2.3.8 -set "version=2.3.8" +REM Connector default version is 2.3.9, you can also choose a custom version. eg: 2.3.9: install-plugin.bat 2.3.9 +set "version=2.3.9" if not "%~1"=="" set "version=%~1" REM Create the lib directory diff --git a/bin/install-plugin.sh b/bin/install-plugin.sh index 1938caf30c3..51afda5ad8a 100755 --- a/bin/install-plugin.sh +++ b/bin/install-plugin.sh @@ -23,8 +23,8 @@ # get seatunnel home SEATUNNEL_HOME=$(cd $(dirname $0);cd ../;pwd) -# connector default version is 2.3.8, you can also choose a custom version. eg: 2.3.8: sh install-plugin.sh 2.3.8 -version=2.3.8 +# connector default version is 2.3.9, you can also choose a custom version. eg: 2.3.9: sh install-plugin.sh 2.3.9 +version=2.3.9 if [ -n "$1" ]; then version="$1" diff --git a/config/jvm_master_options b/config/jvm_master_options index c8a7218d1c0..d435a7d2755 100644 --- a/config/jvm_master_options +++ b/config/jvm_master_options @@ -16,8 +16,8 @@ # # JVM Heap --Xms2g --Xmx2g +# -Xms2g +# -Xmx2g # JVM Dump -XX:+HeapDumpOnOutOfMemoryError diff --git a/config/jvm_options b/config/jvm_options index c8a7218d1c0..d435a7d2755 100644 --- a/config/jvm_options +++ b/config/jvm_options @@ -16,8 +16,8 @@ # # JVM Heap --Xms2g --Xmx2g +# -Xms2g +# -Xmx2g # JVM Dump -XX:+HeapDumpOnOutOfMemoryError diff --git a/config/jvm_worker_options b/config/jvm_worker_options index c8a7218d1c0..d435a7d2755 100644 --- a/config/jvm_worker_options +++ b/config/jvm_worker_options @@ -16,8 +16,8 @@ # # JVM Heap --Xms2g --Xmx2g +# -Xms2g +# -Xmx2g # JVM Dump -XX:+HeapDumpOnOutOfMemoryError diff --git a/config/plugin_config b/config/plugin_config index 317b41480e1..317256863f4 100644 --- a/config/plugin_config +++ b/config/plugin_config @@ -88,6 +88,7 @@ connector-tdengine connector-web3j connector-milvus connector-activemq +connector-prometheus connector-sls connector-qdrant connector-typesense diff --git a/config/seatunnel.yaml b/config/seatunnel.yaml index 4117ecc89d3..79a713a71e0 100644 --- a/config/seatunnel.yaml +++ b/config/seatunnel.yaml @@ -17,6 +17,7 @@ seatunnel: engine: + classloader-cache-mode: true history-job-expire-minutes: 1440 backup-count: 1 queue-type: blockingqueue @@ -37,3 +38,9 @@ seatunnel: telemetry: metric: enabled: false + log: + scheduled-deletion-enable: true + http: + enable-http: true + port: 8080 + enable-dynamic-port: false diff --git a/config/v2.batch.config.template b/config/v2.batch.config.template index 32763acc302..5affe8af423 100644 --- a/config/v2.batch.config.template +++ b/config/v2.batch.config.template @@ -29,7 +29,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/config/v2.streaming.conf.template b/config/v2.streaming.conf.template index f1a2583c364..9211659b6f8 100644 --- a/config/v2.streaming.conf.template +++ b/config/v2.streaming.conf.template @@ -29,7 +29,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/about.md b/docs/en/about.md index 6cf85d500d3..a63fde4d45b 100644 --- a/docs/en/about.md +++ b/docs/en/about.md @@ -64,7 +64,7 @@ SeaTunnel has lots of users. You can find more information about them in [Users]

  

-SeaTunnel enriches the CNCF CLOUD NATIVE Landscape. +SeaTunnel enriches the CNCF CLOUD NATIVE Landscape.

## Learn more diff --git a/docs/en/concept/config.md b/docs/en/concept/config.md index cec01dc0d8f..d1fb0f07cda 100644 --- a/docs/en/concept/config.md +++ b/docs/en/concept/config.md @@ -19,6 +19,12 @@ config directory. The config file is similar to the below one: +:::warn + +The old configuration name `source_table_name`/`result_table_name` is deprecated, please migrate to the new name `plugin_input`/`plugin_output` as soon as possible. + +::: + ### hocon ```hocon @@ -28,7 +34,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -42,8 +48,8 @@ source { transform { Filter { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" fields = [name, card] } } @@ -56,7 +62,7 @@ sink { fields = ["name", "card"] username = "default" password = "" - source_table_name = "fake1" + plugin_input = "fake1" } } ``` @@ -80,7 +86,7 @@ Source is used to define where SeaTunnel needs to fetch data, and use the fetche Multiple sources can be defined at the same time. The supported source can be found in [Source of SeaTunnel](../connector-v2/source). Each source has its own specific parameters to define how to fetch data, and SeaTunnel also extracts the parameters that each source will use, such as -the `result_table_name` parameter, which is used to specify the name of the data generated by the current +the `plugin_output` parameter, which is used to specify the name of the data generated by the current source, which is convenient for follow-up used by other modules. ### transform @@ -96,7 +102,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -116,7 +122,7 @@ sink { fields = ["name", "age", "card"] username = "default" password = "" - source_table_name = "fake" + plugin_input = "fake" } } ``` @@ -134,11 +140,11 @@ and efficiently. Sink and source are very similar, but the difference is reading ### Other Information You will find that when multiple sources and multiple sinks are defined, which data is read by each sink, and -which is the data read by each transform? We introduce two key configurations called `result_table_name` and -`source_table_name`. Each source module will be configured with a `result_table_name` to indicate the name of the -data source generated by the data source, and other transform and sink modules can use `source_table_name` to +which is the data read by each transform? We introduce two key configurations called `plugin_output` and +`plugin_input`. Each source module will be configured with a `plugin_output` to indicate the name of the +data source generated by the data source, and other transform and sink modules can use `plugin_input` to refer to the corresponding data source name, indicating that I want to read the data for processing. Then -transform, as an intermediate processing module, can use both `result_table_name` and `source_table_name` +transform, as an intermediate processing module, can use both `plugin_output` and `plugin_input` configurations at the same time. But you will find that in the above example config, not every module is configured with these two parameters, because in SeaTunnel, there is a default convention, if these two parameters are not configured, then the generated data from the last module of the previous node will be used. @@ -170,7 +176,7 @@ Before writing the config file, please make sure that the name of the config fil "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 100, "schema": { "fields": { @@ -184,8 +190,8 @@ Before writing the config file, please make sure that the name of the config fil "transform": [ { "plugin_name": "Filter", - "source_table_name": "fake", - "result_table_name": "fake1", + "plugin_input": "fake", + "plugin_output": "fake1", "fields": ["name", "card"] } ], @@ -198,7 +204,7 @@ Before writing the config file, please make sure that the name of the config fil "fields": ["name", "card"], "username": "default", "password": "", - "source_table_name": "fake1" + "plugin_input": "fake1" } ] } @@ -234,7 +240,7 @@ env { source { FakeSource { - result_table_name = "${resName:fake_test}_table" + plugin_output = "${resName:fake_test}_table" row.num = "${rowNum:50}" string.template = ${strTemplate} int.template = [20, 21] @@ -249,8 +255,8 @@ source { transform { sql { - source_table_name = "${resName:fake_test}_table" - result_table_name = "sql" + plugin_input = "${resName:fake_test}_table" + plugin_output = "sql" query = "select * from ${resName:fake_test}_table where name = '${nameVal}' " } @@ -258,7 +264,7 @@ transform { sink { Console { - source_table_name = "sql" + plugin_input = "sql" username = ${username} password = ${password} } @@ -291,7 +297,7 @@ env { source { FakeSource { - result_table_name = "fake_test_table" + plugin_output = "fake_test_table" row.num = 50 string.template = ['abc','d~f','hi'] int.template = [20, 21] @@ -306,8 +312,8 @@ source { transform { sql { - source_table_name = "fake_test_table" - result_table_name = "sql" + plugin_input = "fake_test_table" + plugin_output = "sql" query = "select * from fake_test_table where name = 'abc' " } @@ -315,7 +321,7 @@ transform { sink { Console { - source_table_name = "sql" + plugin_input = "sql" username = "seatunnel=2.3.1" password = "$a^b%c.d~e0*9(" } diff --git a/docs/en/concept/schema-evolution.md b/docs/en/concept/schema-evolution.md index 067bfc7b1c9..f28c10f5f54 100644 --- a/docs/en/concept/schema-evolution.md +++ b/docs/en/concept/schema-evolution.md @@ -1,19 +1,25 @@ # Schema evolution Schema Evolution means that the schema of a data table can be changed and the data synchronization task can automatically adapt to the changes of the new table structure without any other operations. -Now we only support the operation about `add column`、`drop column`、`rename column` and `modify column` of the table in CDC source. This feature is only support zeta engine at now. +Now we only support the operation about `add column`、`drop column`、`rename column` and `modify column` of the table in CDC source. This feature is only support zeta engine at now. + ## Supported connectors ### Source [Mysql-CDC](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/MySQL-CDC.md) +[Oracle-CDC](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/Oracle-CDC.md) ### Sink [Jdbc-Mysql](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/sink/Jdbc.md) +[Jdbc-Oracle](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/sink/Jdbc.md) + +Note: The schema evolution is not support the transform at now. The schema evolution of different types of databases(Oracle-CDC -> Jdbc-Mysql)is currently not supported the default value of the column in ddl. -Note: The schema evolution is not support the transform at now. +When you use the Oracle-CDC,you can not use the username named `SYS` or `SYSTEM` to modify the table schema, otherwise the ddl event will be filtered out which can lead to the schema evolution not working. +Otherwise, If your table name start with `ORA_TEMP_` will also has the same problem. ## Enable schema evolution -Schema evolution is disabled by default in CDC source. You need configure `debezium.include.schema.changes = true` which is only supported in MySQL-CDC to enable it. +Schema evolution is disabled by default in CDC source. You need configure `debezium.include.schema.changes = true` which is only supported in CDC to enable it. When you use Oracle-CDC with schema-evolution enabled, you must specify `redo_log_catalog` as `log.mining.strategy` in the `debezium` attribute. ## Examples @@ -56,3 +62,92 @@ sink { } } ``` + +### Oracle-cdc -> Jdbc-Oracle +``` +env { + # You can set engine configuration here + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 +} + +source { + # This is a example source plugin **only for test and demonstrate the feature source plugin** + Oracle-CDC { + plugin_output = "customers" + username = "dbzuser" + password = "dbz" + database-names = ["ORCLCDB"] + schema-names = ["DEBEZIUM"] + table-names = ["ORCLCDB.DEBEZIUM.FULL_TYPES"] + base-url = "jdbc:oracle:thin:@oracle-host:1521/ORCLCDB" + source.reader.close.timeout = 120000 + connection.pool.size = 1 + debezium { + include.schema.changes = true + log.mining.strategy = redo_log_catalog + } + } +} + +sink { + Jdbc { + plugin_input = "customers" + driver = "oracle.jdbc.driver.OracleDriver" + url = "jdbc:oracle:thin:@oracle-host:1521/ORCLCDB" + user = "dbzuser" + password = "dbz" + generate_sink_sql = true + database = "ORCLCDB" + table = "DEBEZIUM.FULL_TYPES_SINK" + batch_size = 1 + primary_keys = ["ID"] + connection.pool.size = 1 + } +} +``` + +### Oracle-cdc -> Jdbc-Mysql +``` +env { + # You can set engine configuration here + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 +} + +source { + # This is a example source plugin **only for test and demonstrate the feature source plugin** + Oracle-CDC { + plugin_output = "customers" + username = "dbzuser" + password = "dbz" + database-names = ["ORCLCDB"] + schema-names = ["DEBEZIUM"] + table-names = ["ORCLCDB.DEBEZIUM.FULL_TYPES"] + base-url = "jdbc:oracle:thin:@oracle-host:1521/ORCLCDB" + source.reader.close.timeout = 120000 + connection.pool.size = 1 + debezium { + include.schema.changes = true + log.mining.strategy = redo_log_catalog + } + } +} + +sink { + jdbc { + plugin_input = "customers" + url = "jdbc:mysql://oracle-host:3306/oracle_sink" + driver = "com.mysql.cj.jdbc.Driver" + user = "st_user_sink" + password = "mysqlpw" + generate_sink_sql = true + # You need to configure both database and table + database = oracle_sink + table = oracle_cdc_2_mysql_sink_table + primary_keys = ["ID"] + } +} +``` diff --git a/docs/en/concept/schema-feature.md b/docs/en/concept/schema-feature.md index feb94cc640a..3a4e83e06e5 100644 --- a/docs/en/concept/schema-feature.md +++ b/docs/en/concept/schema-feature.md @@ -172,6 +172,46 @@ constraintKeys = [ | INDEX_KEY | key | | UNIQUE_KEY | unique key | +## Multi table schemas + +``` +tables_configs = [ + { + schema { + table = "database.schema.table1" + schema_first = false + comment = "comment" + columns = [ + ... + ] + primaryKey { + ... + } + constraintKeys { + ... + } + } + }, + { + schema = { + table = "database.schema.table2" + schema_first = false + comment = "comment" + columns = [ + ... + ] + primaryKey { + ... + } + constraintKeys { + ... + } + } + } +] + +``` + ## How to use schema ### Recommended @@ -180,7 +220,7 @@ constraintKeys = [ source { FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema { table = "FakeDatabase.FakeTable" @@ -234,7 +274,7 @@ If you only need to define the column, you can use fields to define the column, source { FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/Config-Encryption-Decryption.md b/docs/en/connector-v2/Config-Encryption-Decryption.md index a5ff8e3439a..341fbf11a1b 100644 --- a/docs/en/connector-v2/Config-Encryption-Decryption.md +++ b/docs/en/connector-v2/Config-Encryption-Decryption.md @@ -42,7 +42,7 @@ Next, I'll show how to quickly use SeaTunnel's own `base64` encryption: source { MySQL-CDC { - result_table_name = "fake" + plugin_output = "fake" parallelism = 1 server-id = 5656 port = 56725 @@ -96,7 +96,7 @@ Next, I'll show how to quickly use SeaTunnel's own `base64` encryption: "port" : 56725, "database-name" : "inventory_vwyw0n", "parallelism" : 1, - "result_table_name" : "fake", + "plugin_output" : "fake", "table-name" : "products", "plugin_name" : "MySQL-CDC", "server-id" : 5656, diff --git a/docs/en/connector-v2/formats/avro.md b/docs/en/connector-v2/formats/avro.md index 8fef411fb58..62cb19b95da 100644 --- a/docs/en/connector-v2/formats/avro.md +++ b/docs/en/connector-v2/formats/avro.md @@ -51,7 +51,7 @@ source { } } } - result_table_name = "fake" + plugin_output = "fake" } } @@ -76,7 +76,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "test_avro_topic" - result_table_name = "kafka_table" + plugin_output = "kafka_table" start_mode = "earliest" format = avro format_error_handle_way = skip @@ -104,7 +104,7 @@ source { sink { Console { - source_table_name = "kafka_table" + plugin_input = "kafka_table" } } ``` diff --git a/docs/en/connector-v2/formats/canal-json.md b/docs/en/connector-v2/formats/canal-json.md index 6e133a9a82a..cb8aa3d5edb 100644 --- a/docs/en/connector-v2/formats/canal-json.md +++ b/docs/en/connector-v2/formats/canal-json.md @@ -85,7 +85,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "products_binlog" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/en/connector-v2/formats/cdc-compatible-debezium-json.md b/docs/en/connector-v2/formats/cdc-compatible-debezium-json.md index b35501a62a7..564eb2356ce 100644 --- a/docs/en/connector-v2/formats/cdc-compatible-debezium-json.md +++ b/docs/en/connector-v2/formats/cdc-compatible-debezium-json.md @@ -17,7 +17,7 @@ env { source { MySQL-CDC { - result_table_name = "table1" + plugin_output = "table1" base-url="jdbc:mysql://localhost:3306/test" "startup.mode"=INITIAL @@ -43,9 +43,10 @@ source { sink { Kafka { - source_table_name = "table1" + plugin_input = "table1" bootstrap.servers = "localhost:9092" + topic = "${topic}" # compatible_debezium_json options format = compatible_debezium_json diff --git a/docs/en/connector-v2/formats/debezium-json.md b/docs/en/connector-v2/formats/debezium-json.md index 5f71e14f09d..e296d2404e1 100644 --- a/docs/en/connector-v2/formats/debezium-json.md +++ b/docs/en/connector-v2/formats/debezium-json.md @@ -84,7 +84,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "products_binlog" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/en/connector-v2/formats/kafka-compatible-kafkaconnect-json.md b/docs/en/connector-v2/formats/kafka-compatible-kafkaconnect-json.md index def638367ca..32ad5808c1c 100644 --- a/docs/en/connector-v2/formats/kafka-compatible-kafkaconnect-json.md +++ b/docs/en/connector-v2/formats/kafka-compatible-kafkaconnect-json.md @@ -16,7 +16,7 @@ source { Kafka { bootstrap.servers = "localhost:9092" topic = "jdbc_source_record" - result_table_name = "kafka_table" + plugin_output = "kafka_table" start_mode = earliest schema = { fields { diff --git a/docs/en/connector-v2/formats/maxwell-json.md b/docs/en/connector-v2/formats/maxwell-json.md index 5e1c851d9e9..d271d71624a 100644 --- a/docs/en/connector-v2/formats/maxwell-json.md +++ b/docs/en/connector-v2/formats/maxwell-json.md @@ -62,7 +62,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "products_binlog" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/en/connector-v2/formats/ogg-json.md b/docs/en/connector-v2/formats/ogg-json.md index 3faeb33c4f0..fb14802aaa4 100644 --- a/docs/en/connector-v2/formats/ogg-json.md +++ b/docs/en/connector-v2/formats/ogg-json.md @@ -66,7 +66,7 @@ source { Kafka { bootstrap.servers = "127.0.0.1:9092" topic = "ogg" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/en/connector-v2/formats/protobuf.md b/docs/en/connector-v2/formats/protobuf.md new file mode 100644 index 00000000000..916da551b76 --- /dev/null +++ b/docs/en/connector-v2/formats/protobuf.md @@ -0,0 +1,163 @@ +# Protobuf Format + +Protobuf (Protocol Buffers) is a language-neutral, platform-independent data serialization format developed by Google. It provides an efficient way to encode structured data and supports multiple programming languages and platforms. + +Currently, Protobuf format can be used with Kafka. + +## Kafka Usage Example + +- Example of simulating a randomly generated data source and writing it to Kafka in Protobuf format + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + parallelism = 1 + plugin_output = "fake" + row.num = 16 + schema = { + fields { + c_int32 = int + c_int64 = long + c_float = float + c_double = double + c_bool = boolean + c_string = string + c_bytes = bytes + + Address { + city = string + state = string + street = string + } + attributes = "map" + phone_numbers = "array" + } + } + } +} + +sink { + kafka { + topic = "test_protobuf_topic_fake_source" + bootstrap.servers = "kafkaCluster:9092" + format = protobuf + kafka.request.timeout.ms = 60000 + kafka.config = { + acks = "all" + request.timeout.ms = 60000 + buffer.memory = 33554432 + } + protobuf_message_name = Person + protobuf_schema = """ + syntax = "proto3"; + + package org.apache.seatunnel.format.protobuf; + + option java_outer_classname = "ProtobufE2E"; + + message Person { + int32 c_int32 = 1; + int64 c_int64 = 2; + float c_float = 3; + double c_double = 4; + bool c_bool = 5; + string c_string = 6; + bytes c_bytes = 7; + + message Address { + string street = 1; + string city = 2; + string state = 3; + string zip = 4; + } + + Address address = 8; + + map attributes = 9; + + repeated string phone_numbers = 10; + } + """ + } +} +``` + +- Example of reading data from Kafka in Protobuf format and printing it to the console + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + Kafka { + topic = "test_protobuf_topic_fake_source" + format = protobuf + protobuf_message_name = Person + protobuf_schema = """ + syntax = "proto3"; + + package org.apache.seatunnel.format.protobuf; + + option java_outer_classname = "ProtobufE2E"; + + message Person { + int32 c_int32 = 1; + int64 c_int64 = 2; + float c_float = 3; + double c_double = 4; + bool c_bool = 5; + string c_string = 6; + bytes c_bytes = 7; + + message Address { + string street = 1; + string city = 2; + string state = 3; + string zip = 4; + } + + Address address = 8; + + map attributes = 9; + + repeated string phone_numbers = 10; + } + """ + schema = { + fields { + c_int32 = int + c_int64 = long + c_float = float + c_double = double + c_bool = boolean + c_string = string + c_bytes = bytes + + Address { + city = string + state = string + street = string + } + attributes = "map" + phone_numbers = "array" + } + } + bootstrap.servers = "kafkaCluster:9092" + start_mode = "earliest" + plugin_output = "kafka_table" + } +} + +sink { + Console { + plugin_input = "kafka_table" + } +} +``` \ No newline at end of file diff --git a/docs/en/connector-v2/sink-common-options.md b/docs/en/connector-v2/sink-common-options.md index 20ceda3dfe7..c452adc801e 100644 --- a/docs/en/connector-v2/sink-common-options.md +++ b/docs/en/connector-v2/sink-common-options.md @@ -6,13 +6,19 @@ sidebar_position: 4 > Common parameters of sink connectors -| Name | Type | Required | Default | Description | -|-------------------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| source_table_name | String | No | - | When `source_table_name` is not specified, the current plug-in processes the data set `dataset` output by the previous plugin in the configuration file
When `source_table_name` is specified, the current plug-in is processing the data set corresponding to this parameter. | +:::warn + +The old configuration name `source_table_name` is deprecated, please migrate to the new name `plugin_input` as soon as possible. + +::: + +| Name | Type | Required | Default | Description | +|--------------|--------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| plugin_input | String | No | - | When `plugin_input` is not specified, the current plug-in processes the data set `dataset` output by the previous plugin in the configuration file
When `plugin_input` is specified, the current plug-in is processing the data set corresponding to this parameter. | # Important note -When the job configuration `source_table_name` you must set the `result_table_name` parameter +When the job configuration `plugin_input` you must set the `plugin_output` parameter ## Task Example @@ -24,34 +30,34 @@ When the job configuration `source_table_name` you must set the `result_table_na source { FakeSourceStream { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" field_name = "name,age" } } transform { Filter { - source_table_name = "fake" + plugin_input = "fake" fields = [name] - result_table_name = "fake_name" + plugin_output = "fake_name" } Filter { - source_table_name = "fake" + plugin_input = "fake" fields = [age] - result_table_name = "fake_age" + plugin_output = "fake_age" } } sink { Console { - source_table_name = "fake_name" + plugin_input = "fake_name" } Console { - source_table_name = "fake_age" + plugin_input = "fake_age" } } ``` -> If the job only have one source and one(or zero) transform and one sink, You do not need to specify `source_table_name` and `result_table_name` for connector. -> If the number of any operator in source, transform and sink is greater than 1, you must specify the `source_table_name` and `result_table_name` for each connector in the job. +> If the job only have one source and one(or zero) transform and one sink, You do not need to specify `plugin_input` and `plugin_output` for connector. +> If the number of any operator in source, transform and sink is greater than 1, you must specify the `plugin_input` and `plugin_output` for each connector in the job. diff --git a/docs/en/connector-v2/sink/AmazonSqs.md b/docs/en/connector-v2/sink/AmazonSqs.md index 8efabfa395b..4a43349b388 100644 --- a/docs/en/connector-v2/sink/AmazonSqs.md +++ b/docs/en/connector-v2/sink/AmazonSqs.md @@ -70,7 +70,7 @@ source { } } } - result_table_name = "fake" + plugin_output = "fake" } } diff --git a/docs/en/connector-v2/sink/Assert.md b/docs/en/connector-v2/sink/Assert.md index bc5b4c1bf32..026adddfae3 100644 --- a/docs/en/connector-v2/sink/Assert.md +++ b/docs/en/connector-v2/sink/Assert.md @@ -267,13 +267,13 @@ source { ] } ] - result_table_name = "fake" + plugin_output = "fake" } } sink{ Assert { - source_table_name = "fake" + plugin_input = "fake" rules = { row_rules = [ diff --git a/docs/en/connector-v2/sink/Console.md b/docs/en/connector-v2/sink/Console.md index a1c3b570baf..3493915d029 100644 --- a/docs/en/connector-v2/sink/Console.md +++ b/docs/en/connector-v2/sink/Console.md @@ -44,7 +44,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" schema = { fields { name = "string" @@ -56,7 +56,7 @@ source { sink { Console { - source_table_name = "fake" + plugin_input = "fake" } } ``` @@ -73,7 +73,7 @@ env { source { FakeSource { - result_table_name = "fake1" + plugin_output = "fake1" schema = { fields { id = "int" @@ -84,7 +84,7 @@ source { } } FakeSource { - result_table_name = "fake2" + plugin_output = "fake2" schema = { fields { name = "string" @@ -96,10 +96,10 @@ source { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } Console { - source_table_name = "fake2" + plugin_input = "fake2" } } ``` diff --git a/docs/en/connector-v2/sink/DB2.md b/docs/en/connector-v2/sink/DB2.md index 92df20bd63d..7902c31f08c 100644 --- a/docs/en/connector-v2/sink/DB2.md +++ b/docs/en/connector-v2/sink/DB2.md @@ -101,7 +101,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/HdfsFile.md b/docs/en/connector-v2/sink/HdfsFile.md index 9c2aec0c54b..3060e8ac8b2 100644 --- a/docs/en/connector-v2/sink/HdfsFile.md +++ b/docs/en/connector-v2/sink/HdfsFile.md @@ -93,7 +93,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Hive.md b/docs/en/connector-v2/sink/Hive.md index 147fd766a9f..20f3d22cb86 100644 --- a/docs/en/connector-v2/sink/Hive.md +++ b/docs/en/connector-v2/sink/Hive.md @@ -8,7 +8,7 @@ Write data to Hive. :::tip -In order to use this connector, You must ensure your spark/flink cluster already integrated hive. The tested hive version is 2.3.9. +In order to use this connector, You must ensure your spark/flink cluster already integrated hive. The tested hive version is 2.3.9 and 3.1.3 . If you use SeaTunnel Engine, You need put seatunnel-hadoop3-3.1.4-uber.jar and hive-exec-3.1.3.jar and libfb303-0.9.3.jar in $SEATUNNEL_HOME/lib/ dir. ::: @@ -182,6 +182,78 @@ sink { } ``` +### example2: Kerberos + +```bash +sink { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} +``` + +Description: + +- `hive_site_path`: The path to the `hive-site.xml` file. +- `kerberos_principal`: The principal for Kerberos authentication. +- `kerberos_keytab_path`: The keytab file path for Kerberos authentication. +- `krb5_path`: The path to the `krb5.conf` file used for Kerberos authentication. + +Run the case: + +```bash +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + pk_id = bigint + name = string + score = int + } + primaryKey { + name = "pk_id" + columnNames = [pk_id] + } + } + rows = [ + { + kind = INSERT + fields = [1, "A", 100] + }, + { + kind = INSERT + fields = [2, "B", 100] + }, + { + kind = INSERT + fields = [3, "C", 100] + } + ] + } +} + +sink { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} +``` + ## Hive on s3 ### Step 1 @@ -395,26 +467,3 @@ sink { } } ``` - -## Changelog - -### 2.2.0-beta 2022-09-26 - -- Add Hive Sink Connector - -### 2.3.0-beta 2022-10-20 - -- [Improve] Hive Sink supports automatic partition repair ([3133](https://github.com/apache/seatunnel/pull/3133)) - -### 2.3.0 2022-12-30 - -- [BugFix] Fixed the following bugs that failed to write data to files ([3258](https://github.com/apache/seatunnel/pull/3258)) - - When field from upstream is null it will throw NullPointerException - - Sink columns mapping failed - - When restore writer from states getting transaction directly failed - -### Next version - -- [Improve] Support kerberos authentication ([3840](https://github.com/apache/seatunnel/pull/3840)) -- [Improve] Added partition_dir_expression validation logic ([3886](https://github.com/apache/seatunnel/pull/3886)) - diff --git a/docs/en/connector-v2/sink/Hudi.md b/docs/en/connector-v2/sink/Hudi.md index 6c424fde15e..ea4c066d2f8 100644 --- a/docs/en/connector-v2/sink/Hudi.md +++ b/docs/en/connector-v2/sink/Hudi.md @@ -8,7 +8,7 @@ Used to write data to Hudi. ## Key features -- [x] [exactly-once](../../concept/connector-v2-features.md) +- [ ] [exactly-once](../../concept/connector-v2-features.md) - [x] [cdc](../../concept/connector-v2-features.md) - [x] [support multiple table write](../../concept/connector-v2-features.md) @@ -21,7 +21,6 @@ Base configuration: | table_dfs_path | string | yes | - | | conf_files_path | string | no | - | | table_list | Array | no | - | -| auto_commit | boolean | no | true | | schema_save_mode | enum | no | CREATE_SCHEMA_WHEN_NOT_EXIST| | common-options | Config | no | - | @@ -44,6 +43,7 @@ Table list configuration: | index_type | enum | no | BLOOM | | index_class_name | string | no | - | | record_byte_size | Int | no | 1024 | +| cdc_enabled | boolean| no | false | Note: When this configuration corresponds to a single table, you can flatten the configuration items in table_list to the outer layer. @@ -115,9 +115,9 @@ Note: When this configuration corresponds to a single table, you can flatten the `max_commits_to_keep` The max commits to keep of hudi table. -### auto_commit [boolean] +### cdc_enabled [boolean] -`auto_commit` Automatic transaction commit is enabled by default. +`cdc_enabled` Whether to persist the CDC change log. When enable, persist the change data if necessary, and the table can be queried as a CDC query mode. ### schema_save_mode [Enum] diff --git a/docs/en/connector-v2/sink/Iceberg.md b/docs/en/connector-v2/sink/Iceberg.md index 721c5ea7c08..54c46b849bf 100644 --- a/docs/en/connector-v2/sink/Iceberg.md +++ b/docs/en/connector-v2/sink/Iceberg.md @@ -59,7 +59,7 @@ libfb303-xxx.jar ## Sink Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |----------------------------------------|---------|----------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | catalog_name | string | yes | default | User-specified catalog name. default is `default` | | namespace | string | yes | default | The iceberg database name in the backend catalog. default is `default` | @@ -76,6 +76,7 @@ libfb303-xxx.jar | iceberg.table.upsert-mode-enabled | boolean | no | false | Set to `true` to enable upsert mode, default is `false` | | schema_save_mode | Enum | no | CREATE_SCHEMA_WHEN_NOT_EXIST | the schema save mode, please refer to `schema_save_mode` below | | data_save_mode | Enum | no | APPEND_DATA | the data save mode, please refer to `data_save_mode` below | +| custom_sql | string | no | - | Custom `delete` data sql for data save mode. e.g: `delete from ... where ...` | | iceberg.table.commit-branch | string | no | - | Default branch for commits | ## Task Example @@ -91,7 +92,7 @@ env { source { MySQL-CDC { - result_table_name = "customers_mysql_cdc_iceberg" + plugin_output = "customers_mysql_cdc_iceberg" server-id = 5652 username = "st_user" password = "seatunnel" diff --git a/docs/en/connector-v2/sink/Jdbc.md b/docs/en/connector-v2/sink/Jdbc.md index 1ddbdd507d9..9b86a27721d 100644 --- a/docs/en/connector-v2/sink/Jdbc.md +++ b/docs/en/connector-v2/sink/Jdbc.md @@ -226,7 +226,7 @@ In the case of is_exactly_once = "true", Xa transactions are used. This requires there are some reference value for params above. -| datasource | driver | url | xa_data_source_class_name | maven | +| datasource | driver | url | xa_data_source_class_name | maven | |-------------------|----------------------------------------------|--------------------------------------------------------------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| | MySQL | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | com.mysql.cj.jdbc.MysqlXADataSource | https://mvnrepository.com/artifact/mysql/mysql-connector-java | | PostgreSQL | org.postgresql.Driver | jdbc:postgresql://localhost:5432/postgres | org.postgresql.xa.PGXADataSource | https://mvnrepository.com/artifact/org.postgresql/postgresql | @@ -235,7 +235,7 @@ there are some reference value for params above. | SQL Server | com.microsoft.sqlserver.jdbc.SQLServerDriver | jdbc:sqlserver://localhost:1433 | com.microsoft.sqlserver.jdbc.SQLServerXADataSource | https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc | | Oracle | oracle.jdbc.OracleDriver | jdbc:oracle:thin:@localhost:1521/xepdb1 | oracle.jdbc.xa.OracleXADataSource | https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 | | sqlite | org.sqlite.JDBC | jdbc:sqlite:test.db | / | https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc | -| GBase8a | com.gbase.jdbc.Driver | jdbc:gbase://e2e_gbase8aDb:5258/test | / | https://cdn.gbase.cn/products/30/p5CiVwXBKQYIUGN8ecHvk/gbase-connector-java-9.5.0.7-build1-bin.jar | +| GBase8a | com.gbase.jdbc.Driver | jdbc:gbase://e2e_gbase8aDb:5258/test | / | https://cdn.gbase.cn/products/30/p5CiVwXBKQYIUGN8ecHvk/gbase-connector-java-9.5.0.7-build1-bin.jar | | StarRocks | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | / | https://mvnrepository.com/artifact/mysql/mysql-connector-java | | db2 | com.ibm.db2.jcc.DB2Driver | jdbc:db2://localhost:50000/testdb | com.ibm.db2.jcc.DB2XADataSource | https://mvnrepository.com/artifact/com.ibm.db2.jcc/db2jcc/db2jcc4 | | saphana | com.sap.db.jdbc.Driver | jdbc:sap://localhost:39015 | / | https://mvnrepository.com/artifact/com.sap.cloud.db.jdbc/ngdbc | @@ -245,9 +245,10 @@ there are some reference value for params above. | Snowflake | net.snowflake.client.jdbc.SnowflakeDriver | jdbc:snowflake://.snowflakecomputing.com | / | https://mvnrepository.com/artifact/net.snowflake/snowflake-jdbc | | Vertica | com.vertica.jdbc.Driver | jdbc:vertica://localhost:5433 | / | https://repo1.maven.org/maven2/com/vertica/jdbc/vertica-jdbc/12.0.3-0/vertica-jdbc-12.0.3-0.jar | | Kingbase | com.kingbase8.Driver | jdbc:kingbase8://localhost:54321/db_test | / | https://repo1.maven.org/maven2/cn/com/kingbase/kingbase8/8.6.0/kingbase8-8.6.0.jar | -| OceanBase | com.oceanbase.jdbc.Driver | jdbc:oceanbase://localhost:2881 | / | https://repo1.maven.org/maven2/com/oceanbase/oceanbase-client/2.4.11/oceanbase-client-2.4.11.jar | +| OceanBase | com.oceanbase.jdbc.Driver | jdbc:oceanbase://localhost:2881 | / | https://repo1.maven.org/maven2/com/oceanbase/oceanbase-client/2.4.12/oceanbase-client-2.4.12.jar | | xugu | com.xugu.cloudjdbc.Driver | jdbc:xugu://localhost:5138 | / | https://repo1.maven.org/maven2/com/xugudb/xugu-jdbc/12.2.0/xugu-jdbc-12.2.0.jar | | InterSystems IRIS | com.intersystems.jdbc.IRISDriver | jdbc:IRIS://localhost:1972/%SYS | / | https://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/main/JDBC/JDK18/intersystems-jdbc-3.8.4.jar | +| opengauss | org.opengauss.Driver | jdbc:opengauss://localhost:5432/postgres | / | https://repo1.maven.org/maven2/org/opengauss/opengauss-jdbc/5.1.0-og/opengauss-jdbc-5.1.0-og.jar | ## Example diff --git a/docs/en/connector-v2/sink/Kafka.md b/docs/en/connector-v2/sink/Kafka.md index 9868e44f602..d201582e38b 100644 --- a/docs/en/connector-v2/sink/Kafka.md +++ b/docs/en/connector-v2/sink/Kafka.md @@ -111,7 +111,7 @@ env { source { FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Kingbase.md b/docs/en/connector-v2/sink/Kingbase.md index d985517f9ca..d4a5b8b56d5 100644 --- a/docs/en/connector-v2/sink/Kingbase.md +++ b/docs/en/connector-v2/sink/Kingbase.md @@ -105,7 +105,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Kudu.md b/docs/en/connector-v2/sink/Kudu.md index c67b79385fd..d95501ebd17 100644 --- a/docs/en/connector-v2/sink/Kudu.md +++ b/docs/en/connector-v2/sink/Kudu.md @@ -67,7 +67,7 @@ env { } source { FakeSource { - result_table_name = "kudu" + plugin_output = "kudu" schema = { fields { id = int @@ -114,7 +114,7 @@ env { sink { kudu{ - source_table_name = "kudu" + plugin_input = "kudu" kudu_masters = "kudu-master-cdc:7051" table_name = "kudu_sink_table" enable_kerberos = true diff --git a/docs/en/connector-v2/sink/Mivlus.md b/docs/en/connector-v2/sink/Milvus.md similarity index 79% rename from docs/en/connector-v2/sink/Mivlus.md rename to docs/en/connector-v2/sink/Milvus.md index 081f427a5df..6b6598fae30 100644 --- a/docs/en/connector-v2/sink/Mivlus.md +++ b/docs/en/connector-v2/sink/Milvus.md @@ -4,8 +4,11 @@ ## Description -Write data to Milvus or Zilliz Cloud - +This Milvus sink connector write data to Milvus or Zilliz Cloud, it has the following features: +- support read and write data by partition +- support write dynamic schema data from Metadata Column +- json data will be converted to json string and sink as json as well +- retry automatically to bypass ratelimit and grpc limit ## Key Features - [x] [batch](../../concept/connector-v2-features.md) @@ -34,7 +37,7 @@ Write data to Milvus or Zilliz Cloud ## Sink Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |----------------------|---------|----------|------------------------------|-----------------------------------------------------------| | url | String | Yes | - | The URL to connect to Milvus or Zilliz Cloud. | | token | String | Yes | - | User:password | @@ -44,6 +47,7 @@ Write data to Milvus or Zilliz Cloud | enable_upsert | boolean | No | false | Upsert data not insert. | | enable_dynamic_field | boolean | No | true | Enable create table with dynamic field. | | batch_size | int | No | 1000 | Write batch size. | +| partition_key | String | No | | Milvus partition key field | ## Task Example diff --git a/docs/en/connector-v2/sink/Mysql.md b/docs/en/connector-v2/sink/Mysql.md index 6151394b809..78c2e342fd9 100644 --- a/docs/en/connector-v2/sink/Mysql.md +++ b/docs/en/connector-v2/sink/Mysql.md @@ -112,7 +112,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/OceanBase.md b/docs/en/connector-v2/sink/OceanBase.md index 6ebe101b188..accbbd72cd4 100644 --- a/docs/en/connector-v2/sink/OceanBase.md +++ b/docs/en/connector-v2/sink/OceanBase.md @@ -111,7 +111,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Oracle.md b/docs/en/connector-v2/sink/Oracle.md index fefc31e4e18..d42e3b00fb4 100644 --- a/docs/en/connector-v2/sink/Oracle.md +++ b/docs/en/connector-v2/sink/Oracle.md @@ -110,7 +110,7 @@ env { source { FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Paimon.md b/docs/en/connector-v2/sink/Paimon.md index 8133b6e8360..68c0755cfd3 100644 --- a/docs/en/connector-v2/sink/Paimon.md +++ b/docs/en/connector-v2/sink/Paimon.md @@ -31,7 +31,7 @@ libfb303-xxx.jar ## Options -| name | type | required | default value | Description | +| name | type | required | default value | Description | |-----------------------------|--------|----------|------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | warehouse | String | Yes | - | Paimon warehouse path | | catalog_type | String | No | filesystem | Catalog type of Paimon, support filesystem and hive | @@ -43,7 +43,7 @@ libfb303-xxx.jar | data_save_mode | Enum | No | APPEND_DATA | The data save mode | | paimon.table.primary-keys | String | No | - | Default comma-separated list of columns (primary key) that identify a row in tables.(Notice: The partition field needs to be included in the primary key fields) | | paimon.table.partition-keys | String | No | - | Default comma-separated list of partition fields to use when creating tables. | -| paimon.table.write-props | Map | No | - | Properties passed through to paimon table initialization, [reference](https://paimon.apache.org/docs/master/maintenance/configurations/#coreoptions). | +| paimon.table.write-props | Map | No | - | Properties passed through to paimon table initialization, [reference](https://paimon.apache.org/docs/master/maintenance/configurations/#coreoptions). | | paimon.hadoop.conf | Map | No | - | Properties in hadoop conf | | paimon.hadoop.conf-path | String | No | - | The specified loading path for the 'core-site.xml', 'hdfs-site.xml', 'hive-site.xml' files | @@ -52,9 +52,21 @@ You must configure the `changelog-producer=input` option to enable the changelog The changelog producer mode of the paimon table has [four mode](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/) which is `none`、`input`、`lookup` and `full-compaction`. -Currently, we only support the `none` and `input` mode. The default is `none` which will not output the changelog file. The `input` mode will output the changelog file in paimon table. +All `changelog-producer` modes are currently supported. The default is `none`. + +* [`none`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#none) +* [`input`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#input) +* [`lookup`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#lookup) +* [`full-compaction`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#full-compaction) +> note: +> When you use a streaming mode to read paimon table,different mode will produce [different results](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/Paimon.md#changelog)。 + +## Filesystems +The Paimon connector supports writing data to multiple file systems. Currently, the supported file systems are hdfs and s3. +If you use the s3 filesystem. You can configure the `fs.s3a.access-key`、`fs.s3a.secret-key`、`fs.s3a.endpoint`、`fs.s3a.path.style.access`、`fs.s3a.aws.credentials.provider` properties in the `paimon.hadoop.conf` option. +Besides, the warehouse should start with `s3a://`. + -When you use a streaming mode to read paimon table, these two mode will produce [different results](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/Paimon.md#changelog). ## Examples @@ -89,6 +101,53 @@ sink { } ``` +### Single table with s3 filesystem + +```hocon +env { + execution.parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + c_map = "map" + c_array = "array" + c_string = string + c_boolean = boolean + c_tinyint = tinyint + c_smallint = smallint + c_int = int + c_bigint = bigint + c_float = float + c_double = double + c_bytes = bytes + c_date = date + c_decimal = "decimal(38, 18)" + c_timestamp = timestamp + } + } + } +} + +sink { + Paimon { + warehouse = "s3a://test/" + database = "seatunnel_namespace11" + table = "st_test" + paimon.hadoop.conf = { + fs.s3a.access-key=G52pnxg67819khOZ9ezX + fs.s3a.secret-key=SHJuAQqHsLrgZWikvMa3lJf5T0NfM5LMFliJh9HF + fs.s3a.endpoint="http://minio4:9000" + fs.s3a.path.style.access=true + fs.s3a.aws.credentials.provider=org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + } + } +} +``` + ### Single table(Specify hadoop HA config and kerberos config) ```hocon @@ -250,6 +309,38 @@ sink { } ``` +#### Write with the `changelog-producer` attribute + +```hocon +env { + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 +} + +source { + Mysql-CDC { + base-url = "jdbc:mysql://127.0.0.1:3306/seatunnel" + username = "root" + password = "******" + table-names = ["seatunnel.role"] + } +} + +sink { + Paimon { + catalog_name = "seatunnel_test" + warehouse = "file:///tmp/seatunnel/paimon/hadoop-sink/" + database = "seatunnel" + table = "role" + paimon.table.write-props = { + changelog-producer = full-compaction + changelog-tmp-path = /tmp/paimon/changelog + } + } +} +``` + ### Write to dynamic bucket table Single dynamic bucket table with write props of paimon,operates on the primary key table and bucket is -1. diff --git a/docs/en/connector-v2/sink/PostgreSql.md b/docs/en/connector-v2/sink/PostgreSql.md index cde299f6734..cf4bc2e3ada 100644 --- a/docs/en/connector-v2/sink/PostgreSql.md +++ b/docs/en/connector-v2/sink/PostgreSql.md @@ -154,7 +154,7 @@ env { source { FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Prometheus.md b/docs/en/connector-v2/sink/Prometheus.md new file mode 100644 index 00000000000..7852a87d5b5 --- /dev/null +++ b/docs/en/connector-v2/sink/Prometheus.md @@ -0,0 +1,103 @@ +# Prometheus + +> Prometheus sink connector + +## Support Those Engines + +> Spark
+> Flink
+> SeaTunnel Zeta
+ +## Key Features + +- [ ] [exactly-once](../../concept/connector-v2-features.md) +- [ ] [cdc](../../concept/connector-v2-features.md) +- [x] [support multiple table write](../../concept/connector-v2-features.md) + +## Description + +Used to launch web hooks using data. + +> For example, if the data from upstream is [`label: {"__name__": "test1"}, value: 1.2.3,time:2024-08-15T17:00:00`], the body content is the following: `{"label":{"__name__": "test1"}, "value":"1.23","time":"2024-08-15T17:00:00"}` + +**Tips: Prometheus sink only support `post json` webhook and the data from source will be treated as body content in web hook.And does not support passing past data** + +## Supported DataSource Info + +In order to use the Http connector, the following dependencies are required. +They can be downloaded via install-plugin.sh or from the Maven central repository. + +| Datasource | Supported Versions | Dependency | +|------------|--------------------|------------------------------------------------------------------------------------------------------------------| +| Http | universal | [Download](https://mvnrepository.com/artifact/org.apache.seatunnel/seatunnel-connectors-v2/connector-prometheus) | + +## Sink Options + +| Name | Type | Required | Default | Description | +|-----------------------------|--------|----------|---------|-------------------------------------------------------------------------------------------------------------| +| url | String | Yes | - | Http request url | +| headers | Map | No | - | Http headers | +| retry | Int | No | - | The max retry times if request http return to `IOException` | +| retry_backoff_multiplier_ms | Int | No | 100 | The retry-backoff times(millis) multiplier if request http failed | +| retry_backoff_max_ms | Int | No | 10000 | The maximum retry-backoff times(millis) if request http failed | +| connect_timeout_ms | Int | No | 12000 | Connection timeout setting, default 12s. | +| socket_timeout_ms | Int | No | 60000 | Socket timeout setting, default 60s. | +| key_timestamp | Int | NO | - | prometheus timestamp key . | +| key_label | String | yes | - | prometheus label key | +| key_value | Double | yes | - | prometheus value | +| batch_size | Int | false | 1024 | prometheus batch size write | +| flush_interval | Long | false | 300000L | prometheus flush commit interval | +| common-options | | No | - | Sink plugin common parameters, please refer to [Sink Common Options](../sink-common-options.md) for details | + +## Example + +simple: + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + c_map = "map" + c_double = double + c_timestamp = timestamp + } + } + plugin_output = "fake" + rows = [ + { + kind = INSERT + fields = [{"__name__": "test1"}, 1.23, "2024-08-15T17:00:00"] + }, + { + kind = INSERT + fields = [{"__name__": "test2"}, 1.23, "2024-08-15T17:00:00"] + } + ] + } +} + + +sink { + Prometheus { + url = "http://prometheus:9090/api/v1/write" + key_label = "c_map" + key_value = "c_double" + key_timestamp = "c_timestamp" + batch_size = 1 + } +} + +``` + +## Changelog + +### 2.3.8-beta 2024-08-22 + +- Add Http Sink Connector + diff --git a/docs/en/connector-v2/sink/Pulsar.md b/docs/en/connector-v2/sink/Pulsar.md index a0fc5bd092a..3e29eabbea5 100644 --- a/docs/en/connector-v2/sink/Pulsar.md +++ b/docs/en/connector-v2/sink/Pulsar.md @@ -145,7 +145,7 @@ env { source { FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -161,7 +161,7 @@ sink { topic = "example" client.service-url = "localhost:pulsar://localhost:6650" admin.service-url = "http://my-broker.example.com:8080" - result_table_name = "test" + plugin_output = "test" pulsar.config = { sendTimeoutMs = 30000 } diff --git a/docs/en/connector-v2/sink/Redis.md b/docs/en/connector-v2/sink/Redis.md index b5f444bb117..5b37720891b 100644 --- a/docs/en/connector-v2/sink/Redis.md +++ b/docs/en/connector-v2/sink/Redis.md @@ -12,21 +12,25 @@ Used to write data to Redis. ## Options -| name | type | required | default value | -|----------------|--------|-----------------------|---------------| -| host | string | yes | - | -| port | int | yes | - | -| key | string | yes | - | -| data_type | string | yes | - | -| batch_size | int | no | 10 | -| user | string | no | - | -| auth | string | no | - | -| db_num | int | no | 0 | -| mode | string | no | single | -| nodes | list | yes when mode=cluster | - | -| format | string | no | json | -| expire | long | no | -1 | -| common-options | | no | - | +| name | type | required | default value | +|--------------------|---------|-----------------------|---------------| +| host | string | yes | - | +| port | int | yes | - | +| key | string | yes | - | +| data_type | string | yes | - | +| batch_size | int | no | 10 | +| user | string | no | - | +| auth | string | no | - | +| db_num | int | no | 0 | +| mode | string | no | single | +| nodes | list | yes when mode=cluster | - | +| format | string | no | json | +| expire | long | no | -1 | +| support_custom_key | boolean | no | false | +| value_field | string | no | - | +| hash_key_field | string | no | - | +| hash_value_field | string | no | - | +| common-options | | no | - | ### host [string] @@ -50,12 +54,12 @@ Upstream data is the following: | 500 | internal error | false | If you assign field name to `code` and data_type to `key`, two data will be written to redis: -1. `200 -> {code: 200, message: true, data: get success}` -2. `500 -> {code: 500, message: false, data: internal error}` +1. `200 -> {code: 200, data: get success, success: true}` +2. `500 -> {code: 500, data: internal error, success: false}` If you assign field name to `value` and data_type to `key`, only one data will be written to redis because `value` is not existed in upstream data's fields: -1. `value -> {code: 500, message: false, data: internal error}` +1. `value -> {code: 500, data: internal error, success: false}` Please see the data_type section for specific writing rules. @@ -85,7 +89,7 @@ Redis data types, support `key` `hash` `list` `set` `zset` > Each data from upstream will be added to the configured zset key with a weight of 1. So the order of data in zset is based on the order of data consumption. > - ### batch_size [int] +### batch_size [int] ensure the batch write size in single-machine mode; no guarantees in cluster mode. @@ -135,6 +139,61 @@ Connector will generate data as the following and write it to redis: Set redis expiration time, the unit is second. The default value is -1, keys do not automatically expire by default. +### support_custom_key [boolean] + +if true, the key can be customized by the field value in the upstream data. + +Upstream data is the following: + +| code | data | success | +|------|----------------|---------| +| 200 | get success | true | +| 500 | internal error | false | + +You can customize the Redis key using '{' and '}', and the field name in '{}' will be parsed and replaced by the field value in the upstream data. For example, If you assign field name to `{code}` and data_type to `key`, two data will be written to redis: +1. `200 -> {code: 200, data: get success, success: true}` +2. `500 -> {code: 500, data: internal error, success: false}` + +Redis key can be composed of fixed and variable parts, connected by ':'. For example, If you assign field name to `code:{code}` and data_type to `key`, two data will be written to redis: +1. `code:200 -> {code: 200, data: get success, success: true}` +2. `code:500 -> {code: 500, data: internal error, success: false}` + +### value_field [string] + +The field of value you want to write to redis, `data_type` support `key` `list` `set` `zset`. + +When you assign field name to `value` and value_field is `data` and data_type to `key`, for example: + +Upstream data is the following: + +| code | data | success | +|------|-------------|---------| +| 200 | get success | true | + +The following data will be written to redis: +1. `value -> get success` + +### hash_key_field [string] + +The field of hash key you want to write to redis, `data_type` support `hash` + +### hash_value_field [string] + +The field of hash value you want to write to redis, `data_type` support `hash` + +When you assign field name to `value` and hash_key_field is `data` and hash_value_field is `success` and data_type to `hash`, for example: + +Upstream data is the following: + +| code | data | success | +|------|-------------|---------| +| 200 | get success | true | + +Connector will generate data as the following and write it to redis: + +The following data will be written to redis: +1. `value -> get success | true` + ### common options Sink plugin common parameters, please refer to [Sink Common Options](../sink-common-options.md) for details @@ -152,6 +211,43 @@ Redis { } ``` +custom key: + +```hocon +Redis { + host = localhost + port = 6379 + key = "name:{name}" + support_custom_key = true + data_type = key +} +``` + +custom value: + +```hocon +Redis { + host = localhost + port = 6379 + key = person + value_field = "name" + data_type = key +} +``` + +custom HashKey and HashValue: + +```hocon +Redis { + host = localhost + port = 6379 + key = person + hash_key_field = "name" + hash_value_field = "age" + data_type = hash +} +``` + ## Changelog ### 2.2.0-beta 2022-09-26 diff --git a/docs/en/connector-v2/sink/RocketMQ.md b/docs/en/connector-v2/sink/RocketMQ.md index f0672a3c7f6..f1a7fd86234 100644 --- a/docs/en/connector-v2/sink/RocketMQ.md +++ b/docs/en/connector-v2/sink/RocketMQ.md @@ -32,6 +32,7 @@ Write Rows to a Apache RocketMQ topic. | access.key | String | no | | When ACL_ENABLED is true, access key cannot be empty | | secret.key | String | no | | When ACL_ENABLED is true, secret key cannot be empty | | producer.group | String | no | SeaTunnel-producer-Group | SeaTunnel-producer-Group | +| tag | String | no | - | `RocketMQ` message tag. | | partition.key.fields | array | no | - | - | | format | String | no | json | Data format. The default format is json. Optional text format. The default field separator is ",".If you customize the delimiter, add the "field_delimiter" option. | | field.delimiter | String | no | , | Customize the field delimiter for data format. | @@ -114,7 +115,7 @@ source { Rocketmq { name.srv.addr = "localhost:9876" topics = "test_topic" - result_table_name = "rocketmq_table" + plugin_output = "rocketmq_table" schema = { fields { c_map = "map" @@ -160,7 +161,7 @@ source { Rocketmq { name.srv.addr = "localhost:9876" topics = "test_topic" - result_table_name = "rocketmq_table" + plugin_output = "rocketmq_table" start.mode = "CONSUME_FROM_FIRST_OFFSET" batch.size = "400" consumer.group = "test_topic_group" diff --git a/docs/en/connector-v2/sink/S3File.md b/docs/en/connector-v2/sink/S3File.md index 007c9395f7d..4251fe7f532 100644 --- a/docs/en/connector-v2/sink/S3File.md +++ b/docs/en/connector-v2/sink/S3File.md @@ -315,7 +315,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/SftpFile.md b/docs/en/connector-v2/sink/SftpFile.md index a383cc72da5..4509baa7cff 100644 --- a/docs/en/connector-v2/sink/SftpFile.md +++ b/docs/en/connector-v2/sink/SftpFile.md @@ -63,6 +63,8 @@ By default, we use 2PC commit to ensure `exactly-once` | parquet_avro_write_timestamp_as_int96 | boolean | no | false | Only used when file_format is parquet. | | parquet_avro_write_fixed_as_int96 | array | no | - | Only used when file_format is parquet. | | encoding | string | no | "UTF-8" | Only used when file_format_type is json,text,csv,xml. | +| schema_save_mode | string | no | CREATE_SCHEMA_WHEN_NOT_EXIST | Existing dir processing method | +| data_save_mode | string | no | APPEND_DATA | Existing data processing method | ### host [string] @@ -220,6 +222,19 @@ Support writing Parquet INT96 from a 12-byte field, only valid for parquet files Only used when file_format_type is json,text,csv,xml. The encoding of the file to write. This param will be parsed by `Charset.forName(encoding)`. +### schema_save_mode [string] +Existing dir processing method. +- RECREATE_SCHEMA: will create when the dir does not exist, delete and recreate when the dir is exist +- CREATE_SCHEMA_WHEN_NOT_EXIST: will create when the dir does not exist, skipped when the dir is exist +- ERROR_WHEN_SCHEMA_NOT_EXIST: error will be reported when the dir does not exist +- IGNORE :Ignore the treatment of the table + +### data_save_mode [string] +Existing data processing method. +- DROP_DATA: preserve dir and delete data files +- APPEND_DATA: preserve dir, preserve data files +- ERROR_WHEN_DATA_EXISTS: when there is data files, an error is reported + ## Example For text file format with `have_partition` and `custom_filename` and `sink_columns` @@ -247,6 +262,35 @@ SftpFile { is_enable_transaction = true } +``` + +When our source end is multiple tables, and wants different expressions to different directory, we can configure this way + +```hocon +SftpFile { + host = "xxx.xxx.xxx.xxx" + port = 22 + user = "username" + password = "password" + path = "/data/sftp/seatunnel/job1/${table_name}" + tmp_path = "/data/sftp/seatunnel/tmp" + file_format_type = "text" + field_delimiter = "\t" + row_delimiter = "\n" + have_partition = true + partition_by = ["age"] + partition_dir_expression = "${k0}=${v0}" + is_partition_field_write_in_file = true + custom_filename = true + file_name_expression = "${transactionId}_${now}" + filename_time_format = "yyyy.MM.dd" + sink_columns = ["name","age"] + is_enable_transaction = true + schema_save_mode=RECREATE_SCHEMA + data_save_mode=DROP_DATA +} + + ``` ## Changelog diff --git a/docs/en/connector-v2/sink/Sls.md b/docs/en/connector-v2/sink/Sls.md new file mode 100644 index 00000000000..487786548d0 --- /dev/null +++ b/docs/en/connector-v2/sink/Sls.md @@ -0,0 +1,84 @@ +# Sls + +> Sls sink connector + +## Support Those Engines + +> Spark
+> Flink
+> Seatunnel Zeta
+ +## Key Features + +- [ ] [exactly-once](../../concept/connector-v2-features.md) +- [ ] [cdc](../../concept/connector-v2-features.md) + +## Description + +Sink connector for Aliyun Sls. + +## Supported DataSource Info + +In order to use the Sls connector, the following dependencies are required. +They can be downloaded via install-plugin.sh or from the Maven central repository. + +| Datasource | Supported Versions | Maven | +|------------|--------------------|-----------------------------------------------------------------------------------| +| Sls | Universal | [Download](https://mvnrepository.com/artifact/org.apache.seatunnel/connector-sls) | + +## Source Options + +| Name | Type | Required | Default | Description | +|-------------------------------------|---------|----------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| project | String | Yes | - | [Aliyun Sls Project](https://help.aliyun.com/zh/sls/user-guide/manage-a-project?spm=a2c4g.11186623.0.0.6f9755ebyfaYSl) | +| logstore | String | Yes | - | [Aliyun Sls Logstore](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore?spm=a2c4g.11186623.0.0.13137c08nfuiBC) | +| endpoint | String | Yes | - | [Aliyun Access Endpoint](https://help.aliyun.com/zh/sls/developer-reference/api-sls-2020-12-30-endpoint?spm=a2c4g.11186623.0.0.548945a8UyJULa) | +| access_key_id | String | Yes | - | [Aliyun AccessKey ID](https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair?spm=a2c4g.11186623.0.0.4a6e4e554CKhSc#task-2245479) | +| access_key_secret | String | Yes | - | [Aliyun AccessKey Secret](https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair?spm=a2c4g.11186623.0.0.4a6e4e554CKhSc#task-2245479) | +| source | String | No | SeaTunnel-Source | Data Source marking in sls | +| topic | String | No | SeaTunnel-Topic | Data topic marking in sls | + +## Task Example + +### Simple + +> This example write data to the sls's logstore1.And if you have not yet installed and deployed SeaTunnel, you need to follow the instructions in Install SeaTunnel to install and deploy SeaTunnel. And if you have not yet installed and deployed SeaTunnel, you need to follow the instructions in [Install SeaTunnel](../../start-v2/locally/deployment.md) to install and deploy SeaTunnel. And then follow the instructions in [Quick Start With SeaTunnel Engine](../../start-v2/locally/quick-start-seatunnel-engine.md) to run this job. + +[Create RAM user and authorization](https://help.aliyun.com/zh/sls/create-a-ram-user-and-authorize-the-ram-user-to-access-log-service?spm=a2c4g.11186623.0.i4),Please ensure thr ram user have sufficient rights to perform, reference [RAM Custom Authorization Example](https://help.aliyun.com/zh/sls/use-custom-policies-to-grant-permissions-to-a-ram-user?spm=a2c4g.11186623.0.0.4a6e4e554CKhSc#reference-s3z-m1l-z2b) + +```hocon +# Defining the runtime environment +env { + parallelism = 2 + job.mode = "STREAMING" + checkpoint.interval = 30000 +} +source { + FakeSource { + row.num = 10 + map.size = 10 + array.size = 10 + bytes.length = 10 + string.length = 10 + schema = { + fields = { + id = "int" + name = "string" + description = "string" + weight = "string" + } + } + } +} + +sink { + Sls { + endpoint = "cn-hangzhou-intranet.log.aliyuncs.com" + project = "project1" + logstore = "logstore1" + access_key_id = "xxxxxxxxxxxxxxxxxxxxxxxx" + access_key_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } +} +``` + diff --git a/docs/en/connector-v2/sink/Snowflake.md b/docs/en/connector-v2/sink/Snowflake.md index f40cb2b675d..dd84bcc2cef 100644 --- a/docs/en/connector-v2/sink/Snowflake.md +++ b/docs/en/connector-v2/sink/Snowflake.md @@ -89,7 +89,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/sink/Socket.md b/docs/en/connector-v2/sink/Socket.md index 581a1a5caab..cdd5b7b034d 100644 --- a/docs/en/connector-v2/sink/Socket.md +++ b/docs/en/connector-v2/sink/Socket.md @@ -39,7 +39,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" schema = { fields { name = "string" diff --git a/docs/en/connector-v2/sink/SqlServer.md b/docs/en/connector-v2/sink/SqlServer.md index a9f6bdd8955..3a03d3a2df8 100644 --- a/docs/en/connector-v2/sink/SqlServer.md +++ b/docs/en/connector-v2/sink/SqlServer.md @@ -147,7 +147,7 @@ sink { ``` Jdbc { - source_table_name = "customers" + plugin_input = "customers" driver = com.microsoft.sqlserver.jdbc.SQLServerDriver url = "jdbc:sqlserver://localhost:1433;databaseName=column_type_test" user = SA diff --git a/docs/en/connector-v2/sink/Typesense.md b/docs/en/connector-v2/sink/Typesense.md index 8700d68dc77..f3c78af1617 100644 --- a/docs/en/connector-v2/sink/Typesense.md +++ b/docs/en/connector-v2/sink/Typesense.md @@ -77,7 +77,7 @@ Simple example: ```bash sink { Typesense { - source_table_name = "typesense_test_table" + plugin_input = "typesense_test_table" hosts = ["localhost:8108"] collection = "typesense_to_typesense_sink_with_query" max_retry_count = 3 diff --git a/docs/en/connector-v2/sink/Vertica.md b/docs/en/connector-v2/sink/Vertica.md index ef303b59453..04aa77f0e6f 100644 --- a/docs/en/connector-v2/sink/Vertica.md +++ b/docs/en/connector-v2/sink/Vertica.md @@ -109,7 +109,7 @@ source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/en/connector-v2/source-common-options.md b/docs/en/connector-v2/source-common-options.md index a66eb34a44c..1c40f287796 100644 --- a/docs/en/connector-v2/source-common-options.md +++ b/docs/en/connector-v2/source-common-options.md @@ -6,14 +6,20 @@ sidebar_position: 3 > Common parameters of source connectors -| Name | Type | Required | Default | Description | -|-------------------|--------|----------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| result_table_name | String | No | - | When `result_table_name` is not specified, the data processed by this plugin will not be registered as a data set `(dataStream/dataset)` that can be directly accessed by other plugins, or called a temporary table `(table)`
When `result_table_name` is specified, the data processed by this plugin will be registered as a data set `(dataStream/dataset)` that can be directly accessed by other plugins, or called a temporary table `(table)` . The data set `(dataStream/dataset)` registered here can be directly accessed by other plugins by specifying `source_table_name` . | -| parallelism | Int | No | - | When `parallelism` is not specified, the `parallelism` in env is used by default.
When parallelism is specified, it will override the parallelism in env. | +:::warn + +The old configuration name `result_table_name` is deprecated, please migrate to the new name `plugin_output` as soon as possible. + +::: + +| Name | Type | Required | Default | Description | +|---------------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| plugin_output | String | No | - | When `plugin_output` is not specified, the data processed by this plugin will not be registered as a data set `(dataStream/dataset)` that can be directly accessed by other plugins, or called a temporary table `(table)`
When `plugin_output` is specified, the data processed by this plugin will be registered as a data set `(dataStream/dataset)` that can be directly accessed by other plugins, or called a temporary table `(table)` . The data set `(dataStream/dataset)` registered here can be directly accessed by other plugins by specifying `plugin_input` . | +| parallelism | Int | No | - | When `parallelism` is not specified, the `parallelism` in env is used by default.
When parallelism is specified, it will override the parallelism in env. | # Important note -When the job configuration `result_table_name` you must set the `source_table_name` parameter +When the job configuration `plugin_output` you must set the `plugin_input` parameter ## Task Example @@ -24,7 +30,7 @@ When the job configuration `result_table_name` you must set the `source_table_na ```bash source { FakeSourceStream { - result_table_name = "fake_table" + plugin_output = "fake_table" } } ``` @@ -40,7 +46,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -64,9 +70,9 @@ source { transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" - # the query table name must same as field 'source_table_name' + plugin_input = "fake" + plugin_output = "fake1" + # the query table name must same as field 'plugin_input' query = "select id, regexp_replace(name, '.+', 'b') as name, age+1 as age, pi() as pi, c_timestamp, c_date, c_map, c_array, c_decimal, c_row from fake" } # The SQL transform support base function and criteria operation @@ -75,10 +81,10 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } Console { - source_table_name = "fake" + plugin_input = "fake" } } ``` diff --git a/docs/en/connector-v2/source/Cassandra.md b/docs/en/connector-v2/source/Cassandra.md index d4d4e97088a..32966de1e36 100644 --- a/docs/en/connector-v2/source/Cassandra.md +++ b/docs/en/connector-v2/source/Cassandra.md @@ -67,7 +67,7 @@ source { datacenter = "datacenter1" keyspace = "test" cql = "select * from source_table" - result_table_name = "source_table" + plugin_output = "source_table" } } ``` diff --git a/docs/en/connector-v2/source/Clickhouse.md b/docs/en/connector-v2/source/Clickhouse.md index 47907bd3025..e3048894ff7 100644 --- a/docs/en/connector-v2/source/Clickhouse.md +++ b/docs/en/connector-v2/source/Clickhouse.md @@ -80,7 +80,7 @@ source { username = "xxxxx" password = "xxxxx" server_time_zone = "UTC" - result_table_name = "test" + plugin_output = "test" clickhouse.config = { "socket_timeout": "300000" } diff --git a/docs/en/connector-v2/source/CosFile.md b/docs/en/connector-v2/source/CosFile.md index 702439c3062..1cbda880139 100644 --- a/docs/en/connector-v2/source/CosFile.md +++ b/docs/en/connector-v2/source/CosFile.md @@ -45,7 +45,7 @@ To use this connector you need put hadoop-cos-{hadoop.version}-{version}.jar and ## Options -| name | type | required | default value | +| name | type | required | default value | |---------------------------|---------|----------|---------------------| | path | string | yes | - | | file_format_type | string | yes | - | @@ -64,7 +64,7 @@ To use this connector you need put hadoop-cos-{hadoop.version}-{version}.jar and | sheet_name | string | no | - | | xml_row_tag | string | no | - | | xml_use_attr_format | boolean | no | - | -| file_filter_pattern | string | no | - | +| file_filter_pattern | string | no | | | compress_codec | string | no | none | | archive_compress_codec | string | no | none | | encoding | string | no | UTF-8 | @@ -275,6 +275,55 @@ Specifies Whether to process data using the tag attribute format. Filter pattern, which used for filtering files. +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### compress_codec [string] The compress codec of files and the details that supported as the following shown: @@ -294,6 +343,7 @@ The compress codec of archive files and the details that supported as the follow | ZIP | txt,json,excel,xml | .zip | | TAR | txt,json,excel,xml | .tar | | TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | | NONE | all | .* | ### encoding [string] @@ -372,6 +422,33 @@ sink { ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + CosFile { + bucket = "cosn://seatunnel-test-1259587829" + secret_id = "xxxxxxxxxxxxxxxxxxx" + secret_key = "xxxxxxxxxxxxxxxxxxx" + region = "ap-chengdu" + path = "/seatunnel/read/binary/" + file_format_type = "binary" + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` + ## Changelog ### next version diff --git a/docs/en/connector-v2/source/Doris.md b/docs/en/connector-v2/source/Doris.md index c67444b58c8..373b84f8fdd 100644 --- a/docs/en/connector-v2/source/Doris.md +++ b/docs/en/connector-v2/source/Doris.md @@ -13,15 +13,14 @@ - [x] [batch](../../concept/connector-v2-features.md) - [ ] [stream](../../concept/connector-v2-features.md) - [ ] [exactly-once](../../concept/connector-v2-features.md) -- [x] [schema projection](../../concept/connector-v2-features.md) +- [x] [column projection](../../concept/connector-v2-features.md) - [x] [parallelism](../../concept/connector-v2-features.md) - [x] [support user-defined split](../../concept/connector-v2-features.md) +- [x] [support multiple table read](../../concept/connector-v2-features.md) ## Description -Used to read data from Doris. -Doris Source will send a SQL to FE, FE will parse it into an execution plan, send it to BE, and BE will -directly return the data +Used to read data from Apache Doris. ## Supported DataSource Info @@ -29,11 +28,6 @@ directly return the data |------------|--------------------------------------|--------|-----|-------| | Doris | Only Doris2.0 or later is supported. | - | - | - | -## Database Dependency - -> Please download the support list corresponding to 'Maven' and copy it to the '$SEATNUNNEL_HOME/plugins/jdbc/lib/' -> working directory
- ## Data Type Mapping | Doris Data type | SeaTunnel Data type | @@ -54,29 +48,40 @@ directly return the data ## Source Options +Base configuration: + | Name | Type | Required | Default | Description | |----------------------------------|--------|----------|------------|-----------------------------------------------------------------------------------------------------| | fenodes | string | yes | - | FE address, the format is `"fe_host:fe_http_port"` | | username | string | yes | - | User username | | password | string | yes | - | User password | +| doris.request.retries | int | no | 3 | Number of retries to send requests to Doris FE. | +| doris.request.read.timeout.ms | int | no | 30000 | | +| doris.request.connect.timeout.ms | int | no | 30000 | | +| query-port | string | no | 9030 | Doris QueryPort | +| doris.request.query.timeout.s | int | no | 3600 | Timeout period of Doris scan data, expressed in seconds. | +| table_list | string | 否 | - | table list | + +Table list configuration: + +| Name | Type | Required | Default | Description | +|----------------------------------|--------|----------|------------|-----------------------------------------------------------------------------------------------------| | database | string | yes | - | The name of Doris database | | table | string | yes | - | The name of Doris table | | doris.read.field | string | no | - | Use the 'doris.read.field' parameter to select the doris table columns to read | -| query-port | string | no | 9030 | Doris QueryPort | | doris.filter.query | string | no | - | Data filtering in doris. the format is "field = value",example : doris.filter.query = "F_ID > 2" | | doris.batch.size | int | no | 1024 | The maximum value that can be obtained by reading Doris BE once. | -| doris.request.query.timeout.s | int | no | 3600 | Timeout period of Doris scan data, expressed in seconds. | | doris.exec.mem.limit | long | no | 2147483648 | Maximum memory that can be used by a single be scan request. The default memory is 2G (2147483648). | -| doris.request.retries | int | no | 3 | Number of retries to send requests to Doris FE. | -| doris.request.read.timeout.ms | int | no | 30000 | | -| doris.request.connect.timeout.ms | int | no | 30000 | | + +Note: When this configuration corresponds to a single table, you can flatten the configuration items in table_list to the outer layer. ### Tips > It is not recommended to modify advanced parameters at will -## Task Example +## Example +### single table > This is an example of reading a Doris table and writing to Console. ``` @@ -159,4 +164,49 @@ sink { Console {} } ``` +### Multiple table +``` +env{ + parallelism = 1 + job.mode = "BATCH" +} +source{ + Doris { + fenodes = "xxxx:8030" + username = root + password = "" + table_list = [ + { + database = "st_source_0" + table = "doris_table_0" + doris.read.field = "F_ID,F_INT,F_BIGINT,F_TINYINT" + doris.filter.query = "F_ID >= 50" + }, + { + database = "st_source_1" + table = "doris_table_1" + } + ] + } +} + +transform {} + +sink{ + Doris { + fenodes = "xxxx:8030" + schema_save_mode = "RECREATE_SCHEMA" + username = root + password = "" + database = "st_sink" + table = "${table_name}" + sink.enable-2pc = "true" + sink.label-prefix = "test_json" + doris.config = { + format="json" + read_json_by_line="true" + } + } +} +``` diff --git a/docs/en/connector-v2/source/FakeSource.md b/docs/en/connector-v2/source/FakeSource.md index 6f6b259736b..48333e244c0 100644 --- a/docs/en/connector-v2/source/FakeSource.md +++ b/docs/en/connector-v2/source/FakeSource.md @@ -142,7 +142,7 @@ source { c_timestamp = timestamp } } - result_table_name = "fake" + plugin_output = "fake" } } ``` diff --git a/docs/en/connector-v2/source/FtpFile.md b/docs/en/connector-v2/source/FtpFile.md index 656f7a00422..f65255bfd77 100644 --- a/docs/en/connector-v2/source/FtpFile.md +++ b/docs/en/connector-v2/source/FtpFile.md @@ -38,7 +38,7 @@ If you use SeaTunnel Engine, It automatically integrated the hadoop jar when you ## Options -| name | type | required | default value | +| name | type | required | default value | |---------------------------|---------|----------|---------------------| | host | string | yes | - | | port | int | yes | - | @@ -62,6 +62,7 @@ If you use SeaTunnel Engine, It automatically integrated the hadoop jar when you | compress_codec | string | no | none | | archive_compress_codec | string | no | none | | encoding | string | no | UTF-8 | +| null_format | string | no | - | | common-options | | no | - | ### host [string] @@ -84,6 +85,59 @@ The target ftp password is required The source file path. +### file_filter_pattern [string] + +Filter pattern, which used for filtering files. + +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### file_format_type [string] File type, supported as the following file types: @@ -275,6 +329,7 @@ The compress codec of archive files and the details that supported as the follow | ZIP | txt,json,excel,xml | .zip | | TAR | txt,json,excel,xml | .tar | | TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | | NONE | all | .* | ### encoding [string] @@ -282,6 +337,13 @@ The compress codec of archive files and the details that supported as the follow Only used when file_format_type is json,text,csv,xml. The encoding of the file to read. This param will be parsed by `Charset.forName(encoding)`. +### null_format [string] + +Only used when file_format_type is text. +null_format to define which strings can be represented as null. + +e.g: `\N` + ### common options Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details. @@ -306,6 +368,67 @@ Source plugin common parameters, please refer to [Source Common Options](../sour ``` +### Multiple Table + +```hocon + +FtpFile { + tables_configs = [ + { + schema { + table = "student" + } + path = "/tmp/seatunnel/sink/text" + host = "192.168.31.48" + port = 21 + user = tyrantlucifer + password = tianchao + file_format_type = "parquet" + }, + { + schema { + table = "teacher" + } + path = "/tmp/seatunnel/sink/text" + host = "192.168.31.48" + port = 21 + user = tyrantlucifer + password = tianchao + file_format_type = "parquet" + } + ] +} + +``` + +```hocon + +FtpFile { + tables_configs = [ + { + schema { + fields { + name = string + age = int + } + } + path = "/apps/hive/demo/student" + file_format_type = "json" + }, + { + schema { + fields { + name = string + age = int + } + } + path = "/apps/hive/demo/teacher" + file_format_type = "json" + } +} + +``` + ### Transfer Binary File ```hocon @@ -339,6 +462,33 @@ sink { ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FtpFile { + host = "192.168.31.48" + port = 21 + user = tyrantlucifer + password = tianchao + path = "/seatunnel/read/binary/" + file_format_type = "binary" + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` + ## Changelog ### 2.2.0-beta 2022-09-26 diff --git a/docs/en/connector-v2/source/HdfsFile.md b/docs/en/connector-v2/source/HdfsFile.md index 7413c0428b8..caaf9972a06 100644 --- a/docs/en/connector-v2/source/HdfsFile.md +++ b/docs/en/connector-v2/source/HdfsFile.md @@ -41,7 +41,7 @@ Read data from hdfs file system. ## Source Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |---------------------------|---------|----------|---------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | path | string | yes | - | The source file path. | | file_format_type | string | yes | - | We supported as the following file types:`text` `csv` `parquet` `orc` `json` `excel` `xml` `binary`.Please note that, The final file name will end with the file_format's suffix, the suffix of the text file is `txt`. | @@ -62,15 +62,70 @@ Read data from hdfs file system. | sheet_name | string | no | - | Reader the sheet of the workbook,Only used when file_format is excel. | | xml_row_tag | string | no | - | Specifies the tag name of the data rows within the XML file, only used when file_format is xml. | | xml_use_attr_format | boolean | no | - | Specifies whether to process data using the tag attribute format, only used when file_format is xml. | +| file_filter_pattern | string | no | | Filter pattern, which used for filtering files. | | compress_codec | string | no | none | The compress codec of files | | archive_compress_codec | string | no | none | | encoding | string | no | UTF-8 | | +| null_format | string | no | - | Only used when file_format_type is text. null_format to define which strings can be represented as null. e.g: `\N` | | common-options | | no | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details. | ### delimiter/field_delimiter [string] **delimiter** parameter will deprecate after version 2.3.5, please use **field_delimiter** instead. +### file_filter_pattern [string] + +Filter pattern, which used for filtering files. + +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### compress_codec [string] The compress codec of files and the details that supported as the following shown: @@ -90,6 +145,7 @@ The compress codec of archive files and the details that supported as the follow | ZIP | txt,json,excel,xml | .zip | | TAR | txt,json,excel,xml | .tar | | TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | | NONE | all | .* | ### encoding [string] @@ -146,3 +202,26 @@ sink { } ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + HdfsFile { + path = "/apps/hive/demo/student" + file_format_type = "json" + fs.defaultFS = "hdfs://namenode001" + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` diff --git a/docs/en/connector-v2/source/Hive.md b/docs/en/connector-v2/source/Hive.md index 5669906c3b9..d87739f1034 100644 --- a/docs/en/connector-v2/source/Hive.md +++ b/docs/en/connector-v2/source/Hive.md @@ -8,7 +8,7 @@ Read data from Hive. :::tip -In order to use this connector, You must ensure your spark/flink cluster already integrated hive. The tested hive version is 2.3.9. +In order to use this connector, You must ensure your spark/flink cluster already integrated hive. The tested hive version is 2.3.9 and 3.1.3 . If you use SeaTunnel Engine, You need put seatunnel-hadoop3-3.1.4-uber.jar and hive-exec-3.1.3.jar and libfb303-0.9.3.jar in $SEATUNNEL_HOME/lib/ dir. ::: @@ -120,6 +120,24 @@ Source plugin common parameters, please refer to [Source Common Options](../sour ``` ### Example 2: Multiple tables +> Note: Hive is a structured data source and should be use 'table_list', and 'tables_configs' will be removed in the future. + +```bash + + Hive { + table_list = [ + { + table_name = "default.seatunnel_orc_1" + metastore_uri = "thrift://namenode001:9083" + }, + { + table_name = "default.seatunnel_orc_2" + metastore_uri = "thrift://namenode001:9083" + } + ] + } + +``` ```bash @@ -138,6 +156,95 @@ Source plugin common parameters, please refer to [Source Common Options](../sour ``` +### Example3 : Kerberos + +```bash +source { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + result_table_name = hive_source + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} +``` + +Description: + +- `hive_site_path`: The path to the `hive-site.xml` file. +- `kerberos_principal`: The principal for Kerberos authentication. +- `kerberos_keytab_path`: The keytab file path for Kerberos authentication. +- `krb5_path`: The path to the `krb5.conf` file used for Kerberos authentication. + +Run the case: + +```bash +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + result_table_name = hive_source + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} + +sink { + Assert { + source_table_name = hive_source + rules { + row_rules = [ + { + rule_type = MAX_ROW + rule_value = 3 + } + ], + field_rules = [ + { + field_name = pk_id + field_type = bigint + field_value = [ + { + rule_type = NOT_NULL + } + ] + }, + { + field_name = name + field_type = string + field_value = [ + { + rule_type = NOT_NULL + } + ] + }, + { + field_name = score + field_type = int + field_value = [ + { + rule_type = NOT_NULL + } + ] + } + ] + } + } +} +``` + ## Hive on s3 ### Step 1 @@ -265,15 +372,3 @@ sink { } } ``` - -## Changelog - -### 2.2.0-beta 2022-09-26 - -- Add Hive Source Connector - -### Next version - -- [Improve] Support kerberos authentication ([3840](https://github.com/apache/seatunnel/pull/3840)) -- Support user-defined partitions ([3842](https://github.com/apache/seatunnel/pull/3842)) - diff --git a/docs/en/connector-v2/source/HiveJdbc.md b/docs/en/connector-v2/source/HiveJdbc.md index 19619d924c1..23227aa306f 100644 --- a/docs/en/connector-v2/source/HiveJdbc.md +++ b/docs/en/connector-v2/source/HiveJdbc.md @@ -72,7 +72,7 @@ Read external data source data through JDBC. | partition_num | Int | No | job parallelism | The number of partition count, only support positive integer. default value is job parallelism | | fetch_size | Int | No | 0 | For queries that return a large number of objects,you can configure
the row fetch size used in the query toimprove performance by
reducing the number database hits required to satisfy the selection criteria.
Zero means use jdbc default value. | | common-options | | No | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details | -| useKerberos | Boolean | No | no | Whether to enable Kerberos, default is false | +| use_kerberos | Boolean | No | no | Whether to enable Kerberos, default is false | | kerberos_principal | String | No | - | When use kerberos, we should set kerberos principal such as 'test_user@xxx'. | | kerberos_keytab_path | String | No | - | When use kerberos, we should set kerberos principal file path such as '/home/test/test_user.keytab' . | | krb5_path | String | No | /etc/krb5.conf | When use kerberos, we should set krb5 path file path such as '/seatunnel/krb5.conf' or use the default path '/etc/krb5.conf '. | diff --git a/docs/en/connector-v2/source/Http.md b/docs/en/connector-v2/source/Http.md index 9c60b4c9aa4..511ba04132d 100644 --- a/docs/en/connector-v2/source/Http.md +++ b/docs/en/connector-v2/source/Http.md @@ -78,7 +78,7 @@ env { source { Http { - result_table_name = "http" + plugin_output = "http" url = "http://mockserver:1080/example/http" method = "GET" format = "json" diff --git a/docs/en/connector-v2/source/Iceberg.md b/docs/en/connector-v2/source/Iceberg.md index 4203c85bb87..8bb21eb7b63 100644 --- a/docs/en/connector-v2/source/Iceberg.md +++ b/docs/en/connector-v2/source/Iceberg.md @@ -127,7 +127,7 @@ source { } namespace = "database1" table = "source" - result_table_name = "iceberg" + plugin_output = "iceberg" } } @@ -136,7 +136,7 @@ transform { sink { Console { - source_table_name = "iceberg" + plugin_input = "iceberg" } } ``` @@ -160,7 +160,7 @@ source { } namespace = "your_iceberg_database" table = "your_iceberg_table" - result_table_name = "iceberg_test" + plugin_output = "iceberg_test" } } ``` diff --git a/docs/en/connector-v2/source/Jdbc.md b/docs/en/connector-v2/source/Jdbc.md index 27b3d875580..2b5897cbaea 100644 --- a/docs/en/connector-v2/source/Jdbc.md +++ b/docs/en/connector-v2/source/Jdbc.md @@ -113,7 +113,7 @@ The JDBC Source connector supports parallel reading of data from tables. SeaTunn there are some reference value for params above. -| datasource | driver | url | maven | +| datasource | driver | url | maven | |-------------------|-----------------------------------------------------|------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| | mysql | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | https://mvnrepository.com/artifact/mysql/mysql-connector-java | | postgresql | org.postgresql.Driver | jdbc:postgresql://localhost:5432/postgres | https://mvnrepository.com/artifact/org.postgresql/postgresql | @@ -122,7 +122,7 @@ there are some reference value for params above. | sqlserver | com.microsoft.sqlserver.jdbc.SQLServerDriver | jdbc:sqlserver://localhost:1433 | https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc | | oracle | oracle.jdbc.OracleDriver | jdbc:oracle:thin:@localhost:1521/xepdb1 | https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 | | sqlite | org.sqlite.JDBC | jdbc:sqlite:test.db | https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc | -| gbase8a | com.gbase.jdbc.Driver | jdbc:gbase://e2e_gbase8aDb:5258/test | https://cdn.gbase.cn/products/30/p5CiVwXBKQYIUGN8ecHvk/gbase-connector-java-9.5.0.7-build1-bin.jar | +| gbase8a | com.gbase.jdbc.Driver | jdbc:gbase://e2e_gbase8aDb:5258/test | https://cdn.gbase.cn/products/30/p5CiVwXBKQYIUGN8ecHvk/gbase-connector-java-9.5.0.7-build1-bin.jar | | starrocks | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | https://mvnrepository.com/artifact/mysql/mysql-connector-java | | db2 | com.ibm.db2.jcc.DB2Driver | jdbc:db2://localhost:50000/testdb | https://mvnrepository.com/artifact/com.ibm.db2.jcc/db2jcc/db2jcc4 | | tablestore | com.alicloud.openservices.tablestore.jdbc.OTSDriver | "jdbc:ots:http s://myinstance.cn-hangzhou.ots.aliyuncs.com/myinstance" | https://mvnrepository.com/artifact/com.aliyun.openservices/tablestore-jdbc | @@ -133,10 +133,11 @@ there are some reference value for params above. | Redshift | com.amazon.redshift.jdbc42.Driver | jdbc:redshift://localhost:5439/testdb?defaultRowFetchSize=1000 | https://mvnrepository.com/artifact/com.amazon.redshift/redshift-jdbc42 | | Vertica | com.vertica.jdbc.Driver | jdbc:vertica://localhost:5433 | https://repo1.maven.org/maven2/com/vertica/jdbc/vertica-jdbc/12.0.3-0/vertica-jdbc-12.0.3-0.jar | | Kingbase | com.kingbase8.Driver | jdbc:kingbase8://localhost:54321/db_test | https://repo1.maven.org/maven2/cn/com/kingbase/kingbase8/8.6.0/kingbase8-8.6.0.jar | -| OceanBase | com.oceanbase.jdbc.Driver | jdbc:oceanbase://localhost:2881 | https://repo1.maven.org/maven2/com/oceanbase/oceanbase-client/2.4.11/oceanbase-client-2.4.11.jar | +| OceanBase | com.oceanbase.jdbc.Driver | jdbc:oceanbase://localhost:2881 | https://repo1.maven.org/maven2/com/oceanbase/oceanbase-client/2.4.12/oceanbase-client-2.4.12.jar | | Hive | org.apache.hive.jdbc.HiveDriver | jdbc:hive2://localhost:10000 | https://repo1.maven.org/maven2/org/apache/hive/hive-jdbc/3.1.3/hive-jdbc-3.1.3-standalone.jar | | xugu | com.xugu.cloudjdbc.Driver | jdbc:xugu://localhost:5138 | https://repo1.maven.org/maven2/com/xugudb/xugu-jdbc/12.2.0/xugu-jdbc-12.2.0.jar | | InterSystems IRIS | com.intersystems.jdbc.IRISDriver | jdbc:IRIS://localhost:1972/%SYS | https://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/main/JDBC/JDK18/intersystems-jdbc-3.8.4.jar | +| opengauss | org.opengauss.Driver | jdbc:opengauss://localhost:5432/postgres | https://repo1.maven.org/maven2/org/opengauss/opengauss-jdbc/5.1.0-og/opengauss-jdbc-5.1.0-og.jar | ## Example diff --git a/docs/en/connector-v2/source/kafka.md b/docs/en/connector-v2/source/Kafka.md similarity index 94% rename from docs/en/connector-v2/source/kafka.md rename to docs/en/connector-v2/source/Kafka.md index 90c183c2c13..dfc23a7572f 100644 --- a/docs/en/connector-v2/source/kafka.md +++ b/docs/en/connector-v2/source/Kafka.md @@ -59,6 +59,7 @@ They can be downloaded via install-plugin.sh or from the Maven central repositor ### Simple > This example reads the data of kafka's topic_1, topic_2, topic_3 and prints it to the client.And if you have not yet installed and deployed SeaTunnel, you need to follow the instructions in Install SeaTunnel to install and deploy SeaTunnel. And if you have not yet installed and deployed SeaTunnel, you need to follow the instructions in [Install SeaTunnel](../../start-v2/locally/deployment.md) to install and deploy SeaTunnel. And then follow the instructions in [Quick Start With SeaTunnel Engine](../../start-v2/locally/quick-start-seatunnel-engine.md) to run this job. +> In batch mode, during the enumerator sharding process, it will fetch the latest offset for each partition and use it as the stopping point. ```hocon # Defining the runtime environment @@ -188,6 +189,65 @@ source { > This is written to the same pg table according to different formats and topics of parsing kafka Perform upsert operations based on the id +> Note: Kafka is an unstructured data source and should be use 'tables_configs', and 'table_list' will be removed in the future. + +```hocon + +env { + execution.parallelism = 1 + job.mode = "BATCH" +} + +source { + Kafka { + bootstrap.servers = "kafka_e2e:9092" + tables_configs = [ + { + topic = "^test-ogg-sou.*" + pattern = "true" + consumer.group = "ogg_multi_group" + start_mode = earliest + schema = { + fields { + id = "int" + name = "string" + description = "string" + weight = "string" + } + }, + format = ogg_json + }, + { + topic = "test-cdc_mds" + start_mode = earliest + schema = { + fields { + id = "int" + name = "string" + description = "string" + weight = "string" + } + }, + format = canal_json + } + ] + } +} + +sink { + Jdbc { + driver = org.postgresql.Driver + url = "jdbc:postgresql://postgresql:5432/test?loggerLevel=OFF" + user = test + password = test + generate_sink_sql = true + database = test + table = public.sink + primary_keys = ["id"] + } +} +``` + ```hocon env { @@ -289,7 +349,7 @@ source { """ bootstrap.servers = "kafkaCluster:9092" start_mode = "earliest" - result_table_name = "kafka_table" + plugin_output = "kafka_table" } } ``` diff --git a/docs/en/connector-v2/source/Klaviyo.md b/docs/en/connector-v2/source/Klaviyo.md index 10b4ed42e9e..848fe38ef8f 100644 --- a/docs/en/connector-v2/source/Klaviyo.md +++ b/docs/en/connector-v2/source/Klaviyo.md @@ -45,7 +45,7 @@ http request url API private key for login, you can get more detail at this link: -https://developers.klaviyo.com/en/docs/retrieve_api_credentials +https://developers.klaviyo.com/en/docs/authenticate_#private-key-authentication ### revision [String] diff --git a/docs/en/connector-v2/source/Kudu.md b/docs/en/connector-v2/source/Kudu.md index ccd63e090b3..a6fee76f12c 100644 --- a/docs/en/connector-v2/source/Kudu.md +++ b/docs/en/connector-v2/source/Kudu.md @@ -78,7 +78,7 @@ source { kudu { kudu_masters = "kudu-master:7051" table_name = "kudu_source_table" - result_table_name = "kudu" + plugin_output = "kudu" enable_kerberos = true kerberos_principal = "xx@xx.COM" kerberos_keytab = "xx.keytab" @@ -90,11 +90,11 @@ transform { sink { console { - source_table_name = "kudu" + plugin_input = "kudu" } kudu { - source_table_name = "kudu" + plugin_input = "kudu" kudu_masters = "kudu-master:7051" table_name = "kudu_sink_table" enable_kerberos = true @@ -125,7 +125,7 @@ source { table_name = "kudu_source_table_2" } ] - result_table_name = "kudu" + plugin_output = "kudu" } } diff --git a/docs/en/connector-v2/source/LocalFile.md b/docs/en/connector-v2/source/LocalFile.md index 6d11b992e3a..477a4d41399 100644 --- a/docs/en/connector-v2/source/LocalFile.md +++ b/docs/en/connector-v2/source/LocalFile.md @@ -43,7 +43,7 @@ If you use SeaTunnel Engine, It automatically integrated the hadoop jar when you ## Options -| name | type | required | default value | +| name | type | required | default value | |---------------------------|---------|----------|--------------------------------------| | path | string | yes | - | | file_format_type | string | yes | - | @@ -58,10 +58,11 @@ If you use SeaTunnel Engine, It automatically integrated the hadoop jar when you | sheet_name | string | no | - | | xml_row_tag | string | no | - | | xml_use_attr_format | boolean | no | - | -| file_filter_pattern | string | no | - | +| file_filter_pattern | string | no | | | compress_codec | string | no | none | | archive_compress_codec | string | no | none | | encoding | string | no | UTF-8 | +| null_format | string | no | - | | common-options | | no | - | | tables_configs | list | no | used to define a multiple table task | @@ -254,6 +255,55 @@ Specifies Whether to process data using the tag attribute format. Filter pattern, which used for filtering files. +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### compress_codec [string] The compress codec of files and the details that supported as the following shown: @@ -273,6 +323,7 @@ The compress codec of archive files and the details that supported as the follow | ZIP | txt,json,excel,xml | .zip | | TAR | txt,json,excel,xml | .tar | | TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | | NONE | all | .* | ### encoding [string] @@ -280,6 +331,13 @@ The compress codec of archive files and the details that supported as the follow Only used when file_format_type is json,text,csv,xml. The encoding of the file to read. This param will be parsed by `Charset.forName(encoding)`. +### null_format [string] + +Only used when file_format_type is text. +null_format to define which strings can be represented as null. + +e.g: `\N` + ### common options Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details @@ -406,6 +464,30 @@ sink { ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + LocalFile { + path = "/data/seatunnel/" + file_format_type = "csv" + skip_header_row_number = 1 + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` + ## Changelog ### 2.2.0-beta 2022-09-26 @@ -417,4 +499,6 @@ sink { - [BugFix] Fix the bug of incorrect path in windows environment ([2980](https://github.com/apache/seatunnel/pull/2980)) - [Improve] Support extract partition from SeaTunnelRow fields ([3085](https://github.com/apache/seatunnel/pull/3085)) - [Improve] Support parse field from file path ([2985](https://github.com/apache/seatunnel/pull/2985)) +### 2.3.9-beta 2024-11-12 +- [Improve] Support parse field from file path ([8019](https://github.com/apache/seatunnel/issues/8019)) diff --git a/docs/en/connector-v2/source/Mivlus.md b/docs/en/connector-v2/source/Milvus.md similarity index 85% rename from docs/en/connector-v2/source/Mivlus.md rename to docs/en/connector-v2/source/Milvus.md index a56df4c5fe7..e9560489762 100644 --- a/docs/en/connector-v2/source/Mivlus.md +++ b/docs/en/connector-v2/source/Milvus.md @@ -4,7 +4,11 @@ ## Description -Read data from Milvus or Zilliz Cloud +This Milvus source connector reads data from Milvus or Zilliz Cloud, it has the following features: +- support read and write data by partition +- support read dynamic schema data into Metadata Column +- json data will be converted to json string and sink as json as well +- retry automatically to bypass ratelimit and grpc limit ## Key Features @@ -53,3 +57,5 @@ source { } ``` +## Changelog + diff --git a/docs/en/connector-v2/source/MongoDB-CDC.md b/docs/en/connector-v2/source/MongoDB-CDC.md index 301d7075738..d7e6c7e440f 100644 --- a/docs/en/connector-v2/source/MongoDB-CDC.md +++ b/docs/en/connector-v2/source/MongoDB-CDC.md @@ -105,13 +105,14 @@ For specific types in MongoDB, we use Extended JSON format to map them to Seatun ## Source Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |------------------------------------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | hosts | String | Yes | - | The comma-separated list of hostname and port pairs of the MongoDB servers. eg. `localhost:27017,localhost:27018` | | username | String | No | - | Name of the database user to be used when connecting to MongoDB. | | password | String | No | - | Password to be used when connecting to MongoDB. | | database | List | Yes | - | Name of the database to watch for changes. If not set then all databases will be captured. The database also supports regular expressions to monitor multiple databases matching the regular expression. eg. `db1,db2`. | | collection | List | Yes | - | Name of the collection in the database to watch for changes. If not set then all collections will be captured. The collection also supports regular expressions to monitor multiple collections matching fully-qualified collection identifiers. eg. `db1.coll1,db2.coll2`. | +| schema | | yes | - | The structure of the data, including field names and field types. | | connection.options | String | No | - | The ampersand-separated connection options of MongoDB. eg. `replicaSet=test&connectTimeoutMS=300000`. | | batch.size | Long | No | 1024 | The cursor batch size. | | poll.max.batch.size | Enum | No | 1024 | Maximum number of change stream documents to include in a single batch when polling for new data. | @@ -185,6 +186,14 @@ source { collection = ["inventory.products"] username = stuser password = stpw + schema = { + fields { + "_id" : string, + "name" : string, + "description" : string, + "weight" : string + } + } } } @@ -204,76 +213,6 @@ sink { } ``` -## Multi-table Synchronization - -The following example demonstrates how to create a data synchronization job that read the cdc data of multiple library tables mongodb and prints it on the local client: - -```hocon -env { - # You can set engine configuration here - parallelism = 1 - job.mode = "STREAMING" - checkpoint.interval = 5000 -} - -source { - MongoDB-CDC { - hosts = "mongo0:27017" - database = ["inventory","crm"] - collection = ["inventory.products","crm.test"] - username = stuser - password = stpw - } -} - -# Console printing of the read Mongodb data -sink { - Console { - parallelism = 1 - } -} -``` - -### Tips: - -> 1.The cdc synchronization of multiple library tables cannot specify the schema, and can only output json data downstream. -> This is because MongoDB does not provide metadata information for querying, so if you want to support multiple tables, all tables can only be read as one structure. - -## Regular Expression Matching for Multiple Tables - -The following example demonstrates how to create a data synchronization job that through regular expression read the data of multiple library tables mongodb and prints it on the local client: - -| Matching example | Expressions | | Describe | -|------------------|-------------|---|----------------------------------------------------------------------------------------| -| Prefix matching | ^(test).* | | Match the database name or table name with the prefix test, such as test1, test2, etc. | -| Suffix matching | .*[p$] | | Match the database name or table name with the suffix p, such as cdcp, edcp, etc. | - -```hocon -env { - # You can set engine configuration here - parallelism = 1 - job.mode = "STREAMING" - checkpoint.interval = 5000 -} - -source { - MongoDB-CDC { - hosts = "mongo0:27017" - # So this example is used (^(test).*|^(tpc).*|txc|.*[p$]|t{2}).(t[5-8]|tt),matching txc.tt、test2.test5. - database = ["(^(test).*|^(tpc).*|txc|.*[p$]|t{2})"] - collection = ["(t[5-8]|tt)"] - username = stuser - password = stpw - } -} - -# Console printing of the read Mongodb data -sink { - Console { - parallelism = 1 - } -} -``` ## Format of real-time streaming data @@ -309,4 +248,3 @@ sink { } } ``` - diff --git a/docs/en/connector-v2/source/MySQL-CDC.md b/docs/en/connector-v2/source/MySQL-CDC.md index fc2ea4d8ff0..cc58ec44596 100644 --- a/docs/en/connector-v2/source/MySQL-CDC.md +++ b/docs/en/connector-v2/source/MySQL-CDC.md @@ -169,14 +169,14 @@ When an initial consistent snapshot is made for large databases, your establishe ## Source Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |------------------------------------------------|----------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | base-url | String | Yes | - | The URL of the JDBC connection. Refer to a case: `jdbc:mysql://localhost:3306:3306/test`. | | username | String | Yes | - | Name of the database to use when connecting to the database server. | | password | String | Yes | - | Password to use when connecting to the database server. | | database-names | List | No | - | Database name of the database to monitor. | | table-names | List | Yes | - | Table name of the database to monitor. The table name needs to include the database name, for example: `database_name.table_name` | -| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | +| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys": ["key1"],"snapshotSplitColumn": "key2"}] | | startup.mode | Enum | No | INITIAL | Optional startup mode for MySQL CDC consumer, valid enumerations are `initial`, `earliest`, `latest` and `specific`.
`initial`: Synchronize historical data at startup, and then synchronize incremental data.
`earliest`: Startup from the earliest offset possible.
`latest`: Startup from the latest offset.
`specific`: Startup from user-supplied specific offsets. | | startup.specific-offset.file | String | No | - | Start from the specified binlog file name. **Note, This option is required when the `startup.mode` option used `specific`.** | | startup.specific-offset.pos | Long | No | - | Start from the specified binlog file position. **Note, This option is required when the `startup.mode` option used `specific`.** | diff --git a/docs/en/connector-v2/source/Opengauss-CDC.md b/docs/en/connector-v2/source/Opengauss-CDC.md index 81691ea1ff4..26825202963 100644 --- a/docs/en/connector-v2/source/Opengauss-CDC.md +++ b/docs/en/connector-v2/source/Opengauss-CDC.md @@ -64,31 +64,31 @@ select 'ALTER TABLE ' || schemaname || '.' || tablename || ' REPLICA IDENTITY FU ## Source Options -| Name | Type | Required | Default | Description | -|------------------------------------------------|----------|----------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| base-url | String | Yes | - | The URL of the JDBC connection. Refer to a case: `jdbc:postgresql://localhost:5432/postgres_cdc?loggerLevel=OFF`. | -| username | String | Yes | - | Username of the database to use when connecting to the database server. | -| password | String | Yes | - | Password to use when connecting to the database server. | -| database-names | List | No | - | Database name of the database to monitor. | -| table-names | List | Yes | - | Table name of the database to monitor. The table name needs to include the database name, for example: `database_name.table_name` | -| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | -| startup.mode | Enum | No | INITIAL | Optional startup mode for Opengauss CDC consumer, valid enumerations are `initial`, `earliest`, `latest` and `specific`.
`initial`: Synchronize historical data at startup, and then synchronize incremental data.
`earliest`: Startup from the earliest offset possible.
`latest`: Startup from the latest offset.
`specific`: Startup from user-supplied specific offsets. | -| snapshot.split.size | Integer | No | 8096 | The split size (number of rows) of table snapshot, captured tables are split into multiple splits when read the snapshot of table. | -| snapshot.fetch.size | Integer | No | 1024 | The maximum fetch size for per poll when read table snapshot. | -| slot.name | String | No | - | The name of the Opengauss logical decoding slot that was created for streaming changes from a particular plug-in for a particular database/schema. The server uses this slot to stream events to the connector that you are configuring. Default is seatunnel. | -| decoding.plugin.name | String | No | pgoutput | The name of the Postgres logical decoding plug-in installed on the server,Supported values are decoderbufs, wal2json, wal2json_rds, wal2json_streaming,wal2json_rds_streaming and pgoutput. | -| server-time-zone | String | No | UTC | The session time zone in database server. If not set, then ZoneId.systemDefault() is used to determine the server time zone. | -| connect.timeout.ms | Duration | No | 30000 | The maximum time that the connector should wait after trying to connect to the database server before timing out. | -| connect.max-retries | Integer | No | 3 | The max retry times that the connector should retry to build database server connection. | -| connection.pool.size | Integer | No | 20 | The jdbc connection pool size. | +| Name | Type | Required | Default | Description | +|------------------------------------------------|----------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| base-url | String | Yes | - | The URL of the JDBC connection. Refer to a case: `jdbc:postgresql://localhost:5432/postgres_cdc?loggerLevel=OFF`. | +| username | String | Yes | - | Username of the database to use when connecting to the database server. | +| password | String | Yes | - | Password to use when connecting to the database server. | +| database-names | List | No | - | Database name of the database to monitor. | +| table-names | List | Yes | - | Table name of the database to monitor. The table name needs to include the database name, for example: `database_name.table_name` | +| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | +| startup.mode | Enum | No | INITIAL | Optional startup mode for Opengauss CDC consumer, valid enumerations are `initial`, `earliest`, `latest`.
`initial`: Synchronize historical data at startup, and then synchronize incremental data.
`earliest`: Startup from the earliest offset possible.
`latest`: Startup from the latest offset. | +| snapshot.split.size | Integer | No | 8096 | The split size (number of rows) of table snapshot, captured tables are split into multiple splits when read the snapshot of table. | +| snapshot.fetch.size | Integer | No | 1024 | The maximum fetch size for per poll when read table snapshot. | +| slot.name | String | No | - | The name of the Opengauss logical decoding slot that was created for streaming changes from a particular plug-in for a particular database/schema. The server uses this slot to stream events to the connector that you are configuring. Default is seatunnel. | +| decoding.plugin.name | String | No | pgoutput | The name of the Postgres logical decoding plug-in installed on the server,Supported values are decoderbufs, wal2json, wal2json_rds, wal2json_streaming,wal2json_rds_streaming and pgoutput. | +| server-time-zone | String | No | UTC | The session time zone in database server. If not set, then ZoneId.systemDefault() is used to determine the server time zone. | +| connect.timeout.ms | Duration | No | 30000 | The maximum time that the connector should wait after trying to connect to the database server before timing out. | +| connect.max-retries | Integer | No | 3 | The max retry times that the connector should retry to build database server connection. | +| connection.pool.size | Integer | No | 20 | The jdbc connection pool size. | | chunk-key.even-distribution.factor.upper-bound | Double | No | 100 | The upper bound of the chunk key distribution factor. This factor is used to determine whether the table data is evenly distributed. If the distribution factor is calculated to be less than or equal to this upper bound (i.e., (MAX(id) - MIN(id) + 1) / row count), the table chunks would be optimized for even distribution. Otherwise, if the distribution factor is greater, the table will be considered as unevenly distributed and the sampling-based sharding strategy will be used if the estimated shard count exceeds the value specified by `sample-sharding.threshold`. The default value is 100.0. | -| chunk-key.even-distribution.factor.lower-bound | Double | No | 0.05 | The lower bound of the chunk key distribution factor. This factor is used to determine whether the table data is evenly distributed. If the distribution factor is calculated to be greater than or equal to this lower bound (i.e., (MAX(id) - MIN(id) + 1) / row count), the table chunks would be optimized for even distribution. Otherwise, if the distribution factor is less, the table will be considered as unevenly distributed and the sampling-based sharding strategy will be used if the estimated shard count exceeds the value specified by `sample-sharding.threshold`. The default value is 0.05. | -| sample-sharding.threshold | Integer | No | 1000 | This configuration specifies the threshold of estimated shard count to trigger the sample sharding strategy. When the distribution factor is outside the bounds specified by `chunk-key.even-distribution.factor.upper-bound` and `chunk-key.even-distribution.factor.lower-bound`, and the estimated shard count (calculated as approximate row count / chunk size) exceeds this threshold, the sample sharding strategy will be used. This can help to handle large datasets more efficiently. The default value is 1000 shards. | -| inverse-sampling.rate | Integer | No | 1000 | The inverse of the sampling rate used in the sample sharding strategy. For example, if this value is set to 1000, it means a 1/1000 sampling rate is applied during the sampling process. This option provides flexibility in controlling the granularity of the sampling, thus affecting the final number of shards. It's especially useful when dealing with very large datasets where a lower sampling rate is preferred. The default value is 1000. | -| exactly_once | Boolean | No | false | Enable exactly once semantic. | -| format | Enum | No | DEFAULT | Optional output format for Opengauss CDC, valid enumerations are `DEFAULT`, `COMPATIBLE_DEBEZIUM_JSON`. | -| debezium | Config | No | - | Pass-through [Debezium's properties](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/postgresql.adoc#connector-configuration-properties) to Debezium Embedded Engine which is used to capture data changes from Opengauss server. | -| common-options | | no | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details | +| chunk-key.even-distribution.factor.lower-bound | Double | No | 0.05 | The lower bound of the chunk key distribution factor. This factor is used to determine whether the table data is evenly distributed. If the distribution factor is calculated to be greater than or equal to this lower bound (i.e., (MAX(id) - MIN(id) + 1) / row count), the table chunks would be optimized for even distribution. Otherwise, if the distribution factor is less, the table will be considered as unevenly distributed and the sampling-based sharding strategy will be used if the estimated shard count exceeds the value specified by `sample-sharding.threshold`. The default value is 0.05. | +| sample-sharding.threshold | Integer | No | 1000 | This configuration specifies the threshold of estimated shard count to trigger the sample sharding strategy. When the distribution factor is outside the bounds specified by `chunk-key.even-distribution.factor.upper-bound` and `chunk-key.even-distribution.factor.lower-bound`, and the estimated shard count (calculated as approximate row count / chunk size) exceeds this threshold, the sample sharding strategy will be used. This can help to handle large datasets more efficiently. The default value is 1000 shards. | +| inverse-sampling.rate | Integer | No | 1000 | The inverse of the sampling rate used in the sample sharding strategy. For example, if this value is set to 1000, it means a 1/1000 sampling rate is applied during the sampling process. This option provides flexibility in controlling the granularity of the sampling, thus affecting the final number of shards. It's especially useful when dealing with very large datasets where a lower sampling rate is preferred. The default value is 1000. | +| exactly_once | Boolean | No | false | Enable exactly once semantic. | +| format | Enum | No | DEFAULT | Optional output format for Opengauss CDC, valid enumerations are `DEFAULT`, `COMPATIBLE_DEBEZIUM_JSON`. | +| debezium | Config | No | - | Pass-through [Debezium's properties](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/postgresql.adoc#connector-configuration-properties) to Debezium Embedded Engine which is used to capture data changes from Opengauss server. | +| common-options | | no | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details | ## Task Example @@ -109,7 +109,7 @@ env { source { Opengauss-CDC { - result_table_name = "customers_opengauss_cdc" + plugin_output = "customers_opengauss_cdc" username = "gaussdb" password = "openGauss@123" database-names = ["opengauss_cdc"] @@ -126,7 +126,7 @@ transform { sink { jdbc { - source_table_name = "customers_opengauss_cdc" + plugin_input = "customers_opengauss_cdc" url = "jdbc:postgresql://opengauss_cdc_e2e:5432/opengauss_cdc" driver = "org.postgresql.Driver" user = "dailai" @@ -149,7 +149,7 @@ sink { ``` source { Opengauss-CDC { - result_table_name = "customers_opengauss_cdc" + plugin_output = "customers_opengauss_cdc" username = "gaussdb" password = "openGauss@123" database-names = ["opengauss_cdc"] diff --git a/docs/en/connector-v2/source/Oracle-CDC.md b/docs/en/connector-v2/source/Oracle-CDC.md index feef58a0d2e..dad52faa0b1 100644 --- a/docs/en/connector-v2/source/Oracle-CDC.md +++ b/docs/en/connector-v2/source/Oracle-CDC.md @@ -220,7 +220,7 @@ exit; ## Source Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |------------------------------------------------|----------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | base-url | String | Yes | - | The URL of the JDBC connection. Refer to a case: `idbc:oracle:thin:datasource01:1523:xe`. | | username | String | Yes | - | Name of the database to use when connecting to the database server. | @@ -228,7 +228,7 @@ exit; | database-names | List | No | - | Database name of the database to monitor. | | schema-names | List | No | - | Schema name of the database to monitor. | | table-names | List | Yes | - | Table name of the database to monitor. The table name needs to include the database name, for example: `database_name.table_name` | -| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | +| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys": ["key1"],"snapshotSplitColumn": "key2"}] | | startup.mode | Enum | No | INITIAL | Optional startup mode for Oracle CDC consumer, valid enumerations are `initial`, `earliest`, `latest` and `specific`.
`initial`: Synchronize historical data at startup, and then synchronize incremental data.
`earliest`: Startup from the earliest offset possible.
`latest`: Startup from the latest offset.
`specific`: Startup from user-supplied specific offsets. | | startup.specific-offset.file | String | No | - | Start from the specified binlog file name. **Note, This option is required when the `startup.mode` option used `specific`.** | | startup.specific-offset.pos | Long | No | - | Start from the specified binlog file position. **Note, This option is required when the `startup.mode` option used `specific`.** | @@ -262,7 +262,7 @@ exit; source { # This is a example source plugin **only for test and demonstrate the feature source plugin** Oracle-CDC { - result_table_name = "customers" + plugin_output = "customers" username = "system" password = "oracle" database-names = ["XE"] @@ -280,7 +280,7 @@ source { > source { > # This is a example source plugin **only for test and demonstrate the feature source plugin** > Oracle-CDC { -> result_table_name = "customers" +> plugin_output = "customers" > use_select_count = true > username = "system" > password = "oracle" @@ -299,7 +299,7 @@ source { > source { > # This is a example source plugin **only for test and demonstrate the feature source plugin** > Oracle-CDC { -> result_table_name = "customers" +> plugin_output = "customers" > skip_analyze = true > username = "system" > password = "oracle" @@ -318,7 +318,7 @@ source { source { Oracle-CDC { - result_table_name = "customers" + plugin_output = "customers" base-url = "jdbc:oracle:thin:system/oracle@oracle-host:1521:xe" source.reader.close.timeout = 120000 username = "system" diff --git a/docs/en/connector-v2/source/OssFile.md b/docs/en/connector-v2/source/OssFile.md index d5326cb86a4..42163a9d13e 100644 --- a/docs/en/connector-v2/source/OssFile.md +++ b/docs/en/connector-v2/source/OssFile.md @@ -190,7 +190,7 @@ If you assign file type to `parquet` `orc`, schema option not required, connecto ## Options -| name | type | required | default value | Description | +| name | type | required | default value | Description | |---------------------------|---------|----------|---------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | path | string | yes | - | The Oss path that needs to be read can have sub paths, but the sub paths need to meet certain format requirements. Specific requirements can be referred to "parse_partition_from_path" option | | file_format_type | string | yes | - | File type, supported as the following file types: `text` `csv` `parquet` `orc` `json` `excel` `xml` `binary` | @@ -211,7 +211,8 @@ If you assign file type to `parquet` `orc`, schema option not required, connecto | xml_use_attr_format | boolean | no | - | Specifies whether to process data using the tag attribute format, only used when file_format is xml. | | compress_codec | string | no | none | Which compress codec the files used. | | encoding | string | no | UTF-8 | -| file_filter_pattern | string | no | | `*.txt` means you only need read the files end with `.txt` | +| null_format | string | no | - | Only used when file_format_type is text. null_format to define which strings can be represented as null. e.g: `\N` | +| file_filter_pattern | string | no | | Filter pattern, which used for filtering files. | | common-options | config | no | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details. | ### compress_codec [string] @@ -233,6 +234,55 @@ The encoding of the file to read. This param will be parsed by `Charset.forName( Filter pattern, which used for filtering files. +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### schema [config] Only need to be configured when the file_format_type are text, json, excel, xml or csv ( Or other format we can't read the schema from metadata). @@ -344,7 +394,7 @@ source { file_format_type = "orc" } ] - result_table_name = "fake" + plugin_output = "fake" } } @@ -461,7 +511,7 @@ source { } } ] - result_table_name = "fake" + plugin_output = "fake" } } @@ -474,6 +524,33 @@ sink { } ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + OssFile { + path = "/seatunnel/orc" + bucket = "oss://tyrantlucifer-image-bed" + access_key = "xxxxxxxxxxxxxxxxx" + access_secret = "xxxxxxxxxxxxxxxxxxxxxx" + endpoint = "oss-cn-beijing.aliyuncs.com" + file_format_type = "orc" + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` + ## Changelog ### 2.2.0-beta 2022-09-26 diff --git a/docs/en/connector-v2/source/OssJindoFile.md b/docs/en/connector-v2/source/OssJindoFile.md index d5bd6d14fa3..9b83a0b0501 100644 --- a/docs/en/connector-v2/source/OssJindoFile.md +++ b/docs/en/connector-v2/source/OssJindoFile.md @@ -49,7 +49,7 @@ It only supports hadoop version **2.9.X+**. ## Options -| name | type | required | default value | +| name | type | required | default value | |---------------------------|---------|----------|---------------------| | path | string | yes | - | | file_format_type | string | yes | - | @@ -68,10 +68,11 @@ It only supports hadoop version **2.9.X+**. | sheet_name | string | no | - | | xml_row_tag | string | no | - | | xml_use_attr_format | boolean | no | - | -| file_filter_pattern | string | no | - | +| file_filter_pattern | string | no | | | compress_codec | string | no | none | | archive_compress_codec | string | no | none | | encoding | string | no | UTF-8 | +| null_format | string | no | - | | common-options | | no | - | ### path [string] @@ -267,6 +268,55 @@ Reader the sheet of the workbook. Filter pattern, which used for filtering files. +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### compress_codec [string] The compress codec of files and the details that supported as the following shown: @@ -286,6 +336,7 @@ The compress codec of archive files and the details that supported as the follow | ZIP | txt,json,excel,xml | .zip | | TAR | txt,json,excel,xml | .tar | | TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | | NONE | all | .* | ### encoding [string] @@ -293,6 +344,13 @@ The compress codec of archive files and the details that supported as the follow Only used when file_format_type is json,text,csv,xml. The encoding of the file to read. This param will be parsed by `Charset.forName(encoding)`. +### null_format [string] + +Only used when file_format_type is text. +null_format to define which strings can be represented as null. + +e.g: `\N` + ### common options Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details. @@ -364,6 +422,33 @@ sink { ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + OssJindoFile { + bucket = "oss://tyrantlucifer-image-bed" + access_key = "xxxxxxxxxxxxxxxxx" + access_secret = "xxxxxxxxxxxxxxxxxxxxxx" + endpoint = "oss-cn-beijing.aliyuncs.com" + path = "/seatunnel/read/binary/" + file_format_type = "binary" + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` + ## Changelog ### next version diff --git a/docs/en/connector-v2/source/Paimon.md b/docs/en/connector-v2/source/Paimon.md index e586a4fd9d8..cbe3b592f8b 100644 --- a/docs/en/connector-v2/source/Paimon.md +++ b/docs/en/connector-v2/source/Paimon.md @@ -82,6 +82,11 @@ Properties in hadoop conf The specified loading path for the 'core-site.xml', 'hdfs-site.xml', 'hive-site.xml' files +## Filesystems +The Paimon connector supports writing data to multiple file systems. Currently, the supported file systems are hdfs and s3. +If you use the s3 filesystem. You can configure the `fs.s3a.access-key`、`fs.s3a.secret-key`、`fs.s3a.endpoint`、`fs.s3a.path.style.access`、`fs.s3a.aws.credentials.provider` properties in the `paimon.hadoop.conf` option. +Besides, the warehouse should start with `s3a://`. + ## Examples ### Simple example @@ -109,6 +114,33 @@ source { } ``` +### S3 example +```hocon +env { + execution.parallelism = 1 + job.mode = "BATCH" +} + +source { + Paimon { + warehouse = "s3a://test/" + database = "seatunnel_namespace11" + table = "st_test" + paimon.hadoop.conf = { + fs.s3a.access-key=G52pnxg67819khOZ9ezX + fs.s3a.secret-key=SHJuAQqHsLrgZWikvMa3lJf5T0NfM5LMFliJh9HF + fs.s3a.endpoint="http://minio4:9000" + fs.s3a.path.style.access=true + fs.s3a.aws.credentials.provider=org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + } + } +} + +sink { + Console{} +} +``` + ### Hadoop conf example ```hocon diff --git a/docs/en/connector-v2/source/PostgreSQL-CDC.md b/docs/en/connector-v2/source/PostgreSQL-CDC.md index be87d03edd4..21afa42f701 100644 --- a/docs/en/connector-v2/source/PostgreSQL-CDC.md +++ b/docs/en/connector-v2/source/PostgreSQL-CDC.md @@ -86,15 +86,15 @@ ALTER TABLE your_table_name REPLICA IDENTITY FULL; ## Source Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |------------------------------------------------|----------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | base-url | String | Yes | - | The URL of the JDBC connection. Refer to a case: `jdbc:postgresql://localhost:5432/postgres_cdc?loggerLevel=OFF`. | | username | String | Yes | - | Name of the database to use when connecting to the database server. | | password | String | Yes | - | Password to use when connecting to the database server. | | database-names | List | No | - | Database name of the database to monitor. | | table-names | List | Yes | - | Table name of the database to monitor. The table name needs to include the database name, for example: `database_name.table_name` | -| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | -| startup.mode | Enum | No | INITIAL | Optional startup mode for PostgreSQL CDC consumer, valid enumerations are `initial`, `earliest`, `latest` and `specific`.
`initial`: Synchronize historical data at startup, and then synchronize incremental data.
`earliest`: Startup from the earliest offset possible.
`latest`: Startup from the latest offset.
`specific`: Startup from user-supplied specific offsets. | +| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys": ["key1"],"snapshotSplitColumn": "key2"}] | +| startup.mode | Enum | No | INITIAL | Optional startup mode for PostgreSQL CDC consumer, valid enumerations are `initial`, `earliest` and `latest`.
`initial`: Synchronize historical data at startup, and then synchronize incremental data.
`earliest`: Startup from the earliest offset possible.
`latest`: Startup from the latest offset. | | snapshot.split.size | Integer | No | 8096 | The split size (number of rows) of table snapshot, captured tables are split into multiple splits when read the snapshot of table. | | snapshot.fetch.size | Integer | No | 1024 | The maximum fetch size for per poll when read table snapshot. | | slot.name | String | No | - | The name of the PostgreSQL logical decoding slot that was created for streaming changes from a particular plug-in for a particular database/schema. The server uses this slot to stream events to the connector that you are configuring. Default is seatunnel. | @@ -132,7 +132,7 @@ env { source { Postgres-CDC { - result_table_name = "customers_Postgre_cdc" + plugin_output = "customers_Postgre_cdc" username = "postgres" password = "postgres" database-names = ["postgres_cdc"] @@ -148,7 +148,7 @@ transform { sink { jdbc { - source_table_name = "customers_Postgre_cdc" + plugin_input = "customers_Postgre_cdc" url = "jdbc:postgresql://postgres_cdc_e2e:5432/postgres_cdc?loggerLevel=OFF" driver = "org.postgresql.Driver" user = "postgres" @@ -169,7 +169,7 @@ sink { ``` source { Postgres-CDC { - result_table_name = "customers_mysql_cdc" + plugin_output = "customers_mysql_cdc" username = "postgres" password = "postgres" database-names = ["postgres_cdc"] diff --git a/docs/en/connector-v2/source/PostgreSQL.md b/docs/en/connector-v2/source/PostgreSQL.md index 101902d3618..d383b113c2e 100644 --- a/docs/en/connector-v2/source/PostgreSQL.md +++ b/docs/en/connector-v2/source/PostgreSQL.md @@ -261,7 +261,7 @@ source{ partition_column= "id" # The name of the table returned - result_table_name = "jdbc" + plugin_output = "jdbc" partition_lower_bound = 1 partition_upper_bound = 50 partition_num = 5 diff --git a/docs/en/connector-v2/source/Prometheus.md b/docs/en/connector-v2/source/Prometheus.md new file mode 100644 index 00000000000..ba8979f023e --- /dev/null +++ b/docs/en/connector-v2/source/Prometheus.md @@ -0,0 +1,152 @@ +# Prometheus + +> Prometheus source connector + +## Description + +Used to read data from Prometheus. + +## Key features + +- [x] [batch](../../concept/connector-v2-features.md) +- [ ] [stream](../../concept/connector-v2-features.md) +- [ ] [parallelism](../../concept/connector-v2-features.md) + +## Options + +| name | type | required | default value | +|-----------------------------|---------|----------|-----------------| +| url | String | Yes | - | +| query | String | Yes | - | +| query_type | String | Yes | Instant | +| content_field | String | Yes | $.data.result.* | +| schema.fields | Config | Yes | - | +| format | String | No | json | +| params | Map | Yes | - | +| poll_interval_millis | int | No | - | +| retry | int | No | - | +| retry_backoff_multiplier_ms | int | No | 100 | +| retry_backoff_max_ms | int | No | 10000 | +| enable_multi_lines | boolean | No | false | +| common-options | config | No | - | + +### url [String] + +http request url + +### query [String] + +Prometheus expression query string + +### query_type [String] + +Instant/Range + +1. Instant : The following endpoint evaluates an instant query at a single point in time +2. Range : The following endpoint evaluates an expression query over a range of time + +https://prometheus.io/docs/prometheus/latest/querying/api/ + +### params [Map] + +http request params + +### poll_interval_millis [int] + +request http api interval(millis) in stream mode + +### retry [int] + +The max retry times if request http return to `IOException` + +### retry_backoff_multiplier_ms [int] + +The retry-backoff times(millis) multiplier if request http failed + +### retry_backoff_max_ms [int] + +The maximum retry-backoff times(millis) if request http failed + +### format [String] + +the format of upstream data, default `json`. + +### schema [Config] + +Fill in a fixed value + +```hocon + schema = { + fields { + metric = "map" + value = double + time = long + } + } + +``` + +#### fields [Config] + +the schema fields of upstream data + +### common options + +Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details + +## Example + +### Instant: + +```hocon +source { + Prometheus { + plugin_output = "http" + url = "http://mockserver:1080" + query = "up" + query_type = "Instant" + content_field = "$.data.result.*" + format = "json" + schema = { + fields { + metric = "map" + value = double + time = long + } + } + } +} +``` + +### Range + +```hocon +source { + Prometheus { + plugin_output = "http" + url = "http://mockserver:1080" + query = "up" + query_type = "Range" + content_field = "$.data.result.*" + format = "json" + start = "2024-07-22T20:10:30.781Z" + end = "2024-07-22T20:11:00.781Z" + step = "15s" + schema = { + fields { + metric = "map" + value = double + time = long + } + } + } + } +``` + +## Changelog + +### next version + +- Add Prometheus Source Connector +- Reduce configuration items + diff --git a/docs/en/connector-v2/source/Pulsar.md b/docs/en/connector-v2/source/Pulsar.md index 73496180626..77d9938008b 100644 --- a/docs/en/connector-v2/source/Pulsar.md +++ b/docs/en/connector-v2/source/Pulsar.md @@ -147,7 +147,7 @@ source { subscription.name = "seatunnel" client.service-url = "pulsar://localhost:6650" admin.service-url = "http://my-broker.example.com:8080" - result_table_name = "test" + plugin_output = "test" } } ``` diff --git a/docs/en/connector-v2/source/RocketMQ.md b/docs/en/connector-v2/source/RocketMQ.md index 744f4c94ae8..eb8edc1c806 100644 --- a/docs/en/connector-v2/source/RocketMQ.md +++ b/docs/en/connector-v2/source/RocketMQ.md @@ -76,7 +76,7 @@ source { Rocketmq { name.srv.addr = "rocketmq-e2e:9876" topics = "test_topic_json" - result_table_name = "rocketmq_table" + plugin_output = "rocketmq_table" schema = { fields { id = bigint @@ -124,7 +124,7 @@ source { Rocketmq { name.srv.addr = "localhost:9876" topics = "test_topic" - result_table_name = "rocketmq_table" + plugin_output = "rocketmq_table" start.mode = "CONSUME_FROM_FIRST_OFFSET" batch.size = "400" consumer.group = "test_topic_group" diff --git a/docs/en/connector-v2/source/S3File.md b/docs/en/connector-v2/source/S3File.md index d280d6dc7f2..b0e69cd1e36 100644 --- a/docs/en/connector-v2/source/S3File.md +++ b/docs/en/connector-v2/source/S3File.md @@ -196,7 +196,7 @@ If you assign file type to `parquet` `orc`, schema option not required, connecto ## Options -| name | type | required | default value | Description | +| name | type | required | default value | Description | |---------------------------------|---------|----------|-------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | path | string | yes | - | The s3 path that needs to be read can have sub paths, but the sub paths need to meet certain format requirements. Specific requirements can be referred to "parse_partition_from_path" option | | file_format_type | string | yes | - | File type, supported as the following file types: `text` `csv` `parquet` `orc` `json` `excel` `xml` `binary` | @@ -220,12 +220,67 @@ If you assign file type to `parquet` `orc`, schema option not required, connecto | compress_codec | string | no | none | | | archive_compress_codec | string | no | none | | | encoding | string | no | UTF-8 | | +| null_format | string | no | - | Only used when file_format_type is text. null_format to define which strings can be represented as null. e.g: `\N` | +| file_filter_pattern | string | no | | Filter pattern, which used for filtering files. | | common-options | | no | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details. | ### delimiter/field_delimiter [string] **delimiter** parameter will deprecate after version 2.3.5, please use **field_delimiter** instead. +### file_filter_pattern [string] + +Filter pattern, which used for filtering files. + +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### compress_codec [string] The compress codec of files and the details that supported as the following shown: @@ -245,6 +300,7 @@ The compress codec of archive files and the details that supported as the follow | ZIP | txt,json,excel,xml | .zip | | TAR | txt,json,excel,xml | .tar | | TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | | NONE | all | .* | ### encoding [string] @@ -349,6 +405,33 @@ sink { } ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + S3File { + path = "/seatunnel/json" + bucket = "s3a://seatunnel-test" + fs.s3a.endpoint="s3.cn-north-1.amazonaws.com.cn" + fs.s3a.aws.credentials.provider="com.amazonaws.auth.InstanceProfileCredentialsProvider" + file_format_type = "json" + read_columns = ["id", "name"] + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` + ## Changelog ### 2.3.0-beta 2022-10-20 diff --git a/docs/en/connector-v2/source/SftpFile.md b/docs/en/connector-v2/source/SftpFile.md index 6d6ec5ea8db..f5b76ce3055 100644 --- a/docs/en/connector-v2/source/SftpFile.md +++ b/docs/en/connector-v2/source/SftpFile.md @@ -71,7 +71,7 @@ The File does not have a specific type list, and we can indicate which SeaTunnel ## Source Options -| Name | Type | Required | default value | Description | +| Name | Type | Required | default value | Description | |---------------------------|---------|----------|---------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | host | String | Yes | - | The target sftp host is required | | port | Int | Yes | - | The target sftp port is required | @@ -94,8 +94,62 @@ The File does not have a specific type list, and we can indicate which SeaTunnel | compress_codec | String | No | None | The compress codec of files and the details that supported as the following shown:
- txt: `lzo` `None`
- json: `lzo` `None`
- csv: `lzo` `None`
- orc: `lzo` `snappy` `lz4` `zlib` `None`
- parquet: `lzo` `snappy` `lz4` `gzip` `brotli` `zstd` `None`
Tips: excel type does Not support any compression format | | archive_compress_codec | string | no | none | | encoding | string | no | UTF-8 | +| null_format | string | no | - | Only used when file_format_type is text. null_format to define which strings can be represented as null. e.g: `\N` | | common-options | | No | - | Source plugin common parameters, please refer to [Source Common Options](../source-common-options.md) for details. | +### file_filter_pattern [string] + +Filter pattern, which used for filtering files. + +The pattern follows standard regular expressions. For details, please refer to https://en.wikipedia.org/wiki/Regular_expression. +There are some examples. + +File Structure Example: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +Matching Rules Example: + +**Example 1**: *Match all .txt files*,Regular Expression: +``` +/data/seatunnel/20241001/.*\.txt +``` +The result of this example matching is: +``` +/data/seatunnel/20241001/report.txt +``` +**Example 2**: *Match all file starting with abc*,Regular Expression: +``` +/data/seatunnel/20241002/abc.* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**Example 3**: *Match all file starting with abc,And the fourth character is either h or g*, the Regular Expression: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**Example 4**: *Match third level folders starting with 202410 and files ending with .csv*, the Regular Expression: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +The result of this example matching is: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### file_format_type [string] File type, supported as the following file types: @@ -182,11 +236,12 @@ The compress codec of files and the details that supported as the following show The compress codec of archive files and the details that supported as the following shown: | archive_compress_codec | file_format | archive_compress_suffix | -|------------------------|--------------------|-------------------------| -| ZIP | txt,json,excel,xml | .zip | -| TAR | txt,json,excel,xml | .tar | -| TAR_GZ | txt,json,excel,xml | .tar.gz | -| NONE | all | .* | +|--------------------|--------------------|---------------------| +| ZIP | txt,json,excel,xml | .zip | +| TAR | txt,json,excel,xml | .tar | +| TAR_GZ | txt,json,excel,xml | .tar.gz | +| GZ | txt,json,xml | .gz | +| NONE | all | .* | ### encoding [string] @@ -219,7 +274,7 @@ source { password = pass path = "tmp/seatunnel/read/json" file_format_type = "json" - result_table_name = "sftp" + plugin_output = "sftp" schema = { fields { c_map = "map" @@ -264,4 +319,71 @@ sink { } } ``` +### Multiple Table + +```hocon +SftpFile { + tables_configs = [ + { + schema { + table = "student" + fields { + name = string + age = int + } + } + path = "/tmp/seatunnel/sink/text" + host = "192.168.31.48" + port = 21 + user = tyrantlucifer + password = tianchao + file_format_type = "parquet" + }, + { + schema { + table = "teacher" + fields { + name = string + age = int + } + } + path = "/tmp/seatunnel/sink/text" + host = "192.168.31.48" + port = 21 + user = tyrantlucifer + password = tianchao + file_format_type = "parquet" + } + ] +} + +``` + +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + SftpFile { + host = "sftp" + port = 22 + user = seatunnel + password = pass + path = "tmp/seatunnel/read/json" + file_format_type = "json" + plugin_output = "sftp" + // file example abcD2024.csv + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` diff --git a/docs/en/connector-v2/source/SqlServer-CDC.md b/docs/en/connector-v2/source/SqlServer-CDC.md index a64b3abfa88..8a3d8423748 100644 --- a/docs/en/connector-v2/source/SqlServer-CDC.md +++ b/docs/en/connector-v2/source/SqlServer-CDC.md @@ -63,13 +63,13 @@ describes how to setup the Sql Server CDC connector to run SQL queries against S ## Source Options -| Name | Type | Required | Default | Description | +| Name | Type | Required | Default | Description | |------------------------------------------------|----------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | username | String | Yes | - | Name of the database to use when connecting to the database server. | | password | String | Yes | - | Password to use when connecting to the database server. | | database-names | List | Yes | - | Database name of the database to monitor. | | table-names | List | Yes | - | Table name is a combination of schema name and table name (databaseName.schemaName.tableName). | -| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | +| table-names-config | List | No | - | Table config list. for example: [{"table": "db1.schema1.table1","primaryKeys": ["key1"],"snapshotSplitColumn": "key2"}] | | base-url | String | Yes | - | URL has to be with database, like "jdbc:sqlserver://localhost:1433;databaseName=test". | | startup.mode | Enum | No | INITIAL | Optional startup mode for SqlServer CDC consumer, valid enumerations are "initial", "earliest", "latest" and "specific". | | startup.timestamp | Long | No | - | Start from the specified epoch timestamp (in milliseconds).
**Note, This option is required when** the **"startup.mode" option used `'timestamp'`.** | @@ -141,7 +141,7 @@ env { source { # This is a example source plugin **only for test and demonstrate the feature source plugin** SqlServer-CDC { - result_table_name = "customers" + plugin_output = "customers" username = "sa" password = "Y.sa123456" startup.mode="initial" @@ -156,7 +156,7 @@ transform { sink { console { - source_table_name = "customers" + plugin_input = "customers" } ``` @@ -177,7 +177,7 @@ source { SqlServer-CDC { # Set up accurate one read exactly_once=true - result_table_name = "customers" + plugin_output = "customers" username = "sa" password = "Y.sa123456" startup.mode="latest" @@ -192,7 +192,7 @@ transform { sink { console { - source_table_name = "customers" + plugin_input = "customers" } ``` diff --git a/docs/en/connector-v2/source/StarRocks.md b/docs/en/connector-v2/source/StarRocks.md index d46105cc9af..1c1a109480a 100644 --- a/docs/en/connector-v2/source/StarRocks.md +++ b/docs/en/connector-v2/source/StarRocks.md @@ -19,25 +19,25 @@ delivers the query plan as a parameter to BE nodes, and then obtains data result ## Options -| name | type | required | default value | -|-------------------------|--------|----------|-------------------| -| node_urls | list | yes | - | -| username | string | yes | - | -| password | string | yes | - | -| database | string | yes | - | -| table | string | yes | - | -| scan_filter | string | no | - | -| schema | config | yes | - | -| request_tablet_size | int | no | Integer.MAX_VALUE | -| scan_connect_timeout_ms | int | no | 30000 | -| scan_query_timeout_sec | int | no | 3600 | -| scan_keep_alive_min | int | no | 10 | -| scan_batch_rows | int | no | 1024 | -| scan_mem_limit | long | no | 2147483648 | -| max_retries | int | no | 3 | -| scan.params.* | string | no | - | - -### node_urls [list] +| name | type | required | default value | +|--------------------------|--------|----------|-------------------| +| nodeUrls | list | yes | - | +| username | string | yes | - | +| password | string | yes | - | +| database | string | yes | - | +| table | string | yes | - | +| scan_filter | string | no | - | +| schema | config | yes | - | +| request_tablet_size | int | no | Integer.MAX_VALUE | +| scan_connect_timeout_ms | int | no | 30000 | +| scan_query_timeout_sec | int | no | 3600 | +| scan_keep_alive_min | int | no | 10 | +| scan_batch_rows | int | no | 1024 | +| scan_mem_limit | long | no | 2147483648 | +| max_retries | int | no | 3 | +| scan.params.* | string | no | - | + +### nodeUrls [list] `StarRocks` cluster address, the format is `["fe_ip:fe_http_port", ...]` diff --git a/docs/en/connector-v2/source/TDengine.md b/docs/en/connector-v2/source/TDengine.md index a24744d5c17..26480c12354 100644 --- a/docs/en/connector-v2/source/TDengine.md +++ b/docs/en/connector-v2/source/TDengine.md @@ -78,7 +78,7 @@ source { stable : "meters" lower_bound : "2018-10-03 14:38:05.000" upper_bound : "2018-10-03 14:38:16.800" - result_table_name = "tdengine_result" + plugin_output = "tdengine_result" } } ``` diff --git a/docs/en/connector-v2/source/TiDB-CDC.md b/docs/en/connector-v2/source/TiDB-CDC.md index ffa307f8bc1..1cce8ec3ac2 100644 --- a/docs/en/connector-v2/source/TiDB-CDC.md +++ b/docs/en/connector-v2/source/TiDB-CDC.md @@ -91,7 +91,7 @@ env { source { # This is a example source plugin **only for test and demonstrate the feature source plugin** TiDB-CDC { - result_table_name = "products_tidb_cdc" + plugin_output = "products_tidb_cdc" base-url = "jdbc:mysql://tidb0:4000/inventory" driver = "com.mysql.cj.jdbc.Driver" tikv.grpc.timeout_in_ms = 20000 @@ -108,7 +108,7 @@ transform { sink { jdbc { - source_table_name = "products_tidb_cdc" + plugin_input = "products_tidb_cdc" url = "jdbc:mysql://tidb0:4000/inventory" driver = "com.mysql.cj.jdbc.Driver" user = "root" diff --git a/docs/en/contribution/how-to-create-your-connector.md b/docs/en/contribution/how-to-create-your-connector.md new file mode 100644 index 00000000000..b99bc85d999 --- /dev/null +++ b/docs/en/contribution/how-to-create-your-connector.md @@ -0,0 +1,3 @@ +# Develop Your Own Connector + +If you want to develop your own connector for the new SeaTunnel connector API (Connector V2), please check [here](https://github.com/apache/seatunnel/blob/dev/seatunnel-connectors-v2/README.md). \ No newline at end of file diff --git a/docs/en/contribution/setup.md b/docs/en/contribution/setup.md index b2579e1ee1e..8fd632a24b0 100644 --- a/docs/en/contribution/setup.md +++ b/docs/en/contribution/setup.md @@ -80,7 +80,7 @@ After all the above things are done, you just finish the environment setup and c of box. All examples are in module `seatunnel-examples`, you could pick one you are interested in, [Running Or Debugging It In IDEA](https://www.jetbrains.com/help/idea/run-debug-configuration.html) as you wish. -Here we use `seatunnel-examples/seatunnel-engine-examples/src/main/java/org/apache/seatunnel/example/engine/SeaTunnelEngineExample.java` +Here we use `seatunnel-examples/seatunnel-engine-examples/src/main/java/org/apache/seatunnel/example/engine/SeaTunnelEngineLocalExample.java` as an example, when you run it successfully you can see the output as below: ```log diff --git a/docs/en/faq.md b/docs/en/faq.md index 02c125ad4fd..6a4e838eaed 100644 --- a/docs/en/faq.md +++ b/docs/en/faq.md @@ -1,332 +1,116 @@ -# FAQs +# FAQ -## Why should I install a computing engine like Spark or Flink? +## What data sources and destinations does SeaTunnel support? +SeaTunnel supports various data sources and destinations. You can find a detailed list on the following list: +- Supported data sources (Source): [Source List](https://seatunnel.apache.org/docs/connector-v2/source) +- Supported data destinations (Sink): [Sink List](https://seatunnel.apache.org/docs/connector-v2/sink) -SeaTunnel now uses computing engines such as Spark and Flink to complete resource scheduling and node communication, so we can focus on the ease of use of data synchronization and the development of high-performance components. But this is only temporary. +## Does SeaTunnel support batch and streaming processing? +SeaTunnel supports both batch and streaming processing modes. You can select the appropriate mode based on your specific business scenarios and needs. Batch processing is suitable for scheduled data integration tasks, while streaming processing is ideal for real-time integration and Change Data Capture (CDC). -## I have a question, and I cannot solve it by myself +## Is it necessary to install engines like Spark or Flink when using SeaTunnel? +Spark and Flink are not mandatory. SeaTunnel supports Zeta, Spark, and Flink as integration engines, allowing you to choose one based on your needs. The community highly recommends Zeta, a new generation high-performance integration engine specifically designed for integration scenarios. Zeta is affectionately called "Ultraman Zeta" by community users! The community offers extensive support for Zeta, making it the most feature-rich option. -I have encountered a problem when using SeaTunnel and I cannot solve it by myself. What should I do? First, search in [Issue List](https://github.com/apache/seatunnel/issues) or [Mailing List](https://lists.apache.org/list.html?dev@seatunnel.apache.org) to see if someone has already asked the same question and got an answer. If you cannot find an answer to your question, you can contact community members for help in [These Ways](https://github.com/apache/seatunnel#contact-us). +## What data transformation functions does SeaTunnel provide? +SeaTunnel supports multiple data transformation functions, including field mapping, data filtering, data format conversion, and more. You can implement data transformations through the `transform` module in the configuration file. For more details, refer to the SeaTunnel [Transform Documentation](https://seatunnel.apache.org/docs/transform-v2). -## How do I declare a variable? +## Can SeaTunnel support custom data cleansing rules? +Yes, SeaTunnel supports custom data cleansing rules. You can configure custom rules in the `transform` module, such as cleaning up dirty data, removing invalid records, or converting fields. -Do you want to know how to declare a variable in SeaTunnel's configuration, and then dynamically replace the value of the variable at runtime? +## Does SeaTunnel support real-time incremental integration? +SeaTunnel supports incremental data integration. For example, the CDC connector allows real-time capture of data changes, which is ideal for scenarios requiring real-time data integration. -Since `v1.2.4`, SeaTunnel supports variable substitution in the configuration. This feature is often used for timing or non-timing offline processing to replace variables such as time and date. The usage is as follows: +## What CDC data sources are currently supported by SeaTunnel? +SeaTunnel currently supports MongoDB CDC, MySQL CDC, OpenGauss CDC, Oracle CDC, PostgreSQL CDC, SQL Server CDC, TiDB CDC, and more. For more details, refer to the [Source List](https://seatunnel.apache.org/docs/connector-v2/source). -Configure the variable name in the configuration. Here is an example of sql transform (actually, anywhere in the configuration file the value in `'key = value'` can use the variable substitution): +## How do I enable permissions required for SeaTunnel CDC integration? +Please refer to the official SeaTunnel documentation for the necessary steps to enable permissions for each connector’s CDC functionality. -``` -... -transform { - sql { - query = "select * from user_view where city ='"${city}"' and dt = '"${date}"'" - } -} -... -``` +## Does SeaTunnel support CDC from MySQL replicas? How are logs pulled? +Yes, SeaTunnel supports CDC from MySQL replicas by subscribing to binlog logs, which are then parsed on the SeaTunnel server. -Taking Spark Local mode as an example, the startup command is as follows: +## Does SeaTunnel support CDC integration for tables without primary keys? +SeaTunnel does not support CDC integration for tables without primary keys. The reason is that if two identical records exist in the upstream and one is deleted or modified, the downstream cannot determine which record to delete or modify, leading to potential issues. Primary keys are essential to ensure data uniqueness. -```bash -./bin/start-seatunnel-spark.sh \ --c ./config/your_app.conf \ --e client \ --m local[2] \ --i city=shanghai \ --i date=20190319 -``` +## Does SeaTunnel support automatic table creation? +Before starting an integration task, you can select different handling schemes for existing table structures on the target side, controlled via the `schema_save_mode` parameter. Available options include: +- **`RECREATE_SCHEMA`**: Creates the table if it does not exist; if the table exists, it is deleted and recreated. +- **`CREATE_SCHEMA_WHEN_NOT_EXIST`**: Creates the table if it does not exist; skips creation if the table already exists. +- **`ERROR_WHEN_SCHEMA_NOT_EXIST`**: Throws an error if the table does not exist. +- **`IGNORE`**: Ignores table handling. + Many connectors currently support automatic table creation. Refer to the specific connector documentation, such as [Jdbc sink](https://seatunnel.apache.org/docs/2.3.8/connector-v2/sink/Jdbc#schema_save_mode-enum), for more information. -You can use the parameter `-i` or `--variable` followed by `key=value` to specify the value of the variable, where the key needs to be same as the variable name in the configuration. +## Does SeaTunnel support handling existing data before starting a data integration task? +Yes, you can specify different processing schemes for existing data on the target side before starting an integration task, controlled via the `data_save_mode` parameter. Available options include: +- **`DROP_DATA`**: Retains the database structure but deletes the data. +- **`APPEND_DATA`**: Retains both the database structure and data. +- **`CUSTOM_PROCESSING`**: User-defined processing. +- **`ERROR_WHEN_DATA_EXISTS`**: Throws an error if data already exists. + Many connectors support handling existing data; please refer to the respective connector documentation, such as [Jdbc sink](https://seatunnel.apache.org/docs/connector-v2/sink/Jdbc#data_save_mode-enum). -## How do I write a configuration item in multi-line text in the configuration file? +## Does SeaTunnel support exactly-once consistency? +SeaTunnel supports exactly-once consistency for some data sources, such as MySQL and PostgreSQL, ensuring data consistency during integration. Note that exactly-once consistency depends on the capabilities of the underlying database. -When a configured text is very long and you want to wrap it, you can use three double quotes to indicate its start and end: +## Can SeaTunnel execute scheduled tasks? +You can use Linux cron jobs to achieve periodic data integration, or leverage scheduling tools like Apache DolphinScheduler or Apache Airflow to manage complex scheduled tasks. -``` -var = """ - whatever you want -""" -``` +## I encountered an issue with SeaTunnel that I cannot resolve. What should I do? +If you encounter issues with SeaTunnel, here are a few ways to get help: +1. Search the [Issue List](https://github.com/apache/seatunnel/issues) or [Mailing List](https://lists.apache.org/list.html?dev@seatunnel.apache.org) to see if someone else has faced a similar issue. +2. If you cannot find an answer, reach out to the community through [these methods](https://github.com/apache/seatunnel#contact-us). -## How do I implement variable substitution for multi-line text? +## How do I declare variables? +Would you like to declare a variable in SeaTunnel's configuration and dynamically replace it at runtime? This feature is commonly used in both scheduled and ad-hoc offline processing to replace time, date, or other variables. Here's an example: -It is a little troublesome to do variable substitution in multi-line text, because the variable cannot be included in three double quotation marks: - -``` -var = """ -your string 1 -"""${you_var}""" your string 2""" -``` - -Refer to: [lightbend/config#456](https://github.com/lightbend/config/issues/456). - -## Is SeaTunnel supported in Azkaban, Oozie, DolphinScheduler? - -Of course! See the screenshot below: - -![workflow.png](../images/workflow.png) - -![azkaban.png](../images/azkaban.png) - -## Does SeaTunnel have a case for configuring multiple sources, such as configuring elasticsearch and hdfs in source at the same time? - -``` -env { - ... -} - -source { - hdfs { ... } - elasticsearch { ... } - jdbc {...} -} +Define the variable in the configuration. For example, in an SQL transformation (the value in any "key = value" pair in the configuration file can be replaced with variables): +```plaintext +... transform { - ... -} - -sink { - elasticsearch { ... } -} -``` - -## Are there any HBase plugins? - -There is a HBase input plugin. You can download it from here: https://github.com/garyelephant/waterdrop-input-hbase . - -## How can I use SeaTunnel to write data to Hive? - -``` -env { - spark.sql.catalogImplementation = "hive" - spark.hadoop.hive.exec.dynamic.partition = "true" - spark.hadoop.hive.exec.dynamic.partition.mode = "nonstrict" -} - -source { - sql = "insert into ..." -} - -sink { - // The data has been written to hive through the sql source. This is just a placeholder, it does not actually work. - stdout { - limit = 1 - } -} -``` - -In addition, SeaTunnel has implemented a `Hive` output plugin after version `1.5.7` in `1.x` branch; in `2.x` branch. The Hive plugin for the Spark engine has been supported from version `2.0.5`: https://github.com/apache/seatunnel/issues/910. - -## How does SeaTunnel write multiple instances of ClickHouse to achieve load balancing? - -1. Write distributed tables directly (not recommended) - -2. Add a proxy or domain name (DNS) in front of multiple instances of ClickHouse: - - ``` - { - output { - clickhouse { - host = "ck-proxy.xx.xx:8123" - # Local table - table = "table_name" - } - } - } - ``` -3. Configure multiple instances in the configuration: - - ``` - { - output { - clickhouse { - host = "ck1:8123,ck2:8123,ck3:8123" - # Local table - table = "table_name" - } - } - } - ``` -4. Use cluster mode: - - ``` - { - output { - clickhouse { - # Configure only one host - host = "ck1:8123" - cluster = "clickhouse_cluster_name" - # Local table - table = "table_name" - } - } - } - ``` - -## How can I solve OOM when SeaTunnel consumes Kafka? - -In most cases, OOM is caused by not having a rate limit for consumption. The solution is as follows: - -For the current limit of Spark consumption of Kafka: - -1. Suppose the number of partitions of Kafka `Topic 1` you consume with KafkaStream = N. - -2. Assuming that the production speed of the message producer (Producer) of `Topic 1` is K messages/second, the speed of write messages to the partition must be uniform. - -3. Suppose that, after testing, it is found that the processing capacity of Spark Executor per core per second is M. - -The following conclusions can be drawn: - -1. If you want to make Spark's consumption of `Topic 1` keep up with its production speed, then you need `spark.executor.cores` * `spark.executor.instances` >= K / M - -2. When a data delay occurs, if you want the consumption speed not to be too fast, resulting in spark executor OOM, then you need to configure `spark.streaming.kafka.maxRatePerPartition` <= (`spark.executor.cores` * `spark.executor.instances`) * M / N - -3. In general, both M and N are determined, and the conclusion can be drawn from 2: The size of `spark.streaming.kafka.maxRatePerPartition` is positively correlated with the size of `spark.executor.cores` * `spark.executor.instances`, and it can be increased while increasing the resource `maxRatePerPartition` to speed up consumption. - -![Kafka](../images/kafka.png) - -## How can I solve the Error `Exception in thread "main" java.lang.NoSuchFieldError: INSTANCE`? - -The reason is that the version of httpclient.jar that comes with the CDH version of Spark is lower, and The httpclient version that ClickHouse JDBC is based on is 4.5.2, and the package versions conflict. The solution is to replace the jar package that comes with CDH with the httpclient-4.5.2 version. - -## The default JDK of my Spark cluster is JDK7. After I install JDK8, how can I specify that SeaTunnel starts with JDK8? - -In SeaTunnel's config file, specify the following configuration: - -```shell -spark { - ... - spark.executorEnv.JAVA_HOME="/your/java_8_home/directory" - spark.yarn.appMasterEnv.JAVA_HOME="/your/java_8_home/directory" - ... + Sql { + query = "select * from user_view where city ='${city}' and dt = '${date}'" + } } +... ``` -## What should I do if OOM always appears when running SeaTunnel in Spark local[*] mode? - -If you run in local mode, you need to modify the `start-seatunnel.sh` startup script. After `spark-submit`, add a parameter `--driver-memory 4g` . Under normal circumstances, local mode is not used in the production environment. Therefore, this parameter generally does not need to be set during On YARN. See: [Application Properties](https://spark.apache.org/docs/latest/configuration.html#application-properties) for details. - -## Where can I place self-written plugins or third-party jdbc.jars to be loaded by SeaTunnel? - -Place the Jar package under the specified structure of the plugins directory: +To start SeaTunnel in Zeta Local mode with variables: ```bash -cd SeaTunnel -mkdir -p plugins/my_plugins/lib -cp third-part.jar plugins/my_plugins/lib +$SEATUNNEL_HOME/bin/seatunnel.sh \ +-c $SEATUNNEL_HOME/config/your_app.conf \ +-m local[2] \ +-i city=Singapore \ +-i date=20231110 ``` -`my_plugins` can be any string. - -## How do I configure logging-related parameters in SeaTunnel-V1(Spark)? - -There are three ways to configure logging-related parameters (such as Log Level): - -- [Not recommended] Change the default `$SPARK_HOME/conf/log4j.properties`. - - This will affect all programs submitted via `$SPARK_HOME/bin/spark-submit`. -- [Not recommended] Modify logging related parameters directly in the Spark code of SeaTunnel. - - This is equivalent to hardcoding, and each change needs to be recompiled. -- [Recommended] Use the following methods to change the logging configuration in the SeaTunnel configuration file (The change only takes effect if SeaTunnel >= 1.5.5 ): - - ``` - env { - spark.driver.extraJavaOptions = "-Dlog4j.configuration=file:/log4j.properties" - spark.executor.extraJavaOptions = "-Dlog4j.configuration=file:/log4j.properties" - } - source { - ... - } - transform { - ... - } - sink { - ... - } - ``` - -The contents of the log4j configuration file for reference are as follows: - -``` -$ cat log4j.properties -log4j.rootLogger=ERROR, console +Use the `-i` or `--variable` parameter with `key=value` to specify the variable's value, where `key` matches the variable name in the configuration. For details, see: [SeaTunnel Variable Configuration](https://seatunnel.apache.org/docs/concept/config) -# set the log level for these components -log4j.logger.org=ERROR -log4j.logger.org.apache.spark=ERROR -log4j.logger.org.spark-project=ERROR -log4j.logger.org.apache.hadoop=ERROR -log4j.logger.io.netty=ERROR -log4j.logger.org.apache.zookeeper=ERROR +## How can I write multi-line text in the configuration file? +If the text is long and needs to be wrapped, you can use triple quotes to indicate the beginning and end: -# add a ConsoleAppender to the logger stdout to write to the console -log4j.appender.console=org.apache.log4j.ConsoleAppender -log4j.appender.console.layout=org.apache.log4j.PatternLayout -# use a simple message format -log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +```plaintext +var = """ +Apache SeaTunnel is a +next-generation high-performance, +distributed, massive data integration tool. +""" ``` -## How do I configure logging related parameters in SeaTunnel-V2(Spark, Flink)? - -Currently, they cannot be set directly. you need to modify the SeaTunnel startup script. The relevant parameters are specified in the task submission command. For specific parameters, please refer to the official documents: - -- Spark official documentation: http://spark.apache.org/docs/latest/configuration.html#configuring-logging -- Flink official documentation: https://ci.apache.org/projects/flink/flink-docs-stable/monitoring/logging.html - -Reference: - -https://stackoverflow.com/questions/27781187/how-to-stop-info-messages-displaying-on-spark-console - -http://spark.apache.org/docs/latest/configuration.html#configuring-logging - -https://medium.com/@iacomini.riccardo/spark-logging-configuration-in-yarn-faf5ba5fdb01 - -## How do I configure logging related parameters of SeaTunnel-E2E Test? - -The log4j configuration file of `seatunnel-e2e` existed in `seatunnel-e2e/seatunnel-e2e-common/src/test/resources/log4j2.properties`. You can modify logging related parameters directly in the configuration file. - -For example, if you want to output more detailed logs of E2E Test, just downgrade `rootLogger.level` in the configuration file. - -## Error when writing to ClickHouse: ClassCastException - -In SeaTunnel, the data type will not be actively converted. After the Input reads the data, the corresponding -Schema. When writing ClickHouse, the field type needs to be strictly matched, and the mismatch needs to be resolved. +## How do I perform variable substitution in multi-line text? +Performing variable substitution in multi-line text can be tricky because variables cannot be enclosed within triple quotes: -Data conversion can be achieved through the following two plugins: - -1. Filter Convert plugin -2. Filter Sql plugin - -Detailed data type conversion reference: [ClickHouse Data Type Check List](https://interestinglab.github.io/seatunnel-docs/#/en/configuration/output-plugins/Clickhouse?id=clickhouse-data-type-check-list) - -Refer to issue:[#488](https://github.com/apache/seatunnel/issues/488) [#382](https://github.com/apache/seatunnel/issues/382). - -## How does SeaTunnel access kerberos-authenticated HDFS, YARN, Hive and other resources? - -Please refer to: [#590](https://github.com/apache/seatunnel/issues/590). - -## How do I troubleshoot NoClassDefFoundError, ClassNotFoundException and other issues? - -There is a high probability that there are multiple different versions of the corresponding Jar package class loaded in the Java classpath, because of the conflict of the load order, not because the Jar is really missing. Modify this SeaTunnel startup command, adding the following parameters to the spark-submit submission section, and debug in detail through the output log. - -``` -spark-submit --verbose - ... - --conf 'spark.driver.extraJavaOptions=-verbose:class' - --conf 'spark.executor.extraJavaOptions=-verbose:class' - ... +```plaintext +var = """ +your string 1 +"""${your_var}""" your string 2""" ``` -## I want to learn the source code of SeaTunnel. Where should I start? - -SeaTunnel has a completely abstract and structured code implementation, and many people have chosen SeaTunnel As a way to learn Spark. You can learn the source code from the main program entry: SeaTunnel.java - -## When SeaTunnel developers develop their own plugins, do they need to understand the SeaTunnel code? Should these plugins be integrated into the SeaTunnel project? - -The plugin developed by the developer has nothing to do with the SeaTunnel project and does not need to include your plugin code. +For more details, see: [lightbend/config#456](https://github.com/lightbend/config/issues/456). -The plugin can be completely independent from SeaTunnel project, so you can write it using Java, Scala, Maven, sbt, Gradle, or whatever you want. This is also the way we recommend developers to develop plugins. -## When I import a project, the compiler has the exception "class not found `org.apache.seatunnel.shade.com.typesafe.config.Config`" +## Where should I start if I want to learn SeaTunnel source code? +SeaTunnel features a highly abstracted and well-structured architecture, making it an excellent choice for learning big data architecture. You can start by exploring and debugging the `seatunnel-examples` module: `SeaTunnelEngineLocalExample.java`. For more details, refer to the [SeaTunnel Contribution Guide](https://seatunnel.apache.org/docs/contribution/setup). -Run `mvn install` first. In the `seatunnel-config/seatunnel-config-base` subproject, the package `com.typesafe.config` has been relocated to `org.apache.seatunnel.shade.com.typesafe.config` and installed to the maven local repository in the subproject `seatunnel-config/seatunnel-config-shade`. +## Do I need to understand all of SeaTunnel’s source code if I want to develop my own source, sink, or transform? +No, you only need to focus on the interfaces for source, sink, and transform. If you want to develop your own connector (Connector V2) for the SeaTunnel API, refer to the **[Connector Development Guide](https://github.com/apache/seatunnel/blob/dev/seatunnel-connectors-v2/README.md)**. diff --git a/docs/en/other-engine/flink.md b/docs/en/other-engine/flink.md index 8a77fbfc241..b6e7d6af77e 100644 --- a/docs/en/other-engine/flink.md +++ b/docs/en/other-engine/flink.md @@ -37,7 +37,7 @@ env { source { FakeSource { row.num = 16 - result_table_name = "fake_table" + plugin_output = "fake_table" schema = { fields { c_map = "map" diff --git a/docs/en/seatunnel-engine/checkpoint-storage.md b/docs/en/seatunnel-engine/checkpoint-storage.md index 7027f8067fb..19c617e0154 100644 --- a/docs/en/seatunnel-engine/checkpoint-storage.md +++ b/docs/en/seatunnel-engine/checkpoint-storage.md @@ -14,7 +14,7 @@ Checkpoint Storage is a storage mechanism for storing checkpoint data. SeaTunnel Engine supports the following checkpoint storage types: -- HDFS (OSS,S3,HDFS,LocalFile) +- HDFS (OSS,COS,S3,HDFS,LocalFile) - LocalFile (native), (it's deprecated: use Hdfs(LocalFile) instead. We use the microkernel design pattern to separate the checkpoint storage module from the engine. This allows users to implement their own checkpoint storage modules. @@ -73,6 +73,42 @@ For additional reading on the Hadoop Credential Provider API, you can see: [Cred For Aliyun OSS Credential Provider implements, you can see: [Auth Credential Providers](https://github.com/aliyun/aliyun-oss-java-sdk/tree/master/src/main/java/com/aliyun/oss/common/auth) +#### COS + +Tencent COS based hdfs-file you can refer [Hadoop COS Docs](https://hadoop.apache.org/docs/stable/hadoop-cos/cloud-storage/) to config COS. + +Except when interacting with cos buckets, the cos client needs the credentials needed to interact with buckets. +The client supports multiple authentication mechanisms and can be configured as to which mechanisms to use, and their order of use. Custom implementations of com.qcloud.cos.auth.COSCredentialsProvider may also be used. +If you used SimpleCredentialsProvider (can be obtained from the Tencent Cloud API Key Management), these consist of an access key, a secret key. +You can config like this: + +```yaml +seatunnel: + engine: + checkpoint: + interval: 6000 + timeout: 7000 + storage: + type: hdfs + max-retained: 3 + plugin-config: + storage.type: cos + cos.bucket: cosn://your-bucket + fs.cosn.credentials.provider: org.apache.hadoop.fs.cosn.auth.SimpleCredentialsProvider + fs.cosn.userinfo.secretId: your-secretId + fs.cosn.userinfo.secretKey: your-secretKey + fs.cosn.bucket.region: your-region +``` + +For additional reading on the Hadoop Credential Provider API, you can see: [Credential Provider API](https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/CredentialProviderAPI.html). + +For additional COS configuration, you can see: [Tencent Hadoop-COS Docs](https://doc.fincloud.tencent.cn/tcloud/Storage/COS/846365/hadoop) + +Please add the following jar to the lib directory: +- [hadoop-cos-3.4.1.jar](https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-cos/3.4.1) +- [cos_api-bundle-5.6.69.jar](https://mvnrepository.com/artifact/com.qcloud/cos_api-bundle/5.6.69) +- [hadoop-shaded-guava-1.1.1.jar](https://mvnrepository.com/artifact/org.apache.hadoop.thirdparty/hadoop-shaded-guava/1.1.1) + #### S3 S3 based hdfs-file you can refer [hadoop s3 docs](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html) to config s3. diff --git a/docs/en/seatunnel-engine/download-seatunnel.md b/docs/en/seatunnel-engine/download-seatunnel.md index 48b5ed63a54..12b169e482c 100644 --- a/docs/en/seatunnel-engine/download-seatunnel.md +++ b/docs/en/seatunnel-engine/download-seatunnel.md @@ -20,7 +20,7 @@ Go to the [Seatunnel Download Page](https://seatunnel.apache.org/download) to do Or you can also download it through the terminal. ```shell -export version="2.3.8" +export version="2.3.9" wget "https://archive.apache.org/dist/seatunnel/${version}/apache-seatunnel-${version}-bin.tar.gz" tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" ``` @@ -33,10 +33,10 @@ Starting from the 2.2.0-beta version, the binary package no longer provides the sh bin/install-plugin.sh ``` -If you need a specific connector version, taking 2.3.8 as an example, you need to execute the following command. +If you need a specific connector version, taking 2.3.9 as an example, you need to execute the following command. ```bash -sh bin/install-plugin.sh 2.3.8 +sh bin/install-plugin.sh 2.3.9 ``` Usually you don't need all the connector plugins, so you can specify the plugins you need through configuring `config/plugin_config`, for example, if you only need the `connector-console` plugin, then you can modify the plugin.properties configuration file as follows. diff --git a/docs/en/seatunnel-engine/hybrid-cluster-deployment.md b/docs/en/seatunnel-engine/hybrid-cluster-deployment.md index ebada38957f..ac072c494df 100644 --- a/docs/en/seatunnel-engine/hybrid-cluster-deployment.md +++ b/docs/en/seatunnel-engine/hybrid-cluster-deployment.md @@ -43,7 +43,7 @@ Therefore, the SeaTunnel Engine can implement cluster HA without using other ser `backup count` is a parameter that defines the number of synchronous backups. For example, if it is set to 1, the backup of the partition will be placed on one other member. If it is set to 2, it will be placed on two other members. -We recommend that the value of `backup count` be `min(1, max(5, N/2))`. `N` is the number of cluster nodes. +We recommend that the value of `backup count` be `max(1, min(5, N/2))`. `N` is the number of cluster nodes. ```yaml seatunnel: @@ -127,7 +127,7 @@ seatunnel: This configuration primarily addresses the issue of resource leakage caused by constantly creating and attempting to destroy the class loader. If you encounter exceptions related to metaspace overflow, you can try enabling this configuration. To reduce the frequency of class loader creation, after enabling this configuration, SeaTunnel will not attempt to release the corresponding class loader when a job is completed, allowing it to be used by subsequent jobs. This is more effective when the number of Source/Sink connectors used in the running job is not excessive. -The default value is false. +The default value is true. Example ```yaml @@ -136,6 +136,24 @@ seatunnel: classloader-cache-mode: true ``` +### 4.6 Job Scheduling Strategy + +When resources are insufficient, the job scheduling strategy can be configured in the following two modes: + +1. `WAIT`: Wait for resources to be available. + +2. `REJECT`: Reject the job, default value. + +Example + +```yaml +seatunnel: + engine: + job-schedule-strategy: WAIT +``` + +When `dynamic-slot: true` is used, the `job-schedule-strategy: WAIT` configuration will become invalid and will be forcibly changed to `job-schedule-strategy: REJECT`, because this parameter is meaningless in dynamic slots. + ## 5. Configure The SeaTunnel Engine Network Service All SeaTunnel Engine network-related configurations are in the `hazelcast.yaml` file. @@ -319,4 +337,4 @@ Now that the cluster is deployed, you can complete the submission and management ### 8.2 Submit Jobs With The REST API -The SeaTunnel Engine provides a REST API for submitting and managing jobs. For more information, please refer to [REST API](rest-api.md) \ No newline at end of file +The SeaTunnel Engine provides a REST API for submitting and managing jobs. For more information, please refer to [REST API V2](rest-api-v2.md) diff --git a/docs/en/seatunnel-engine/local-mode-deployment.md b/docs/en/seatunnel-engine/local-mode-deployment.md index 92df4220b68..5418477c523 100644 --- a/docs/en/seatunnel-engine/local-mode-deployment.md +++ b/docs/en/seatunnel-engine/local-mode-deployment.md @@ -27,6 +27,16 @@ In this mode, you only need to copy the downloaded and created installation pack $SEATUNNEL_HOME/bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template -m local ``` +### Configure The JVM Options For Local Mode + +Local Mode supports two methods for setting JVM options: + +1. Add the JVM options to `$SEATUNNEL_HOME/config/jvm_client_options`. + + Modify the JVM parameters in the `$SEATUNNEL_HOME/config/jvm_client_options` file. Please note that the JVM parameters in this file will be applied to all jobs submitted using `seatunnel.sh`, including Local Mode and Cluster Mode. + +2. Add JVM options when starting the Local Mode. For example, `$SEATUNNEL_HOME/bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template -m local -DJvmOption="-Xms2G -Xmx2G"` + ## Job Operations Jobs submitted in local mode will run in the process that submitted the job, and the process will exit when the job is completed. If you want to abort the job, you only need to exit the process that submitted the job. The job's runtime logs will be output to the standard output of the process that submitted the job. diff --git a/docs/en/seatunnel-engine/logging.md b/docs/en/seatunnel-engine/logging.md index 7c827887b82..be0bc12f0a2 100644 --- a/docs/en/seatunnel-engine/logging.md +++ b/docs/en/seatunnel-engine/logging.md @@ -30,7 +30,7 @@ The MDC is propagated by slf4j to the logging backend which usually adds it to t Log4j 2 is controlled using property files. -The SeaTunnel Engine distribution ships with the following log4j properties files in the `confing` directory, which are used automatically if Log4j 2 is enabled: +The SeaTunnel Engine distribution ships with the following log4j properties files in the `config` directory, which are used automatically if Log4j 2 is enabled: - `log4j2_client.properties`: used by the command line client (e.g., `seatunnel.sh`) - `log4j2.properties`: used for SeaTunnel Engine server processes (e.g., `seatunnel-cluster.sh`) @@ -80,6 +80,36 @@ appender.file.layout.pattern = [%X{ST-JID}] %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%- SeaTunnel Engine automatically integrates Log framework bridge, allowing existing applications that work against Log4j1/Logback classes to continue working. +### Query Logs via REST API + +SeaTunnel provides an API for querying logs. + +**Usage examples:** +- Retrieve logs for all nodes with `jobId` of `733584788375666689`: `http://localhost:8080/logs/733584788375666689` +- Retrieve the log list for all nodes: `http://localhost:8080/logs` +- Retrieve the log list for all nodes in JSON format: `http://localhost:8080/logs?format=json` +- Retrieve log file content: `http://localhost:8080/logs/job-898380162133917698.log` + +For more details, please refer to the [REST-API](rest-api-v2.md). + +## SeaTunnel Log Configuration + +### Scheduled deletion of old logs + +SeaTunnel supports scheduled deletion of old log files to prevent disk space exhaustion. You can add the following configuration in the `seatunnel.yml` file: + +```yaml +seatunnel: + engine: + history-job-expire-minutes: 1440 + telemetry: + logs: + scheduled-deletion-enable: true +``` + +- `history-job-expire-minutes`: Sets the retention time for historical job data and logs (in minutes). The system will automatically clear expired job information and log files after the specified period. +- `scheduled-deletion-enable`: Enable scheduled cleanup, with default value of `true`. The system will automatically delete relevant log files when job expiration time, as defined by `history-job-expire-minutes`, is reached. If this feature is disabled, logs will remain permanently on disk, requiring manual management, which may affect disk space usage. It is recommended to configure this setting based on specific needs. + ## Best practices for developers You can create an SLF4J logger by calling `org.slf4j.LoggerFactory#LoggerFactory.getLogger` with the Class of your class as an argument. diff --git a/docs/en/seatunnel-engine/resource-isolation.md b/docs/en/seatunnel-engine/resource-isolation.md index 5b9e1ff0ba0..4b68401ee15 100644 --- a/docs/en/seatunnel-engine/resource-isolation.md +++ b/docs/en/seatunnel-engine/resource-isolation.md @@ -4,46 +4,46 @@ sidebar_position: 9 # Resource Isolation -After version 2.3.6. SeaTunnel can add `tag` to each worker node, when you submit job you can use `tag_filter` to filter the node you want run this job. +SeaTunnel can add `tag` to each worker node, when you submit job you can use `tag_filter` to filter the node you want run this job. -## How To Archive This: +## Configuration 1. update the config in `hazelcast.yaml`, -```yaml -hazelcast: - cluster-name: seatunnel - network: - rest-api: - enabled: true - endpoint-groups: - CLUSTER_WRITE: + ```yaml + hazelcast: + cluster-name: seatunnel + network: + rest-api: enabled: true - DATA: - enabled: true - join: - tcp-ip: - enabled: true - member-list: - - localhost - port: - auto-increment: false - port: 5801 - properties: - hazelcast.invocation.max.retry.count: 20 - hazelcast.tcp.join.port.try.count: 30 - hazelcast.logging.type: log4j2 - hazelcast.operation.generic.thread.count: 50 - member-attributes: - group: - type: string - value: platform - team: - type: string - value: team1 -``` - -In this config, we specify the tag by `member-attributes`, the node has `group=platform, team=team1` tags. + endpoint-groups: + CLUSTER_WRITE: + enabled: true + DATA: + enabled: true + join: + tcp-ip: + enabled: true + member-list: + - localhost + port: + auto-increment: false + port: 5801 + properties: + hazelcast.invocation.max.retry.count: 20 + hazelcast.tcp.join.port.try.count: 30 + hazelcast.logging.type: log4j2 + hazelcast.operation.generic.thread.count: 50 + member-attributes: + group: + type: string + value: platform + team: + type: string + value: team1 + ``` + + In this config, we specify the tag by `member-attributes`, the node has `group=platform, team=team1` tags. 2. add `tag_filter` to your job config @@ -58,7 +58,7 @@ env { } source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" parallelism = 1 schema = { fields { @@ -71,18 +71,18 @@ transform { } sink { console { - source_table_name="fake" + plugin_input="fake" } } ``` -**Notice:** -- If not set `tag_filter` in job config, it will random choose the node in all active nodes. -- When you add multiple tag in `tag_filter`, it need all key exist and value match. if all node not match, you will get `NoEnoughResourceException` exception. + **Notice:** + - If not set `tag_filter` in job config, it will random choose the node in all active nodes. + - When you add multiple tag in `tag_filter`, it need all key exist and value match. if all node not match, you will get `NoEnoughResourceException` exception. -![img.png](../../images/resource-isolation.png) + ![img.png](../../images/resource-isolation.png) 3. update running node tags by rest api (optional) -for more information, please refer to [Update the tags of running node](https://seatunnel.apache.org/docs/seatunnel-engine/rest-api/) + for more information, please refer to [Update the tags of running node](rest-api-v2.md) diff --git a/docs/en/seatunnel-engine/rest-api.md b/docs/en/seatunnel-engine/rest-api-v1.md similarity index 81% rename from docs/en/seatunnel-engine/rest-api.md rename to docs/en/seatunnel-engine/rest-api-v1.md index a1ddc76e539..8859faa32f1 100644 --- a/docs/en/seatunnel-engine/rest-api.md +++ b/docs/en/seatunnel-engine/rest-api-v1.md @@ -2,7 +2,13 @@ sidebar_position: 11 --- -# RESTful API +# RESTful API V1 + +:::caution warn + +It is recommended to use the v2 version of the Rest API. The v1 version is deprecated and will be removed in the future. + +::: SeaTunnel has a monitoring API that can be used to query status and statistics of running jobs, as well as recent completed jobs. The monitoring API is a RESTful API that accepts HTTP requests and responds with JSON data. @@ -115,10 +121,19 @@ network: }, "createTime": "", "jobDag": { - "vertices": [ + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } ], - "edges": [ - ] + "pipelineEdges": {} }, "pluginJarsUrls": [ ], @@ -155,10 +170,19 @@ network: "jobStatus": "", "createTime": "", "jobDag": { - "vertices": [ + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } ], - "edges": [ - ] + "pipelineEdges": {} }, "metrics": { "sourceReceivedCount": "", @@ -212,10 +236,19 @@ This API has been deprecated, please use /hazelcast/rest/maps/job-info/:jobId in "jobStatus": "", "createTime": "", "jobDag": { - "vertices": [ + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } ], - "edges": [ - ] + "pipelineEdges": {} }, "metrics": { "SourceReceivedCount": "", @@ -283,7 +316,21 @@ When we can't get the job info, the response will be: "errorMsg": null, "createTime": "", "finishTime": "", - "jobDag": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, "metrics": "" } ] @@ -384,7 +431,7 @@ When we can't get the job info, the response will be: "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 100, "schema": { "fields": { @@ -400,7 +447,7 @@ When we can't get the job info, the response will be: "sink": [ { "plugin_name": "Console", - "source_table_name": ["fake"] + "plugin_input": ["fake"] } ] } @@ -447,7 +494,7 @@ When we can't get the job info, the response will be: "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 1000, "schema": { "fields": { @@ -463,7 +510,7 @@ When we can't get the job info, the response will be: "sink": [ { "plugin_name": "Console", - "source_table_name": ["fake"] + "plugin_input": ["fake"] } ] }, @@ -478,7 +525,7 @@ When we can't get the job info, the response will be: "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 1000, "schema": { "fields": { @@ -494,7 +541,7 @@ When we can't get the job info, the response will be: "sink": [ { "plugin_name": "Console", - "source_table_name": ["fake"] + "plugin_input": ["fake"] } ] } @@ -603,7 +650,7 @@ For more information about customize encryption, please refer to the documentati "age": "int" } }, - "result_table_name": "fake", + "plugin_output": "fake", "parallelism": 1, "hostname": "127.0.0.1", "username": "seatunnel", @@ -643,7 +690,7 @@ For more information about customize encryption, please refer to the documentati "age": "int" } }, - "result_table_name": "fake", + "plugin_output": "fake", "parallelism": 1, "hostname": "127.0.0.1", "username": "c2VhdHVubmVs", @@ -729,3 +776,70 @@ If the parameter is an empty `Map` object, it means that the tags of the current ``` +------------------------------------------------------------------------------------------ + +### Get All Node Log Content + +
+ GET /hazelcast/rest/maps/logs/:jobId (Returns a list of logs.) + +#### Request Parameters + +#### Parameters (Add in the `params` field of the request body) + +> | Parameter Name | Required | Type | Description | +> |----------------------|------------|---------|---------------------------------| +> | jobId | optional | string | job id | + +When `jobId` is empty, it returns log information for all nodes; otherwise, it returns the log list of the specified `jobId` across all nodes. + +#### Response + +Returns a list of logs and content from the requested nodes. + +#### Get All Log Files List + +If you'd like to view the log list first, you can use a `GET` request to retrieve the log list: +`http://localhost:5801/hazelcast/rest/maps/logs?format=json` + +```json +[ + { + "node": "localhost:5801", + "logLink": "http://localhost:5801/hazelcast/rest/maps/logs/job-899485770241277953.log", + "logName": "job-899485770241277953.log" + }, + { + "node": "localhost:5801", + "logLink": "http://localhost:5801/hazelcast/rest/maps/logs/job-899470314109468673.log", + "logName": "job-899470314109468673.log" + } +] +``` + +The supported formats are `json` and `html`, with `html` as the default. + +#### Examples + +Retrieve logs for all nodes with the `jobId` of `733584788375666689`: `http://localhost:5801/hazelcast/rest/maps/logs/733584788375666689` +Retrieve the log list for all nodes: `http://localhost:5801/hazelcast/rest/maps/logs` +Retrieve the log list for all nodes in JSON format: `http://localhost:5801/hazelcast/rest/maps/logs?format=json` +Retrieve log file content: `http://localhost:5801/hazelcast/rest/maps/logs/job-898380162133917698.log` + +
+ +### Get Log Content from a Single Node + +
+ GET /hazelcast/rest/maps/log (Returns a list of logs.) + +#### Response + +Returns a list of logs from the requested node. + +#### Examples + +To get a list of logs from the current node: `http://localhost:5801/hazelcast/rest/maps/log` +To get the content of a log file: `http://localhost:5801/hazelcast/rest/maps/log/job-898380162133917698.log` + +
\ No newline at end of file diff --git a/docs/en/seatunnel-engine/rest-api-v2.md b/docs/en/seatunnel-engine/rest-api-v2.md new file mode 100644 index 00000000000..8a5e3a8d7d3 --- /dev/null +++ b/docs/en/seatunnel-engine/rest-api-v2.md @@ -0,0 +1,847 @@ +--- +sidebar_position: 12 +--- + +# RESTful API V2 + +SeaTunnel has a monitoring API that can be used to query status and statistics of running jobs, as well as recent +completed jobs. The monitoring API is a RESTful API that accepts HTTP requests and responds with JSON data. + +## Overview + +The v2 version of the api uses jetty support. It is the same as the interface specification of v1 version +, you can specify the port and context-path by modifying the configuration items in `seatunnel.yaml`, +you can configure `enable-dynamic-port` to enable dynamic ports (the default port is accumulated starting from `port`), and the default is closed, +If enable-dynamic-port is true, We will use the unused port in the range within the range of `port` and `port` + `port-range`, default range is 100 + +```yaml + +seatunnel: + engine: + http: + enable-http: true + port: 8080 + enable-dynamic-port: false + port-range: 100 +``` + +Context-path can also be configured as follows: + +```yaml + +seatunnel: + engine: + http: + enable-http: true + port: 8080 + context-path: /seatunnel +``` + +## API reference + +### Returns an overview over the Zeta engine cluster. + +
+ GET /overview?tag1=value1&tag2=value2 (Returns an overview over the Zeta engine cluster.) + +#### Parameters + +> | name | type | data type | description | +> |----------|----------|-----------|------------------------------------------------------------------------------------------------------| +> | tag_name | optional | string | the tags filter, you can add tag filter to get those matched worker count, and slot on those workers | + +#### Responses + +```json +{ + "projectVersion":"2.3.5-SNAPSHOT", + "gitCommitAbbrev":"DeadD0d0", + "totalSlot":"0", + "unassignedSlot":"0", + "works":"1", + "runningJobs":"0", + "finishedJobs":"0", + "failedJobs":"0", + "cancelledJobs":"0" +} +``` + +**Notes:** +- If you use `dynamic-slot`, the `totalSlot` and `unassignedSlot` always be `0`. when you set it to fix slot number, it will return the correct total and unassigned slot number +- If the url has tag filter, the `works`, `totalSlot` and `unassignedSlot` will return the result on the matched worker. but the job related metric will always return the cluster level information. + +
+ +------------------------------------------------------------------------------------------ + +### Returns An Overview And State Of All Jobs + +
+ GET /running-jobs (Returns an overview over all jobs and their current state.) + +#### Parameters + +#### Responses + +```json +[ + { + "jobId": "", + "jobName": "", + "jobStatus": "", + "envOptions": { + }, + "createTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "pluginJarsUrls": [ + ], + "isStartWithSavePoint": false, + "metrics": { + "sourceReceivedCount": "", + "sinkWriteCount": "" + } + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### Return Details Of A Job + +
+ GET /job-info/:jobId (Return details of a job. ) + +#### Parameters + +> | name | type | data type | description | +> |-------|----------|-----------|-------------| +> | jobId | required | long | job id | + +#### Responses + +```json +{ + "jobId": "", + "jobName": "", + "jobStatus": "", + "createTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "metrics": { + "sourceReceivedCount": "", + "sinkWriteCount": "" + }, + "finishedTime": "", + "errorMsg": null, + "envOptions": { + }, + "pluginJarsUrls": [ + ], + "isStartWithSavePoint": false +} +``` + +`jobId`, `jobName`, `jobStatus`, `createTime`, `jobDag`, `metrics` always be returned. +`envOptions`, `pluginJarsUrls`, `isStartWithSavePoint` will return when job is running. +`finishedTime`, `errorMsg` will return when job is finished. + +When we can't get the job info, the response will be: + +```json +{ + "jobId" : "" +} +``` + +
+ +------------------------------------------------------------------------------------------ + +### Return Details Of A Job + +This API has been deprecated, please use /job-info/:jobId instead + +
+ GET /running-job/:jobId (Return details of a job. ) + +#### Parameters + +> | name | type | data type | description | +> |-------|----------|-----------|-------------| +> | jobId | required | long | job id | + +#### Responses + +```json +{ + "jobId": "", + "jobName": "", + "jobStatus": "", + "createTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "metrics": { + "SourceReceivedCount": "", + "SourceReceivedQPS": "", + "SourceReceivedBytes": "", + "SourceReceivedBytesPerSeconds": "", + "SinkWriteCount": "", + "SinkWriteQPS": "", + "SinkWriteBytes": "", + "SinkWriteBytesPerSeconds": "", + "TableSourceReceivedCount": {}, + "TableSourceReceivedBytes": {}, + "TableSourceReceivedBytesPerSeconds": {}, + "TableSourceReceivedQPS": {}, + "TableSinkWriteCount": {}, + "TableSinkWriteQPS": {}, + "TableSinkWriteBytes": {}, + "TableSinkWriteBytesPerSeconds": {} + }, + "finishedTime": "", + "errorMsg": null, + "envOptions": { + }, + "pluginJarsUrls": [ + ], + "isStartWithSavePoint": false +} +``` + +`jobId`, `jobName`, `jobStatus`, `createTime`, `jobDag`, `metrics` always be returned. +`envOptions`, `pluginJarsUrls`, `isStartWithSavePoint` will return when job is running. +`finishedTime`, `errorMsg` will return when job is finished. + +When we can't get the job info, the response will be: + +```json +{ + "jobId" : "" +} +``` + +
+ +------------------------------------------------------------------------------------------ + +### Return All Finished Jobs Info + +
+ GET /finished-jobs/:state (Return all finished Jobs Info.) + +#### Parameters + +> | name | type | data type | description | +> |-------|----------|-----------|------------------------------------------------------------------| +> | state | optional | string | finished job status. `FINISHED`,`CANCELED`,`FAILED`,`UNKNOWABLE` | + +#### Responses + +```json +[ + { + "jobId": "", + "jobName": "", + "jobStatus": "", + "errorMsg": null, + "createTime": "", + "finishTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "metrics": "" + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### Returns System Monitoring Information + +
+ GET /system-monitoring-information (Returns system monitoring information.) + +#### Parameters + +#### Responses + +```json +[ + { + "processors":"8", + "physical.memory.total":"16.0G", + "physical.memory.free":"16.3M", + "swap.space.total":"0", + "swap.space.free":"0", + "heap.memory.used":"135.7M", + "heap.memory.free":"440.8M", + "heap.memory.total":"576.5M", + "heap.memory.max":"3.6G", + "heap.memory.used/total":"23.54%", + "heap.memory.used/max":"3.73%", + "minor.gc.count":"6", + "minor.gc.time":"110ms", + "major.gc.count":"2", + "major.gc.time":"73ms", + "load.process":"24.78%", + "load.system":"60.00%", + "load.systemAverage":"2.07", + "thread.count":"117", + "thread.peakCount":"118", + "cluster.timeDiff":"0", + "event.q.size":"0", + "executor.q.async.size":"0", + "executor.q.client.size":"0", + "executor.q.client.query.size":"0", + "executor.q.client.blocking.size":"0", + "executor.q.query.size":"0", + "executor.q.scheduled.size":"0", + "executor.q.io.size":"0", + "executor.q.system.size":"0", + "executor.q.operations.size":"0", + "executor.q.priorityOperation.size":"0", + "operations.completed.count":"10", + "executor.q.mapLoad.size":"0", + "executor.q.mapLoadAllKeys.size":"0", + "executor.q.cluster.size":"0", + "executor.q.response.size":"0", + "operations.running.count":"0", + "operations.pending.invocations.percentage":"0.00%", + "operations.pending.invocations.count":"0", + "proxy.count":"8", + "clientEndpoint.count":"0", + "connection.active.count":"2", + "client.connection.count":"0", + "connection.count":"0" + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### Submit A Job + +
+POST /submit-job (Returns jobId and jobName if job submitted successfully.) + +#### Parameters + +> | name | type | data type | description | +> |----------------------|----------|-----------|-----------------------------------| +> | jobId | optional | string | job id | +> | jobName | optional | string | job name | +> | isStartWithSavePoint | optional | string | if job is started with save point | +> | format | optional | string | config format, support json and hocon, default json | + +#### Body + +You can choose json or hocon to pass request body. +The json format example: +``` json +{ + "env": { + "job.mode": "batch" + }, + "source": [ + { + "plugin_name": "FakeSource", + "plugin_output": "fake", + "row.num": 100, + "schema": { + "fields": { + "name": "string", + "age": "int", + "card": "int" + } + } + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Console", + "plugin_input": ["fake"] + } + ] +} +``` +The hocon format example: +``` hocon +env { + job.mode = "batch" +} + +source { + FakeSource { + result_table_name = "fake" + row.num = 100 + schema = { + fields { + name = "string" + age = "int" + card = "int" + } + } + } +} + +transform { +} + +sink { + Console { + source_table_name = "fake" + } +} + +``` + + +#### Responses + +```json +{ + "jobId": 733584788375666689, + "jobName": "rest_api_test" +} +``` + +
+ +------------------------------------------------------------------------------------------ + +### Batch Submit Jobs + +
+POST /submit-jobs (Returns jobId and jobName if the job is successfully submitted.) + +#### Parameters (add in the `params` field in the request body) + +> | Parameter Name | Required | Type | Description | +> |----------------------|--------------|---------|---------------------------------------| +> | jobId | optional | string | job id | +> | jobName | optional | string | job name | +> | isStartWithSavePoint | optional | string | if the job is started with save point | + +#### Request Body + +```json +[ + { + "params":{ + "jobId":"123456", + "jobName":"SeaTunnel-01" + }, + "env": { + "job.mode": "batch" + }, + "source": [ + { + "plugin_name": "FakeSource", + "plugin_output": "fake", + "row.num": 1000, + "schema": { + "fields": { + "name": "string", + "age": "int", + "card": "int" + } + } + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Console", + "plugin_input": ["fake"] + } + ] + }, + { + "params":{ + "jobId":"1234567", + "jobName":"SeaTunnel-02" + }, + "env": { + "job.mode": "batch" + }, + "source": [ + { + "plugin_name": "FakeSource", + "plugin_output": "fake", + "row.num": 1000, + "schema": { + "fields": { + "name": "string", + "age": "int", + "card": "int" + } + } + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Console", + "plugin_input": ["fake"] + } + ] + } +] +``` + +#### Response + +```json +[ + { + "jobId": "123456", + "jobName": "SeaTunnel-01" + },{ + "jobId": "1234567", + "jobName": "SeaTunnel-02" + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### Stop A Job + +
+POST /stop-job (Returns jobId if job stoped successfully.) + +#### Body + +```json +{ + "jobId": 733584788375666689, + "isStopWithSavePoint": false # if job is stopped with save point +} +``` + +#### Responses + +```json +{ +"jobId": 733584788375666689 +} +``` + +
+ +------------------------------------------------------------------------------------------ +### Batch Stop Jobs + +
+POST /stop-jobs (Returns jobId if the job is successfully stopped.) + +#### Request Body + +```json +[ + { + "jobId": 881432421482889220, + "isStopWithSavePoint": false + }, + { + "jobId": 881432456517910529, + "isStopWithSavePoint": false + } +] +``` + +#### Response + +```json +[ + { + "jobId": 881432421482889220 + }, + { + "jobId": 881432456517910529 + } +] +``` + +
+ +------------------------------------------------------------------------------------------ +### Encrypt Config + +
+POST /encrypt-config (Returns the encrypted config if config is encrypted successfully.) +For more information about customize encryption, please refer to the documentation [config-encryption-decryption](../connector-v2/Config-Encryption-Decryption.md). + +#### Body + +```json +{ + "env": { + "parallelism": 1, + "shade.identifier":"base64" + }, + "source": [ + { + "plugin_name": "MySQL-CDC", + "schema" : { + "fields": { + "name": "string", + "age": "int" + } + }, + "plugin_output": "fake", + "parallelism": 1, + "hostname": "127.0.0.1", + "username": "seatunnel", + "password": "seatunnel_password", + "table-name": "inventory_vwyw0n" + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Clickhouse", + "host": "localhost:8123", + "database": "default", + "table": "fake_all", + "username": "seatunnel", + "password": "seatunnel_password" + } + ] +} +``` + +#### Responses + +```json +{ + "env": { + "parallelism": 1, + "shade.identifier": "base64" + }, + "source": [ + { + "plugin_name": "MySQL-CDC", + "schema": { + "fields": { + "name": "string", + "age": "int" + } + }, + "plugin_output": "fake", + "parallelism": 1, + "hostname": "127.0.0.1", + "username": "c2VhdHVubmVs", + "password": "c2VhdHVubmVsX3Bhc3N3b3Jk", + "table-name": "inventory_vwyw0n" + } + ], + "transform": [], + "sink": [ + { + "plugin_name": "Clickhouse", + "host": "localhost:8123", + "database": "default", + "table": "fake_all", + "username": "c2VhdHVubmVs", + "password": "c2VhdHVubmVsX3Bhc3N3b3Jk" + } + ] +} +``` + +
+ + +------------------------------------------------------------------------------------------ + +### Update the tags of running node + +
POST/update-tagsBecause the update can only target a specific node, the current node's `ip:port` needs to be used for the update(If the update is successful, return a success message) + + +#### update node tags +##### Body +If the request parameter is a `Map` object, it indicates that the tags of the current node need to be updated +```json +{ + "tag1": "dev_1", + "tag2": "dev_2" +} +``` +##### Responses + +```json +{ + "status": "success", + "message": "update node tags done." +} +``` +#### remove node tags +##### Body +If the parameter is an empty `Map` object, it means that the tags of the current node need to be cleared +```json +{} +``` +##### Responses + +```json +{ + "status": "success", + "message": "update node tags done." +} +``` + +#### Request parameter exception +- If the parameter body is empty + +##### Responses + +```json +{ + "status": "fail", + "message": "Request body is empty." +} +``` +- If the parameter is not a `Map` object +##### Responses + +```json +{ + "status": "fail", + "message": "Invalid JSON format in request body." +} +``` +
+ +------------------------------------------------------------------------------------------ + +### Get Logs from All Nodes + +
+ GET /logs/:jobId (Returns a list of logs.) + +#### Request Parameters + +#### Parameters (to be added in the `params` field of the request body) + +> | Parameter Name | Required | Type | Description | +> |-----------------------|--------------|---------|------------------------------------| +> | jobId | optional | string | job id | + +If `jobId` is empty, the request will return logs from all nodes. Otherwise, it will return the list of logs for the specified `jobId` from all nodes. + +#### Response + +Returns a list of logs from the requested nodes along with their content. + +#### Return List of All Log Files + +If you want to view the log list first, you can retrieve it via a `GET` request: `http://localhost:8080/logs?format=json` + +```json +[ + { + "node": "localhost:8080", + "logLink": "http://localhost:8080/logs/job-899485770241277953.log", + "logName": "job-899485770241277953.log" + }, + { + "node": "localhost:8080", + "logLink": "http://localhost:8080/logs/job-899470314109468673.log", + "logName": "job-899470314109468673.log" + } +] +``` + +Supported formats are `json` and `html`, with `html` as the default. + +#### Examples + +Retrieve logs for `jobId` `733584788375666689` across all nodes: `http://localhost:8080/logs/733584788375666689` +Retrieve the list of logs from all nodes: `http://localhost:8080/logs` +Retrieve the list of logs in JSON format: `http://localhost:8080/logs?format=json` +Retrieve the content of a specific log file: `http://localhost:8080/logs/job-898380162133917698.log` + +
+ +### Get Log Content from a Single Node + +
+ GET /log (Returns a list of logs.) + +#### Response + +Returns a list of logs from the requested node. + +#### Examples + +To get a list of logs from the current node: `http://localhost:5801/log` +To get the content of a log file: `http://localhost:5801/log/job-898380162133917698.log` + +
diff --git a/docs/en/seatunnel-engine/separated-cluster-deployment.md b/docs/en/seatunnel-engine/separated-cluster-deployment.md index dcfe58d2f55..91215eb459a 100644 --- a/docs/en/seatunnel-engine/separated-cluster-deployment.md +++ b/docs/en/seatunnel-engine/separated-cluster-deployment.md @@ -71,7 +71,7 @@ SeaTunnel Engine implements cluster management based on [Hazelcast IMDG](https:/ The `backup count` is a parameter that defines the number of synchronous backups. For example, if it is set to 1, the backup of the partition will be placed on one other member. If it is set to 2, it will be placed on two other members. -We recommend that the value of `backup-count` be `min(1, max(5, N/2))`. `N` is the number of cluster nodes. +We recommend that the value of `backup-count` be `max(1, min(5, N/2))`. `N` is the number of cluster nodes. ```yaml seatunnel: @@ -173,7 +173,7 @@ seatunnel: This configuration mainly solves the problem of resource leakage caused by continuously creating and attempting to destroy class loaders. If you encounter an exception related to metaspace space overflow, you can try to enable this configuration. In order to reduce the frequency of creating class loaders, after enabling this configuration, SeaTunnel will not try to release the corresponding class loader when the job is completed, so that it can be used by subsequent jobs, that is to say, when not too many types of Source/Sink connector are used in the running job, it is more effective. -The default value is false. +The default value is true. Example ```yaml @@ -280,6 +280,23 @@ netty-common-4.1.89.Final.jar seatunnel-hadoop3-3.1.4-uber.jar ``` +### 4.7 Job Scheduling Strategy + +When resources are insufficient, the job scheduling strategy can be configured in the following two modes: + +1. `WAIT`: Wait for resources to be available. + +2. `REJECT`: Reject the job, default value. + +Example + +```yaml +seatunnel: + engine: + job-schedule-strategy: WAIT +``` +When `dynamic-slot: true` is used, the `job-schedule-strategy: WAIT` configuration will become invalid and will be forcibly changed to `job-schedule-strategy: REJECT`, because this parameter is meaningless in dynamic slots. + ## 5. Configuring SeaTunnel Engine Network Services All network-related configurations of the SeaTunnel Engine are in the `hazelcast-master.yaml` and `hazelcast-worker.yaml` files. @@ -431,4 +448,4 @@ Now that the cluster has been deployed, you can complete the job submission and ### 8.2 Submit Jobs With The REST API -The SeaTunnel Engine provides a REST API for submitting and managing jobs. For more information, please refer to [REST API](rest-api.md) \ No newline at end of file +The SeaTunnel Engine provides a REST API for submitting and managing jobs. For more information, please refer to [REST API V2](rest-api-v2.md) diff --git a/docs/en/seatunnel-engine/telemetry.md b/docs/en/seatunnel-engine/telemetry.md index 1febb3f08e6..530385392a3 100644 --- a/docs/en/seatunnel-engine/telemetry.md +++ b/docs/en/seatunnel-engine/telemetry.md @@ -1,5 +1,5 @@ --- -sidebar_position: 13 +sidebar_position: 14 --- # Telemetry @@ -48,8 +48,8 @@ Note: All metrics both have the same labelName `cluster`, that's value is the co | hazelcast_executor_queueSize | Gauge | **type**, the type of executor, including: "async" "client" "clientBlocking" "clientQuery" "io" "offloadable" "scheduled" "system" | The hazelcast executor queueSize of seatunnel cluster node | | hazelcast_partition_partitionCount | Gauge | - | The partitionCount of seatunnel cluster node | | hazelcast_partition_activePartition | Gauge | - | The activePartition of seatunnel cluster node | -| hazelcast_partition_isClusterSafe | Gauge | - | Weather is cluster safe of partition | -| hazelcast_partition_isLocalMemberSafe | Gauge | - | Weather is local member safe of partition | +| hazelcast_partition_isClusterSafe | Gauge | - | Whether is cluster safe of partition | +| hazelcast_partition_isLocalMemberSafe | Gauge | - | Whether is local member safe of partition | ### Thread Pool Status diff --git a/docs/en/seatunnel-engine/user-command.md b/docs/en/seatunnel-engine/user-command.md index 2504198b2b1..f8957a98276 100644 --- a/docs/en/seatunnel-engine/user-command.md +++ b/docs/en/seatunnel-engine/user-command.md @@ -1,5 +1,5 @@ --- -sidebar_position: 12 +sidebar_position: 13 --- # Command Line Tool @@ -122,3 +122,13 @@ This command will cancel the specified job. After canceling the job, the job wil Supports batch cancellation of jobs, and can cancel multiple jobs at one time. All breakpoint information of the canceled job will be deleted and cannot be resumed by seatunnel.sh -r <jobId>. + +## Configure The JVM Options + +We can configure the JVM options for the SeaTunnel Engine client in the following ways: + +1. Add the JVM options to `$SEATUNNEL_HOME/config/jvm_client_options`. + + Modify the JVM parameters in the `$SEATUNNEL_HOME/config/jvm_client_options` file. Please note that the JVM parameters in this file will be applied to all jobs submitted using `seatunnel.sh`, including Local Mode and Cluster Mode. + +2. Add JVM options when submitting jobs. For example, `sh bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template -DJvmOption="-Xms2G -Xmx2G"` diff --git a/docs/en/seatunnel-engine/web-ui.md b/docs/en/seatunnel-engine/web-ui.md new file mode 100644 index 00000000000..84c73d2fae8 --- /dev/null +++ b/docs/en/seatunnel-engine/web-ui.md @@ -0,0 +1,48 @@ +# Web UI + +## Access + +Before accessing the web ui we need to enable the http rest api. first you need to configure it in the `seatunnel.yaml` configuration file + +``` +seatunnel: + engine: + http: + enable-http: true + port: 8080 + +``` + +Then visit `http://ip:8080/#/overview` + +## Overview + +The Web UI of Apache SeaTunnel offers a user-friendly interface for monitoring and managing SeaTunnel jobs. Through the Web UI, users can view real-time information on currently running jobs, finished jobs, and the status of worker and master nodes within the cluster. The main functional modules include Jobs, Workers, and Master, each providing detailed status information and operational options to help users efficiently manage and optimize their data processing workflows. +![overview.png](../../images/ui/overview.png) + +## Jobs + +### Running Jobs + +The "Running Jobs" section lists all SeaTunnel jobs that are currently in execution. Users can view basic information for each job, including Job ID, submission time, status, execution time, and more. By clicking on a specific job, users can access detailed information such as task distribution, resource utilization, and log outputs, allowing for real-time monitoring of job progress and timely handling of potential issues. +![running.png](../../images/ui/running.png) +![detail.png](../../images/ui/detail.png) + +### Finished Jobs + +The "Finished Jobs" section displays all SeaTunnel jobs that have either successfully completed or failed. This section provides execution results, completion times, durations, and failure reasons (if any) for each job. Users can review past job records through this module to analyze job performance, troubleshoot issues, or rerun specific jobs as needed. +![finished.png](../../images/ui/finished.png) + +## Workers + +### Workers Information + +The "Workers" section displays detailed information about all worker nodes in the cluster, including each worker's address, running status, CPU and memory usage, number of tasks being executed, and more. Through this module, users can monitor the health of each worker node, promptly identify and address resource bottlenecks or node failures, ensuring the stable operation of the SeaTunnel cluster. +![workers.png](../../images/ui/workers.png) + +## Master + +### Master Information + +The "Master" section provides the status and configuration information of the master node in the SeaTunnel cluster. Users can view the master's address, running status, job scheduling responsibilities, and overall resource allocation within the cluster. This module helps users gain a comprehensive understanding of the cluster's core management components, facilitating cluster configuration optimization and troubleshooting. +![master.png](../../images/ui/master.png) diff --git a/docs/en/start-v2/docker/docker.md b/docs/en/start-v2/docker/docker.md index 3dfe3ec8c85..2c2c7824f4f 100644 --- a/docs/en/start-v2/docker/docker.md +++ b/docs/en/start-v2/docker/docker.md @@ -40,7 +40,7 @@ You can download the source code from the [download page](https://seatunnel.apac ```shell cd seatunnel # Use already sett maven profile -sh ./mvnw -B clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Dlicense.skipAddThirdParty=true -D"docker.build.skip"=false -D"docker.verify.skip"=false -D"docker.push.skip"=true -D"docker.tag"=2.3.8 -Dmaven.deploy.skip --no-snapshot-updates -Pdocker,seatunnel +sh ./mvnw -B clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Dlicense.skipAddThirdParty=true -D"docker.build.skip"=false -D"docker.verify.skip"=false -D"docker.push.skip"=true -D"docker.tag"=2.3.9 -Dmaven.deploy.skip -D"skip.spotless"=true --no-snapshot-updates -Pdocker,seatunnel # Check the docker image docker images | grep apache/seatunnel @@ -53,10 +53,10 @@ sh ./mvnw clean package -DskipTests -Dskip.spotless=true # Build docker image cd seatunnel-dist -docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.8 -t apache/seatunnel:2.3.8 . +docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.9 -t apache/seatunnel:2.3.9 . # If you build from dev branch, you should add SNAPSHOT suffix to the version -docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.8-SNAPSHOT -t apache/seatunnel:2.3.8-SNAPSHOT . +docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.9-SNAPSHOT -t apache/seatunnel:2.3.9-SNAPSHOT . # Check the docker image docker images | grep apache/seatunnel @@ -167,24 +167,26 @@ docker run -d --name seatunnel_master \ - get created container ip ```shell -docker inspect master-1 +docker inspect seatunnel_master ``` run this command to get the pod ip. - start worker node ```shell +# you need update yourself master container ip to `ST_DOCKER_MEMBER_LIST` docker run -d --name seatunnel_worker_1 \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set master container ip to here + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r worker ## start worker2 -docker run -d --name seatunnel_worker_2 \ +# you need update yourself master container ip to `ST_DOCKER_MEMBER_LIST` +docker run -d --name seatunnel_worker_2 \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set master container ip to here + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r worker @@ -194,20 +196,22 @@ docker run -d --name seatunnel_worker_2 \ run this command to start master node. ```shell +# you need update yourself master container ip to `ST_DOCKER_MEMBER_LIST` docker run -d --name seatunnel_master \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set exist master container ip to here + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r master ``` run this command to start worker node. ```shell +# you need update yourself master container ip to `ST_DOCKER_MEMBER_LIST` docker run -d --name seatunnel_worker_1 \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set master container ip to here + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r worker ``` @@ -371,21 +375,23 @@ and run `docker-compose up -d` command, the new worker node will start, and the #### use docker as a client - submit job : ```shell +# you need update yourself master container ip to `ST_DOCKER_MEMBER_LIST` docker run --name seatunnel_client \ --network seatunnel-network \ + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ --rm \ apache/seatunnel \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set it as master node container ip - ./bin/seatunnel.sh -c config/v2.batch.config.template # this is an default config, if you need submit your self config, you can mount config file. + ./bin/seatunnel.sh -c config/v2.batch.config.template ``` - list job ```shell +# you need update yourself master container ip to `ST_DOCKER_MEMBER_LIST` docker run --name seatunnel_client \ --network seatunnel-network \ + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ --rm \ apache/seatunnel \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set it as master node container ip ./bin/seatunnel.sh -l ``` @@ -395,5 +401,5 @@ more command please refer [user-command](../../seatunnel-engine/user-command.md) #### use rest api -please refer [Submit A Job](../../seatunnel-engine/rest-api.md#submit-a-job) +please refer [Submit A Job](../../seatunnel-engine/rest-api-v2.md#submit-a-job) diff --git a/docs/en/start-v2/kubernetes/kubernetes.mdx b/docs/en/start-v2/kubernetes/kubernetes.mdx index eb231850514..f3cc9e6b0d5 100644 --- a/docs/en/start-v2/kubernetes/kubernetes.mdx +++ b/docs/en/start-v2/kubernetes/kubernetes.mdx @@ -44,7 +44,7 @@ To run the image with SeaTunnel, first create a `Dockerfile`: ```Dockerfile FROM flink:1.13 -ENV SEATUNNEL_VERSION="2.3.8" +ENV SEATUNNEL_VERSION="2.3.9" ENV SEATUNNEL_HOME="/opt/seatunnel" RUN wget https://dlcdn.apache.org/seatunnel/${SEATUNNEL_VERSION}/apache-seatunnel-${SEATUNNEL_VERSION}-bin.tar.gz @@ -56,13 +56,13 @@ RUN cd ${SEATUNNEL_HOME} && sh bin/install-plugin.sh ${SEATUNNEL_VERSION} Then run the following commands to build the image: ```bash -docker build -t seatunnel:2.3.8-flink-1.13 -f Dockerfile . +docker build -t seatunnel:2.3.9-flink-1.13 -f Dockerfile . ``` -Image `seatunnel:2.3.8-flink-1.13` needs to be present in the host (minikube) so that the deployment can take place. +Image `seatunnel:2.3.9-flink-1.13` needs to be present in the host (minikube) so that the deployment can take place. Load image to minikube via: ```bash -minikube image load seatunnel:2.3.8-flink-1.13 +minikube image load seatunnel:2.3.9-flink-1.13 ``` @@ -72,7 +72,7 @@ minikube image load seatunnel:2.3.8-flink-1.13 ```Dockerfile FROM openjdk:8 -ENV SEATUNNEL_VERSION="2.3.8" +ENV SEATUNNEL_VERSION="2.3.9" ENV SEATUNNEL_HOME="/opt/seatunnel" RUN wget https://dlcdn.apache.org/seatunnel/${SEATUNNEL_VERSION}/apache-seatunnel-${SEATUNNEL_VERSION}-bin.tar.gz @@ -84,13 +84,13 @@ RUN cd ${SEATUNNEL_HOME} && sh bin/install-plugin.sh ${SEATUNNEL_VERSION} Then run the following commands to build the image: ```bash -docker build -t seatunnel:2.3.8 -f Dockerfile . +docker build -t seatunnel:2.3.9 -f Dockerfile . ``` -Image `seatunnel:2.3.8` need to be present in the host (minikube) so that the deployment can take place. +Image `seatunnel:2.3.9` need to be present in the host (minikube) so that the deployment can take place. Load image to minikube via: ```bash -minikube image load seatunnel:2.3.8 +minikube image load seatunnel:2.3.9 ``` @@ -100,7 +100,7 @@ minikube image load seatunnel:2.3.8 ```Dockerfile FROM openjdk:8 -ENV SEATUNNEL_VERSION="2.3.8" +ENV SEATUNNEL_VERSION="2.3.9" ENV SEATUNNEL_HOME="/opt/seatunnel" RUN wget https://dlcdn.apache.org/seatunnel/${SEATUNNEL_VERSION}/apache-seatunnel-${SEATUNNEL_VERSION}-bin.tar.gz @@ -112,13 +112,13 @@ RUN cd ${SEATUNNEL_HOME} && sh bin/install-plugin.sh ${SEATUNNEL_VERSION} Then run the following commands to build the image: ```bash -docker build -t seatunnel:2.3.8 -f Dockerfile . +docker build -t seatunnel:2.3.9 -f Dockerfile . ``` -Image `seatunnel:2.3.8` needs to be present in the host (minikube) so that the deployment can take place. +Image `seatunnel:2.3.9` needs to be present in the host (minikube) so that the deployment can take place. Load image to minikube via: ```bash -minikube image load seatunnel:2.3.8 +minikube image load seatunnel:2.3.9 ``` @@ -191,7 +191,7 @@ none ]}> -In this guide we will use [seatunnel.streaming.conf](https://github.com/apache/seatunnel/blob/2.3.8-release/config/v2.streaming.conf.template): +In this guide we will use [seatunnel.streaming.conf](https://github.com/apache/seatunnel/blob/2.3.9-release/config/v2.streaming.conf.template): ```conf env { @@ -202,7 +202,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 160000 schema = { fields { @@ -215,8 +215,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -226,7 +226,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` @@ -245,7 +245,7 @@ kind: FlinkDeployment metadata: name: seatunnel-flink-streaming-example spec: - image: seatunnel:2.3.8-flink-1.13 + image: seatunnel:2.3.9-flink-1.13 flinkVersion: v1_13 flinkConfiguration: taskmanager.numberOfTaskSlots: "2" @@ -291,7 +291,7 @@ kubectl apply -f seatunnel-flink.yaml -In this guide we will use [seatunnel.streaming.conf](https://github.com/apache/seatunnel/blob/2.3.8-release/config/v2.streaming.conf.template): +In this guide we will use [seatunnel.streaming.conf](https://github.com/apache/seatunnel/blob/2.3.9-release/config/v2.streaming.conf.template): ```conf env { @@ -303,7 +303,7 @@ env { source { FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -334,7 +334,7 @@ metadata: spec: containers: - name: seatunnel - image: seatunnel:2.3.8 + image: seatunnel:2.3.9 command: ["/bin/sh","-c","/opt/seatunnel/bin/seatunnel.sh --config /data/seatunnel.streaming.conf -e local"] resources: limits: @@ -366,7 +366,7 @@ kubectl apply -f seatunnel.yaml -In this guide we will use [seatunnel.streaming.conf](https://github.com/apache/seatunnel/blob/2.3.8-release/config/v2.streaming.conf.template): +In this guide we will use [seatunnel.streaming.conf](https://github.com/apache/seatunnel/blob/2.3.9-release/config/v2.streaming.conf.template): ```conf env { @@ -378,7 +378,7 @@ env { source { FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -524,7 +524,7 @@ spec: spec: containers: - name: seatunnel - image: seatunnel:2.3.8 + image: seatunnel:2.3.9 imagePullPolicy: IfNotPresent ports: - containerPort: 5801 diff --git a/docs/en/start-v2/locally/deployment.md b/docs/en/start-v2/locally/deployment.md index 8555c097f36..4684871acb0 100644 --- a/docs/en/start-v2/locally/deployment.md +++ b/docs/en/start-v2/locally/deployment.md @@ -22,7 +22,7 @@ Visit the [SeaTunnel Download Page](https://seatunnel.apache.org/download) to do Or you can also download it through the terminal: ```shell -export version="2.3.8" +export version="2.3.9" wget "https://archive.apache.org/dist/seatunnel/${version}/apache-seatunnel-${version}-bin.tar.gz" tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" ``` @@ -35,10 +35,10 @@ Starting from version 2.2.0-beta, the binary package no longer provides connecto sh bin/install-plugin.sh ``` -If you need a specific connector version, taking 2.3.8 as an example, you need to execute the following command: +If you need a specific connector version, taking 2.3.9 as an example, you need to execute the following command: ```bash -sh bin/install-plugin.sh 2.3.8 +sh bin/install-plugin.sh 2.3.9 ``` Typically, you do not need all the connector plugins. You can specify the required plugins by configuring `config/plugin_config`. For example, if you want the sample application to work properly, you will need the `connector-console` and `connector-fake` plugins. You can modify the `plugin_config` configuration file as follows: @@ -71,7 +71,7 @@ You can download the source code from the [download page](https://seatunnel.apac cd seatunnel sh ./mvnw clean install -DskipTests -Dskip.spotless=true # get the binary package -cp seatunnel-dist/target/apache-seatunnel-2.3.8-bin.tar.gz /The-Path-You-Want-To-Copy +cp seatunnel-dist/target/apache-seatunnel-2.3.9-bin.tar.gz /The-Path-You-Want-To-Copy cd /The-Path-You-Want-To-Copy tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" diff --git a/docs/en/start-v2/locally/quick-start-flink.md b/docs/en/start-v2/locally/quick-start-flink.md index 244dfd8c9e6..fbfc945fc7c 100644 --- a/docs/en/start-v2/locally/quick-start-flink.md +++ b/docs/en/start-v2/locally/quick-start-flink.md @@ -27,7 +27,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -40,8 +40,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -51,7 +51,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } diff --git a/docs/en/start-v2/locally/quick-start-seatunnel-engine.md b/docs/en/start-v2/locally/quick-start-seatunnel-engine.md index d5b48b27247..fe9d8ee7983 100644 --- a/docs/en/start-v2/locally/quick-start-seatunnel-engine.md +++ b/docs/en/start-v2/locally/quick-start-seatunnel-engine.md @@ -21,7 +21,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -34,8 +34,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -45,7 +45,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } diff --git a/docs/en/start-v2/locally/quick-start-spark.md b/docs/en/start-v2/locally/quick-start-spark.md index d5dd82725bd..e490f238b3d 100644 --- a/docs/en/start-v2/locally/quick-start-spark.md +++ b/docs/en/start-v2/locally/quick-start-spark.md @@ -28,7 +28,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -41,8 +41,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -52,7 +52,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } diff --git a/docs/en/transform-v2/common-options.md b/docs/en/transform-v2/common-options.md index 5bd0e6e2591..32e91bf8243 100644 --- a/docs/en/transform-v2/common-options.md +++ b/docs/en/transform-v2/common-options.md @@ -6,10 +6,16 @@ sidebar_position: 1 > This is a process of intermediate conversion between the source and sink terminals,You can use sql statements to smoothly complete the conversion process -| Name | Type | Required | Default | Description | -|-------------------|--------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| result_table_name | String | No | - | When `source_table_name` is not specified, the current plugin processes the data set `(dataset)` output by the previous plugin in the configuration file;
When `source_table_name` is specified, the current plugin is processing the data set corresponding to this parameter. | -| source_table_name | String | No | - | When `result_table_name` is not specified, the data processed by this plugin will not be registered as a data set that can be directly accessed by other plugins, or called a temporary table `(table)`;
When `result_table_name` is specified, the data processed by this plugin will be registered as a data set `(dataset)` that can be directly accessed by other plugins, or called a temporary table `(table)` . The dataset registered here can be directly accessed by other plugins by specifying `source_table_name` . | +:::warn + +The old configuration name `source_table_name`/`result_table_name` is deprecated, please migrate to the new name `plugin_input`/`plugin_output` as soon as possible. + +::: + +| Name | Type | Required | Default | Description | +|---------------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| plugin_output | String | No | - | When `plugin_input` is not specified, the current plugin processes the data set `(dataset)` output by the previous plugin in the configuration file;
When `plugin_input` is specified, the current plugin is processing the data set corresponding to this parameter. | +| plugin_input | String | No | - | When `plugin_output` is not specified, the data processed by this plugin will not be registered as a data set that can be directly accessed by other plugins, or called a temporary table `(table)`;
When `plugin_output` is specified, the data processed by this plugin will be registered as a data set `(dataset)` that can be directly accessed by other plugins, or called a temporary table `(table)` . The dataset registered here can be directly accessed by other plugins by specifying `plugin_input` . | ## Task Example @@ -24,7 +30,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -48,9 +54,9 @@ source { transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" - # the query table name must same as field 'source_table_name' + plugin_input = "fake" + plugin_output = "fake1" + # the query table name must same as field 'plugin_input' query = "select id, regexp_replace(name, '.+', 'b') as name, age+1 as age, pi() as pi, c_timestamp, c_date, c_map, c_array, c_decimal, c_row from fake" } # The SQL transform support base function and criteria operation @@ -59,10 +65,10 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } Console { - source_table_name = "fake" + plugin_input = "fake" } } ``` diff --git a/docs/en/transform-v2/copy.md b/docs/en/transform-v2/copy.md index 7a0e73f44be..eede3f7d077 100644 --- a/docs/en/transform-v2/copy.md +++ b/docs/en/transform-v2/copy.md @@ -36,8 +36,8 @@ We want copy fields `name`、`age` to a new fields `name1`、`name2`、`age1`, w ``` transform { Copy { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" fields { name1 = name name2 = name diff --git a/docs/en/transform-v2/dynamic-compile.md b/docs/en/transform-v2/dynamic-compile.md index fb5500880ac..d5f21f2708d 100644 --- a/docs/en/transform-v2/dynamic-compile.md +++ b/docs/en/transform-v2/dynamic-compile.md @@ -82,13 +82,13 @@ Use this DynamicCompile to add a new column `compile_language`, and update the ` ```hacon transform { DynamicCompile { - source_table_name = "fake" - result_table_name = "groovy_out" + plugin_input = "fake" + plugin_output = "groovy_out" compile_language="GROOVY" compile_pattern="SOURCE_CODE" source_code=""" import org.apache.seatunnel.api.table.catalog.Column - import org.apache.seatunnel.transform.common.SeaTunnelRowAccessor + import org.apache.seatunnel.api.table.type.SeaTunnelRowAccessor import org.apache.seatunnel.api.table.catalog.CatalogTable import org.apache.seatunnel.api.table.catalog.PhysicalColumn; import org.apache.seatunnel.api.table.type.*; @@ -140,13 +140,13 @@ transform { ```hacon transform { DynamicCompile { - source_table_name = "fake" - result_table_name = "java_out" + plugin_input = "fake" + plugin_output = "java_out" compile_language="JAVA" compile_pattern="SOURCE_CODE" source_code=""" import org.apache.seatunnel.api.table.catalog.Column; - import org.apache.seatunnel.transform.common.SeaTunnelRowAccessor; + import org.apache.seatunnel.api.table.type.SeaTunnelRowAccessor; import org.apache.seatunnel.api.table.catalog.*; import org.apache.seatunnel.api.table.type.*; import java.util.ArrayList; @@ -195,8 +195,8 @@ transform { ```hacon transform { DynamicCompile { - source_table_name = "fake" - result_table_name = "groovy_out" + plugin_input = "fake" + plugin_output = "groovy_out" compile_language="GROOVY" compile_pattern="ABSOLUTE_PATH" absolute_path="""/tmp/GroovyFile""" diff --git a/docs/en/transform-v2/embedding.md b/docs/en/transform-v2/embedding.md index 046f72789ac..350a23fc555 100644 --- a/docs/en/transform-v2/embedding.md +++ b/docs/en/transform-v2/embedding.md @@ -166,13 +166,13 @@ source { "Herman Melville (1819–1891) was an American novelist, short story writer, and poet of the American Renaissance period. Born in New York City, Melville gained initial fame with novels such as Typee and Omoo, but it was Moby-Dick, published in 1851, that would later be recognized as his masterpiece. Melville’s work is known for its complexity, symbolism, and exploration of themes such as man’s place in the universe, the nature of evil, and the quest for meaning. Despite facing financial difficulties and critical neglect during his lifetime, Melville’s reputation soared posthumously, and he is now considered one of the great American authors." ], kind = INSERT} ] - result_table_name = "fake" + plugin_output = "fake" } } transform { Embedding { - source_table_name = "fake" + plugin_input = "fake" embedding_model_provider = QIANFAN model = bge_large_en api_key = xxxxxxxxxx @@ -182,13 +182,13 @@ transform { book_intro_vector = book_intro author_biography_vector = author_biography } - result_table_name = "embedding_output" + plugin_output = "embedding_output" } } sink { Assert { - source_table_name = "embedding_output" + plugin_input = "embedding_output" rules = { field_rules = [ @@ -293,13 +293,13 @@ source { "Herman Melville (1819–1891) was an American novelist, short story writer, and poet of the American Renaissance period. Born in New York City, Melville gained initial fame with novels such as Typee and Omoo, but it was Moby-Dick, published in 1851, that would later be recognized as his masterpiece. Melville’s work is known for its complexity, symbolism, and exploration of themes such as man’s place in the universe, the nature of evil, and the quest for meaning. Despite facing financial difficulties and critical neglect during his lifetime, Melville’s reputation soared posthumously, and he is now considered one of the great American authors." ], kind = INSERT} ] - result_table_name = "fake" + plugin_output = "fake" } } transform { Embedding { - source_table_name = "fake" + plugin_input = "fake" model_provider = CUSTOM model = text-embedding-3-small api_key = xxxxxxxx @@ -320,13 +320,13 @@ transform { inputx = ["${input}"] } } - result_table_name = "embedding_output_1" + plugin_output = "embedding_output_1" } } sink { Assert { - source_table_name = "embedding_output_1" + plugin_input = "embedding_output_1" rules = { field_rules = [ diff --git a/docs/en/transform-v2/field-mapper.md b/docs/en/transform-v2/field-mapper.md index e0bd32e1492..fa54ced741e 100644 --- a/docs/en/transform-v2/field-mapper.md +++ b/docs/en/transform-v2/field-mapper.md @@ -36,8 +36,8 @@ We want to delete `age` field and update the filed order to `id`, `card`, `name` ``` transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { id = id card = card diff --git a/docs/en/transform-v2/filter-rowkind.md b/docs/en/transform-v2/filter-rowkind.md index e6ef5ba98cd..68aab44b973 100644 --- a/docs/en/transform-v2/filter-rowkind.md +++ b/docs/en/transform-v2/filter-rowkind.md @@ -39,7 +39,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -53,15 +53,15 @@ source { transform { FilterRowKind { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" exclude_kinds = ["INSERT"] } } sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` diff --git a/docs/en/transform-v2/filter.md b/docs/en/transform-v2/filter.md index f9f28b8398a..748934e621a 100644 --- a/docs/en/transform-v2/filter.md +++ b/docs/en/transform-v2/filter.md @@ -43,8 +43,8 @@ we want to keep the field named `name`, `card`, we can add a `Filter` Transform ``` transform { Filter { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" include_fields = [name, card] } } @@ -55,8 +55,8 @@ Or we can delete the field named `age` by adding a `Filter` Transform with `excl ``` transform { Filter { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" exclude_fields = [age] } } diff --git a/docs/en/transform-v2/jsonpath.md b/docs/en/transform-v2/jsonpath.md index 1948f5ca694..f787487069e 100644 --- a/docs/en/transform-v2/jsonpath.md +++ b/docs/en/transform-v2/jsonpath.md @@ -93,8 +93,8 @@ Assuming we want to use JsonPath to extract properties. ```json transform { JsonPath { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" columns = [ { "src_field" = "data" @@ -175,8 +175,8 @@ The JsonPath transform converts the values of seatunnel into an array, ```hocon transform { JsonPath { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" row_error_handle_way = FAIL columns = [ diff --git a/docs/en/transform-v2/llm.md b/docs/en/transform-v2/llm.md index 8ee5a36a9ab..c1c9798abe3 100644 --- a/docs/en/transform-v2/llm.md +++ b/docs/en/transform-v2/llm.md @@ -11,7 +11,7 @@ more. ## Options | name | type | required | default value | -|------------------------| ------ | -------- |---------------| +|------------------------|--------|----------|---------------| | model_provider | enum | yes | | | output_data_type | enum | no | String | | output_column_name | string | no | llm_output | @@ -28,7 +28,9 @@ more. ### model_provider The model provider to use. The available options are: -OPENAI, DOUBAO, KIMIAI, CUSTOM +OPENAI, DOUBAO, KIMIAI, MICROSOFT, CUSTOM + +> tips: If you use Microsoft, please make sure api_path cannot be empty ### output_data_type @@ -254,6 +256,7 @@ sink { } } ``` + ### Customize the LLM model ```hocon @@ -277,13 +280,13 @@ source { {fields = [4, "Eric"], kind = INSERT} {fields = [5, "Guangdong Liu"], kind = INSERT} ] - result_table_name = "fake" + plugin_output = "fake" } } transform { LLM { - source_table_name = "fake" + plugin_input = "fake" model_provider = CUSTOM model = gpt-4o-mini api_key = sk-xxx @@ -308,13 +311,13 @@ transform { }] } } - result_table_name = "llm_output" + plugin_output = "llm_output" } } sink { Assert { - source_table_name = "llm_output" + plugin_input = "llm_output" rules = { field_rules = [ diff --git a/docs/en/transform-v2/metadata.md b/docs/en/transform-v2/metadata.md new file mode 100644 index 00000000000..abae10e4483 --- /dev/null +++ b/docs/en/transform-v2/metadata.md @@ -0,0 +1,85 @@ +# Metadata + +> Metadata transform plugin + +## Description +Metadata transform plugin for adding metadata fields to data + +## Available Metadata + +| Key | DataType | Description | +|:---------:|:--------:|:---------------------------------------------------------------------------------------------------| +| Database | string | Name of the table that contain the row. | +| Table | string | Name of the table that contain the row. | +| RowKind | string | The type of operation | +| EventTime | Long | The time at which the connector processed the event. | +| Delay | Long | The difference between data extraction time and database change time | +| Partition | string | Contains the partition field of the corresponding number table of the row, multiple using `,` join | + +### note + `Delay` `Partition` only worked on cdc series connectors for now , except TiDB-CDC + +## Options + +| name | type | required | default value | Description | +|:---------------:|------|----------|---------------|---------------------------------------------------------------------------| +| metadata_fields | map | yes | | A mapping metadata input fields and their corresponding output fields. | + +### metadata_fields [map] + +A mapping between metadata fields and their respective output fields. + +```hocon +metadata_fields { + Database = c_database + Table = c_table + RowKind = c_rowKind + EventTime = c_ts_ms + Delay = c_delay +} +``` + +## Examples + +```yaml + +env { + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 + read_limit.bytes_per_second = 7000000 + read_limit.rows_per_second = 400 +} + +source { + MySQL-CDC { + plugin_output = "customers_mysql_cdc" + server-id = 5652 + username = "root" + password = "zdyk_Dev@2024" + table-names = ["source.user"] + base-url = "jdbc:mysql://172.16.17.123:3306/source" + } +} + +transform { + Metadata { + metadata_fields { + Database = database + Table = table + RowKind = rowKind + EventTime = ts_ms + Delay = delay + } + plugin_output = "trans_result" + } +} + +sink { + Console { + plugin_input = "custom_name" + } +} + +``` + diff --git a/docs/en/transform-v2/replace.md b/docs/en/transform-v2/replace.md index 1cc99c0ace7..ebb15a9c8ba 100644 --- a/docs/en/transform-v2/replace.md +++ b/docs/en/transform-v2/replace.md @@ -56,8 +56,8 @@ We want to replace the char ` ` to `_` at the `name` field. Then we can add a `R ``` transform { Replace { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" replace_field = "name" pattern = " " replacement = "_" @@ -84,7 +84,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -97,8 +97,8 @@ source { transform { Replace { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" replace_field = "name" pattern = ".+" replacement = "b" @@ -108,7 +108,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` diff --git a/docs/en/transform-v2/rowkind-extractor.md b/docs/en/transform-v2/rowkind-extractor.md new file mode 100644 index 00000000000..a2ee384c347 --- /dev/null +++ b/docs/en/transform-v2/rowkind-extractor.md @@ -0,0 +1,113 @@ +# RowKindExtractor + +> RowKindExtractor transform plugin + +## Description + +transform cdc row to append only row that contains the cdc RowKind.
+Example:
+CDC row: -D 1, test1, test2
+transformed Row: +I 1,test1,test2,DELETE + +## Options + +| name | type | required | default value | +|-------------------|--------|----------|---------------| +| custom_field_name | string | yes | row_kind | +| transform_type | enum | yes | SHORT | + +### custom_field_name [string] + +Custom field name of the RowKind field + +### transform_type [enum] + +the RowKind field value formatting , the option can be `SHORT` or `FULL` + +`SHORT` : +I, -U , +U, -D +`FULL` : INSERT, UPDATE_BEFORE, UPDATE_AFTER , DELETE + +## Examples + + +```yaml + +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + pk_id = bigint + name = string + score = int + } + primaryKey { + name = "pk_id" + columnNames = [pk_id] + } + } + rows = [ + { + kind = INSERT + fields = [1, "A", 100] + }, + { + kind = INSERT + fields = [2, "B", 100] + }, + { + kind = INSERT + fields = [3, "C", 100] + }, + { + kind = INSERT + fields = [4, "D", 100] + }, + { + kind = UPDATE_BEFORE + fields = [1, "A", 100] + }, + { + kind = UPDATE_AFTER + fields = [1, "F", 100] + } + { + kind = UPDATE_BEFORE + fields = [2, "B", 100] + }, + { + kind = UPDATE_AFTER + fields = [2, "G", 100] + }, + { + kind = DELETE + fields = [3, "C", 100] + }, + { + kind = DELETE + fields = [4, "D", 100] + } + ] + } +} + +transform { + RowKindExtractor { + custom_field_name = "custom_name" + transform_type = FULL + plugin_output = "trans_result" + } +} + +sink { + Console { + plugin_input = "custom_name" + } +} + +``` + diff --git a/docs/en/transform-v2/split.md b/docs/en/transform-v2/split.md index ecfe94c854b..0df9afbdef2 100644 --- a/docs/en/transform-v2/split.md +++ b/docs/en/transform-v2/split.md @@ -46,8 +46,8 @@ We want split `name` field to `first_name` and `second name`, we can add `Split` ``` transform { Split { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" separator = " " split_field = "name" output_fields = [first_name, second_name] diff --git a/docs/en/transform-v2/sql-functions.md b/docs/en/transform-v2/sql-functions.md index 3438a24de9c..31a33989375 100644 --- a/docs/en/transform-v2/sql-functions.md +++ b/docs/en/transform-v2/sql-functions.md @@ -302,6 +302,14 @@ Example: REPLACE(NAME, ' ') +### SPLIT + +Split a string into an array. + +Example: + +select SPLIT(test,';') as arrays + ### SOUNDEX ```SOUNDEX(string)``` @@ -973,3 +981,37 @@ It is used to determine whether the condition is valid and return different valu Example: case when c_string in ('c_string') then 1 else 0 end + +### UUID + +```UUID()``` + +Generate a uuid through java function. + +Example: + +select UUID() as seatunnel_uuid + +### ARRAY + +Generate an array. + +Example: + +select ARRAY('test1','test2','test3') as arrays + + +### LATERAL VIEW +#### EXPLODE + +explode array column to rows. +OUTER EXPLODE will return NULL, while array is NULL or empty +EXPLODE(SPLIT(FIELD_NAME,separator))Used to split string type. The first parameter of SPLIT function is the field name, the second parameter is the separator +EXPLODE(ARRAY(value1,value2)) Used to custom array type. +``` +SELECT * FROM fake + LATERAL VIEW EXPLODE ( SPLIT ( NAME, ',' ) ) AS NAME + LATERAL VIEW EXPLODE ( SPLIT ( pk_id, ';' ) ) AS pk_id + LATERAL VIEW OUTER EXPLODE ( age ) AS age + LATERAL VIEW OUTER EXPLODE ( ARRAY(1,1) ) AS num +``` diff --git a/docs/en/transform-v2/sql-udf.md b/docs/en/transform-v2/sql-udf.md index df5d3b93fe5..a857fe4c51f 100644 --- a/docs/en/transform-v2/sql-udf.md +++ b/docs/en/transform-v2/sql-udf.md @@ -110,8 +110,8 @@ We use UDF of SQL query to transform the source data like this: ``` transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" query = "select id, example(name) as name, age from fake" } } diff --git a/docs/en/transform-v2/sql.md b/docs/en/transform-v2/sql.md index a3bdb9bbfc1..a8f12568d53 100644 --- a/docs/en/transform-v2/sql.md +++ b/docs/en/transform-v2/sql.md @@ -12,11 +12,11 @@ SQL transform use memory SQL engine, we can via SQL functions and ability of SQL | name | type | required | default value | |-------------------|--------|----------|---------------| -| source_table_name | string | yes | - | -| result_table_name | string | yes | - | +| plugin_input | string | yes | - | +| plugin_output | string | yes | - | | query | string | yes | - | -### source_table_name [string] +### plugin_input [string] The source table name, the query SQL table name must match this field. @@ -43,8 +43,8 @@ We use SQL query to transform the source data like this: ``` transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" query = "select id, concat(name, '_') as name, age+1 as age from fake where id>0" } } @@ -66,7 +66,7 @@ if your upstream data schema is like this: ```hacon source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 string.template = ["innerQuery"] schema = { @@ -123,7 +123,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -137,15 +137,15 @@ source { transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" query = "select id, concat(name, '_') as name, age+1 as age from fake where id>0" } } sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` diff --git a/docs/en/transform-v2/transform-multi-table.md b/docs/en/transform-v2/transform-multi-table.md new file mode 100644 index 00000000000..e642ec9cd2d --- /dev/null +++ b/docs/en/transform-v2/transform-multi-table.md @@ -0,0 +1,128 @@ +--- +sidebar_position: 2 +--- + +# Multi-Table Transform in SeaTunnel + +SeaTunnel’s transform feature supports multi-table transformations, which is especially useful when the upstream plugin outputs multiple tables. This allows you to complete all necessary transformation operations within a single transform configuration. Currently, many connectors in SeaTunnel support multi-table outputs, such as `JDBCSource` and `MySQL-CDC`. All transforms can be configured for multi-table transform as described below. + +:::tip + +Multi-table Transform has no limitations on Transform capabilities; any Transform configuration can be used in a multi-table Transform. The purpose of multi-table Transform is to handle multiple tables in the data stream individually and merge the Transform configurations of multiple tables into one Transform for easier management. + +::: + +## Properties + +| Name | Type | Required | Default | Description | +|----------------------------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| table_match_regex | String | No | .* | A regular expression to match the tables that require transformation. By default, it matches all tables. Note that this table name refers to the actual upstream table name, not `result_table_name`. | +| table_transform | List | No | - | You can use a list in `table_transform` to specify rules for individual tables. If a transformation rule is configured for a specific table in `table_transform`, the outer rules will not apply to that table. The rules in `table_transform` take precedence. | +| table_transform.table_path | String | No | - | When configuring a transformation rule for a table in `table_transform`, you need to specify the table path using the `table_path` field. The table path should include `databaseName[.schemaName].tableName`. | + +## Matching Logic + +Suppose we read five tables from upstream: `test.abc`, `test.abcd`, `test.xyz`, `test.xyzxyz`, and `test.www`. They share the same structure, each having three fields: `id`, `name`, and `age`. + +| id | name | age | + +Now, let's say we want to copy the data from these five tables using the Copy transform with the following specific requirements: +- For tables `test.abc` and `test.abcd`, we need to copy the `name` field to a new field `name1`. +- For `test.xyz`, we want to copy the `name` field to `name2`. +- For `test.xyzxyz`, we want to copy the `name` field to `name3`. +- For `test.www`, no changes are needed. + +We can configure this as follows: + +```hocon +transform { + Copy { + source_table_name = "fake" // Optional dataset name to read from + result_table_name = "fake1" // Optional dataset name for output + + table_match_regex = "test.a.*" // 1. Matches tables needing transformation, here matching `test.abc` and `test.abcd` + src_field = "name" // Source field + dest_field = "name1" // Destination field + + table_transform = [{ + table_path = "test.xyz" // 2. Specifies the table name for transformation + src_field = "name" // Source field + dest_field = "name2" // Destination field + }, { + table_path = "test.xyzxyz" + src_field = "name" + dest_field = "name3" + }] + } +} +``` + +### Explanation + +1. With the regular expression and corresponding Copy transform options, we match tables `test.abc` and `test.abcd` and copy the `name` field to `name1`. +2. Using the `table_transform` configuration, we specify that for table `test.xyz`, the `name` field should be copied to `name2`. + +This allows us to handle transformations for multiple tables within a single transform configuration. + +For each table, the priority of configuration is: `table_transform` > `table_match_regex`. If no rules match a table, no transformation will be applied. + +Below are the transform configurations for each table: + +- **test.abc** and **test.abcd** + +```hocon +transform { + Copy { + src_field = "name" + dest_field = "name1" + } +} +``` + +Output structure: + +| id | name | age | name1 | + +- **test.xyz** + +```hocon +transform { + Copy { + src_field = "name" + dest_field = "name2" + } +} +``` + +Output structure: + +| id | name | age | name2 | + +- **test.xyzxyz** + +```hocon +transform { + Copy { + src_field = "name" + dest_field = "name3" + } +} +``` + +Output structure: + +| id | name | age | name3 | + +- **test.www** + +```hocon +transform { + // No transformation needed +} +``` + +Output structure: + +| id | name | age | + +In this example, we used the Copy transform, but all transforms in SeaTunnel support multi-table transformations, and you can configure them similarly within the corresponding transform block. \ No newline at end of file diff --git a/docs/images/icons/Apache Iceberg.svg b/docs/images/icons/Apache Iceberg.svg new file mode 100644 index 00000000000..d04e866a0f6 --- /dev/null +++ b/docs/images/icons/Apache Iceberg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Doris.svg b/docs/images/icons/Doris.svg new file mode 100644 index 00000000000..2729c9a6985 --- /dev/null +++ b/docs/images/icons/Doris.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/FtpFile.svg b/docs/images/icons/FtpFile.svg new file mode 100644 index 00000000000..4cf14476e97 --- /dev/null +++ b/docs/images/icons/FtpFile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Greenplum.svg b/docs/images/icons/Greenplum.svg new file mode 100644 index 00000000000..ead7dc6bfeb --- /dev/null +++ b/docs/images/icons/Greenplum.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git "a/docs/images/icons/Hdfs\346\226\207\344\273\266.svg" "b/docs/images/icons/Hdfs\346\226\207\344\273\266.svg" new file mode 100644 index 00000000000..7bc4a938f74 --- /dev/null +++ "b/docs/images/icons/Hdfs\346\226\207\344\273\266.svg" @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Hive.svg b/docs/images/icons/Hive.svg new file mode 100644 index 00000000000..70859e23b97 --- /dev/null +++ b/docs/images/icons/Hive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/HiveJdbc.svg b/docs/images/icons/HiveJdbc.svg new file mode 100644 index 00000000000..70859e23b97 --- /dev/null +++ b/docs/images/icons/HiveJdbc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Http.svg b/docs/images/icons/Http.svg new file mode 100644 index 00000000000..e9fcaf50aca --- /dev/null +++ b/docs/images/icons/Http.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/InfluxDB.svg b/docs/images/icons/InfluxDB.svg new file mode 100644 index 00000000000..a0bd1c639b6 --- /dev/null +++ b/docs/images/icons/InfluxDB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/IoTDB.svg b/docs/images/icons/IoTDB.svg new file mode 100644 index 00000000000..1aad0988b75 --- /dev/null +++ b/docs/images/icons/IoTDB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/JDBC.svg b/docs/images/icons/JDBC.svg new file mode 100644 index 00000000000..00365006920 --- /dev/null +++ b/docs/images/icons/JDBC.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Jira.svg b/docs/images/icons/Jira.svg new file mode 100644 index 00000000000..e49c6d768f9 --- /dev/null +++ b/docs/images/icons/Jira.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Kafka.png b/docs/images/icons/Kafka.png deleted file mode 100644 index a4b5359b866..00000000000 Binary files a/docs/images/icons/Kafka.png and /dev/null differ diff --git a/docs/images/icons/Kafka.svg b/docs/images/icons/Kafka.svg new file mode 100644 index 00000000000..094d598c4c2 --- /dev/null +++ b/docs/images/icons/Kafka.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Kingbase.svg b/docs/images/icons/Kingbase.svg new file mode 100644 index 00000000000..65a72ff2122 --- /dev/null +++ b/docs/images/icons/Kingbase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Klaviyo.svg b/docs/images/icons/Klaviyo.svg new file mode 100644 index 00000000000..77f75c139fa --- /dev/null +++ b/docs/images/icons/Klaviyo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/LocalFile.svg b/docs/images/icons/LocalFile.svg new file mode 100644 index 00000000000..414c3dde3b9 --- /dev/null +++ b/docs/images/icons/LocalFile.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Maxcompute.svg b/docs/images/icons/Maxcompute.svg new file mode 100644 index 00000000000..dca95d03c36 --- /dev/null +++ b/docs/images/icons/Maxcompute.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Milvus.svg b/docs/images/icons/Milvus.svg new file mode 100644 index 00000000000..a057c16e418 --- /dev/null +++ b/docs/images/icons/Milvus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/MySQL CDC.svg b/docs/images/icons/MySQL CDC.svg new file mode 100644 index 00000000000..92cca4e38d0 --- /dev/null +++ b/docs/images/icons/MySQL CDC.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Notion.svg b/docs/images/icons/Notion.svg new file mode 100644 index 00000000000..3c6e3b0f72f --- /dev/null +++ b/docs/images/icons/Notion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/ObsFile.png b/docs/images/icons/ObsFile.png new file mode 100644 index 00000000000..be943c607ac Binary files /dev/null and b/docs/images/icons/ObsFile.png differ diff --git a/docs/images/icons/OceanBase.svg b/docs/images/icons/OceanBase.svg new file mode 100644 index 00000000000..e4589987ea6 --- /dev/null +++ b/docs/images/icons/OceanBase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/OneSignal.svg b/docs/images/icons/OneSignal.svg new file mode 100644 index 00000000000..8f0c26700da --- /dev/null +++ b/docs/images/icons/OneSignal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/OpenMldb.png b/docs/images/icons/OpenMldb.png new file mode 100644 index 00000000000..b66e8dedef4 Binary files /dev/null and b/docs/images/icons/OpenMldb.png differ diff --git a/docs/images/icons/Oracle CDC.svg b/docs/images/icons/Oracle CDC.svg new file mode 100644 index 00000000000..9f739d77862 --- /dev/null +++ b/docs/images/icons/Oracle CDC.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Oracle.svg b/docs/images/icons/Oracle.svg new file mode 100644 index 00000000000..c4865624c3e --- /dev/null +++ b/docs/images/icons/Oracle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Paimon.svg b/docs/images/icons/Paimon.svg new file mode 100644 index 00000000000..9dac157fdb6 --- /dev/null +++ b/docs/images/icons/Paimon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Persistiq.svg b/docs/images/icons/Persistiq.svg new file mode 100644 index 00000000000..2ab14f08a78 --- /dev/null +++ b/docs/images/icons/Persistiq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Phoenix.svg b/docs/images/icons/Phoenix.svg new file mode 100644 index 00000000000..6fa6e48a403 --- /dev/null +++ b/docs/images/icons/Phoenix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/PostgreSQL CDC.svg b/docs/images/icons/PostgreSQL CDC.svg new file mode 100644 index 00000000000..38547f16078 --- /dev/null +++ b/docs/images/icons/PostgreSQL CDC.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/PostgreSQL.svg b/docs/images/icons/PostgreSQL.svg new file mode 100644 index 00000000000..38547f16078 --- /dev/null +++ b/docs/images/icons/PostgreSQL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Pulsar.svg b/docs/images/icons/Pulsar.svg new file mode 100644 index 00000000000..cabedf1e022 --- /dev/null +++ b/docs/images/icons/Pulsar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Qdrant.svg b/docs/images/icons/Qdrant.svg new file mode 100644 index 00000000000..b431d111a6a --- /dev/null +++ b/docs/images/icons/Qdrant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Rabbitmq.svg b/docs/images/icons/Rabbitmq.svg new file mode 100644 index 00000000000..a4ecbc6cfbf --- /dev/null +++ b/docs/images/icons/Rabbitmq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Redis.svg b/docs/images/icons/Redis.svg new file mode 100644 index 00000000000..4cbd41cada9 --- /dev/null +++ b/docs/images/icons/Redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/RocketMQ.svg b/docs/images/icons/RocketMQ.svg new file mode 100644 index 00000000000..3fd2c1adba9 --- /dev/null +++ b/docs/images/icons/RocketMQ.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/S3File.svg b/docs/images/icons/S3File.svg new file mode 100644 index 00000000000..ddd50aeff00 --- /dev/null +++ b/docs/images/icons/S3File.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/SQL Server.svg b/docs/images/icons/SQL Server.svg new file mode 100644 index 00000000000..db4b76ca740 --- /dev/null +++ b/docs/images/icons/SQL Server.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Sftp.svg b/docs/images/icons/Sftp.svg new file mode 100644 index 00000000000..2a8015eb504 --- /dev/null +++ b/docs/images/icons/Sftp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Snowflake.svg b/docs/images/icons/Snowflake.svg new file mode 100644 index 00000000000..fb4c2868fba --- /dev/null +++ b/docs/images/icons/Snowflake.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/images/icons/StarRocks.svg b/docs/images/icons/StarRocks.svg new file mode 100644 index 00000000000..10a52bbf355 --- /dev/null +++ b/docs/images/icons/StarRocks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/TDengine.svg b/docs/images/icons/TDengine.svg new file mode 100644 index 00000000000..588347b3727 --- /dev/null +++ b/docs/images/icons/TDengine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Tablestore.svg b/docs/images/icons/Tablestore.svg new file mode 100644 index 00000000000..24526c988b9 --- /dev/null +++ b/docs/images/icons/Tablestore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/icons/Typesense.png b/docs/images/icons/Typesense.png new file mode 100644 index 00000000000..f25cc7e9e71 Binary files /dev/null and b/docs/images/icons/Typesense.png differ diff --git a/docs/images/icons/Web3j.png b/docs/images/icons/Web3j.png new file mode 100644 index 00000000000..ec031cb3280 Binary files /dev/null and b/docs/images/icons/Web3j.png differ diff --git a/docs/images/ui/detail.png b/docs/images/ui/detail.png new file mode 100644 index 00000000000..a376b6e4880 Binary files /dev/null and b/docs/images/ui/detail.png differ diff --git a/docs/images/ui/finished.png b/docs/images/ui/finished.png new file mode 100644 index 00000000000..fa800bd6029 Binary files /dev/null and b/docs/images/ui/finished.png differ diff --git a/docs/images/ui/master.png b/docs/images/ui/master.png new file mode 100644 index 00000000000..5e42d2854ee Binary files /dev/null and b/docs/images/ui/master.png differ diff --git a/docs/images/ui/overview.png b/docs/images/ui/overview.png new file mode 100644 index 00000000000..67123532499 Binary files /dev/null and b/docs/images/ui/overview.png differ diff --git a/docs/images/ui/running.png b/docs/images/ui/running.png new file mode 100644 index 00000000000..889edb303b1 Binary files /dev/null and b/docs/images/ui/running.png differ diff --git a/docs/images/ui/workers.png b/docs/images/ui/workers.png new file mode 100644 index 00000000000..a2bf39ec218 Binary files /dev/null and b/docs/images/ui/workers.png differ diff --git a/docs/sidebars.js b/docs/sidebars.js index e3bb42f9e3f..3257181b11a 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -202,10 +202,12 @@ const sidebars = { "seatunnel-engine/engine-jar-storage-mode", "seatunnel-engine/tcp", "seatunnel-engine/resource-isolation", - "seatunnel-engine/rest-api", + "seatunnel-engine/rest-api-v1", + "seatunnel-engine/rest-api-v2", "seatunnel-engine/user-command", "seatunnel-engine/logging", - "seatunnel-engine/telemetry" + "seatunnel-engine/telemetry", + "seatunnel-engine/web-ui" ] }, { @@ -224,6 +226,7 @@ const sidebars = { 'contribution/new-license', 'contribution/coding-guide', 'contribution/contribute-transform-v2-guide', + 'contribution/how-to-create-your-connector' ], }, "faq" diff --git a/docs/zh/about.md b/docs/zh/about.md index c938cc7b62e..244b27af1ae 100644 --- a/docs/zh/about.md +++ b/docs/zh/about.md @@ -62,7 +62,7 @@ SeaTunnel 拥有大量用户。 您可以在[用户](https://seatunnel.apache.or

  

-SeaTunnel 丰富了CNCF 云原生景观。 +SeaTunnel 丰富了CNCF 云原生景观

## 了解更多 diff --git a/docs/zh/concept/config.md b/docs/zh/concept/config.md index 2206441e801..98bf85735fe 100644 --- a/docs/zh/concept/config.md +++ b/docs/zh/concept/config.md @@ -15,6 +15,12 @@ 配置文件类似下面这个例子: +:::warn + +旧的配置名称 `result_table_name`/`source_table_name` 已经过时,请尽快迁移到新名称 `plugin_output`/`plugin_input`。 + +::: + ### hocon ```hocon @@ -24,7 +30,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -38,8 +44,8 @@ source { transform { Filter { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" fields = [name, card] } } @@ -52,7 +58,7 @@ sink { fields = ["name", "card"] username = "default" password = "" - source_table_name = "fake1" + plugin_input = "fake1" } } ``` @@ -72,7 +78,7 @@ sink { source用于定义SeaTunnel在哪儿检索数据,并将检索的数据用于下一步。 可以同时定义多个source。目前支持的source请看[Source of SeaTunnel](../../en/connector-v2/source)。每种source都有自己特定的参数用来 -定义如何检索数据,SeaTunnel也抽象了每种source所使用的参数,例如 `result_table_name` 参数,用于指定当前source生成的数据的名称, +定义如何检索数据,SeaTunnel也抽象了每种source所使用的参数,例如 `plugin_output` 参数,用于指定当前source生成的数据的名称, 方便后续其他模块使用。 ### transform @@ -87,7 +93,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -107,7 +113,7 @@ sink { fields = ["name", "age", "card"] username = "default" password = "" - source_table_name = "fake1" + plugin_input = "fake1" } } ``` @@ -123,10 +129,10 @@ sink模块,你可以快速高效地完成这个操作。Sink和source非常相 ### 其它 -你会疑惑当定义了多个source和多个sink时,每个sink读取哪些数据,每个transform读取哪些数据?我们使用`result_table_name` 和 -`source_table_name` 两个配置。每个source模块都会配置一个`result_table_name`来指示数据源生成的数据源名称,其它transform和sink -模块可以使用`source_table_name` 引用相应的数据源名称,表示要读取数据进行处理。然后transform,作为一个中间的处理模块,可以同时使用 -`result_table_name` 和 `source_table_name` 配置。但你会发现在上面的配置例子中,不是每个模块都配置了这些参数,因为在SeaTunnel中, +你会疑惑当定义了多个source和多个sink时,每个sink读取哪些数据,每个transform读取哪些数据?我们使用`plugin_output` 和 +`plugin_input` 两个配置。每个source模块都会配置一个`plugin_output`来指示数据源生成的数据源名称,其它transform和sink +模块可以使用`plugin_input` 引用相应的数据源名称,表示要读取数据进行处理。然后transform,作为一个中间的处理模块,可以同时使用 +`plugin_output` 和 `plugin_input` 配置。但你会发现在上面的配置例子中,不是每个模块都配置了这些参数,因为在SeaTunnel中, 有一个默认的约定,如果这两个参数没有配置,则使用上一个节点的最后一个模块生成的数据。当只有一个source时这是非常方便的。 ## 多行文本支持 @@ -155,7 +161,7 @@ sql = """ select * from "table" """ "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 100, "schema": { "fields": { @@ -169,8 +175,8 @@ sql = """ select * from "table" """ "transform": [ { "plugin_name": "Filter", - "source_table_name": "fake", - "result_table_name": "fake1", + "plugin_input": "fake", + "plugin_output": "fake1", "fields": ["name", "card"] } ], @@ -183,7 +189,7 @@ sql = """ select * from "table" """ "fields": ["name", "card"], "username": "default", "password": "", - "source_table_name": "fake1" + "plugin_input": "fake1" } ] } @@ -218,7 +224,7 @@ env { source { FakeSource { - result_table_name = "${resName:fake_test}_table" + plugin_output = "${resName:fake_test}_table" row.num = "${rowNum:50}" string.template = ${strTemplate} int.template = [20, 21] @@ -233,8 +239,8 @@ source { transform { sql { - source_table_name = "${resName:fake_test}_table" - result_table_name = "sql" + plugin_input = "${resName:fake_test}_table" + plugin_output = "sql" query = "select * from ${resName:fake_test}_table where name = '${nameVal}' " } @@ -242,7 +248,7 @@ transform { sink { Console { - source_table_name = "sql" + plugin_input = "sql" username = ${username} password = ${password} } @@ -277,7 +283,7 @@ env { source { FakeSource { - result_table_name = "fake_test_table" + plugin_output = "fake_test_table" row.num = 50 string.template = ['abc','d~f','hi'] int.template = [20, 21] @@ -292,8 +298,8 @@ source { transform { sql { - source_table_name = "fake_test_table" - result_table_name = "sql" + plugin_input = "fake_test_table" + plugin_output = "sql" query = "select * from fake_test_table where name = 'abc' " } @@ -301,7 +307,7 @@ transform { sink { Console { - source_table_name = "sql" + plugin_input = "sql" username = "seatunnel=2.3.1" password = "$a^b%c.d~e0*9(" } diff --git a/docs/zh/concept/schema-evolution.md b/docs/zh/concept/schema-evolution.md index 16f0f5dbc81..57d562946ff 100644 --- a/docs/zh/concept/schema-evolution.md +++ b/docs/zh/concept/schema-evolution.md @@ -6,15 +6,19 @@ ### 源 [Mysql-CDC](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/MySQL-CDC.md) +[Oracle-CDC](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/Oracle-CDC.md) ### 目标 [Jdbc-Mysql](https://github.com/apache/seatunnel/blob/dev/docs/zh/connector-v2/sink/Jdbc.md) +[Jdbc-Oracle](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/sink/Jdbc.md) -注意: 目前模式演进不支持transform. +注意: 目前模式演进不支持transform。不同类型数据库(Oracle-CDC -> Jdbc-Mysql)的模式演进目前不支持ddl中列的默认值。 +当你使用Oracle-CDC时,你不能使用用户名`SYS`或`SYSTEM`来修改表结构,否则ddl事件将被过滤,这可能导致模式演进不起作用; +另外,如果你的表名以`ORA_TEMP_`开头,也会有相同的问题。 ## 启用Schema evolution功能 -在CDC源连接器中模式演进默认是关闭的。你需要在CDC连接器中配置`debezium.include.schema.changes = true`来启用它。 +在CDC源连接器中模式演进默认是关闭的。你需要在CDC连接器中配置`debezium.include.schema.changes = true`来启用它。当你使用Oracle-CDC并且启用schema-evolution时,你必须将`debezium`属性中的`log.mining.strategy`指定为`redo_log_catalog`。 ## 示例 @@ -57,3 +61,92 @@ sink { } } ``` + +### Oracle-cdc -> Jdbc-Oracle +``` +env { + # You can set engine configuration here + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 +} + +source { + # This is a example source plugin **only for test and demonstrate the feature source plugin** + Oracle-CDC { + plugin_output = "customers" + username = "dbzuser" + password = "dbz" + database-names = ["ORCLCDB"] + schema-names = ["DEBEZIUM"] + table-names = ["ORCLCDB.DEBEZIUM.FULL_TYPES"] + base-url = "jdbc:oracle:thin:@oracle-host:1521/ORCLCDB" + source.reader.close.timeout = 120000 + connection.pool.size = 1 + debezium { + include.schema.changes = true + log.mining.strategy = redo_log_catalog + } + } +} + +sink { + Jdbc { + plugin_input = "customers" + driver = "oracle.jdbc.driver.OracleDriver" + url = "jdbc:oracle:thin:@oracle-host:1521/ORCLCDB" + user = "dbzuser" + password = "dbz" + generate_sink_sql = true + database = "ORCLCDB" + table = "DEBEZIUM.FULL_TYPES_SINK" + batch_size = 1 + primary_keys = ["ID"] + connection.pool.size = 1 + } +} +``` + +### Oracle-cdc -> Jdbc-Mysql +``` +env { + # You can set engine configuration here + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 +} + +source { + # This is a example source plugin **only for test and demonstrate the feature source plugin** + Oracle-CDC { + plugin_output = "customers" + username = "dbzuser" + password = "dbz" + database-names = ["ORCLCDB"] + schema-names = ["DEBEZIUM"] + table-names = ["ORCLCDB.DEBEZIUM.FULL_TYPES"] + base-url = "jdbc:oracle:thin:@oracle-host:1521/ORCLCDB" + source.reader.close.timeout = 120000 + connection.pool.size = 1 + debezium { + include.schema.changes = true + log.mining.strategy = redo_log_catalog + } + } +} + +sink { + jdbc { + plugin_input = "customers" + url = "jdbc:mysql://oracle-host:3306/oracle_sink" + driver = "com.mysql.cj.jdbc.Driver" + user = "st_user_sink" + password = "mysqlpw" + generate_sink_sql = true + # You need to configure both database and table + database = oracle_sink + table = oracle_cdc_2_mysql_sink_table + primary_keys = ["ID"] + } +} +``` diff --git a/docs/zh/concept/schema-feature.md b/docs/zh/concept/schema-feature.md index d719a7953e5..b504d264f83 100644 --- a/docs/zh/concept/schema-feature.md +++ b/docs/zh/concept/schema-feature.md @@ -172,6 +172,46 @@ constraintKeys = [ | INDEX_KEY | 键 | | UNIQUE_KEY | 唯一键 | +## 多表Schema + +``` +tables_configs = [ + { + schema { + table = "database.schema.table1" + schema_first = false + comment = "comment" + columns = [ + ... + ] + primaryKey { + ... + } + constraintKeys { + ... + } + } + }, + { + schema = { + table = "database.schema.table2" + schema_first = false + comment = "comment" + columns = [ + ... + ] + primaryKey { + ... + } + constraintKeys { + ... + } + } + } +] + +``` + ## 如何使用schema ### 推荐 @@ -180,7 +220,7 @@ constraintKeys = [ source { FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema { table = "FakeDatabase.FakeTable" @@ -234,7 +274,7 @@ source { source { FakeSource { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/zh/connector-v2/Config-Encryption-Decryption.md b/docs/zh/connector-v2/Config-Encryption-Decryption.md index e7b13aea86a..c2b7ced0ab6 100644 --- a/docs/zh/connector-v2/Config-Encryption-Decryption.md +++ b/docs/zh/connector-v2/Config-Encryption-Decryption.md @@ -43,7 +43,7 @@ Base64编码支持加密以下参数: source { MySQL-CDC { - result_table_name = "fake" + plugin_output = "fake" parallelism = 1 server-id = 5656 port = 56725 @@ -97,7 +97,7 @@ Base64编码支持加密以下参数: "port" : 56725, "database-name" : "inventory_vwyw0n", "parallelism" : 1, - "result_table_name" : "fake", + "plugin_output" : "fake", "table-name" : "products", "plugin_name" : "MySQL-CDC", "server-id" : 5656, diff --git a/docs/zh/connector-v2/formats/avro.md b/docs/zh/connector-v2/formats/avro.md index 7176f4e507f..826fc27b448 100644 --- a/docs/zh/connector-v2/formats/avro.md +++ b/docs/zh/connector-v2/formats/avro.md @@ -51,7 +51,7 @@ source { } } } - result_table_name = "fake" + plugin_output = "fake" } } @@ -76,7 +76,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "test_avro_topic" - result_table_name = "kafka_table" + plugin_output = "kafka_table" start_mode = "earliest" format = avro format_error_handle_way = skip @@ -104,7 +104,7 @@ source { sink { Console { - source_table_name = "kafka_table" + plugin_input = "kafka_table" } } ``` diff --git a/docs/zh/connector-v2/formats/canal-json.md b/docs/zh/connector-v2/formats/canal-json.md index 92c4338eb56..fc3344d963c 100644 --- a/docs/zh/connector-v2/formats/canal-json.md +++ b/docs/zh/connector-v2/formats/canal-json.md @@ -86,7 +86,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "products_binlog" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/zh/connector-v2/formats/cdc-compatible-debezium-json.md b/docs/zh/connector-v2/formats/cdc-compatible-debezium-json.md index e34a5b39a22..8febab18fb0 100644 --- a/docs/zh/connector-v2/formats/cdc-compatible-debezium-json.md +++ b/docs/zh/connector-v2/formats/cdc-compatible-debezium-json.md @@ -17,7 +17,7 @@ env { source { MySQL-CDC { - result_table_name = "table1" + plugin_output = "table1" base-url="jdbc:mysql://localhost:3306/test" "startup.mode"=INITIAL @@ -43,9 +43,10 @@ source { sink { Kafka { - source_table_name = "table1" + plugin_input = "table1" bootstrap.servers = "localhost:9092" + topic = "${topic}" # compatible_debezium_json options format = compatible_debezium_json diff --git a/docs/zh/connector-v2/formats/debezium-json.md b/docs/zh/connector-v2/formats/debezium-json.md index 3e70a5d31ed..88b32540395 100644 --- a/docs/zh/connector-v2/formats/debezium-json.md +++ b/docs/zh/connector-v2/formats/debezium-json.md @@ -85,7 +85,7 @@ source { Kafka { bootstrap.servers = "kafkaCluster:9092" topic = "products_binlog" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/zh/connector-v2/formats/kafka-compatible-kafkaconnect-json.md b/docs/zh/connector-v2/formats/kafka-compatible-kafkaconnect-json.md index d0ceb58ac6c..027d90ded07 100644 --- a/docs/zh/connector-v2/formats/kafka-compatible-kafkaconnect-json.md +++ b/docs/zh/connector-v2/formats/kafka-compatible-kafkaconnect-json.md @@ -16,7 +16,7 @@ source { Kafka { bootstrap.servers = "localhost:9092" topic = "jdbc_source_record" - result_table_name = "kafka_table" + plugin_output = "kafka_table" start_mode = earliest schema = { fields { diff --git a/docs/zh/connector-v2/formats/ogg-json.md b/docs/zh/connector-v2/formats/ogg-json.md index 7b64f5b5e41..80c88e6ac13 100644 --- a/docs/zh/connector-v2/formats/ogg-json.md +++ b/docs/zh/connector-v2/formats/ogg-json.md @@ -66,7 +66,7 @@ source { Kafka { bootstrap.servers = "127.0.0.1:9092" topic = "ogg" - result_table_name = "kafka_name" + plugin_output = "kafka_name" start_mode = earliest schema = { fields { diff --git a/docs/zh/connector-v2/formats/protobuf.md b/docs/zh/connector-v2/formats/protobuf.md new file mode 100644 index 00000000000..5fac7f93211 --- /dev/null +++ b/docs/zh/connector-v2/formats/protobuf.md @@ -0,0 +1,164 @@ +# Protobuf 格式 + +Protobuf(Protocol Buffers)是一种由Google开发的语言中立、平台无关的数据序列化格式。它提供了一种高效的方式来编码结构化数据,同时支持多种编程语言和平台。 + +目前支持在 Kafka 中使用 protobuf 格式。 + +## Kafka 使用示例 + +- 模拟随机生成数据源,并以 protobuf 的格式 写入 kafka 的实例 + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + parallelism = 1 + plugin_output = "fake" + row.num = 16 + schema = { + fields { + c_int32 = int + c_int64 = long + c_float = float + c_double = double + c_bool = boolean + c_string = string + c_bytes = bytes + + Address { + city = string + state = string + street = string + } + attributes = "map" + phone_numbers = "array" + } + } + } +} + +sink { + kafka { + topic = "test_protobuf_topic_fake_source" + bootstrap.servers = "kafkaCluster:9092" + format = protobuf + kafka.request.timeout.ms = 60000 + kafka.config = { + acks = "all" + request.timeout.ms = 60000 + buffer.memory = 33554432 + } + protobuf_message_name = Person + protobuf_schema = """ + syntax = "proto3"; + + package org.apache.seatunnel.format.protobuf; + + option java_outer_classname = "ProtobufE2E"; + + message Person { + int32 c_int32 = 1; + int64 c_int64 = 2; + float c_float = 3; + double c_double = 4; + bool c_bool = 5; + string c_string = 6; + bytes c_bytes = 7; + + message Address { + string street = 1; + string city = 2; + string state = 3; + string zip = 4; + } + + Address address = 8; + + map attributes = 9; + + repeated string phone_numbers = 10; + } + """ + } +} +``` + +- 从 kafka 读取 protobuf 格式的数据并打印到控制台的示例 + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + Kafka { + topic = "test_protobuf_topic_fake_source" + format = protobuf + protobuf_message_name = Person + protobuf_schema = """ + syntax = "proto3"; + + package org.apache.seatunnel.format.protobuf; + + option java_outer_classname = "ProtobufE2E"; + + message Person { + int32 c_int32 = 1; + int64 c_int64 = 2; + float c_float = 3; + double c_double = 4; + bool c_bool = 5; + string c_string = 6; + bytes c_bytes = 7; + + message Address { + string street = 1; + string city = 2; + string state = 3; + string zip = 4; + } + + Address address = 8; + + map attributes = 9; + + repeated string phone_numbers = 10; + } + """ + schema = { + fields { + c_int32 = int + c_int64 = long + c_float = float + c_double = double + c_bool = boolean + c_string = string + c_bytes = bytes + + Address { + city = string + state = string + street = string + } + attributes = "map" + phone_numbers = "array" + } + } + bootstrap.servers = "kafkaCluster:9092" + start_mode = "earliest" + plugin_output = "kafka_table" + } +} + +sink { + Console { + plugin_input = "kafka_table" + } +} +``` + diff --git a/docs/zh/connector-v2/sink-common-options.md b/docs/zh/connector-v2/sink-common-options.md index f9dd8c33918..2944181fb8e 100644 --- a/docs/zh/connector-v2/sink-common-options.md +++ b/docs/zh/connector-v2/sink-common-options.md @@ -6,16 +6,22 @@ sidebar_position: 4 > Sink 连接器常用参数 -| 名称 | 类型 | 是否需要 | 默认值 | -|-------------------|--------|------|-----| -| source_table_name | string | 否 | - | -| parallelism | int | 否 | - | +:::warn -### source_table_name [string] +旧的配置名称 `source_table_name` 已经过时,请尽快迁移到新名称 `plugin_input`。 -当不指定 `source_table_name` 时,当前插件处理配置文件中上一个插件输出的数据集 `dataset` +::: -当指定了 `source_table_name` 时,当前插件正在处理该参数对应的数据集 +| 名称 | 类型 | 是否需要 | 默认值 | +|--------------|--------|------|-----| +| plugin_input | string | 否 | - | +| parallelism | int | 否 | - | + +### plugin_input [string] + +当不指定 `plugin_input` 时,当前插件处理配置文件中上一个插件输出的数据集 `dataset` + +当指定了 `plugin_input` 时,当前插件正在处理该参数对应的数据集 ### parallelism [int] @@ -29,34 +35,34 @@ sidebar_position: 4 source { FakeSourceStream { parallelism = 2 - result_table_name = "fake" + plugin_output = "fake" field_name = "name,age" } } transform { Filter { - source_table_name = "fake" + plugin_input = "fake" fields = [name] - result_table_name = "fake_name" + plugin_output = "fake_name" } Filter { - source_table_name = "fake" + plugin_input = "fake" fields = [age] - result_table_name = "fake_age" + plugin_output = "fake_age" } } sink { Console { - source_table_name = "fake_name" + plugin_input = "fake_name" } Console { - source_table_name = "fake_age" + plugin_input = "fake_age" } } ``` -> 如果作业只有一个 source 和一个(或零个)transform 和一个 sink ,则不需要为连接器指定 `source_table_name` 和 `result_table_name`。 -> 如果 source 、transform 和 sink 中任意运算符的数量大于 1,则必须为作业中的每个连接器指定 `source_table_name` 和 `result_table_name` +> 如果作业只有一个 source 和一个(或零个)transform 和一个 sink ,则不需要为连接器指定 `plugin_input` 和 `plugin_output`。 +> 如果 source 、transform 和 sink 中任意运算符的数量大于 1,则必须为作业中的每个连接器指定 `plugin_input` 和 `plugin_output` diff --git a/docs/zh/connector-v2/sink/Assert.md b/docs/zh/connector-v2/sink/Assert.md index ca6d4199207..af94a45ba83 100644 --- a/docs/zh/connector-v2/sink/Assert.md +++ b/docs/zh/connector-v2/sink/Assert.md @@ -268,13 +268,13 @@ source { ] } ] - result_table_name = "fake" + plugin_output = "fake" } } sink{ Assert { - source_table_name = "fake" + plugin_input = "fake" rules = { row_rules = [ diff --git a/docs/zh/connector-v2/sink/Console.md b/docs/zh/connector-v2/sink/Console.md index 50788d97d1d..19702acb3d1 100644 --- a/docs/zh/connector-v2/sink/Console.md +++ b/docs/zh/connector-v2/sink/Console.md @@ -44,7 +44,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" schema = { fields { name = "string" @@ -56,7 +56,7 @@ source { sink { Console { - source_table_name = "fake" + plugin_input = "fake" } } ``` @@ -73,7 +73,7 @@ env { source { FakeSource { - result_table_name = "fake1" + plugin_output = "fake1" schema = { fields { id = "int" @@ -84,7 +84,7 @@ source { } } FakeSource { - result_table_name = "fake2" + plugin_output = "fake2" schema = { fields { name = "string" @@ -96,10 +96,10 @@ source { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } Console { - source_table_name = "fake2" + plugin_input = "fake2" } } ``` diff --git a/docs/zh/connector-v2/sink/HdfsFile.md b/docs/zh/connector-v2/sink/HdfsFile.md index c0310896559..81081bad94d 100644 --- a/docs/zh/connector-v2/sink/HdfsFile.md +++ b/docs/zh/connector-v2/sink/HdfsFile.md @@ -87,7 +87,7 @@ source { # 这是一个示例源插件 **仅用于测试和演示功能源插件** FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/zh/connector-v2/sink/Hive.md b/docs/zh/connector-v2/sink/Hive.md new file mode 100644 index 00000000000..b0741ed0e23 --- /dev/null +++ b/docs/zh/connector-v2/sink/Hive.md @@ -0,0 +1,465 @@ +# Hive + +> Hive Sink 连接器 + +## 描述 + +将数据写入 Hive。 + +:::提示 + +为了使用此连接器,您必须确保您的 Spark/Flink 集群已经集成了 Hive。测试过的 Hive 版本是 2.3.9 和 3.1.3。 + +如果您使用 SeaTunnel 引擎,您需要将 `seatunnel-hadoop3-3.1.4-uber.jar`、`hive-exec-3.1.3.jar` 和 `libfb303-0.9.3.jar` 放在 `$SEATUNNEL_HOME/lib/` 目录中。 +::: + +## 关键特性 + +- [x] [支持多表写入](../../concept/connector-v2-features.md) +- [x] [精确一次](../../concept/connector-v2-features.md) + +默认情况下,我们使用 2PC 提交来确保“精确一次”。 + +- [x] 文件格式 + - [x] 文本 + - [x] CSV + - [x] Parquet + - [x] ORC + - [x] JSON +- [x] 压缩编解码器 + - [x] LZO + +## 选项 + +| 名称 | 类型 | 必需 | 默认值 | +|-------------------------------|---------|------|---------| +| table_name | string | 是 | - | +| metastore_uri | string | 是 | - | +| compress_codec | string | 否 | none | +| hdfs_site_path | string | 否 | - | +| hive_site_path | string | 否 | - | +| hive.hadoop.conf | Map | 否 | - | +| hive.hadoop.conf-path | string | 否 | - | +| krb5_path | string | 否 | /etc/krb5.conf | +| kerberos_principal | string | 否 | - | +| kerberos_keytab_path | string | 否 | - | +| abort_drop_partition_metadata | boolean | 否 | true | +| common-options | | 否 | - | + +### table_name [string] + +目标 Hive 表名,例如:`db1.table1`。如果源是多模式,您可以使用 `${database_name}.${table_name}` 来生成表名,它将用源生成的 CatalogTable 的值替换 `${database_name}` 和 `${table_name}`。 + +### metastore_uri [string] + +Hive 元存储 URI + +### hdfs_site_path [string] + +`hdfs-site.xml` 的路径,用于加载 Namenode 的高可用配置 + +### hive_site_path [string] + +`hive-site.xml` 的路径 + +### hive.hadoop.conf [map] + +Hadoop 配置中的属性(`core-site.xml`、`hdfs-site.xml`、`hive-site.xml`) + +### hive.hadoop.conf-path [string] + +指定加载 `core-site.xml`、`hdfs-site.xml`、`hive-site.xml` 文件的路径 + +### krb5_path [string] + +`krb5.conf` 的路径,用于 Kerberos 认证 + +`hive-site.xml` 的路径,用于 Hive 元存储认证 + +### kerberos_principal [string] + +Kerberos 的主体 + +### kerberos_keytab_path [string] + +Kerberos 的 keytab 文件路径 + +### abort_drop_partition_metadata [boolean] + +在中止操作期间是否从 Hive Metastore 中删除分区元数据的标志。注意:这只影响元存储中的元数据,分区中的数据将始终被删除(同步过程中生成的数据)。 + +### 通用选项 + +Sink 插件的通用参数,请参阅 [Sink Common Options](../sink-common-options.md) 了解详细信息。 + +## 示例 + +```bash + Hive { + table_name = "default.seatunnel_orc" + metastore_uri = "thrift://namenode001:9083" + } +``` + +### 示例 1 + +我们有一个源表如下: + +```bash +create table test_hive_source( + test_tinyint TINYINT, + test_smallint SMALLINT, + test_int INT, + test_bigint BIGINT, + test_boolean BOOLEAN, + test_float FLOAT, + test_double DOUBLE, + test_string STRING, + test_binary BINARY, + test_timestamp TIMESTAMP, + test_decimal DECIMAL(8,2), + test_char CHAR(64), + test_varchar VARCHAR(64), + test_date DATE, + test_array ARRAY, + test_map MAP, + test_struct STRUCT + ) +PARTITIONED BY (test_par1 STRING, test_par2 STRING); +``` + +我们需要从源表读取数据并写入另一个表: + +```bash +create table test_hive_sink_text_simple( + test_tinyint TINYINT, + test_smallint SMALLINT, + test_int INT, + test_bigint BIGINT, + test_boolean BOOLEAN, + test_float FLOAT, + test_double DOUBLE, + test_string STRING, + test_binary BINARY, + test_timestamp TIMESTAMP, + test_decimal DECIMAL(8,2), + test_char CHAR(64), + test_varchar VARCHAR(64), + test_date DATE + ) +PARTITIONED BY (test_par1 STRING, test_par2 STRING); +``` + +作业配置文件可以如下: + +``` +env { + parallelism = 3 + job.name="test_hive_source_to_hive" +} + +source { + Hive { + table_name = "test_hive.test_hive_source" + metastore_uri = "thrift://ctyun7:9083" + } +} + +sink { + # 选择 stdout 输出插件将数据输出到控制台 + + Hive { + table_name = "test_hive.test_hive_sink_text_simple" + metastore_uri = "thrift://ctyun7:9083" + hive.hadoop.conf = { + bucket = "s3a://mybucket" + fs.s3a.aws.credentials.provider="com.amazonaws.auth.InstanceProfileCredentialsProvider" + } +} +``` + +### 示例 2:Kerberos + +```bash +sink { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} +``` + +描述: + +- `hive_site_path`:`hive-site.xml` 文件的路径。 +- `kerberos_principal`:Kerberos 认证的主体。 +- `kerberos_keytab_path`:Kerberos 认证的 keytab 文件路径。 +- `krb5_path`:用于 Kerberos 认证的 `krb5.conf` 文件路径。 + +运行案例: + +```bash +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + pk_id = bigint + name = string + score = int + } + primaryKey { + name = "pk_id" + columnNames = [pk_id] + } + } + rows = [ + { + kind = INSERT + fields = [1, "A", 100] + }, + { + kind = INSERT + fields = [2, "B", 100] + }, + { + kind = INSERT + fields = [3, "C", 100] + } + ] + } +} + +sink { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} +``` + +## Hive on s3 + +### 步骤 1 + +为 EMR 的 Hive 创建 lib 目录。 + +```shell +mkdir -p ${SEATUNNEL_HOME}/plugins/Hive/lib +``` + +### 步骤 2 + +从 Maven 中心获取 jar 文件到 lib。 + +```shell +cd ${SEATUNNEL_HOME}/plugins/Hive/lib +wget https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/2.6.5/hadoop-aws-2.6.5.jar +wget https://repo1.maven.org/maven2/org/apache/hive/hive-exec/2.3.9/hive-exec-2.3.9.jar +``` + +### 步骤 3 + +从您的 EMR 环境中复制 jar 文件到 lib 目录。 + +```shell +cp /usr/share/aws/emr/emrfs/lib/emrfs-hadoop-assembly-2.60.0.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +cp /usr/share/aws/emr/hadoop-state-pusher/lib/hadoop-common-3.3.6-amzn-1.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +cp /usr/share/aws/emr/hadoop-state-pusher/lib/javax.inject-1.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +cp /usr/share/aws/emr/hadoop-state-pusher/lib/aopalliance-1.0.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +``` + +### 步骤 4 + +运行案例。 + +```shell +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + pk_id = bigint + name = string + score = int + } + primaryKey { + name = "pk_id" + columnNames = [pk_id] + } + } + rows = [ + { + kind = INSERT + fields = [1, "A", 100] + }, + { + kind = INSERT + fields = [2, "B", 100] + }, + { + kind = INSERT + fields = [3, "C", 100] + } + ] + } +} + +sink { + Hive { + table_name = "test_hive.test_hive_sink_on_s3" + metastore_uri = "thrift://ip-192-168-0-202.cn-north-1.compute.internal:9083" + hive.hadoop.conf-path = "/home/ec2-user/hadoop-conf" + hive.hadoop.conf = { + bucket="s3://ws-package" + fs.s3a.aws.credentials.provider="com.amazonaws.auth.InstanceProfileCredentialsProvider" + } + } +} +``` + +## Hive on oss + +### 步骤 1 + +为 EMR 的 Hive 创建 lib 目录。 + +```shell +mkdir -p ${SEATUNNEL_HOME}/plugins/Hive/lib +``` + +### 步骤 2 + +从 Maven 中心获取 jar 文件到 lib。 + +```shell +cd ${SEATUNNEL_HOME}/plugins/Hive/lib +wget https://repo1.maven.org/maven2/org/apache/hive/hive-exec/2.3.9/hive-exec-2.3.9.jar +``` + +### 步骤 3 + +从您的 EMR 环境中复制 jar 文件到 lib 目录并删除冲突的 jar。 + +```shell +cp -r /opt/apps/JINDOSDK/jindosdk-current/lib/jindo-*.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +rm -f ${SEATUNNEL_HOME}/lib/hadoop-aliyun-*.jar +``` + +### 步骤 4 + +运行案例。 + +```shell +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + pk_id = bigint + name = string + score = int + } + primaryKey { + name = "pk_id" + columnNames = [pk_id] + } + } + rows = [ + { + kind = INSERT + fields = [1, "A", 100] + }, + { + kind = INSERT + fields = [2, "B", 100] + }, + { + kind = INSERT + fields = [3, "C", 100] + } + ] + } +} + +sink { + Hive { + table_name = "test_hive.test_hive_sink_on_oss" + metastore_uri = "thrift://master-1-1.c-1009b01725b501f2.cn-wulanchabu.emr.aliyuncs.com:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + hive.hadoop.conf = { + bucket="oss://emr-osshdfs.cn-wulanchabu.oss-dls.aliyuncs.com" + } + } +} +``` + +### 示例 2 + +我们有多个源表如下: + +```bash +create table test_1( +) +PARTITIONED BY (xx); + +create table test_2( +) +PARTITIONED BY (xx); +... +``` + +我们需要从这些源表读取数据并写入其他表: + +作业配置文件可以如下: + +``` +env { + # 您可以在此处设置 Flink 配置 + parallelism = 3 + job.name="test_hive_source_to_hive" +} + +source { + Hive { + tables_configs = [ + { + table_name = "test_hive.test_1" + metastore_uri = "thrift://ctyun6:9083" + }, + { + table_name = "test_hive.test_2" + metastore_uri = "thrift://ctyun7:9083" + } + ] + } +} + +sink { + # 选择 stdout 输出插件将数据输出到控制台 + Hive { + table_name = "${database_name}.${table_name}" + metastore_uri = "thrift://ctyun7:9083" + } +} +``` diff --git a/docs/zh/connector-v2/sink/Hudi.md b/docs/zh/connector-v2/sink/Hudi.md index 2fbf0271358..7d8007f6b03 100644 --- a/docs/zh/connector-v2/sink/Hudi.md +++ b/docs/zh/connector-v2/sink/Hudi.md @@ -8,7 +8,7 @@ ## 主要特点 -- [x] [exactly-once](../../concept/connector-v2-features.md) +- [ ] [exactly-once](../../concept/connector-v2-features.md) - [x] [cdc](../../concept/connector-v2-features.md) - [x] [support multiple table write](../../concept/connector-v2-features.md) @@ -21,7 +21,6 @@ | table_dfs_path | string | 是 | - | | conf_files_path | string | 否 | - | | table_list | string | 否 | - | -| auto_commit | boolean| 否 | true | | schema_save_mode | enum | 否 | CREATE_SCHEMA_WHEN_NOT_EXIST | | common-options | config | 否 | - | @@ -44,6 +43,7 @@ | index_type | enum | no | BLOOM | | index_class_name | string | no | - | | record_byte_size | Int | no | 1024 | +| cdc_enabled | boolean| no | false | 注意: 当此配置对应于单个表时,您可以将table_list中的配置项展平到外层。 @@ -115,9 +115,9 @@ `max_commits_to_keep` Hudi 表保留的最多提交数。 -### auto_commit [boolean] +### cdc_enabled [boolean] -`auto_commit` 是否自动提交. +`cdc_enabled` 是否持久化Hudi表的CDC变更日志。启用后,在必要时持久化更改数据,表可以作为CDC模式进行查询. ### schema_save_mode [Enum] diff --git a/docs/zh/connector-v2/sink/Jdbc.md b/docs/zh/connector-v2/sink/Jdbc.md index e1ab422952e..4370af20026 100644 --- a/docs/zh/connector-v2/sink/Jdbc.md +++ b/docs/zh/connector-v2/sink/Jdbc.md @@ -216,26 +216,27 @@ Sink插件常用参数,请参考 [Sink常用选项](../sink-common-options.md) 附录参数仅提供参考 -| 数据源 | driver | url | xa_data_source_class_name | maven | -|------------|----------------------------------------------|--------------------------------------------------------------------|----------------------------------------------------|-------------------------------------------------------------------------------------------------------------| -| MySQL | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | com.mysql.cj.jdbc.MysqlXADataSource | https://mvnrepository.com/artifact/mysql/mysql-connector-java | -| PostgreSQL | org.postgresql.Driver | jdbc:postgresql://localhost:5432/postgres | org.postgresql.xa.PGXADataSource | https://mvnrepository.com/artifact/org.postgresql/postgresql | -| DM | dm.jdbc.driver.DmDriver | jdbc:dm://localhost:5236 | dm.jdbc.driver.DmdbXADataSource | https://mvnrepository.com/artifact/com.dameng/DmJdbcDriver18 | -| Phoenix | org.apache.phoenix.queryserver.client.Driver | jdbc:phoenix:thin:url=http://localhost:8765;serialization=PROTOBUF | / | https://mvnrepository.com/artifact/com.aliyun.phoenix/ali-phoenix-shaded-thin-client | -| SQL Server | com.microsoft.sqlserver.jdbc.SQLServerDriver | jdbc:sqlserver://localhost:1433 | com.microsoft.sqlserver.jdbc.SQLServerXADataSource | https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc | -| Oracle | oracle.jdbc.OracleDriver | jdbc:oracle:thin:@localhost:1521/xepdb1 | oracle.jdbc.xa.OracleXADataSource | https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 | -| sqlite | org.sqlite.JDBC | jdbc:sqlite:test.db | / | https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc | -| GBase8a | com.gbase.jdbc.Driver | jdbc:gbase://e2e_gbase8aDb:5258/test | / | https://cdn.gbase.cn/products/30/p5CiVwXBKQYIUGN8ecHvk/gbase-connector-java-9.5.0.7-build1-bin.jar | -| StarRocks | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | / | https://mvnrepository.com/artifact/mysql/mysql-connector-java | -| db2 | com.ibm.db2.jcc.DB2Driver | jdbc:db2://localhost:50000/testdb | com.ibm.db2.jcc.DB2XADataSource | https://mvnrepository.com/artifact/com.ibm.db2.jcc/db2jcc/db2jcc4 | -| saphana | com.sap.db.jdbc.Driver | jdbc:sap://localhost:39015 | / | https://mvnrepository.com/artifact/com.sap.cloud.db.jdbc/ngdbc | -| Doris | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | / | https://mvnrepository.com/artifact/mysql/mysql-connector-java | -| teradata | com.teradata.jdbc.TeraDriver | jdbc:teradata://localhost/DBS_PORT=1025,DATABASE=test | / | https://mvnrepository.com/artifact/com.teradata.jdbc/terajdbc | -| Redshift | com.amazon.redshift.jdbc42.Driver | jdbc:redshift://localhost:5439/testdb | com.amazon.redshift.xa.RedshiftXADataSource | https://mvnrepository.com/artifact/com.amazon.redshift/redshift-jdbc42 | -| Snowflake | net.snowflake.client.jdbc.SnowflakeDriver | jdbc:snowflake://.snowflakecomputing.com | / | https://mvnrepository.com/artifact/net.snowflake/snowflake-jdbc | -| Vertica | com.vertica.jdbc.Driver | jdbc:vertica://localhost:5433 | / | https://repo1.maven.org/maven2/com/vertica/jdbc/vertica-jdbc/12.0.3-0/vertica-jdbc-12.0.3-0.jar | -| Kingbase | com.kingbase8.Driver | jdbc:kingbase8://localhost:54321/db_test | / | https://repo1.maven.org/maven2/cn/com/kingbase/kingbase8/8.6.0/kingbase8-8.6.0.jar | -| OceanBase | com.oceanbase.jdbc.Driver | jdbc:oceanbase://localhost:2881 | / | https://repo1.maven.org/maven2/com/oceanbase/oceanbase-client/2.4.11/oceanbase-client-2.4.11.jar | +| 数据源 | driver | url | xa_data_source_class_name | maven | +|------------|----------------------------------------------|--------------------------------------------------------------------|----------------------------------------------------|------------------------------------------------------------------------------------------------------| +| MySQL | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | com.mysql.cj.jdbc.MysqlXADataSource | https://mvnrepository.com/artifact/mysql/mysql-connector-java | +| PostgreSQL | org.postgresql.Driver | jdbc:postgresql://localhost:5432/postgres | org.postgresql.xa.PGXADataSource | https://mvnrepository.com/artifact/org.postgresql/postgresql | +| DM | dm.jdbc.driver.DmDriver | jdbc:dm://localhost:5236 | dm.jdbc.driver.DmdbXADataSource | https://mvnrepository.com/artifact/com.dameng/DmJdbcDriver18 | +| Phoenix | org.apache.phoenix.queryserver.client.Driver | jdbc:phoenix:thin:url=http://localhost:8765;serialization=PROTOBUF | / | https://mvnrepository.com/artifact/com.aliyun.phoenix/ali-phoenix-shaded-thin-client | +| SQL Server | com.microsoft.sqlserver.jdbc.SQLServerDriver | jdbc:sqlserver://localhost:1433 | com.microsoft.sqlserver.jdbc.SQLServerXADataSource | https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc | +| Oracle | oracle.jdbc.OracleDriver | jdbc:oracle:thin:@localhost:1521/xepdb1 | oracle.jdbc.xa.OracleXADataSource | https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 | +| sqlite | org.sqlite.JDBC | jdbc:sqlite:test.db | / | https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc | +| GBase8a | com.gbase.jdbc.Driver | jdbc:gbase://e2e_gbase8aDb:5258/test | / | https://cdn.gbase.cn/products/30/p5CiVwXBKQYIUGN8ecHvk/gbase-connector-java-9.5.0.7-build1-bin.jar | +| StarRocks | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | / | https://mvnrepository.com/artifact/mysql/mysql-connector-java | +| db2 | com.ibm.db2.jcc.DB2Driver | jdbc:db2://localhost:50000/testdb | com.ibm.db2.jcc.DB2XADataSource | https://mvnrepository.com/artifact/com.ibm.db2.jcc/db2jcc/db2jcc4 | +| saphana | com.sap.db.jdbc.Driver | jdbc:sap://localhost:39015 | / | https://mvnrepository.com/artifact/com.sap.cloud.db.jdbc/ngdbc | +| Doris | com.mysql.cj.jdbc.Driver | jdbc:mysql://localhost:3306/test | / | https://mvnrepository.com/artifact/mysql/mysql-connector-java | +| teradata | com.teradata.jdbc.TeraDriver | jdbc:teradata://localhost/DBS_PORT=1025,DATABASE=test | / | https://mvnrepository.com/artifact/com.teradata.jdbc/terajdbc | +| Redshift | com.amazon.redshift.jdbc42.Driver | jdbc:redshift://localhost:5439/testdb | com.amazon.redshift.xa.RedshiftXADataSource | https://mvnrepository.com/artifact/com.amazon.redshift/redshift-jdbc42 | +| Snowflake | net.snowflake.client.jdbc.SnowflakeDriver | jdbc:snowflake://.snowflakecomputing.com | / | https://mvnrepository.com/artifact/net.snowflake/snowflake-jdbc | +| Vertica | com.vertica.jdbc.Driver | jdbc:vertica://localhost:5433 | / | https://repo1.maven.org/maven2/com/vertica/jdbc/vertica-jdbc/12.0.3-0/vertica-jdbc-12.0.3-0.jar | +| Kingbase | com.kingbase8.Driver | jdbc:kingbase8://localhost:54321/db_test | / | https://repo1.maven.org/maven2/cn/com/kingbase/kingbase8/8.6.0/kingbase8-8.6.0.jar | +| OceanBase | com.oceanbase.jdbc.Driver | jdbc:oceanbase://localhost:2881 | / | https://repo1.maven.org/maven2/com/oceanbase/oceanbase-client/2.4.12/oceanbase-client-2.4.12.jar | +| opengauss | org.opengauss.Driver | jdbc:opengauss://localhost:5432/postgres | / | https://repo1.maven.org/maven2/org/opengauss/opengauss-jdbc/5.1.0-og/opengauss-jdbc-5.1.0-og.jar | ## 示例 diff --git a/docs/zh/connector-v2/sink/Kafka.md b/docs/zh/connector-v2/sink/Kafka.md index a7273c3b56c..c43b0d41664 100644 --- a/docs/zh/connector-v2/sink/Kafka.md +++ b/docs/zh/connector-v2/sink/Kafka.md @@ -112,7 +112,7 @@ env { source { FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { diff --git a/docs/zh/connector-v2/sink/Paimon.md b/docs/zh/connector-v2/sink/Paimon.md index 32d35a5e958..09f4e63fbfc 100644 --- a/docs/zh/connector-v2/sink/Paimon.md +++ b/docs/zh/connector-v2/sink/Paimon.md @@ -30,30 +30,40 @@ libfb303-xxx.jar ## 连接器选项 -| 名称 | 类型 | 是否必须 | 默认值 | 描述 | -|-----------------------------|-------|----------|------------------------------|---------------------------------------------------------------------------------------------------| -| warehouse | 字符串 | 是 | - | Paimon warehouse路径 | -| catalog_type | 字符串 | 否 | filesystem | Paimon的catalog类型,目前支持filesystem和hive | -| catalog_uri | 字符串 | 否 | - | Paimon catalog的uri,仅当catalog_type为hive时需要配置 | -| database | 字符串 | 是 | - | 数据库名称 | -| table | 字符串 | 是 | - | 表名 | -| hdfs_site_path | 字符串 | 否 | - | hdfs-site.xml文件路径 | -| schema_save_mode | 枚举 | 否 | CREATE_SCHEMA_WHEN_NOT_EXIST | Schema保存模式 | -| data_save_mode | 枚举 | 否 | APPEND_DATA | 数据保存模式 | -| paimon.table.primary-keys | 字符串 | 否 | - | 主键字段列表,联合主键使用逗号分隔(注意:分区字段需要包含在主键字段中) | -| paimon.table.partition-keys | 字符串 | 否 | - | 分区字段列表,多字段使用逗号分隔 | -| paimon.table.write-props | Map | 否 | - | Paimon表初始化指定的属性, [参考](https://paimon.apache.org/docs/master/maintenance/configurations/#coreoptions) | -| paimon.hadoop.conf | Map | 否 | - | Hadoop配置文件属性信息 | -| paimon.hadoop.conf-path | 字符串 | 否 | - | Hadoop配置文件目录,用于加载'core-site.xml', 'hdfs-site.xml', 'hive-site.xml'文件配置 | +| 名称 | 类型 | 是否必须 | 默认值 | 描述 | +|-----------------------------|------|------|------------------------------|-------------------------------------------------------------------------------------------------------| +| warehouse | 字符串 | 是 | - | Paimon warehouse路径 | +| catalog_type | 字符串 | 否 | filesystem | Paimon的catalog类型,目前支持filesystem和hive | +| catalog_uri | 字符串 | 否 | - | Paimon catalog的uri,仅当catalog_type为hive时需要配置 | +| database | 字符串 | 是 | - | 数据库名称 | +| table | 字符串 | 是 | - | 表名 | +| hdfs_site_path | 字符串 | 否 | - | hdfs-site.xml文件路径 | +| schema_save_mode | 枚举 | 否 | CREATE_SCHEMA_WHEN_NOT_EXIST | Schema保存模式 | +| data_save_mode | 枚举 | 否 | APPEND_DATA | 数据保存模式 | +| paimon.table.primary-keys | 字符串 | 否 | - | 主键字段列表,联合主键使用逗号分隔(注意:分区字段需要包含在主键字段中) | +| paimon.table.partition-keys | 字符串 | 否 | - | 分区字段列表,多字段使用逗号分隔 | +| paimon.table.write-props | Map | 否 | - | Paimon表初始化指定的属性, [参考](https://paimon.apache.org/docs/master/maintenance/configurations/#coreoptions) | +| paimon.hadoop.conf | Map | 否 | - | Hadoop配置文件属性信息 | +| paimon.hadoop.conf-path | 字符串 | 否 | - | Hadoop配置文件目录,用于加载'core-site.xml', 'hdfs-site.xml', 'hive-site.xml'文件配置 | ## 更新日志 你必须配置`changelog-producer=input`来启用paimon表的changelog产生模式。如果你使用了paimon sink的自动建表功能,你可以在`paimon.table.write-props`中指定这个属性。 Paimon表的changelog产生模式有[四种](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/),分别是`none`、`input`、`lookup` 和 `full-compaction`。 -目前,我们只支持`none`和`input`模式。默认是`none`,这种模式将不会产生changelog文件。`input`模式将会在Paimon表下产生changelog文件。 +目前支持全部`changelog-producer`模式。默认是`none`模式。 -当你使用流模式去读paimon表的数据时,这两种模式将会产生[不同的结果](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/Paimon.md#changelog)。 +* [`none`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#none) +* [`input`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#input) +* [`lookup`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#lookup) +* [`full-compaction`](https://paimon.apache.org/docs/master/primary-key-table/changelog-producer/#full-compaction) +> 注意: +> 当你使用流模式去读paimon表的数据时,不同模式将会产生[不同的结果](https://github.com/apache/seatunnel/blob/dev/docs/en/connector-v2/source/Paimon.md#changelog)。 + +## 文件系统 +Paimon连接器支持向多文件系统写入数据。目前支持的文件系统有hdfs和s3。 +如果您使用s3文件系统。您可以配置`fs.s3a.access-key `, `fs.s3a.secret-key`, `fs.s3a.endpoint`, `fs.s3a.path.style.access`, `fs.s3a.aws.credentials`。在`paimon.hadoop.conf`选项中设置提供程序的属性。 +除此之外,warehouse应该以`s3a://`开头。 ## 示例 @@ -88,6 +98,53 @@ sink { } ``` +### 单表(基于S3文件系统) + +```hocon +env { + execution.parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + c_map = "map" + c_array = "array" + c_string = string + c_boolean = boolean + c_tinyint = tinyint + c_smallint = smallint + c_int = int + c_bigint = bigint + c_float = float + c_double = double + c_bytes = bytes + c_date = date + c_decimal = "decimal(38, 18)" + c_timestamp = timestamp + } + } + } +} + +sink { + Paimon { + warehouse = "s3a://test/" + database = "seatunnel_namespace11" + table = "st_test" + paimon.hadoop.conf = { + fs.s3a.access-key=G52pnxg67819khOZ9ezX + fs.s3a.secret-key=SHJuAQqHsLrgZWikvMa3lJf5T0NfM5LMFliJh9HF + fs.s3a.endpoint="http://minio4:9000" + fs.s3a.path.style.access=true + fs.s3a.aws.credentials.provider=org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + } + } +} +``` + ### 单表(指定hadoop HA配置和kerberos配置) ```hocon @@ -248,6 +305,37 @@ sink { } } ``` +#### 使用`changelog-producer`属性写入 + +```hocon +env { + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 +} + +source { + Mysql-CDC { + base-url = "jdbc:mysql://127.0.0.1:3306/seatunnel" + username = "root" + password = "******" + table-names = ["seatunnel.role"] + } +} + +sink { + Paimon { + catalog_name = "seatunnel_test" + warehouse = "file:///tmp/seatunnel/paimon/hadoop-sink/" + database = "seatunnel" + table = "role" + paimon.table.write-props = { + changelog-producer = full-compaction + changelog-tmp-path = /tmp/paimon/changelog + } + } +} +``` ### 动态分桶paimon单表 diff --git a/docs/zh/connector-v2/sink/Prometheus.md b/docs/zh/connector-v2/sink/Prometheus.md new file mode 100644 index 00000000000..834d8128aa4 --- /dev/null +++ b/docs/zh/connector-v2/sink/Prometheus.md @@ -0,0 +1,101 @@ +# Prometheus + +> Prometheus 数据接收器 + +## 引擎支持 + +> Spark
+> Flink
+> SeaTunnel Zeta
+ +## 主要特性 + +- [ ] [exactly-once](../../concept/connector-v2-features.md) +- [ ] [cdc](../../concept/connector-v2-features.md) +- [x] [support multiple table write](../../concept/connector-v2-features.md) + +## 描述 + +接收Source端传入的数据,利用数据触发 web hooks。 + +> 例如,来自上游的数据为 [`label: {"__name__": "test1"}, value: 1.2.3,time:2024-08-15T17:00:00`], 则body内容如下: `{"label":{"__name__": "test1"}, "value":"1.23","time":"2024-08-15T17:00:00"}` + +**Tips: Prometheus 数据接收器 仅支持 `post json` 类型的 web hook,source 数据将被视为 webhook 中的 body 内容。并且不支持传递过去太久的数据** + +## 支持的数据源信息 + +想使用 Prometheus 连接器,需要安装以下必要的依赖。可以通过运行 install-plugin.sh 脚本或者从 Maven 中央仓库下载这些依赖 + +| 数据源 | 支持版本 | 依赖 | +|------|-----------|------------------------------------------------------------------------------------------------------------------| +| Http | universal | [Download](https://mvnrepository.com/artifact/org.apache.seatunnel/seatunnel-connectors-v2/connector-prometheus) | + +## 接收器选项 + +| Name | Type | Required | Default | Description | +|-----------------------------|--------|----------|---------|-------------------------------------------------------------------| +| url | String | Yes | - | Http 请求链接 | +| headers | Map | No | - | Http 标头 | +| retry | Int | No | - | 如果请求http返回`IOException`的最大重试次数 | +| retry_backoff_multiplier_ms | Int | No | 100 | http请求失败,重试回退次数(毫秒)乘数 | +| retry_backoff_max_ms | Int | No | 10000 | http请求失败,最大重试回退时间(毫秒) | +| connect_timeout_ms | Int | No | 12000 | 连接超时设置,默认12s | +| socket_timeout_ms | Int | No | 60000 | 套接字超时设置,默认为60s | +| key_timestamp | Int | NO | - | prometheus时间戳的key. | +| key_label | String | yes | - | prometheus标签的key | +| key_value | Double | yes | - | prometheus值的key | +| batch_size | Int | false | 1024 | prometheus批量写入大小 | +| flush_interval | Long | false | 300000L | prometheus定时写入 | +| common-options | | No | - | Sink插件常用参数,请参考 [Sink常用选项 ](../sink-common-options.md) 了解详情 | + +## 示例 + +简单示例: + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + c_map = "map" + c_double = double + c_timestamp = timestamp + } + } + plugin_output = "fake" + rows = [ + { + kind = INSERT + fields = [{"__name__": "test1"}, 1.23, "2024-08-15T17:00:00"] + }, + { + kind = INSERT + fields = [{"__name__": "test2"}, 1.23, "2024-08-15T17:00:00"] + } + ] + } +} + + +sink { + Prometheus { + url = "http://prometheus:9090/api/v1/write" + key_label = "c_map" + key_value = "c_double" + key_timestamp = "c_timestamp" + batch_size = 1 + } +} +``` + +## Changelog + +### 2.3.8-beta 2024-08-22 + +- 添加prometheus接收连接器 + diff --git a/docs/zh/connector-v2/sink/Pulsar.md b/docs/zh/connector-v2/sink/Pulsar.md index 1f92c876351..9f965dcf7ba 100644 --- a/docs/zh/connector-v2/sink/Pulsar.md +++ b/docs/zh/connector-v2/sink/Pulsar.md @@ -136,7 +136,7 @@ env { source { FakeSource { parallelism = 1 - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -152,7 +152,7 @@ sink { topic = "example" client.service-url = "localhost:pulsar://localhost:6650" admin.service-url = "http://my-broker.example.com:8080" - result_table_name = "test" + plugin_output = "test" pulsar.config = { sendTimeoutMs = 30000 } diff --git a/docs/zh/connector-v2/sink/Redis.md b/docs/zh/connector-v2/sink/Redis.md index b47d9de9146..d4bb13cd888 100644 --- a/docs/zh/connector-v2/sink/Redis.md +++ b/docs/zh/connector-v2/sink/Redis.md @@ -12,20 +12,25 @@ ## 选项 -| 名称 | 类型 | 是否必须 | 默认值 | -|----------------|--------|---------------------|--------| -| host | string | 是 | - | -| port | int | 是 | - | -| key | string | 是 | - | -| data_type | string | 是 | - | -| user | string | 否 | - | -| auth | string | 否 | - | -| db_num | int | 否 | 0 | -| mode | string | 否 | single | -| nodes | list | 当 mode=cluster 时为:是 | - | -| format | string | 否 | json | -| expire | long | 否 | -1 | -| common-options | | 否 | - | +| name | type | required | default value | +|--------------------|---------|-----------------------|---------------| +| host | string | yes | - | +| port | int | yes | - | +| key | string | yes | - | +| data_type | string | yes | - | +| batch_size | int | no | 10 | +| user | string | no | - | +| auth | string | no | - | +| db_num | int | no | 0 | +| mode | string | no | single | +| nodes | list | yes when mode=cluster | - | +| format | string | no | json | +| expire | long | no | -1 | +| support_custom_key | boolean | no | false | +| value_field | string | no | - | +| hash_key_field | string | no | - | +| hash_value_field | string | no | - | +| common-options | | no | - | ### host [string] @@ -48,13 +53,12 @@ Redis 端口 | 200 | 获取成功 | true | | 500 | 内部错误 | false | -如果将字段名称指定为 `code` 并将 data_type 设置为 `key`,将有两个数据写入 Redis: -1. `200 -> {code: 200, message: true, data: 获取成功}` -2. `500 -> {code: 500, message: false, data: 内部错误}` - -如果将字段名称指定为 `value` 并将 data_type 设置为 `key`,则由于上游数据的字段中没有 `value` 字段,将只有一个数据写入 Redis: - -1. `value -> {code: 500, message: false, data: 内部错误}` +如果将字段名称指定为 code 并将 data_type 设置为 key,将有两个数据写入 Redis: +1. `200 -> {code: 200, data: 获取成功, success: true}` +2. `500 -> {code: 500, data: 内部错误, success: false}` + +如果将字段名称指定为 value 并将 data_type 设置为 key,则由于上游数据的字段中没有 value 字段,将只有一个数据写入 Redis: +1. `value -> {code: 500, data: 内部错误, success: false}` 请参见 data_type 部分以了解具体的写入规则。 @@ -128,6 +132,59 @@ Redis 节点信息,在集群模式下使用,必须按如下格式: 设置 Redis 的过期时间,单位为秒。默认值为 -1,表示键不会自动过期。 +### support_custom_key [boolean] + +设置为true,表示启用自定义Key。 + +上游数据如下: + +| code | data | success | +|------|------|---------| +| 200 | 获取成功 | true | +| 500 | 内部错误 | false | + +可以使用`{`和`}`符号自定义Redis键名,`{}`中的字段名会被解析替换为上游数据中的某个字段值,例如:将字段名称指定为 `{code}` 并将 data_type 设置为 `key`,将有两个数据写入 Redis: +1. `200 -> {code: 200, data: 获取成功, success: true}` +2. `500 -> {code: 500, data: 内部错误, success: false}` + +Redis键名可以由固定部分和变化部分组成,通过Redis分组符号:连接,例如:将字段名称指定为 `code:{code}` 并将 data_type 设置为 `key`,将有两个数据写入 Redis: +1. `code:200 -> {code: 200, data: 获取成功, success: true}` +2. `code:500 -> {code: 500, data: 内部错误, success: false}` + +### value_field [string] + +要写入Redis的值的字段, `data_type` 支持 `key` `list` `set` `zset`. + +当你指定Redis键名字段`key`指定为 `value`,值字段`value_field`指定为`data`,并将`data_type`指定为`key`时, + +上游数据如下: + +| code | data | success | +|------|------|---------| +| 200 | 获取成功 | true | + +如下的数据会被写入Redis: +1. `value -> 获取成功` + +### hash_key_field [string] + +要写入Redis的hash键字段, `data_type` 支持 `hash` + +### hash_value_field [string] + +要写入Redis的hash值字段, `data_type` 支持 `hash` + +当你指定Redis键名字段`key`指定为 `value`,hash键字段`hash_key_field`指定为`data`,hash值字段`hash_value_field`指定为`success`,并将`data_type`指定为`hash`时, + +上游数据如下: + +| code | data | success | +|------|------|---------| +| 200 | 获取成功 | true | + +如下的数据会被写入Redis: +1. `value -> 获取成功 | true` + ### common options Sink 插件通用参数,请参考 [Sink Common Options](../sink-common-options.md) 获取详情 @@ -145,6 +202,43 @@ Redis { } ``` +自定义Key示例: + +```hocon +Redis { + host = localhost + port = 6379 + key = "name:{name}" + support_custom_key = true + data_type = key +} +``` + +自定义Value示例: + +```hocon +Redis { + host = localhost + port = 6379 + key = person + value_field = "name" + data_type = key +} +``` + +自定义HashKey和HashValue示例: + +```hocon +Redis { + host = localhost + port = 6379 + key = person + hash_key_field = "name" + hash_value_field = "age" + data_type = hash +} +``` + ## 更新日志 ### 2.2.0-beta 2022-09-26 diff --git a/docs/zh/connector-v2/sink/Sls.md b/docs/zh/connector-v2/sink/Sls.md new file mode 100644 index 00000000000..94e4f3c07a8 --- /dev/null +++ b/docs/zh/connector-v2/sink/Sls.md @@ -0,0 +1,84 @@ +# Sls + +> Sls sink connector + +## Support Those Engines + +> Spark
+> Flink
+> Seatunnel Zeta
+ +## 主要特性 + +- [ ] [exactly-once](../../concept/connector-v2-features.md) +- [ ] [cdc](../../concept/connector-v2-features.md) + +## 描述 + +Sink connector for Aliyun Sls. + +从写入数据到阿里云Sls日志服务 + +为了使用Sls连接器,需要以下依赖关系。 +它们可以通过install-plugin.sh或Maven中央存储库下载。 + +| Datasource | Supported Versions | Maven | +|------------|--------------------|-----------------------------------------------------------------------------------| +| Sls | Universal | [Download](https://mvnrepository.com/artifact/org.apache.seatunnel/connector-sls) | + +## 支持的数据源信息 + +| Name | Type | Required | Default | Description | +|-------------------------------------|----------|----------|-------------------|------------------------------------------------------------------------------------------------------------------------------------| +| project | String | Yes | - | [阿里云 Sls 项目](https://help.aliyun.com/zh/sls/user-guide/manage-a-project?spm=a2c4g.11186623.0.0.6f9755ebyfaYSl) | +| logstore | String | Yes | - | [阿里云 Sls 日志库](https://help.aliyun.com/zh/sls/user-guide/manage-a-logstore?spm=a2c4g.11186623.0.0.13137c08nfuiBC) | +| endpoint | String | Yes | - | [阿里云访问服务点](https://help.aliyun.com/zh/sls/developer-reference/api-sls-2020-12-30-endpoint?spm=a2c4g.11186623.0.0.548945a8UyJULa) | +| access_key_id | String | Yes | - | [阿里云访问用户ID](https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair?spm=a2c4g.11186623.0.0.4a6e4e554CKhSc#task-2245479) | +| access_key_secret | String | Yes | - | [阿里云访问用户密码](https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair?spm=a2c4g.11186623.0.0.4a6e4e554CKhSc#task-2245479) | +| source | String | No | SeaTunnel-Source | 在sls中数据来源标记 | +| topic | String | No | SeaTunnel-Topic | 在sls中数据主题标记 | + +## 任务示例 + +### 简单示例 + +> 此示例写入sls的logstore1的数据。如果您尚未安装和部署SeaTunnel,则需要按照安装SeaTunnel中的说明安装和部署SeaTunnel。然后按照[快速启动SeaTunnel引擎](../../Start-v2/locale/Quick-Start SeaTunnel Engine.md)中的说明运行此作业。 + +[创建RAM用户及授权](https://help.aliyun.com/zh/sls/create-a-ram-user-and-authorize-the-ram-user-to-access-log-service?spm=a2c4g.11186623.0.i4), 请确认RAM用户有足够的权限来读取及管理数据,参考:[RAM自定义授权示例](https://help.aliyun.com/zh/sls/use-custom-policies-to-grant-permissions-to-a-ram-user?spm=a2c4g.11186623.0.0.4a6e4e554CKhSc#reference-s3z-m1l-z2b) + +```hocon +# Defining the runtime environment +env { + parallelism = 2 + job.mode = "STREAMING" + checkpoint.interval = 30000 +} +source { + FakeSource { + row.num = 10 + map.size = 10 + array.size = 10 + bytes.length = 10 + string.length = 10 + schema = { + fields = { + id = "int" + name = "string" + description = "string" + weight = "string" + } + } + } +} + +sink { + Sls { + endpoint = "cn-hangzhou-intranet.log.aliyuncs.com" + project = "project1" + logstore = "logstore1" + access_key_id = "xxxxxxxxxxxxxxxxxxxxxxxx" + access_key_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } +} +``` + diff --git a/docs/zh/connector-v2/sink/Typesense.md b/docs/zh/connector-v2/sink/Typesense.md index 99017f32cb5..f6c06e5f2b5 100644 --- a/docs/zh/connector-v2/sink/Typesense.md +++ b/docs/zh/connector-v2/sink/Typesense.md @@ -79,7 +79,7 @@ Sink插件常用参数,请参考 [Sink常用选项](../sink-common-options.md) ```bash sink { Typesense { - source_table_name = "typesense_test_table" + plugin_input = "typesense_test_table" hosts = ["localhost:8108"] collection = "typesense_to_typesense_sink_with_query" max_retry_count = 3 diff --git a/docs/zh/connector-v2/source-common-options.md b/docs/zh/connector-v2/source-common-options.md index e8f8798d628..9a95c163390 100644 --- a/docs/zh/connector-v2/source-common-options.md +++ b/docs/zh/connector-v2/source-common-options.md @@ -6,14 +6,20 @@ sidebar_position: 3 > Source connector 的常用参数 -| 名称 | 类型 | 必填 | 默认值 | 描述 | -|-------------------|--------|----|-----|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| result_table_name | String | 否 | - | 当未指定 `result_table_name` 时,此插件处理的数据将不会被注册为可由其他插件直接访问的数据集 `(dataStream/dataset)`,或称为临时表 `(table)`。
当指定了 `result_table_name` 时,此插件处理的数据将被注册为可由其他插件直接访问的数据集 `(dataStream/dataset)`,或称为临时表 `(table)`。此处注册的数据集 `(dataStream/dataset)` 可通过指定 `source_table_name` 直接被其他插件访问。 | -| parallelism | Int | 否 | - | 当未指定 `parallelism` 时,默认使用环境中的 `parallelism`。
当指定了 `parallelism` 时,将覆盖环境中的 `parallelism` 设置。 | +:::warn + +旧的配置名称 `result_table_name` 已经过时,请尽快迁移到新名称 `plugin_output`。 + +::: + +| 名称 | 类型 | 必填 | 默认值 | 描述 | +|---------------|--------|----|-----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| plugin_output | String | 否 | - | 当未指定 `plugin_output` 时,此插件处理的数据将不会被注册为可由其他插件直接访问的数据集 `(dataStream/dataset)`,或称为临时表 `(table)`。
当指定了 `plugin_output` 时,此插件处理的数据将被注册为可由其他插件直接访问的数据集 `(dataStream/dataset)`,或称为临时表 `(table)`。此处注册的数据集 `(dataStream/dataset)` 可通过指定 `plugin_input` 直接被其他插件访问。 | +| parallelism | Int | 否 | - | 当未指定 `parallelism` 时,默认使用环境中的 `parallelism`。
当指定了 `parallelism` 时,将覆盖环境中的 `parallelism` 设置。 | # 重要提示 -在作业配置中使用 `result_table_name` 时,必须设置 `source_table_name` 参数。 +在作业配置中使用 `plugin_output` 时,必须设置 `plugin_input` 参数。 ## 任务示例 @@ -24,7 +30,7 @@ sidebar_position: 3 ```bash source { FakeSourceStream { - result_table_name = "fake_table" + plugin_output = "fake_table" } } ``` @@ -40,7 +46,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -64,9 +70,9 @@ source { transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" - # 查询表名必须与字段 'source_table_name' 相同 + plugin_input = "fake" + plugin_output = "fake1" + # 查询表名必须与字段 'plugin_input' 相同 query = "select id, regexp_replace(name, '.+', 'b') as name, age+1 as age, pi() as pi, c_timestamp, c_date, c_map, c_array, c_decimal, c_row from fake" } # SQL 转换支持基本函数和条件操作 @@ -75,10 +81,10 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } Console { - source_table_name = "fake" + plugin_input = "fake" } } ``` diff --git a/docs/zh/connector-v2/source/Doris.md b/docs/zh/connector-v2/source/Doris.md new file mode 100644 index 00000000000..ba3549473a5 --- /dev/null +++ b/docs/zh/connector-v2/source/Doris.md @@ -0,0 +1,212 @@ +# Doris + +> Doris 源连接器 + +## 支持的引擎 + +> Spark
+> Flink
+> SeaTunnel Zeta
+ +## 主要功能 + +- [x] [批处理](../../concept/connector-v2-features.md) +- [ ] [流处理](../../concept/connector-v2-features.md) +- [ ] [精确一次](../../concept/connector-v2-features.md) +- [x] [列投影](../../concept/connector-v2-features.md) +- [x] [并行度](../../concept/connector-v2-features.md) +- [x] [支持用户自定义分片](../../concept/connector-v2-features.md) +- [x] [支持多表读](../../concept/connector-v2-features.md) + +## 描述 + +用于 Apache Doris 的源连接器。 + +## 支持的数据源信息 + +| 数据源 | 支持版本 | 驱动 | Url | Maven | +|------------|--------------------------------------|--------|-----|-------| +| Doris | 仅支持Doris2.0及以上版本. | - | - | - | + +## 数据类型映射 + +| Doris 数据类型 | SeaTunnel 数据类型 | +|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| INT | INT | +| TINYINT | TINYINT | +| SMALLINT | SMALLINT | +| BIGINT | BIGINT | +| LARGEINT | STRING | +| BOOLEAN | BOOLEAN | +| DECIMAL | DECIMAL((Get the designated column's specified column size)+1,
(Gets the designated column's number of digits to right of the decimal point.))) | +| FLOAT | FLOAT | +| DOUBLE | DOUBLE | +| CHAR
VARCHAR
STRING
TEXT | STRING | +| DATE | DATE | +| DATETIME
DATETIME(p) | TIMESTAMP | +| ARRAY | ARRAY | + +## 源选项 + +基础配置: + +| 名称 | 类型 | 是否必须 | 默认值 | 描述 | +|----------------------------------|--------|----------|------------|-----------------------------------------------------------------------------------------------------| +| fenodes | string | yes | - | FE 地址, 格式:`"fe_host:fe_http_port"` | +| username | string | yes | - | 用户名 | +| password | string | yes | - | 密码 | +| doris.request.retries | int | no | 3 | 请求Doris FE的重试次数 | +| doris.request.read.timeout.ms | int | no | 30000 | | +| doris.request.connect.timeout.ms | int | no | 30000 | | +| query-port | string | no | 9030 | Doris查询端口 | +| doris.request.query.timeout.s | int | no | 3600 | Doris扫描数据的超时时间,单位秒 | +| table_list | string | 否 | - | 表清单 | + +表清单配置: + +| 名称 | 类型 | 是否必须 | 默认值 | 描述 | +|----------------------------------|--------|----------|------------|-----------------------------------------------------------------------------------------------------| +| database | string | yes | - | 数据库 | +| table | string | yes | - | 表名 | +| doris.read.field | string | no | - | 选择要读取的Doris表字段 | +| doris.filter.query | string | no | - | 数据过滤. 格式:"字段 = 值", 例如:doris.filter.query = "F_ID > 2" | +| doris.batch.size | int | no | 1024 | 每次能够从BE中读取到的最大行数 | +| doris.exec.mem.limit | long | no | 2147483648 | 单个be扫描请求可以使用的最大内存。默认内存为2G(2147483648) | + +注意: 当此配置对应于单个表时,您可以将table_list中的配置项展平到外层。 + +### 提示 + +> 不建议随意修改高级参数 + +## 例子 + +### 单表 +> 这是一个从doris读取数据后,输出到控制台的例子: + +``` +env { + parallelism = 2 + job.mode = "BATCH" +} +source{ + Doris { + fenodes = "doris_e2e:8030" + username = root + password = "" + database = "e2e_source" + table = "doris_e2e_table" + } +} + +transform { + # If you would like to get more information about how to configure seatunnel and see full list of transform plugins, + # please go to https://seatunnel.apache.org/docs/transform/sql +} + +sink { + Console {} +} +``` + +使用`doris.read.field`参数来选择需要读取的Doris表字段: + +``` +env { + parallelism = 2 + job.mode = "BATCH" +} +source{ + Doris { + fenodes = "doris_e2e:8030" + username = root + password = "" + database = "e2e_source" + table = "doris_e2e_table" + doris.read.field = "F_ID,F_INT,F_BIGINT,F_TINYINT,F_SMALLINT" + } +} + +transform { + # If you would like to get more information about how to configure seatunnel and see full list of transform plugins, + # please go to https://seatunnel.apache.org/docs/transform/sql +} + +sink { + Console {} +} +``` + +使用`doris.filter.query`来过滤数据,参数值将作为过滤条件直接传递到doris: + +``` +env { + parallelism = 2 + job.mode = "BATCH" +} +source{ + Doris { + fenodes = "doris_e2e:8030" + username = root + password = "" + database = "e2e_source" + table = "doris_e2e_table" + doris.filter.query = "F_ID > 2" + } +} + +transform { + # If you would like to get more information about how to configure seatunnel and see full list of transform plugins, + # please go to https://seatunnel.apache.org/docs/transform/sql +} + +sink { + Console {} +} +``` +### 多表 +``` +env{ + parallelism = 1 + job.mode = "BATCH" +} + +source{ + Doris { + fenodes = "xxxx:8030" + username = root + password = "" + table_list = [ + { + database = "st_source_0" + table = "doris_table_0" + doris.read.field = "F_ID,F_INT,F_BIGINT,F_TINYINT" + doris.filter.query = "F_ID >= 50" + }, + { + database = "st_source_1" + table = "doris_table_1" + } + ] + } +} + +transform {} + +sink{ + Doris { + fenodes = "xxxx:8030" + schema_save_mode = "RECREATE_SCHEMA" + username = root + password = "" + database = "st_sink" + table = "${table_name}" + sink.enable-2pc = "true" + sink.label-prefix = "test_json" + doris.config = { + format="json" + read_json_by_line="true" + } + } +} +``` diff --git a/docs/zh/connector-v2/source/HdfsFile.md b/docs/zh/connector-v2/source/HdfsFile.md index 0f983a80bcf..4de3014f5c0 100644 --- a/docs/zh/connector-v2/source/HdfsFile.md +++ b/docs/zh/connector-v2/source/HdfsFile.md @@ -39,7 +39,7 @@ ## 源选项 -| 名称 | 类型 | 是否必须 | 默认值 | 描述 | +| 名称 | 类型 | 是否必须 | 默认值 | 描述 | |---------------------------|---------|------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | path | string | 是 | - | 源文件路径。 | | file_format_type | string | 是 | - | 我们支持以下文件类型:`text` `json` `csv` `orc` `parquet` `excel`。请注意,最终文件名将以文件格式的后缀结束,文本文件的后缀是 `txt`。 | @@ -55,6 +55,8 @@ | kerberos_principal | string | 否 | - | kerberos 的 principal。 | | kerberos_keytab_path | string | 否 | - | kerberos 的 keytab 路径。 | | skip_header_row_number | long | 否 | 0 | 跳过前几行,但仅适用于 txt 和 csv。例如,设置如下:`skip_header_row_number = 2`。然后 Seatunnel 将跳过源文件中的前两行。 | +| file_filter_pattern | string | 否 | - | 过滤模式,用于过滤文件。 | +| null_format | string | 否 | - | 定义哪些字符串可以表示为 null,但仅适用于 txt 和 csv. 例如: `\N` | | schema | config | 否 | - | 上游数据的模式字段。 | | sheet_name | string | 否 | - | 读取工作簿的表格,仅在文件格式为 excel 时使用。 | | compress_codec | string | 否 | none | 文件的压缩编解码器。 | @@ -64,6 +66,60 @@ **delimiter** 参数在版本 2.3.5 后将被弃用,请改用 **field_delimiter**。 +### file_filter_pattern [string] + +过滤模式,用于过滤文件。 + +这个过滤规则遵循正则表达式. 关于详情,请参考 https://en.wikipedia.org/wiki/Regular_expression 学习 + +这里是一些例子. + +文件清单: +``` +/data/seatunnel/20241001/report.txt +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +/data/seatunnel/20241012/logo.png +``` +匹配规则: + +**例子 1**: *匹配所有txt为后缀名的文件*,匹配正则为: +``` +/data/seatunnel/20241001/.*\.txt +``` +匹配的结果是: +``` +/data/seatunnel/20241001/report.txt +``` +**例子 2**: *匹配所有文件名以abc开头的文件*,匹配正则为: +``` +/data/seatunnel/20241002/abc.* +``` +匹配的结果是: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +``` +**例子 3**: *匹配所有文件名以abc开头,并且文件第四个字母是 h 或者 g 的文件*, 匹配正则为: +``` +/data/seatunnel/20241007/abc[h,g].* +``` +匹配的结果是: +``` +/data/seatunnel/20241007/abch202410.csv +``` +**例子 4**: *匹配所有文件夹第三级以 202410 开头并且文件后缀名是.csv的文件*, 匹配正则为: +``` +/data/seatunnel/202410\d*/.*\.csv +``` +匹配的结果是: +``` +/data/seatunnel/20241007/abch202410.csv +/data/seatunnel/20241002/abcg202410.csv +/data/seatunnel/20241005/old_data.csv +``` + ### compress_codec [string] 文件的压缩编解码器及支持的详细信息如下所示: @@ -125,3 +181,25 @@ sink { } ``` +### Filter File + +```hocon +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + HdfsFile { + path = "/apps/hive/demo/student" + file_format_type = "json" + fs.defaultFS = "hdfs://namenode001" + file_filter_pattern = "abc[DX]*.*" + } +} + +sink { + Console { + } +} +``` \ No newline at end of file diff --git a/docs/zh/connector-v2/source/Hive.md b/docs/zh/connector-v2/source/Hive.md new file mode 100644 index 00000000000..00a322cc408 --- /dev/null +++ b/docs/zh/connector-v2/source/Hive.md @@ -0,0 +1,368 @@ +# Hive + +> Hive 源连接器 + +## 描述 + +从 Hive 读取数据。 + +:::提示 + +为了使用此连接器,您必须确保您的 Spark/Flink 集群已经集成了 Hive。测试过的 Hive 版本是 2.3.9 和 3.1.3。 + +如果您使用 SeaTunnel 引擎,您需要将 `seatunnel-hadoop3-3.1.4-uber.jar`、`hive-exec-3.1.3.jar` 和 `libfb303-0.9.3.jar` 放在 `$SEATUNNEL_HOME/lib/` 目录中。 +::: + +## 关键特性 + +- [x] [批处理](../../concept/connector-v2-features.md) +- [ ] [流处理](../../concept/connector-v2-features.md) +- [x] [精确一次](../../concept/connector-v2-features.md) + +在 `pollNext` 调用中读取分片中的所有数据。读取的分片将保存在快照中。 + +- [x] [schema 投影](../../concept/connector-v2-features.md) +- [x] [并行度](../../concept/connector-v2-features.md) +- [ ] [支持用户定义的分片](../../concept/connector-v2-features.md) +- [x] 文件格式 + - [x] 文本 + - [x] CSV + - [x] Parquet + - [x] ORC + - [x] JSON + +## 选项 + +| 名称 | 类型 | 必需 | 默认值 | +|-----------------------|--------|------|---------| +| table_name | string | 是 | - | +| metastore_uri | string | 是 | - | +| krb5_path | string | 否 | /etc/krb5.conf | +| kerberos_principal | string | 否 | - | +| kerberos_keytab_path | string | 否 | - | +| hdfs_site_path | string | 否 | - | +| hive_site_path | string | 否 | - | +| hive.hadoop.conf | Map | 否 | - | +| hive.hadoop.conf-path | string | 否 | - | +| read_partitions | list | 否 | - | +| read_columns | list | 否 | - | +| compress_codec | string | 否 | none | +| common-options | | 否 | - | + +### table_name [string] + +目标 Hive 表名,例如:`db1.table1` + +### metastore_uri [string] + +Hive 元存储 URI + +### hdfs_site_path [string] + +`hdfs-site.xml` 的路径,用于加载 Namenode 的高可用配置 + +### hive.hadoop.conf [map] + +Hadoop 配置中的属性(`core-site.xml`、`hdfs-site.xml`、`hive-site.xml`) + +### hive.hadoop.conf-path [string] + +指定加载 `core-site.xml`、`hdfs-site.xml`、`hive-site.xml` 文件的路径 + +### read_partitions [list] + +用户希望从 Hive 表中读取的目标分区,如果用户未设置此参数,将读取 Hive 表中的所有数据。 + +**提示:分区列表中的每个分区应具有相同的目录层级。例如,一个 Hive 表有两个分区:`par1` 和 `par2`,如果用户设置如下:** +**`read_partitions = [par1=xxx, par1=yyy/par2=zzz]`,这是不合法的** + +### krb5_path [string] + +`krb5.conf` 的路径,用于 Kerberos 认证 + +### kerberos_principal [string] + +Kerberos 认证的主体 + +### kerberos_keytab_path [string] + +Kerberos 认证的 keytab 文件路径 + +### read_columns [list] + +数据源的读取列列表,用户可以使用它来实现字段投影。 + +### compress_codec [string] + +文件的压缩编解码器,支持的详细信息如下所示: + +- txt: `lzo` `none` +- json: `lzo` `none` +- csv: `lzo` `none` +- orc/parquet: + 自动识别压缩类型,无需额外设置。 + +### 通用选项 + +源插件的通用参数,请参阅 [Source Common Options](../source-common-options.md) 了解详细信息。 + +## 示例 + +### 示例 1:单表 + +```bash + Hive { + table_name = "default.seatunnel_orc" + metastore_uri = "thrift://namenode001:9083" + } +``` + +### 示例 2:多表 +> 注意:Hive 是结构化数据源,应使用 `table_list`,`tables_configs` 将在未来移除。 + +```bash + Hive { + table_list = [ + { + table_name = "default.seatunnel_orc_1" + metastore_uri = "thrift://namenode001:9083" + }, + { + table_name = "default.seatunnel_orc_2" + metastore_uri = "thrift://namenode001:9083" + } + ] + } +``` + +```bash + Hive { + tables_configs = [ + { + table_name = "default.seatunnel_orc_1" + metastore_uri = "thrift://namenode001:9083" + }, + { + table_name = "default.seatunnel_orc_2" + metastore_uri = "thrift://namenode001:9083" + } + ] + } +``` + +### 示例 3:Kerberos + +```bash +source { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + result_table_name = hive_source + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} +``` + +描述: + +- `hive_site_path`:`hive-site.xml` 文件的路径。 +- `kerberos_principal`:Kerberos 认证的主体。 +- `kerberos_keytab_path`:Kerberos 认证的 keytab 文件路径。 +- `krb5_path`:用于 Kerberos 认证的 `krb5.conf` 文件路径。 + +运行案例: + +```bash +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + Hive { + table_name = "default.test_hive_sink_on_hdfs_with_kerberos" + metastore_uri = "thrift://metastore:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + result_table_name = hive_source + hive_site_path = "/tmp/hive-site.xml" + kerberos_principal = "hive/metastore.seatunnel@EXAMPLE.COM" + kerberos_keytab_path = "/tmp/hive.keytab" + krb5_path = "/tmp/krb5.conf" + } +} + +sink { + Assert { + source_table_name = hive_source + rules { + row_rules = [ + { + rule_type = MAX_ROW + rule_value = 3 + } + ], + field_rules = [ + { + field_name = pk_id + field_type = bigint + field_value = [ + { + rule_type = NOT_NULL + } + ] + }, + { + field_name = name + field_type = string + field_value = [ + { + rule_type = NOT_NULL + } + ] + }, + { + field_name = score + field_type = int + field_value = [ + { + rule_type = NOT_NULL + } + ] + } + ] + } + } +} +``` + +## Hive on s3 + +### 步骤 1 + +为 EMR 的 Hive 创建 lib 目录。 + +```shell +mkdir -p ${SEATUNNEL_HOME}/plugins/Hive/lib +``` + +### 步骤 2 + +从 Maven 中心获取 jar 文件到 lib。 + +```shell +cd ${SEATUNNEL_HOME}/plugins/Hive/lib +wget https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/2.6.5/hadoop-aws-2.6.5.jar +wget https://repo1.maven.org/maven2/org/apache/hive/hive-exec/2.3.9/hive-exec-2.3.9.jar +``` + +### 步骤 3 + +从您的 EMR 环境中复制 jar 文件到 lib 目录。 + +```shell +cp /usr/share/aws/emr/emrfs/lib/emrfs-hadoop-assembly-2.60.0.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +cp /usr/share/aws/emr/hadoop-state-pusher/lib/hadoop-common-3.3.6-amzn-1.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +cp /usr/share/aws/emr/hadoop-state-pusher/lib/javax.inject-1.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +cp /usr/share/aws/emr/hadoop-state-pusher/lib/aopalliance-1.0.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +``` + +### 步骤 4 + +运行案例。 + +```shell +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + Hive { + table_name = "test_hive.test_hive_sink_on_s3" + metastore_uri = "thrift://ip-192-168-0-202.cn-north-1.compute.internal:9083" + hive.hadoop.conf-path = "/home/ec2-user/hadoop-conf" + hive.hadoop.conf = { + bucket="s3://ws-package" + fs.s3a.aws.credentials.provider="com.amazonaws.auth.InstanceProfileCredentialsProvider" + } + read_columns = ["pk_id", "name", "score"] + } +} + +sink { + Hive { + table_name = "test_hive.test_hive_sink_on_s3_sink" + metastore_uri = "thrift://ip-192-168-0-202.cn-north-1.compute.internal:9083" + hive.hadoop.conf-path = "/home/ec2-user/hadoop-conf" + hive.hadoop.conf = { + bucket="s3://ws-package" + fs.s3a.aws.credentials.provider="com.amazonaws.auth.InstanceProfileCredentialsProvider" + } + } +} +``` + +## Hive on oss + +### 步骤 1 + +为 EMR 的 Hive 创建 lib 目录。 + +```shell +mkdir -p ${SEATUNNEL_HOME}/plugins/Hive/lib +``` + +### 步骤 2 + +从 Maven 中心获取 jar 文件到 lib。 + +```shell +cd ${SEATUNNEL_HOME}/plugins/Hive/lib +wget https://repo1.maven.org/maven2/org/apache/hive/hive-exec/2.3.9/hive-exec-2.3.9.jar +``` + +### 步骤 3 + +从您的 EMR 环境中复制 jar 文件到 lib 目录并删除冲突的 jar。 + +```shell +cp -r /opt/apps/JINDOSDK/jindosdk-current/lib/jindo-*.jar ${SEATUNNEL_HOME}/plugins/Hive/lib +rm -f ${SEATUNNEL_HOME}/lib/hadoop-aliyun-*.jar +``` + +### 步骤 4 + +运行案例。 + +```shell +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + Hive { + table_name = "test_hive.test_hive_sink_on_oss" + metastore_uri = "thrift://master-1-1.c-1009b01725b501f2.cn-wulanchabu.emr.aliyuncs.com:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + hive.hadoop.conf = { + bucket="oss://emr-osshdfs.cn-wulanchabu.oss-dls.aliyuncs.com" + } + } +} + +sink { + Hive { + table_name = "test_hive.test_hive_sink_on_oss_sink" + metastore_uri = "thrift://master-1-1.c-1009b01725b501f2.cn-wulanchabu.emr.aliyuncs.com:9083" + hive.hadoop.conf-path = "/tmp/hadoop" + hive.hadoop.conf = { + bucket="oss://emr-osshdfs.cn-wulanchabu.oss-dls.aliyuncs.com" + } + } +} +``` diff --git a/docs/zh/connector-v2/source/Kafka.md b/docs/zh/connector-v2/source/Kafka.md index 44e27215564..04820cc7c13 100644 --- a/docs/zh/connector-v2/source/Kafka.md +++ b/docs/zh/connector-v2/source/Kafka.md @@ -181,6 +181,65 @@ source { > 根据不同的 Kafka 主题和格式解析数据,并基于 ID 执行 upsert 操作。 +> 注意: Kafka是一个非结构化数据源,应该使用`tables_configs`,将来会删除`table_list` + +```hocon + +env { + execution.parallelism = 1 + job.mode = "BATCH" +} + +source { + Kafka { + bootstrap.servers = "kafka_e2e:9092" + tables_configs = [ + { + topic = "^test-ogg-sou.*" + pattern = "true" + consumer.group = "ogg_multi_group" + start_mode = earliest + schema = { + fields { + id = "int" + name = "string" + description = "string" + weight = "string" + } + }, + format = ogg_json + }, + { + topic = "test-cdc_mds" + start_mode = earliest + schema = { + fields { + id = "int" + name = "string" + description = "string" + weight = "string" + } + }, + format = canal_json + } + ] + } +} + +sink { + Jdbc { + driver = org.postgresql.Driver + url = "jdbc:postgresql://postgresql:5432/test?loggerLevel=OFF" + user = test + password = test + generate_sink_sql = true + database = test + table = public.sink + primary_keys = ["id"] + } +} +``` + ```hocon env { execution.parallelism = 1 @@ -283,7 +342,7 @@ source { """ bootstrap.servers = "kafkaCluster:9092" start_mode = "earliest" - result_table_name = "kafka_table" + plugin_output = "kafka_table" } } ``` diff --git a/docs/zh/connector-v2/source/Opengauss-CDC.md b/docs/zh/connector-v2/source/Opengauss-CDC.md index 83da40b363e..b175f611ecb 100644 --- a/docs/zh/connector-v2/source/Opengauss-CDC.md +++ b/docs/zh/connector-v2/source/Opengauss-CDC.md @@ -63,31 +63,31 @@ select 'ALTER TABLE ' || schemaname || '.' || tablename || ' REPLICA IDENTITY FU ## 源端可选项 -| Name | Type | Required | Default | Description | -|------------------------------------------------|------|----------|----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| base-url | 字符串 | 是 | - | JDBC连接的URL. 参考: `jdbc:postgresql://localhost:5432/postgres_cdc?loggerLevel=OFF`. | -| username | 字符串 | 是 | - | 连接数据库的用户名 | -| password | 字符串 | 是 | - | 连接数据库的密码 | -| database-names | 列表 | 否 | - | 监控的数据库名称 | -| table-names | 列表 | 是 | - | 监控的数据表名称. 表名需要包含数据库名称, 例如: `database_name.table_name` | -| table-names-config | 列表 | 否 | - | 表配置的列表集合. 例如: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | -| startup.mode | 枚举 | 否 | INITIAL | Opengauss CDC消费者的可选启动模式, 有效的枚举是`initial`, `earliest`, `latest` and `specific`.
`initial`: 启动时同步历史数据,然后同步增量数据
`earliest`: 从可能的最早偏移量启动
`latest`: 从最近的偏移量启动
`specific`: 从用户指定的偏移量启动 | -| snapshot.split.size | 整型 | 否 | 8096 | 表快照的分割大小(行数),在读取表的快照时,捕获的表被分割成多个split | -| snapshot.fetch.size | 整型 | 否 | 1024 | 读取表快照时,每次轮询的最大读取大小 | -| slot.name | 字符串 | 否 | - | Opengauss逻辑解码插槽的名称,该插槽是为特定数据库/模式的特定插件的流式更改而创建的。服务器使用此插槽将事件流传输到正在配置的连接器。默认值为seatunnel | -| decoding.plugin.name | 字符串 | 否 | pgoutput | 安装在服务器上的Postgres逻辑解码插件的名称,支持的值是decoderbufs、wal2json、wal2json_rds、wal2json_streaming、wal2json_rds_streaming和pgoutput | -| server-time-zone | 字符串 | 否 | UTC | 数据库服务器中的会话时区。如果没有设置,则使用ZoneId.systemDefault()来确定服务器的时区 | -| connect.timeout.ms | 时间间隔 | 否 | 30000 | 在尝试连接数据库服务器之后,连接器在超时之前应该等待的最大时间 | -| connect.max-retries | 整型 | 否 | 3 | 连接器在建立数据库服务器连接时应该重试的最大次数 | -| connection.pool.size | 整型 | 否 | 20 | jdbc连接池的大小 | -| chunk-key.even-distribution.factor.upper-bound | 双浮点型 | 否 | 100 | chunk的key分布因子的上界。该因子用于确定表数据是否均匀分布。如果分布因子被计算为小于或等于这个上界(即(MAX(id) - MIN(id) + 1) /行数),表的所有chunk将被优化以达到均匀分布。否则,如果分布因子更大,则认为表分布不均匀,如果估计的分片数量超过`sample-sharding.threshold`指定的值,则将使用基于采样的分片策略。默认值为100.0。 | -| chunk-key.even-distribution.factor.lower-bound | 双浮点型 | 否 | 0.05 | chunk的key分布因子的下界。该因子用于确定表数据是否均匀分布。如果分布因子的计算结果大于或等于这个下界(即(MAX(id) - MIN(id) + 1) /行数),那么表的所有块将被优化以达到均匀分布。否则,如果分布因子较小,则认为表分布不均匀,如果估计的分片数量超过`sample-sharding.threshold`指定的值,则使用基于采样的分片策略。缺省值为0.05。 | -| sample-sharding.threshold | 整型 | 否 | 1000 | 此配置指定了用于触发采样分片策略的估计分片数的阈值。当分布因子超出了由`chunk-key.even-distribution.factor.upper-bound `和`chunk-key.even-distribution.factor.lower-bound`,并且估计的分片计数(以近似的行数/块大小计算)超过此阈值,则将使用样本分片策略。这有助于更有效地处理大型数据集。默认值为1000个分片。 | -| inverse-sampling.rate | 整型 | 否 | 1000 | 采样分片策略中使用的采样率的倒数。例如,如果该值设置为1000,则意味着在采样过程中应用了1/1000的采样率。该选项提供了控制采样粒度的灵活性,从而影响最终的分片数量。当处理非常大的数据集时,它特别有用,其中首选较低的采样率。缺省值为1000。 | -| exactly_once | 布尔 | 否 | false | 启用exactly once语义 | -| format | 枚举 | 否 | DEFAULT | Opengauss CDC可选的输出格式, 有效的枚举是`DEFAULT`, `COMPATIBLE_DEBEZIUM_JSON`. | -| debezium | 配置 | 否 | - | 将 [Debezium的属性](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/postgresql.adoc#connector-configuration-properties) 传递到Debezium嵌入式引擎,该引擎用于捕获来自Opengauss服务的数据更改 | -| common-options | | 否 | - | 源码插件通用参数, 请参考[Source Common Options](../source-common-options.md)获取详情 | +| Name | Type | Required | Default | Description | +|------------------------------------------------|------|----------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| base-url | 字符串 | 是 | - | JDBC连接的URL. 参考: `jdbc:postgresql://localhost:5432/postgres_cdc?loggerLevel=OFF`. | +| username | 字符串 | 是 | - | 连接数据库的用户名 | +| password | 字符串 | 是 | - | 连接数据库的密码 | +| database-names | 列表 | 否 | - | 监控的数据库名称 | +| table-names | 列表 | 是 | - | 监控的数据表名称. 表名需要包含数据库名称, 例如: `database_name.table_name` | +| table-names-config | 列表 | 否 | - | 表配置的列表集合. 例如: [{"table": "db1.schema1.table1","primaryKeys":["key1"]}] | +| startup.mode | 枚举 | 否 | INITIAL | Opengauss CDC消费者的可选启动模式, 有效的枚举是`initial`, `earliest`, `latest`.
`initial`: 启动时同步历史数据,然后同步增量数据
`earliest`: 从可能的最早偏移量启动
`latest`: 从最近的偏移量启动 | +| snapshot.split.size | 整型 | 否 | 8096 | 表快照的分割大小(行数),在读取表的快照时,捕获的表被分割成多个split | +| snapshot.fetch.size | 整型 | 否 | 1024 | 读取表快照时,每次轮询的最大读取大小 | +| slot.name | 字符串 | 否 | - | Opengauss逻辑解码插槽的名称,该插槽是为特定数据库/模式的特定插件的流式更改而创建的。服务器使用此插槽将事件流传输到正在配置的连接器。默认值为seatunnel | +| decoding.plugin.name | 字符串 | 否 | pgoutput | 安装在服务器上的Postgres逻辑解码插件的名称,支持的值是decoderbufs、wal2json、wal2json_rds、wal2json_streaming、wal2json_rds_streaming和pgoutput | +| server-time-zone | 字符串 | 否 | UTC | 数据库服务器中的会话时区。如果没有设置,则使用ZoneId.systemDefault()来确定服务器的时区 | +| connect.timeout.ms | 时间间隔 | 否 | 30000 | 在尝试连接数据库服务器之后,连接器在超时之前应该等待的最大时间 | +| connect.max-retries | 整型 | 否 | 3 | 连接器在建立数据库服务器连接时应该重试的最大次数 | +| connection.pool.size | 整型 | 否 | 20 | jdbc连接池的大小 | +| chunk-key.even-distribution.factor.upper-bound | 双浮点型 | 否 | 100 | chunk的key分布因子的上界。该因子用于确定表数据是否均匀分布。如果分布因子被计算为小于或等于这个上界(即(MAX(id) - MIN(id) + 1) /行数),表的所有chunk将被优化以达到均匀分布。否则,如果分布因子更大,则认为表分布不均匀,如果估计的分片数量超过`sample-sharding.threshold`指定的值,则将使用基于采样的分片策略。默认值为100.0。 | +| chunk-key.even-distribution.factor.lower-bound | 双浮点型 | 否 | 0.05 | chunk的key分布因子的下界。该因子用于确定表数据是否均匀分布。如果分布因子的计算结果大于或等于这个下界(即(MAX(id) - MIN(id) + 1) /行数),那么表的所有块将被优化以达到均匀分布。否则,如果分布因子较小,则认为表分布不均匀,如果估计的分片数量超过`sample-sharding.threshold`指定的值,则使用基于采样的分片策略。缺省值为0.05。 | +| sample-sharding.threshold | 整型 | 否 | 1000 | 此配置指定了用于触发采样分片策略的估计分片数的阈值。当分布因子超出了由`chunk-key.even-distribution.factor.upper-bound `和`chunk-key.even-distribution.factor.lower-bound`,并且估计的分片计数(以近似的行数/块大小计算)超过此阈值,则将使用样本分片策略。这有助于更有效地处理大型数据集。默认值为1000个分片。 | +| inverse-sampling.rate | 整型 | 否 | 1000 | 采样分片策略中使用的采样率的倒数。例如,如果该值设置为1000,则意味着在采样过程中应用了1/1000的采样率。该选项提供了控制采样粒度的灵活性,从而影响最终的分片数量。当处理非常大的数据集时,它特别有用,其中首选较低的采样率。缺省值为1000。 | +| exactly_once | 布尔 | 否 | false | 启用exactly once语义 | +| format | 枚举 | 否 | DEFAULT | Opengauss CDC可选的输出格式, 有效的枚举是`DEFAULT`, `COMPATIBLE_DEBEZIUM_JSON`. | +| debezium | 配置 | 否 | - | 将 [Debezium的属性](https://github.com/debezium/debezium/blob/v1.9.8.Final/documentation/modules/ROOT/pages/connectors/postgresql.adoc#connector-configuration-properties) 传递到Debezium嵌入式引擎,该引擎用于捕获来自Opengauss服务的数据更改 | +| common-options | | 否 | - | 源码插件通用参数, 请参考[Source Common Options](../source-common-options.md)获取详情 | ## 任务示例 @@ -108,7 +108,7 @@ env { source { Opengauss-CDC { - result_table_name = "customers_opengauss_cdc" + plugin_output = "customers_opengauss_cdc" username = "gaussdb" password = "openGauss@123" database-names = ["opengauss_cdc"] @@ -125,7 +125,7 @@ transform { sink { jdbc { - source_table_name = "customers_opengauss_cdc" + plugin_input = "customers_opengauss_cdc" url = "jdbc:postgresql://opengauss_cdc_e2e:5432/opengauss_cdc" driver = "org.postgresql.Driver" user = "dailai" @@ -148,7 +148,7 @@ sink { ``` source { Opengauss-CDC { - result_table_name = "customers_opengauss_cdc" + plugin_output = "customers_opengauss_cdc" username = "gaussdb" password = "openGauss@123" database-names = ["opengauss_cdc"] diff --git a/docs/zh/connector-v2/source/Prometheus.md b/docs/zh/connector-v2/source/Prometheus.md new file mode 100644 index 00000000000..1dca6b463cb --- /dev/null +++ b/docs/zh/connector-v2/source/Prometheus.md @@ -0,0 +1,152 @@ +# Prometheus + +> Prometheus 数据源连接器 + +## 描述 + +用于读取prometheus数据。 + +## 主要特性 + +- [x] [批处理](../../concept/connector-v2-features.md) +- [ ] [流处理](../../concept/connector-v2-features.md) +- [ ] [并行](../../concept/connector-v2-features.md) + +## 源选项 + +| 名称 | 类型 | 是否必填 | 默认值 | +|-----------------------------|---------|------|-----------------| +| url | String | Yes | - | +| query | String | Yes | - | +| query_type | String | Yes | Instant | +| content_field | String | Yes | $.data.result.* | +| schema.fields | Config | Yes | - | +| format | String | No | json | +| params | Map | Yes | - | +| poll_interval_millis | int | No | - | +| retry | int | No | - | +| retry_backoff_multiplier_ms | int | No | 100 | +| retry_backoff_max_ms | int | No | 10000 | +| enable_multi_lines | boolean | No | false | +| common-options | config | No | | + +### url [String] + +http 请求路径。 + +### query [String] + +Prometheus 表达式查询字符串 + +### query_type [String] + +Instant/Range + +1. Instant : 简单指标的即时查询。 +2. Range : 一段时间内指标数据。 + +https://prometheus.io/docs/prometheus/latest/querying/api/ + +### params [Map] + +http 请求参数 + +### poll_interval_millis [int] + +流模式下请求HTTP API间隔(毫秒) + +### retry [int] + +The max retry times if request http return to `IOException` + +### retry_backoff_multiplier_ms [int] + +请求http返回到' IOException '的最大重试次数 + +### retry_backoff_max_ms [int] + +http请求失败,最大重试回退时间(毫秒) + +### format [String] + +上游数据的格式,默认为json。 + +### schema [Config] + +按照如下填写一个固定值 + +```hocon + schema = { + fields { + metric = "map" + value = double + time = long + } + } + +``` + +#### fields [Config] + +上游数据的模式字段 + +### common options + +源插件常用参数,请参考[Source Common Options](../source-common-options.md) 了解详细信息 + +## 示例 + +### Instant: + +```hocon +source { + Prometheus { + plugin_output = "http" + url = "http://mockserver:1080" + query = "up" + query_type = "Instant" + content_field = "$.data.result.*" + format = "json" + schema = { + fields { + metric = "map" + value = double + time = long + } + } + } +} +``` + +### Range + +```hocon +source { + Prometheus { + plugin_output = "http" + url = "http://mockserver:1080" + query = "up" + query_type = "Range" + content_field = "$.data.result.*" + format = "json" + start = "2024-07-22T20:10:30.781Z" + end = "2024-07-22T20:11:00.781Z" + step = "15s" + schema = { + fields { + metric = "map" + value = double + time = long + } + } + } + } +``` + +## Changelog + +### next version + +- 添加Prometheus源连接器 +- 减少配置项 + diff --git a/docs/zh/connector-v2/source/TiDB-CDC.md b/docs/zh/connector-v2/source/TiDB-CDC.md index f4aeaa1458c..a2f4ba21af4 100644 --- a/docs/zh/connector-v2/source/TiDB-CDC.md +++ b/docs/zh/connector-v2/source/TiDB-CDC.md @@ -89,7 +89,7 @@ env { source { TiDB-CDC { - result_table_name = "products_tidb_cdc" + plugin_output = "products_tidb_cdc" base-url = "jdbc:mysql://tidb0:4000/inventory" driver = "com.mysql.cj.jdbc.Driver" tikv.grpc.timeout_in_ms = 20000 @@ -106,7 +106,7 @@ transform { sink { jdbc { - source_table_name = "products_tidb_cdc" + plugin_input = "products_tidb_cdc" url = "jdbc:mysql://tidb0:4000/inventory" driver = "com.mysql.cj.jdbc.Driver" user = "root" diff --git a/docs/zh/contribution/how-to-create-your-connector.md b/docs/zh/contribution/how-to-create-your-connector.md index 3aef1b140c2..c8157fbb992 100644 --- a/docs/zh/contribution/how-to-create-your-connector.md +++ b/docs/zh/contribution/how-to-create-your-connector.md @@ -1,4 +1,4 @@ -## 开发自己的Connector +# 开发自己的Connector 如果你想针对SeaTunnel新的连接器API开发自己的连接器(Connector V2),请查看[这里](https://github.com/apache/seatunnel/blob/dev/seatunnel-connectors-v2/README.zh.md) 。 diff --git a/docs/zh/contribution/setup.md b/docs/zh/contribution/setup.md index c00c3132c22..662663a4961 100644 --- a/docs/zh/contribution/setup.md +++ b/docs/zh/contribution/setup.md @@ -75,7 +75,7 @@ Apache SeaTunnel 使用 `Spotless` 来统一代码风格和格式检查。可以 完成上面所有的工作后,环境搭建已经完成, 可以直接运行我们的示例了。 所有的示例在 `seatunnel-examples` 模块里, 你可以随意选择进行编译和调试,参考 [running or debugging it in IDEA](https://www.jetbrains.com/help/idea/run-debug-configuration.html)。 -我们使用 `seatunnel-examples/seatunnel-engine-examples/src/main/java/org/apache/seatunnel/example/engine/SeaTunnelEngineExample.java` +我们使用 `seatunnel-examples/seatunnel-engine-examples/src/main/java/org/apache/seatunnel/example/engine/SeaTunnelEngineLocalExample.java` 作为示例, 运行成功后的输出如下: ```log diff --git a/docs/zh/faq.md b/docs/zh/faq.md index 4fc24e6a3ad..26867e4a188 100644 --- a/docs/zh/faq.md +++ b/docs/zh/faq.md @@ -1,56 +1,108 @@ # 常见问题解答 -## 为什么要安装Spark或者Flink这样的计算引擎? - -SeaTunnel现在使用Spark、Flink等计算引擎来完成资源调度和节点通信,因此我们可以专注于数据同步的易用性和高性能组件的开发。 但这只是暂时的。 +## SeaTunnel 支持哪些数据来源和数据目的地? +SeaTunnel 支持多种数据源来源和数据目的地,您可以在官网找到详细的列表: +SeaTunnel 支持的数据来源(Source)列表:https://seatunnel.apache.org/docs/connector-v2/source +SeaTunnel 支持的数据目的地(Sink)列表:https://seatunnel.apache.org/docs/connector-v2/sink + +## SeaTunnel 是否支持批处理和流处理? +SeaTunnel 支持批流一体,SeaTunnel 可以设置批处理和流处理两种模式。您可以根据具体的业务场景和需求选择合适的处理模式。批处理适合定时数据同步场景,而流处理适合实时同步和数据变更捕获 (CDC) 场景。 + +## 使用 SeaTunnel 需要安装 Spark 或者 Flink 这样的引擎么? +Spark 和 Flink 不是必需的,SeaTunnel 可以支持 Zeta、Spark 和 Flink 3 种作为同步引擎的选择,您可以选择之一就行,社区尤其推荐使用 Zeta 这种专为同步场景打造的新一代超高性能同步引擎。Zeta 被社区用户亲切的称为 “泽塔奥特曼”! +社区对 Zeta 的支持力度是最大的,功能也更丰富。 + +## SeaTunnel 支持的数据转换功能有哪些? +SeaTunnel 支持多种数据转换功能,包括字段映射、数据过滤、数据格式转换等。可以通过在配置文件中定义 `transform` 模块来实现数据转换。详情请参考 SeaTunnel [Transform 文档](https://seatunnel.apache.org/docs/transform-v2)。 + +## SeaTunnel 是否可以自定义数据清洗规则? +SeaTunnel 支持自定义数据清洗规则。可以在 `transform` 模块中配置自定义规则,例如清理脏数据、删除无效记录或字段转换。 + +## SeaTunnel 是否支持实时增量同步? +SeaTunnel 支持增量数据同步。例如通过 CDC 连接器实现对数据库的增量同步,适用于需要实时捕获数据变更的场景。 + +## SeaTunnel 目前支持哪些数据源的 CDC ? +目前支持 MongoDB CDC、MySQL CDC、Opengauss CDC、Oracle CDC、PostgreSQL CDC、Sql Server CDC、TiDB CDC等,更多请查阅[Source](https://seatunnel.apache.org/docs/connector-v2/source)。 + +## SeaTunnel CDC 同步需要的权限如何开启? +这样就可以了。 +这里多说一句,连接器对应的 cdc 权限开启步骤在官网都有写,请参照 SeaTunnel 对应的官网操作即可 + +## SeaTunnel 支持从 MySQL 备库进行 CDC 么?日志如何拉取? +支持,是通过订阅 MySQL binlog 日志方式到同步服务器上解析 binlog 日志方式进行 + +## SeaTunnel 是否支持无主键表的 CDC 同步? +不支持无主键表的 cdc 同步。原因如下: +比如上游有 2 条一模一样的数据,然后上游删除或修改了一条,下游由于无法区分到底是哪条需要删除或修改,会出现这 2 条都被删除或修改的情况。 +没主键要类似去重的效果本身有点儿自相矛盾,就像辨别西游记里的真假悟空,到底哪个是真的 + +## SeaTunnel 是否支持自动建表? +在同步任务启动之前,可以为目标端已有的表结构选择不同的处理方案。是通过 `schema_save_mode` 参数来控制的。 +`schema_save_mode` 有以下几种方式可选: +- **`RECREATE_SCHEMA`**:当表不存在时会创建,若表已存在则删除并重新创建。 +- **`CREATE_SCHEMA_WHEN_NOT_EXIST`**:当表不存在时会创建,若表已存在则跳过创建。 +- **`ERROR_WHEN_SCHEMA_NOT_EXIST`**:当表不存在时会报错。 +- **`IGNORE`**:忽略对表的处理。 + 目前很多 connector 已经支持了自动建表,请参考对应的 connector 文档,这里拿 Jdbc 举例,请参考 [Jdbc sink](https://seatunnel.apache.org/docs/connector-v2/sink/Jdbc#schema_save_mode-enum) + +## SeaTunnel 是否支持数据同步任务开始前对已有数据进行处理? +在同步任务启动之前,可以为目标端已有的数据选择不同的处理方案。是通过 `data_save_mode` 参数来控制的。 +`data_save_mode` 有以下几种可选项: +- **`DROP_DATA`**:保留数据库结构,删除数据。 +- **`APPEND_DATA`**:保留数据库结构,保留数据。 +- **`CUSTOM_PROCESSING`**:用户自定义处理。 +- **`ERROR_WHEN_DATA_EXISTS`**:当存在数据时,报错。 + 目前很多 connector 已经支持了对已有数据进行处理,请参考对应的 connector 文档,这里拿 Jdbc 举例,请参考 [Jdbc sink](https://seatunnel.apache.org/docs/connector-v2/sink/Jdbc#data_save_mode-enum) + +## SeaTunnel 是否支持精确一致性管理? +SeaTunnel 支持一部分数据源的精确一致性,例如支持 MySQL、PostgreSQL 等数据库的事务写入,确保数据在同步过程中的一致性,另外精确一致性也要看数据库本身是否可以支持 + +## SeaTunnel 可以定期执行任务吗? +您可以通过使用 linux 自带 cron 能力来实现定时数据同步任务,也可以结合 DolphinScheduler 等调度工具实现复杂的定时任务管理。 ## 我有一个问题,我自己无法解决 - -我在使用SeaTunnel时遇到了问题,无法自行解决。 我应该怎么办? 首先,在[问题列表](https://github.com/apache/seatunnel/issues)或[邮件列表](https://lists.apache.org/list.html?dev@seatunnel.apache.org)中搜索 )看看是否有人已经问过同样的问题并得到答案。 如果您找不到问题的答案,您可以通过[这些方式](https://github.com/apache/seatunnel#contact-us)联系社区成员寻求帮助。 +我在使用 SeaTunnel 时遇到了问题,无法自行解决。 我应该怎么办?有以下几种方式 +1、在[问题列表](https://github.com/apache/seatunnel/issues)或[邮件列表](https://lists.apache.org/list.html?dev@seatunnel.apache.org)中搜索看看是否有人已经问过同样的问题并得到答案。 +2、如果您找不到问题的答案,您可以通过[这些方式](https://github.com/apache/seatunnel#contact-us)联系社区成员寻求帮助。 +3、中国用户可以添加微信群助手:seatunnel1,加入社区交流群,也欢迎大家关注微信公众号:seatunnel。 ## 如何声明变量? - -您想知道如何在 SeaTunnel 的配置中声明一个变量,然后在运行时动态替换该变量的值吗? - -从“v1.2.4”开始,SeaTunnel 支持配置中的变量替换。 该功能常用于定时或非定时离线处理,以替代时间、日期等变量。 用法如下: - +您想知道如何在 SeaTunnel 的配置中声明一个变量,然后在运行时动态替换该变量的值吗? 该功能常用于定时或非定时离线处理,以替代时间、日期等变量。 用法如下: 在配置中配置变量名称。 下面是一个sql转换的例子(实际上,配置文件中任何地方“key = value”中的值都可以使用变量替换): - ``` ... transform { - sql { - query = "select * from user_view where city ='"${city}"' and dt = '"${date}"'" + Sql { + query = "select * from user_view where city ='${city}' and dt = '${date}'" } } ... ``` -以Spark Local模式为例,启动命令如下: +以使用 SeaTunnel Zeta Local模式为例,启动命令如下: ```bash -./bin/start-seatunnel-spark.sh \ --c ./config/your_app.conf \ --e client \ +$SEATUNNEL_HOME/bin/seatunnel.sh \ +-c $SEATUNNEL_HOME/config/your_app.conf \ -m local[2] \ --i city=shanghai \ --i date=20190319 +-i city=Singapore \ +-i date=20231110 ``` -您可以使用参数“-i”或“--variable”后跟“key=value”来指定变量的值,其中key需要与配置中的变量名称相同。 +您可以使用参数“-i”或“--variable”后跟“key=value”来指定变量的值,其中key需要与配置中的变量名称相同。详情可以参考:https://seatunnel.apache.org/docs/concept/config ## 如何在配置文件中写入多行文本的配置项? - -当配置的文本很长并且想要将其换行时,可以使用三个双引号来指示其开始和结束: +当配置的文本很长并且想要将其换行时,您可以使用三个双引号来指示其开始和结束: ``` var = """ - whatever you want +Apache SeaTunnel is a +next-generation high-performance, +distributed, massive data integration tool. """ ``` ## 如何实现多行文本的变量替换? - 在多行文本中进行变量替换有点麻烦,因为变量不能包含在三个双引号中: ``` @@ -61,273 +113,14 @@ your string 1 请参阅:[lightbend/config#456](https://github.com/lightbend/config/issues/456)。 -## Azkaban、Oozie、DolphinScheduler 是否支持 SeaTunnel? - -当然! 请参阅下面的屏幕截图: - -![工作流程.png](../images/workflow.png) - -![azkaban.png](../images/azkaban.png) - -## SeaTunnel是否有配置多个源的情况,例如同时在源中配置elasticsearch和hdfs? - -``` -env { - ... -} - -source { - hdfs { ... } - elasticsearch { ... } - jdbc {...} -} - -transform { - ... -} - -sink { - elasticsearch { ... } -} -``` - -## 有 HBase 插件吗? - -有一个 HBase 输入插件。 您可以从这里下载:https://github.com/garyelephant/waterdrop-input-hbase - -## 如何使用SeaTunnel将数据写入Hive? - -``` -env { - spark.sql.catalogImplementation = "hive" - spark.hadoop.hive.exec.dynamic.partition = "true" - spark.hadoop.hive.exec.dynamic.partition.mode = "nonstrict" -} - -source { - sql = "insert into ..." -} - -sink { - // The data has been written to hive through the sql source. This is just a placeholder, it does not actually work. - stdout { - limit = 1 - } -} -``` - -此外,SeaTunnel 在 `1.5.7` 版本之后在 `1.x` 分支中实现了 `Hive` 输出插件; 在“2.x”分支中。 Spark 引擎的 Hive 插件已从版本“2.0.5”开始支持:https://github.com/apache/seatunnel/issues/910。 - -## SeaTunnel如何编写ClickHouse的多个实例来实现负载均衡? - -1.直接写分布式表(不推荐) - -2.在ClickHouse的多个实例前面添加代理或域名(DNS): - -``` -{ - output { - clickhouse { - host = "ck-proxy.xx.xx:8123" - # Local table - table = "table_name" - } - } -} -``` - -3. 在配置文件中配置多个ClickHouse实例: - - ``` - { - output { - clickhouse { - host = "ck1:8123,ck2:8123,ck3:8123" - # Local table - table = "table_name" - } - } - } - ``` -4. 使用集群模式: - - ``` - { - output { - clickhouse { - # Configure only one host - host = "ck1:8123" - cluster = "clickhouse_cluster_name" - # Local table - table = "table_name" - } - } - } - ``` - -## SeaTunnel 消费 Kafka 时如何解决 OOM? - -大多数情况下,OOM是由于没有对消费进行速率限制而导致的。 解决方法如下: - -对于目前Kafka的Spark消费限制: - -1. 假设您使用 KafkaStream 消费的 Kafka `Topic 1` 的分区数量 = N。 - -2. 假设“Topic 1”的消息生产者(Producer)的生产速度为K条消息/秒,则向分区写入消息的速度必须一致。 - -3、假设经过测试发现Spark Executor每核每秒的处理能力为M。 - -可以得出以下结论: - -1、如果想让Spark对`Topic 1`的消耗跟上它的生产速度,那么需要 `spark.executor.cores` * `spark.executor.instances` >= K / M - -2、当出现数据延迟时,如果希望消耗速度不要太快,导致spark执行器OOM,那么需要配置 `spark.streaming.kafka.maxRatePerPartition` <= (`spark.executor.cores` * `spark.executor.instances`) * M / N - -3、一般来说,M和N都确定了,从2可以得出结论:`spark.streaming.kafka.maxRatePerPartition`的大小与`spark.executor.cores` * `spark的大小正相关 .executor.instances`,可以在增加资源`maxRatePerPartition`的同时增加,以加快消耗。 - -![Kafka](../images/kafka.png) - -## 如何解决错误 `Exception in thread "main" java.lang.NoSuchFieldError: INSTANCE`? - -原因是Spark的CDH版本自带的httpclient.jar版本较低,而ClickHouse JDBC基于的httpclient版本是4.5.2,包版本冲突。 解决办法是将CDH自带的jar包替换为httpclient-4.5.2版本。 - -## 我的Spark集群默认的JDK是JDK7。 安装JDK8后,如何指定SeaTunnel以JDK8启动? - -在 SeaTunnel 的配置文件中,指定以下配置: - -```shell -spark { - ... - spark.executorEnv.JAVA_HOME="/your/java_8_home/directory" - spark.yarn.appMasterEnv.JAVA_HOME="/your/java_8_home/directory" - ... -} -``` - -## Spark local[*]模式运行SeaTunnel时总是出现OOM怎么办? - -如果以本地模式运行,则需要修改`start-seatunnel.sh`启动脚本。 在 `spark-submit` 之后添加参数 `--driver-memory 4g` 。 一般情况下,生产环境中不使用本地模式。 因此,On YARN时一般不需要设置该参数。 有关详细信息,请参阅:[应用程序属性](https://spark.apache.org/docs/latest/configuration.html#application-properties)。 - -## 我可以在哪里放置自己编写的插件或第三方 jdbc.jar 以供 SeaTunnel 加载? - -将Jar包放置在plugins目录指定结构下: - -```bash -cd SeaTunnel -mkdir -p plugins/my_plugins/lib -cp third-part.jar plugins/my_plugins/lib -``` - -`my_plugins` 可以是任何字符串。 - -## 如何在 SeaTunnel-V1(Spark) 中配置日志记录相关参数? - -可以通过三种方式配置日志相关参数(例如日志级别): - -- [不推荐] 更改默认的`$SPARK_HOME/conf/log4j.properties`。 - - 这将影响通过 `$SPARK_HOME/bin/spark-submit` 提交的所有程序。 -- [不推荐]直接在SeaTunnel的Spark代码中修改日志相关参数。 - - 这相当于写死了,每次改变都需要重新编译。 -- [推荐] 使用以下方法更改 SeaTunnel 配置文件中的日志记录配置(更改仅在 SeaTunnel >= 1.5.5 时生效): - - ``` - env { - spark.driver.extraJavaOptions = "-Dlog4j.configuration=file:/log4j.properties" - spark.executor.extraJavaOptions = "-Dlog4j.configuration=file:/log4j.properties" - } - source { - ... - } - transform { - ... - } - sink { - ... - } - ``` - -可供参考的log4j配置文件内容如下: - -``` -$ cat log4j.properties -log4j.rootLogger=ERROR, console - -# set the log level for these components -log4j.logger.org=ERROR -log4j.logger.org.apache.spark=ERROR -log4j.logger.org.spark-project=ERROR -log4j.logger.org.apache.hadoop=ERROR -log4j.logger.io.netty=ERROR -log4j.logger.org.apache.zookeeper=ERROR - -# add a ConsoleAppender to the logger stdout to write to the console -log4j.appender.console=org.apache.log4j.ConsoleAppender -log4j.appender.console.layout=org.apache.log4j.PatternLayout -# use a simple message format -log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n -``` - -## 如何在 SeaTunnel-V2(Spark、Flink) 中配置日志记录相关参数? - -目前,无法直接设置它们。 您需要修改SeaTunnel启动脚本。 相关参数在任务提交命令中指定。 具体参数请参考官方文档: - -- Spark官方文档:http://spark.apache.org/docs/latest/configuration.html#configuring-logging -- Flink 官方文档:https://ci.apache.org/projects/flink/flink-docs-stable/monitoring/logging.html - -参考: - -https://stackoverflow.com/questions/27781187/how-to-stop-info-messages-displaying-on-spark-console - -http://spark.apache.org/docs/latest/configuration.html#configuring-logging - -https://medium.com/@iacomini.riccardo/spark-logging-configuration-in-yarn-faf5ba5fdb01 - -## 如何配置SeaTunnel-E2E Test的日志记录相关参数? - -`seatunnel-e2e` 的 log4j 配置文件位于 `seatunnel-e2e/seatunnel-e2e-common/src/test/resources/log4j2.properties` 中。 您可以直接在配置文件中修改日志记录相关参数。 - -例如,如果您想输出更详细的E2E Test日志,只需将配置文件中的“rootLogger.level”降级即可。 - -## 写入 ClickHouse 时出错:ClassCastException - -在SeaTunnel中,不会主动转换数据类型。 Input读取数据后,对应的 -架构。 编写ClickHouse时,需要严格匹配字段类型,不匹配的情况需要解决。 - -数据转换可以通过以下两个插件实现: - -1.过滤器转换插件 -2.过滤Sql插件 - -详细数据类型转换参考:[ClickHouse数据类型检查列表](https://interestinglab.github.io/seatunnel-docs/#/en/configuration/output-plugins/Clickhouse?id=clickhouse-data-type-check-list) - -请参阅问题:[#488](https://github.com/apache/seatunnel/issues/488)[#382](https://github.com/apache/seatunnel/issues/382)。 - -## SeaTunnel 如何访问经过 kerberos 验证的 HDFS、YARN、Hive 等资源? - -请参考:[#590](https://github.com/apache/seatunnel/issues/590)。 - -## 如何排查 NoClassDefFoundError、ClassNotFoundException 等问题? - -有很大概率是Java类路径中加载了多个不同版本的对应Jar包类,是因为加载顺序冲突,而不是因为Jar确实丢失了。 修改这条SeaTunnel启动命令,在spark-submit提交部分添加如下参数,通过输出日志进行详细调试。 - -``` -spark-submit --verbose - ... - --conf 'spark.driver.extraJavaOptions=-verbose:class' - --conf 'spark.executor.extraJavaOptions=-verbose:class' - ... -``` - -## 我想学习SeaTunnel的源代码。 我应该从哪里开始? - -SeaTunnel 拥有完全抽象、结构化的代码实现,很多人都选择 SeaTunnel 作为学习 Spark 的方式。 您可以从主程序入口了解源代码:SeaTunnel.java - -## SeaTunnel开发者开发自己的插件时,是否需要了解SeaTunnel代码? 这些插件是否应该集成到 SeaTunnel 项目中? -开发者开发的插件与SeaTunnel项目无关,不需要包含您的插件代码。 +## 如果想学习 SeaTunnel 的源代码,应该从哪里开始? +SeaTunnel 拥有完全抽象、结构化的非常优秀的架构设计和代码实现,很多用户都选择 SeaTunnel 作为学习大数据架构的方式。 您可以从`seatunnel-examples`模块开始了解和调试源代码:SeaTunnelEngineLocalExample.java +具体参考:https://seatunnel.apache.org/docs/contribution/setup +针对中国用户,如果有伙伴想贡献自己的一份力量让 SeaTunnel 更好,特别欢迎加入社区贡献者种子群,欢迎添加微信:davidzollo,添加时请注明 "参与开源共建", 群仅仅用于技术交流, 重要的事情讨论还请发到 dev@seatunnel.apache.org 邮件里进行讨论。 -该插件可以完全独立于 SeaTunnel 项目,因此您可以使用 Java、Scala、Maven、sbt、Gradle 或任何您想要的方式编写它。 这也是我们推荐开发者开发插件的方式。 +## 如果想开发自己的 source、sink、transform 时,是否需要了解 SeaTunnel 所有源代码? +不需要,您只需要关注 source、sink、transform 对应的接口即可。 +如果你想针对 SeaTunnel API 开发自己的连接器(Connector V2),请查看**[Connector Development Guide](https://github.com/apache/seatunnel/blob/dev/seatunnel-connectors-v2/README.zh.md)** 。 -## 当我导入项目时,编译器出现异常“找不到类`org.apache.seatunnel.shade.com.typesafe.config.Config`” -首先运行“mvn install”。 在 `seatunnel-config/seatunnel-config-base` 子项目中,包 `com.typesafe.config` 已重新定位到 `org.apache.seatunnel.shade.com.typesafe.config` 并安装到 maven 本地存储库 在子项目 `seatunnel-config/seatunnel-config-shade` 中。 diff --git a/docs/zh/other-engine/flink.md b/docs/zh/other-engine/flink.md index 856aeb78101..06f51a82b46 100644 --- a/docs/zh/other-engine/flink.md +++ b/docs/zh/other-engine/flink.md @@ -36,7 +36,7 @@ env { source { FakeSource { row.num = 16 - result_table_name = "fake_table" + plugin_output = "fake_table" schema = { fields { c_map = "map" diff --git a/docs/zh/seatunnel-engine/checkpoint-storage.md b/docs/zh/seatunnel-engine/checkpoint-storage.md index 86165d5d3be..a60fdff5ae0 100644 --- a/docs/zh/seatunnel-engine/checkpoint-storage.md +++ b/docs/zh/seatunnel-engine/checkpoint-storage.md @@ -12,7 +12,7 @@ sidebar_position: 7 SeaTunnel Engine支持以下检查点存储类型: -- HDFS (OSS,S3,HDFS,LocalFile) +- HDFS (OSS,COS,S3,HDFS,LocalFile) - LocalFile (本地),(已弃用: 使用HDFS(LocalFile)替代). 我们使用微内核设计模式将检查点存储模块从引擎中分离出来。这允许用户实现他们自己的检查点存储模块。 @@ -71,6 +71,42 @@ seatunnel: 阿里云OSS凭证提供程序实现见: [验证凭证提供](https://github.com/aliyun/aliyun-oss-java-sdk/tree/master/src/main/java/com/aliyun/oss/common/auth) +#### COS + +腾讯云COS基于hdfs-file,所以你可以参考[Hadoop COS文档](https://hadoop.apache.org/docs/stable/hadoop-cos/cloud-storage/)来配置COS. + +除了与公共COS buckets交互之外,COS客户端需要与buckets交互所需的凭据。 +客户端支持多种身份验证机制,并且可以配置使用哪种机制及其使用顺序。也可以使用com.qcloud.cos.auth.COSCredentialsProvider的自定义实现。 +如果您使用SimpleCredentialsProvider(可以从腾讯云API密钥管理中获得),它们包括一个secretId和一个secretKey。 +您可以这样配置: + +```yaml +seatunnel: + engine: + checkpoint: + interval: 6000 + timeout: 7000 + storage: + type: hdfs + max-retained: 3 + plugin-config: + storage.type: cos + cos.bucket: cosn://your-bucket + fs.cosn.credentials.provider: org.apache.hadoop.fs.cosn.auth.SimpleCredentialsProvider + fs.cosn.userinfo.secretId: your-secretId + fs.cosn.userinfo.secretKey: your-secretKey + fs.cosn.bucket.region: your-region +``` + +有关Hadoop Credential Provider API的更多信息,请参见: [Credential Provider API](https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-common/CredentialProviderAPI.html). + +腾讯云COS相关配置可参考:[Tencent Hadoop-COS文档](https://doc.fincloud.tencent.cn/tcloud/Storage/COS/846365/hadoop) + +使用前请将如下jar添加到lib目录下: +- [hadoop-cos-3.4.1.jar](https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-cos/3.4.1) +- [cos_api-bundle-5.6.69.jar](https://mvnrepository.com/artifact/com.qcloud/cos_api-bundle/5.6.69) +- [hadoop-shaded-guava-1.1.1.jar](https://mvnrepository.com/artifact/org.apache.hadoop.thirdparty/hadoop-shaded-guava/1.1.1) + #### S3 S3基于hdfs-file,所以你可以参考[Hadoop s3文档](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html)来配置s3。 diff --git a/docs/zh/seatunnel-engine/download-seatunnel.md b/docs/zh/seatunnel-engine/download-seatunnel.md index 18b8cc68db5..8d06a2e4f78 100644 --- a/docs/zh/seatunnel-engine/download-seatunnel.md +++ b/docs/zh/seatunnel-engine/download-seatunnel.md @@ -20,7 +20,7 @@ import TabItem from '@theme/TabItem'; 或者您也可以通过终端下载 ```shell -export version="2.3.8" +export version="2.3.9" wget "https://archive.apache.org/dist/seatunnel/${version}/apache-seatunnel-${version}-bin.tar.gz" tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" ``` @@ -30,13 +30,13 @@ tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" 从2.2.0-beta版本开始,二进制包不再默认提供连接器依赖,因此在第一次使用它时,您需要执行以下命令来安装连接器:(当然,您也可以从 [Apache Maven Repository](https://repo.maven.apache.org/maven2/org/apache/seatunnel/) 手动下载连接器,然后将其移动至`connectors/seatunnel`目录下)。 ```bash -sh bin/install-plugin.sh 2.3.8 +sh bin/install-plugin.sh 2.3.9 ``` -如果您需要指定的连接器版本,以2.3.7为例,您需要执行如下命令 +如果您需要指定的连接器版本,以2.3.9为例,您需要执行如下命令 ```bash -sh bin/install-plugin.sh 2.3.8 +sh bin/install-plugin.sh 2.3.9 ``` 通常您并不需要所有的连接器插件,所以您可以通过配置`config/plugin_config`来指定您所需要的插件,例如,您只需要`connector-console`插件,那么您可以修改plugin.properties配置文件如下 diff --git a/docs/zh/seatunnel-engine/hybrid-cluster-deployment.md b/docs/zh/seatunnel-engine/hybrid-cluster-deployment.md index 83ce92bbd6b..77805273452 100644 --- a/docs/zh/seatunnel-engine/hybrid-cluster-deployment.md +++ b/docs/zh/seatunnel-engine/hybrid-cluster-deployment.md @@ -43,7 +43,7 @@ SeaTunnel Engine 基于 [Hazelcast IMDG](https://docs.hazelcast.com/imdg/4.1/) `backup count` 是定义同步备份数量的参数。例如,如果设置为 1,则分区的备份将放置在一个其他成员上。如果设置为 2,则将放置在两个其他成员上。 -我们建议 `backup-count` 的值为 `min(1, max(5, N/2))`。 `N` 是集群节点的数量。 +我们建议 `backup-count` 的值为 `max(1, min(5, N/2))`。 `N` 是集群节点的数量。 ```yaml seatunnel: @@ -136,6 +136,23 @@ seatunnel: classloader-cache-mode: true ``` +### 4.6 作业调度策略 + +当资源不足时,作业调度策略可以配置为以下两种模式: + +1. `WAIT`:等待资源可用。 +2. `REJECT`:拒绝作业,默认值。 + +示例 + +```yaml +seatunnel: + engine: + job-schedule-strategy: WAIT +``` + +当`dynamic-slot: ture`时,`job-schedule-strategy: WAIT` 配置会失效,将被强制修改为`job-schedule-strategy: REJECT`,因为动态Slot时该参数没有意义,可以直接提交。 + ## 5. 配置 SeaTunnel Engine 网络服务 所有 SeaTunnel Engine 网络相关的配置都在 `hazelcast.yaml` 文件中. @@ -319,4 +336,4 @@ mkdir -p $SEATUNNEL_HOME/logs ### 8.2 使用 REST API 提交作业 -SeaTunnel Engine 提供了 REST API 用于提交作业。有关详细信息,请参阅 [REST API](rest-api.md) \ No newline at end of file +SeaTunnel Engine 提供了 REST API 用于提交作业。有关详细信息,请参阅 [REST API V2](rest-api-v2.md) \ No newline at end of file diff --git a/docs/zh/seatunnel-engine/local-mode-deployment.md b/docs/zh/seatunnel-engine/local-mode-deployment.md index e3186ed7c00..e69bf426d8a 100644 --- a/docs/zh/seatunnel-engine/local-mode-deployment.md +++ b/docs/zh/seatunnel-engine/local-mode-deployment.md @@ -27,6 +27,16 @@ Local模式下每个任务都会启动一个独立的进程,任务运行完成 $SEATUNNEL_HOME/bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template -e local ``` +### 配置本地模式的JVM参数 + +本地模式支持两种设置JVM参数的方式: + +1. 添加JVM参数到`$SEATUNNEL_HOME/config/jvm_client_options`文件中。 + + 修改`$SEATUNNEL_HOME/config/jvm_client_options`文件中的JVM参数。 请注意,该文件中的JVM参数会应用到所有使用`seatunnel.sh`提交的作业。包括Local模式和集群模式。 + +2. 在启动Local模式时添加JVM参数。例如,`$SEATUNNEL_HOME/bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template -m local -DJvmOption="-Xms2G -Xmx2G"` + ## 作业运维 Local模式下提交的作业会在提交作业的进程中运行,作业完成后进程会退出,如果要中止作业只需要退出提交作业的进程即可。作业的运行日志会输出到提交作业的进程的标准输出中。 diff --git a/docs/zh/seatunnel-engine/logging.md b/docs/zh/seatunnel-engine/logging.md index 8f04eaa9117..f97ea572e8c 100644 --- a/docs/zh/seatunnel-engine/logging.md +++ b/docs/zh/seatunnel-engine/logging.md @@ -30,10 +30,10 @@ MDC 由 slf4j 传播到日志后端,后者通常会自动将其添加到日志 Log4j2 使用属性文件进行控制。 -SeaTunnel Engine 发行版在 `confing` 目录中附带以下 log4j 属性文件,如果启用了 Log4j2,则会自动使用这些文件: +SeaTunnel Engine 发行版在 `config` 目录中附带以下 log4j 属性文件,如果启用了 Log4j2,则会自动使用这些文件: -- `log4j2_client.properties`: 由命令行客户端使用 (e.g., `seatunnel.sh`) -- `log4j2.properties`: 由 SeaTunnel 引擎服务使用 (e.g., `seatunnel-cluster.sh`) +- `log4j2_client.properties`: 由命令行客户端使用 (例如, `seatunnel.sh`) +- `log4j2.properties`: 由 SeaTunnel 引擎服务使用 (例如, `seatunnel-cluster.sh`) 默认情况下,日志文件输出到 `logs` 目录。 @@ -80,6 +80,37 @@ appender.file.layout.pattern = [%X{ST-JID}] %d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%- SeaTunnel Engine 自动集成了大多数 Log 桥接器,允许针对 Log4j1/Logback 类工作的现有应用程序继续工作。 +### REST-API方式查询日志 + +SeaTunnel 提供了一个 API,用于查询日志。 + +**使用样例:** +- 获取所有节点jobId为`733584788375666689`的日志信息:`http://localhost:8080/logs/733584788375666689` +- 获取所有节点日志列表:`http://localhost:8080/logs` +- 获取所有节点日志列表以JSON格式返回:`http://localhost:8080/logs?format=json` +- 获取日志文件内容:`http://localhost:8080/logs/job-898380162133917698.log` + +有关详细信息,请参阅 [REST-API](rest-api-v2.md)。 + +## SeaTunnel 日志配置 + +### 定时删除旧日志 + +SeaTunnel 支持定时删除旧日志文件,以避免磁盘空间不足。您可以在 `seatunnel.yml` 文件中添加以下配置: + +```yaml +seatunnel: + engine: + history-job-expire-minutes: 1440 + telemetry: + logs: + scheduled-deletion-enable: true +``` + +- `history-job-expire-minutes`: 设置历史作业和日志的保留时间(单位:分钟)。系统将在指定的时间后自动清除过期的作业信息和日志文件。 +- `scheduled-deletion-enable`: 启用定时清理功能,默认为 `true`。系统将在作业达到 `history-job-expire-minutes` 设置的过期时间后自动删除相关日志文件。关闭该功能后,日志将永久保留在磁盘上,需要用户自行管理,否则可能影响磁盘占用。建议根据需求合理配置。 + + ## 开发人员最佳实践 您可以通过调用 `org.slf4j.LoggerFactory#LoggerFactory.getLogger` 并以您的类的类作为参数来创建 SLF4J 记录器。 diff --git a/docs/zh/seatunnel-engine/resource-isolation.md b/docs/zh/seatunnel-engine/resource-isolation.md index 88ddf0b8716..5cb3db9cf35 100644 --- a/docs/zh/seatunnel-engine/resource-isolation.md +++ b/docs/zh/seatunnel-engine/resource-isolation.md @@ -4,46 +4,46 @@ sidebar_position: 9 # 资源隔离 -在2.3.6版本之后, SeaTunnel支持对每个实例添加`tag`, 然后在提交任务时可以在配置文件中使用`tag_filter`来选择任务将要运行的节点. +SeaTunnel支持对每个实例添加`tag`, 然后在提交任务时可以在配置文件中使用`tag_filter`来选择任务将要运行的节点. -## 如何实现改功能 +## 配置 1. 更新`hazelcast.yaml`文件 -```yaml -hazelcast: - cluster-name: seatunnel - network: - rest-api: - enabled: true - endpoint-groups: - CLUSTER_WRITE: + ```yaml + hazelcast: + cluster-name: seatunnel + network: + rest-api: enabled: true - DATA: - enabled: true - join: - tcp-ip: - enabled: true - member-list: - - localhost - port: - auto-increment: false - port: 5801 - properties: - hazelcast.invocation.max.retry.count: 20 - hazelcast.tcp.join.port.try.count: 30 - hazelcast.logging.type: log4j2 - hazelcast.operation.generic.thread.count: 50 - member-attributes: - group: - type: string - value: platform - team: - type: string - value: team1 -``` - -在这个配置中, 我们通过`member-attributes`设置了`group=platform, team=team1`这样两个`tag` + endpoint-groups: + CLUSTER_WRITE: + enabled: true + DATA: + enabled: true + join: + tcp-ip: + enabled: true + member-list: + - localhost + port: + auto-increment: false + port: 5801 + properties: + hazelcast.invocation.max.retry.count: 20 + hazelcast.tcp.join.port.try.count: 30 + hazelcast.logging.type: log4j2 + hazelcast.operation.generic.thread.count: 50 + member-attributes: + group: + type: string + value: platform + team: + type: string + value: team1 + ``` + + 在这个配置中, 我们通过`member-attributes`设置了`group=platform, team=team1`这样两个`tag` 2. 在任务的配置中添加`tag_filter`来选择你需要运行该任务的节点 @@ -58,7 +58,7 @@ env { } source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" parallelism = 1 schema = { fields { @@ -71,18 +71,18 @@ transform { } sink { console { - source_table_name="fake" + plugin_input="fake" } } ``` -**注意:** -- 当在任务的配置中, 没有添加`tag_filter`时, 会从所有节点中随机选择节点来运行任务. -- 当`tag_filter`中存在多个过滤条件时, 会根据key存在以及value相等的全部匹配的节点, 当没有找到匹配的节点时, 会抛出 `NoEnoughResourceException`异常. + **注意:** + - 当在任务的配置中, 没有添加`tag_filter`时, 会从所有节点中随机选择节点来运行任务. + - 当`tag_filter`中存在多个过滤条件时, 会根据key存在以及value相等的全部匹配的节点, 当没有找到匹配的节点时, 会抛出 `NoEnoughResourceException`异常. -![img.png](../../images/resource-isolation.png) + ![img.png](../../images/resource-isolation.png) 3. 更新运行中node的tags (可选) -获取具体的使用信息,请参考 [更新运行节点的tags](https://seatunnel.apache.org/zh-CN/docs/seatunnel-engine/rest-api/) + 获取具体的使用信息,请参考 [更新运行节点的tags](rest-api-v2.md) diff --git a/docs/zh/seatunnel-engine/rest-api.md b/docs/zh/seatunnel-engine/rest-api-v1.md similarity index 81% rename from docs/zh/seatunnel-engine/rest-api.md rename to docs/zh/seatunnel-engine/rest-api-v1.md index 3965a76ff25..15b0cf0545d 100644 --- a/docs/zh/seatunnel-engine/rest-api.md +++ b/docs/zh/seatunnel-engine/rest-api-v1.md @@ -2,7 +2,13 @@ sidebar_position: 11 --- -# RESTful API +# RESTful API V1 + +:::caution warn + +推荐使用v2版本的Rest API。 v1 版本已弃用,并将在将来删除。 + +::: SeaTunnel有一个用于监控的API,可用于查询运行作业的状态和统计信息,以及最近完成的作业。监控API是RESTful风格的,它接受HTTP请求并使用JSON数据格式进行响应。 @@ -113,10 +119,19 @@ network: }, "createTime": "", "jobDag": { - "vertices": [ + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } ], - "edges": [ - ] + "pipelineEdges": {} }, "pluginJarsUrls": [ ], @@ -153,10 +168,19 @@ network: "jobStatus": "", "createTime": "", "jobDag": { - "vertices": [ + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } ], - "edges": [ - ] + "pipelineEdges": {} }, "metrics": { "SourceReceivedCount": "", @@ -224,10 +248,19 @@ network: "jobStatus": "", "createTime": "", "jobDag": { - "vertices": [ + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } ], - "edges": [ - ] + "pipelineEdges": {} }, "metrics": { "sourceReceivedCount": "", @@ -281,7 +314,21 @@ network: "errorMsg": null, "createTime": "", "finishTime": "", - "jobDag": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, "metrics": "" } ] @@ -382,7 +429,7 @@ network: "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 100, "schema": { "fields": { @@ -398,7 +445,7 @@ network: "sink": [ { "plugin_name": "Console", - "source_table_name": ["fake"] + "plugin_input": ["fake"] } ] } @@ -448,7 +495,7 @@ network: "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 1000, "schema": { "fields": { @@ -464,7 +511,7 @@ network: "sink": [ { "plugin_name": "Console", - "source_table_name": ["fake"] + "plugin_input": ["fake"] } ] }, @@ -479,7 +526,7 @@ network: "source": [ { "plugin_name": "FakeSource", - "result_table_name": "fake", + "plugin_output": "fake", "row.num": 1000, "schema": { "fields": { @@ -495,7 +542,7 @@ network: "sink": [ { "plugin_name": "Console", - "source_table_name": ["fake"] + "plugin_input": ["fake"] } ] } @@ -607,7 +654,7 @@ network: "age": "int" } }, - "result_table_name": "fake", + "plugin_output": "fake", "parallelism": 1, "hostname": "127.0.0.1", "username": "seatunnel", @@ -647,7 +694,7 @@ network: "age": "int" } }, - "result_table_name": "fake", + "plugin_output": "fake", "parallelism": 1, "hostname": "127.0.0.1", "username": "c2VhdHVubmVs", @@ -731,4 +778,74 @@ network: "message": "Invalid JSON format in request body." } ``` - \ No newline at end of file + + + +------------------------------------------------------------------------------------------ + +### 获取所有节点日志内容 + +
+ GET /hazelcast/rest/maps/logs/:jobId (返回日志列表。) + +#### 请求参数 + +#### 参数(在请求体中params字段中添加) + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |----------------------|----------|--------|-----------------------------------| +> | jobId | optional | string | job id | + +当`jobId`为空时,返回所有节点的日志信息,否则返回指定`jobId`在所有节点的的日志列表。 + +#### 响应 + +返回请求节点的日志列表、内容 + +#### 返回所有日志文件列表 + +如果你想先查看日志列表,可以通过`GET`请求获取日志列表,`http://localhost:5801/hazelcast/rest/maps/logs?format=json` + +```json +[ + { + "node": "localhost:5801", + "logLink": "http://localhost:5801/hazelcast/rest/maps/logs/job-899485770241277953.log", + "logName": "job-899485770241277953.log" + }, + { + "node": "localhost:5801", + "logLink": "http://localhost:5801/hazelcast/rest/maps/logs/job-899470314109468673.log", + "logName": "job-899470314109468673.log" + } +] +``` + +当前支持的格式有`json`和`html`,默认为`html`。 + +#### 例子 + +获取所有节点jobId为`733584788375666689`的日志信息:`http://localhost:5801/hazelcast/rest/maps/logs/733584788375666689` +获取所有节点日志列表:`http://localhost:5801/hazelcast/rest/maps/logs` +获取所有节点日志列表以JSON格式返回:`http://localhost:5801/hazelcast/rest/maps/logs?format=json` +获取日志文件内容:`http://localhost:5801/hazelcast/rest/maps/logs/job-898380162133917698.log`` + + +
+ + +### 获取单节点日志内容 + +
+ GET /hazelcast/rest/maps/log (返回日志列表。) + +#### 响应 + +返回请求节点的日志列表 + +#### 例子 + +获取当前节点的日志列表:`http://localhost:5801/hazelcast/rest/maps/log` +获取日志文件内容:`http://localhost:5801/hazelcast/rest/maps/log/job-898380162133917698.log` + +
diff --git a/docs/zh/seatunnel-engine/rest-api-v2.md b/docs/zh/seatunnel-engine/rest-api-v2.md new file mode 100644 index 00000000000..0e3b3e2657d --- /dev/null +++ b/docs/zh/seatunnel-engine/rest-api-v2.md @@ -0,0 +1,852 @@ +--- +sidebar_position: 12 +--- + +# RESTful API V2 + +SeaTunnel有一个用于监控的API,可用于查询运行作业的状态和统计信息,以及最近完成的作业。监控API是RESTful风格的,它接受HTTP请求并使用JSON数据格式进行响应。 + +## 概述 + +v2版本的api使用jetty支持,与v1版本的接口规范相同 ,可以通过修改`seatunnel.yaml`中的配置项来指定端口和context-path, +同时可以配置 `enable-dynamic-port` 开启动态端口(默认从 `port` 开始累加),默认为关闭, +如果`enable-dynamic-port`为`true`,我们将使用`port`和`port`+`port-range`范围内未使用的端口,默认范围是100。 + +```yaml + +seatunnel: + engine: + http: + enable-http: true + port: 8080 + enable-dynamic-port: false + port-range: 100 +``` + +同时也可以配置context-path,配置如下: + +```yaml + +seatunnel: + engine: + http: + enable-http: true + port: 8080 + context-path: /seatunnel +``` + +## API参考 + +### 返回Zeta集群的概览 + +
+ GET /overview?tag1=value1&tag2=value2 (Returns an overview over the Zeta engine cluster.) + +#### 参数 + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |--------|------|------|--------------------------| +> | tag键值对 | 否 | 字符串 | 一组标签值, 通过该标签值过滤满足条件的节点信息 | + +#### 响应 + +```json +{ + "projectVersion":"2.3.5-SNAPSHOT", + "gitCommitAbbrev":"DeadD0d0", + "totalSlot":"0", + "unassignedSlot":"0", + "works":"1", + "runningJobs":"0", + "finishedJobs":"0", + "failedJobs":"0", + "cancelledJobs":"0" +} +``` + +**注意:** +- 当你使用`dynamic-slot`时, 返回结果中的`totalSlot`和`unassignedSlot`将始终为0. 设置为固定的slot值后, 将正确返回集群中总共的slot数量以及未分配的slot数量. +- 当添加标签过滤后, `works`, `totalSlot`, `unassignedSlot`将返回满足条件的节点的相关指标. 注意`runningJobs`等job相关指标为集群级别结果, 无法根据标签进行过滤. + +
+ +------------------------------------------------------------------------------------------ + +### 返回所有作业及其当前状态的概览 + +
+ GET /running-jobs (返回所有作业及其当前状态的概览。) + +#### 参数 + +#### 响应 + +```json +[ + { + "jobId": "", + "jobName": "", + "jobStatus": "", + "createTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "pluginJarsUrls": [ + ], + "isStartWithSavePoint": false, + "metrics": { + "sourceReceivedCount": "", + "sinkWriteCount": "" + } + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### 返回作业的详细信息 + +
+ GET /job-info/:jobId (返回作业的详细信息。) + +#### 参数 + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |-------|------|------|--------| +> | jobId | 是 | long | job id | + +#### 响应 + +```json +{ + "jobId": "", + "jobName": "", + "jobStatus": "", + "createTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "metrics": { + "SourceReceivedCount": "", + "SourceReceivedQPS": "", + "SourceReceivedBytes": "", + "SourceReceivedBytesPerSeconds": "", + "SinkWriteCount": "", + "SinkWriteQPS": "", + "SinkWriteBytes": "", + "SinkWriteBytesPerSeconds": "", + "TableSourceReceivedCount": {}, + "TableSourceReceivedBytes": {}, + "TableSourceReceivedBytesPerSeconds": {}, + "TableSourceReceivedQPS": {}, + "TableSinkWriteCount": {}, + "TableSinkWriteQPS": {}, + "TableSinkWriteBytes": {}, + "TableSinkWriteBytesPerSeconds": {} + }, + "finishedTime": "", + "errorMsg": null, + "envOptions": { + }, + "pluginJarsUrls": [ + ], + "isStartWithSavePoint": false +} +``` + +`jobId`, `jobName`, `jobStatus`, `createTime`, `jobDag`, `metrics` 字段总会返回. +`envOptions`, `pluginJarsUrls`, `isStartWithSavePoint` 字段在Job在RUNNING状态时会返回 +`finishedTime`, `errorMsg` 字段在Job结束时会返回,结束状态为不为RUNNING,可能为FINISHED,可能为CANCEL + +当我们查询不到这个Job时,返回结果为: + +```json +{ + "jobId" : "" +} +``` + +
+ +------------------------------------------------------------------------------------------ + +### 返回作业的详细信息 + +此API已经弃用,请使用/job-info/:jobId替代。 + +
+ GET /running-job/:jobId (返回作业的详细信息。) + +#### 参数 + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |-------|------|------|--------| +> | jobId | 是 | long | job id | + +#### 响应 + +```json +{ + "jobId": "", + "jobName": "", + "jobStatus": "", + "createTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "metrics": { + "sourceReceivedCount": "", + "sinkWriteCount": "" + }, + "finishedTime": "", + "errorMsg": null, + "envOptions": { + }, + "pluginJarsUrls": [ + ], + "isStartWithSavePoint": false +} +``` + +`jobId`, `jobName`, `jobStatus`, `createTime`, `jobDag`, `metrics` 字段总会返回. +`envOptions`, `pluginJarsUrls`, `isStartWithSavePoint` 字段在Job在RUNNING状态时会返回 +`finishedTime`, `errorMsg` 字段在Job结束时会返回,结束状态为不为RUNNING,可能为FINISHED,可能为CANCEL + +当我们查询不到这个Job时,返回结果为: + +```json +{ + "jobId" : "" +} +``` + +
+ +------------------------------------------------------------------------------------------ + +### 返回所有已完成的作业信息 + +
+ GET /finished-jobs/:state (返回所有已完成的作业信息。) + +#### 参数 + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |-------|----------|--------|------------------------------------------------------------------| +> | state | optional | string | finished job status. `FINISHED`,`CANCELED`,`FAILED`,`UNKNOWABLE` | + +#### 响应 + +```json +[ + { + "jobId": "", + "jobName": "", + "jobStatus": "", + "errorMsg": null, + "createTime": "", + "finishTime": "", + "jobDag": { + "jobId": "", + "envOptions": [], + "vertexInfoMap": [ + { + "vertexId": 1, + "type": "", + "vertexName": "", + "tablePaths": [ + "" + ] + } + ], + "pipelineEdges": {} + }, + "metrics": "" + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### 返回系统监控信息 + +
+ GET /system-monitoring-information (返回系统监控信息。) + +#### 参数 + +#### 响应 + +```json +[ + { + "processors":"8", + "physical.memory.total":"16.0G", + "physical.memory.free":"16.3M", + "swap.space.total":"0", + "swap.space.free":"0", + "heap.memory.used":"135.7M", + "heap.memory.free":"440.8M", + "heap.memory.total":"576.5M", + "heap.memory.max":"3.6G", + "heap.memory.used/total":"23.54%", + "heap.memory.used/max":"3.73%", + "minor.gc.count":"6", + "minor.gc.time":"110ms", + "major.gc.count":"2", + "major.gc.time":"73ms", + "load.process":"24.78%", + "load.system":"60.00%", + "load.systemAverage":"2.07", + "thread.count":"117", + "thread.peakCount":"118", + "cluster.timeDiff":"0", + "event.q.size":"0", + "executor.q.async.size":"0", + "executor.q.client.size":"0", + "executor.q.client.query.size":"0", + "executor.q.client.blocking.size":"0", + "executor.q.query.size":"0", + "executor.q.scheduled.size":"0", + "executor.q.io.size":"0", + "executor.q.system.size":"0", + "executor.q.operations.size":"0", + "executor.q.priorityOperation.size":"0", + "operations.completed.count":"10", + "executor.q.mapLoad.size":"0", + "executor.q.mapLoadAllKeys.size":"0", + "executor.q.cluster.size":"0", + "executor.q.response.size":"0", + "operations.running.count":"0", + "operations.pending.invocations.percentage":"0.00%", + "operations.pending.invocations.count":"0", + "proxy.count":"8", + "clientEndpoint.count":"0", + "connection.active.count":"2", + "client.connection.count":"0", + "connection.count":"0" + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### 提交作业 + +
+POST /submit-job (如果作业提交成功,返回jobId和jobName。) + +#### 参数 + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |----------------------|----------|-----------------------------------|-----------------------------------| +> | jobId | optional | string | job id | +> | jobName | optional | string | job name | +> | isStartWithSavePoint | optional | string | if job is started with save point | +> | format | optional | string | 配置风格,支持json和hocon,默认 json | + +#### 请求体 + +你可以选择用json或者hocon的方式来传递请求体。 +Json请求示例: +```json +{ + "env": { + "job.mode": "batch" + }, + "source": [ + { + "plugin_name": "FakeSource", + "plugin_output": "fake", + "row.num": 100, + "schema": { + "fields": { + "name": "string", + "age": "int", + "card": "int" + } + } + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Console", + "plugin_input": ["fake"] + } + ] +} +``` + +Hocon请求示例: +```hocon +env { + job.mode = "batch" +} + +source { + FakeSource { + result_table_name = "fake" + row.num = 100 + schema = { + fields { + name = "string" + age = "int" + card = "int" + } + } + } +} + +transform { +} + +sink { + Console { + source_table_name = "fake" + } +} + +``` +#### 响应 + +```json +{ + "jobId": 733584788375666689, + "jobName": "rest_api_test" +} +``` + +
+ +------------------------------------------------------------------------------------------ + + +### 批量提交作业 + +
+POST /submit-jobs (如果作业提交成功,返回jobId和jobName。) + +#### 参数(在请求体中params字段中添加) + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |----------------------|----------|--------|-----------------------------------| +> | jobId | optional | string | job id | +> | jobName | optional | string | job name | +> | isStartWithSavePoint | optional | string | if job is started with save point | + + + +#### 请求体 + +```json +[ + { + "params":{ + "jobId":"123456", + "jobName":"SeaTunnel-01" + }, + "env": { + "job.mode": "batch" + }, + "source": [ + { + "plugin_name": "FakeSource", + "plugin_output": "fake", + "row.num": 1000, + "schema": { + "fields": { + "name": "string", + "age": "int", + "card": "int" + } + } + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Console", + "plugin_input": ["fake"] + } + ] + }, + { + "params":{ + "jobId":"1234567", + "jobName":"SeaTunnel-02" + }, + "env": { + "job.mode": "batch" + }, + "source": [ + { + "plugin_name": "FakeSource", + "plugin_output": "fake", + "row.num": 1000, + "schema": { + "fields": { + "name": "string", + "age": "int", + "card": "int" + } + } + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Console", + "plugin_input": ["fake"] + } + ] + } +] +``` + +#### 响应 + +```json +[ + { + "jobId": "123456", + "jobName": "SeaTunnel-01" + },{ + "jobId": "1234567", + "jobName": "SeaTunnel-02" + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### 停止作业 + +
+POST /stop-job (如果作业成功停止,返回jobId。) + +#### 请求体 + +```json +{ + "jobId": 733584788375666689, + "isStopWithSavePoint": false # if job is stopped with save point +} +``` + +#### 响应 + +```json +{ +"jobId": 733584788375666689 +} +``` + +
+ + +------------------------------------------------------------------------------------------ + +### 批量停止作业 + +
+POST /stop-jobs (如果作业成功停止,返回jobId。) + +#### 请求体 + +```json +[ + { + "jobId": 881432421482889220, + "isStopWithSavePoint": false + }, + { + "jobId": 881432456517910529, + "isStopWithSavePoint": false + } +] +``` + +#### 响应 + +```json +[ + { + "jobId": 881432421482889220 + }, + { + "jobId": 881432456517910529 + } +] +``` + +
+ +------------------------------------------------------------------------------------------ + +### 加密配置 + +
+POST /encrypt-config (如果配置加密成功,则返回加密后的配置。) +有关自定义加密的更多信息,请参阅文档[配置-加密-解密](../connector-v2/Config-Encryption-Decryption.md). + +#### 请求体 + +```json +{ + "env": { + "parallelism": 1, + "shade.identifier":"base64" + }, + "source": [ + { + "plugin_name": "MySQL-CDC", + "schema" : { + "fields": { + "name": "string", + "age": "int" + } + }, + "plugin_output": "fake", + "parallelism": 1, + "hostname": "127.0.0.1", + "username": "seatunnel", + "password": "seatunnel_password", + "table-name": "inventory_vwyw0n" + } + ], + "transform": [ + ], + "sink": [ + { + "plugin_name": "Clickhouse", + "host": "localhost:8123", + "database": "default", + "table": "fake_all", + "username": "seatunnel", + "password": "seatunnel_password" + } + ] +} +``` + +#### 响应 + +```json +{ + "env": { + "parallelism": 1, + "shade.identifier": "base64" + }, + "source": [ + { + "plugin_name": "MySQL-CDC", + "schema": { + "fields": { + "name": "string", + "age": "int" + } + }, + "plugin_output": "fake", + "parallelism": 1, + "hostname": "127.0.0.1", + "username": "c2VhdHVubmVs", + "password": "c2VhdHVubmVsX3Bhc3N3b3Jk", + "table-name": "inventory_vwyw0n" + } + ], + "transform": [], + "sink": [ + { + "plugin_name": "Clickhouse", + "host": "localhost:8123", + "database": "default", + "table": "fake_all", + "username": "c2VhdHVubmVs", + "password": "c2VhdHVubmVsX3Bhc3N3b3Jk" + } + ] +} +``` + +
+ +------------------------------------------------------------------------------------------ + +### 更新运行节点的tags + +
+POST/update-tags因为更新只能针对于某个节点,因此需要用当前节点ip:port用于更新(如果更新成功,则返回"success"信息) + + +#### 更新节点tags +##### 请求体 +如果请求参数是`Map`对象,表示要更新当前节点的tags +```json +{ + "tag1": "dev_1", + "tag2": "dev_2" +} +``` +##### 响应 + +```json +{ + "status": "success", + "message": "update node tags done." +} +``` +#### 移除节点tags +##### 请求体 +如果参数为空`Map`对象,表示要清除当前节点的tags +```json +{} +``` +##### 响应 +响应体将为: +```json +{ + "status": "success", + "message": "update node tags done." +} +``` + +#### 请求参数异常 +- 如果请求参数为空 + +##### 响应 + +```json +{ + "status": "fail", + "message": "Request body is empty." +} +``` +- 如果参数不是`Map`对象 +##### 响应 + +```json +{ + "status": "fail", + "message": "Invalid JSON format in request body." +} +``` +
+ + +------------------------------------------------------------------------------------------ + +### 获取所有节点日志内容 + +
+ GET /logs/:jobId (返回日志列表。) + +#### 请求参数 + +#### 参数(在请求体中params字段中添加) + +> | 参数名称 | 是否必传 | 参数类型 | 参数描述 | +> |----------------------|----------|--------|-----------------------------------| +> | jobId | optional | string | job id | + +当`jobId`为空时,返回所有节点的日志信息,否则返回指定`jobId`在所有节点的的日志列表。 + +#### 响应 + +返回请求节点的日志列表、内容 + +#### 返回所有日志文件列表 + +如果你想先查看日志列表,可以通过`GET`请求获取日志列表,`http://localhost:8080/logs?format=json` + +```json +[ + { + "node": "localhost:8080", + "logLink": "http://localhost:8080/logs/job-899485770241277953.log", + "logName": "job-899485770241277953.log" + }, + { + "node": "localhost:8080", + "logLink": "http://localhost:8080/logs/job-899470314109468673.log", + "logName": "job-899470314109468673.log" + } +] +``` + +当前支持的格式有`json`和`html`,默认为`html`。 + + +#### 例子 + +获取所有节点jobId为`733584788375666689`的日志信息:`http://localhost:8080/logs/733584788375666689` +获取所有节点日志列表:`http://localhost:8080/logs` +获取所有节点日志列表以JSON格式返回:`http://localhost:8080/logs?format=json` +获取日志文件内容:`http://localhost:8080/logs/job-898380162133917698.log` + + +
+ + +### 获取单节点日志内容 + +
+ GET /log (返回日志列表。) + +#### 响应 + +返回请求节点的日志列表 + +#### 例子 + +获取当前节点的日志列表:`http://localhost:5801/log` +获取日志文件内容:`http://localhost:5801/log/job-898380162133917698.log`` + +
diff --git a/docs/zh/seatunnel-engine/separated-cluster-deployment.md b/docs/zh/seatunnel-engine/separated-cluster-deployment.md index 9c6a230537e..bdc369ff8c0 100644 --- a/docs/zh/seatunnel-engine/separated-cluster-deployment.md +++ b/docs/zh/seatunnel-engine/separated-cluster-deployment.md @@ -75,7 +75,7 @@ SeaTunnel Engine 基于 [Hazelcast IMDG](https://docs.hazelcast.com/imdg/4.1/) `backup count` 是定义同步备份数量的参数。例如,如果设置为 1,则分区的备份将放置在一个其他成员上。如果设置为 2,则将放置在两个其他成员上。 -我们建议 `backup-count` 的值为 `min(1, max(5, N/2))`。 `N` 是集群节点的数量。 +我们建议 `backup-count` 的值为 `max(1, min(5, N/2))`。 `N` 是集群节点的数量。 ```yaml seatunnel: @@ -284,6 +284,23 @@ netty-common-4.1.89.Final.jar seatunnel-hadoop3-3.1.4-uber.jar ``` +### 4.7 作业调度策略 + +当资源不足时,作业调度策略可以配置为以下两种模式: + +1. `WAIT`:等待资源可用。 +2. `REJECT`:拒绝作业,默认值。 + +示例 + +```yaml +seatunnel: + engine: + job-schedule-strategy: WAIT +``` + +当`dynamic-slot: ture`时,`job-schedule-strategy: WAIT` 配置会失效,将被强制修改为`job-schedule-strategy: REJECT`,因为动态Slot时该参数没有意义,可以直接提交。 + ## 5. 配置 SeaTunnel Engine 网络服务 所有 SeaTunnel Engine 网络相关的配置都在 `hazelcast-master.yaml`和`hazelcast-worker.yaml` 文件中. @@ -448,4 +465,4 @@ hazelcast-client: ### 8.2 使用 REST API 提交作业 -SeaTunnel Engine 提供了 REST API 用于提交作业。有关详细信息,请参阅 [REST API](rest-api.md) +SeaTunnel Engine 提供了 REST API 用于提交作业。有关详细信息,请参阅 [REST API V2](rest-api-v2.md) diff --git a/docs/zh/seatunnel-engine/telemetry.md b/docs/zh/seatunnel-engine/telemetry.md index 31972a44bca..b65a3d0225b 100644 --- a/docs/zh/seatunnel-engine/telemetry.md +++ b/docs/zh/seatunnel-engine/telemetry.md @@ -1,5 +1,5 @@ --- -sidebar_position: 13 +sidebar_position: 14 --- # Telemetry diff --git a/docs/zh/seatunnel-engine/telemetry/grafana-dashboard.json b/docs/zh/seatunnel-engine/telemetry/grafana-dashboard.json index e69de29bb2d..7a87e47ff38 100644 --- a/docs/zh/seatunnel-engine/telemetry/grafana-dashboard.json +++ b/docs/zh/seatunnel-engine/telemetry/grafana-dashboard.json @@ -0,0 +1 @@ +{"annotations":{"list":[{"builtIn":1,"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"enable":true,"hide":true,"iconColor":"rgba(0, 211, 255, 1)","name":"Annotations & Alerts","target":{"limit":100,"matchAny":false,"tags":[],"type":"dashboard"},"type":"dashboard"}]},"editable":true,"fiscalYearStartMonth":0,"graphTooltip":0,"id":8,"links":[],"liveNow":false,"panels":[{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"thresholds"},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"super-light-blue","value":null},{"color":"red","value":100000}]}},"overrides":[]},"gridPos":{"h":4,"w":12,"x":0,"y":0},"id":17,"options":{"colorMode":"background","graphMode":"none","justifyMode":"center","orientation":"auto","percentChangeColorMode":"standard","reduceOptions":{"calcs":["lastNotNull"],"fields":"","values":false},"showPercentChange":false,"textMode":"auto","wideLayout":true},"pluginVersion":"11.2.0","targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"node_count{instance=~\"$instance\"}","interval":"","legendFormat":"","range":true,"refId":"A"}],"title":"Total Node Count","type":"stat"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"thresholds"},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":4,"w":12,"x":12,"y":0},"id":18,"options":{"colorMode":"background","graphMode":"none","justifyMode":"auto","orientation":"auto","percentChangeColorMode":"standard","reduceOptions":{"calcs":["lastNotNull"],"fields":"","values":false},"showPercentChange":false,"text":{"titleSize":1},"textMode":"auto","wideLayout":true},"pluginVersion":"11.2.0","targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"sum(node_state{instance=~\"$instance\"})","interval":"","legendFormat":"__auto","range":true,"refId":"A"}],"title":"UP Node Count","type":"stat"},{"collapsed":false,"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"gridPos":{"h":1,"w":24,"x":0,"y":4},"id":22,"panels":[],"targets":[{"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"refId":"A"}],"title":"Hazelcast Partition","type":"row"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":7,"w":12,"x":0,"y":5},"id":32,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_partition_partitionCount{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}","range":true,"refId":"A"}],"title":"partitionCount","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"linear","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":7,"w":12,"x":12,"y":5},"id":33,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_partition_activePartition{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}","range":true,"refId":"A"}],"title":"activePartition","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":7,"w":12,"x":0,"y":12},"id":34,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"pluginVersion":"8.3.3","targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_partition_isClusterSafe{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}","range":true,"refId":"A"}],"title":"isClusterSafe","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":7,"w":12,"x":12,"y":12},"id":35,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_partition_isLocalMemberSafe{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}","range":true,"refId":"A"}],"title":"isLocalMemberSafe","type":"timeseries"},{"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"gridPos":{"h":1,"w":24,"x":0,"y":19},"id":20,"targets":[{"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"refId":"A"}],"title":"Hazelcast Executor","type":"row"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":6,"w":24,"x":0,"y":20},"id":24,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_executedCount{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"executedCount","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":26},"id":26,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_isTerminated{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"isTerminated","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":26},"id":25,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_isShutdown{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"isShutdown","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":34},"id":28,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_poolSize{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"poolSize","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":34},"id":27,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_maxPoolSize{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"maxPoolSize","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":0,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"linear","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":42},"id":30,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_queueRemainingCapacity{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"queueRemainingCapacity","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":0,"gradientMode":"none","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"linear","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":42},"id":29,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"hazelcast_executor_queueSize{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"}],"title":"queueSize","type":"timeseries"},{"collapsed":false,"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"gridPos":{"h":1,"w":24,"x":0,"y":50},"id":7,"panels":[],"targets":[{"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"refId":"A"}],"title":"System","type":"row"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"description":"","fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":18,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineStyle":{"fill":"solid"},"lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":51},"id":9,"interval":"300","options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"rate(process_cpu_seconds_total{instance=~\"$instance\"}[$__interval])*100","interval":"","legendFormat":"{{instance}}","range":true,"refId":"A"}],"title":"Cpu Usage","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"description":"","fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":22,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":51},"id":10,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"100 * (jvm_memory_bytes_used{instance=~\"$instance\",area=\"heap\"} / jvm_memory_bytes_max{instance=~\"$instance\",area=\"heap\"})","interval":"","legendFormat":"{{instance}}","range":true,"refId":"A"}],"title":"Heap Memory Usage","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":0,"y":59},"id":12,"interval":"300","options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"increase(jvm_gc_collection_seconds_count[$__interval])","interval":"","legendFormat":"{{instance}}-{{gc}}","range":true,"refId":"A"}],"title":"GC Count","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":12,"x":12,"y":59},"id":13,"interval":"300","options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"increase(jvm_gc_collection_seconds_sum{instance=~\"$instance\"}[$__interval])*1000","interval":"","legendFormat":"{{instance}}-{{gc}}","range":true,"refId":"A"}],"title":"GC Cost Time","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"linear","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":8,"w":24,"x":0,"y":67},"id":14,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"right","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"jvm_threads_current{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-current","range":true,"refId":"A"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"jvm_threads_daemon{instance=~\"$instance\"}","hide":false,"interval":"","legendFormat":"{{instance}}-daemon","range":true,"refId":"B"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"jvm_threads_peak{instance=~\"$instance\"}","hide":false,"interval":"","legendFormat":"{{instance}}-peak","range":true,"refId":"C"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"jvm_threads_deadlocked{instance=~\"$instance\"}","hide":false,"interval":"","legendFormat":"{{instance}}-deadlocked","range":true,"refId":"D"}],"title":"Jvm Threads","type":"timeseries"},{"collapsed":false,"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"gridPos":{"h":1,"w":24,"x":0,"y":75},"id":5,"panels":[],"targets":[{"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"refId":"A"}],"title":"Job","type":"row"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"continuous-YlBl"},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null}]}},"overrides":[]},"gridPos":{"h":6,"w":24,"x":0,"y":76},"id":2,"options":{"displayMode":"basic","maxVizHeight":300,"minVizHeight":16,"minVizWidth":8,"namePlacement":"auto","orientation":"vertical","reduceOptions":{"calcs":["lastNotNull"],"fields":"","values":false},"showUnfilled":true,"sizing":"auto","valueMode":"color"},"pluginVersion":"11.2.0","targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"sum(job_count) by (type) ","hide":false,"interval":"","legendFormat":"__auto","range":true,"refId":"A"}],"title":"Job Count","type":"bargauge"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":6,"w":12,"x":0,"y":82},"id":3,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"job_thread_pool_activeCount{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"job_thread_pool_corePoolSize{instance=~\"$instance\"}","hide":false,"interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"B"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"job_thread_pool_maximumPoolSize{instance=~\"$instance\"}","hide":true,"interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"C"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"job_thread_pool_poolSize{instance=~\"$instance\"}","hide":false,"interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"D"}],"title":"Job Thread Pool","type":"timeseries"},{"datasource":{"default":true,"type":"prometheus","uid":"edwo9tknxxgqof"},"fieldConfig":{"defaults":{"color":{"mode":"palette-classic"},"custom":{"axisBorderShow":false,"axisCenteredZero":false,"axisColorMode":"text","axisLabel":"","axisPlacement":"auto","barAlignment":0,"barWidthFactor":0.6,"drawStyle":"line","fillOpacity":12,"gradientMode":"opacity","hideFrom":{"legend":false,"tooltip":false,"viz":false},"insertNulls":false,"lineInterpolation":"smooth","lineWidth":1,"pointSize":1,"scaleDistribution":{"type":"linear"},"showPoints":"auto","spanNulls":false,"stacking":{"group":"A","mode":"none"},"thresholdsStyle":{"mode":"off"}},"mappings":[],"thresholds":{"mode":"absolute","steps":[{"color":"green","value":null},{"color":"red","value":80}]}},"overrides":[]},"gridPos":{"h":6,"w":12,"x":12,"y":82},"id":15,"options":{"legend":{"calcs":[],"displayMode":"list","placement":"bottom","showLegend":true},"tooltip":{"mode":"single","sort":"none"}},"targets":[{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"job_thread_pool_completedTask_total{instance=~\"$instance\"}","interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"A"},{"datasource":{"type":"prometheus","uid":"jUi2yaj4k"},"editorMode":"code","exemplar":true,"expr":"job_thread_pool_task_total{instance=~\"$instance\"}","hide":false,"interval":"","legendFormat":"{{instance}}-{{type}}","range":true,"refId":"B"}],"title":"Job Thread Pool Total","type":"timeseries"}],"refresh":"30s","schemaVersion":39,"tags":[],"templating":{"list":[{"current":{"selected":true,"text":["All"],"value":["$__all"]},"datasource":{"type":"prometheus","uid":"edwo9tknxxgqof"},"definition":"label_values(cluster_info,instance)","description":"instance","hide":0,"includeAll":true,"label":"","multi":true,"name":"instance","options":[],"query":{"qryType":5,"query":"label_values(cluster_info,instance)","refId":"PrometheusVariableQueryEditor-VariableQuery"},"refresh":1,"regex":"","skipUrlSync":false,"sort":0,"type":"query"}]},"time":{"from":"now-15m","to":"now"},"timepicker":{},"timezone":"","title":"Seatunnel","uid":"bdx1j097hmku8d","version":11,"weekStart":""} \ No newline at end of file diff --git a/docs/zh/seatunnel-engine/user-command.md b/docs/zh/seatunnel-engine/user-command.md index 9311ad912f6..1ceea35c85d 100644 --- a/docs/zh/seatunnel-engine/user-command.md +++ b/docs/zh/seatunnel-engine/user-command.md @@ -1,5 +1,5 @@ --- -sidebar_position: 12 +sidebar_position: 13 --- # 命令行工具 @@ -138,3 +138,12 @@ bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template 被cancel的作业的所有断点信息都将被删除,无法通过seatunnel.sh -r <jobId>恢复。 +## 配置JVM参数 + +我们可以通过以下方式为 SeaTunnel Engine 客户端配置 JVM 参数: + +1. 添加JVM参数到`$SEATUNNEL_HOME/config/jvm_client_options`文件中。 + + 在 `$SEATUNNEL_HOME/config/jvm_client_options` 文件中修改 JVM 参数。请注意,该文件中的 JVM 参数将应用于使用 `seatunnel.sh` 提交的所有作业,包括 Local 模式和 Cluster 模式。 + +2. 在提交作业时添加 JVM 参数。例如,`sh bin/seatunnel.sh --config $SEATUNNEL_HOME/config/v2.batch.config.template -DJvmOption="-Xms2G -Xmx2G"` diff --git a/docs/zh/seatunnel-engine/web-ui.md b/docs/zh/seatunnel-engine/web-ui.md new file mode 100644 index 00000000000..35b551225ec --- /dev/null +++ b/docs/zh/seatunnel-engine/web-ui.md @@ -0,0 +1,47 @@ +# Web UI + +## 访问 + +在访问 web ui 之前我们需要开启 http rest api。首先需要在`seatunnel.yaml`配置文件中配置 + +``` +seatunnel: + engine: + http: + enable-http: true + port: 8080 +``` + +然后访问 `http://ip:8080/#/overview` + +## 概述 + +Apache SeaTunnel 的 Web UI 提供了一个友好的用户界面,用于监控和管理 SeaTunnel 作业。通过 Web UI,用户可以实时查看当前运行的作业、已完成的作业,以及集群中工作节点和管理节点的状态。主要功能模块包括 Jobs、Workers 和 Master,每个模块都提供了详细的状态信息和操作选项,帮助用户高效地管理和优化其数据处理流程。 +![overview.png](../../images/ui/overview.png) + +## 作业 + +### 运行中的作业 + +“运行中的作业”模块列出了当前正在执行的所有 SeaTunnel 作业。用户可以查看每个作业的基本信息,包括作业 ID、提交时间、状态、执行时间等。点击具体作业可以查看更多详细信息,如任务分布、资源使用情况和日志输出,便于用户实时监控作业进度并及时处理潜在问题。 +![running.png](../../images/ui/running.png) +![detail.png](../../images/ui/detail.png) + +### 已完成的作业 + +“已完成的作业”模块展示了所有已成功完成或失败的 SeaTunnel 作业。此部分提供了每个作业的执行结果、完成时间、耗时以及失败原因(如果有)。用户可以通过此模块回顾过去的作业记录,分析作业性能,进行故障排查或重复执行某些特定作业。 +![finished.png](../../images/ui/finished.png) + +## 工作节点 + +### 工作节点信息 + +“工作节点”模块展示了集群中所有工作节点的详细信息,包括每个工作节点的地址、运行状态、CPU 和内存使用情况、正在执行的任务数量等。通过该模块,用户可以监控各个工作节点的健康状况,及时发现和处理资源瓶颈或节点故障,确保 SeaTunnel 集群的稳定运行。 +![workers.png](../../images/ui/workers.png) + +## 管理节点 + +### 管理节点信息 + +“管理节点”模块提供了 SeaTunnel 集群中主节点的状态和配置信息。用户可以查看 Master 节点的地址、运行状态、负责的作业调度情况以及整体集群的资源分配情况。该模块帮助用户全面了解集群的核心管理部分,便于进行集群配置优化和故障排查。 +![master.png](../../images/ui/master.png) diff --git a/docs/zh/start-v2/docker/docker.md b/docs/zh/start-v2/docker/docker.md index 309417a5993..1c4bc5d4b10 100644 --- a/docs/zh/start-v2/docker/docker.md +++ b/docs/zh/start-v2/docker/docker.md @@ -40,7 +40,7 @@ docker run --rm -it -v /tmp/job/:/config apache/seatunnel: ./bin/se ```shell cd seatunnel # Use already sett maven profile -mvn -B clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Dlicense.skipAddThirdParty=true -D"docker.build.skip"=false -D"docker.verify.skip"=false -D"docker.push.skip"=true -D"docker.tag"=2.3.8 -Dmaven.deploy.skip --no-snapshot-updates -Pdocker,seatunnel +mvn -B clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -Dlicense.skipAddThirdParty=true -D"docker.build.skip"=false -D"docker.verify.skip"=false -D"docker.push.skip"=true -D"docker.tag"=2.3.9 -Dmaven.deploy.skip -D"skip.spotless"=true --no-snapshot-updates -Pdocker,seatunnel # Check the docker image docker images | grep apache/seatunnel @@ -53,10 +53,10 @@ mvn clean package -DskipTests -Dskip.spotless=true # Build docker image cd seatunnel-dist -docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.8 -t apache/seatunnel:2.3.8 . +docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.9 -t apache/seatunnel:2.3.9 . # If you build from dev branch, you should add SNAPSHOT suffix to the version -docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.8-SNAPSHOT -t apache/seatunnel:2.3.8-SNAPSHOT . +docker build -f src/main/docker/Dockerfile --build-arg VERSION=2.3.9-SNAPSHOT -t apache/seatunnel:2.3.9-SNAPSHOT . # Check the docker image docker images | grep apache/seatunnel @@ -170,23 +170,26 @@ docker run -d --name seatunnel_master \ - 获取容器的ip ```shell -docker inspect master-1 +docker inspect seatunnel_master ``` 运行此命令获取master容器的ip - 启动worker节点 ```shell +# 将ST_DOCKER_MEMBER_LIST设置为master容器的ip docker run -d --name seatunnel_worker_1 \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # 设置为刚刚启动的master容器ip + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r worker -docker run -d --name seatunnel_worker_2 \ +## 启动第二个worker节点 +# 将ST_DOCKER_MEMBER_LIST设置为master容器的ip +docker run -d --name seatunnel_worker_2 \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # 设置为刚刚启动的master容器ip + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r worker @@ -195,21 +198,22 @@ docker run -d --name seatunnel_worker_2 \ #### 集群扩容 ```shell -## start master and export 5801 port +# 将ST_DOCKER_MEMBER_LIST设置为已经启动的master容器的ip docker run -d --name seatunnel_master \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # 设置为已启动的master容器ip + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r master ``` 运行这个命令创建一个worker节点 ```shell +# 将ST_DOCKER_MEMBER_LIST设置为master容器的ip docker run -d --name seatunnel_worker_1 \ --network seatunnel-network \ --rm \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # 设置为已启动的master容器ip + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ apache/seatunnel \ ./bin/seatunnel-cluster.sh -r worker ``` @@ -363,25 +367,27 @@ networks: #### 使用docker container作为客户端 - 提交任务 ```shell +# 将ST_DOCKER_MEMBER_LIST设置为master容器的ip docker run --name seatunnel_client \ --network seatunnel-network \ + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ --rm \ apache/seatunnel \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set it as master node container ip ./bin/seatunnel.sh -c config/v2.batch.config.template ``` - 查看作业列表 ```shell +# 将ST_DOCKER_MEMBER_LIST设置为master容器的ip docker run --name seatunnel_client \ --network seatunnel-network \ + -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ --rm \ apache/seatunnel \ - -e ST_DOCKER_MEMBER_LIST=172.18.0.2:5801 \ # set it as master node container ip ./bin/seatunnel.sh -l ``` 更多其他命令请参考[命令行工具](../../seatunnel-engine/user-command.md) #### 使用RestAPI -请参考 [提交作业](../../seatunnel-engine/rest-api.md#提交作业) \ No newline at end of file +请参考 [提交作业](../../seatunnel-engine/rest-api-v2.md#提交作业) \ No newline at end of file diff --git a/docs/zh/start-v2/locally/deployment.md b/docs/zh/start-v2/locally/deployment.md index ce17e773319..927f5476ece 100644 --- a/docs/zh/start-v2/locally/deployment.md +++ b/docs/zh/start-v2/locally/deployment.md @@ -22,7 +22,7 @@ import TabItem from '@theme/TabItem'; 或者您也可以通过终端下载: ```shell -export version="2.3.8" +export version="2.3.9" wget "https://archive.apache.org/dist/seatunnel/${version}/apache-seatunnel-${version}-bin.tar.gz" tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" ``` @@ -35,10 +35,10 @@ tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" sh bin/install-plugin.sh ``` -如果您需要指定的连接器版本,以2.3.7为例,您需要执行如下命令: +如果您需要指定的连接器版本,以2.3.9为例,您需要执行如下命令: ```bash -sh bin/install-plugin.sh 2.3.8 +sh bin/install-plugin.sh 2.3.9 ``` 通常情况下,你不需要所有的连接器插件。你可以通过配置`config/plugin_config`来指定所需的插件。例如,如果你想让示例应用程序正常工作,你将需要`connector-console`和`connector-fake`插件。你可以修改`plugin_config`配置文件,如下所示: @@ -71,7 +71,7 @@ connector-console cd seatunnel sh ./mvnw clean install -DskipTests -Dskip.spotless=true # 获取构建好的二进制包 -cp seatunnel-dist/target/apache-seatunnel-2.3.8-bin.tar.gz /The-Path-You-Want-To-Copy +cp seatunnel-dist/target/apache-seatunnel-2.3.9-bin.tar.gz /The-Path-You-Want-To-Copy cd /The-Path-You-Want-To-Copy tar -xzvf "apache-seatunnel-${version}-bin.tar.gz" diff --git a/docs/zh/start-v2/locally/quick-start-flink.md b/docs/zh/start-v2/locally/quick-start-flink.md index d6a5a794bdd..efd27343235 100644 --- a/docs/zh/start-v2/locally/quick-start-flink.md +++ b/docs/zh/start-v2/locally/quick-start-flink.md @@ -27,7 +27,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -40,8 +40,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -51,7 +51,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } diff --git a/docs/zh/start-v2/locally/quick-start-seatunnel-engine.md b/docs/zh/start-v2/locally/quick-start-seatunnel-engine.md index bf2515ddb6e..a24baca61d2 100644 --- a/docs/zh/start-v2/locally/quick-start-seatunnel-engine.md +++ b/docs/zh/start-v2/locally/quick-start-seatunnel-engine.md @@ -21,7 +21,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -34,8 +34,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -45,7 +45,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } diff --git a/docs/zh/start-v2/locally/quick-start-spark.md b/docs/zh/start-v2/locally/quick-start-spark.md index 59fbe47049a..8e3f8fdeba6 100644 --- a/docs/zh/start-v2/locally/quick-start-spark.md +++ b/docs/zh/start-v2/locally/quick-start-spark.md @@ -28,7 +28,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 16 schema = { fields { @@ -41,8 +41,8 @@ source { transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { age = age name = new_name @@ -52,7 +52,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } diff --git a/docs/zh/transform-v2/common-options.md b/docs/zh/transform-v2/common-options.md index 9a756760f2c..101283c9ae8 100644 --- a/docs/zh/transform-v2/common-options.md +++ b/docs/zh/transform-v2/common-options.md @@ -2,22 +2,28 @@ > 源端连接器的常见参数 -| 参数名称 | 参数类型 | 是否必须 | 默认值 | -|-------------------|--------|------|-----| -| result_table_name | string | no | - | -| source_table_name | string | no | - | +:::warn -### source_table_name [string] +旧的配置名称 `result_table_name`/`source_table_name` 已经过时,请尽快迁移到新名称 `plugin_output`/`plugin_input`。 -当未指定 `source_table_name` 时,当前插件在配置文件中处理由前一个插件输出的数据集 `(dataset)` ; +::: -当指定了 `source_table_name` 时,当前插件正在处理与该参数对应的数据集 +| 参数名称 | 参数类型 | 是否必须 | 默认值 | +|---------------|--------|------|-----| +| plugin_output | string | no | - | +| plugin_input | string | no | - | -### result_table_name [string] +### plugin_input [string] -当未指定 `result_table_name` 时,此插件处理的数据不会被注册为其他插件可以直接访问的数据集,也不会被称为临时表 `(table)`; +当未指定 `plugin_input` 时,当前插件在配置文件中处理由前一个插件输出的数据集 `(dataset)` ; -当指定了 `result_table_name` 时,此插件处理的数据将被注册为其他插件可以直接访问的数据集 `(dataset)`,或者被称为临时表 `(table)`。在这里注册的数据集可以通过指定 `source_table_name` 被其他插件直接访问。 +当指定了 `plugin_input` 时,当前插件正在处理与该参数对应的数据集 + +### plugin_output [string] + +当未指定 `plugin_output` 时,此插件处理的数据不会被注册为其他插件可以直接访问的数据集,也不会被称为临时表 `(table)`; + +当指定了 `plugin_output` 时,此插件处理的数据将被注册为其他插件可以直接访问的数据集 `(dataset)`,或者被称为临时表 `(table)`。在这里注册的数据集可以通过指定 `plugin_input` 被其他插件直接访问。 ## 示例 diff --git a/docs/zh/transform-v2/copy.md b/docs/zh/transform-v2/copy.md index a4ca5c613a7..707bc5233e7 100644 --- a/docs/zh/transform-v2/copy.md +++ b/docs/zh/transform-v2/copy.md @@ -36,8 +36,8 @@ ``` transform { Copy { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" fields { name1 = name name2 = name diff --git a/docs/zh/transform-v2/dynamic-compile.md b/docs/zh/transform-v2/dynamic-compile.md index c5af808d4e9..c9cc8708164 100644 --- a/docs/zh/transform-v2/dynamic-compile.md +++ b/docs/zh/transform-v2/dynamic-compile.md @@ -79,13 +79,13 @@ SOURCE_CODE,ABSOLUTE_PATH ```hacon transform { DynamicCompile { - source_table_name = "fake" - result_table_name = "groovy_out" + plugin_input = "fake" + plugin_output = "groovy_out" compile_language="GROOVY" compile_pattern="SOURCE_CODE" source_code=""" import org.apache.seatunnel.api.table.catalog.Column - import org.apache.seatunnel.transform.common.SeaTunnelRowAccessor + import org.apache.seatunnel.api.table.type.SeaTunnelRowAccessor import org.apache.seatunnel.api.table.catalog.CatalogTable import org.apache.seatunnel.api.table.catalog.PhysicalColumn; import org.apache.seatunnel.api.table.type.*; @@ -137,13 +137,13 @@ transform { ```hacon transform { DynamicCompile { - source_table_name = "fake" - result_table_name = "java_out" + plugin_input = "fake" + plugin_output = "java_out" compile_language="JAVA" compile_pattern="SOURCE_CODE" source_code=""" import org.apache.seatunnel.api.table.catalog.Column; - import org.apache.seatunnel.transform.common.SeaTunnelRowAccessor; + import org.apache.seatunnel.api.table.type.SeaTunnelRowAccessor; import org.apache.seatunnel.api.table.catalog.*; import org.apache.seatunnel.api.table.type.*; import java.util.ArrayList; @@ -192,8 +192,8 @@ transform { ```hacon transform { DynamicCompile { - source_table_name = "fake" - result_table_name = "groovy_out" + plugin_input = "fake" + plugin_output = "groovy_out" compile_language="GROOVY" compile_pattern="ABSOLUTE_PATH" absolute_path="""/tmp/GroovyFile""" diff --git a/docs/zh/transform-v2/embedding.md b/docs/zh/transform-v2/embedding.md index fbee0ac33e9..e05c9c24422 100644 --- a/docs/zh/transform-v2/embedding.md +++ b/docs/zh/transform-v2/embedding.md @@ -154,13 +154,13 @@ source { "Herman Melville (1819–1891) was an American novelist, short story writer, and poet of the American Renaissance period. Born in New York City, Melville gained initial fame with novels such as Typee and Omoo, but it was Moby-Dick, published in 1851, that would later be recognized as his masterpiece. Melville’s work is known for its complexity, symbolism, and exploration of themes such as man’s place in the universe, the nature of evil, and the quest for meaning. Despite facing financial difficulties and critical neglect during his lifetime, Melville’s reputation soared posthumously, and he is now considered one of the great American authors." ], kind = INSERT} ] - result_table_name = "fake" + plugin_output = "fake" } } transform { Embedding { - source_table_name = "fake" + plugin_input = "fake" embedding_model_provider = QIANFAN model = bge_large_en api_key = xxxxxxxxxx @@ -170,13 +170,13 @@ transform { book_intro_vector = book_intro author_biography_vector = author_biography } - result_table_name = "embedding_output" + plugin_output = "embedding_output" } } sink { Assert { - source_table_name = "embedding_output" + plugin_input = "embedding_output" rules = @@ -283,13 +283,13 @@ source { "Herman Melville (1819–1891) was an American novelist, short story writer, and poet of the American Renaissance period. Born in New York City, Melville gained initial fame with novels such as Typee and Omoo, but it was Moby-Dick, published in 1851, that would later be recognized as his masterpiece. Melville’s work is known for its complexity, symbolism, and exploration of themes such as man’s place in the universe, the nature of evil, and the quest for meaning. Despite facing financial difficulties and critical neglect during his lifetime, Melville’s reputation soared posthumously, and he is now considered one of the great American authors." ], kind = INSERT} ] - result_table_name = "fake" + plugin_output = "fake" } } transform { Embedding { - source_table_name = "fake" + plugin_input = "fake" model_provider = CUSTOM model = text-embedding-3-small api_key = xxxxxxxx @@ -310,13 +310,13 @@ transform { inputx = ["${input}"] } } - result_table_name = "embedding_output_1" + plugin_output = "embedding_output_1" } } sink { Assert { - source_table_name = "embedding_output_1" + plugin_input = "embedding_output_1" rules = { field_rules = [ diff --git a/docs/zh/transform-v2/field-mapper.md b/docs/zh/transform-v2/field-mapper.md index 298d3fa72c9..9c2f82dee80 100644 --- a/docs/zh/transform-v2/field-mapper.md +++ b/docs/zh/transform-v2/field-mapper.md @@ -36,8 +36,8 @@ ``` transform { FieldMapper { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" field_mapper = { id = id card = card diff --git a/docs/zh/transform-v2/filter-rowkind.md b/docs/zh/transform-v2/filter-rowkind.md index 74d2b2d5b1e..60bb6208539 100644 --- a/docs/zh/transform-v2/filter-rowkind.md +++ b/docs/zh/transform-v2/filter-rowkind.md @@ -39,7 +39,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -53,15 +53,15 @@ source { transform { FilterRowKind { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" exclude_kinds = ["INSERT"] } } sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` diff --git a/docs/zh/transform-v2/filter.md b/docs/zh/transform-v2/filter.md index 1f02c999a37..66937b00b9b 100644 --- a/docs/zh/transform-v2/filter.md +++ b/docs/zh/transform-v2/filter.md @@ -43,8 +43,8 @@ ``` transform { Filter { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" include_fields = [name, card] } } @@ -55,8 +55,8 @@ transform { ``` transform { Filter { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" exclude_fields = [age] } } diff --git a/docs/zh/transform-v2/jsonpath.md b/docs/zh/transform-v2/jsonpath.md index 4074d78bdac..a83767e0c19 100644 --- a/docs/zh/transform-v2/jsonpath.md +++ b/docs/zh/transform-v2/jsonpath.md @@ -93,8 +93,8 @@ ```json transform { JsonPath { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" columns = [ { "src_field" = "data" @@ -175,8 +175,8 @@ JsonPath 转换将 seatunnel 的值转换为一个数组。 ```hocon transform { JsonPath { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" row_error_handle_way = FAIL columns = [ diff --git a/docs/zh/transform-v2/llm.md b/docs/zh/transform-v2/llm.md index c6f7aeefead..7b505bde243 100644 --- a/docs/zh/transform-v2/llm.md +++ b/docs/zh/transform-v2/llm.md @@ -26,7 +26,9 @@ ### model_provider 要使用的模型提供者。可用选项为: -OPENAI、DOUBAO、KIMIAI、CUSTOM +OPENAI、DOUBAO、KIMIAI、MICROSOFT, CUSTOM + +> tips: 如果使用 Microsoft, 请确保 api_path 配置不能为空 ### output_data_type @@ -269,13 +271,13 @@ source { {fields = [4, "Eric"], kind = INSERT} {fields = [5, "Guangdong Liu"], kind = INSERT} ] - result_table_name = "fake" + plugin_output = "fake" } } transform { LLM { - source_table_name = "fake" + plugin_input = "fake" model_provider = CUSTOM model = gpt-4o-mini api_key = sk-xxx @@ -300,13 +302,13 @@ transform { }] } } - result_table_name = "llm_output" + plugin_output = "llm_output" } } sink { Assert { - source_table_name = "llm_output" + plugin_input = "llm_output" rules = { field_rules = [ diff --git a/docs/zh/transform-v2/metadata.md b/docs/zh/transform-v2/metadata.md new file mode 100644 index 00000000000..f0ff383f6cf --- /dev/null +++ b/docs/zh/transform-v2/metadata.md @@ -0,0 +1,85 @@ +# Metadata + +> Metadata transform plugin + +## Description +元数据转换插件,用于将元数据字段添加到数据中 + +## 支持的元数据 + +| Key | DataType | Description | +|:---------:|:--------:|:-----------------------:| +| Database | string | 包含该行的数据库名 | +| Table | string | 包含该行的数表名 | +| RowKind | string | 行类型 | +| EventTime | Long | | +| Delay | Long | 数据抽取时间与数据库变更时间的差 | +| Partition | string | 包含该行对应数表的分区字段,多个使用`,`连接 | + +### 注意事项 + `Delay` `Partition`目前只适用于cdc系列连接器,除外TiDB-CDC + +## 配置选项 + +| name | type | required | default value | Description | +|:---------------:|------|:--------:|:-------------:|-------------------| +| metadata_fields | map | 是 | - | 元数据字段与输入字段相应的映射关系 | + +### metadata_fields [map] + +元数据字段和相应的输出字段之间的映射关系 + +```hocon +metadata_fields { + database = c_database + table = c_table + rowKind = c_rowKind + ts_ms = c_ts_ms + delay = c_delay +} +``` + +## 示例 + +```yaml + +env { + parallelism = 1 + job.mode = "STREAMING" + checkpoint.interval = 5000 + read_limit.bytes_per_second = 7000000 + read_limit.rows_per_second = 400 +} + +source { + MySQL-CDC { + plugin_output = "customers_mysql_cdc" + server-id = 5652 + username = "root" + password = "zdyk_Dev@2024" + table-names = ["source.user"] + base-url = "jdbc:mysql://172.16.17.123:3306/source" + } +} + +transform { + Metadata { + metadata_fields { + Database = database + Table = table + RowKind = rowKind + EventTime = ts_ms + Delay = delay + } + plugin_output = "trans_result" + } +} + +sink { + Console { + plugin_input = "custom_name" + } +} + +``` + diff --git a/docs/zh/transform-v2/replace.md b/docs/zh/transform-v2/replace.md index 99eef89a1ab..6f8c15743bd 100644 --- a/docs/zh/transform-v2/replace.md +++ b/docs/zh/transform-v2/replace.md @@ -56,8 +56,8 @@ ``` transform { Replace { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" replace_field = "name" pattern = " " replacement = "_" @@ -84,7 +84,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -97,8 +97,8 @@ source { transform { Replace { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" replace_field = "name" pattern = ".+" replacement = "b" @@ -108,7 +108,7 @@ transform { sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` diff --git a/docs/zh/transform-v2/rowkind-extractor.md b/docs/zh/transform-v2/rowkind-extractor.md new file mode 100644 index 00000000000..cfa4d8fd6c4 --- /dev/null +++ b/docs/zh/transform-v2/rowkind-extractor.md @@ -0,0 +1,112 @@ +# RowKindExtractor + +> RowKindExtractor transform plugin + +## Description + +将CDC Row 转换为 Append only Row, 转换后的行扩展了RowKind字段
+Example:
+CDC row: -D 1, test1, test2
+transformed Row: +I 1,test1,test2,DELETE + +## Options + +| name | type | required | default value | +|-------------------|--------|----------|---------------| +| custom_field_name | string | yes | row_kind | +| transform_type | enum | yes | SHORT | + +### custom_field_name [string] + +RowKind列的自定义名 + +### transform_type [enum] + +格式化RowKind值 , 配置为 `SHORT` 或 `FULL` + +`SHORT` : +I, -U , +U, -D +`FULL` : INSERT, UPDATE_BEFORE, UPDATE_AFTER , DELETE + +## Examples + +```yaml + +env { + parallelism = 1 + job.mode = "BATCH" +} + +source { + FakeSource { + schema = { + fields { + pk_id = bigint + name = string + score = int + } + primaryKey { + name = "pk_id" + columnNames = [pk_id] + } + } + rows = [ + { + kind = INSERT + fields = [1, "A", 100] + }, + { + kind = INSERT + fields = [2, "B", 100] + }, + { + kind = INSERT + fields = [3, "C", 100] + }, + { + kind = INSERT + fields = [4, "D", 100] + }, + { + kind = UPDATE_BEFORE + fields = [1, "A", 100] + }, + { + kind = UPDATE_AFTER + fields = [1, "F", 100] + } + { + kind = UPDATE_BEFORE + fields = [2, "B", 100] + }, + { + kind = UPDATE_AFTER + fields = [2, "G", 100] + }, + { + kind = DELETE + fields = [3, "C", 100] + }, + { + kind = DELETE + fields = [4, "D", 100] + } + ] + } +} + +transform { + RowKindExtractor { + custom_field_name = "custom_name" + transform_type = FULL + plugin_output = "trans_result" + } +} + +sink { + Console { + plugin_input = "custom_name" + } +} + +``` + diff --git a/docs/zh/transform-v2/split.md b/docs/zh/transform-v2/split.md index ef8c3f58540..7fba623520a 100644 --- a/docs/zh/transform-v2/split.md +++ b/docs/zh/transform-v2/split.md @@ -46,8 +46,8 @@ ``` transform { Split { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" separator = " " split_field = "name" output_fields = [first_name, second_name] diff --git a/docs/zh/transform-v2/sql-functions.md b/docs/zh/transform-v2/sql-functions.md index 57c440a39b3..7e3f8454e1d 100644 --- a/docs/zh/transform-v2/sql-functions.md +++ b/docs/zh/transform-v2/sql-functions.md @@ -302,6 +302,15 @@ REPEAT(NAME || ' ', 10) REPLACE(NAME, ' ') + +### SPLIT + +将字符串切分成数组。 + +示例: + +select SPLIT(test,';') as arrays + ### SOUNDEX ```SOUNDEX(string)``` @@ -964,3 +973,37 @@ from 示例: case when c_string in ('c_string') then 1 else 0 end + +### UUID + +```UUID()``` + +通过java函数生成uuid + +示例: + +select UUID() as seatunnel_uuid + + +### ARRAY + +生成一个数组。 + +示例: + +select ARRAY('test1','test2','test3') as arrays + +### LATERAL VIEW +#### EXPLODE + +将 array 列展开成多行。 +OUTER EXPLODE 当 array 为NULL或者为空时,返回NULL +EXPLODE(SPLIT(FIELD_NAME,separator))用来切分字符串类型,SPLIT 第一个参数是字段名,第二个参数是分隔符 +EXPLODE(ARRAY(value1,value2)) 用于自定义数组切分,在原有基础上生成一个新的字段。 +``` +SELECT * FROM fake + LATERAL VIEW EXPLODE ( SPLIT ( NAME, ',' ) ) AS NAME + LATERAL VIEW EXPLODE ( SPLIT ( pk_id, ';' ) ) AS pk_id + LATERAL VIEW OUTER EXPLODE ( age ) AS age + LATERAL VIEW OUTER EXPLODE ( ARRAY(1,1) ) AS num +``` diff --git a/docs/zh/transform-v2/sql-udf.md b/docs/zh/transform-v2/sql-udf.md index 4c1a3777408..df03b5db5c2 100644 --- a/docs/zh/transform-v2/sql-udf.md +++ b/docs/zh/transform-v2/sql-udf.md @@ -109,8 +109,8 @@ public class ExampleUDF implements ZetaUDF { ``` transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" query = "select id, example(name) as name, age from fake" } } diff --git a/docs/zh/transform-v2/sql.md b/docs/zh/transform-v2/sql.md index 1b56f1fef3f..87d9380e7a0 100644 --- a/docs/zh/transform-v2/sql.md +++ b/docs/zh/transform-v2/sql.md @@ -12,11 +12,11 @@ SQL 转换使用内存中的 SQL 引擎,我们可以通过 SQL 函数和 SQL | 名称 | 类型 | 是否必须 | 默认值 | |-------------------|--------|------|-----| -| source_table_name | string | yes | - | -| result_table_name | string | yes | - | +| plugin_input | string | yes | - | +| plugin_output | string | yes | - | | query | string | yes | - | -### source_table_name [string] +### plugin_input [string] 源表名称,查询 SQL 表名称必须与此字段匹配。 @@ -43,8 +43,8 @@ SQL 转换使用内存中的 SQL 引擎,我们可以通过 SQL 函数和 SQL ``` transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" query = "select id, concat(name, '_') as name, age+1 as age from fake where id>0" } } @@ -66,7 +66,7 @@ transform { ```hacon source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 string.template = ["innerQuery"] schema = { @@ -123,7 +123,7 @@ env { source { FakeSource { - result_table_name = "fake" + plugin_output = "fake" row.num = 100 schema = { fields { @@ -137,15 +137,15 @@ source { transform { Sql { - source_table_name = "fake" - result_table_name = "fake1" + plugin_input = "fake" + plugin_output = "fake1" query = "select id, concat(name, '_') as name, age+1 as age from fake where id>0" } } sink { Console { - source_table_name = "fake1" + plugin_input = "fake1" } } ``` diff --git a/docs/zh/transform-v2/transform-multi-table.md b/docs/zh/transform-v2/transform-multi-table.md new file mode 100644 index 00000000000..2881f319e7a --- /dev/null +++ b/docs/zh/transform-v2/transform-multi-table.md @@ -0,0 +1,124 @@ +--- +sidebar_position: 2 +--- + +# Transform的多表转换 + +SeaTunnel transform支持多表转换,在上游插件输出多个表的时候特别有用,能够在一个transform中完成所有的转换操作。目前SeaTunnel很多Connectors支持多表输出,比如`JDBCSource`、`MySQL-CDC` +等。所有的Transform都可以通过如下配置实现多表转换。 + +:::tip + +多表Transform没有对Transform能力的限制,任何Transform的配置都可以在多表Transform中使用。多表Transform的作用针对数据流中的多个表进行单独的处理,并将多个表的Transform配置合并到一个Transform中,方便用户管理。 + +::: + +## 属性 + +| Name | Type | Required | Default | Description | +|----------------------------|--------|----------|---------|--------------------------------------------------------------------------------------------------| +| table_match_regex | String | No | .* | 表名的正则表达式,通过正则表达式来匹配需要进行转换的表,默认匹配所有的表。注意这个表名是上游的真正表名,不是result_table_name。 | +| table_transform | List | No | - | 可以通过table_transform列表来指定部分表的规则,当在table_transform中配置某个表的转换规则后,外层针对当前表的规则不会生效,以table_transform中的为准 | +| table_transform.table_path | String | No | - | 当在table_transform中配置某个表的转换规则后,需要使用table_path字段指定表名,表名需要包含`databaseName[.schemaName].tableName`。 | + +## 匹配逻辑 + +假设我们从上游读取了5张表,分别为`test.abc`,`test.abcd`,`test.xyz`,`test.xyzxyz`,`test.www`。他们的表结构一致,都有`id`、`name`、`age`三个字段。 + +| id | name | age | + +现在我们想通过Copy transform将这5张表的数据进行复制,具体需求是,`test.abc`,`test.abcd`表需要将`name`复制为`name1`, +`test.xyz`表需要复制为`name2`,`test.xyzxyz`表需要复制为`name3`,`test.www`数据结构不变。那么我们可以通过如下配置来实现: + +```hocon +transform { + Copy { + source_table_name = "fake" // 可选的读取数据集名 + result_table_name = "fake1" // 可选的输出数据集名 + + table_match_regex = "test.a.*" // 1. 通过正则表达式匹配需要进行转换的表,test.a.*表示匹配test.abc和test.abcd + src_field = "name" // 源字段 + dest_field = "name1" // 目标字段 + table_transform = [{ + table_path = "test.xyz" // 2. 指定表名进行转换 + src_field = "name" // 源字段 + dest_field = "name2" // 目标字段 + }, { + table_path = "test.xyzxyz" + src_field = "name" + dest_field = "name3" + }] + } +} +``` + +### 解释 + +1. 通过第一层的正则表达式,和对应的Copy transform options配置,我们可以匹配到`test.abc`和`test.abcd`表,将`name`字段复制为`name1`。 +2. 通过`table_transform`配置,我们可以指定`test.xyz`表,将`name`字段复制为`name2`。 + +这样我们就可以通过一个transform完成对多个表的转换操作。 + +对于每个表来说,配置的优先级是:`table_transform` > `table_match_regex`。如果所有的规则都没有匹配到,那么该表将不会进行任何转换操作。 + +针对每个表来说,他们的Transform配置是: + +- **test.abc**和**test.abcd** + +```hocon +transform { + Copy { + src_field = "name" + dest_field = "name1" + } +} +``` + +输出表结构: + +| id | name | age | name1 | + +- **test.xyz** + +```hocon +transform { + Copy { + src_field = "name" + dest_field = "name2" + } +} +``` + +输出表结构: + +| id | name | age | name2 | + +- **test.xyzxyz** + +```hocon +transform { + Copy { + src_field = "name" + dest_field = "name3" + } +} +``` + +输出表结构: + +| id | name | age | name3 | + +- **test.www** + +```hocon +transform { + // 无需转换 +} +``` + +输出表结构: + +| id | name | age | + +我们使用了Copy Transform作为了示例,实际上所有的Transform都支持多表转换,只需要在对应的Transform中配置即可。 + diff --git a/plugin-mapping.properties b/plugin-mapping.properties index 3673ade48f6..c494686161e 100644 --- a/plugin-mapping.properties +++ b/plugin-mapping.properties @@ -132,12 +132,16 @@ seatunnel.sink.ObsFile = connector-file-obs seatunnel.source.Milvus = connector-milvus seatunnel.sink.Milvus = connector-milvus seatunnel.sink.ActiveMQ = connector-activemq +seatunnel.source.Prometheus = connector-prometheus +seatunnel.sink.Prometheus = connector-prometheus seatunnel.source.Qdrant = connector-qdrant seatunnel.sink.Qdrant = connector-qdrant seatunnel.source.Sls = connector-sls +seatunnel.sink.Sls = connector-sls seatunnel.source.Typesense = connector-typesense seatunnel.sink.Typesense = connector-typesense seatunnel.source.Opengauss-CDC = connector-cdc-opengauss + seatunnel.transform.Sql = seatunnel-transforms-v2 seatunnel.transform.FieldMapper = seatunnel-transforms-v2 seatunnel.transform.Filter = seatunnel-transforms-v2 @@ -149,4 +153,5 @@ seatunnel.transform.Copy = seatunnel-transforms-v2 seatunnel.transform.DynamicCompile = seatunnel-transforms-v2 seatunnel.transform.LLM = seatunnel-transforms-v2 seatunnel.transform.Embedding = seatunnel-transforms-v2 - +seatunnel.transform.RowKindExtractor = seatunnel-transforms-v2 +seatunnel.transform.Metadata = seatunnel-transforms-v2 diff --git a/pom.xml b/pom.xml index ab8453af400..e050018b2b8 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - 2.3.8-SNAPSHOT + 2.3.9-SNAPSHOT 2.1.1 UTF-8 1.8 @@ -123,7 +123,7 @@ 5.9.0 4.11.0 1.3.3 - 3.3.0 + 3.4.1 3.2.0 4.0.4 1.3.0 @@ -135,9 +135,11 @@ 2.0.0 1.17.6 2.29.0 - 4.5 + 4.9 2.7.0 4.0.16 + 9.4.56.v20240826 + 4.0.4 false true @@ -154,6 +156,9 @@ 0.16.0 + true + + 3.1.4 @@ -866,11 +871,13 @@ linux/amd64,linux/arm64 --no-cache --push - --all-tags - ${docker.hub}/${docker.repo} + -t + ${docker.hub}/${docker.repo}:${docker.tag} + -t + ${docker.hub}/${docker.repo}:latest ${project.basedir} --build-arg - VERSION=${revision} + VERSION=${project.version} --file=src/main/docker/Dockerfile diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/common/CommonOptions.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/common/CommonOptions.java index a3227440efe..13f223698f0 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/common/CommonOptions.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/common/CommonOptions.java @@ -30,28 +30,30 @@ public interface CommonOptions { .noDefaultValue() .withDescription("Name of the SPI plugin class."); - Option RESULT_TABLE_NAME = - Options.key("result_table_name") + Option PLUGIN_OUTPUT = + Options.key("plugin_output") .stringType() .noDefaultValue() + .withFallbackKeys("result_table_name") .withDescription( - "When result_table_name is not specified, " + "When plugin_output is not specified, " + "the data processed by this plugin will not be registered as a data set (dataStream/dataset) " + "that can be directly accessed by other plugins, or called a temporary table (table)" - + "When result_table_name is specified, " + + "When plugin_output is specified, " + "the data processed by this plugin will be registered as a data set (dataStream/dataset) " + "that can be directly accessed by other plugins, or called a temporary table (table) . " + "The data set (dataStream/dataset) registered here can be directly accessed by other plugins " - + "by specifying source_table_name ."); + + "by specifying plugin_input ."); - Option> SOURCE_TABLE_NAME = - Options.key("source_table_name") + Option> PLUGIN_INPUT = + Options.key("plugin_input") .listType() .noDefaultValue() + .withFallbackKeys("source_table_name") .withDescription( - "When source_table_name is not specified, " + "When plugin_input is not specified, " + "the current plug-in processes the data set dataset output by the previous plugin in the configuration file. " - + "When source_table_name is specified, the current plug-in is processing the data set corresponding to this parameter."); + + "When plugin_input is specified, the current plug-in is processing the data set corresponding to this parameter."); Option PARALLELISM = Options.key("parallelism") diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/configuration/util/OptionRule.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/configuration/util/OptionRule.java index 684620245c1..0d700bdbc11 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/configuration/util/OptionRule.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/configuration/util/OptionRule.java @@ -165,7 +165,7 @@ public Builder conditional( @NonNull Option... requiredOptions) { verifyConditionalExists(conditionalOption); - if (expectValues.size() == 0) { + if (expectValues.isEmpty()) { throw new OptionValidationException( String.format( "conditional option '%s' must have expect values .", @@ -187,7 +187,7 @@ public Builder conditional( RequiredOption.ConditionalRequiredOptions option = RequiredOption.ConditionalRequiredOptions.of( expression, new ArrayList<>(Arrays.asList(requiredOptions))); - verifyRequiredOptionDuplicate(option); + verifyRequiredOptionDuplicate(option, true); this.requiredOptions.add(option); return this; } @@ -204,7 +204,7 @@ public Builder conditional( RequiredOption.ConditionalRequiredOptions.of( expression, new ArrayList<>(Arrays.asList(requiredOptions))); - verifyRequiredOptionDuplicate(conditionalRequiredOption); + verifyRequiredOptionDuplicate(conditionalRequiredOption, true); this.requiredOptions.add(conditionalRequiredOption); return this; } @@ -242,12 +242,30 @@ private void verifyDuplicateWithOptionOptions( } private void verifyRequiredOptionDuplicate(@NonNull RequiredOption requiredOption) { + verifyRequiredOptionDuplicate(requiredOption, false); + } + + /** + * Verifies if there are duplicate options within the required options. + * + * @param requiredOption The required option to be verified + * @param ignoreVerifyDuplicateOptions Whether to ignore duplicate option verification If + * the value is true, the existing items in OptionOptions are ignored Currently, it + * applies only to conditional + * @throws OptionValidationException If duplicate options are found + */ + private void verifyRequiredOptionDuplicate( + @NonNull RequiredOption requiredOption, + @NonNull Boolean ignoreVerifyDuplicateOptions) { requiredOption .getOptions() .forEach( option -> { - verifyDuplicateWithOptionOptions( - option, requiredOption.getClass().getSimpleName()); + if (!ignoreVerifyDuplicateOptions) { + // Check if required option that duplicate with option options + verifyDuplicateWithOptionOptions( + option, requiredOption.getClass().getSimpleName()); + } requiredOptions.forEach( ro -> { if (ro diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandler.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandler.java index 269b3181597..264a557e101 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandler.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandler.java @@ -43,6 +43,7 @@ public class DefaultSaveModeHandler implements SaveModeHandler { @Nonnull public TablePath tablePath; @Nullable public CatalogTable catalogTable; @Nullable public String customSql; + private boolean isNewTableCreated; public DefaultSaveModeHandler( SchemaSaveMode schemaSaveMode, @@ -56,7 +57,18 @@ public DefaultSaveModeHandler( catalog, catalogTable.getTableId().toTablePath(), catalogTable, - customSql); + customSql, + false); + } + + public DefaultSaveModeHandler( + SchemaSaveMode schemaSaveMode, + DataSaveMode dataSaveMode, + Catalog catalog, + TablePath tablePath, + CatalogTable catalogTable, + String customSql) { + this(schemaSaveMode, dataSaveMode, catalog, tablePath, catalogTable, customSql, false); } @Override @@ -123,7 +135,7 @@ protected void errorWhenSchemaNotExist() { } protected void keepSchemaDropData() { - if (tableExists()) { + if (tableExists() && !isNewTableCreated) { truncateTable(); } } @@ -187,6 +199,7 @@ protected void createTablePreCheck() { protected void createTable() { createTablePreCheck(); catalog.createTable(tablePath, catalogTable, true); + isNewTableCreated = true; } protected void truncateTable() { diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SeaTunnelSink.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SeaTunnelSink.java index cd869a3ca8f..954bec748ce 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SeaTunnelSink.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SeaTunnelSink.java @@ -21,6 +21,7 @@ import org.apache.seatunnel.api.common.SeaTunnelPluginLifeCycle; import org.apache.seatunnel.api.serialization.Serializer; import org.apache.seatunnel.api.source.SeaTunnelJobAware; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -135,4 +136,13 @@ default Optional> getCommitInfoSerializer() { default Optional> getAggregatedCommitInfoSerializer() { return Optional.empty(); } + + /** + * Get the catalog table of the sink. + * + * @return Optional of catalog table. + */ + default Optional getWriteCatalogTable() { + return Optional.empty(); + } } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SinkWriter.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SinkWriter.java index 4567e98cbfe..103b282a24b 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SinkWriter.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SinkWriter.java @@ -19,7 +19,7 @@ import org.apache.seatunnel.api.common.metrics.MetricsContext; import org.apache.seatunnel.api.event.EventListener; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import java.io.IOException; import java.io.Serializable; @@ -46,13 +46,20 @@ public interface SinkWriter { */ void write(T element) throws IOException; + /** @deprecated instead by {@link SupportSchemaEvolutionSinkWriter} TODO: remove this method */ + @Deprecated + default void applySchemaChange(SchemaChangeEvent event) throws IOException {} + /** - * apply schema change to third party data receiver. + * prepare the commit, will be called before {@link #snapshotState(long checkpointId)}. If you + * need to use 2pc, you can return the commit info in this method, and receive the commit info + * in {@link SinkCommitter#commit(List)}. If this method failed (by throw exception), **Only** + * Spark engine will call {@link #abortPrepare()} * - * @param event - * @throws IOException + * @return the commit info need to commit */ - default void applySchemaChange(SchemaChangeEvent event) throws IOException {} + @Deprecated + Optional prepareCommit() throws IOException; /** * prepare the commit, will be called before {@link #snapshotState(long checkpointId)}. If you @@ -60,9 +67,13 @@ default void applySchemaChange(SchemaChangeEvent event) throws IOException {} * in {@link SinkCommitter#commit(List)}. If this method failed (by throw exception), **Only** * Spark engine will call {@link #abortPrepare()} * + * @param checkpointId checkpointId * @return the commit info need to commit + * @throws IOException If fail to prepareCommit */ - Optional prepareCommit() throws IOException; + default Optional prepareCommit(long checkpointId) throws IOException { + return prepareCommit(); + } /** * @return The writer's state. diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SupportSchemaEvolutionSink.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SupportSchemaEvolutionSink.java new file mode 100644 index 00000000000..d5b33763457 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SupportSchemaEvolutionSink.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.sink; + +import org.apache.seatunnel.api.table.schema.SchemaChangeType; + +import java.util.List; + +public interface SupportSchemaEvolutionSink { + + /** + * The sink connector supports schema evolution types. + * + * @return the supported schema change types + */ + List supports(); +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SupportSchemaEvolutionSinkWriter.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SupportSchemaEvolutionSinkWriter.java new file mode 100644 index 00000000000..54727ec9505 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/SupportSchemaEvolutionSinkWriter.java @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.sink; + +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; + +import java.io.IOException; + +public interface SupportSchemaEvolutionSinkWriter { + + /** + * apply schema change to third party data receiver. + * + * @param event + * @throws IOException + */ + void applySchemaChange(SchemaChangeEvent event) throws IOException; +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSink.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSink.java index 3db7a8b7d2b..23f4fc455bb 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSink.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSink.java @@ -25,14 +25,19 @@ import org.apache.seatunnel.api.sink.SinkCommitter; import org.apache.seatunnel.api.sink.SinkCommonOptions; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.factory.MultiTableFactoryContext; +import org.apache.seatunnel.api.table.schema.SchemaChangeType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import lombok.Getter; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,10 +47,11 @@ public class MultiTableSink implements SeaTunnelSink< - SeaTunnelRow, - MultiTableState, - MultiTableCommitInfo, - MultiTableAggregatedCommitInfo> { + SeaTunnelRow, + MultiTableState, + MultiTableCommitInfo, + MultiTableAggregatedCommitInfo>, + SupportSchemaEvolutionSink { @Getter private final Map sinks; private final int replicaNum; @@ -157,7 +163,18 @@ public Optional> getCommitInfoSerializer() { } public List getSinkTables() { - return sinks.keySet().stream().map(TablePath::of).collect(Collectors.toList()); + + List tablePaths = new ArrayList<>(); + List values = new ArrayList<>(sinks.values()); + for (int i = 0; i < values.size(); i++) { + if (values.get(i).getWriteCatalogTable().isPresent()) { + tablePaths.add( + ((CatalogTable) values.get(i).getWriteCatalogTable().get()).getTablePath()); + } else { + tablePaths.add(TablePath.of(sinks.keySet().toArray(new String[0])[i])); + } + } + return tablePaths; } @Override @@ -170,4 +187,18 @@ public List getSinkTables() { public void setJobContext(JobContext jobContext) { sinks.values().forEach(sink -> sink.setJobContext(jobContext)); } + + @Override + public Optional getWriteCatalogTable() { + return SeaTunnelSink.super.getWriteCatalogTable(); + } + + @Override + public List supports() { + SeaTunnelSink firstSink = sinks.entrySet().iterator().next().getValue(); + if (firstSink instanceof SupportSchemaEvolutionSink) { + return ((SupportSchemaEvolutionSink) firstSink).supports(); + } + return Collections.emptyList(); + } } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriter.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriter.java index f01c3d65dcf..e05cf4cb8b2 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriter.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriter.java @@ -20,8 +20,8 @@ import org.apache.seatunnel.api.sink.MultiTableResourceManager; import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSinkWriter; -import org.apache.seatunnel.api.sink.event.WriterCloseEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSinkWriter; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.tracing.MDCTracer; @@ -46,7 +46,8 @@ @Slf4j public class MultiTableSinkWriter - implements SinkWriter { + implements SinkWriter, + SupportSchemaEvolutionSinkWriter { private final Map> sinkWriters; private final Map sinkWritersContext; @@ -133,7 +134,10 @@ private void initResourceManager(int queueSize) { private void subSinkErrorCheck() { for (MultiTableWriterRunnable writerRunnable : runnable) { if (writerRunnable.getThrowable() != null) { - throw new RuntimeException(writerRunnable.getThrowable()); + throw new RuntimeException( + String.format( + "table %s sink throw error", writerRunnable.getCurrentTableId()), + writerRunnable.getThrowable()); } } } @@ -153,7 +157,14 @@ public void applySchemaChange(SchemaChangeEvent event) throws IOException { sinkWriterEntry.getKey().getTableIdentifier(), sinkWriterEntry.getKey().getIndex()); synchronized (runnable.get(i)) { - sinkWriterEntry.getValue().applySchemaChange(event); + if (sinkWriterEntry.getValue() + instanceof SupportSchemaEvolutionSinkWriter) { + ((SupportSchemaEvolutionSinkWriter) sinkWriterEntry.getValue()) + .applySchemaChange(event); + } else { + // TODO remove deprecated method + sinkWriterEntry.getValue().applySchemaChange(event); + } } log.info( "Finish apply schema change for table {} sub-writer {}", @@ -220,6 +231,11 @@ public List snapshotState(long checkpointId) throws IOException @Override public Optional prepareCommit() throws IOException { + return Optional.empty(); + } + + @Override + public Optional prepareCommit(long checkpointId) throws IOException { checkQueueRemain(); subSinkErrorCheck(); MultiTableCommitInfo multiTableCommitInfo = @@ -238,7 +254,9 @@ public Optional prepareCommit() throws IOException { .entrySet()) { Optional commit; try { - commit = sinkWriterEntry.getValue().prepareCommit(); + SinkWriter sinkWriter = + sinkWriterEntry.getValue(); + commit = sinkWriter.prepareCommit(checkpointId); } catch (IOException e) { throw new RuntimeException(e); } @@ -311,10 +329,6 @@ public void close() throws IOException { (identifier, sinkWriter) -> { try { sinkWriter.close(); - sinkWritersContext - .get(identifier) - .getEventListener() - .onEvent(new WriterCloseEvent()); } catch (Throwable e) { if (firstE[0] == null) { firstE[0] = e; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableWriterRunnable.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableWriterRunnable.java index 3026dc778b8..7d1a19c8859 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableWriterRunnable.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableWriterRunnable.java @@ -32,6 +32,7 @@ public class MultiTableWriterRunnable implements Runnable { private final Map> tableIdWriterMap; private final BlockingQueue queue; private volatile Throwable throwable; + private volatile String currentTableId; public MultiTableWriterRunnable( Map> tableIdWriterMap, @@ -52,11 +53,14 @@ public void run() { if (writer == null) { if (tableIdWriterMap.size() == 1) { writer = tableIdWriterMap.values().stream().findFirst().get(); + currentTableId = tableIdWriterMap.keySet().stream().findFirst().get(); } else { throw new RuntimeException( "MultiTableWriterRunnable can't find writer for tableId: " + row.getTableId()); } + } else { + currentTableId = row.getTableId(); } synchronized (this) { writer.write(row); @@ -77,4 +81,8 @@ public void run() { public Throwable getThrowable() { return throwable; } + + public String getCurrentTableId() { + return currentTableId; + } } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/Collector.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/Collector.java index 51ace474e5f..895e4aa6db1 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/Collector.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/Collector.java @@ -17,7 +17,7 @@ package org.apache.seatunnel.api.source; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; /** * A {@link Collector} is used to collect data from {@link SourceReader}. diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/SupportSchemaEvolution.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/SupportSchemaEvolution.java new file mode 100644 index 00000000000..23a1edcc331 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/source/SupportSchemaEvolution.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.source; + +import org.apache.seatunnel.api.table.schema.SchemaChangeType; + +import java.util.List; + +public interface SupportSchemaEvolution { + + /** + * Whether the source connector supports schema evolution. + * + * @return the supported schema change types + */ + List supports(); +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogOptions.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogOptions.java index 2d1a3bc41b8..046ac1dbed2 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogOptions.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogOptions.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.api.table.catalog; +import org.apache.seatunnel.shade.com.fasterxml.jackson.core.type.TypeReference; + import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; @@ -56,4 +58,12 @@ public interface CatalogOptions { .withDescription( "The table names RegEx of the database to capture." + "The table name needs to include the database name, for example: database_.*\\.table_.*"); + + Option>> TABLE_LIST = + Options.key("table_list") + .type(new TypeReference>>() {}) + .noDefaultValue() + .withDescription( + "SeaTunnel Multi Table Schema, acts on structed data sources. " + + "such as jdbc, paimon, doris, etc"); } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogTableUtil.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogTableUtil.java index eafaedf05d2..95eaa5563b2 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogTableUtil.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/CatalogTableUtil.java @@ -157,7 +157,8 @@ public static SeaTunnelDataType convertToDataType( } } - public static MultipleRowType convertToMultipleRowType(List catalogTables) { + @Deprecated + private static MultipleRowType convertToMultipleRowType(List catalogTables) { Map rowTypeMap = new HashMap<>(); for (CatalogTable catalogTable : catalogTables) { String tableId = catalogTable.getTableId().toTablePath().toString(); @@ -215,9 +216,9 @@ public static CatalogTable buildWithConfig(String catalogName, ReadonlyConfig re schemaConfig.get( TableSchemaOptions.TableIdentifierOptions.SCHEMA_FIRST)); } else { - Optional resultTableNameOptional = - readonlyConfig.getOptional(CommonOptions.RESULT_TABLE_NAME); - tablePath = resultTableNameOptional.map(TablePath::of).orElse(TablePath.DEFAULT); + Optional pluginOutputIdentifierOptional = + readonlyConfig.getOptional(CommonOptions.PLUGIN_OUTPUT); + tablePath = pluginOutputIdentifierOptional.map(TablePath::of).orElse(TablePath.DEFAULT); } return CatalogTable.of( @@ -254,7 +255,7 @@ public static CatalogTable newCatalogTable( finalColumns.add(column); } else { finalColumns.add( - PhysicalColumn.of(fieldNames[i], fieldTypes[i], 0, false, null, null)); + PhysicalColumn.of(fieldNames[i], fieldTypes[i], 0, true, null, null)); } } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/ConstraintKey.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/ConstraintKey.java index f2d62852a07..0086cc9f641 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/ConstraintKey.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/ConstraintKey.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Data public class ConstraintKey implements Serializable { diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/PhysicalColumn.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/PhysicalColumn.java index db9da1b2b75..2a425000222 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/PhysicalColumn.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/PhysicalColumn.java @@ -215,11 +215,25 @@ public static PhysicalColumn of( String comment, String sourceType, Map options) { + return new PhysicalColumn( + name, dataType, columnLength, nullable, defaultValue, comment, sourceType, options); + } + + public static PhysicalColumn of( + String name, + SeaTunnelDataType dataType, + Long columnLength, + Integer scale, + boolean nullable, + Object defaultValue, + String comment, + String sourceType, + Map options) { return new PhysicalColumn( name, dataType, columnLength, - null, + scale, nullable, defaultValue, comment, diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/TableSchema.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/TableSchema.java index d327a0668be..2238da26171 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/TableSchema.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/TableSchema.java @@ -20,25 +20,37 @@ import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import lombok.AllArgsConstructor; +import lombok.AccessLevel; import lombok.Data; +import lombok.Getter; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** Represent a physical table schema. */ @Data -@AllArgsConstructor public final class TableSchema implements Serializable { private static final long serialVersionUID = 1L; private final List columns; + @Getter(AccessLevel.PRIVATE) + private final List columnNames; + private final PrimaryKey primaryKey; private final List constraintKeys; + public TableSchema( + List columns, PrimaryKey primaryKey, List constraintKeys) { + this.columns = columns; + this.columnNames = columns.stream().map(Column::getName).collect(Collectors.toList()); + this.primaryKey = primaryKey; + this.constraintKeys = constraintKeys; + } + public static Builder builder() { return new Builder(); } @@ -58,7 +70,23 @@ public SeaTunnelRowType toPhysicalRowDataType() { } public String[] getFieldNames() { - return columns.stream().map(Column::getName).toArray(String[]::new); + return columnNames.toArray(new String[0]); + } + + public int indexOf(String columnName) { + return columnNames.indexOf(columnName); + } + + public Column getColumn(String columnName) { + return columns.get(indexOf(columnName)); + } + + public boolean contains(String columnName) { + return columnNames.contains(columnName); + } + + public List getColumns() { + return Collections.unmodifiableList(columns); } public static final class Builder { diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParser.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParser.java index ab85455b34e..8cbea1de838 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParser.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParser.java @@ -131,14 +131,12 @@ public List parse(ReadonlyConfig schemaConfig) { new IllegalArgumentException( "schema.columns.* config need option [type], please correct your config first")); - Integer columnLength = + Long columnLength = columnConfig.get( TableSchemaOptions.ColumnOptions.COLUMN_LENGTH); - Integer columnScale = columnConfig.get( TableSchemaOptions.ColumnOptions.COLUMN_SCALE); - Boolean nullable = columnConfig.get(TableSchemaOptions.ColumnOptions.NULLABLE); Object defaultValue = @@ -149,7 +147,7 @@ public List parse(ReadonlyConfig schemaConfig) { return PhysicalColumn.of( name, seaTunnelDataType, - Long.valueOf(columnLength), + columnLength, columnScale, nullable, defaultValue, diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/TableSchemaOptions.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/TableSchemaOptions.java index 9ede187ea96..34ca23ced42 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/TableSchemaOptions.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/catalog/schema/TableSchemaOptions.java @@ -55,6 +55,14 @@ public static class TableIdentifierOptions { .noDefaultValue() .withDescription("SeaTunnel Schema"); + public static final Option>> TABLE_CONFIGS = + Options.key("tables_configs") + .type(new TypeReference>>() {}) + .noDefaultValue() + .withDescription( + "SeaTunnel Multi Table Schema, acts on unstructed data sources. " + + "such as file, assert, mongodb, etc"); + // We should use ColumnOptions instead of FieldOptions @Deprecated public static class FieldOptions { @@ -92,10 +100,10 @@ public static class ColumnOptions { .noDefaultValue() .withDescription("SeaTunnel Schema Column scale"); - public static final Option COLUMN_LENGTH = + public static final Option COLUMN_LENGTH = Options.key("columnLength") - .intType() - .defaultValue(0) + .longType() + .defaultValue(0L) .withDescription("SeaTunnel Schema Column Length"); public static final Option NULLABLE = diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/converter/BasicTypeDefine.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/converter/BasicTypeDefine.java index d15529e0a4e..e7c3c04110f 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/converter/BasicTypeDefine.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/converter/BasicTypeDefine.java @@ -31,6 +31,8 @@ public class BasicTypeDefine implements Serializable { protected String columnType; // e.g. `varchar` for MySQL protected String dataType; + // It's jdbc sql type(java.sql.Types) not SeaTunnel SqlType + protected int sqlType; protected T nativeType; // e.g. `varchar` length is 10 protected Long length; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceCheckpoint.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceCheckpoint.java new file mode 100644 index 00000000000..dfd08fd9ee7 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceCheckpoint.java @@ -0,0 +1,34 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.factory; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +@Data +@AllArgsConstructor +public class ChangeStreamTableSourceCheckpoint implements Serializable { + // The state of the enumerator, from checkpoint data + private byte[] enumeratorState; + + // The splits of the enumerator, from checkpoint data + public List> splits; +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceFactory.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceFactory.java new file mode 100644 index 00000000000..3fe40bf7d0e --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceFactory.java @@ -0,0 +1,120 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.factory; + +import org.apache.seatunnel.api.serialization.DefaultSerializer; +import org.apache.seatunnel.api.serialization.Serializer; +import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.source.SourceSplit; +import org.apache.seatunnel.api.table.connector.TableSource; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A factory to create a {@link TableSource} for a {@link SeaTunnelSource} that supports change + * stream. e.g. CDC/MQ Source The factory can be used to restore the source from the checkpoint + * state. The factory can also be used to serialize and deserialize the checkpoint state. + */ +public interface ChangeStreamTableSourceFactory extends TableSourceFactory { + + /** + * see {@link SeaTunnelSource#getSplitSerializer()}. + * + * @return + * @param + */ + default Serializer getSplitSerializer() { + return new DefaultSerializer<>(); + } + + /** + * see {@link SeaTunnelSource#getEnumeratorStateSerializer()}. + * + * @return + * @param + */ + default Serializer getEnumeratorStateSerializer() { + return new DefaultSerializer<>(); + } + + /** + * Create a {@link ChangeStreamTableSourceState} from the given {@link + * ChangeStreamTableSourceCheckpoint}. The default implementation uses the {@link + * #getSplitSerializer()} and {@link #getEnumeratorStateSerializer()} to deserialize the splits + * and enumerator state. + * + *

If the splits or enumerator state is null, the corresponding field in the returned state + * will be null. + * + * @param checkpoint + * @return + * @param + * @param + * @throws IOException + */ + default + ChangeStreamTableSourceState deserializeTableSourceState( + ChangeStreamTableSourceCheckpoint checkpoint) throws IOException { + StateT enumeratorState = null; + if (checkpoint.getEnumeratorState() != null) { + Serializer enumeratorStateSerializer = getEnumeratorStateSerializer(); + enumeratorState = + enumeratorStateSerializer.deserialize(checkpoint.getEnumeratorState()); + } + + List> deserializedSplits = new ArrayList<>(); + if (checkpoint.getSplits() != null && !checkpoint.getSplits().isEmpty()) { + Serializer splitSerializer = getSplitSerializer(); + List> splits = checkpoint.getSplits(); + for (int i = 0; i < splits.size(); i++) { + List subTaskSplits = splits.get(i); + if (subTaskSplits == null || subTaskSplits.isEmpty()) { + deserializedSplits.add(Collections.emptyList()); + } else { + List deserializedSubTaskSplits = new ArrayList<>(subTaskSplits.size()); + for (byte[] split : subTaskSplits) { + if (split != null) { + deserializedSubTaskSplits.add(splitSerializer.deserialize(split)); + } + } + deserializedSplits.add(deserializedSubTaskSplits); + } + } + } + return new ChangeStreamTableSourceState<>(enumeratorState, deserializedSplits); + } + + /** + * Restore the source from the checkpoint state. + * + * @param context + * @param state checkpoint state + * @return + * @param + * @param + * @param + */ + + TableSource restoreSource( + TableSourceFactoryContext context, + ChangeStreamTableSourceState state); +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceState.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceState.java new file mode 100644 index 00000000000..a7146c56949 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/ChangeStreamTableSourceState.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.factory; + +import org.apache.seatunnel.api.source.SourceSplit; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * The state of the enumerator and splits of the enumerator, which is used to resume the enumerator + * and reader. + * + * @param + * @param + */ +@Data +@AllArgsConstructor +public class ChangeStreamTableSourceState { + // The state of the enumerator, which is used to resume the enumerator. + private StateT enumeratorState; + + // The splits of the enumerator, which is used to resume the reader. + public List> splits; +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/DataTypeConvertorFactory.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/DataTypeConvertorFactory.java index 3bb0df7143a..a28789dd6bd 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/DataTypeConvertorFactory.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/DataTypeConvertorFactory.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.ServiceLoader; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; public class DataTypeConvertorFactory { diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/FactoryUtil.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/FactoryUtil.java index 79c0c18706f..c94b88be7cc 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/FactoryUtil.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/FactoryUtil.java @@ -44,7 +44,6 @@ import java.io.Serializable; import java.net.URL; -import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -66,12 +65,31 @@ public final class FactoryUtil { public static Tuple2, List> createAndPrepareSource( ReadonlyConfig options, ClassLoader classLoader, String factoryIdentifier) { + return restoreAndPrepareSource(options, classLoader, factoryIdentifier, null); + } + + public static + Tuple2, List> restoreAndPrepareSource( + ReadonlyConfig options, + ClassLoader classLoader, + String factoryIdentifier, + ChangeStreamTableSourceCheckpoint checkpoint) { try { final TableSourceFactory factory = discoverFactory(classLoader, TableSourceFactory.class, factoryIdentifier); - SeaTunnelSource source = - createAndPrepareSource(factory, options, classLoader); + SeaTunnelSource source; + if (factory instanceof ChangeStreamTableSourceFactory && checkpoint != null) { + ChangeStreamTableSourceFactory changeStreamTableSourceFactory = + (ChangeStreamTableSourceFactory) factory; + ChangeStreamTableSourceState state = + changeStreamTableSourceFactory.deserializeTableSourceState(checkpoint); + source = + restoreAndPrepareSource( + changeStreamTableSourceFactory, options, classLoader, state); + } else { + source = createAndPrepareSource(factory, options, classLoader); + } List catalogTables; try { catalogTables = source.getProducedCatalogTables(); @@ -79,7 +97,7 @@ Tuple2, List> createAndPrepareS // TODO remove it when all connector use `getProducedCatalogTables` SeaTunnelDataType seaTunnelDataType = source.getProducedType(); final String tableId = - options.getOptional(CommonOptions.RESULT_TABLE_NAME).orElse(DEFAULT_ID); + options.getOptional(CommonOptions.PLUGIN_OUTPUT).orElse(DEFAULT_ID); catalogTables = CatalogTableUtil.convertDataTypeToCatalogTables(seaTunnelDataType, tableId); } @@ -113,6 +131,19 @@ SeaTunnelSource createAndPrepareSource( return tableSource.createSource(); } + private static + SeaTunnelSource restoreAndPrepareSource( + ChangeStreamTableSourceFactory factory, + ReadonlyConfig options, + ClassLoader classLoader, + ChangeStreamTableSourceState state) { + TableSourceFactoryContext context = new TableSourceFactoryContext(options, classLoader); + ConfigValidator.of(context.getOptions()).validate(factory.optionRule()); + LOG.info("Restore create source from checkpoint state: {}", state); + TableSource tableSource = factory.restoreSource(context, state); + return tableSource.createSource(); + } + public static SeaTunnelSink createAndPrepareSink( CatalogTable catalogTable, @@ -307,16 +338,15 @@ public static OptionRule sinkFullOptionRule(@NonNull TableSinkFactory factory) { return sinkOptionRule; } - public static SeaTunnelTransform createAndPrepareTransform( - CatalogTable catalogTable, + public static SeaTunnelTransform createAndPrepareMultiTableTransform( + List catalogTables, ReadonlyConfig options, ClassLoader classLoader, String factoryIdentifier) { final TableTransformFactory factory = discoverFactory(classLoader, TableTransformFactory.class, factoryIdentifier); TableTransformFactoryContext context = - new TableTransformFactoryContext( - Collections.singletonList(catalogTable), options, classLoader); + new TableTransformFactoryContext(catalogTables, options, classLoader); ConfigValidator.of(context.getOptions()).validate(factory.optionRule()); return factory.createTransform(context).createTransform(); } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/TableSinkFactoryContext.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/TableSinkFactoryContext.java index b83c1087e20..a52aa66e5d2 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/TableSinkFactoryContext.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/TableSinkFactoryContext.java @@ -17,11 +17,12 @@ package org.apache.seatunnel.api.table.factory; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.sink.TablePlaceholderProcessor; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import com.google.common.annotations.VisibleForTesting; import lombok.Getter; import java.util.Collection; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/SchemaChangeType.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/SchemaChangeType.java new file mode 100644 index 00000000000..e2a08c4e3a4 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/SchemaChangeType.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.schema; + +public enum SchemaChangeType { + /** Add column to table. */ + ADD_COLUMN, + /** Drop column from table. */ + DROP_COLUMN, + /** Update column in table. */ + UPDATE_COLUMN, + /** Rename column in table. */ + RENAME_COLUMN; +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableAddColumnEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableAddColumnEvent.java similarity index 97% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableAddColumnEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableAddColumnEvent.java index 7bb2218d885..6b9332b5f7f 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableAddColumnEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableAddColumnEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.table.catalog.Column; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableChangeColumnEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableChangeColumnEvent.java similarity index 86% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableChangeColumnEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableChangeColumnEvent.java index 672f0998667..e6aa9f5f9e0 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableChangeColumnEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableChangeColumnEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.table.catalog.Column; @@ -26,7 +26,10 @@ @Getter @ToString(callSuper = true) -public class AlterTableChangeColumnEvent extends AlterTableAddColumnEvent { +public class AlterTableChangeColumnEvent extends AlterTableColumnEvent { + private final Column column; + private final boolean first; + private final String afterColumn; private final String oldColumn; public AlterTableChangeColumnEvent( @@ -35,8 +38,11 @@ public AlterTableChangeColumnEvent( Column column, boolean first, String afterColumn) { - super(tableIdentifier, column, first, afterColumn); + super(tableIdentifier); this.oldColumn = oldColumn; + this.column = column; + this.first = first; + this.afterColumn = afterColumn; } public static AlterTableChangeColumnEvent changeFirst( diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableColumnEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableColumnEvent.java similarity index 95% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableColumnEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableColumnEvent.java index 97076560f02..5f7aa001483 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableColumnEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableColumnEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.table.catalog.TableIdentifier; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableColumnsEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableColumnsEvent.java similarity index 97% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableColumnsEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableColumnsEvent.java index ce487681767..4ebae018748 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableColumnsEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableColumnsEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.table.catalog.TableIdentifier; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableDropColumnEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableDropColumnEvent.java similarity index 96% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableDropColumnEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableDropColumnEvent.java index ea4b204142a..f67c310527d 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableDropColumnEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableDropColumnEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.table.catalog.TableIdentifier; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableEvent.java similarity index 95% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableEvent.java index 475dd1ce77a..b624c6f03a7 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.table.catalog.TableIdentifier; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableModifyColumnEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableModifyColumnEvent.java similarity index 84% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableModifyColumnEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableModifyColumnEvent.java index 342d24ce73f..0cc93804dae 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableModifyColumnEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableModifyColumnEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.table.catalog.Column; @@ -26,10 +26,17 @@ @Getter @ToString(callSuper = true) -public class AlterTableModifyColumnEvent extends AlterTableAddColumnEvent { +public class AlterTableModifyColumnEvent extends AlterTableColumnEvent { + private final Column column; + private final boolean first; + private final String afterColumn; + public AlterTableModifyColumnEvent( TableIdentifier tableIdentifier, Column column, boolean first, String afterColumn) { - super(tableIdentifier, column, first, afterColumn); + super(tableIdentifier); + this.column = column; + this.first = first; + this.afterColumn = afterColumn; } public static AlterTableModifyColumnEvent modifyFirst( diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableNameEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableNameEvent.java similarity index 93% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableNameEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableNameEvent.java index 9454f6a5469..4d642630353 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/AlterTableNameEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/AlterTableNameEvent.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.table.catalog.TableIdentifier; @@ -26,7 +26,7 @@ @Getter @ToString(callSuper = true) -public class AlterTableNameEvent extends AlterTableColumnEvent { +public class AlterTableNameEvent extends AlterTableEvent { private final TableIdentifier newTableIdentifier; public AlterTableNameEvent( diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/SchemaChangeEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/SchemaChangeEvent.java similarity index 78% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/SchemaChangeEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/SchemaChangeEvent.java index b3d73db9f15..2fbc96e4034 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/SchemaChangeEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/SchemaChangeEvent.java @@ -15,9 +15,10 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; import org.apache.seatunnel.api.event.Event; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TableIdentifier; import org.apache.seatunnel.api.table.catalog.TablePath; @@ -39,4 +40,18 @@ default TablePath tablePath() { * @return */ TableIdentifier tableIdentifier(); + + /** + * Get the table struct after the change + * + * @return + */ + CatalogTable getChangeAfter(); + + /** + * Set the table struct after the change + * + * @param table + */ + void setChangeAfter(CatalogTable table); } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/TableEvent.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/TableEvent.java similarity index 90% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/TableEvent.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/TableEvent.java index af08377a9cb..d10a55c7ba0 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/TableEvent.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/event/TableEvent.java @@ -15,8 +15,9 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event; +package org.apache.seatunnel.api.table.schema.event; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TableIdentifier; import org.apache.seatunnel.api.table.catalog.TablePath; @@ -34,6 +35,7 @@ public abstract class TableEvent implements SchemaChangeEvent { @Getter @Setter private String jobId; @Getter @Setter private String statement; @Getter @Setter protected String sourceDialectName; + @Getter @Setter private CatalogTable changeAfter; @Override public TableIdentifier tableIdentifier() { diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/AlterTableEventHandler.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/AlterTableEventHandler.java similarity index 86% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/AlterTableEventHandler.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/AlterTableEventHandler.java index b0972ec68a0..a55d33f16aa 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/AlterTableEventHandler.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/AlterTableEventHandler.java @@ -15,18 +15,18 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event.handler; +package org.apache.seatunnel.api.table.schema.handler; import org.apache.seatunnel.api.table.catalog.Column; -import org.apache.seatunnel.api.table.event.AlterTableAddColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableChangeColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnsEvent; -import org.apache.seatunnel.api.table.event.AlterTableDropColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableEvent; -import org.apache.seatunnel.api.table.event.AlterTableModifyColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableNameEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableNameEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -35,6 +35,8 @@ import java.util.LinkedList; import java.util.List; +/** @deprecated instead by {@link AlterTableSchemaEventHandler} */ +@Deprecated public class AlterTableEventHandler implements DataTypeChangeEventHandler { private SeaTunnelRowType dataType; @@ -154,10 +156,19 @@ private SeaTunnelRowType applyChangeColumn( String oldColumn = changeColumnEvent.getOldColumn(); int oldColumnIndex = dataType.indexOf(oldColumn); + // The operation of rename column which only has the name of old column and the name of new + // column, + // so we need to fill the data type which is the same as the old column. + SeaTunnelDataType fieldType = dataType.getFieldType(oldColumnIndex); + Column column = changeColumnEvent.getColumn(); + if (column.getDataType() == null) { + column = column.copy(fieldType); + } + return applyModifyColumn( dataType, oldColumnIndex, - changeColumnEvent.getColumn(), + column, changeColumnEvent.isFirst(), changeColumnEvent.getAfterColumn()); } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/AlterTableSchemaEventHandler.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/AlterTableSchemaEventHandler.java new file mode 100644 index 00000000000..43f92a0a3eb --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/AlterTableSchemaEventHandler.java @@ -0,0 +1,197 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.schema.handler; + +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableNameEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class AlterTableSchemaEventHandler implements TableSchemaChangeEventHandler { + private TableSchema schema; + + @Override + public TableSchema get() { + return schema; + } + + @Override + public TableSchemaChangeEventHandler reset(TableSchema schema) { + this.schema = schema; + return this; + } + + @Override + public TableSchema apply(SchemaChangeEvent event) { + AlterTableEvent alterTableEvent = (AlterTableEvent) event; + return apply(schema, alterTableEvent); + } + + private TableSchema apply(TableSchema schema, AlterTableEvent alterTableEvent) { + if (alterTableEvent instanceof AlterTableNameEvent) { + return schema; + } + if (alterTableEvent instanceof AlterTableDropColumnEvent) { + return applyDropColumn(schema, (AlterTableDropColumnEvent) alterTableEvent); + } + if (alterTableEvent instanceof AlterTableModifyColumnEvent) { + return applyModifyColumn(schema, (AlterTableModifyColumnEvent) alterTableEvent); + } + if (alterTableEvent instanceof AlterTableChangeColumnEvent) { + return applyChangeColumn(schema, (AlterTableChangeColumnEvent) alterTableEvent); + } + if (alterTableEvent instanceof AlterTableAddColumnEvent) { + return applyAddColumn(schema, (AlterTableAddColumnEvent) alterTableEvent); + } + if (alterTableEvent instanceof AlterTableColumnsEvent) { + TableSchema newSchema = schema; + for (AlterTableColumnEvent columnEvent : + ((AlterTableColumnsEvent) alterTableEvent).getEvents()) { + newSchema = apply(newSchema, columnEvent); + } + return newSchema; + } + + throw new UnsupportedOperationException( + "Unsupported alter table event: " + alterTableEvent); + } + + private TableSchema applyAddColumn( + TableSchema schema, AlterTableAddColumnEvent addColumnEvent) { + LinkedList originFields = new LinkedList<>(Arrays.asList(schema.getFieldNames())); + Column column = addColumnEvent.getColumn(); + if (originFields.contains(column.getName())) { + return applyModifyColumn( + schema, + new AlterTableModifyColumnEvent( + addColumnEvent.tableIdentifier(), + addColumnEvent.getColumn(), + addColumnEvent.isFirst(), + addColumnEvent.getAfterColumn())); + } + + LinkedList newColumns = new LinkedList<>(schema.getColumns()); + if (addColumnEvent.isFirst()) { + newColumns.addFirst(column); + } else if (addColumnEvent.getAfterColumn() != null) { + int index = originFields.indexOf(addColumnEvent.getAfterColumn()); + newColumns.add(index + 1, column); + } else { + newColumns.addLast(column); + } + + return TableSchema.builder() + .columns(newColumns) + .primaryKey(schema.getPrimaryKey()) + .constraintKey(schema.getConstraintKeys()) + .build(); + } + + private TableSchema applyDropColumn( + TableSchema schema, AlterTableDropColumnEvent dropColumnEvent) { + List newColumns = + schema.getColumns().stream() + .filter(c -> !c.getName().equals(dropColumnEvent.getColumn())) + .collect(Collectors.toList()); + + return TableSchema.builder() + .columns(newColumns) + .primaryKey(schema.getPrimaryKey()) + .constraintKey(schema.getConstraintKeys()) + .build(); + } + + private TableSchema applyModifyColumn( + TableSchema schema, AlterTableModifyColumnEvent modifyColumnEvent) { + List fieldNames = Arrays.asList(schema.getFieldNames()); + if (!fieldNames.contains(modifyColumnEvent.getColumn().getName())) { + return schema; + } + + String modifyColumnName = modifyColumnEvent.getColumn().getName(); + int modifyColumnIndex = fieldNames.indexOf(modifyColumnName); + return applyModifyColumn( + schema, + modifyColumnIndex, + modifyColumnEvent.getColumn(), + modifyColumnEvent.isFirst(), + modifyColumnEvent.getAfterColumn()); + } + + private TableSchema applyChangeColumn( + TableSchema schema, AlterTableChangeColumnEvent changeColumnEvent) { + String oldColumn = changeColumnEvent.getOldColumn(); + int oldColumnIndex = schema.indexOf(oldColumn); + + // The operation of rename column which only has the name of old column and the name of new + // column, + // so we need to fill the data type which is the same as the old column. + Column column = changeColumnEvent.getColumn(); + if (column.getDataType() == null) { + SeaTunnelDataType fieldType = schema.getColumn(oldColumn).getDataType(); + column = column.copy(fieldType); + } + + return applyModifyColumn( + schema, + oldColumnIndex, + column, + changeColumnEvent.isFirst(), + changeColumnEvent.getAfterColumn()); + } + + private TableSchema applyModifyColumn( + TableSchema schema, int columnIndex, Column column, boolean first, String afterColumn) { + LinkedList originColumns = new LinkedList<>(schema.getColumns()); + + if (first) { + originColumns.remove(columnIndex); + originColumns.addFirst(column); + } else if (afterColumn != null) { + originColumns.remove(columnIndex); + + int index = + originColumns.stream() + .filter(c -> c.getName().equals(afterColumn)) + .findFirst() + .map(originColumns::indexOf) + .get(); + originColumns.add(index + 1, column); + } else { + originColumns.set(columnIndex, column); + } + return TableSchema.builder() + .columns(originColumns) + .primaryKey(schema.getPrimaryKey()) + .constraintKey(schema.getConstraintKeys()) + .build(); + } +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/DataTypeChangeEventDispatcher.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/DataTypeChangeEventDispatcher.java similarity index 78% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/DataTypeChangeEventDispatcher.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/DataTypeChangeEventDispatcher.java index ec4f69334f7..0fd1e7f6ab7 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/DataTypeChangeEventDispatcher.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/DataTypeChangeEventDispatcher.java @@ -15,16 +15,16 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event.handler; +package org.apache.seatunnel.api.table.schema.handler; -import org.apache.seatunnel.api.table.event.AlterTableAddColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableChangeColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnsEvent; -import org.apache.seatunnel.api.table.event.AlterTableDropColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableEvent; -import org.apache.seatunnel.api.table.event.AlterTableModifyColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableNameEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableNameEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import lombok.extern.slf4j.Slf4j; @@ -32,6 +32,8 @@ import java.util.HashMap; import java.util.Map; +/** @deprecated instead by {@link TableSchemaChangeEventDispatcher} */ +@Deprecated @Slf4j public class DataTypeChangeEventDispatcher implements DataTypeChangeEventHandler { diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/DataTypeChangeEventHandler.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/DataTypeChangeEventHandler.java similarity index 88% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/DataTypeChangeEventHandler.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/DataTypeChangeEventHandler.java index 01d8924d531..3202d0eb53e 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/DataTypeChangeEventHandler.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/DataTypeChangeEventHandler.java @@ -15,11 +15,13 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event.handler; +package org.apache.seatunnel.api.table.schema.handler; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +/** @deprecated instead by {@link TableSchemaChangeEventHandler} */ +@Deprecated public interface DataTypeChangeEventHandler extends SchemaChangeEventHandler { SeaTunnelRowType get(); diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/SchemaChangeEventHandler.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/SchemaChangeEventHandler.java similarity index 88% rename from seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/SchemaChangeEventHandler.java rename to seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/SchemaChangeEventHandler.java index 167dc6cc315..fc28134c33a 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/event/handler/SchemaChangeEventHandler.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/SchemaChangeEventHandler.java @@ -15,9 +15,9 @@ * limitations under the License. */ -package org.apache.seatunnel.api.table.event.handler; +package org.apache.seatunnel.api.table.schema.handler; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import java.io.Serializable; diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/TableSchemaChangeEventDispatcher.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/TableSchemaChangeEventDispatcher.java new file mode 100644 index 00000000000..37cef6c5c3b --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/TableSchemaChangeEventDispatcher.java @@ -0,0 +1,79 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.schema.handler; + +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableNameEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; + +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class TableSchemaChangeEventDispatcher implements TableSchemaChangeEventHandler { + + private final Map handlers; + private TableSchema schema; + + public TableSchemaChangeEventDispatcher() { + this.handlers = createHandlers(); + } + + @Override + public TableSchema get() { + return schema; + } + + @Override + public TableSchemaChangeEventHandler reset(TableSchema schema) { + this.schema = schema; + return this; + } + + @Override + public TableSchema apply(SchemaChangeEvent event) { + TableSchemaChangeEventHandler handler = handlers.get(event.getClass()); + if (handler == null) { + log.warn("Not found handler for event: {}", event.getClass()); + return schema; + } + return handler.reset(schema).apply(event); + } + + private static Map createHandlers() { + Map handlers = new HashMap<>(); + + AlterTableSchemaEventHandler alterTableEventHandler = new AlterTableSchemaEventHandler(); + handlers.put(AlterTableEvent.class, alterTableEventHandler); + handlers.put(AlterTableNameEvent.class, alterTableEventHandler); + handlers.put(AlterTableColumnsEvent.class, alterTableEventHandler); + handlers.put(AlterTableAddColumnEvent.class, alterTableEventHandler); + handlers.put(AlterTableModifyColumnEvent.class, alterTableEventHandler); + handlers.put(AlterTableDropColumnEvent.class, alterTableEventHandler); + handlers.put(AlterTableChangeColumnEvent.class, alterTableEventHandler); + return handlers; + } +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/TableSchemaChangeEventHandler.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/TableSchemaChangeEventHandler.java new file mode 100644 index 00000000000..b411217a576 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/schema/handler/TableSchemaChangeEventHandler.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.schema.handler; + +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; + +public interface TableSchemaChangeEventHandler extends SchemaChangeEventHandler { + + TableSchema get(); + + TableSchemaChangeEventHandler reset(TableSchema schema); + + default TableSchema handle(SchemaChangeEvent event) { + if (get() == null) { + throw new IllegalStateException("Handler not reset"); + } + + try { + return apply(event); + } finally { + reset(null); + if (get() != null) { + throw new IllegalStateException("Handler not reset"); + } + } + } + + TableSchema apply(SchemaChangeEvent event); +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/CommonOptions.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/CommonOptions.java new file mode 100644 index 00000000000..8b5b36682a8 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/CommonOptions.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.type; + +import org.apache.seatunnel.api.table.catalog.Column; + +import lombok.Getter; + +/** + * Common option keys of SeaTunnel {@link Column#getOptions()} / {@link SeaTunnelRow#getOptions()}. + * Used to store some extra information of the column value. + */ +@Getter +public enum CommonOptions { + /** + * The key of {@link Column#getOptions()} to specify the column value is a json format string. + */ + JSON("Json", false), + /** The key of {@link Column#getOptions()} to specify the column value is a metadata field. */ + METADATA("Metadata", false), + /** + * The key of {@link SeaTunnelRow#getOptions()} to store the partition value of the row value. + */ + PARTITION("Partition", true), + /** + * The key of {@link SeaTunnelRow#getOptions()} to store the DATABASE value of the row value. + */ + DATABASE("Database", true), + /** The key of {@link SeaTunnelRow#getOptions()} to store the TABLE value of the row value. */ + TABLE("Table", true), + /** + * The key of {@link SeaTunnelRow#getOptions()} to store the ROW_KIND value of the row value. + */ + ROW_KIND("RowKind", true), + /** + * The key of {@link SeaTunnelRow#getOptions()} to store the EVENT_TIME value of the row value. + */ + EVENT_TIME("EventTime", true), + /** The key of {@link SeaTunnelRow#getOptions()} to store the DELAY value of the row value. */ + DELAY("Delay", true); + + private final String name; + private final boolean supportMetadataTrans; + + CommonOptions(String name, boolean supportMetadataTrans) { + this.name = name; + this.supportMetadataTrans = supportMetadataTrans; + } + + public static CommonOptions fromName(String name) { + for (CommonOptions option : CommonOptions.values()) { + if (option.getName().equals(name)) { + return option; + } + } + throw new IllegalArgumentException("Unknown option name: " + name); + } +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/MetadataUtil.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/MetadataUtil.java new file mode 100644 index 00000000000..42ab2035768 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/MetadataUtil.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.type; + +import org.apache.seatunnel.api.table.catalog.TablePath; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import static org.apache.seatunnel.api.table.type.CommonOptions.DELAY; +import static org.apache.seatunnel.api.table.type.CommonOptions.EVENT_TIME; +import static org.apache.seatunnel.api.table.type.CommonOptions.PARTITION; + +public class MetadataUtil { + + public static final List METADATA_FIELDS; + + static { + METADATA_FIELDS = new ArrayList<>(); + Stream.of(CommonOptions.values()) + .filter(CommonOptions::isSupportMetadataTrans) + .map(CommonOptions::getName) + .forEach(METADATA_FIELDS::add); + } + + public static void setDelay(SeaTunnelRow row, Long delay) { + row.getOptions().put(DELAY.getName(), delay); + } + + public static void setPartition(SeaTunnelRow row, String[] partition) { + row.getOptions().put(PARTITION.getName(), partition); + } + + public static void setEventTime(SeaTunnelRow row, Long delay) { + row.getOptions().put(EVENT_TIME.getName(), delay); + } + + public static Long getDelay(SeaTunnelRowAccessor row) { + return (Long) row.getOptions().get(DELAY.getName()); + } + + public static String getDatabase(SeaTunnelRowAccessor row) { + if (row.getTableId() == null) { + return null; + } + return TablePath.of(row.getTableId()).getDatabaseName(); + } + + public static String getTable(SeaTunnelRowAccessor row) { + if (row.getTableId() == null) { + return null; + } + return TablePath.of(row.getTableId()).getTableName(); + } + + public static String getRowKind(SeaTunnelRowAccessor row) { + return row.getRowKind().shortString(); + } + + public static String getPartitionStr(SeaTunnelRowAccessor row) { + Object partition = row.getOptions().get(PARTITION.getName()); + return Objects.nonNull(partition) ? String.join(",", (String[]) partition) : null; + } + + public static String[] getPartition(SeaTunnelRowAccessor row) { + return (String[]) row.getOptions().get(PARTITION.getName()); + } + + public static Long getEventTime(SeaTunnelRowAccessor row) { + return (Long) row.getOptions().get(EVENT_TIME.getName()); + } + + public static boolean isMetadataField(String fieldName) { + return METADATA_FIELDS.contains(fieldName); + } +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java index 10a5b33a935..84e172f2dfd 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRow.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -33,6 +34,8 @@ public final class SeaTunnelRow implements Serializable { /** The array to store the actual internal format values. */ private final Object[] fields; + private Map options; + private volatile int size; public SeaTunnelRow(int arity) { @@ -55,6 +58,10 @@ public void setRowKind(RowKind rowKind) { this.rowKind = rowKind; } + public void setOptions(Map options) { + this.options = options; + } + public int getArity() { return fields.length; } @@ -67,6 +74,13 @@ public RowKind getRowKind() { return this.rowKind; } + public Map getOptions() { + if (options == null) { + options = new HashMap<>(); + } + return options; + } + public Object[] getFields() { return fields; } diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRowAccessor.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRowAccessor.java new file mode 100644 index 00000000000..6bbca49cd52 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/table/type/SeaTunnelRowAccessor.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.type; + +import lombok.AllArgsConstructor; + +import java.util.Map; + +@AllArgsConstructor +public class SeaTunnelRowAccessor { + private final SeaTunnelRow row; + + public int getArity() { + return row.getArity(); + } + + public String getTableId() { + return row.getTableId(); + } + + public RowKind getRowKind() { + return row.getRowKind(); + } + + public Object getField(int pos) { + return row.getField(pos); + } + + public Object[] getFields() { + return row.getFields(); + } + + public Map getOptions() { + return row.getOptions(); + } +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelFlatMapTransform.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelFlatMapTransform.java new file mode 100644 index 00000000000..e0e2d85b5d0 --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelFlatMapTransform.java @@ -0,0 +1,31 @@ +/* + * 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. + */ +package org.apache.seatunnel.api.transform; + +import java.util.List; + +public interface SeaTunnelFlatMapTransform extends SeaTunnelTransform { + + /** + * Transform input data to {@link this#getProducedCatalogTable().getSeaTunnelRowType()} types + * data. + * + * @param row the data need be transformed. + * @return transformed data. + */ + List flatMap(T row); +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelMapTransform.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelMapTransform.java new file mode 100644 index 00000000000..165c405bafa --- /dev/null +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelMapTransform.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.transform; + +public interface SeaTunnelMapTransform extends SeaTunnelTransform { + + /** + * Transform input data to {@link this#getProducedCatalogTable().getSeaTunnelRowType()} types + * data. + * + * @param row the data need be transformed. + * @return transformed data. + */ + T map(T row); +} diff --git a/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelTransform.java b/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelTransform.java index a64e1b7c7d5..b12d69bafbb 100644 --- a/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelTransform.java +++ b/seatunnel-api/src/main/java/org/apache/seatunnel/api/transform/SeaTunnelTransform.java @@ -20,9 +20,11 @@ import org.apache.seatunnel.api.common.PluginIdentifierInterface; import org.apache.seatunnel.api.source.SeaTunnelJobAware; import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import java.io.Serializable; +import java.util.List; public interface SeaTunnelTransform extends Serializable, PluginIdentifierInterface, SeaTunnelJobAware { @@ -44,14 +46,11 @@ default void setTypeInfo(SeaTunnelDataType inputDataType) { /** Get the catalog table output by this transform */ CatalogTable getProducedCatalogTable(); - /** - * Transform input data to {@link this#getProducedCatalogTable().getSeaTunnelRowType()} types - * data. - * - * @param row the data need be transformed. - * @return transformed data. - */ - T map(T row); + List getProducedCatalogTables(); + + default SchemaChangeEvent mapSchemaChangeEvent(SchemaChangeEvent schemaChangeEvent) { + return schemaChangeEvent; + } /** call it when Transformer completed */ default void close() {} diff --git a/seatunnel-api/src/test/java/org/apache/seatunnel/api/configuration/util/OptionRuleTest.java b/seatunnel-api/src/test/java/org/apache/seatunnel/api/configuration/util/OptionRuleTest.java index 65c86d0f88e..cde60582091 100644 --- a/seatunnel-api/src/test/java/org/apache/seatunnel/api/configuration/util/OptionRuleTest.java +++ b/seatunnel-api/src/test/java/org/apache/seatunnel/api/configuration/util/OptionRuleTest.java @@ -31,6 +31,7 @@ import static org.apache.seatunnel.api.configuration.OptionTest.TEST_MODE; import static org.apache.seatunnel.api.configuration.OptionTest.TEST_NUM; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -155,6 +156,32 @@ public void testVerify() { assertEquals( "ErrorCode:[API-02], ErrorDescription:[Option item validate failed] - ConditionalRequiredOptions 'option.timestamp' duplicate in ConditionalRequiredOptions options.", assertThrows(OptionValidationException.class, executable).getMessage()); + + // Test conditional only does not conflict with optional options + // Test option TEST_TIMESTAMP + executable = + () -> { + OptionRule.builder() + .optional(TEST_NUM, TEST_MODE, TEST_TIMESTAMP) + .exclusive(TEST_TOPIC_PATTERN, TEST_TOPIC) + .required(TEST_PORTS) + .conditional(TEST_MODE, OptionTest.TestMode.TIMESTAMP, TEST_TIMESTAMP) + .conditional(TEST_MODE, OptionTest.TestMode.LATEST, TEST_TIMESTAMP) + .build(); + }; + assertDoesNotThrow(executable); + executable = + () -> { + OptionRule.builder() + .optional(TEST_NUM, TEST_MODE) + .exclusive(TEST_TOPIC_PATTERN, TEST_TOPIC, TEST_TIMESTAMP) + .required(TEST_PORTS) + .conditional(TEST_MODE, OptionTest.TestMode.TIMESTAMP, TEST_TIMESTAMP) + .build(); + }; + assertEquals( + "ErrorCode:[API-02], ErrorDescription:[Option item validate failed] - ConditionalRequiredOptions 'option.timestamp' duplicate in ExclusiveRequiredOptions options.", + assertThrows(OptionValidationException.class, executable).getMessage()); } @Test diff --git a/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandlerTest.java b/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandlerTest.java new file mode 100644 index 00000000000..6f4785ce226 --- /dev/null +++ b/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/DefaultSaveModeHandlerTest.java @@ -0,0 +1,130 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.sink; + +import org.apache.seatunnel.api.table.catalog.Catalog; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; +import org.apache.seatunnel.api.table.catalog.InMemoryCatalog; +import org.apache.seatunnel.api.table.catalog.InMemoryCatalogFactory; +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class DefaultSaveModeHandlerTest { + + private SeaTunnelRowType rowType; + private InMemoryCatalogFactory catalogFactory; + + @BeforeEach + public void setup() { + String[] fieldNames = new String[] {"id", "name", "description", "weight"}; + SeaTunnelDataType[] dataTypes = + new SeaTunnelDataType[] { + BasicType.LONG_TYPE, + BasicType.STRING_TYPE, + BasicType.STRING_TYPE, + BasicType.STRING_TYPE + }; + rowType = new SeaTunnelRowType(fieldNames, dataTypes); + catalogFactory = new InMemoryCatalogFactory(); + } + + @Test + public void shouldTruncateExistingTable() { + // SchemaSaveMode is CREATE_SCHEMA_WHEN_NOT_EXIST and DataSaveMode is DROP_DATA and table + // exist, truncateTable needs to be executed + CatalogTable catalogTable = createCatalogTable("table1"); + Catalog catalog = catalogFactory.createCatalog("test", null); + DefaultSaveModeHandler handler = + createHandler( + SchemaSaveMode.CREATE_SCHEMA_WHEN_NOT_EXIST, + DataSaveMode.DROP_DATA, + catalog, + catalogTable); + + handler.handleSchemaSaveMode(); + handler.handleDataSaveMode(); + + InMemoryCatalog inMemoryCatalog = (InMemoryCatalog) catalog; + assertTrue(inMemoryCatalog.isRunTruncateTable(), "Should truncate data for existing table"); + } + + @Test + public void shouldNotTruncateNewlyCreatedTable() { + // SchemaSaveMode is CREATE_SCHEMA_WHEN_NOT_EXIST and DataSaveMode is DROP_DATA and table + // not exist, truncateTable no needs to be executed + CatalogTable catalogTable = createCatalogTable("notExistsTable"); + Catalog catalog = catalogFactory.createCatalog("test", null); + DefaultSaveModeHandler handler = + createHandler( + SchemaSaveMode.CREATE_SCHEMA_WHEN_NOT_EXIST, + DataSaveMode.DROP_DATA, + catalog, + catalogTable); + + handler.handleSchemaSaveMode(); + handler.handleDataSaveMode(); + + InMemoryCatalog inMemoryCatalog = (InMemoryCatalog) catalog; + assertFalse( + inMemoryCatalog.isRunTruncateTable(), + "Should not truncate data for newly created table"); + } + + @Test + public void shouldNotTruncateRecreatedTable() { + // SchemaSaveMode is RECREATE_SCHEMA and DataSaveMode is DROP_DATA , truncateTable no needs + // to be executed + CatalogTable catalogTable = createCatalogTable("notExistsTable"); + Catalog catalog = catalogFactory.createCatalog("test", null); + DefaultSaveModeHandler handler = + createHandler( + SchemaSaveMode.RECREATE_SCHEMA, + DataSaveMode.DROP_DATA, + catalog, + catalogTable); + + handler.handleSchemaSaveMode(); + handler.handleDataSaveMode(); + + InMemoryCatalog inMemoryCatalog = (InMemoryCatalog) catalog; + assertFalse( + inMemoryCatalog.isRunTruncateTable(), + "Should not truncate data for recreated table"); + } + + private CatalogTable createCatalogTable(String tableName) { + return CatalogTableUtil.getCatalogTable("", "st", "public", tableName, rowType); + } + + private DefaultSaveModeHandler createHandler( + SchemaSaveMode schemaSaveMode, + DataSaveMode dataSaveMode, + Catalog catalog, + CatalogTable catalogTable) { + return new DefaultSaveModeHandler( + schemaSaveMode, dataSaveMode, catalog, catalogTable, null); + } +} diff --git a/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriterTest.java b/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriterTest.java index 66e0ff0d4ef..86722eb2466 100644 --- a/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriterTest.java +++ b/seatunnel-api/src/test/java/org/apache/seatunnel/api/sink/multitablesink/MultiTableSinkWriterTest.java @@ -57,7 +57,7 @@ public void testPrepareCommitState() throws IOException { DefaultSerializer defaultSerializer = new DefaultSerializer<>(); for (int i = 0; i < 100; i++) { - byte[] bytes = defaultSerializer.serialize(multiTableSinkWriter.prepareCommit().get()); + byte[] bytes = defaultSerializer.serialize(multiTableSinkWriter.prepareCommit(i).get()); defaultSerializer.deserialize(bytes); } } diff --git a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/InMemoryCatalog.java b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/InMemoryCatalog.java index 8fc09a261a3..eaa5cd7f3f8 100644 --- a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/InMemoryCatalog.java +++ b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/InMemoryCatalog.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.api.table.catalog; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.table.catalog.exception.CatalogException; import org.apache.seatunnel.api.table.catalog.exception.DatabaseAlreadyExistException; @@ -29,7 +31,7 @@ import org.apache.commons.lang3.tuple.Pair; -import com.google.common.collect.Lists; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; @@ -47,6 +49,7 @@ public class InMemoryCatalog implements Catalog { private final Map> catalogTables; private static final String DEFAULT_DATABASE = "default"; private static final String UNSUPPORTED_DATABASE = "unsupported"; + @Getter private boolean isRunTruncateTable = false; InMemoryCatalog(String catalogName, ReadonlyConfig options) { this.name = catalogName; @@ -161,6 +164,12 @@ public String getDefaultDatabase() throws CatalogException { return DEFAULT_DATABASE; } + @Override + public void truncateTable(TablePath tablePath, boolean ignoreIfNotExists) + throws TableNotExistException, CatalogException { + isRunTruncateTable = true; + } + @Override public boolean databaseExists(String databaseName) throws CatalogException { return catalogTables.containsKey(databaseName); diff --git a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParserTest.java b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParserTest.java index bc10be1b9f5..7ab713e6722 100644 --- a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParserTest.java +++ b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/catalog/schema/ReadonlyConfigParserTest.java @@ -77,7 +77,7 @@ private void assertConstraintKey(TableSchema tableSchema) { constraintKey.getColumnNames().get(0).getSortType()); } - private void assertColumn(TableSchema tableSchema, boolean checkDefaultValue) { + private void assertColumn(TableSchema tableSchema, boolean comeFromColumnConfig) { List columns = tableSchema.getColumns(); Assertions.assertEquals(19, columns.size()); @@ -109,12 +109,13 @@ private void assertColumn(TableSchema tableSchema, boolean checkDefaultValue) { SeaTunnelRowType seatunnalRowType1 = (SeaTunnelRowType) seaTunnelRowType.getFieldType(17); Assertions.assertEquals(17, seatunnalRowType1.getTotalFields()); - if (checkDefaultValue) { + if (comeFromColumnConfig) { Assertions.assertEquals(0, columns.get(0).getDefaultValue()); Assertions.assertEquals("I'm default value", columns.get(4).getDefaultValue()); Assertions.assertEquals(false, columns.get(5).getDefaultValue()); Assertions.assertEquals(1.1, columns.get(10).getDefaultValue()); Assertions.assertEquals("2020-01-01", columns.get(15).getDefaultValue()); + Assertions.assertEquals(4294967295L, columns.get(4).getColumnLength()); } } } diff --git a/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/schema/event/EventTest.java b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/schema/event/EventTest.java new file mode 100644 index 00000000000..77fad92d668 --- /dev/null +++ b/seatunnel-api/src/test/java/org/apache/seatunnel/api/table/schema/event/EventTest.java @@ -0,0 +1,81 @@ +/* + * 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. + */ + +package org.apache.seatunnel.api.table.schema.event; + +import org.apache.seatunnel.api.event.EventType; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.type.BasicType; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class EventTest { + + @Test + public void testTableColumnEventInstanceOf() { + AlterTableModifyColumnEvent modifyColumnEvent = + AlterTableModifyColumnEvent.modify( + TableIdentifier.of("", TablePath.DEFAULT), + PhysicalColumn.builder() + .name("test") + .dataType(BasicType.STRING_TYPE) + .build()); + Assertions.assertEquals( + EventType.SCHEMA_CHANGE_MODIFY_COLUMN, getEventType(modifyColumnEvent)); + + AlterTableChangeColumnEvent changeColumnEvent = + AlterTableChangeColumnEvent.change( + TableIdentifier.of("", TablePath.DEFAULT), + "old", + PhysicalColumn.builder() + .name("test") + .dataType(BasicType.STRING_TYPE) + .build()); + Assertions.assertEquals( + EventType.SCHEMA_CHANGE_CHANGE_COLUMN, getEventType(changeColumnEvent)); + + AlterTableAddColumnEvent addColumnEvent = + AlterTableAddColumnEvent.add( + TableIdentifier.of("", TablePath.DEFAULT), + PhysicalColumn.builder() + .name("test") + .dataType(BasicType.STRING_TYPE) + .build()); + Assertions.assertEquals(EventType.SCHEMA_CHANGE_ADD_COLUMN, getEventType(addColumnEvent)); + + AlterTableDropColumnEvent dropColumnEvent = + new AlterTableDropColumnEvent(TableIdentifier.of("", TablePath.DEFAULT), "test"); + Assertions.assertEquals(EventType.SCHEMA_CHANGE_DROP_COLUMN, getEventType(dropColumnEvent)); + } + + private EventType getEventType(AlterTableColumnEvent event) { + if (event instanceof AlterTableAddColumnEvent) { + return EventType.SCHEMA_CHANGE_ADD_COLUMN; + } else if (event instanceof AlterTableDropColumnEvent) { + return EventType.SCHEMA_CHANGE_DROP_COLUMN; + } else if (event instanceof AlterTableModifyColumnEvent) { + return EventType.SCHEMA_CHANGE_MODIFY_COLUMN; + } else if (event instanceof AlterTableChangeColumnEvent) { + return EventType.SCHEMA_CHANGE_CHANGE_COLUMN; + } + throw new UnsupportedOperationException( + "Unsupported event type: " + event.getClass().getName()); + } +} diff --git a/seatunnel-api/src/test/resources/conf/catalog/schema_column.conf b/seatunnel-api/src/test/resources/conf/catalog/schema_column.conf index 5e6c987e27d..4578c26755f 100644 --- a/seatunnel-api/src/test/resources/conf/catalog/schema_column.conf +++ b/seatunnel-api/src/test/resources/conf/catalog/schema_column.conf @@ -47,6 +47,8 @@ schema = { type = "string" nullable = true defaultValue = "I'm default value" + // bigger than integer max value + columnLength = 4294967295 comment = "string value" }, { diff --git a/seatunnel-api/src/test/resources/conf/getCatalogTable.conf b/seatunnel-api/src/test/resources/conf/getCatalogTable.conf index 63cf0f6cf86..7a6761f67ad 100644 --- a/seatunnel-api/src/test/resources/conf/getCatalogTable.conf +++ b/seatunnel-api/src/test/resources/conf/getCatalogTable.conf @@ -21,7 +21,7 @@ env { source { InMemory { - result_table_name = "fake" + plugin_output = "fake" username = "st" password = "stpassword" table-names = ["st.public.table1", "st.public.table2"] @@ -34,7 +34,7 @@ transform { sink { InMemory { - source_table_name = "fake" + plugin_input = "fake" username = "st" password = "stpassword" address = "localhost" diff --git a/seatunnel-common/src/main/java/org/apache/seatunnel/common/Constants.java b/seatunnel-common/src/main/java/org/apache/seatunnel/common/Constants.java index 602143e100c..cadd447efc7 100644 --- a/seatunnel-common/src/main/java/org/apache/seatunnel/common/Constants.java +++ b/seatunnel-common/src/main/java/org/apache/seatunnel/common/Constants.java @@ -53,7 +53,7 @@ public final class Constants { + "\\____/ \\___| \\__,_| \\_/ \\__,_||_| |_||_| |_| \\___||_|\n" + " \n"; public static final String COPYRIGHT_LINE = - "Copyright © 2021-2022 The Apache Software Foundation. Apache SeaTunnel, SeaTunnel, and its feather logo are trademarks of The Apache Software Foundation."; + "Copyright © 2021-2024 The Apache Software Foundation. Apache SeaTunnel, SeaTunnel, and its feather logo are trademarks of The Apache Software Foundation."; private Constants() {} } diff --git a/seatunnel-common/src/main/java/org/apache/seatunnel/common/Handover.java b/seatunnel-common/src/main/java/org/apache/seatunnel/common/Handover.java index 1686514a15b..41e7bea3dbf 100644 --- a/seatunnel-common/src/main/java/org/apache/seatunnel/common/Handover.java +++ b/seatunnel-common/src/main/java/org/apache/seatunnel/common/Handover.java @@ -21,7 +21,7 @@ import java.util.Optional; import java.util.concurrent.LinkedBlockingQueue; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; public final class Handover implements Closeable { private static final int DEFAULT_QUEUE_SIZE = 10000; @@ -30,7 +30,10 @@ public final class Handover implements Closeable { new LinkedBlockingQueue<>(DEFAULT_QUEUE_SIZE); private Throwable error; - public boolean isEmpty() { + public boolean isEmpty() throws Exception { + if (error != null) { + rethrowException(error, error.getMessage()); + } return blockingQueue.isEmpty(); } diff --git a/seatunnel-common/src/main/java/org/apache/seatunnel/common/config/Common.java b/seatunnel-common/src/main/java/org/apache/seatunnel/common/config/Common.java index 95928d1e4cc..df7fe5cef64 100644 --- a/seatunnel-common/src/main/java/org/apache/seatunnel/common/config/Common.java +++ b/seatunnel-common/src/main/java/org/apache/seatunnel/common/config/Common.java @@ -17,9 +17,9 @@ package org.apache.seatunnel.common.config; -import org.apache.commons.lang3.StringUtils; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; -import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; @@ -39,6 +39,8 @@ public class Common { + private static final String FLINK_YARN_APPLICATION_PATH = "runtime.tar.gz"; + private Common() { throw new IllegalStateException("Utility class"); } @@ -113,8 +115,10 @@ public static Path appRootDir() { } catch (URISyntaxException e) { throw new RuntimeException(e); } - } else if (DeployMode.CLUSTER == MODE || DeployMode.RUN_APPLICATION == MODE) { + } else if (DeployMode.CLUSTER == MODE) { return Paths.get(""); + } else if (DeployMode.RUN_APPLICATION == MODE) { + return Paths.get(FLINK_YARN_APPLICATION_PATH); } else { throw new IllegalStateException("deploy mode not support : " + MODE); } diff --git a/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/FileUtils.java b/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/FileUtils.java index bfdda942755..279c4bf4cad 100644 --- a/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/FileUtils.java +++ b/seatunnel-common/src/main/java/org/apache/seatunnel/common/utils/FileUtils.java @@ -37,6 +37,7 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -194,4 +195,31 @@ private static void deleteFiles(@NonNull File file) { throw CommonError.fileOperationFailed("SeaTunnel", "delete", file.toString(), e); } } + + public static List listFile(String dirPath) { + try { + File file = new File(dirPath); + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files == null) { + return null; + } + return Arrays.stream(files) + .map( + currFile -> { + if (currFile.isDirectory()) { + return null; + } else { + return Arrays.asList(currFile); + } + }) + .filter(Objects::nonNull) + .flatMap(List::stream) + .collect(Collectors.toList()); + } + return Arrays.asList(file); + } catch (Exception e) { + throw CommonError.fileOperationFailed("SeaTunnel", "list", dirPath, e); + } + } } diff --git a/seatunnel-common/src/test/java/org/apache/seatunnel/common/HandoverTest.java b/seatunnel-common/src/test/java/org/apache/seatunnel/common/HandoverTest.java new file mode 100644 index 00000000000..199a2d4e723 --- /dev/null +++ b/seatunnel-common/src/test/java/org/apache/seatunnel/common/HandoverTest.java @@ -0,0 +1,31 @@ +/* + * 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. + */ + +package org.apache.seatunnel.common; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class HandoverTest { + + @Test + public void testThrowExceptionWhenQueueIsEmtpy() { + Handover handover = new Handover<>(); + handover.reportError(new RuntimeException("test")); + Assertions.assertThrows(RuntimeException.class, handover::isEmpty); + } +} diff --git a/seatunnel-config/seatunnel-config-base/pom.xml b/seatunnel-config/seatunnel-config-base/pom.xml index 677b1d57ee1..5610cab85e5 100644 --- a/seatunnel-config/seatunnel-config-base/pom.xml +++ b/seatunnel-config/seatunnel-config-base/pom.xml @@ -53,7 +53,7 @@ maven-shade-plugin true - true + ${enableSourceJarCreation} true false false @@ -69,11 +69,29 @@ com/typesafe/config/ConfigParseOptions.class com/typesafe/config/ConfigMergeable.class com/typesafe/config/impl/ConfigParser.class + com/typesafe/config/impl/ConfigParser$1.class + com/typesafe/config/impl/ConfigParser$ParseContext.class com/typesafe/config/impl/ConfigNodePath.class com/typesafe/config/impl/PathParser.class + com/typesafe/config/impl/PathParser$Element.class com/typesafe/config/impl/Path.class com/typesafe/config/impl/SimpleConfigObject.class + com/typesafe/config/impl/SimpleConfigObject$1.class + com/typesafe/config/impl/SimpleConfigObject$RenderComparator.class + com/typesafe/config/impl/SimpleConfigObject$ResolveModifier.class com/typesafe/config/impl/PropertiesParser.class + com/typesafe/config/impl/PropertiesParser$1.class + com/typesafe/config/impl/ConfigImpl.class + com/typesafe/config/impl/ConfigImpl$1.class + com/typesafe/config/impl/ConfigImpl$ClasspathNameSource.class + com/typesafe/config/impl/ConfigImpl$ClasspathNameSourceWithClass.class + com/typesafe/config/impl/ConfigImpl$DebugHolder.class + com/typesafe/config/impl/ConfigImpl$DefaultIncluderHolder.class + com/typesafe/config/impl/ConfigImpl$EnvVariablesHolder.class + com/typesafe/config/impl/ConfigImpl$FileNameSource.class + com/typesafe/config/impl/ConfigImpl$LoaderCache.class + com/typesafe/config/impl/ConfigImpl$LoaderCacheHolder.class + com/typesafe/config/impl/ConfigImpl$SystemPropertiesHolder.class diff --git a/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/ConfigImpl.java b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/ConfigImpl.java new file mode 100644 index 00000000000..f078897ed2d --- /dev/null +++ b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/ConfigImpl.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2011-2012 Typesafe Inc. + */ + +package org.apache.seatunnel.shade.com.typesafe.config.impl; + +import org.apache.seatunnel.shade.com.typesafe.config.Config; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigException; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigIncluder; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigMemorySize; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigObject; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigOrigin; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigParseOptions; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigParseable; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigValue; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Callable; + +/** + * Internal implementation detail, not ABI stable, do not touch. For use only by the {@link + * com.typesafe.config} package. + */ +public class ConfigImpl { + + private static class LoaderCache { + private Config currentSystemProperties; + private WeakReference currentLoader; + private Map cache; + + LoaderCache() { + this.currentSystemProperties = null; + this.currentLoader = new WeakReference(null); + this.cache = new LinkedHashMap(); + } + + // for now, caching as long as the loader remains the same, + // drop entire cache if it changes. + synchronized Config getOrElseUpdate( + ClassLoader loader, String key, Callable updater) { + if (loader != currentLoader.get()) { + // reset the cache if we start using a different loader + cache.clear(); + currentLoader = new WeakReference(loader); + } + + Config systemProperties = systemPropertiesAsConfig(); + if (systemProperties != currentSystemProperties) { + cache.clear(); + currentSystemProperties = systemProperties; + } + + Config config = cache.get(key); + if (config == null) { + try { + config = updater.call(); + } catch (RuntimeException e) { + throw e; // this will include ConfigException + } catch (Exception e) { + throw new ConfigException.Generic(e.getMessage(), e); + } + if (config == null) + throw new ConfigException.BugOrBroken("null config from cache updater"); + cache.put(key, config); + } + + return config; + } + } + + private static class LoaderCacheHolder { + static final LoaderCache cache = new LoaderCache(); + } + + public static Config computeCachedConfig( + ClassLoader loader, String key, Callable updater) { + LoaderCache cache; + try { + cache = LoaderCacheHolder.cache; + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + return cache.getOrElseUpdate(loader, key, updater); + } + + static class FileNameSource implements SimpleIncluder.NameSource { + @Override + public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) { + return Parseable.newFile(new File(name), parseOptions); + } + }; + + static class ClasspathNameSource implements SimpleIncluder.NameSource { + @Override + public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) { + return Parseable.newResources(name, parseOptions); + } + }; + + static class ClasspathNameSourceWithClass implements SimpleIncluder.NameSource { + private final Class klass; + + public ClasspathNameSourceWithClass(Class klass) { + this.klass = klass; + } + + @Override + public ConfigParseable nameToParseable(String name, ConfigParseOptions parseOptions) { + return Parseable.newResources(klass, name, parseOptions); + } + }; + + public static ConfigObject parseResourcesAnySyntax( + Class klass, String resourceBasename, ConfigParseOptions baseOptions) { + SimpleIncluder.NameSource source = new ClasspathNameSourceWithClass(klass); + return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); + } + + public static ConfigObject parseResourcesAnySyntax( + String resourceBasename, ConfigParseOptions baseOptions) { + SimpleIncluder.NameSource source = new ClasspathNameSource(); + return SimpleIncluder.fromBasename(source, resourceBasename, baseOptions); + } + + public static ConfigObject parseFileAnySyntax(File basename, ConfigParseOptions baseOptions) { + SimpleIncluder.NameSource source = new FileNameSource(); + return SimpleIncluder.fromBasename(source, basename.getPath(), baseOptions); + } + + static AbstractConfigObject emptyObject(String originDescription) { + ConfigOrigin origin = + originDescription != null ? SimpleConfigOrigin.newSimple(originDescription) : null; + return emptyObject(origin); + } + + public static Config emptyConfig(String originDescription) { + return emptyObject(originDescription).toConfig(); + } + + static AbstractConfigObject empty(ConfigOrigin origin) { + return emptyObject(origin); + } + + // default origin for values created with fromAnyRef and no origin specified + private static final ConfigOrigin defaultValueOrigin = + SimpleConfigOrigin.newSimple("hardcoded value"); + private static final ConfigBoolean defaultTrueValue = + new ConfigBoolean(defaultValueOrigin, true); + private static final ConfigBoolean defaultFalseValue = + new ConfigBoolean(defaultValueOrigin, false); + private static final ConfigNull defaultNullValue = new ConfigNull(defaultValueOrigin); + private static final SimpleConfigList defaultEmptyList = + new SimpleConfigList(defaultValueOrigin, Collections.emptyList()); + private static final SimpleConfigObject defaultEmptyObject = + SimpleConfigObject.empty(defaultValueOrigin); + + private static SimpleConfigList emptyList(ConfigOrigin origin) { + if (origin == null || origin == defaultValueOrigin) return defaultEmptyList; + else return new SimpleConfigList(origin, Collections.emptyList()); + } + + private static AbstractConfigObject emptyObject(ConfigOrigin origin) { + // we want null origin to go to SimpleConfigObject.empty() to get the + // origin "empty config" rather than "hardcoded value" + if (origin == defaultValueOrigin) return defaultEmptyObject; + else return SimpleConfigObject.empty(origin); + } + + private static ConfigOrigin valueOrigin(String originDescription) { + if (originDescription == null) return defaultValueOrigin; + else return SimpleConfigOrigin.newSimple(originDescription); + } + + public static ConfigValue fromAnyRef(Object object, String originDescription) { + ConfigOrigin origin = valueOrigin(originDescription); + return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS); + } + + public static ConfigObject fromPathMap( + Map pathMap, String originDescription) { + ConfigOrigin origin = valueOrigin(originDescription); + return (ConfigObject) fromAnyRef(pathMap, origin, FromMapMode.KEYS_ARE_PATHS); + } + + static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin, FromMapMode mapMode) { + if (origin == null) throw new ConfigException.BugOrBroken("origin not supposed to be null"); + + if (object == null) { + if (origin != defaultValueOrigin) return new ConfigNull(origin); + else return defaultNullValue; + } else if (object instanceof AbstractConfigValue) { + return (AbstractConfigValue) object; + } else if (object instanceof Boolean) { + if (origin != defaultValueOrigin) { + return new ConfigBoolean(origin, (Boolean) object); + } else if ((Boolean) object) { + return defaultTrueValue; + } else { + return defaultFalseValue; + } + } else if (object instanceof String) { + return new ConfigString.Quoted(origin, (String) object); + } else if (object instanceof Number) { + // here we always keep the same type that was passed to us, + // rather than figuring out if a Long would fit in an Int + // or a Double has no fractional part. i.e. deliberately + // not using ConfigNumber.newNumber() when we have a + // Double, Integer, or Long. + if (object instanceof Double) { + return new ConfigDouble(origin, (Double) object, null); + } else if (object instanceof Integer) { + return new ConfigInt(origin, (Integer) object, null); + } else if (object instanceof Long) { + return new ConfigLong(origin, (Long) object, null); + } else { + return ConfigNumber.newNumber(origin, ((Number) object).doubleValue(), null); + } + } else if (object instanceof Duration) { + return new ConfigLong(origin, ((Duration) object).toMillis(), null); + } else if (object instanceof Map) { + if (((Map) object).isEmpty()) return emptyObject(origin); + + if (mapMode == FromMapMode.KEYS_ARE_KEYS) { + Map values = + new LinkedHashMap(); + for (Map.Entry entry : ((Map) object).entrySet()) { + Object key = entry.getKey(); + if (!(key instanceof String)) + throw new ConfigException.BugOrBroken( + "bug in method caller: not valid to create ConfigObject from map with non-String key: " + + key); + AbstractConfigValue value = fromAnyRef(entry.getValue(), origin, mapMode); + values.put((String) key, value); + } + + return new SimpleConfigObject(origin, values); + } else { + return PropertiesParser.fromPathMap(origin, (Map) object); + } + } else if (object instanceof Iterable) { + Iterator i = ((Iterable) object).iterator(); + if (!i.hasNext()) return emptyList(origin); + + List values = new ArrayList(); + while (i.hasNext()) { + AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode); + values.add(v); + } + + return new SimpleConfigList(origin, values); + } else if (object instanceof ConfigMemorySize) { + return new ConfigLong(origin, ((ConfigMemorySize) object).toBytes(), null); + } else { + throw new ConfigException.BugOrBroken( + "bug in method caller: not valid to create ConfigValue from: " + object); + } + } + + private static class DefaultIncluderHolder { + static final ConfigIncluder defaultIncluder = new SimpleIncluder(null); + } + + static ConfigIncluder defaultIncluder() { + try { + return DefaultIncluderHolder.defaultIncluder; + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + private static Properties getSystemProperties() { + // Avoid ConcurrentModificationException due to parallel setting of system properties by + // copying properties + final Properties systemProperties = System.getProperties(); + final Properties systemPropertiesCopy = new Properties(); + synchronized (systemProperties) { + systemPropertiesCopy.putAll(systemProperties); + } + return systemPropertiesCopy; + } + + private static AbstractConfigObject loadSystemProperties() { + return (AbstractConfigObject) + Parseable.newProperties( + getSystemProperties(), + ConfigParseOptions.defaults() + .setOriginDescription("system properties")) + .parse(); + } + + private static class SystemPropertiesHolder { + // this isn't final due to the reloadSystemPropertiesConfig() hack below + static volatile AbstractConfigObject systemProperties = loadSystemProperties(); + } + + static AbstractConfigObject systemPropertiesAsConfigObject() { + try { + return SystemPropertiesHolder.systemProperties; + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static Config systemPropertiesAsConfig() { + return systemPropertiesAsConfigObject().toConfig(); + } + + public static void reloadSystemPropertiesConfig() { + // ConfigFactory.invalidateCaches() relies on this having the side + // effect that it drops all caches + SystemPropertiesHolder.systemProperties = loadSystemProperties(); + } + + private static AbstractConfigObject loadEnvVariables() { + return PropertiesParser.fromStringMap(newSimpleOrigin("env variables"), System.getenv()); + } + + private static class EnvVariablesHolder { + static volatile AbstractConfigObject envVariables = loadEnvVariables(); + } + + static AbstractConfigObject envVariablesAsConfigObject() { + try { + return EnvVariablesHolder.envVariables; + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static Config envVariablesAsConfig() { + return envVariablesAsConfigObject().toConfig(); + } + + public static void reloadEnvVariablesConfig() { + // ConfigFactory.invalidateCaches() relies on this having the side + // effect that it drops all caches + EnvVariablesHolder.envVariables = loadEnvVariables(); + } + + public static Config defaultReference(final ClassLoader loader) { + return computeCachedConfig( + loader, + "defaultReference", + new Callable() { + @Override + public Config call() { + Config unresolvedResources = + Parseable.newResources( + "reference.conf", + ConfigParseOptions.defaults() + .setClassLoader(loader)) + .parse() + .toConfig(); + return systemPropertiesAsConfig() + .withFallback(unresolvedResources) + .resolve(); + } + }); + } + + private static class DebugHolder { + private static String LOADS = "loads"; + private static String SUBSTITUTIONS = "substitutions"; + + private static Map loadDiagnostics() { + Map result = new LinkedHashMap(); + result.put(LOADS, false); + result.put(SUBSTITUTIONS, false); + + // People do -Dconfig.trace=foo,bar to enable tracing of different things + String s = System.getProperty("config.trace"); + if (s == null) { + return result; + } else { + String[] keys = s.split(","); + for (String k : keys) { + if (k.equals(LOADS)) { + result.put(LOADS, true); + } else if (k.equals(SUBSTITUTIONS)) { + result.put(SUBSTITUTIONS, true); + } else { + System.err.println( + "config.trace property contains unknown trace topic '" + k + "'"); + } + } + return result; + } + } + + private static final Map diagnostics = loadDiagnostics(); + + private static final boolean traceLoadsEnabled = diagnostics.get(LOADS); + private static final boolean traceSubstitutionsEnabled = diagnostics.get(SUBSTITUTIONS); + + static boolean traceLoadsEnabled() { + return traceLoadsEnabled; + } + + static boolean traceSubstitutionsEnabled() { + return traceSubstitutionsEnabled; + } + } + + public static boolean traceLoadsEnabled() { + try { + return DebugHolder.traceLoadsEnabled(); + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static boolean traceSubstitutionsEnabled() { + try { + return DebugHolder.traceSubstitutionsEnabled(); + } catch (ExceptionInInitializerError e) { + throw ConfigImplUtil.extractInitializerError(e); + } + } + + public static void trace(String message) { + System.err.println(message); + } + + public static void trace(int indentLevel, String message) { + while (indentLevel > 0) { + System.err.print(" "); + indentLevel -= 1; + } + System.err.println(message); + } + + // the basic idea here is to add the "what" and have a canonical + // toplevel error message. the "original" exception may however have extra + // detail about what happened. call this if you have a better "what" than + // further down on the stack. + static ConfigException.NotResolved improveNotResolved( + Path what, ConfigException.NotResolved original) { + String newMessage = + what.render() + + " has not been resolved, you need to call Config#resolve()," + + " see API docs for Config#resolve()"; + if (newMessage.equals(original.getMessage())) return original; + else return new ConfigException.NotResolved(newMessage, original); + } + + public static ConfigOrigin newSimpleOrigin(String description) { + if (description == null) { + return defaultValueOrigin; + } else { + return SimpleConfigOrigin.newSimple(description); + } + } + + public static ConfigOrigin newFileOrigin(String filename) { + return SimpleConfigOrigin.newFile(filename); + } + + public static ConfigOrigin newURLOrigin(URL url) { + return SimpleConfigOrigin.newURL(url); + } +} diff --git a/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java index 735df6829c9..b10148977b7 100644 --- a/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java +++ b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -277,7 +278,7 @@ protected SimpleConfigObject mergedWithObject(AbstractConfigObject abstractFallb boolean changed = false; boolean allResolved = true; Map merged = new LinkedHashMap<>(); - Set allKeys = new HashSet<>(); + Set allKeys = new LinkedHashSet<>(); allKeys.addAll(this.keySet()); allKeys.addAll(fallback.keySet()); @@ -386,8 +387,7 @@ ResolveResult resolveSubstitutions( ResolveSource sourceWithParent = source.pushParent(this); try { - SimpleConfigObject.ResolveModifier modifier = - new SimpleConfigObject.ResolveModifier(context, sourceWithParent); + ResolveModifier modifier = new ResolveModifier(context, sourceWithParent); AbstractConfigValue value = this.modifyMayThrow(modifier); return ResolveResult.make(modifier.context, value).asObjectResult(); } catch (NotPossibleToResolve | RuntimeException var6) { @@ -562,7 +562,7 @@ public boolean containsValue(Object v) { } public Set> entrySet() { - HashSet> entries = new HashSet<>(); + HashSet> entries = new LinkedHashSet<>(); for (Entry stringAbstractConfigValueEntry : this.value.entrySet()) { @@ -584,7 +584,7 @@ public int size() { } public Collection values() { - return new HashSet<>(this.value.values()); + return new ArrayList<>(this.value.values()); } static SimpleConfigObject empty() { diff --git a/seatunnel-config/seatunnel-config-shade/src/test/java/org/apache/seatunnel/config/ConfigTest.java b/seatunnel-config/seatunnel-config-shade/src/test/java/org/apache/seatunnel/config/ConfigTest.java new file mode 100644 index 00000000000..6d8eb73ffae --- /dev/null +++ b/seatunnel-config/seatunnel-config-shade/src/test/java/org/apache/seatunnel/config/ConfigTest.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.apache.seatunnel.config; + +import org.apache.seatunnel.shade.com.typesafe.config.Config; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigRenderOptions; + +import org.apache.seatunnel.config.utils.FileUtils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.net.URISyntaxException; + +public class ConfigTest { + + @Test + public void testConfigKeyOrder() throws URISyntaxException { + String expected = + "{\"env\":{\"job.mode\":\"BATCH\"},\"source\":[{\"row.num\":100,\"schema\":{\"fields\":{\"name\":\"string\",\"age\":\"int\"}},\"plugin_name\":\"FakeSource\"}],\"sink\":[{\"plugin_name\":\"Console\"}]}"; + + Config config = + ConfigFactory.parseFile( + FileUtils.getFileFromResources("/seatunnel/serialize.conf")); + Assertions.assertEquals(expected, config.root().render(ConfigRenderOptions.concise())); + } +} diff --git a/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.conf b/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.conf index 27ad42b4139..00c09c78e01 100644 --- a/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.conf +++ b/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.conf @@ -31,12 +31,12 @@ env { source { # This is a example input plugin **only for test and demonstrate the feature input plugin** Fake { - result_table_name = "my_dataset" + plugin_output = "my_dataset" } # You can also use other input plugins, such as hdfs # hdfs { - # result_table_name = "accesslog" + # plugin_output = "accesslog" # path = "hdfs://hadoop-cluster-01/nginx/accesslog" # format = "json" # } diff --git a/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.json b/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.json index f0f68aee576..90a2ed63128 100644 --- a/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.json +++ b/seatunnel-config/seatunnel-config-shade/src/test/resources/json/spark.batch.json @@ -13,7 +13,7 @@ "source" : [ { "plugin_name" : "Fake", - "result_table_name" : "my_dataset" + "plugin_output" : "my_dataset" } ], "transform" : [] diff --git a/seatunnel-config/seatunnel-config-shade/src/test/resources/seatunnel/variables.conf b/seatunnel-config/seatunnel-config-shade/src/test/resources/seatunnel/variables.conf index f06fb1c1619..24995129a42 100644 --- a/seatunnel-config/seatunnel-config-shade/src/test/resources/seatunnel/variables.conf +++ b/seatunnel-config/seatunnel-config-shade/src/test/resources/seatunnel/variables.conf @@ -42,19 +42,19 @@ transform { sql { table_name = "user_view" sql = "select * from user_view where city = '"${city2}"'" - result_table_name = "result1" + plugin_output = "result1" } sql { table_name = "user_view" sql = "select * from user_view where dt = '"${dt}"'" - result_table_name = "result2" + plugin_output = "result2" } } sink { stdout { - source_table_name="result1" + plugin_input="result1" } stdout { diff --git a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/ConfigTemplate.java b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/ConfigTemplate.java index a9e951c62fc..0b7b94b730e 100644 --- a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/ConfigTemplate.java +++ b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/ConfigTemplate.java @@ -77,16 +77,16 @@ private static String transformItems(List transformConfigs) { for (TransformConfig transformConfig : transformConfigs) { transformItems.append(" sql {\n"); transformItems - .append(" source_table_name = \"") - .append(transformConfig.getSourceTableName()) + .append(" plugin_input = \"") + .append(transformConfig.getPluginInputIdentifier()) .append("\"\n"); transformItems .append(" query = \"\"\"") .append(transformConfig.getQuery()) .append("\"\"\"\n"); transformItems - .append(" result_table_name = \"") - .append(transformConfig.getResultTableName()) + .append(" plugin_output = \"") + .append(transformConfig.getPluginOutputIdentifier()) .append("\"\n"); transformItems.append(" }\n"); } diff --git a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/SqlConfigBuilder.java b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/SqlConfigBuilder.java index f0d68e089b5..2e5fc7777a4 100644 --- a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/SqlConfigBuilder.java +++ b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/SqlConfigBuilder.java @@ -43,7 +43,6 @@ import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; import net.sf.jsqlparser.statement.select.SelectItem; import java.nio.file.Files; @@ -61,9 +60,9 @@ import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_DELIMITER; import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_DOUBLE_SINGLE_QUOTES; import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_KV_DELIMITER; -import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_RESULT_TABLE_NAME_KEY; +import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_PLUGIN_INPUT_KEY; +import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_PLUGIN_OUTPUT_KEY; import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_SINGLE_QUOTES; -import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_SOURCE_TABLE_NAME_KEY; import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_TABLE_CONNECTOR_KEY; import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_TABLE_TYPE_KEY; import static org.apache.seatunnel.config.sql.utils.Constant.OPTION_TABLE_TYPE_SINK; @@ -118,15 +117,14 @@ public static Config of(@NonNull Path sqlFilePath) { } } - // filter out the sink config without 'source_table_name' option + // filter out the sink config without 'plugin_input' option seaTunnelConfig.setSinkConfigs( seaTunnelConfig.getSinkConfigs().stream() .filter( sinkConfig -> { boolean containSourceTable = false; for (Option option : sinkConfig.getOptions()) { - if (option.getKey() - .equals(OPTION_SOURCE_TABLE_NAME_KEY)) { + if (option.getKey().equals(OPTION_PLUGIN_INPUT_KEY)) { containSourceTable = true; break; } @@ -265,12 +263,12 @@ private static SourceConfig parseSourceSql( SourceConfig sourceConfig = new SourceConfig(); sourceConfig.setConnector(connector); - String resultTableName = createTable.getTable().getName(); - sourceConfig.setResultTableName(resultTableName); + String pluginOutputIdentifier = createTable.getTable().getName(); + sourceConfig.setPluginOutputIdentifier(pluginOutputIdentifier); convertOptions(options, sourceConfig.getOptions()); sourceConfig .getOptions() - .add(Option.of(OPTION_RESULT_TABLE_NAME_KEY, "\"" + resultTableName + "\"")); + .add(Option.of(OPTION_PLUGIN_OUTPUT_KEY, "\"" + pluginOutputIdentifier + "\"")); return sourceConfig; } @@ -281,8 +279,8 @@ private static SinkConfig parseSinkSql(Map options) { } SinkConfig sinkConfig = new SinkConfig(); sinkConfig.setConnector(connector); - // original sink table without source_table_name - options.remove(OPTION_SOURCE_TABLE_NAME_KEY); + // original sink table without plugin_input + options.remove(OPTION_PLUGIN_INPUT_KEY); convertOptions(options, sinkConfig.getOptions()); return sinkConfig; @@ -293,7 +291,7 @@ private static void convertOptions(Map options, Collection { if (OPTION_TABLE_CONNECTOR_KEY.equalsIgnoreCase(k) || OPTION_TABLE_TYPE_KEY.equalsIgnoreCase(k) - || OPTION_RESULT_TABLE_NAME_KEY.equalsIgnoreCase(k)) { + || OPTION_PLUGIN_OUTPUT_KEY.equalsIgnoreCase(k)) { return; } String trimVal = v.trim(); @@ -314,22 +312,22 @@ private static TransformConfig parseCreateAsSql( TransformConfig transformConfig = new TransformConfig(); PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); Table table = (Table) plainSelect.getFromItem(); - String sourceTableName = table.getName(); - if (!sqlTables.containsKey(sourceTableName)) { + String pluginInputIdentifier = table.getName(); + if (!sqlTables.containsKey(pluginInputIdentifier)) { throw new ParserException( - String.format("The source table[%s] is not found", sourceTableName)); + String.format("The source table[%s] is not found", pluginInputIdentifier)); } - String resultTableName = createTable.getTable().getName(); - if (sqlTables.containsKey(resultTableName)) { + String pluginOutputIdentifier = createTable.getTable().getName(); + if (sqlTables.containsKey(pluginOutputIdentifier)) { throw new ParserException( - String.format("Table name duplicate: %s", resultTableName)); + String.format("Table name duplicate: %s", pluginOutputIdentifier)); } - sqlTables.put(resultTableName, transformConfig); + sqlTables.put(pluginOutputIdentifier, transformConfig); String query = select.toString(); - transformConfig.setSourceTableName(sourceTableName); - transformConfig.setResultTableName(resultTableName); + transformConfig.setPluginInputIdentifier(pluginInputIdentifier); + transformConfig.setPluginOutputIdentifier(pluginOutputIdentifier); transformConfig.setQuery(query); return transformConfig; @@ -357,56 +355,58 @@ private static void parseInsertSql( if (select.getSelectBody() instanceof PlainSelect) { PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); - String sourceTableName; - String resultTableName; + String pluginInputIdentifier; + String pluginOutputIdentifier; if (plainSelect.getFromItem() == null) { - List selectItems = plainSelect.getSelectItems(); + List> selectItems = plainSelect.getSelectItems(); if (selectItems.size() != 1) { throw new ParserException( "Source table must be specified in SQL: " + insertSql); } - SelectExpressionItem selectItem = (SelectExpressionItem) selectItems.get(0); + SelectItem selectItem = selectItems.get(0); Column column = (Column) selectItem.getExpression(); - sourceTableName = column.getColumnName(); - resultTableName = sourceTableName; + pluginInputIdentifier = column.getColumnName(); + pluginOutputIdentifier = pluginInputIdentifier; } else { if (!(plainSelect.getFromItem() instanceof Table)) { throw new ParserException("Unsupported syntax: " + insertSql); } Table table = (Table) plainSelect.getFromItem(); - sourceTableName = table.getName(); - resultTableName = - sourceTableName + TEMP_TABLE_SUFFIX + tempTableIndex.getAndIncrement(); + pluginInputIdentifier = table.getName(); + pluginOutputIdentifier = + pluginInputIdentifier + + TEMP_TABLE_SUFFIX + + tempTableIndex.getAndIncrement(); String query = select.toString(); - transformConfig.setSourceTableName(sourceTableName); - transformConfig.setResultTableName(resultTableName); + transformConfig.setPluginInputIdentifier(pluginInputIdentifier); + transformConfig.setPluginOutputIdentifier(pluginOutputIdentifier); transformConfig.setQuery(query); seaTunnelConfig.getTransformConfigs().add(transformConfig); } - if (!sqlTables.containsKey(sourceTableName) + if (!sqlTables.containsKey(pluginInputIdentifier) || (!OPTION_TABLE_TYPE_SOURCE.equalsIgnoreCase( - sqlTables.get(sourceTableName).getType()) + sqlTables.get(pluginInputIdentifier).getType()) && !OPTION_TABLE_TYPE_TRANSFORM.equalsIgnoreCase( - sqlTables.get(sourceTableName).getType()))) { + sqlTables.get(pluginInputIdentifier).getType()))) { throw new ParserException( - String.format("The source table[%s] is not found", sourceTableName)); + String.format("The source table[%s] is not found", pluginInputIdentifier)); } if (!sqlTables.containsKey(targetTableName) || !OPTION_TABLE_TYPE_SINK.equalsIgnoreCase( sqlTables.get(targetTableName).getType())) { throw new ParserException( - String.format("The sink table[%s] is not found", sourceTableName)); + String.format("The sink table[%s] is not found", pluginInputIdentifier)); } SinkConfig sinkConfig = (SinkConfig) sqlTables.get(targetTableName); SinkConfig sinkConfigNew = new SinkConfig(); sinkConfigNew.setConnector(sinkConfig.getConnector()); - sinkConfigNew.setSourceTableName(resultTableName); + sinkConfigNew.setPluginInputIdentifier(pluginOutputIdentifier); sinkConfigNew.getOptions().addAll(sinkConfig.getOptions()); sinkConfigNew .getOptions() - .add(Option.of(OPTION_SOURCE_TABLE_NAME_KEY, "\"" + resultTableName + "\"")); + .add(Option.of(OPTION_PLUGIN_INPUT_KEY, "\"" + pluginOutputIdentifier + "\"")); seaTunnelConfig.getSinkConfigs().add(sinkConfigNew); } else { diff --git a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/model/BaseConfig.java b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/model/BaseConfig.java index a5706ff2afb..fc9ed02e70d 100644 --- a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/model/BaseConfig.java +++ b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/model/BaseConfig.java @@ -26,7 +26,7 @@ public abstract class BaseConfig { protected String type; - protected String sourceTableName; + protected String pluginInputIdentifier; - protected String resultTableName; + protected String pluginOutputIdentifier; } diff --git a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/utils/Constant.java b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/utils/Constant.java index f006754675e..abe5746694c 100644 --- a/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/utils/Constant.java +++ b/seatunnel-config/seatunnel-config-sql/src/main/java/org/apache/seatunnel/config/sql/utils/Constant.java @@ -47,9 +47,9 @@ public class Constant { public static final String OPTION_TABLE_CONNECTOR_KEY = "connector"; - public static final String OPTION_SOURCE_TABLE_NAME_KEY = "source_table_name"; + public static final String OPTION_PLUGIN_INPUT_KEY = "plugin_input"; - public static final String OPTION_RESULT_TABLE_NAME_KEY = "result_table_name"; + public static final String OPTION_PLUGIN_OUTPUT_KEY = "plugin_output"; public static final String OPTION_SINGLE_QUOTES = "'"; diff --git a/seatunnel-config/seatunnel-config-sql/src/test/java/org/apache/seatunnel/config/sql/SqlConfigBuilderTest.java b/seatunnel-config/seatunnel-config-sql/src/test/java/org/apache/seatunnel/config/sql/SqlConfigBuilderTest.java index 2cc45254c1d..30b3f3ab108 100644 --- a/seatunnel-config/seatunnel-config-sql/src/test/java/org/apache/seatunnel/config/sql/SqlConfigBuilderTest.java +++ b/seatunnel-config/seatunnel-config-sql/src/test/java/org/apache/seatunnel/config/sql/SqlConfigBuilderTest.java @@ -38,19 +38,19 @@ public void testSqlConfigBuild() { Config config = SqlConfigBuilder.of(file.toPath()); Config sourceConfig = config.getConfigList("source").get(0); Assertions.assertEquals("FakeSource", sourceConfig.getString("plugin_name")); - Assertions.assertEquals("test1", sourceConfig.getString("result_table_name")); + Assertions.assertEquals("test1", sourceConfig.getString("plugin_output")); List transformConfigs = config.getConfigList("transform"); Assertions.assertEquals(2, transformConfigs.size()); Config transformConf1 = (Config) transformConfigs.get(0); - Assertions.assertEquals("test1", transformConf1.getString("source_table_name")); - Assertions.assertEquals("test09", transformConf1.getString("result_table_name")); + Assertions.assertEquals("test1", transformConf1.getString("plugin_input")); + Assertions.assertEquals("test09", transformConf1.getString("plugin_output")); Config transformConf2 = (Config) transformConfigs.get(1); - Assertions.assertEquals("test09", transformConf2.getString("source_table_name")); - Assertions.assertEquals("test09__temp1", transformConf2.getString("result_table_name")); + Assertions.assertEquals("test09", transformConf2.getString("plugin_input")); + Assertions.assertEquals("test09__temp1", transformConf2.getString("plugin_output")); Config sinkConfig = config.getConfigList("sink").get(0); Assertions.assertEquals("jdbc", sinkConfig.getString("plugin_name")); - Assertions.assertEquals("test09__temp1", sinkConfig.getString("source_table_name")); + Assertions.assertEquals("test09__temp1", sinkConfig.getString("plugin_input")); } } diff --git a/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/config/ActivemqConfig.java b/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/config/ActivemqConfig.java index 868ac40a0c8..6bf0045361e 100644 --- a/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/config/ActivemqConfig.java +++ b/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/config/ActivemqConfig.java @@ -17,12 +17,12 @@ package org.apache.seatunnel.connectors.seatunnel.activemq.config; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; -import com.google.common.annotations.VisibleForTesting; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; diff --git a/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSink.java b/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSink.java index d1d37017959..85ecb347a7f 100644 --- a/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSink.java +++ b/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSink.java @@ -19,25 +19,29 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSinkWriter; import java.io.IOException; +import java.util.Optional; public class ActivemqSink extends AbstractSimpleSink { private final SeaTunnelRowType seaTunnelRowType; private final ReadonlyConfig pluginConfig; + private final CatalogTable catalogTable; @Override public String getPluginName() { return "ActiveMQ"; } - public ActivemqSink(ReadonlyConfig pluginConfig, SeaTunnelRowType rowType) { + public ActivemqSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { this.pluginConfig = pluginConfig; - this.seaTunnelRowType = rowType; + this.catalogTable = catalogTable; + this.seaTunnelRowType = catalogTable.getTableSchema().toPhysicalRowDataType(); } @Override @@ -45,4 +49,9 @@ public AbstractSinkWriter createWriter(SinkWriter.Context co throws IOException { return new ActivemqSinkWriter(pluginConfig, seaTunnelRowType); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.of(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSinkFactory.java b/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSinkFactory.java index 7f0dca38f6a..ec40d648ae4 100644 --- a/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSinkFactory.java +++ b/seatunnel-connectors-v2/connector-activemq/src/main/java/org/apache/seatunnel/connectors/seatunnel/activemq/sink/ActivemqSinkFactory.java @@ -75,9 +75,6 @@ public OptionRule optionRule() { @Override public TableSink createSink(TableSinkFactoryContext context) { - return () -> - new ActivemqSink( - context.getOptions(), - context.getCatalogTable().getTableSchema().toPhysicalRowDataType()); + return () -> new ActivemqSink(context.getOptions(), context.getCatalogTable()); } } diff --git a/seatunnel-connectors-v2/connector-amazondynamodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazondynamodb/sink/AmazonDynamoDBSink.java b/seatunnel-connectors-v2/connector-amazondynamodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazondynamodb/sink/AmazonDynamoDBSink.java index f26b388cd70..68dcc84a424 100644 --- a/seatunnel-connectors-v2/connector-amazondynamodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazondynamodb/sink/AmazonDynamoDBSink.java +++ b/seatunnel-connectors-v2/connector-amazondynamodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazondynamodb/sink/AmazonDynamoDBSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -36,6 +37,7 @@ import com.google.auto.service.AutoService; import java.io.IOException; +import java.util.Optional; import static org.apache.seatunnel.connectors.seatunnel.amazondynamodb.config.AmazonDynamoDBConfig.ACCESS_KEY_ID; import static org.apache.seatunnel.connectors.seatunnel.amazondynamodb.config.AmazonDynamoDBConfig.REGION; @@ -85,4 +87,9 @@ public AbstractSinkWriter createWriter(SinkWriter.Context co throws IOException { return new AmazonDynamoDBWriter(amazondynamodbSourceOptions, rowType); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSink.java b/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSink.java index 04cbdba2a5d..9952217f073 100644 --- a/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSink.java +++ b/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSink.java @@ -19,25 +19,29 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSinkWriter; import java.io.IOException; +import java.util.Optional; public class AmazonSqsSink extends AbstractSimpleSink { - private SeaTunnelRowType typeInfo; - private ReadonlyConfig pluginConfig; + private final SeaTunnelRowType typeInfo; + private final ReadonlyConfig pluginConfig; + private final CatalogTable catalogTable; @Override public String getPluginName() { return "AmazonSqs"; } - public AmazonSqsSink(ReadonlyConfig pluginConfig, SeaTunnelRowType typeInfo) { - this.typeInfo = typeInfo; + public AmazonSqsSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { + this.typeInfo = catalogTable.getTableSchema().toPhysicalRowDataType(); this.pluginConfig = pluginConfig; + this.catalogTable = catalogTable; } @Override @@ -45,4 +49,9 @@ public AbstractSinkWriter createWriter(SinkWriter.Context co throws IOException { return new AmazonSqsSinkWriter(typeInfo, pluginConfig); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.of(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSinkFactory.java b/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSinkFactory.java index 81ccf9a37bb..030e9d221a9 100644 --- a/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSinkFactory.java +++ b/seatunnel-connectors-v2/connector-amazonsqs/src/main/java/org/apache/seatunnel/connectors/seatunnel/amazonsqs/sink/AmazonSqsSinkFactory.java @@ -41,8 +41,7 @@ public String factoryIdentifier() { public TableSink createSink(TableSinkFactoryContext context) { ReadonlyConfig config = context.getOptions(); CatalogTable catalogTable = context.getCatalogTable(); - return () -> - new AmazonSqsSink(config, catalogTable.getTableSchema().toPhysicalRowDataType()); + return () -> new AmazonSqsSink(config, catalogTable); } @Override diff --git a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/excecutor/AssertExecutor.java b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/excecutor/AssertExecutor.java index f6908b989d2..cc1e12a80f7 100644 --- a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/excecutor/AssertExecutor.java +++ b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/excecutor/AssertExecutor.java @@ -17,6 +17,9 @@ package org.apache.seatunnel.connectors.seatunnel.assertion.excecutor; +import org.apache.seatunnel.shade.com.google.common.collect.Iterables; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.type.ArrayType; import org.apache.seatunnel.api.table.type.DecimalType; import org.apache.seatunnel.api.table.type.MapType; @@ -31,9 +34,6 @@ import org.apache.commons.lang3.StringUtils; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; @@ -285,11 +285,11 @@ private boolean compareMapValue(Map value, MapType type, Map c private Boolean checkType(Object value, SeaTunnelDataType fieldType) { if (value == null) { - if (fieldType.getSqlType() == SqlType.NULL) { - return true; - } else { - return false; - } + return true; + } + + if (fieldType.getSqlType() == SqlType.NULL) { + return false; } if (fieldType.getSqlType() == SqlType.ROW) { diff --git a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRule.java b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRule.java index 9d4e35493a7..6f48555aa18 100644 --- a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRule.java +++ b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRule.java @@ -34,6 +34,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Objects; import static org.apache.seatunnel.connectors.seatunnel.assertion.exception.AssertConnectorErrorCode.CATALOG_TABLE_FAILED; @@ -137,15 +138,35 @@ public void checkRule(List check) { if (CollectionUtils.isEmpty(check)) { throw new AssertConnectorException(CATALOG_TABLE_FAILED, "columns is null"); } - if (CollectionUtils.isNotEmpty(columns) - && !CollectionUtils.isEqualCollection(columns, check)) { + + if (columns.size() != check.size()) { throw new AssertConnectorException( CATALOG_TABLE_FAILED, String.format("columns: %s is not equal to %s", check, columns)); } + for (int i = 0; i < columns.size(); i++) { + if (!isColumnEqual(columns.get(i), check.get(i))) { + throw new AssertConnectorException( + CATALOG_TABLE_FAILED, + String.format( + "columns: %s is not equal to %s", + check.get(i), columns.get(i))); + } + } } } + private static boolean isColumnEqual(Column column1, Column column2) { + return Objects.equals(column1.getName(), column2.getName()) + && Objects.equals(column1.getDataType(), column2.getDataType()) + && Objects.equals(column1.getColumnLength(), column2.getColumnLength()) + && Objects.equals(column1.getScale(), column2.getScale()) + && column1.isNullable() == column2.isNullable() + && Objects.equals(column1.getDefaultValue(), column2.getDefaultValue()) + && Objects.equals(column1.getComment(), column2.getComment()) + && Objects.equals(column1.getSourceType(), column2.getSourceType()); + } + @Data @AllArgsConstructor public static class AssertTableIdentifierRule implements Serializable { diff --git a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRuleParser.java b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRuleParser.java index 964f0de566c..87a7a52db09 100644 --- a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRuleParser.java +++ b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/rule/AssertCatalogTableRuleParser.java @@ -87,7 +87,7 @@ private Optional parseColumnRule( config -> { String name = config.getString(COLUMN_NAME); String type = config.getString(COLUMN_TYPE); - Integer columnLength = + Long columnLength = TypesafeConfigUtils.getConfig( config, COLUMN_LENGTH, diff --git a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertConfig.java b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertConfig.java index d9fcea69aee..a35e91837f5 100644 --- a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertConfig.java +++ b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertConfig.java @@ -22,7 +22,6 @@ import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; -import java.util.List; import java.util.Map; public class AssertConfig { @@ -85,13 +84,6 @@ public static class TableIdentifierRule { .withDescription( "Rule definition of user's available data. Each rule represents one field validation or row num validation."); - public static final Option>> TABLE_CONFIGS = - Options.key("tables_configs") - .type(new TypeReference>>() {}) - .noDefaultValue() - .withDescription( - "Table configuration for the sink. Each table configuration contains the table name and the rules for the table."); - public static final Option TABLE_PATH = Options.key("table_path") .stringType() diff --git a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertSink.java b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertSink.java index 927943adc29..290298fdcea 100644 --- a/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertSink.java +++ b/seatunnel-connectors-v2/connector-assert/src/main/java/org/apache/seatunnel/connectors/seatunnel/assertion/sink/AssertSink.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.assertion.sink; +import org.apache.seatunnel.shade.com.google.common.base.Throwables; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.shade.com.typesafe.config.ConfigException; import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; @@ -34,18 +35,17 @@ import org.apache.seatunnel.connectors.seatunnel.assertion.rule.AssertTableRule; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; -import com.google.common.base.Throwables; - import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import static org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions.TABLE_CONFIGS; import static org.apache.seatunnel.connectors.seatunnel.assertion.sink.AssertConfig.CATALOG_TABLE_RULES; import static org.apache.seatunnel.connectors.seatunnel.assertion.sink.AssertConfig.FIELD_RULES; import static org.apache.seatunnel.connectors.seatunnel.assertion.sink.AssertConfig.ROW_RULES; import static org.apache.seatunnel.connectors.seatunnel.assertion.sink.AssertConfig.RULES; -import static org.apache.seatunnel.connectors.seatunnel.assertion.sink.AssertConfig.TABLE_CONFIGS; import static org.apache.seatunnel.connectors.seatunnel.assertion.sink.AssertConfig.TABLE_PATH; public class AssertSink extends AbstractSimpleSink @@ -56,6 +56,7 @@ public class AssertSink extends AbstractSimpleSink private final AssertTableRule assertTableRule; private final Map assertCatalogTableRule; private final String catalogTableName; + private final CatalogTable catalogTable; public AssertSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { this.seaTunnelRowType = catalogTable.getSeaTunnelRowType(); @@ -65,6 +66,7 @@ public AssertSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { assertFieldRules = new ConcurrentHashMap<>(); assertRowRules = new ConcurrentHashMap<>(); assertCatalogTableRule = new ConcurrentHashMap<>(); + catalogTableName = catalogTable.getTablePath().getFullName(); Config ruleConfig = ConfigFactory.parseMap(pluginConfig.get(RULES)); if (ruleConfig.hasPath(TABLE_CONFIGS.key())) { List tableConfigs = ruleConfig.getConfigList(TABLE_CONFIGS.key()); @@ -76,7 +78,6 @@ public AssertSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { String tableName = catalogTable.getTablePath().getFullName(); initTableRule(catalogTable, ruleConfig, tableName); } - catalogTableName = catalogTable.getTablePath().getFullName(); if (ruleConfig.hasPath(CatalogOptions.TABLE_NAMES.key())) { assertTableRule = @@ -93,6 +94,7 @@ public AssertSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { new ConfigException.BadValue( RULES.key(), "Assert rule config is empty, please add rule config.")); } + this.catalogTable = catalogTable; } private void initTableRule(CatalogTable catalogTable, Config tableConfig, String tableName) { @@ -111,7 +113,9 @@ private void initTableRule(CatalogTable catalogTable, Config tableConfig, String AssertCatalogTableRule catalogTableRule = new AssertRuleParser() .parseCatalogTableRule(tableConfig.getConfig(CATALOG_TABLE_RULES)); - catalogTableRule.checkRule(catalogTable); + if (tableName.equals(catalogTableName)) { + catalogTableRule.checkRule(catalogTable); + } assertCatalogTableRule.put(tableName, catalogTableRule); } } @@ -130,4 +134,9 @@ public AssertSinkWriter createWriter(SinkWriter.Context context) { public String getPluginName() { return "Assert"; } + + @Override + public Optional getWriteCatalogTable() { + return Optional.of(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-assert/src/test/java/org/apache/seatunnel/flink/assertion/AssertExecutorTest.java b/seatunnel-connectors-v2/connector-assert/src/test/java/org/apache/seatunnel/flink/assertion/AssertExecutorTest.java index 96b61bfaaef..c1e14e1f928 100644 --- a/seatunnel-connectors-v2/connector-assert/src/test/java/org/apache/seatunnel/flink/assertion/AssertExecutorTest.java +++ b/seatunnel-connectors-v2/connector-assert/src/test/java/org/apache/seatunnel/flink/assertion/AssertExecutorTest.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.flink.assertion; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; import org.apache.seatunnel.shade.com.typesafe.config.ConfigValue; @@ -35,8 +36,6 @@ import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDate; diff --git a/seatunnel-connectors-v2/connector-cassandra/src/main/java/org/apache/seatunnel/connectors/seatunnel/cassandra/sink/CassandraSink.java b/seatunnel-connectors-v2/connector-cassandra/src/main/java/org/apache/seatunnel/connectors/seatunnel/cassandra/sink/CassandraSink.java index 09a50b8c95a..9b37d942664 100644 --- a/seatunnel-connectors-v2/connector-cassandra/src/main/java/org/apache/seatunnel/connectors/seatunnel/cassandra/sink/CassandraSink.java +++ b/seatunnel-connectors-v2/connector-cassandra/src/main/java/org/apache/seatunnel/connectors/seatunnel/cassandra/sink/CassandraSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -43,6 +44,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static org.apache.seatunnel.connectors.seatunnel.cassandra.config.CassandraConfig.HOST; import static org.apache.seatunnel.connectors.seatunnel.cassandra.config.CassandraConfig.KEYSPACE; @@ -122,4 +124,9 @@ public AbstractSinkWriter createWriter(SinkWriter.Context co throws IOException { return new CassandraSinkWriter(cassandraParameters, seaTunnelRowType, tableSchema); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/BaseSourceConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/BaseSourceConfig.java index f4a82d2de12..2b441483a2c 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/BaseSourceConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/BaseSourceConfig.java @@ -22,6 +22,7 @@ import io.debezium.config.Configuration; import lombok.Getter; +import java.util.Map; import java.util.Properties; /** A basic Source configuration which is used by {@link IncrementalSource}. */ @@ -34,6 +35,7 @@ public abstract class BaseSourceConfig implements SourceConfig { @Getter protected final StopConfig stopConfig; @Getter protected final int splitSize; + @Getter protected final Map splitColumn; @Getter protected final double distributionFactorUpper; @Getter protected final double distributionFactorLower; @@ -50,6 +52,7 @@ public BaseSourceConfig( StartupConfig startupConfig, StopConfig stopConfig, int splitSize, + Map splitColumn, double distributionFactorUpper, double distributionFactorLower, int sampleShardingThreshold, @@ -59,6 +62,7 @@ public BaseSourceConfig( this.startupConfig = startupConfig; this.stopConfig = stopConfig; this.splitSize = splitSize; + this.splitColumn = splitColumn; this.distributionFactorUpper = distributionFactorUpper; this.distributionFactorLower = distributionFactorLower; this.sampleShardingThreshold = sampleShardingThreshold; diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfig.java index 9d46ab3393b..ddd47a2b83a 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfig.java @@ -22,6 +22,7 @@ import io.debezium.relational.RelationalDatabaseConnectorConfig; import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -49,6 +50,7 @@ public JdbcSourceConfig( List databaseList, List tableList, int splitSize, + Map splitColumn, double distributionFactorUpper, double distributionFactorLower, int sampleShardingThreshold, @@ -70,6 +72,7 @@ public JdbcSourceConfig( startupConfig, stopConfig, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfigFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfigFactory.java index d5d920c2573..99ddb3bd175 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfigFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceConfigFactory.java @@ -25,7 +25,9 @@ import lombok.Setter; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; /** A {@link SourceConfig.Factory} to provide {@link SourceConfig} of JDBC data source. */ @@ -51,6 +53,7 @@ public abstract class JdbcSourceConfigFactory implements SourceConfig.Factory splitColumn; protected int fetchSize = SourceOptions.SNAPSHOT_FETCH_SIZE.defaultValue(); protected String serverTimeZone = JdbcSourceOptions.SERVER_TIME_ZONE.defaultValue(); protected long connectTimeoutMillis = JdbcSourceOptions.CONNECT_TIMEOUT_MS.defaultValue(); @@ -65,6 +68,11 @@ public JdbcSourceConfigFactory hostname(String hostname) { return this; } + public JdbcSourceConfigFactory splitColumn(Map splitColumn) { + this.splitColumn = splitColumn; + return this; + } + /** Integer port number of the database server. */ public JdbcSourceConfigFactory port(int port) { this.port = port; @@ -239,6 +247,17 @@ public JdbcSourceConfigFactory fromReadonlyConfig(ReadonlyConfig config) { this.sampleShardingThreshold = config.get(JdbcSourceOptions.SAMPLE_SHARDING_THRESHOLD); this.inverseSamplingRate = config.get(JdbcSourceOptions.INVERSE_SAMPLING_RATE); this.splitSize = config.get(SourceOptions.SNAPSHOT_SPLIT_SIZE); + this.splitColumn = new HashMap<>(); + config.getOptional(JdbcSourceOptions.TABLE_NAMES_CONFIG) + .ifPresent( + jtcs -> { + jtcs.forEach( + jtc -> { + this.splitColumn.put( + jtc.getTable(), jtc.getSnapshotSplitColumn()); + }); + }); + this.fetchSize = config.get(SourceOptions.SNAPSHOT_FETCH_SIZE); this.serverTimeZone = config.get(JdbcSourceOptions.SERVER_TIME_ZONE); this.connectTimeoutMillis = config.get(JdbcSourceOptions.CONNECT_TIMEOUT_MS); diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceTableConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceTableConfig.java index 5cafa363e8d..2c55270bd4a 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceTableConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/config/JdbcSourceTableConfig.java @@ -26,4 +26,5 @@ public class JdbcSourceTableConfig implements Serializable { private String table; private List primaryKeys; + private String snapshotSplitColumn; } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/dialect/JdbcDataSourceDialect.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/dialect/JdbcDataSourceDialect.java index 05e9a89c04c..f9193c9eb79 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/dialect/JdbcDataSourceDialect.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/dialect/JdbcDataSourceDialect.java @@ -49,6 +49,9 @@ public interface JdbcDataSourceDialect extends DataSourceDialect discoverDataCollections(JdbcSourceConfig sourceConfig); + default void checkAllTablesEnabledCapture(JdbcConnection jdbcConnection, List tableIds) + throws SQLException {} + /** * Creates and opens a new {@link JdbcConnection} backing connection pool. * diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/JdbcSourceOptions.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/JdbcSourceOptions.java index 6cd7ba0631e..909e5c2980b 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/JdbcSourceOptions.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/JdbcSourceOptions.java @@ -152,7 +152,8 @@ public class JdbcSourceOptions extends SourceOptions { + "[" + " {" + " \"table\": \"db1.schema1.table1\"," - + " \"primaryKeys\": [\"key1\",\"key2\"]" + + " \"primaryKeys\": [\"key1\",\"key2\"]," + + " \"snapshotSplitColumn\": \"key2\"" + " }" + "]"); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java index 87483d9cff7..6c83088ef28 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/option/SourceOptions.java @@ -36,7 +36,6 @@ public class SourceOptions { .defaultValue(8096) .withDescription( "The split size (number of rows) of table snapshot, captured tables are split into multiple splits when read the snapshot of table."); - public static final Option SNAPSHOT_FETCH_SIZE = Options.key("snapshot.fetch.size") .intType() diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/AbstractSchemaChangeResolver.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/AbstractSchemaChangeResolver.java index ac86dd0d2bc..8401f86236f 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/AbstractSchemaChangeResolver.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/AbstractSchemaChangeResolver.java @@ -17,6 +17,14 @@ package org.apache.seatunnel.connectors.cdc.base.schema; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; import org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils; @@ -24,18 +32,24 @@ import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.source.SourceRecord; -import com.google.common.collect.Lists; +import io.debezium.relational.Tables; +import io.debezium.relational.ddl.DdlParser; import io.debezium.relational.history.HistoryRecord; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.util.List; +import java.util.Objects; @Slf4j public abstract class AbstractSchemaChangeResolver implements SchemaChangeResolver { protected static final List SUPPORT_DDL = Lists.newArrayList("ALTER TABLE"); - protected JdbcSourceConfig jdbcSourceConfig; + protected final JdbcSourceConfig jdbcSourceConfig; + @Setter protected transient DdlParser ddlParser; + @Setter protected transient Tables tables; + @Setter protected String sourceDialectName; public AbstractSchemaChangeResolver(JdbcSourceConfig jdbcSourceConfig) { this.jdbcSourceConfig = jdbcSourceConfig; @@ -55,4 +69,39 @@ public boolean support(SourceRecord record) { .map(String::toUpperCase) .anyMatch(prefix -> ddl.toUpperCase().contains(prefix)); } + + @Override + public SchemaChangeEvent resolve(SourceRecord record, SeaTunnelDataType dataType) { + TablePath tablePath = SourceRecordUtils.getTablePath(record); + String ddl = SourceRecordUtils.getDdl(record); + if (Objects.isNull(ddlParser)) { + this.ddlParser = createDdlParser(tablePath); + } + if (Objects.isNull(tables)) { + this.tables = new Tables(); + } + ddlParser.setCurrentDatabase(tablePath.getDatabaseName()); + ddlParser.setCurrentSchema(tablePath.getSchemaName()); + // Parse DDL statement using Debezium's Antlr parser + ddlParser.parse(ddl, tables); + List parsedEvents = getAndClearParsedEvents(); + parsedEvents.forEach(e -> e.setSourceDialectName(getSourceDialectName())); + AlterTableColumnsEvent alterTableColumnsEvent = + new AlterTableColumnsEvent( + TableIdentifier.of( + StringUtils.EMPTY, + tablePath.getDatabaseName(), + tablePath.getSchemaName(), + tablePath.getTableName()), + parsedEvents); + alterTableColumnsEvent.setStatement(ddl); + alterTableColumnsEvent.setSourceDialectName(getSourceDialectName()); + return parsedEvents.isEmpty() ? null : alterTableColumnsEvent; + } + + protected abstract DdlParser createDdlParser(TablePath tablePath); + + protected abstract List getAndClearParsedEvents(); + + protected abstract String getSourceDialectName(); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeResolver.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeResolver.java index ee3ef08f7d2..c4d403b8d24 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeResolver.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/schema/SchemaChangeResolver.java @@ -17,7 +17,7 @@ package org.apache.seatunnel.connectors.cdc.base.schema; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.kafka.connect.source.SourceRecord; diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java new file mode 100644 index 00000000000..a801a626e16 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/BaseChangeStreamTableSourceFactory.java @@ -0,0 +1,118 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.cdc.base.source; + +import org.apache.seatunnel.api.source.SourceSplit; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.connector.TableSource; +import org.apache.seatunnel.api.table.factory.ChangeStreamTableSourceFactory; +import org.apache.seatunnel.api.table.factory.ChangeStreamTableSourceState; +import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; +import org.apache.seatunnel.connectors.cdc.base.source.split.IncrementalSplit; +import org.apache.seatunnel.connectors.cdc.base.source.split.SourceSplitBase; + +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * CDC Base class for {@link ChangeStreamTableSourceFactory}. It provides a default implementation + * for {@link ChangeStreamTableSourceFactory#restoreSource(TableSourceFactoryContext, + * ChangeStreamTableSourceState)}. The default implementation will restore the source using the + * checkpoint tables in the {@link ChangeStreamTableSourceState}. + */ +@Slf4j +public abstract class BaseChangeStreamTableSourceFactory implements ChangeStreamTableSourceFactory { + @Override + public + TableSource createSource(TableSourceFactoryContext context) { + return restoreSource(context, Collections.emptyList()); + } + + @Override + public + TableSource restoreSource( + TableSourceFactoryContext context, + ChangeStreamTableSourceState state) { + return restoreSource(context, getRestoreTableStruct(state)); + } + + public abstract + TableSource restoreSource( + TableSourceFactoryContext context, List restoreTableStruct); + + protected + List getRestoreTableStruct( + ChangeStreamTableSourceState state) { + List incrementalSplits = + state.getSplits().stream() + .flatMap(List::stream) + .filter(e -> e != null) + .map(e -> SourceSplitBase.class.cast(e)) + .filter(e -> e.isIncrementalSplit()) + .map(e -> e.asIncrementalSplit()) + .collect(Collectors.toList()); + if (incrementalSplits.size() > 1) { + throw new UnsupportedOperationException( + "Multiple incremental splits are not supported"); + } + + if (incrementalSplits.size() == 1) { + IncrementalSplit incrementalSplit = incrementalSplits.get(0); + if (incrementalSplit.getCheckpointTables() != null) { + List checkpointTableStruct = incrementalSplit.getCheckpointTables(); + log.info("Restore source using checkpoint tables: {}", checkpointTableStruct); + return checkpointTableStruct; + } + if (incrementalSplit.getCheckpointDataType() != null) { + // TODO: Waiting for remove of compatible logic + List checkpointDataTypeStruct = + CatalogTableUtil.convertDataTypeToCatalogTables( + incrementalSplit.getCheckpointDataType(), "default.default"); + log.info("Restore source using checkpoint tables: {}", checkpointDataTypeStruct); + return checkpointDataTypeStruct; + } + } + + log.info("Restore source using checkpoint tables is empty"); + return Collections.emptyList(); + } + + protected List mergeTableStruct( + List dbTableStruct, List restoreTableStruct) { + if (!restoreTableStruct.isEmpty()) { + Map restoreTableMap = + restoreTableStruct.stream() + .collect(Collectors.toMap(CatalogTable::getTablePath, t -> t)); + + List mergedTableStruct = + dbTableStruct.stream() + .map(e -> restoreTableMap.getOrDefault(e.getTablePath(), e)) + .collect(Collectors.toList()); + log.info("Merge db table struct with checkpoint table struct: {}", mergedTableStruct); + return mergedTableStruct; + } + return dbTableStruct; + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/IncrementalSource.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/IncrementalSource.java index 003ff64e8ff..b75900c438b 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/IncrementalSource.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/IncrementalSource.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.cdc.base.source; +import org.apache.seatunnel.shade.com.google.common.collect.Sets; + import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.Boundedness; @@ -25,8 +27,6 @@ import org.apache.seatunnel.api.source.SourceSplitEnumerator; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.config.StartupConfig; import org.apache.seatunnel.connectors.cdc.base.config.StopConfig; @@ -59,7 +59,6 @@ import org.apache.seatunnel.connectors.seatunnel.common.source.reader.SourceReaderOptions; import org.apache.seatunnel.format.compatible.debezium.json.CompatibleDebeziumJsonDeserializationSchema; -import com.google.common.collect.Sets; import io.debezium.relational.TableId; import lombok.NoArgsConstructor; @@ -94,13 +93,7 @@ public abstract class IncrementalSource protected StopMode stopMode; protected DebeziumDeserializationSchema deserializationSchema; - protected SeaTunnelDataType dataType; - - protected IncrementalSource( - ReadonlyConfig options, - SeaTunnelDataType dataType, - List catalogTables) { - this.dataType = dataType; + protected IncrementalSource(ReadonlyConfig options, List catalogTables) { this.catalogTables = catalogTables; this.readonlyConfig = options; this.startupConfig = getStartupConfig(readonlyConfig); diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/IncrementalSplitAssigner.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/IncrementalSplitAssigner.java index 1accc47af23..580f927d690 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/IncrementalSplitAssigner.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/IncrementalSplitAssigner.java @@ -19,7 +19,7 @@ import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.source.enumerator.state.IncrementalPhaseState; import org.apache.seatunnel.connectors.cdc.base.source.event.SnapshotSplitWatermark; @@ -75,7 +75,8 @@ public class IncrementalSplitAssigner implements SplitAs private final Map assignedSplits = new HashMap<>(); private boolean startWithSnapshotMinimumOffset = true; - private SeaTunnelDataType checkpointDataType; + private List checkpointTables; + private Map historyTableChanges; public IncrementalSplitAssigner( SplitAssigner.Context context, @@ -158,7 +159,8 @@ public void addSplits(Collection splits) { } tableWatermarks.put(tableId, startupOffset); } - checkpointDataType = incrementalSplit.getCheckpointDataType(); + checkpointTables = incrementalSplit.getCheckpointTables(); + historyTableChanges = incrementalSplit.getHistoryTableChanges(); }); if (!tableWatermarks.isEmpty()) { this.startWithSnapshotMinimumOffset = false; @@ -257,7 +259,8 @@ private IncrementalSplit createIncrementalSplit( incrementalSplitStartOffset, sourceConfig.getStopConfig().getStopOffset(offsetFactory), completedSnapshotSplitInfos, - checkpointDataType); + checkpointTables, + historyTableChanges); } @VisibleForTesting diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/splitter/AbstractJdbcSourceChunkSplitter.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/splitter/AbstractJdbcSourceChunkSplitter.java index 60a208de866..05bf5e5d21b 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/splitter/AbstractJdbcSourceChunkSplitter.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/enumerator/splitter/AbstractJdbcSourceChunkSplitter.java @@ -25,6 +25,8 @@ import org.apache.seatunnel.connectors.cdc.base.source.split.SnapshotSplit; import org.apache.seatunnel.connectors.cdc.base.utils.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + import io.debezium.jdbc.JdbcConnection; import io.debezium.relational.Column; import io.debezium.relational.Table; @@ -36,9 +38,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; import static java.math.BigDecimal.ROUND_CEILING; import static org.apache.seatunnel.connectors.cdc.base.utils.ObjectUtils.doubleCompare; @@ -379,12 +384,53 @@ protected SnapshotSplit createSnapshotSplit( protected Column getSplitColumn( JdbcConnection jdbc, JdbcDataSourceDialect dialect, TableId tableId) throws SQLException { - Optional primaryKey = dialect.getPrimaryKey(jdbc, tableId); Column splitColumn = null; + Table table = dialect.queryTableSchema(jdbc, tableId).getTable(); + + // first , compare user defined split column is in the primary key or unique key + Map splitColumnsConfig = new HashMap<>(); + try { + splitColumnsConfig = sourceConfig.getSplitColumn(); + } catch (Exception e) { + log.error("Config snapshotSplitColumn get exception in {}:{}", tableId, e); + } + String tableSc = + splitColumnsConfig.getOrDefault(tableId.catalog() + "." + tableId.table(), null); + + if (StringUtils.isNotEmpty(tableSc)) { + // Is tableSc(table split column) the unique key + AtomicBoolean isUniqueKey = new AtomicBoolean(false); + dialect.getUniqueKeys(jdbc, tableId) + .forEach( + ck -> + ck.getColumnNames() + .forEach( + ckc -> { + if (tableSc.equals(ckc.getColumnName())) { + isUniqueKey.set(true); + } + })); + + if (isUniqueKey.get()) { + Column column = table.columnWithName(tableSc); + if (isEvenlySplitColumn(column)) { + return column; + } else { + log.warn( + "Config snapshotSplitColumn type in {} is not TINYINT、SMALLINT、INT、BIGINT、DECIMAL、STRING", + tableId); + } + } else { + log.warn("Config snapshotSplitColumn not unique key for table {}", tableId); + } + } else { + log.info("Config snapshotSplitColumn not exists for table {}", tableId); + } + + Optional primaryKey = dialect.getPrimaryKey(jdbc, tableId); if (primaryKey.isPresent()) { List pkColumns = primaryKey.get().getColumnNames(); - Table table = dialect.queryTableSchema(jdbc, tableId).getTable(); for (String pkColumn : pkColumns) { Column column = table.columnWithName(pkColumn); if (isEvenlySplitColumn(column)) { @@ -400,7 +446,6 @@ protected Column getSplitColumn( List uniqueKeys = dialect.getUniqueKeys(jdbc, tableId); if (!uniqueKeys.isEmpty()) { - Table table = dialect.queryTableSchema(jdbc, tableId).getTable(); for (ConstraintKey uniqueKey : uniqueKeys) { List uniqueKeyColumns = uniqueKey.getColumnNames(); diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/parser/SeatunnelDDLParser.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/parser/SeatunnelDDLParser.java new file mode 100644 index 00000000000..355df7fddef --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/parser/SeatunnelDDLParser.java @@ -0,0 +1,67 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.cdc.base.source.parser; + +import org.apache.seatunnel.api.table.catalog.TableIdentifier; + +import org.apache.commons.lang3.StringUtils; + +import io.debezium.relational.Column; +import io.debezium.relational.TableId; + +public interface SeatunnelDDLParser { + + /** + * @param column The column to convert + * @return The converted column in SeaTunnel format which has full type information + */ + default org.apache.seatunnel.api.table.catalog.Column toSeatunnelColumnWithFullTypeInfo( + Column column) { + org.apache.seatunnel.api.table.catalog.Column seatunnelColumn = toSeatunnelColumn(column); + String sourceColumnType = getSourceColumnTypeWithLengthScale(column); + return seatunnelColumn.reSourceType(sourceColumnType); + } + + /** + * @param column The column to convert + * @return The converted column in SeaTunnel format + */ + org.apache.seatunnel.api.table.catalog.Column toSeatunnelColumn(Column column); + + /** + * @param column The column to convert + * @return The type with length and scale + */ + default String getSourceColumnTypeWithLengthScale(Column column) { + StringBuilder sb = new StringBuilder(column.typeName()); + if (column.length() >= 0) { + sb.append('(').append(column.length()); + if (column.scale().isPresent()) { + sb.append(", ").append(column.scale().get()); + } + + sb.append(')'); + } + return sb.toString(); + } + + default TableIdentifier toTableIdentifier(TableId tableId) { + return new TableIdentifier( + StringUtils.EMPTY, tableId.catalog(), tableId.schema(), tableId.table()); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceReader.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceReader.java index abfccdeb75e..c11cdd95185 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceReader.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceReader.java @@ -19,7 +19,7 @@ import org.apache.seatunnel.api.source.Collector; import org.apache.seatunnel.api.source.SourceReader; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.dialect.DataSourceDialect; import org.apache.seatunnel.connectors.cdc.base.source.event.CompletedSnapshotPhaseEvent; @@ -52,7 +52,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkState; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; /** * The multi-parallel source reader for table snapshot phase from {@link SnapshotSplit} and then @@ -215,7 +215,7 @@ protected SourceSplitStateBase initializedState(SourceSplitBase split) { incrementalSplit.splitId(), incrementalSplit.getCheckpointDataType()); debeziumDeserializationSchema.restoreCheckpointProducedType( - incrementalSplit.getCheckpointDataType()); + incrementalSplit.getCheckpointTables()); } IncrementalSplitState splitState = new IncrementalSplitState(incrementalSplit); if (splitState.autoEnterPureIncrementPhaseIfAllowed()) { @@ -265,13 +265,18 @@ private boolean isIncrementalSplitPhase(List stateSplits) { } private List snapshotCheckpointDataType(IncrementalSplit incrementalSplit) { - // Snapshot current datatype to checkpoint - SeaTunnelDataType checkpointDataType = debeziumDeserializationSchema.getProducedType(); + // Snapshot current table struct to checkpoint + List checkpointTables = debeziumDeserializationSchema.getProducedType(); + + // Snapshot current history table changes to checkpoint for debezium IncrementalSplit newIncrementalSplit = - new IncrementalSplit(incrementalSplit, checkpointDataType); + new IncrementalSplit( + incrementalSplit, + checkpointTables, + debeziumDeserializationSchema.getHistoryTableChanges()); log.debug( "Snapshot checkpoint datatype {} into split[{}] state.", - checkpointDataType, + checkpointTables, incrementalSplit.splitId()); return Arrays.asList(newIncrementalSplit); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceRecordEmitter.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceRecordEmitter.java index 7c25d3ce5c4..0b09f048bfd 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceRecordEmitter.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/IncrementalSourceRecordEmitter.java @@ -22,7 +22,7 @@ import org.apache.seatunnel.api.source.Collector; import org.apache.seatunnel.api.source.SourceReader; import org.apache.seatunnel.api.source.event.MessageDelayedEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.connectors.cdc.base.source.event.CompletedSnapshotPhaseEvent; import org.apache.seatunnel.connectors.cdc.base.source.offset.Offset; import org.apache.seatunnel.connectors.cdc.base.source.offset.OffsetFactory; @@ -141,6 +141,8 @@ protected void processElement( emitElement(element, output); } } else if (isSchemaChangeEvent(element) && splitState.isIncrementalSplitState()) { + Offset position = getOffsetPosition(element); + splitState.asIncrementalSplitState().setStartupOffset(position); emitElement(element, output); } else if (isDataChangeRecord(element)) { if (splitState.isIncrementalSplitState()) { diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceScanFetcher.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceScanFetcher.java index 7f927af5878..dd64af10a9a 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceScanFetcher.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceScanFetcher.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.cdc.base.source.reader.external; +import org.apache.seatunnel.shade.com.google.common.util.concurrent.ThreadFactoryBuilder; + import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.cdc.base.source.split.SnapshotSplit; import org.apache.seatunnel.connectors.cdc.base.source.split.SourceRecords; @@ -25,7 +27,6 @@ import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.source.SourceRecord; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.debezium.connector.base.ChangeEventQueue; import io.debezium.pipeline.DataChangeEvent; import lombok.extern.slf4j.Slf4j; @@ -41,10 +42,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import static com.google.common.base.Preconditions.checkState; import static org.apache.seatunnel.connectors.cdc.base.source.split.wartermark.WatermarkEvent.isEndWatermarkEvent; import static org.apache.seatunnel.connectors.cdc.base.source.split.wartermark.WatermarkEvent.isHighWatermarkEvent; import static org.apache.seatunnel.connectors.cdc.base.source.split.wartermark.WatermarkEvent.isLowWatermarkEvent; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; /** * Fetcher to fetch data from table split, the split is the snapshot split {@link SnapshotSplit}. @@ -222,12 +223,15 @@ private void checkReadException() { @Override public void close() { try { - if (taskContext != null) { - taskContext.close(); - } + // 1. try close the split task if (snapshotSplitReadTask != null) { - snapshotSplitReadTask.shutdown(); + try { + snapshotSplitReadTask.shutdown(); + } catch (Exception e) { + log.error("Close snapshot split read task error", e); + } } + // 2. close the fetcher thread if (executorService != null) { executorService.shutdown(); if (!executorService.awaitTermination( @@ -240,6 +244,11 @@ public void close() { } } catch (Exception e) { log.error("Close scan fetcher error", e); + } finally { + // 3. close the task context + if (taskContext != null) { + taskContext.close(); + } } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcher.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcher.java index 16e45376566..ec960f4cb21 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcher.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcher.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.cdc.base.source.reader.external; +import org.apache.seatunnel.shade.com.google.common.util.concurrent.ThreadFactoryBuilder; + import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeResolver; import org.apache.seatunnel.connectors.cdc.base.source.offset.Offset; @@ -29,7 +31,6 @@ import org.apache.kafka.connect.source.SourceRecord; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.debezium.connector.base.ChangeEventQueue; import io.debezium.pipeline.DataChangeEvent; import io.debezium.relational.TableId; @@ -187,12 +188,15 @@ private void checkReadException() { @Override public void close() { try { - if (taskContext != null) { - taskContext.close(); - } + // 1. try close the split task if (streamFetchTask != null) { - streamFetchTask.shutdown(); + try { + streamFetchTask.shutdown(); + } catch (Exception e) { + log.error("Close stream split read task error", e); + } } + // 2. close the fetcher thread if (executorService != null) { executorService.shutdown(); if (!executorService.awaitTermination( @@ -205,6 +209,11 @@ public void close() { } } catch (Exception e) { log.error("Close stream fetcher error", e); + } finally { + // 3. close the task context + if (taskContext != null) { + taskContext.close(); + } } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/JdbcSourceFetchTaskContext.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/JdbcSourceFetchTaskContext.java index 01b5c251f57..48380d024fc 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/JdbcSourceFetchTaskContext.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/JdbcSourceFetchTaskContext.java @@ -23,8 +23,10 @@ import org.apache.seatunnel.connectors.cdc.base.dialect.JdbcDataSourceDialect; import org.apache.seatunnel.connectors.cdc.base.relational.JdbcSourceEventDispatcher; import org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils; +import org.apache.seatunnel.connectors.cdc.debezium.ConnectTableChangeSerializer; import org.apache.kafka.connect.data.Struct; +import org.apache.kafka.connect.json.JsonConverter; import org.apache.kafka.connect.source.SourceRecord; import io.debezium.config.CommonConnectorConfig; @@ -39,6 +41,7 @@ import java.time.Instant; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -50,6 +53,9 @@ public abstract class JdbcSourceFetchTaskContext implements FetchTask.Context { protected final JdbcDataSourceDialect dataSourceDialect; protected final CommonConnectorConfig dbzConnectorConfig; protected final SchemaNameAdjuster schemaNameAdjuster; + protected final ConnectTableChangeSerializer tableChangeSerializer = + new ConnectTableChangeSerializer(); + protected final JsonConverter jsonConverter; public JdbcSourceFetchTaskContext( JdbcSourceConfig sourceConfig, JdbcDataSourceDialect dataSourceDialect) { @@ -57,6 +63,8 @@ public JdbcSourceFetchTaskContext( this.dataSourceDialect = dataSourceDialect; this.dbzConnectorConfig = sourceConfig.getDbzConnectorConfig(); this.schemaNameAdjuster = SchemaNameAdjuster.create(); + this.jsonConverter = new JsonConverter(); + jsonConverter.configure(Collections.singletonMap("schemas.enable", true), false); } @Override diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/IncrementalSplit.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/IncrementalSplit.java index 640e173682a..e9052da4618 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/IncrementalSplit.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/IncrementalSplit.java @@ -17,14 +17,20 @@ package org.apache.seatunnel.connectors.cdc.base.source.split; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.connectors.cdc.base.source.offset.Offset; import io.debezium.relational.TableId; import lombok.Getter; +import lombok.ToString; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +@ToString @Getter public class IncrementalSplit extends SourceSplitBase { private static final long serialVersionUID = 1L; @@ -44,7 +50,12 @@ public class IncrementalSplit extends SourceSplitBase { */ private final List completedSnapshotSplitInfos; - private final SeaTunnelDataType checkpointDataType; + // Remove in the next version + @Deprecated private SeaTunnelDataType checkpointDataType; + private List checkpointTables; + + // debezium history table changes + private final Map historyTableChanges; public IncrementalSplit( String splitId, @@ -52,9 +63,17 @@ public IncrementalSplit( Offset startupOffset, Offset stopOffset, List completedSnapshotSplitInfos) { - this(splitId, capturedTables, startupOffset, stopOffset, completedSnapshotSplitInfos, null); + this( + splitId, + capturedTables, + startupOffset, + stopOffset, + completedSnapshotSplitInfos, + new ArrayList<>(), + new HashMap<>()); } + @Deprecated public IncrementalSplit(IncrementalSplit split, SeaTunnelDataType checkpointDataType) { this( split.splitId(), @@ -65,6 +84,21 @@ public IncrementalSplit(IncrementalSplit split, SeaTunnelDataType checkpointData checkpointDataType); } + public IncrementalSplit( + IncrementalSplit split, + List tables, + Map historyTableChanges) { + this( + split.splitId(), + split.getTableIds(), + split.getStartupOffset(), + split.getStopOffset(), + split.getCompletedSnapshotSplitInfos(), + tables, + historyTableChanges); + } + + @Deprecated public IncrementalSplit( String splitId, List capturedTables, @@ -78,5 +112,23 @@ public IncrementalSplit( this.stopOffset = stopOffset; this.completedSnapshotSplitInfos = completedSnapshotSplitInfos; this.checkpointDataType = checkpointDataType; + this.historyTableChanges = new HashMap<>(); + } + + public IncrementalSplit( + String splitId, + List capturedTables, + Offset startupOffset, + Offset stopOffset, + List completedSnapshotSplitInfos, + List checkpointTables, + Map historyTableChanges) { + super(splitId); + this.tableIds = capturedTables; + this.startupOffset = startupOffset; + this.stopOffset = stopOffset; + this.completedSnapshotSplitInfos = completedSnapshotSplitInfos; + this.checkpointTables = checkpointTables; + this.historyTableChanges = historyTableChanges; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/SnapshotSplit.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/SnapshotSplit.java index 95b354d2643..1ef6b821438 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/SnapshotSplit.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/source/split/SnapshotSplit.java @@ -22,7 +22,9 @@ import io.debezium.relational.TableId; import lombok.Getter; +import lombok.ToString; +@ToString @Getter public class SnapshotSplit extends SourceSplitBase { private static final long serialVersionUID = 1L; diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/utils/SourceRecordUtils.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/utils/SourceRecordUtils.java index 3245273ace2..1df22ee4287 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/utils/SourceRecordUtils.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/base/utils/SourceRecordUtils.java @@ -36,6 +36,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; import static io.debezium.connector.AbstractSourceInfo.DATABASE_NAME_KEY; import static io.debezium.connector.AbstractSourceInfo.SCHEMA_NAME_KEY; @@ -46,8 +47,12 @@ public class SourceRecordUtils { private SourceRecordUtils() {} - public static final String SCHEMA_CHANGE_EVENT_KEY_NAME = - "io.debezium.connector.mysql.SchemaChangeKey"; + /** Todo: Support more schema change event key name, currently only support MySQL and Oracle. */ + public static final List SUPPORT_SCHEMA_CHANGE_EVENT_KEY_NAME = + Arrays.asList( + "io.debezium.connector.mysql.SchemaChangeKey", + "io.debezium.connector.oracle.SchemaChangeKey"); + public static final String HEARTBEAT_VALUE_SCHEMA_KEY_NAME = "io.debezium.connector.common.Heartbeat"; private static final DocumentReader DOCUMENT_READER = DocumentReader.defaultReader(); @@ -62,10 +67,9 @@ public static Object[] rowToArray(ResultSet rs, int size) throws SQLException { } /** - * Return the timestamp when the change event is produced in MySQL. - * - *

The field `source.ts_ms` in {@link SourceRecord} data struct is the time when the change - * event is operated in MySQL. + * In the source object, ts_ms indicates the time that the change was made in the database. By + * comparing the value for payload.source.ts_ms with the value for payload.ts_ms, you can + * determine the lag between the source database update and Debezium. */ public static Long getMessageTimestamp(SourceRecord record) { Schema schema = record.valueSchema(); @@ -97,7 +101,9 @@ public static Long getFetchTimestamp(SourceRecord record) { public static boolean isSchemaChangeEvent(SourceRecord sourceRecord) { Schema keySchema = sourceRecord.keySchema(); - return keySchema != null && SCHEMA_CHANGE_EVENT_KEY_NAME.equalsIgnoreCase(keySchema.name()); + return keySchema != null + && SUPPORT_SCHEMA_CHANGE_EVENT_KEY_NAME.stream() + .anyMatch(name -> name.equalsIgnoreCase(keySchema.name())); } public static boolean isDataChangeRecord(SourceRecord record) { diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/AbstractDebeziumDeserializationSchema.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/AbstractDebeziumDeserializationSchema.java new file mode 100644 index 00000000000..f1b0f503797 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/AbstractDebeziumDeserializationSchema.java @@ -0,0 +1,87 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.cdc.debezium; + +import org.apache.seatunnel.api.source.Collector; + +import org.apache.kafka.connect.data.Struct; +import org.apache.kafka.connect.json.JsonConverter; +import org.apache.kafka.connect.source.SourceRecord; + +import io.debezium.relational.TableId; +import io.debezium.relational.history.HistoryRecord; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils.isSchemaChangeEvent; + +/** + * Abstract class for Debezium deserialization schema. + * + *

It provides the basic functionality to serialize the table changes struct and history table + * changes. + * + * @param + */ +public abstract class AbstractDebeziumDeserializationSchema + implements DebeziumDeserializationSchema { + + protected final Map tableChangesStructMap = new HashMap<>(); + protected transient JsonConverter converter; + + public AbstractDebeziumDeserializationSchema(Map tableIdTableChangeMap) { + this.tableChangesStructMap.putAll( + tableIdTableChangeMap.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> serializeStruct(entry.getValue())))); + } + + @Override + public Map getHistoryTableChanges() { + return new HashMap<>(tableChangesStructMap); + } + + public void deserialize(SourceRecord record, Collector out) throws Exception { + if (isSchemaChangeEvent(record)) { + Struct recordValue = (Struct) record.value(); + List tableChangesStruct = + (List) recordValue.get(HistoryRecord.Fields.TABLE_CHANGES); + tableChangesStruct.forEach( + tableChangeStruct -> { + tableChangesStructMap.put( + TableId.parse(tableChangeStruct.getString("id")), + serializeStruct(tableChangeStruct)); + }); + } + } + + private byte[] serializeStruct(Struct struct) { + if (converter == null) { + converter = new JsonConverter(); + Map configs = Collections.singletonMap("schemas.enable", true); + converter.configure(configs, false); + } + return converter.fromConnectData("topic", struct.schema(), struct); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/ConnectTableChangeSerializer.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/ConnectTableChangeSerializer.java new file mode 100644 index 00000000000..c933e82a486 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/ConnectTableChangeSerializer.java @@ -0,0 +1,235 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.cdc.debezium; + +import org.apache.kafka.connect.data.Schema; +import org.apache.kafka.connect.data.SchemaBuilder; +import org.apache.kafka.connect.data.Struct; + +import io.debezium.relational.Column; +import io.debezium.relational.ColumnEditor; +import io.debezium.relational.Table; +import io.debezium.relational.TableId; +import io.debezium.relational.history.TableChanges; +import io.debezium.util.SchemaNameAdjuster; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static io.debezium.relational.history.ConnectTableChangeSerializer.AUTO_INCREMENTED_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.CHARSET_NAME_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.COLUMNS_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.COMMENT_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.DEFAULT_CHARSET_NAME_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.GENERATED_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.ID_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.JDBC_TYPE_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.LENGTH_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.NAME_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.NATIVE_TYPE_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.OPTIONAL_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.POSITION_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.PRIMARY_KEY_COLUMN_NAMES_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.SCALE_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.TABLE_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.TYPE_EXPRESSION_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.TYPE_KEY; +import static io.debezium.relational.history.ConnectTableChangeSerializer.TYPE_NAME_KEY; + +/** + * A serializer for {@link TableChanges} that deserialize the table list of {@link Struct} into a + * {@link TableChanges}. This class is used to deserialize the checkpoint data into {@link + * TableChanges}. + */ +@Slf4j +public class ConnectTableChangeSerializer + implements TableChanges.TableChangesSerializer>, Serializable { + private static final String ENUM_VALUES_KEY = "enumValues"; + private static final SchemaNameAdjuster SCHEMA_NAME_ADJUSTER = SchemaNameAdjuster.create(); + + private static final Schema COLUMN_SCHEMA = + SchemaBuilder.struct() + .name(SCHEMA_NAME_ADJUSTER.adjust("io.debezium.connector.schema.Column")) + .field(NAME_KEY, Schema.STRING_SCHEMA) + .field(JDBC_TYPE_KEY, Schema.INT32_SCHEMA) + .field(NATIVE_TYPE_KEY, Schema.OPTIONAL_INT32_SCHEMA) + .field(TYPE_NAME_KEY, Schema.STRING_SCHEMA) + .field(TYPE_EXPRESSION_KEY, Schema.OPTIONAL_STRING_SCHEMA) + .field(CHARSET_NAME_KEY, Schema.OPTIONAL_STRING_SCHEMA) + .field(LENGTH_KEY, Schema.OPTIONAL_INT32_SCHEMA) + .field(SCALE_KEY, Schema.OPTIONAL_INT32_SCHEMA) + .field(POSITION_KEY, Schema.INT32_SCHEMA) + .field(OPTIONAL_KEY, Schema.OPTIONAL_BOOLEAN_SCHEMA) + .field(AUTO_INCREMENTED_KEY, Schema.OPTIONAL_BOOLEAN_SCHEMA) + .field(GENERATED_KEY, Schema.OPTIONAL_BOOLEAN_SCHEMA) + .field(COMMENT_KEY, Schema.OPTIONAL_STRING_SCHEMA) + .field( + ENUM_VALUES_KEY, + SchemaBuilder.array(Schema.STRING_SCHEMA).optional().build()) + .build(); + + public static final Schema TABLE_SCHEMA = + SchemaBuilder.struct() + .name(SCHEMA_NAME_ADJUSTER.adjust("io.debezium.connector.schema.Table")) + .field(DEFAULT_CHARSET_NAME_KEY, Schema.OPTIONAL_STRING_SCHEMA) + .field( + PRIMARY_KEY_COLUMN_NAMES_KEY, + SchemaBuilder.array(Schema.STRING_SCHEMA).optional().build()) + .field(COLUMNS_KEY, SchemaBuilder.array(COLUMN_SCHEMA).build()) + .field(COMMENT_KEY, Schema.OPTIONAL_STRING_SCHEMA) + .build(); + + public static final Schema CHANGE_SCHEMA = + SchemaBuilder.struct() + .name(SCHEMA_NAME_ADJUSTER.adjust("io.debezium.connector.schema.Change")) + .field(TYPE_KEY, Schema.STRING_SCHEMA) + .field(ID_KEY, Schema.STRING_SCHEMA) + .field(TABLE_KEY, TABLE_SCHEMA) + .build(); + + @Override + public List serialize(TableChanges tableChanges) { + return StreamSupport.stream(tableChanges.spliterator(), false) + .map(this::toStruct) + .collect(Collectors.toList()); + } + + @Override + public TableChanges deserialize(List data, boolean useCatalogBeforeSchema) { + TableChanges tableChanges = new TableChanges(); + for (Struct struct : data) { + String tableId = struct.getString(ID_KEY); + TableChanges.TableChangeType changeType = + TableChanges.TableChangeType.valueOf(struct.getString(TYPE_KEY)); + Table table = toTable(struct.getStruct(TABLE_KEY), TableId.parse(tableId)); + switch (changeType) { + case CREATE: + tableChanges.create(table); + break; + case DROP: + tableChanges.drop(table); + break; + case ALTER: + tableChanges.alter(table); + break; + default: + throw new IllegalArgumentException("Unknown table change type: " + changeType); + } + } + return tableChanges; + } + + public Table toTable(Struct struct, TableId tableId) { + return Table.editor() + .tableId(tableId) + .setDefaultCharsetName(struct.getString(DEFAULT_CHARSET_NAME_KEY)) + .setPrimaryKeyNames(struct.getArray(PRIMARY_KEY_COLUMN_NAMES_KEY)) + .setColumns( + struct.getArray(COLUMNS_KEY).stream() + .map(Struct.class::cast) + .map(this::toColumn) + .collect(Collectors.toList())) + .create(); + } + + private Column toColumn(Struct struct) { + ColumnEditor editor = + Column.editor() + .name(struct.getString(NAME_KEY)) + .jdbcType(struct.getInt32(JDBC_TYPE_KEY)) + .type( + struct.getString(TYPE_NAME_KEY), + struct.getString(TYPE_EXPRESSION_KEY)) + .charsetName(struct.getString(CHARSET_NAME_KEY)) + .position(struct.getInt32(POSITION_KEY)) + .optional(struct.getBoolean(OPTIONAL_KEY)) + .autoIncremented(struct.getBoolean(AUTO_INCREMENTED_KEY)) + .generated(struct.getBoolean(GENERATED_KEY)); + if (struct.get(NATIVE_TYPE_KEY) != null) { + editor.nativeType(struct.getInt32(NATIVE_TYPE_KEY)); + } + if (struct.get(LENGTH_KEY) != null) { + editor.length(struct.getInt32(LENGTH_KEY)); + } + if (struct.get(SCALE_KEY) != null) { + editor.scale(struct.getInt32(SCALE_KEY)); + } + if (struct.get(COMMENT_KEY) != null) { + editor.comment(struct.getString(COMMENT_KEY)); + } + if (struct.schema().field(ENUM_VALUES_KEY) != null) { + editor.enumValues(struct.getArray(ENUM_VALUES_KEY)); + } + return editor.create(); + } + + public Struct toStruct(TableChanges.TableChange tableChange) { + final Struct struct = new Struct(CHANGE_SCHEMA); + + struct.put(TYPE_KEY, tableChange.getType().name()); + struct.put(ID_KEY, tableChange.getId().toDoubleQuotedString()); + struct.put(TABLE_KEY, toStruct(tableChange.getTable())); + return struct; + } + + private Struct toStruct(Table table) { + final Struct struct = new Struct(TABLE_SCHEMA); + + struct.put(DEFAULT_CHARSET_NAME_KEY, table.defaultCharsetName()); + struct.put(PRIMARY_KEY_COLUMN_NAMES_KEY, table.primaryKeyColumnNames()); + + final List columns = + table.columns().stream().map(this::toStruct).collect(Collectors.toList()); + + struct.put(COLUMNS_KEY, columns); + return struct; + } + + private Struct toStruct(Column column) { + final Struct struct = new Struct(COLUMN_SCHEMA); + + struct.put(NAME_KEY, column.name()); + struct.put(JDBC_TYPE_KEY, column.jdbcType()); + + if (column.nativeType() != Column.UNSET_INT_VALUE) { + struct.put(NATIVE_TYPE_KEY, column.nativeType()); + } + + struct.put(TYPE_NAME_KEY, column.typeName()); + struct.put(TYPE_EXPRESSION_KEY, column.typeExpression()); + struct.put(CHARSET_NAME_KEY, column.charsetName()); + + if (column.length() != Column.UNSET_INT_VALUE) { + struct.put(LENGTH_KEY, column.length()); + } + + column.scale().ifPresent(s -> struct.put(SCALE_KEY, s)); + + struct.put(POSITION_KEY, column.position()); + struct.put(OPTIONAL_KEY, column.isOptional()); + struct.put(AUTO_INCREMENTED_KEY, column.isAutoIncremented()); + struct.put(GENERATED_KEY, column.isGenerated()); + struct.put(COMMENT_KEY, column.comment()); + struct.put(ENUM_VALUES_KEY, column.enumValues()); + + return struct; + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/DebeziumDeserializationSchema.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/DebeziumDeserializationSchema.java index 8e8cb3c09c2..3f9bc766c3d 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/DebeziumDeserializationSchema.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/DebeziumDeserializationSchema.java @@ -18,12 +18,16 @@ package org.apache.seatunnel.connectors.cdc.debezium; import org.apache.seatunnel.api.source.Collector; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeResolver; import org.apache.kafka.connect.source.SourceRecord; +import io.debezium.relational.TableId; + import java.io.Serializable; +import java.util.List; +import java.util.Map; /** * The deserialization schema describes how to turn the Debezium SourceRecord into data types @@ -36,11 +40,13 @@ public interface DebeziumDeserializationSchema extends Serializable { /** Deserialize the Debezium record, it is represented in Kafka {@link SourceRecord}. */ void deserialize(SourceRecord record, Collector out) throws Exception; - SeaTunnelDataType getProducedType(); + List getProducedType(); - default void restoreCheckpointProducedType(SeaTunnelDataType checkpointDataType) {} + default void restoreCheckpointProducedType(List checkpointDataType) {} default SchemaChangeResolver getSchemaChangeResolver() { return null; } + + Map getHistoryTableChanges(); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/DebeziumJsonDeserializeSchema.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/DebeziumJsonDeserializeSchema.java index 4f5ad0545da..47b1413bd73 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/DebeziumJsonDeserializeSchema.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/DebeziumJsonDeserializeSchema.java @@ -18,25 +18,37 @@ package org.apache.seatunnel.connectors.cdc.debezium.row; import org.apache.seatunnel.api.source.Collector; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationSchema; +import org.apache.seatunnel.connectors.cdc.debezium.AbstractDebeziumDeserializationSchema; import org.apache.seatunnel.format.compatible.debezium.json.CompatibleDebeziumJsonDeserializationSchema; +import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.source.SourceRecord; +import io.debezium.relational.TableId; import lombok.extern.slf4j.Slf4j; +import java.util.HashMap; +import java.util.List; import java.util.Map; @Slf4j -public class DebeziumJsonDeserializeSchema implements DebeziumDeserializationSchema { +public class DebeziumJsonDeserializeSchema + extends AbstractDebeziumDeserializationSchema { private static final String KEY_SCHEMA_ENABLE = "key.converter.schemas.enable"; private static final String VALUE_SCHEMA_ENABLE = "value.converter.schemas.enable"; private final CompatibleDebeziumJsonDeserializationSchema deserializationSchema; public DebeziumJsonDeserializeSchema(Map debeziumConfig) { + this(debeziumConfig, new HashMap<>()); + } + + public DebeziumJsonDeserializeSchema( + Map debeziumConfig, Map tableIdTableChangeMap) { + super(tableIdTableChangeMap); boolean keySchemaEnable = Boolean.valueOf(debeziumConfig.getOrDefault(KEY_SCHEMA_ENABLE, "true")); boolean valueSchemaEnable = @@ -47,12 +59,15 @@ public DebeziumJsonDeserializeSchema(Map debeziumConfig) { @Override public void deserialize(SourceRecord record, Collector out) throws Exception { + super.deserialize(record, out); + SeaTunnelRow row = deserializationSchema.deserialize(record); out.collect(row); } @Override - public SeaTunnelDataType getProducedType() { - return deserializationSchema.getProducedType(); + public List getProducedType() { + return CatalogTableUtil.convertDataTypeToCatalogTables( + deserializationSchema.getProducedType(), "default.default"); } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConverters.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConverters.java index 0a2fb09cf8d..89b9c50c30d 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConverters.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConverters.java @@ -85,7 +85,7 @@ public SeaTunnelRow convert(SourceRecord record, Struct struct, Schema schema) if (field == null) { row.setField(i, null); } else { - Object fieldValue = struct.get(fieldName); + Object fieldValue = struct.getWithoutDefault(fieldName); Schema fieldSchema = field.schema(); Object convertedField = SeaTunnelRowDebeziumDeserializationConverters.convertField( @@ -494,11 +494,11 @@ public Object convert(Object dbzObj, Schema schema) throws Exception { SeaTunnelRow row = new SeaTunnelRow(arity); for (int i = 0; i < arity; i++) { String fieldName = fieldNames[i]; - Object fieldValue = struct.get(fieldName); Field field = schema.field(fieldName); if (field == null) { row.setField(i, null); } else { + Object fieldValue = struct.getWithoutDefault(fieldName); Schema fieldSchema = field.schema(); Object convertedField = SeaTunnelRowDebeziumDeserializationConverters.convertField( diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java index d09e7b77b5c..e568154ba6c 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/main/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializeSchema.java @@ -17,21 +17,23 @@ package org.apache.seatunnel.connectors.cdc.debezium.row; +import org.apache.seatunnel.api.event.EventType; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TablePath; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; -import org.apache.seatunnel.api.table.event.handler.DataTypeChangeEventDispatcher; -import org.apache.seatunnel.api.table.event.handler.DataTypeChangeEventHandler; -import org.apache.seatunnel.api.table.type.MultipleRowType; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.handler.TableSchemaChangeEventDispatcher; +import org.apache.seatunnel.api.table.schema.handler.TableSchemaChangeEventHandler; +import org.apache.seatunnel.api.table.type.MetadataUtil; import org.apache.seatunnel.api.table.type.RowKind; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.api.table.type.SqlType; import org.apache.seatunnel.connectors.cdc.base.schema.SchemaChangeResolver; import org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils; +import org.apache.seatunnel.connectors.cdc.debezium.AbstractDebeziumDeserializationSchema; import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationConverterFactory; -import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationSchema; import org.apache.seatunnel.connectors.cdc.debezium.MetadataConverter; import org.apache.kafka.connect.data.Schema; @@ -39,6 +41,7 @@ import org.apache.kafka.connect.source.SourceRecord; import io.debezium.data.Envelope; +import io.debezium.relational.TableId; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.Setter; @@ -46,19 +49,22 @@ import lombok.extern.slf4j.Slf4j; import java.time.ZoneId; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.seatunnel.connectors.cdc.base.source.split.wartermark.WatermarkEvent.isSchemaChangeAfterWatermarkEvent; import static org.apache.seatunnel.connectors.cdc.base.source.split.wartermark.WatermarkEvent.isSchemaChangeBeforeWatermarkEvent; import static org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils.isDataChangeRecord; import static org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils.isSchemaChangeEvent; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** Deserialization schema from Debezium object to {@link SeaTunnelRow}. */ @Slf4j public final class SeaTunnelRowDebeziumDeserializeSchema - implements DebeziumDeserializationSchema { + extends AbstractDebeziumDeserializationSchema { private static final long serialVersionUID = 1L; private static final String DEFAULT_TABLE_NAME_KEY = null; @@ -66,34 +72,34 @@ public final class SeaTunnelRowDebeziumDeserializeSchema private final ZoneId serverTimeZone; private final DebeziumDeserializationConverterFactory userDefinedConverterFactory; private final SchemaChangeResolver schemaChangeResolver; - private final DataTypeChangeEventHandler dataTypeChangeEventHandler; - private SeaTunnelDataType resultTypeInfo; + private final TableSchemaChangeEventHandler tableSchemaChangeHandler; + private List tables; private Map tableRowConverters; SeaTunnelRowDebeziumDeserializeSchema( - SeaTunnelDataType physicalDataType, MetadataConverter[] metadataConverters, - SeaTunnelDataType resultType, + List tables, ZoneId serverTimeZone, DebeziumDeserializationConverterFactory userDefinedConverterFactory, - SchemaChangeResolver schemaChangeResolver) { + SchemaChangeResolver schemaChangeResolver, + Map tableIdTableChangeMap) { + super(tableIdTableChangeMap); this.metadataConverters = metadataConverters; this.serverTimeZone = serverTimeZone; this.userDefinedConverterFactory = userDefinedConverterFactory; - this.resultTypeInfo = checkNotNull(resultType); + this.tables = checkNotNull(tables); this.schemaChangeResolver = schemaChangeResolver; - this.dataTypeChangeEventHandler = new DataTypeChangeEventDispatcher(); + this.tableSchemaChangeHandler = new TableSchemaChangeEventDispatcher(); this.tableRowConverters = createTableRowConverters( - resultType, - metadataConverters, - serverTimeZone, - userDefinedConverterFactory); + tables, metadataConverters, serverTimeZone, userDefinedConverterFactory); } @Override public void deserialize(SourceRecord record, Collector collector) throws Exception { + super.deserialize(record, collector); + if (isSchemaChangeBeforeWatermarkEvent(record)) { collector.markSchemaChangeBeforeCheckpoint(); return; @@ -117,42 +123,81 @@ public void deserialize(SourceRecord record, Collector collector) private void deserializeSchemaChangeRecord( SourceRecord record, Collector collector) { - SchemaChangeEvent schemaChangeEvent = schemaChangeResolver.resolve(record, resultTypeInfo); + SchemaChangeEvent schemaChangeEvent = null; + try { + if (schemaChangeResolver != null) { + schemaChangeEvent = schemaChangeResolver.resolve(record, null); + } + } catch (Exception e) { + log.warn("Failed to resolve schemaChangeEvent, just skip.", e); + return; + } if (schemaChangeEvent == null) { log.warn("Unsupported resolve schemaChangeEvent {}, just skip.", record); return; } - if (resultTypeInfo instanceof MultipleRowType) { - Map newRowTypeMap = new HashMap<>(); - for (Map.Entry entry : (MultipleRowType) resultTypeInfo) { - if (!entry.getKey().equals(schemaChangeEvent.tablePath().toString())) { - newRowTypeMap.put(entry.getKey(), entry.getValue()); - continue; - } + boolean tableExist = false; + for (int i = 0; i < tables.size(); i++) { + CatalogTable changeBefore = tables.get(i); + if (!schemaChangeEvent.tablePath().equals(changeBefore.getTablePath())) { + continue; + } - log.debug("Table[{}] datatype change before: {}", entry.getKey(), entry.getValue()); - SeaTunnelRowType newRowType = - dataTypeChangeEventHandler.reset(entry.getValue()).apply(schemaChangeEvent); - newRowTypeMap.put(entry.getKey(), newRowType); - log.debug("Table[{}] datatype change after: {}", entry.getKey(), newRowType); + tableExist = true; + log.debug( + "Table[{}] change before: {}", + schemaChangeEvent.tablePath(), + changeBefore.getTableSchema()); + + CatalogTable changeAfter = null; + if (EventType.SCHEMA_CHANGE_UPDATE_COLUMNS.equals(schemaChangeEvent.getEventType())) { + AlterTableColumnsEvent alterTableColumnsEvent = + (AlterTableColumnsEvent) schemaChangeEvent; + for (AlterTableColumnEvent event : alterTableColumnsEvent.getEvents()) { + TableSchema changeAfterSchema = + tableSchemaChangeHandler + .reset(changeBefore.getTableSchema()) + .apply(event); + changeAfter = + CatalogTable.of( + changeBefore.getTableId(), + changeAfterSchema, + changeBefore.getOptions(), + changeBefore.getPartitionKeys(), + changeBefore.getComment()); + event.setChangeAfter(changeAfter); + + changeBefore = changeAfter; + } + } else { + TableSchema changeAfterSchema = + tableSchemaChangeHandler + .reset(changeBefore.getTableSchema()) + .apply(schemaChangeEvent); + changeAfter = + CatalogTable.of( + changeBefore.getTableId(), + changeAfterSchema, + changeBefore.getOptions(), + changeBefore.getPartitionKeys(), + changeBefore.getComment()); } - resultTypeInfo = new MultipleRowType(newRowTypeMap); - } else { - log.debug("Table datatype change before: {}", resultTypeInfo); - resultTypeInfo = - dataTypeChangeEventHandler - .reset((SeaTunnelRowType) resultTypeInfo) - .apply(schemaChangeEvent); - log.debug("table datatype change after: {}", resultTypeInfo); + tables.set(i, changeAfter); + schemaChangeEvent.setChangeAfter(changeAfter); + log.debug( + "Table[{}] change after: {}", + schemaChangeEvent.tablePath(), + changeAfter.getTableSchema()); + break; + } + if (!tableExist) { + log.error( + "Not found table {}, skip schema change event {}", + schemaChangeEvent.tablePath()); } - tableRowConverters = createTableRowConverters( - resultTypeInfo, - metadataConverters, - serverTimeZone, - userDefinedConverterFactory); - + tables, metadataConverters, serverTimeZone, userDefinedConverterFactory); collector.collect(schemaChangeEvent); } @@ -164,7 +209,7 @@ private void deserializeDataChangeRecord(SourceRecord record, Collector 1) { converters = tableRowConverters.get(tableId); if (converters == null) { log.debug("Ignore newly added table {}", tableId); @@ -173,26 +218,39 @@ private void deserializeDataChangeRecord(SourceRecord record, Collector getProducedType() { - return resultTypeInfo; + public List getProducedType() { + return tables; } @Override @@ -234,77 +292,61 @@ public SchemaChangeResolver getSchemaChangeResolver() { } @Override - public void restoreCheckpointProducedType(SeaTunnelDataType checkpointDataType) { + public void restoreCheckpointProducedType(List checkpointDataType) { // If checkpointDataType is null, it indicates that DDL changes are not supported. // Therefore, we need to use the latest table structure to ensure that data from newly added // columns can be parsed correctly. if (schemaChangeResolver == null) { return; } - if (SqlType.ROW.equals(checkpointDataType.getSqlType()) - && SqlType.MULTIPLE_ROW.equals(resultTypeInfo.getSqlType())) { - // TODO: Older versions may have this issue - log.warn( - "Skip incompatible restore type. produced type: {}, checkpoint type: {}", - resultTypeInfo, - checkpointDataType); - return; - } - if (checkpointDataType instanceof MultipleRowType) { - MultipleRowType latestDataType = (MultipleRowType) resultTypeInfo; - Map newRowTypeMap = new HashMap<>(); - for (Map.Entry entry : latestDataType) { - newRowTypeMap.put(entry.getKey(), entry.getValue()); - } - for (Map.Entry entry : (MultipleRowType) checkpointDataType) { - SeaTunnelRowType oldDataType = latestDataType.getRowType(entry.getKey()); - if (oldDataType == null) { - log.info("Ignore restore table[{}] datatype has been deleted.", entry.getKey()); - continue; - } - log.info("Table[{}] datatype restore before: {}", entry.getKey(), oldDataType); - newRowTypeMap.put(entry.getKey(), entry.getValue()); - log.info("Table[{}] datatype restore after: {}", entry.getKey(), entry.getValue()); + Map latestTableMap = + this.tables.stream().collect(Collectors.toMap(CatalogTable::getTablePath, t -> t)); + Map restoreTableMap = + checkpointDataType.stream() + .collect(Collectors.toMap(CatalogTable::getTablePath, t -> t)); + for (TablePath tablePath : restoreTableMap.keySet()) { + CatalogTable latestTable = latestTableMap.get(tablePath); + CatalogTable restoreTable = restoreTableMap.get(tablePath); + if (latestTable == null) { + log.info("Ignore restore table[{}] has been deleted.", tablePath); + continue; } - resultTypeInfo = new MultipleRowType(newRowTypeMap); - } else { - log.info("Table datatype restore before: {}", resultTypeInfo); - resultTypeInfo = checkpointDataType; - log.info("Table datatype restore after: {}", checkpointDataType); + + log.info("Table[{}] restore before: {}", tablePath, latestTable.getSeaTunnelRowType()); + latestTableMap.put(tablePath, restoreTable); + log.info("Table[{}] restore after: {}", tablePath, restoreTable.getSeaTunnelRowType()); } - tableRowConverters = + this.tables = new ArrayList<>(latestTableMap.values()); + this.tableRowConverters = createTableRowConverters( - resultTypeInfo, - metadataConverters, - serverTimeZone, - userDefinedConverterFactory); + tables, metadataConverters, serverTimeZone, userDefinedConverterFactory); } private static Map createTableRowConverters( - SeaTunnelDataType inputDataType, + List tables, MetadataConverter[] metadataConverters, ZoneId serverTimeZone, DebeziumDeserializationConverterFactory userDefinedConverterFactory) { Map tableRowConverters = new HashMap<>(); - if (inputDataType instanceof MultipleRowType) { - for (Map.Entry item : (MultipleRowType) inputDataType) { + if (tables.size() > 1) { + for (CatalogTable table : tables) { SeaTunnelRowDebeziumDeserializationConverters itemRowConverter = new SeaTunnelRowDebeziumDeserializationConverters( - item.getValue(), + table.getSeaTunnelRowType(), metadataConverters, serverTimeZone, userDefinedConverterFactory); - tableRowConverters.put(item.getKey(), itemRowConverter); + tableRowConverters.put(table.getTablePath().toString(), itemRowConverter); } return tableRowConverters; } SeaTunnelRowDebeziumDeserializationConverters tableRowConverter = new SeaTunnelRowDebeziumDeserializationConverters( - (SeaTunnelRowType) inputDataType, + tables.get(0).getSeaTunnelRowType(), metadataConverters, serverTimeZone, userDefinedConverterFactory); @@ -320,22 +362,22 @@ public static Builder builder() { @Accessors(chain = true) @NoArgsConstructor(access = AccessLevel.PRIVATE) public static class Builder { - private SeaTunnelDataType physicalRowType; - private SeaTunnelDataType resultTypeInfo; + private List tables; private MetadataConverter[] metadataConverters = new MetadataConverter[0]; private ZoneId serverTimeZone = ZoneId.systemDefault(); private DebeziumDeserializationConverterFactory userDefinedConverterFactory = DebeziumDeserializationConverterFactory.DEFAULT; + private Map tableIdTableChangeMap = new HashMap<>(); private SchemaChangeResolver schemaChangeResolver; public SeaTunnelRowDebeziumDeserializeSchema build() { return new SeaTunnelRowDebeziumDeserializeSchema( - physicalRowType, metadataConverters, - resultTypeInfo, + tables, serverTimeZone, userDefinedConverterFactory, - schemaChangeResolver); + schemaChangeResolver, + tableIdTableChangeMap); } } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcherTest.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcherTest.java index ee8d4d7e5df..23906ae6f47 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcherTest.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/reader/external/IncrementalSourceStreamFetcherTest.java @@ -50,6 +50,7 @@ import java.util.concurrent.atomic.AtomicReference; import static io.debezium.config.CommonConnectorConfig.TRANSACTION_TOPIC; +import static io.debezium.connector.AbstractSourceInfo.DEBEZIUM_CONNECTOR_KEY; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -336,7 +337,22 @@ static SourceRecord createSchemaChangeUnknownEvent() { static SourceRecord createSchemaChangeEvent(String topic) { Schema keySchema = - SchemaBuilder.struct().name(SourceRecordUtils.SCHEMA_CHANGE_EVENT_KEY_NAME).build(); + SchemaBuilder.struct().name("io.debezium.connector.mysql.SchemaChangeKey").build(); + Schema valueKeySchema = + SchemaBuilder.struct() + .name("io.debezium.connector.mysql.Source") + .field(DEBEZIUM_CONNECTOR_KEY, Schema.STRING_SCHEMA) + .build(); + Struct valueValues = new Struct(valueKeySchema); + valueValues.put(DEBEZIUM_CONNECTOR_KEY, "mysql"); + + Schema valueSchema = + SchemaBuilder.struct() + .field(Envelope.FieldName.SOURCE, valueKeySchema) + .name("") + .build(); + Struct value = new Struct(valueSchema); + value.put(valueSchema.field(Envelope.FieldName.SOURCE), valueValues); SourceRecord record = new SourceRecord( Collections.emptyMap(), @@ -344,8 +360,8 @@ static SourceRecord createSchemaChangeEvent(String topic) { topic, keySchema, null, - null, - null); + valueSchema, + value); Assertions.assertTrue(SourceRecordUtils.isSchemaChangeEvent(record)); return record; } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/split/state/IncrementalSplitStateTest.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/split/state/IncrementalSplitStateTest.java index 4a0b40852a2..70fa137b16a 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/split/state/IncrementalSplitStateTest.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/base/source/split/state/IncrementalSplitStateTest.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.cdc.base.source.split.state; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.cdc.base.source.event.SnapshotSplitWatermark; import org.apache.seatunnel.connectors.cdc.base.source.offset.Offset; import org.apache.seatunnel.connectors.cdc.base.source.split.CompletedSnapshotSplitInfo; @@ -137,7 +138,8 @@ private static IncrementalSplit createIncrementalSplit( startupOffset, null, snapshotSplits, - null); + (List) null, + Collections.emptyMap()); } private static CompletedSnapshotSplitInfo createCompletedSnapshotSplitInfo( diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConvertersTest.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConvertersTest.java new file mode 100644 index 00000000000..74e832d6e0f --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-base/src/test/java/org/apache/seatunnel/connectors/cdc/debezium/row/SeaTunnelRowDebeziumDeserializationConvertersTest.java @@ -0,0 +1,78 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.cdc.debezium.row; + +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationConverterFactory; +import org.apache.seatunnel.connectors.cdc.debezium.MetadataConverter; + +import org.apache.kafka.connect.data.Schema; +import org.apache.kafka.connect.data.SchemaBuilder; +import org.apache.kafka.connect.data.Struct; +import org.apache.kafka.connect.source.SourceRecord; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; + +public class SeaTunnelRowDebeziumDeserializationConvertersTest { + + @Test + void testDefaultValueNotUsed() throws Exception { + SeaTunnelRowDebeziumDeserializationConverters converters = + new SeaTunnelRowDebeziumDeserializationConverters( + new SeaTunnelRowType( + new String[] {"id", "name"}, + new SeaTunnelDataType[] { + BasicType.INT_TYPE, BasicType.STRING_TYPE + }), + new MetadataConverter[] {}, + ZoneId.systemDefault(), + DebeziumDeserializationConverterFactory.DEFAULT); + Schema schema = + SchemaBuilder.struct() + .field("id", SchemaBuilder.int32().build()) + .field("name", SchemaBuilder.string().defaultValue("UL")) + .build(); + Struct value = new Struct(schema); + // the value of `name` is null, so do not put value for it + value.put("id", 1); + SourceRecord record = + new SourceRecord( + new HashMap<>(), + new HashMap<>(), + "topicName", + null, + SchemaBuilder.int32().build(), + 1, + schema, + value, + null, + new ArrayList<>()); + + SeaTunnelRow row = converters.convert(record, value, schema); + Assertions.assertEquals(row.getField(0), 1); + Assertions.assertNull(row.getField(1)); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSource.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSource.java index e85b4b57a7c..3350d4e3c72 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSource.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSource.java @@ -21,8 +21,6 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.SupportParallelism; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.dialect.DataSourceDialect; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; @@ -50,11 +48,8 @@ public class MongodbIncrementalSource extends IncrementalSource dataType, - List catalogTables) { - super(options, dataType, catalogTables); + public MongodbIncrementalSource(ReadonlyConfig options, List catalogTables) { + super(options, catalogTables); } @Override @@ -115,9 +110,8 @@ public DebeziumDeserializationSchema createDebeziumDeserializationSchema( config.get(JdbcSourceOptions.DEBEZIUM_PROPERTIES)); } - SeaTunnelDataType physicalRowType = dataType; return (DebeziumDeserializationSchema) - new MongoDBConnectorDeserializationSchema(physicalRowType, physicalRowType); + new MongoDBConnectorDeserializationSchema(catalogTables); } @Override diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSourceFactory.java index ede71f0f792..5e3a95145c5 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSourceFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/MongodbIncrementalSourceFactory.java @@ -22,20 +22,26 @@ import org.apache.seatunnel.api.source.SourceSplit; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.StartupMode; import org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions; +import org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.exception.MongodbConnectorException; import com.google.auto.service.AutoService; import java.io.Serializable; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT; @AutoService(Factory.class) public class MongodbIncrementalSourceFactory implements TableSourceFactory { @@ -50,7 +56,8 @@ public OptionRule optionRule() { .required( MongodbSourceOptions.HOSTS, MongodbSourceOptions.DATABASE, - MongodbSourceOptions.COLLECTION) + MongodbSourceOptions.COLLECTION, + TableSchemaOptions.SCHEMA) .optional( MongodbSourceOptions.USERNAME, MongodbSourceOptions.PASSWORD, @@ -79,13 +86,30 @@ public Class getSourceClass() { public TableSource createSource(TableSourceFactoryContext context) { return () -> { - List catalogTables = + List configCatalog = CatalogTableUtil.getCatalogTables( context.getOptions(), context.getClassLoader()); - SeaTunnelDataType dataType = - CatalogTableUtil.convertToMultipleRowType(catalogTables); + List collections = context.getOptions().get(MongodbSourceOptions.COLLECTION); + if (collections.size() != configCatalog.size()) { + throw new MongodbConnectorException( + ILLEGAL_ARGUMENT, + "The number of collections must be equal to the number of schema tables"); + } + List catalogTables = + IntStream.range(0, configCatalog.size()) + .mapToObj( + i -> { + CatalogTable catalogTable = configCatalog.get(i); + String fullName = collections.get(i); + TableIdentifier tableIdentifier = + TableIdentifier.of( + catalogTable.getCatalogName(), + TablePath.of(fullName)); + return CatalogTable.of(tableIdentifier, catalogTable); + }) + .collect(Collectors.toList()); return (SeaTunnelSource) - new MongodbIncrementalSource<>(context.getOptions(), dataType, catalogTables); + new MongodbIncrementalSource<>(context.getOptions(), catalogTables); }; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/sender/MongoDBConnectorDeserializationSchema.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/sender/MongoDBConnectorDeserializationSchema.java index 8ce920e8416..823ed5d9ec1 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/sender/MongoDBConnectorDeserializationSchema.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mongodb/sender/MongoDBConnectorDeserializationSchema.java @@ -17,17 +17,22 @@ package org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.sender; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.type.ArrayType; import org.apache.seatunnel.api.table.type.DecimalType; import org.apache.seatunnel.api.table.type.MapType; -import org.apache.seatunnel.api.table.type.MultipleRowType; +import org.apache.seatunnel.api.table.type.MetadataUtil; import org.apache.seatunnel.api.table.type.RowKind; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.api.table.type.SqlType; -import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationSchema; +import org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils; +import org.apache.seatunnel.connectors.cdc.debezium.AbstractDebeziumDeserializationSchema; import org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.exception.MongodbConnectorException; import org.apache.kafka.connect.data.Schema; @@ -42,6 +47,7 @@ import org.bson.types.Decimal128; import com.mongodb.client.model.changestream.OperationType; +import io.debezium.relational.TableId; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -62,29 +68,39 @@ import static org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT; import static org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE; import static org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.COLL_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.DB_FIELD; import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.DEFAULT_JSON_WRITER_SETTINGS; import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.DOCUMENT_KEY; import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.ENCODE_VALUE_FIELD; import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.FULL_DOCUMENT; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.NS_FIELD; import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.utils.MongodbRecordUtils.extractBsonDocument; import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Slf4j public class MongoDBConnectorDeserializationSchema - implements DebeziumDeserializationSchema { - private final SeaTunnelDataType resultTypeInfo; + extends AbstractDebeziumDeserializationSchema { + private final List tables; private final Map tableRowConverters; + public MongoDBConnectorDeserializationSchema(List tables) { + this(tables, new HashMap<>()); + } + public MongoDBConnectorDeserializationSchema( - SeaTunnelDataType physicalDataType, - SeaTunnelDataType resultTypeInfo) { - this.tableRowConverters = createConverter(physicalDataType); - this.resultTypeInfo = resultTypeInfo; + List tables, Map tableIdTableChangeMap) { + super(tableIdTableChangeMap); + this.tableRowConverters = createConverter(tables); + this.tables = tables; } @Override - public void deserialize(@Nonnull SourceRecord record, Collector out) { + public void deserialize(@Nonnull SourceRecord record, Collector out) + throws Exception { + super.deserialize(record, out); + Struct value = (Struct) record.value(); Schema valueSchema = record.valueSchema(); @@ -105,18 +121,27 @@ public void deserialize(@Nonnull SourceRecord record, Collector ou log.debug("Ignore newly added table {}", tableId); return; } - + Long fetchTimestamp = SourceRecordUtils.getFetchTimestamp(record); + Long messageTimestamp = SourceRecordUtils.getMessageTimestamp(record); + long delay = -1L; + if (fetchTimestamp != null && messageTimestamp != null) { + delay = fetchTimestamp - messageTimestamp; + } switch (op) { case INSERT: SeaTunnelRow insert = extractRowData(tableRowConverter, fullDocument); insert.setRowKind(RowKind.INSERT); insert.setTableId(tableId); + MetadataUtil.setDelay(insert, delay); + MetadataUtil.setEventTime(insert, fetchTimestamp); emit(record, insert, out); break; case DELETE: SeaTunnelRow delete = extractRowData(tableRowConverter, documentKey); delete.setRowKind(RowKind.DELETE); delete.setTableId(tableId); + MetadataUtil.setDelay(delete, delay); + MetadataUtil.setEventTime(delete, fetchTimestamp); emit(record, delete, out); break; case UPDATE: @@ -126,12 +151,16 @@ public void deserialize(@Nonnull SourceRecord record, Collector ou SeaTunnelRow updateAfter = extractRowData(tableRowConverter, fullDocument); updateAfter.setRowKind(RowKind.UPDATE_AFTER); updateAfter.setTableId(tableId); + MetadataUtil.setDelay(updateAfter, delay); + MetadataUtil.setEventTime(updateAfter, fetchTimestamp); emit(record, updateAfter, out); break; case REPLACE: SeaTunnelRow replaceAfter = extractRowData(tableRowConverter, fullDocument); replaceAfter.setRowKind(RowKind.UPDATE_AFTER); replaceAfter.setTableId(tableId); + MetadataUtil.setDelay(replaceAfter, delay); + MetadataUtil.setEventTime(replaceAfter, fetchTimestamp); emit(record, replaceAfter, out); break; case INVALIDATE: @@ -145,8 +174,8 @@ public void deserialize(@Nonnull SourceRecord record, Collector ou } @Override - public SeaTunnelDataType getProducedType() { - return resultTypeInfo; + public List getProducedType() { + return tables; } private @Nonnull OperationType operationTypeFor(@Nonnull SourceRecord record) { @@ -169,8 +198,16 @@ private SeaTunnelRow extractRowData( } private String extractTableId(SourceRecord record) { - // TODO extract table id from record - return null; + Struct messageStruct = (Struct) record.value(); + Struct nsStruct = (Struct) messageStruct.get(NS_FIELD); + String databaseName = nsStruct.getString(DB_FIELD); + String tableName = nsStruct.getString(COLL_FIELD); + return TablePath.of(databaseName, null, tableName).toString(); + } + + @VisibleForTesting + public String extractTableIdForTest(SourceRecord record) { + return extractTableId(record); } // ------------------------------------------------------------------------------------- @@ -182,12 +219,11 @@ public interface DeserializationRuntimeConverter extends Serializable { Object convert(BsonValue bsonValue); } - public Map createConverter( - SeaTunnelDataType inputDataType) { + public Map createConverter(List tables) { Map tableRowConverters = new HashMap<>(); - for (Map.Entry item : (MultipleRowType) inputDataType) { + for (CatalogTable table : tables) { SerializableFunction internalRowConverter = - createNullSafeInternalConverter(item.getValue()); + createNullSafeInternalConverter(table.getSeaTunnelRowType()); DeserializationRuntimeConverter itemRowConverter = new DeserializationRuntimeConverter() { private static final long serialVersionUID = 1L; @@ -197,7 +233,7 @@ public Object convert(BsonValue bsonValue) { return internalRowConverter.apply(bsonValue); } }; - tableRowConverters.put(item.getKey(), itemRowConverter); + tableRowConverters.put(table.getTablePath().toString(), itemRowConverter); } return tableRowConverters; } @@ -307,6 +343,15 @@ public Object apply(BsonValue bsonValue) { return convertToLocalDateTime(bsonValue).toLocalDate(); } }; + case TIME: + return new SerializableFunction() { + private static final long serialVersionUID = 1L; + + @Override + public Object apply(BsonValue bsonValue) { + return convertToLocalDateTime(bsonValue).toLocalTime(); + } + }; case TIMESTAMP: return new SerializableFunction() { private static final long serialVersionUID = 1L; @@ -346,7 +391,7 @@ public Object apply(BsonValue bsonValue) { private static LocalDateTime convertToLocalDateTime(BsonValue bsonValue) { Instant instant; if (bsonValue.isTimestamp()) { - instant = Instant.ofEpochSecond(bsonValue.asTimestamp().getTime()); + instant = Instant.ofEpochSecond(bsonValue.asTimestamp().getValue()); } else if (bsonValue.isDateTime()) { instant = Instant.ofEpochMilli(bsonValue.asDateTime().getValue()); } else { @@ -485,7 +530,7 @@ private static boolean convertToBoolean(@Nonnull BsonValue bsonValue) { } private static double convertToDouble(@Nonnull BsonValue bsonValue) { - if (bsonValue.isDouble()) { + if (bsonValue.isNumber()) { return bsonValue.asNumber().doubleValue(); } throw new MongodbConnectorException( @@ -496,9 +541,20 @@ private static double convertToDouble(@Nonnull BsonValue bsonValue) { + bsonValue.getBsonType()); } - private static int convertToInt(@Nonnull BsonValue bsonValue) { + private static int convertToInt(BsonValue bsonValue) { if (bsonValue.isInt32()) { - return bsonValue.asNumber().intValue(); + return bsonValue.asInt32().getValue(); + } else if (bsonValue.isNumber()) { + long longValue = bsonValue.asNumber().longValue(); + if (longValue > Integer.MAX_VALUE || longValue < Integer.MIN_VALUE) { + throw new MongodbConnectorException( + UNSUPPORTED_DATA_TYPE, + "Unable to convert to integer from unexpected value '" + + bsonValue + + "' of type " + + bsonValue.getBsonType()); + } + return (int) longValue; } throw new MongodbConnectorException( UNSUPPORTED_DATA_TYPE, @@ -532,8 +588,19 @@ private static byte[] convertToBinary(@Nonnull BsonValue bsonValue) { "Unsupported BYTES value type: " + bsonValue.getClass().getSimpleName()); } - private static long convertToLong(@Nonnull BsonValue bsonValue) { - if (bsonValue.isInt64()) { + private static long convertToLong(BsonValue bsonValue) { + if (bsonValue.isInt64() || bsonValue.isInt32()) { + return bsonValue.asNumber().longValue(); + } else if (bsonValue.isDouble()) { + double value = bsonValue.asNumber().doubleValue(); + if (value > Long.MAX_VALUE || value < Long.MIN_VALUE) { + throw new MongodbConnectorException( + UNSUPPORTED_DATA_TYPE, + "Unable to convert to long from unexpected value '" + + bsonValue + + "' of type " + + bsonValue.getBsonType()); + } return bsonValue.asNumber().longValue(); } throw new MongodbConnectorException( @@ -563,4 +630,31 @@ private static BigDecimal convertToBigDecimal(@Nonnull BsonValue bsonValue) { + "' of type " + bsonValue.getBsonType()); } + + @VisibleForTesting + public Object convertToObject( + @Nonnull SeaTunnelDataType dataType, @Nonnull BsonValue bsonValue) { + switch (dataType.getSqlType()) { + case INT: + return convertToInt(bsonValue); + case BIGINT: + return convertToLong(bsonValue); + case DOUBLE: + return convertToDouble(bsonValue); + case STRING: + return convertToString(bsonValue); + case DATE: + return convertToLocalDateTime(bsonValue).toLocalDate(); + case TIME: + return convertToLocalDateTime(bsonValue).toLocalTime(); + case TIMESTAMP: + return convertToLocalDateTime(bsonValue); + case DECIMAL: + DecimalType decimalType = (DecimalType) dataType; + BigDecimal decimalValue = convertToBigDecimal(bsonValue); + return fromBigDecimal( + decimalValue, decimalType.getPrecision(), decimalType.getScale()); + } + return null; + } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/test/java/mongodb/sender/MongoDBConnectorDeserializationSchemaTest.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/test/java/mongodb/sender/MongoDBConnectorDeserializationSchemaTest.java new file mode 100644 index 00000000000..91c9fb47bc7 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mongodb/src/test/java/mongodb/sender/MongoDBConnectorDeserializationSchemaTest.java @@ -0,0 +1,270 @@ +/* + * 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. + */ + +package mongodb.sender; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.DecimalType; +import org.apache.seatunnel.api.table.type.LocalTimeType; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.exception.MongodbConnectorException; +import org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.sender.MongoDBConnectorDeserializationSchema; +import org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.utils.MongodbRecordUtils; + +import org.apache.kafka.connect.source.SourceRecord; + +import org.bson.BsonDateTime; +import org.bson.BsonDecimal128; +import org.bson.BsonDocument; +import org.bson.BsonDouble; +import org.bson.BsonInt32; +import org.bson.BsonInt64; +import org.bson.BsonObjectId; +import org.bson.BsonString; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.stream.IntStream; + +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.COLL_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.DB_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.DOCUMENT_KEY; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.FULL_DOCUMENT; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.ID_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.NS_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.OPERATION_TYPE; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.OPERATION_TYPE_INSERT; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.SNAPSHOT_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.SNAPSHOT_TRUE; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.SOURCE_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.config.MongodbSourceOptions.TS_MS_FIELD; +import static org.apache.seatunnel.connectors.seatunnel.cdc.mongodb.utils.MongodbRecordUtils.createSourceOffsetMap; + +public class MongoDBConnectorDeserializationSchemaTest { + + private static TableSchema tableSchema; + private static CatalogTable catalogTable; + + @BeforeAll + public static void setUp() { + tableSchema = + TableSchema.builder() + .column(PhysicalColumn.of("int", BasicType.INT_TYPE, 1L, true, null, "")) + .column(PhysicalColumn.of("long", BasicType.LONG_TYPE, 1L, true, null, "")) + .column( + PhysicalColumn.of( + "double", BasicType.DOUBLE_TYPE, 1L, true, null, "")) + .column( + PhysicalColumn.of( + "decimal", new DecimalType(10, 2), 1L, true, null, "")) + .column( + PhysicalColumn.of( + "string", BasicType.STRING_TYPE, 200L, true, null, "")) + .column( + PhysicalColumn.of( + "date", + LocalTimeType.LOCAL_DATE_TYPE, + null, + null, + true, + null, + null)) + .column( + PhysicalColumn.of( + "time", + LocalTimeType.LOCAL_TIME_TYPE, + null, + null, + true, + null, + null)) + .column( + PhysicalColumn.of( + "timestamp", + LocalTimeType.LOCAL_DATE_TIME_TYPE, + null, + null, + true, + null, + null)) + .build(); + catalogTable = + CatalogTable.of( + TableIdentifier.of("catalog", "database", "table"), + tableSchema, + Collections.emptyMap(), + Collections.emptyList(), + "comment"); + } + + @Test + public void extractTableId() { + MongoDBConnectorDeserializationSchema schema = + new MongoDBConnectorDeserializationSchema(Collections.singletonList(catalogTable)); + + // Build SourceRecord + Map partitionMap = + MongodbRecordUtils.createPartitionMap("localhost:27017", "inventory", "products"); + + BsonDocument valueDocument = + new BsonDocument() + .append( + ID_FIELD, + new BsonDocument(ID_FIELD, new BsonInt64(10000000000001L))) + .append(OPERATION_TYPE, new BsonString(OPERATION_TYPE_INSERT)) + .append( + NS_FIELD, + new BsonDocument(DB_FIELD, new BsonString("inventory")) + .append(COLL_FIELD, new BsonString("products"))) + .append( + DOCUMENT_KEY, + new BsonDocument(ID_FIELD, new BsonInt64(10000000000001L))) + .append(FULL_DOCUMENT, new BsonDocument()) + .append(TS_MS_FIELD, new BsonInt64(System.currentTimeMillis())) + .append( + SOURCE_FIELD, + new BsonDocument(SNAPSHOT_FIELD, new BsonString(SNAPSHOT_TRUE)) + .append(TS_MS_FIELD, new BsonInt64(0L))); + BsonDocument keyDocument = new BsonDocument(ID_FIELD, valueDocument.get(ID_FIELD)); + SourceRecord sourceRecord = + MongodbRecordUtils.buildSourceRecord( + partitionMap, + createSourceOffsetMap(keyDocument.getDocument(ID_FIELD), true), + "inventory.products", + keyDocument, + valueDocument); + Object tableId = schema.extractTableIdForTest(sourceRecord); + Assertions.assertEquals("inventory.products", tableId); + } + + @Test + public void testBsonConvert() { + MongoDBConnectorDeserializationSchema schema = + new MongoDBConnectorDeserializationSchema(Collections.singletonList(catalogTable)); + // check int + Assertions.assertEquals( + 123456, schema.convertToObject(getDataType("int"), new BsonInt32(123456))); + Assertions.assertEquals( + Integer.MAX_VALUE, + schema.convertToObject(getDataType("int"), new BsonInt64(Integer.MAX_VALUE))); + Assertions.assertEquals( + 123456, schema.convertToObject(getDataType("int"), new BsonDouble(123456))); + Assertions.assertThrowsExactly( + MongodbConnectorException.class, + () -> + schema.convertToObject( + getDataType("int"), new BsonDouble(1234567890123456789.0d))); + Assertions.assertThrowsExactly( + MongodbConnectorException.class, + () -> schema.convertToObject(getDataType("int"), new BsonInt64(Long.MIN_VALUE))); + // check long + Assertions.assertEquals( + 123456L, schema.convertToObject(getDataType("long"), new BsonInt32(123456))); + Assertions.assertEquals( + (long) Integer.MAX_VALUE, + schema.convertToObject(getDataType("long"), new BsonInt64(Integer.MAX_VALUE))); + Assertions.assertEquals( + 123456L, schema.convertToObject(getDataType("long"), new BsonDouble(123456))); + Assertions.assertThrowsExactly( + MongodbConnectorException.class, + () -> + schema.convertToObject( + getDataType("long"), + new BsonDouble(12345678901234567891234567890123456789.0d))); + + // check double + Assertions.assertEquals( + 1.0d, schema.convertToObject(getDataType("double"), new BsonInt32(1))); + Assertions.assertEquals( + 1.0d, schema.convertToObject(getDataType("double"), new BsonInt64(1))); + Assertions.assertEquals( + 4.4d, schema.convertToObject(getDataType("double"), new BsonDouble(4.4))); + // check decimal + Assertions.assertEquals( + new BigDecimal("3.14"), + schema.convertToObject( + getDataType("decimal"), new BsonDecimal128(Decimal128.parse("3.1415926")))); + // check string + Assertions.assertEquals( + "123456", schema.convertToObject(getDataType("string"), new BsonString("123456"))); + Assertions.assertEquals( + "507f191e810c19729de860ea", + schema.convertToObject( + getDataType("string"), + new BsonObjectId(new ObjectId("507f191e810c19729de860ea")))); + BsonDocument document = + new BsonDocument() + .append("key", new BsonString("123456")) + .append("value", new BsonInt64(123456789L)); + Assertions.assertEquals( + "{\"key\": \"123456\", \"value\": 123456789}", + schema.convertToObject(getDataType("string"), document)); + + LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS); + long epochMilli = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + // check localDate + Assertions.assertEquals( + now.toLocalDate(), + schema.convertToObject(getDataType("date"), new BsonDateTime(epochMilli))); + Assertions.assertEquals( + now.toLocalDate(), + schema.convertToObject(getDataType("date"), new BsonDateTime(epochMilli))); + // check localTime + Assertions.assertEquals( + now.toLocalTime(), + schema.convertToObject(getDataType("time"), new BsonDateTime(epochMilli))); + Assertions.assertEquals( + now.toLocalTime(), + schema.convertToObject(getDataType("time"), new BsonDateTime(epochMilli))); + // check localDateTime + Assertions.assertEquals( + now, + schema.convertToObject(getDataType("timestamp"), new BsonDateTime(epochMilli))); + Assertions.assertEquals( + now, + schema.convertToObject(getDataType("timestamp"), new BsonDateTime(epochMilli))); + } + + private SeaTunnelDataType getDataType(String fieldName) { + String[] fieldNames = tableSchema.getFieldNames(); + return IntStream.range(0, fieldNames.length) + .mapToObj( + i -> { + if (fieldName.equals(fieldNames[i])) { + return tableSchema.getColumns().get(i).getDataType(); + } + return null; + }) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new RuntimeException("not found field")); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfig.java index 19d1124847b..86aa8943517 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfig.java @@ -25,6 +25,7 @@ import io.debezium.relational.RelationalTableFilters; import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -41,6 +42,7 @@ public MySqlSourceConfig( List databaseList, List tableList, int splitSize, + Map splitColumn, double distributionFactorUpper, double distributionFactorLower, int sampleShardingThreshold, @@ -64,6 +66,7 @@ public MySqlSourceConfig( databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfigFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfigFactory.java index b39655293e4..fd5d7deadfb 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfigFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/config/MySqlSourceConfigFactory.java @@ -26,10 +26,12 @@ import java.util.Properties; import java.util.UUID; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** A factory to initialize {@link MySqlSourceConfig}. */ public class MySqlSourceConfigFactory extends JdbcSourceConfigFactory { + public static final String SCHEMA_CHANGE_KEY = "include.schema.changes"; + public static final Boolean SCHEMA_CHANGE_DEFAULT = true; private ServerIdRange serverIdRange; @@ -77,8 +79,8 @@ public MySqlSourceConfig create(int subtaskId) { // only DataStream API program need to emit the schema record, the Table API need not // Some scenarios do not require automatic capture of table structure changes, so the - // default setting is false. - props.setProperty("include.schema.changes", String.valueOf(false)); + // default setting is true. + props.setProperty(SCHEMA_CHANGE_KEY, SCHEMA_CHANGE_DEFAULT.toString()); // disable the offset flush totally props.setProperty("offset.flush.interval.ms", String.valueOf(Long.MAX_VALUE)); // disable tombstones @@ -116,6 +118,7 @@ public MySqlSourceConfig create(int subtaskId) { databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java index 1d2d9f9a033..9e5ee0f1670 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSource.java @@ -20,10 +20,11 @@ import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.SupportParallelism; +import org.apache.seatunnel.api.source.SupportSchemaEvolution; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.schema.SchemaChangeType; import org.apache.seatunnel.common.utils.JdbcUrlUtil; +import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.dialect.DataSourceDialect; @@ -32,6 +33,7 @@ import org.apache.seatunnel.connectors.cdc.base.option.StopMode; import org.apache.seatunnel.connectors.cdc.base.source.IncrementalSource; import org.apache.seatunnel.connectors.cdc.base.source.offset.OffsetFactory; +import org.apache.seatunnel.connectors.cdc.debezium.ConnectTableChangeSerializer; import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationSchema; import org.apache.seatunnel.connectors.cdc.debezium.DeserializeFormat; import org.apache.seatunnel.connectors.cdc.debezium.row.DebeziumJsonDeserializeSchema; @@ -40,18 +42,25 @@ import org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.offset.BinlogOffsetFactory; import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.JdbcCatalogOptions; +import org.apache.kafka.connect.data.Struct; + +import io.debezium.jdbc.JdbcConnection; +import io.debezium.relational.TableId; +import io.debezium.relational.history.TableChanges; + import java.time.ZoneId; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; public class MySqlIncrementalSource extends IncrementalSource - implements SupportParallelism { + implements SupportParallelism, SupportSchemaEvolution { static final String IDENTIFIER = "MySQL-CDC"; - public MySqlIncrementalSource( - ReadonlyConfig options, - SeaTunnelDataType dataType, - List catalogTables) { - super(options, dataType, catalogTables); + public MySqlIncrementalSource(ReadonlyConfig options, List catalogTables) { + super(options, catalogTables); } @Override @@ -88,20 +97,22 @@ public SourceConfig.Factory createSourceConfigFactory(Readonly @Override public DebeziumDeserializationSchema createDebeziumDeserializationSchema( ReadonlyConfig config) { + Map tableIdTableChangeMap = tableChanges(); + if (DeserializeFormat.COMPATIBLE_DEBEZIUM_JSON.equals( config.get(JdbcSourceOptions.FORMAT))) { return (DebeziumDeserializationSchema) new DebeziumJsonDeserializeSchema( - config.get(JdbcSourceOptions.DEBEZIUM_PROPERTIES)); + config.get(JdbcSourceOptions.DEBEZIUM_PROPERTIES), + tableIdTableChangeMap); } - SeaTunnelDataType physicalRowType = dataType; String zoneId = config.get(JdbcSourceOptions.SERVER_TIME_ZONE); return (DebeziumDeserializationSchema) SeaTunnelRowDebeziumDeserializeSchema.builder() - .setPhysicalRowType(physicalRowType) - .setResultTypeInfo(physicalRowType) + .setTables(catalogTables) .setServerTimeZone(ZoneId.of(zoneId)) + .setTableIdTableChangeMap(tableIdTableChangeMap) .setSchemaChangeResolver( new MySqlSchemaChangeResolver(createSourceConfigFactory(config))) .build(); @@ -117,4 +128,40 @@ public OffsetFactory createOffsetFactory(ReadonlyConfig config) { return new BinlogOffsetFactory( (MySqlSourceConfigFactory) configFactory, (MySqlDialect) dataSourceDialect); } + + private Map tableChanges() { + JdbcSourceConfig jdbcSourceConfig = configFactory.create(0); + MySqlDialect mySqlDialect = + new MySqlDialect((MySqlSourceConfigFactory) configFactory, catalogTables); + List discoverTables = mySqlDialect.discoverDataCollections(jdbcSourceConfig); + ConnectTableChangeSerializer connectTableChangeSerializer = + new ConnectTableChangeSerializer(); + try (JdbcConnection jdbcConnection = mySqlDialect.openJdbcConnection(jdbcSourceConfig)) { + return discoverTables.stream() + .collect( + Collectors.toMap( + Function.identity(), + (tableId) -> { + TableChanges tableChanges = new TableChanges(); + tableChanges.create( + mySqlDialect + .queryTableSchema(jdbcConnection, tableId) + .getTable()); + return connectTableChangeSerializer + .serialize(tableChanges) + .get(0); + })); + } catch (Exception e) { + throw new SeaTunnelException(e); + } + } + + @Override + public List supports() { + return Arrays.asList( + SchemaChangeType.ADD_COLUMN, + SchemaChangeType.DROP_COLUMN, + SchemaChangeType.RENAME_COLUMN, + SchemaChangeType.UPDATE_COLUMN); + } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java index 8147dfe737f..8de399b587a 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlIncrementalSourceFactory.java @@ -26,16 +26,15 @@ import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; -import org.apache.seatunnel.api.table.factory.TableSourceFactory; import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceTableConfig; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.StartupMode; import org.apache.seatunnel.connectors.cdc.base.option.StopMode; +import org.apache.seatunnel.connectors.cdc.base.source.BaseChangeStreamTableSourceFactory; import org.apache.seatunnel.connectors.cdc.base.utils.CatalogTableUtils; +import org.apache.seatunnel.connectors.seatunnel.cdc.mysql.config.MySqlSourceConfigFactory; import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.JdbcCatalogOptions; import com.google.auto.service.AutoService; @@ -45,7 +44,7 @@ import java.util.Optional; @AutoService(Factory.class) -public class MySqlIncrementalSourceFactory implements TableSourceFactory { +public class MySqlIncrementalSourceFactory extends BaseChangeStreamTableSourceFactory { @Override public String factoryIdentifier() { return MySqlIncrementalSource.IDENTIFIER; @@ -96,11 +95,27 @@ public Class getSourceClass() { @Override public - TableSource createSource(TableSourceFactoryContext context) { + TableSource restoreSource( + TableSourceFactoryContext context, List restoreTables) { return () -> { List catalogTables = CatalogTableUtil.getCatalogTables( context.getOptions(), context.getClassLoader()); + boolean enableSchemaChange = + context.getOptions() + .getOptional(SourceOptions.DEBEZIUM_PROPERTIES) + .map( + e -> + e.getOrDefault( + MySqlSourceConfigFactory.SCHEMA_CHANGE_KEY, + MySqlSourceConfigFactory.SCHEMA_CHANGE_DEFAULT + .toString())) + .map(Boolean::parseBoolean) + .orElse(MySqlSourceConfigFactory.SCHEMA_CHANGE_DEFAULT); + if (!restoreTables.isEmpty() && enableSchemaChange) { + catalogTables = mergeTableStruct(catalogTables, restoreTables); + } + Optional> tableConfigs = context.getOptions().getOptional(JdbcSourceOptions.TABLE_NAMES_CONFIG); if (tableConfigs.isPresent()) { @@ -110,10 +125,8 @@ TableSource createSource(TableSourceFactoryContext context) { tableConfigs.get(), text -> TablePath.of(text, false)); } - SeaTunnelDataType dataType = - CatalogTableUtil.convertToMultipleRowType(catalogTables); return (SeaTunnelSource) - new MySqlIncrementalSource<>(context.getOptions(), dataType, catalogTables); + new MySqlIncrementalSource<>(context.getOptions(), catalogTables); }; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlSchemaChangeResolver.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlSchemaChangeResolver.java index 3ea4a0dfcea..28f9b508b51 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlSchemaChangeResolver.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/MySqlSchemaChangeResolver.java @@ -17,64 +17,37 @@ package org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source; -import org.apache.seatunnel.api.table.catalog.TableIdentifier; import org.apache.seatunnel.api.table.catalog.TablePath; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnsEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.schema.AbstractSchemaChangeResolver; -import org.apache.seatunnel.connectors.cdc.base.utils.SourceRecordUtils; import org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.parser.CustomMySqlAntlrDdlParser; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; -import org.apache.commons.lang3.StringUtils; -import org.apache.kafka.connect.source.SourceRecord; - -import io.debezium.relational.Tables; +import io.debezium.relational.ddl.DdlParser; import java.util.List; -import java.util.Objects; public class MySqlSchemaChangeResolver extends AbstractSchemaChangeResolver { - private transient Tables tables; - private transient CustomMySqlAntlrDdlParser customMySqlAntlrDdlParser; public MySqlSchemaChangeResolver(SourceConfig.Factory sourceConfigFactory) { super(sourceConfigFactory.create(0)); } @Override - public SchemaChangeEvent resolve(SourceRecord record, SeaTunnelDataType dataType) { - TablePath tablePath = SourceRecordUtils.getTablePath(record); - String ddl = SourceRecordUtils.getDdl(record); - if (Objects.isNull(customMySqlAntlrDdlParser)) { - this.customMySqlAntlrDdlParser = - new CustomMySqlAntlrDdlParser( - tablePath, this.jdbcSourceConfig.getDbzConnectorConfig()); - } - if (Objects.isNull(tables)) { - this.tables = new Tables(); - } - customMySqlAntlrDdlParser.setCurrentDatabase(tablePath.getDatabaseName()); - customMySqlAntlrDdlParser.setCurrentSchema(tablePath.getSchemaName()); - // Parse DDL statement using Debezium's Antlr parser - customMySqlAntlrDdlParser.parse(ddl, tables); - List parsedEvents = - customMySqlAntlrDdlParser.getAndClearParsedEvents(); - parsedEvents.forEach(e -> e.setSourceDialectName(DatabaseIdentifier.MYSQL)); - AlterTableColumnsEvent alterTableColumnsEvent = - new AlterTableColumnsEvent( - TableIdentifier.of( - StringUtils.EMPTY, - tablePath.getDatabaseName(), - tablePath.getSchemaName(), - tablePath.getTableName()), - parsedEvents); - alterTableColumnsEvent.setStatement(ddl); - alterTableColumnsEvent.setSourceDialectName(DatabaseIdentifier.MYSQL); - return parsedEvents.isEmpty() ? null : alterTableColumnsEvent; + protected DdlParser createDdlParser(TablePath tablePath) { + return new CustomMySqlAntlrDdlParser( + tablePath, this.jdbcSourceConfig.getDbzConnectorConfig()); + } + + @Override + protected List getAndClearParsedEvents() { + return ((CustomMySqlAntlrDdlParser) ddlParser).getAndClearParsedEvents(); + } + + @Override + protected String getSourceDialectName() { + return DatabaseIdentifier.MYSQL; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomAlterTableParserListener.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomAlterTableParserListener.java index bf36d7831ee..c315fa36963 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomAlterTableParserListener.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomAlterTableParserListener.java @@ -18,11 +18,12 @@ package org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.parser; import org.apache.seatunnel.api.table.catalog.TableIdentifier; -import org.apache.seatunnel.api.table.event.AlterTableAddColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableChangeColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableDropColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.connectors.cdc.base.source.parser.SeatunnelDDLParser; import org.apache.seatunnel.connectors.seatunnel.cdc.mysql.utils.MySqlTypeUtils; import org.apache.commons.lang3.StringUtils; @@ -40,7 +41,8 @@ import java.util.LinkedList; import java.util.List; -public class CustomAlterTableParserListener extends MySqlParserBaseListener { +public class CustomAlterTableParserListener extends MySqlParserBaseListener + implements SeatunnelDDLParser { private static final int STARTING_INDEX = 1; private final MySqlAntlrDdlParser parser; private final List listeners; @@ -95,9 +97,7 @@ public void exitAlterByAddColumn(MySqlParser.AlterByAddColumnContext ctx) { () -> { Column column = columnDefinitionListener.getColumn(); org.apache.seatunnel.api.table.catalog.Column seatunnelColumn = - toSeatunnelColumn(column); - String sourceColumnType = getSourceColumnType(column); - seatunnelColumn = seatunnelColumn.reSourceType(sourceColumnType); + toSeatunnelColumnWithFullTypeInfo(column); if (ctx.FIRST() != null) { AlterTableAddColumnEvent alterTableAddColumnEvent = AlterTableAddColumnEvent.addFirst(tableIdentifier, seatunnelColumn); @@ -153,9 +153,7 @@ public void exitAlterByModifyColumn(MySqlParser.AlterByModifyColumnContext ctx) () -> { Column column = columnDefinitionListener.getColumn(); org.apache.seatunnel.api.table.catalog.Column seatunnelColumn = - toSeatunnelColumn(column); - String sourceColumnType = getSourceColumnType(column); - seatunnelColumn = seatunnelColumn.reSourceType(sourceColumnType); + toSeatunnelColumnWithFullTypeInfo(column); if (ctx.FIRST() != null) { AlterTableModifyColumnEvent alterTableModifyColumnEvent = AlterTableModifyColumnEvent.modifyFirst( @@ -197,9 +195,7 @@ public void exitAlterByChangeColumn(MySqlParser.AlterByChangeColumnContext ctx) () -> { Column column = columnDefinitionListener.getColumn(); org.apache.seatunnel.api.table.catalog.Column seatunnelColumn = - toSeatunnelColumn(column); - String sourceColumnType = getSourceColumnType(column); - seatunnelColumn = seatunnelColumn.reSourceType(sourceColumnType); + toSeatunnelColumnWithFullTypeInfo(column); String oldColumnName = column.name(); String newColumnName = parser.parseName(ctx.newColumn); seatunnelColumn = seatunnelColumn.rename(newColumnName); @@ -223,24 +219,8 @@ public void enterAlterByDropColumn(MySqlParser.AlterByDropColumnContext ctx) { super.enterAlterByDropColumn(ctx); } - private org.apache.seatunnel.api.table.catalog.Column toSeatunnelColumn(Column column) { + @Override + public org.apache.seatunnel.api.table.catalog.Column toSeatunnelColumn(Column column) { return MySqlTypeUtils.convertToSeaTunnelColumn(column, dbzConnectorConfig); } - - private TableIdentifier toTableIdentifier(TableId tableId) { - return new TableIdentifier("", tableId.catalog(), tableId.schema(), tableId.table()); - } - - private String getSourceColumnType(Column column) { - StringBuilder sb = new StringBuilder(column.typeName()); - if (column.length() >= 0) { - sb.append('(').append(column.length()); - if (column.scale().isPresent()) { - sb.append(", ").append(column.scale().get()); - } - - sb.append(')'); - } - return sb.toString(); - } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParser.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParser.java index 00d75c986a3..7d957280e68 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParser.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParser.java @@ -17,10 +17,11 @@ package org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.parser; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.TablePath; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; -import com.google.common.collect.Lists; import io.debezium.antlr.AntlrDdlParserListener; import io.debezium.antlr.DataTypeResolver; import io.debezium.connector.mysql.antlr.MySqlAntlrDdlParser; diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParserListener.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParserListener.java index 4ea5ec70560..c8be8460a3c 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParserListener.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/parser/CustomMySqlAntlrDdlParserListener.java @@ -17,7 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.cdc.mysql.source.parser; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java index a67bc30dcc3..e2417872385 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/source/reader/fetch/MySqlSourceFetchTaskContext.java @@ -33,6 +33,7 @@ import org.apache.seatunnel.connectors.seatunnel.cdc.mysql.utils.MySqlConnectionUtils; import org.apache.seatunnel.connectors.seatunnel.cdc.mysql.utils.MySqlUtils; +import org.apache.kafka.connect.data.SchemaAndValue; import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.source.SourceRecord; @@ -73,6 +74,8 @@ import java.sql.SQLException; import java.time.Instant; import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -325,7 +328,29 @@ private void registerDatabaseHistory(SourceSplitBase sourceSplitBase) { dataSourceDialect.queryTableSchema(connection, snapshotSplit.getTableId())); } else { IncrementalSplit incrementalSplit = (IncrementalSplit) sourceSplitBase; + Map historyTableChanges = incrementalSplit.getHistoryTableChanges(); for (TableId tableId : incrementalSplit.getTableIds()) { + if (historyTableChanges != null && historyTableChanges.containsKey(tableId)) { + SchemaAndValue schemaAndValue = + jsonConverter.toConnectData("topic", historyTableChanges.get(tableId)); + Struct deserializedStruct = (Struct) schemaAndValue.value(); + + TableChanges tableChanges = + tableChangeSerializer.deserialize( + Collections.singletonList(deserializedStruct), false); + + Iterator iterator = tableChanges.iterator(); + TableChanges.TableChange tableChange = null; + while (iterator.hasNext()) { + if (tableChange != null) { + throw new IllegalStateException( + "The table changes should only have one element"); + } + tableChange = iterator.next(); + } + engineHistory.add(tableChange); + continue; + } engineHistory.add(dataSourceDialect.queryTableSchema(connection, tableId)); } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/utils/MySqlTypeUtils.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/utils/MySqlTypeUtils.java index 22f9514c6f3..fd85258eb3c 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/utils/MySqlTypeUtils.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-mysql/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/mysql/utils/MySqlTypeUtils.java @@ -30,7 +30,6 @@ import io.debezium.relational.RelationalDatabaseConnectorConfig; import lombok.extern.slf4j.Slf4j; -import java.util.Objects; import java.util.Optional; /** Utilities for converting from MySQL types to SeaTunnel types. */ @@ -70,7 +69,6 @@ public static org.apache.seatunnel.api.table.catalog.Column convertToSeaTunnelCo Optional defaultValueExpression = column.defaultValueExpression(); Object defaultValue = defaultValueExpression.orElse(null); if (defaultValueExpression.isPresent() - && Objects.nonNull(defaultValue) && !MysqlDefaultValueUtils.isSpecialDefaultValue(defaultValue)) { defaultValue = mySqlDefaultValueConverter @@ -82,11 +80,14 @@ public static org.apache.seatunnel.api.table.catalog.Column convertToSeaTunnelCo .name(column.name()) .columnType(column.typeName()) .dataType(column.typeName()) - .length((long) column.length()) - .precision((long) column.length()) .scale(column.scale().orElse(0)) .nullable(column.isOptional()) .defaultValue(defaultValue); + + if (column.length() >= 0) { + builder.length((long) column.length()).precision((long) column.length()); + } + switch (column.typeName().toUpperCase()) { case MySqlTypeConverter.MYSQL_CHAR: case MySqlTypeConverter.MYSQL_VARCHAR: diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-opengauss/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/opengauss/OpengaussIncrementalSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-opengauss/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/opengauss/OpengaussIncrementalSourceFactory.java index e9f552db6c0..6b88b352dbf 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-opengauss/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/opengauss/OpengaussIncrementalSourceFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-opengauss/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/opengauss/OpengaussIncrementalSourceFactory.java @@ -28,8 +28,6 @@ import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceTableConfig; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.StartupMode; @@ -101,10 +99,8 @@ TableSource createSource(TableSourceFactoryContext context) { CatalogTableUtils.mergeCatalogTableConfig( catalogTables, tableConfigs.get(), s -> TablePath.of(s, true)); } - SeaTunnelDataType dataType = - CatalogTableUtil.convertToMultipleRowType(catalogTables); return (SeaTunnelSource) - new PostgresIncrementalSource<>(context.getOptions(), dataType, catalogTables); + new PostgresIncrementalSource<>(context.getOptions(), catalogTables); }; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfig.java index 32bcb41f78f..263bb38d884 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfig.java @@ -27,6 +27,7 @@ import lombok.Getter; import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -49,6 +50,7 @@ public OracleSourceConfig( List databaseList, List tableList, int splitSize, + Map splitColumn, double distributionFactorUpper, double distributionFactorLower, int sampleShardingThreshold, @@ -72,6 +74,7 @@ public OracleSourceConfig( databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfigFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfigFactory.java index d6018083c29..b08d4e4dad7 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfigFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/config/OracleSourceConfigFactory.java @@ -27,7 +27,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** A factory to initialize {@link OracleSourceConfig}. */ public class OracleSourceConfigFactory extends JdbcSourceConfigFactory { @@ -36,6 +36,8 @@ public class OracleSourceConfigFactory extends JdbcSourceConfigFactory { private static final String DATABASE_SERVER_NAME = "oracle_logminer"; private static final String DRIVER_CLASS_NAME = "oracle.jdbc.driver.OracleDriver"; + public static final String SCHEMA_CHANGE_KEY = "include.schema.changes"; + public static final Boolean SCHEMA_CHANGE_DEFAULT = true; private List schemaList; @@ -92,6 +94,10 @@ public OracleSourceConfig create(int subtask) { props.setProperty("database.history.skip.unparseable.ddl", String.valueOf(true)); props.setProperty("database.history.refer.ddl", String.valueOf(true)); + // Some scenarios do not require automatic capture of table structure changes, so the + // default setting is true. + props.setProperty(SCHEMA_CHANGE_KEY, SCHEMA_CHANGE_DEFAULT.toString()); + props.setProperty("connect.timeout.ms", String.valueOf(connectTimeoutMillis)); // disable tombstones props.setProperty("tombstones.on.delete", String.valueOf(false)); @@ -144,6 +150,7 @@ public OracleSourceConfig create(int subtask) { databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java index a1bbd0cb25c..80b4a0b3c00 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSource.java @@ -20,18 +20,20 @@ import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.SupportParallelism; +import org.apache.seatunnel.api.source.SupportSchemaEvolution; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.schema.SchemaChangeType; import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; import org.apache.seatunnel.connectors.cdc.base.dialect.DataSourceDialect; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; +import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.StartupMode; import org.apache.seatunnel.connectors.cdc.base.option.StopMode; import org.apache.seatunnel.connectors.cdc.base.source.IncrementalSource; import org.apache.seatunnel.connectors.cdc.base.source.offset.OffsetFactory; +import org.apache.seatunnel.connectors.cdc.debezium.ConnectTableChangeSerializer; import org.apache.seatunnel.connectors.cdc.debezium.DebeziumDeserializationSchema; import org.apache.seatunnel.connectors.cdc.debezium.DeserializeFormat; import org.apache.seatunnel.connectors.cdc.debezium.row.DebeziumJsonDeserializeSchema; @@ -44,26 +46,22 @@ import io.debezium.jdbc.JdbcConnection; import io.debezium.relational.TableId; -import io.debezium.relational.history.ConnectTableChangeSerializer; import io.debezium.relational.history.TableChanges; -import io.debezium.util.SchemaNameAdjuster; import java.time.ZoneId; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; public class OracleIncrementalSource extends IncrementalSource - implements SupportParallelism { + implements SupportParallelism, SupportSchemaEvolution { static final String IDENTIFIER = "Oracle-CDC"; - public OracleIncrementalSource( - ReadonlyConfig options, - SeaTunnelDataType dataType, - List catalogTables) { - super(options, dataType, catalogTables); + public OracleIncrementalSource(ReadonlyConfig options, List catalogTables) { + super(options, catalogTables); } @Override @@ -98,22 +96,32 @@ public SourceConfig.Factory createSourceConfigFactory(Readonly @Override public DebeziumDeserializationSchema createDebeziumDeserializationSchema( ReadonlyConfig config) { - // todo:table metadata change reservation Map tableIdStructMap = tableChanges(); + Map debeziumProperties = config.get(SourceOptions.DEBEZIUM_PROPERTIES); if (DeserializeFormat.COMPATIBLE_DEBEZIUM_JSON.equals( config.get(JdbcSourceOptions.FORMAT))) { return (DebeziumDeserializationSchema) - new DebeziumJsonDeserializeSchema( - config.get(JdbcSourceOptions.DEBEZIUM_PROPERTIES)); + new DebeziumJsonDeserializeSchema(debeziumProperties, tableIdStructMap); } - SeaTunnelDataType physicalRowType = dataType; String zoneId = config.get(JdbcSourceOptions.SERVER_TIME_ZONE); + + boolean enableDDL = + Boolean.parseBoolean( + debeziumProperties.getOrDefault( + OracleSourceConfigFactory.SCHEMA_CHANGE_KEY, + OracleSourceConfigFactory.SCHEMA_CHANGE_DEFAULT.toString())); + return (DebeziumDeserializationSchema) SeaTunnelRowDebeziumDeserializeSchema.builder() - .setPhysicalRowType(physicalRowType) - .setResultTypeInfo(physicalRowType) + .setTables(catalogTables) .setServerTimeZone(ZoneId.of(zoneId)) + .setSchemaChangeResolver( + enableDDL + ? new OracleSchemaChangeResolver( + createSourceConfigFactory(config)) + : null) + .setTableIdTableChangeMap(tableIdStructMap) .build(); } @@ -133,9 +141,8 @@ private Map tableChanges() { OracleDialect dialect = new OracleDialect((OracleSourceConfigFactory) configFactory, catalogTables); List discoverTables = dialect.discoverDataCollections(jdbcSourceConfig); - SchemaNameAdjuster schemaNameAdjuster = SchemaNameAdjuster.create(); ConnectTableChangeSerializer connectTableChangeSerializer = - new ConnectTableChangeSerializer(schemaNameAdjuster); + new ConnectTableChangeSerializer(); try (JdbcConnection jdbcConnection = dialect.openJdbcConnection(jdbcSourceConfig)) { return discoverTables.stream() .collect( @@ -154,4 +161,13 @@ private Map tableChanges() { throw new SeaTunnelException(e); } } + + @Override + public List supports() { + return Arrays.asList( + SchemaChangeType.ADD_COLUMN, + SchemaChangeType.DROP_COLUMN, + SchemaChangeType.RENAME_COLUMN, + SchemaChangeType.UPDATE_COLUMN); + } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java index 21e08c2af7f..d790107cf1f 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleIncrementalSourceFactory.java @@ -26,16 +26,15 @@ import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; -import org.apache.seatunnel.api.table.factory.TableSourceFactory; import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceTableConfig; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.StartupMode; import org.apache.seatunnel.connectors.cdc.base.option.StopMode; +import org.apache.seatunnel.connectors.cdc.base.source.BaseChangeStreamTableSourceFactory; import org.apache.seatunnel.connectors.cdc.base.utils.CatalogTableUtils; +import org.apache.seatunnel.connectors.seatunnel.cdc.oracle.config.OracleSourceConfigFactory; import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.JdbcCatalogOptions; import com.google.auto.service.AutoService; @@ -45,7 +44,7 @@ import java.util.Optional; @AutoService(Factory.class) -public class OracleIncrementalSourceFactory implements TableSourceFactory { +public class OracleIncrementalSourceFactory extends BaseChangeStreamTableSourceFactory { @Override public String factoryIdentifier() { return OracleIncrementalSource.IDENTIFIER; @@ -102,11 +101,27 @@ public Class getSourceClass() { @Override public - TableSource createSource(TableSourceFactoryContext context) { + TableSource restoreSource( + TableSourceFactoryContext context, List restoreTables) { return () -> { List catalogTables = CatalogTableUtil.getCatalogTables( context.getOptions(), context.getClassLoader()); + boolean enableSchemaChange = + context.getOptions() + .getOptional(SourceOptions.DEBEZIUM_PROPERTIES) + .map( + e -> + e.getOrDefault( + OracleSourceConfigFactory.SCHEMA_CHANGE_KEY, + OracleSourceConfigFactory.SCHEMA_CHANGE_DEFAULT + .toString())) + .map(Boolean::parseBoolean) + .orElse(OracleSourceConfigFactory.SCHEMA_CHANGE_DEFAULT); + if (!restoreTables.isEmpty() && enableSchemaChange) { + catalogTables = mergeTableStruct(catalogTables, restoreTables); + } + Optional> tableConfigs = context.getOptions().getOptional(JdbcSourceOptions.TABLE_NAMES_CONFIG); if (tableConfigs.isPresent()) { @@ -114,9 +129,7 @@ TableSource createSource(TableSourceFactoryContext context) { CatalogTableUtils.mergeCatalogTableConfig( catalogTables, tableConfigs.get(), s -> TablePath.of(s, true)); } - SeaTunnelDataType dataType = - CatalogTableUtil.convertToMultipleRowType(catalogTables); - return new OracleIncrementalSource(context.getOptions(), dataType, catalogTables); + return new OracleIncrementalSource(context.getOptions(), catalogTables); }; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleSchemaChangeResolver.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleSchemaChangeResolver.java new file mode 100644 index 00000000000..5be13ffc391 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/OracleSchemaChangeResolver.java @@ -0,0 +1,51 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source; + +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; +import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; +import org.apache.seatunnel.connectors.cdc.base.schema.AbstractSchemaChangeResolver; +import org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser.CustomOracleAntlrDdlParser; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; + +import io.debezium.relational.ddl.DdlParser; + +import java.util.List; + +public class OracleSchemaChangeResolver extends AbstractSchemaChangeResolver { + public OracleSchemaChangeResolver(SourceConfig.Factory sourceConfigFactory) { + super(sourceConfigFactory.create(0)); + } + + @Override + protected DdlParser createDdlParser(TablePath tablePath) { + return new CustomOracleAntlrDdlParser(tablePath); + } + + @Override + protected List getAndClearParsedEvents() { + return ((CustomOracleAntlrDdlParser) ddlParser).getAndClearParsedEvents(); + } + + @Override + protected String getSourceDialectName() { + return DatabaseIdentifier.ORACLE; + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/BaseParserListener.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/BaseParserListener.java new file mode 100644 index 00000000000..cba04a72625 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/BaseParserListener.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser; + +import io.debezium.ddl.parser.oracle.generated.PlSqlParser; +import io.debezium.ddl.parser.oracle.generated.PlSqlParserBaseListener; + +public class BaseParserListener extends PlSqlParserBaseListener { + + /** + * Resolves a table or column name from the provided string. + * + *

Oracle table and column names are inherently stored in upper-case; however, if the objects + * are created using double-quotes, the case of the object name is retained. Therefore when + * needing to parse a table or column name, this method will adhere to those rules and will + * always return the name in upper-case unless the provided name is double-quoted in which the + * returned value will have the double-quotes removed and case retained. + * + * @param name table or column name + * @return parsed table or column name from the supplied name argument + */ + private static String getTableOrColumnName(String name) { + return removeQuotes(name, true); + } + + /** + * Removes leading and trailing double quote characters from the provided string. + * + * @param text value to have double quotes removed + * @param upperCaseIfNotQuoted control if returned string is upper-cased if not quoted + * @return string that has had quotes removed + */ + @SuppressWarnings("SameParameterValue") + private static String removeQuotes(String text, boolean upperCaseIfNotQuoted) { + if (text != null && text.length() > 2 && text.startsWith("\"") && text.endsWith("\"")) { + return text.substring(1, text.length() - 1); + } + return (upperCaseIfNotQuoted && text != null) ? text.toUpperCase() : text; + } + + String getColumnName(final PlSqlParser.Column_nameContext ctx) { + final String columnName; + if (ctx.id_expression() != null && !ctx.id_expression().isEmpty()) { + columnName = + getTableOrColumnName( + ctx.id_expression(ctx.id_expression().size() - 1).getText()); + } else { + columnName = getTableOrColumnName(ctx.identifier().id_expression().getText()); + } + return columnName; + } + + String getColumnName(final PlSqlParser.Old_column_nameContext ctx) { + return getTableOrColumnName(ctx.getText()); + } + + String getColumnName(final PlSqlParser.New_column_nameContext ctx) { + return getTableOrColumnName(ctx.getText()); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomAlterTableParserListener.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomAlterTableParserListener.java new file mode 100644 index 00000000000..03a3b427f5c --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomAlterTableParserListener.java @@ -0,0 +1,231 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser; + +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.connectors.cdc.base.source.parser.SeatunnelDDLParser; +import org.apache.seatunnel.connectors.seatunnel.cdc.oracle.utils.OracleTypeUtils; + +import org.apache.commons.lang3.StringUtils; + +import org.antlr.v4.runtime.tree.ParseTreeListener; + +import io.debezium.ddl.parser.oracle.generated.PlSqlParser; +import io.debezium.relational.Column; +import io.debezium.relational.ColumnEditor; +import io.debezium.relational.TableId; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +@Slf4j +public class CustomAlterTableParserListener extends BaseParserListener + implements SeatunnelDDLParser { + + private static final int STARTING_INDEX = 0; + private CustomOracleAntlrDdlParser parser; + private final List listeners; + private CustomColumnDefinitionParserListener columnDefinitionListener; + private List columnEditors; + private int parsingColumnIndex = STARTING_INDEX; + + private final LinkedList changes; + private TableIdentifier tableIdentifier; + + public CustomAlterTableParserListener( + CustomOracleAntlrDdlParser parser, + List listeners, + LinkedList changes) { + this.parser = parser; + this.listeners = listeners; + this.changes = changes; + } + + @Override + public void enterAlter_table(PlSqlParser.Alter_tableContext ctx) { + TableId tableId = this.parser.parseQualifiedTableId(); + this.tableIdentifier = toTableIdentifier(tableId); + super.enterAlter_table(ctx); + } + + @Override + public void exitAlter_table(PlSqlParser.Alter_tableContext ctx) { + listeners.remove(columnDefinitionListener); + super.exitAlter_table(ctx); + } + + @Override + public void enterAdd_column_clause(PlSqlParser.Add_column_clauseContext ctx) { + List columns = ctx.column_definition(); + columnEditors = new ArrayList<>(columns.size()); + for (PlSqlParser.Column_definitionContext column : columns) { + String columnName = getColumnName(column.column_name()); + ColumnEditor editor = Column.editor().name(columnName); + columnEditors.add(editor); + } + columnDefinitionListener = new CustomColumnDefinitionParserListener(); + listeners.add(columnDefinitionListener); + super.enterAdd_column_clause(ctx); + } + + @Override + public void exitAdd_column_clause(PlSqlParser.Add_column_clauseContext ctx) { + columnEditors.forEach( + columnEditor -> { + Column column = columnEditor.create(); + org.apache.seatunnel.api.table.catalog.Column seaTunnelColumn = + toSeatunnelColumnWithFullTypeInfo(column); + AlterTableAddColumnEvent addEvent = + AlterTableAddColumnEvent.add(tableIdentifier, seaTunnelColumn); + changes.add(addEvent); + }); + listeners.remove(columnDefinitionListener); + columnDefinitionListener = null; + super.exitAdd_column_clause(ctx); + } + + @Override + public void enterModify_column_clauses(PlSqlParser.Modify_column_clausesContext ctx) { + List columns = ctx.modify_col_properties(); + columnEditors = new ArrayList<>(columns.size()); + for (PlSqlParser.Modify_col_propertiesContext column : columns) { + String columnName = getColumnName(column.column_name()); + ColumnEditor editor = Column.editor().name(columnName); + columnEditors.add(editor); + } + columnDefinitionListener = new CustomColumnDefinitionParserListener(); + listeners.add(columnDefinitionListener); + super.enterModify_column_clauses(ctx); + } + + @Override + public void exitModify_column_clauses(PlSqlParser.Modify_column_clausesContext ctx) { + parser.runIfNotNull( + () -> { + Column column = columnDefinitionListener.getColumn(); + org.apache.seatunnel.api.table.catalog.Column seaTunnelColumn = + toSeatunnelColumnWithFullTypeInfo(column); + AlterTableModifyColumnEvent alterTableModifyColumnEvent = + AlterTableModifyColumnEvent.modify(tableIdentifier, seaTunnelColumn); + changes.add(alterTableModifyColumnEvent); + listeners.remove(columnDefinitionListener); + columnDefinitionListener = null; + super.exitModify_column_clauses(ctx); + }, + columnDefinitionListener); + } + + @Override + public void enterModify_col_properties(PlSqlParser.Modify_col_propertiesContext ctx) { + parser.runIfNotNull( + () -> { + // column editor list is not null when a multiple columns are parsed in one + // statement + if (columnEditors.size() > parsingColumnIndex) { + // assign next column editor to parse another column definition + columnDefinitionListener.setColumnEditor( + columnEditors.get(parsingColumnIndex++)); + } + }, + columnEditors); + super.enterModify_col_properties(ctx); + } + + @Override + public void exitModify_col_properties(PlSqlParser.Modify_col_propertiesContext ctx) { + parser.runIfNotNull( + () -> { + if (columnEditors.size() == parsingColumnIndex) { + // all columns parsed + // reset global variables for next parsed statement + parsingColumnIndex = STARTING_INDEX; + } + }, + columnEditors); + super.exitModify_col_properties(ctx); + } + + @Override + public void enterColumn_definition(PlSqlParser.Column_definitionContext ctx) { + parser.runIfNotNull( + () -> { + // column editor list is not null when a multiple columns are parsed in one + // statement + if (columnEditors.size() > parsingColumnIndex) { + // assign next column editor to parse another column definition + columnDefinitionListener.setColumnEditor( + columnEditors.get(parsingColumnIndex++)); + } + }, + columnEditors); + } + + @Override + public void exitColumn_definition(PlSqlParser.Column_definitionContext ctx) { + parser.runIfNotNull( + () -> { + if (columnEditors.size() == parsingColumnIndex) { + // all columns parsed + // reset global variables for next parsed statement + parsingColumnIndex = STARTING_INDEX; + } + }, + columnEditors); + super.exitColumn_definition(ctx); + } + + @Override + public void enterDrop_column_clause(PlSqlParser.Drop_column_clauseContext ctx) { + List columnNameContexts = ctx.column_name(); + columnEditors = new ArrayList<>(columnNameContexts.size()); + for (PlSqlParser.Column_nameContext columnNameContext : columnNameContexts) { + String columnName = getColumnName(columnNameContext); + AlterTableDropColumnEvent alterTableDropColumnEvent = + new AlterTableDropColumnEvent(tableIdentifier, columnName); + changes.add(alterTableDropColumnEvent); + } + super.enterDrop_column_clause(ctx); + } + + @Override + public void enterRename_column_clause(PlSqlParser.Rename_column_clauseContext ctx) { + String oldColumnName = getColumnName(ctx.old_column_name()); + String newColumnName = getColumnName(ctx.new_column_name()); + PhysicalColumn newColumn = PhysicalColumn.builder().name(newColumnName).build(); + AlterTableChangeColumnEvent alterTableChangeColumnEvent = + AlterTableChangeColumnEvent.change(tableIdentifier, oldColumnName, newColumn); + if (StringUtils.isNotBlank(newColumnName) + && !StringUtils.equals(oldColumnName, newColumnName)) { + changes.add(alterTableChangeColumnEvent); + } + super.enterRename_column_clause(ctx); + } + + @Override + public org.apache.seatunnel.api.table.catalog.Column toSeatunnelColumn(Column column) { + return OracleTypeUtils.convertToSeaTunnelColumn(column); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomColumnDefinitionParserListener.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomColumnDefinitionParserListener.java new file mode 100644 index 00000000000..12e02dbd281 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomColumnDefinitionParserListener.java @@ -0,0 +1,281 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser; + +import io.debezium.ddl.parser.oracle.generated.PlSqlParser; +import io.debezium.relational.Column; +import io.debezium.relational.ColumnEditor; +import lombok.Getter; +import lombok.Setter; +import oracle.jdbc.OracleTypes; + +import java.sql.Types; + +@Getter +@Setter +public class CustomColumnDefinitionParserListener extends BaseParserListener { + private ColumnEditor columnEditor; + + public CustomColumnDefinitionParserListener() {} + + @Override + public void enterColumn_definition(PlSqlParser.Column_definitionContext ctx) { + if (columnEditor != null) { + resolveColumnDataType(ctx); + if (ctx.DEFAULT() != null) { + this.columnEditor.defaultValueExpression(ctx.column_default_value().getText()); + } + } + super.enterColumn_definition(ctx); + } + + @Override + public void enterModify_col_properties(PlSqlParser.Modify_col_propertiesContext ctx) { + if (columnEditor != null) { + resolveColumnDataType(ctx); + if (ctx.DEFAULT() != null) { + columnEditor.defaultValueExpression(ctx.column_default_value().getText()); + } + } + super.enterModify_col_properties(ctx); + } + + // todo use dataTypeResolver instead + private void resolveColumnDataType(PlSqlParser.Column_definitionContext ctx) { + columnEditor.name(getColumnName(ctx.column_name())); + + boolean hasNotNullConstraint = + ctx.inline_constraint().stream().anyMatch(c -> c.NOT() != null); + columnEditor.optional(!hasNotNullConstraint); + + if (ctx.datatype() == null) { + if (ctx.type_name() != null + && "MDSYS.SDO_GEOMETRY" + .equalsIgnoreCase(ctx.type_name().getText().replace("\"", ""))) { + columnEditor.jdbcType(Types.STRUCT).type("MDSYS.SDO_GEOMETRY"); + } + } else { + resolveColumnDataType(ctx.datatype()); + } + } + + private void resolveColumnDataType(PlSqlParser.Modify_col_propertiesContext ctx) { + columnEditor.name(getColumnName(ctx.column_name())); + + resolveColumnDataType(ctx.datatype()); + + boolean hasNullConstraint = + ctx.inline_constraint().stream().anyMatch(c -> c.NULL_() != null); + boolean hasNotNullConstraint = + ctx.inline_constraint().stream().anyMatch(c -> c.NOT() != null); + if (hasNotNullConstraint && columnEditor.isOptional()) { + columnEditor.optional(false); + } else if (hasNullConstraint && !columnEditor.isOptional()) { + columnEditor.optional(true); + } + } + + private void resolveColumnDataType(PlSqlParser.DatatypeContext ctx) { + // If the context is null, there is nothing this method can resolve and it is safe to return + if (ctx == null) { + return; + } + + if (ctx.native_datatype_element() != null) { + PlSqlParser.Precision_partContext precisionPart = ctx.precision_part(); + if (ctx.native_datatype_element().INT() != null + || ctx.native_datatype_element().INTEGER() != null + || ctx.native_datatype_element().SMALLINT() != null + || ctx.native_datatype_element().NUMERIC() != null + || ctx.native_datatype_element().DECIMAL() != null) { + // NUMERIC and DECIMAL types have by default zero scale + columnEditor.jdbcType(Types.NUMERIC).type("NUMBER"); + + if (precisionPart != null) { + setPrecision(precisionPart, columnEditor); + setScale(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().DATE() != null) { + // JDBC driver reports type as timestamp but name DATE + columnEditor.jdbcType(Types.TIMESTAMP).type("DATE"); + } else if (ctx.native_datatype_element().TIMESTAMP() != null) { + if (ctx.WITH() != null && ctx.TIME() != null && ctx.ZONE() != null) { + if (ctx.LOCAL() != null) { + columnEditor + .jdbcType(OracleTypes.TIMESTAMPLTZ) + .type("TIMESTAMP WITH LOCAL TIME ZONE"); + } else { + columnEditor + .jdbcType(OracleTypes.TIMESTAMPTZ) + .type("TIMESTAMP WITH TIME ZONE"); + } + } else { + columnEditor.jdbcType(Types.TIMESTAMP).type("TIMESTAMP"); + } + + if (precisionPart == null) { + columnEditor.length(6); + } else { + setPrecision(precisionPart, columnEditor); + } + } + // VARCHAR is the same as VARCHAR2 in Oracle + else if (ctx.native_datatype_element().VARCHAR2() != null + || ctx.native_datatype_element().VARCHAR() != null) { + columnEditor.jdbcType(Types.VARCHAR).type("VARCHAR2"); + + if (precisionPart == null) { + columnEditor.length(getVarCharDefaultLength()); + } else { + setPrecision(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().NVARCHAR2() != null) { + columnEditor.jdbcType(Types.NVARCHAR).type("NVARCHAR2"); + + if (precisionPart == null) { + columnEditor.length(getVarCharDefaultLength()); + } else { + setPrecision(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().CHAR() != null) { + columnEditor.jdbcType(Types.CHAR).type("CHAR").length(1); + + if (precisionPart != null) { + setPrecision(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().NCHAR() != null) { + columnEditor.jdbcType(Types.NCHAR).type("NCHAR").length(1); + + if (precisionPart != null) { + setPrecision(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().BINARY_FLOAT() != null) { + columnEditor.jdbcType(OracleTypes.BINARY_FLOAT).type("BINARY_FLOAT"); + } else if (ctx.native_datatype_element().BINARY_DOUBLE() != null) { + columnEditor.jdbcType(OracleTypes.BINARY_DOUBLE).type("BINARY_DOUBLE"); + } + // PRECISION keyword is mandatory + else if (ctx.native_datatype_element().FLOAT() != null + || (ctx.native_datatype_element().DOUBLE() != null + && ctx.native_datatype_element().PRECISION() != null)) { + columnEditor.jdbcType(Types.FLOAT).type("FLOAT"); + + // TODO float's precision is about bits not decimal digits; should be ok for now to + // over-size + if (precisionPart != null) { + setPrecision(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().REAL() != null) { + columnEditor + .jdbcType(Types.FLOAT) + .type("FLOAT") + // TODO float's precision is about bits not decimal digits; should be ok for + // now to over-size + .length(63); + } else if (ctx.native_datatype_element().NUMBER() != null) { + columnEditor.jdbcType(Types.NUMERIC).type("NUMBER"); + + if (precisionPart != null) { + if (precisionPart.ASTERISK() != null) { + // when asterisk is used, explicitly set precision to 38 + columnEditor.length(38); + } else { + setPrecision(precisionPart, columnEditor); + } + setScale(precisionPart, columnEditor); + } + } else if (ctx.native_datatype_element().BLOB() != null) { + columnEditor.jdbcType(Types.BLOB).type("BLOB"); + } else if (ctx.native_datatype_element().CLOB() != null) { + columnEditor.jdbcType(Types.CLOB).type("CLOB"); + } else if (ctx.native_datatype_element().NCLOB() != null) { + columnEditor.jdbcType(Types.NCLOB).type("NCLOB"); + } else if (ctx.native_datatype_element().RAW() != null) { + columnEditor.jdbcType(OracleTypes.RAW).type("RAW"); + + setPrecision(precisionPart, columnEditor); + } else if (ctx.native_datatype_element().SDO_GEOMETRY() != null) { + // Allows the registration of new SDO_GEOMETRY columns via an CREATE/ALTER TABLE + // This is the same registration of the column that is resolved during JDBC metadata + // inspection. + columnEditor.jdbcType(OracleTypes.OTHER).type("SDO_GEOMETRY").length(1); + } else if (ctx.native_datatype_element().ROWID() != null) { + columnEditor.jdbcType(Types.VARCHAR).type("ROWID"); + } else { + columnEditor + .jdbcType(OracleTypes.OTHER) + .type(ctx.native_datatype_element().getText()); + } + } else if (ctx.INTERVAL() != null + && ctx.YEAR() != null + && ctx.TO() != null + && ctx.MONTH() != null) { + columnEditor.jdbcType(OracleTypes.INTERVALYM).type("INTERVAL YEAR TO MONTH").length(2); + if (!ctx.expression().isEmpty()) { + columnEditor.length(Integer.valueOf((ctx.expression(0).getText()))); + } + } else if (ctx.INTERVAL() != null + && ctx.DAY() != null + && ctx.TO() != null + && ctx.SECOND() != null) { + columnEditor + .jdbcType(OracleTypes.INTERVALDS) + .type("INTERVAL DAY TO SECOND") + .length(2) + .scale(6); + for (final PlSqlParser.ExpressionContext e : ctx.expression()) { + if (e.getSourceInterval().startsAfter(ctx.TO().getSourceInterval())) { + columnEditor.scale(Integer.valueOf(e.getText())); + } else { + columnEditor.length(Integer.valueOf(e.getText())); + } + } + if (!ctx.expression().isEmpty()) { + columnEditor.length(Integer.valueOf((ctx.expression(0).getText()))); + } + } else { + columnEditor.jdbcType(OracleTypes.OTHER).type(ctx.getText()); + } + } + + public Column getColumn() { + return columnEditor.create(); + } + + private int getVarCharDefaultLength() { + // TODO replace with value from select name, value from v$parameter where + // name='max_string_size'; + return 4000; + } + + private void setPrecision( + PlSqlParser.Precision_partContext precisionPart, ColumnEditor columnEditor) { + columnEditor.length(Integer.valueOf(precisionPart.numeric(0).getText())); + } + + private void setScale( + PlSqlParser.Precision_partContext precisionPart, ColumnEditor columnEditor) { + if (precisionPart.numeric().size() > 1) { + columnEditor.scale(Integer.valueOf(precisionPart.numeric(1).getText())); + } else if (precisionPart.numeric_negative() != null) { + columnEditor.scale(Integer.valueOf(precisionPart.numeric_negative().getText())); + } else { + columnEditor.scale(0); + } + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomOracleAntlrDdlParser.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomOracleAntlrDdlParser.java new file mode 100644 index 00000000000..f8146dc8f28 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomOracleAntlrDdlParser.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser; + +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; + +import io.debezium.antlr.AntlrDdlParserListener; +import io.debezium.connector.oracle.antlr.OracleDdlParser; +import io.debezium.relational.TableId; + +import java.util.LinkedList; +import java.util.List; + +/** A ddl parser that will use custom listener. */ +public class CustomOracleAntlrDdlParser extends OracleDdlParser { + + private final LinkedList parsedEvents; + + private final TablePath tablePath; + + public CustomOracleAntlrDdlParser(TablePath tablePath) { + super(); + this.tablePath = tablePath; + this.parsedEvents = new LinkedList<>(); + } + + public TableId parseQualifiedTableId() { + return new TableId( + tablePath.getDatabaseName(), tablePath.getSchemaName(), tablePath.getTableName()); + } + + @Override + protected AntlrDdlParserListener createParseTreeWalkerListener() { + return new CustomOracleAntlrDdlParserListener(this, parsedEvents); + } + + public List getAndClearParsedEvents() { + List result = Lists.newArrayList(parsedEvents); + parsedEvents.clear(); + return result; + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomOracleAntlrDdlParserListener.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomOracleAntlrDdlParserListener.java new file mode 100644 index 00000000000..eb7bbb5264b --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/CustomOracleAntlrDdlParserListener.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser; + +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTreeListener; + +import io.debezium.antlr.AntlrDdlParserListener; +import io.debezium.antlr.ProxyParseTreeListenerUtil; +import io.debezium.text.ParsingException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class CustomOracleAntlrDdlParserListener extends BaseParserListener + implements AntlrDdlParserListener { + + private final List listeners = new CopyOnWriteArrayList<>(); + private final Collection errors = new ArrayList<>(); + + public CustomOracleAntlrDdlParserListener( + CustomOracleAntlrDdlParser parser, LinkedList parsedEvents) { + // Currently only DDL statements that modify the table structure are supported, so add + // custom listeners to handle these events. + listeners.add(new CustomAlterTableParserListener(parser, listeners, parsedEvents)); + } + + /** + * Returns all caught errors during tree walk. + * + * @return list of Parsing exceptions + */ + @Override + public Collection getErrors() { + return Collections.emptyList(); + } + + @Override + public void enterEveryRule(ParserRuleContext ctx) { + ProxyParseTreeListenerUtil.delegateEnterRule(ctx, listeners, errors); + } + + @Override + public void exitEveryRule(ParserRuleContext ctx) { + ProxyParseTreeListenerUtil.delegateExitRule(ctx, listeners, errors); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/reader/fetch/OracleSourceFetchTaskContext.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/reader/fetch/OracleSourceFetchTaskContext.java index c10c6be72f7..942532a7f63 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/reader/fetch/OracleSourceFetchTaskContext.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/reader/fetch/OracleSourceFetchTaskContext.java @@ -31,6 +31,7 @@ import org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.offset.RedoLogOffset; import org.apache.seatunnel.connectors.seatunnel.cdc.oracle.utils.OracleUtils; +import org.apache.kafka.connect.data.SchemaAndValue; import org.apache.kafka.connect.data.Struct; import org.apache.kafka.connect.source.SourceRecord; @@ -67,6 +68,8 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -264,7 +267,29 @@ private void registerDatabaseHistory(SourceSplitBase sourceSplitBase) { dataSourceDialect.queryTableSchema(connection, snapshotSplit.getTableId())); } else { IncrementalSplit incrementalSplit = (IncrementalSplit) sourceSplitBase; + Map historyTableChanges = incrementalSplit.getHistoryTableChanges(); for (TableId tableId : incrementalSplit.getTableIds()) { + if (historyTableChanges != null && historyTableChanges.containsKey(tableId)) { + SchemaAndValue schemaAndValue = + jsonConverter.toConnectData("topic", historyTableChanges.get(tableId)); + Struct deserializedStruct = (Struct) schemaAndValue.value(); + + TableChanges tableChanges = + tableChangeSerializer.deserialize( + Collections.singletonList(deserializedStruct), false); + + Iterator iterator = tableChanges.iterator(); + TableChanges.TableChange tableChange = null; + while (iterator.hasNext()) { + if (tableChange != null) { + throw new IllegalStateException( + "The table changes should only have one element"); + } + tableChange = iterator.next(); + } + engineHistory.add(tableChange); + continue; + } engineHistory.add(dataSourceDialect.queryTableSchema(connection, tableId)); } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/utils/OracleTypeUtils.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/utils/OracleTypeUtils.java index 91547b17b2e..8147a187bb1 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/utils/OracleTypeUtils.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/utils/OracleTypeUtils.java @@ -23,6 +23,8 @@ import io.debezium.relational.Column; +import java.util.Optional; + /** Utilities for converting from oracle types to SeaTunnel types. */ public class OracleTypeUtils { @@ -40,4 +42,33 @@ public static SeaTunnelDataType convertFromColumn(Column column) { OracleTypeConverter.INSTANCE.convert(typeDefine); return seaTunnelColumn.getDataType(); } + + public static org.apache.seatunnel.api.table.catalog.Column convertToSeaTunnelColumn( + io.debezium.relational.Column column) { + + Optional defaultValueExpression = column.defaultValueExpression(); + Object defaultValue = defaultValueExpression.orElse(null); + + BasicTypeDefine.BasicTypeDefineBuilder builder = + BasicTypeDefine.builder() + .name(column.name()) + .columnType(column.typeName()) + .dataType(column.typeName()) + .scale(column.scale().orElse(0)) + .nullable(column.isOptional()) + .defaultValue(defaultValue); + + // The default value of length in column is -1 if it is not set + if (column.length() >= 0) { + builder.length((long) column.length()).precision((long) column.length()); + } + + // TIMESTAMP or TIMESTAMP WITH TIME ZONE + // This is useful for OracleTypeConverter.convert() + if (column.typeName() != null && column.typeName().toUpperCase().startsWith("TIMESTAMP")) { + builder.scale(column.length()); + } + + return new OracleTypeConverter(false).convert(builder.build()); + } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/OracleDdlParserTest.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/OracleDdlParserTest.java new file mode 100644 index 00000000000..850b8add844 --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-oracle/src/test/java/org/apache/seatunnel/connectors/seatunnel/cdc/oracle/source/parser/OracleDdlParserTest.java @@ -0,0 +1,410 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.cdc.oracle.source.parser; + +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.debezium.relational.Tables; + +import java.util.List; + +public class OracleDdlParserTest { + private static final String PDB_NAME = "qyws_empi"; + private static final String SCHEMA_NAME = "QYWS_EMPI"; + private static final String TABLE_NAME = "STUDENTS"; + private static CustomOracleAntlrDdlParser parser; + + @BeforeAll + public static void setUp() { + parser = new CustomOracleAntlrDdlParser(TablePath.of(PDB_NAME, SCHEMA_NAME, TABLE_NAME)); + parser.setCurrentDatabase(PDB_NAME); + parser.setCurrentSchema(SCHEMA_NAME); + } + + @Test + public void testParseDDLForAddColumn() { + String ddl = + "alter table \"" + + SCHEMA_NAME + + "\".\"" + + TABLE_NAME + + "\" add (" + + "\"col21\" varchar2(20), col22 number(19));"; + parser.parse(ddl, new Tables()); + List addEvent1 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(2, addEvent1.size()); + testColumn(addEvent1.get(0), "col21", "varchar2(20)", "STRING", 20 * 4L, null, true, null); + testColumn( + addEvent1.get(1), + "col22".toUpperCase(), + "number(19, 0)", + "Decimal(19, 0)", + 19L, + null, + true, + null); + + ddl = "alter table " + TABLE_NAME + " add (col23 varchar2(20) not null);"; + parser.parse(ddl, new Tables()); + List addEvent2 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, addEvent2.size()); + testColumn( + addEvent2.get(0), + "col23".toUpperCase(), + "varchar2(20)", + "STRING", + 20 * 4L, + null, + false, + null); + + ddl = + "alter table " + + TABLE_NAME + + " add (" + + "col1 numeric(4,2),\n" + + "col2 varchar2(255) default 'debezium' not null ,\n" + + "col3 varchar2(255) default sys_context('userenv','host') not null ,\n" + + "col4 nvarchar2(255) not null,\n" + + "col5 char(4),\n" + + "col6 nchar(4),\n" + + "col7 float default '3.0' not null,\n" + + "col8 date,\n" + + "col9 timestamp(6) default sysdate,\n" + + "col10 blob,\n" + + "col11 clob,\n" + + "col12 number(1,0),\n" + + "col13 timestamp with time zone not null,\n" + + "col14 number default (sysdate-to_date('1970-01-01 08:00:00', 'yyyy-mm-dd hh24:mi:ss'))*86400000,\n" + + "col15 timestamp(9) default to_timestamp('20190101 00:00:00.000000','yyyymmdd hh24:mi:ss.ff6') not null,\n" + + "col16 date default sysdate not null);"; + parser.parse(ddl, new Tables()); + List addEvent3 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(16, addEvent3.size()); + // Special default values are handled for reference: + // io.debezium.connector.oracle.OracleDefaultValueConverter.castTemporalFunctionCall + testColumn( + addEvent3.get(0), + "col1".toUpperCase(), + "number(4, 2)", + "Decimal(4, 2)", + 4L, + 2, + true, + null); + testColumn( + addEvent3.get(1), + "col2".toUpperCase(), + "varchar2(255)", + "STRING", + 255 * 4L, + null, + false, + "'debezium'"); + testColumn( + addEvent3.get(2), + "col3".toUpperCase(), + "varchar2(255)", + "STRING", + 255 * 4L, + null, + false, + "sys_context('userenv','host')"); + testColumn( + addEvent3.get(3), + "col4".toUpperCase(), + "nvarchar2(255)", + "STRING", + 255 * 2L, + null, + false, + null); + testColumn( + addEvent3.get(4), + "col5".toUpperCase(), + "char(4)", + "STRING", + 4 * 4L, + null, + true, + null); + testColumn( + addEvent3.get(5), + "col6".toUpperCase(), + "nchar(4)", + "STRING", + 4 * 2L, + null, + true, + null); + testColumn( + addEvent3.get(6), + "col7".toUpperCase(), + "float", + "Decimal(38, 18)", + 38L, + 18, + false, + "'3.0'"); + testColumn( + addEvent3.get(7), + "col8".toUpperCase(), + "date", + "TIMESTAMP", + null, + null, + true, + null); + testColumn( + addEvent3.get(8), + "col9".toUpperCase(), + "timestamp(6)", + "TIMESTAMP", + null, + 6, + true, + "sysdate"); + testColumn( + addEvent3.get(9), + "col10".toUpperCase(), + "blob", + "BYTES", + OracleTypeConverter.BYTES_4GB - 1, + null, + true, + null); + testColumn( + addEvent3.get(10), + "col11".toUpperCase(), + "clob", + "STRING", + OracleTypeConverter.BYTES_4GB - 1, + null, + true, + null); + testColumn( + addEvent3.get(11), + "col12".toUpperCase(), + "number(1, 0)", + "Decimal(1, 0)", + 1L, + null, + true, + null); + testColumn( + addEvent3.get(12), + "col13".toUpperCase(), + "timestamp with time zone(6)", + "TIMESTAMP", + null, + 6, + false, + null); + testColumn( + addEvent3.get(13), + "col14".toUpperCase(), + "number", + "Decimal(38, 0)", + 38L, + null, + true, + "(sysdate-to_date('1970-01-01 08:00:00','yyyy-mm-dd hh24:mi:ss'))*86400000"); + testColumn( + addEvent3.get(14), + "col15".toUpperCase(), + "timestamp(9)", + "TIMESTAMP", + null, + 9, + false, + "to_timestamp('20190101 00:00:00.000000','yyyymmdd hh24:mi:ss.ff6')"); + testColumn( + addEvent3.get(15), + "col16".toUpperCase(), + "date", + "TIMESTAMP", + null, + null, + false, + "sysdate"); + + ddl = + "ALTER TABLE \"" + + SCHEMA_NAME + + "\".\"" + + TABLE_NAME + + "\" ADD \"ADD_COL2\" TIMESTAMP(6) DEFAULT current_timestamp(6) NOT NULL "; + parser.parse(ddl, new Tables()); + List addEvent4 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, addEvent4.size()); + testColumn( + addEvent4.get(0), + "ADD_COL2", + "timestamp(6)", + "TIMESTAMP", + null, + 6, + false, + "current_timestamp(6)"); + } + + @Test + public void testParseDDLForDropColumn() { + String ddl = "ALTER TABLE \"" + SCHEMA_NAME + "\".\"" + TABLE_NAME + "\" DROP (T_VARCHAR2)"; + parser.parse(ddl, new Tables()); + List dropEvent1 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, dropEvent1.size()); + Assertions.assertEquals( + "T_VARCHAR2", ((AlterTableDropColumnEvent) dropEvent1.get(0)).getColumn()); + + ddl = "alter table " + TABLE_NAME + " drop (col22, col23);"; + parser.parse(ddl, new Tables()); + List dropEvent2 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(2, dropEvent2.size()); + Assertions.assertEquals( + "col22".toUpperCase(), ((AlterTableDropColumnEvent) dropEvent2.get(0)).getColumn()); + Assertions.assertEquals( + "col23".toUpperCase(), ((AlterTableDropColumnEvent) dropEvent2.get(1)).getColumn()); + + ddl = "alter table " + TABLE_NAME + " drop (\"col22\");"; + parser.parse(ddl, new Tables()); + List dropEvent3 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, dropEvent3.size()); + Assertions.assertEquals( + "col22", ((AlterTableDropColumnEvent) dropEvent3.get(0)).getColumn()); + } + + @Test + public void testParseDDLForRenameColumn() { + String ddl = "alter table " + TABLE_NAME + " rename column STUDENT_NAME to STUDENT_NAME1"; + parser.parse(ddl, new Tables()); + List renameEvent1 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, renameEvent1.size()); + Assertions.assertEquals( + "STUDENT_NAME", ((AlterTableChangeColumnEvent) renameEvent1.get(0)).getOldColumn()); + Assertions.assertEquals( + "STUDENT_NAME1", + ((AlterTableChangeColumnEvent) renameEvent1.get(0)).getColumn().getName()); + + ddl = + "alter table \"" + + TABLE_NAME + + "\" rename column STUDENT_ID to STUDENT_ID1;\n" + + "alter table \"" + + TABLE_NAME + + "\" rename column CLASS_ID to CLASS_ID1\n"; + + parser.parse(ddl, new Tables()); + List renameEvent2 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(2, renameEvent2.size()); + Assertions.assertEquals( + "STUDENT_ID", ((AlterTableChangeColumnEvent) renameEvent2.get(0)).getOldColumn()); + Assertions.assertEquals( + "STUDENT_ID1", + ((AlterTableChangeColumnEvent) renameEvent2.get(0)).getColumn().getName()); + Assertions.assertEquals( + "CLASS_ID", ((AlterTableChangeColumnEvent) renameEvent2.get(1)).getOldColumn()); + Assertions.assertEquals( + "CLASS_ID1", + ((AlterTableChangeColumnEvent) renameEvent2.get(1)).getColumn().getName()); + } + + @Test + public void testParseDDLForModifyColumn() { + String ddl = "ALTER TABLE " + TABLE_NAME + " MODIFY COL1 varchar2(50) not null;"; + parser.parse(ddl, new Tables()); + List modifyEvent1 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, modifyEvent1.size()); + testColumn( + modifyEvent1.get(0), "COL1", "varchar2(50)", "STRING", 50 * 4L, null, false, null); + + ddl = "alter table " + TABLE_NAME + " modify sex char(2) default 'M' not null ;"; + parser.parse(ddl, new Tables()); + List modifyEvent2 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, modifyEvent2.size()); + testColumn( + modifyEvent2.get(0), + "sex".toUpperCase(), + "char(2)", + "STRING", + 2 * 4L, + null, + false, + "'M'"); + ddl = + "ALTER TABLE \"" + + SCHEMA_NAME + + "\".\"" + + TABLE_NAME + + "\" MODIFY (ID NUMBER(*,0) NULL);"; + parser.parse(ddl, new Tables()); + List modifyEvent3 = parser.getAndClearParsedEvents(); + Assertions.assertEquals(1, modifyEvent3.size()); + testColumn( + modifyEvent3.get(0), + "ID", + "number(38, 0)", + "Decimal(38, 0)", + 38L, + null, + true, + null); + } + + private void testColumn( + AlterTableColumnEvent alterTableColumnEvent, + String columnName, + String sourceType, + String dataType, + Long columnLength, + Integer scale, + boolean isNullable, + Object defaultValue) { + Column column; + switch (alterTableColumnEvent.getEventType()) { + case SCHEMA_CHANGE_ADD_COLUMN: + column = ((AlterTableAddColumnEvent) alterTableColumnEvent).getColumn(); + break; + case SCHEMA_CHANGE_MODIFY_COLUMN: + column = ((AlterTableModifyColumnEvent) alterTableColumnEvent).getColumn(); + break; + default: + throw new UnsupportedOperationException( + "Unsupported method named getColumn() for the AlterTableColumnEvent: " + + alterTableColumnEvent.getEventType().name()); + } + Assertions.assertEquals(columnName, column.getName()); + Assertions.assertEquals(sourceType.toUpperCase(), column.getSourceType()); + Assertions.assertEquals(dataType, column.getDataType().toString()); + Assertions.assertEquals(columnLength, column.getColumnLength()); + Assertions.assertEquals(scale, column.getScale()); + Assertions.assertEquals(isNullable, column.isNullable()); + Assertions.assertEquals(defaultValue, column.getDefaultValue()); + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfig.java index 92ef7345664..4f63a2d4fe6 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfig.java @@ -25,6 +25,7 @@ import io.debezium.relational.RelationalTableFilters; import java.util.List; +import java.util.Map; import java.util.Properties; public class PostgresSourceConfig extends JdbcSourceConfig { @@ -36,6 +37,7 @@ public PostgresSourceConfig( List databaseList, List tableList, int splitSize, + Map splitColumn, double distributionFactorUpper, double distributionFactorLower, int sampleShardingThreshold, @@ -59,6 +61,7 @@ public PostgresSourceConfig( databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfigFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfigFactory.java index ebe1cd0a154..3b35946ae0e 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfigFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/config/PostgresSourceConfigFactory.java @@ -29,7 +29,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; public class PostgresSourceConfigFactory extends JdbcSourceConfigFactory { @@ -115,6 +115,7 @@ public PostgresSourceConfig create(int subtask) { databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresDialect.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresDialect.java index 94781a2e542..921cb52518f 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresDialect.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresDialect.java @@ -43,6 +43,7 @@ import io.debezium.connector.postgresql.PostgresConnectorConfig; import io.debezium.connector.postgresql.connection.PostgresConnection; +import io.debezium.connector.postgresql.connection.ServerInfo; import io.debezium.jdbc.JdbcConnection; import io.debezium.relational.RelationalDatabaseConnectorConfig; import io.debezium.relational.TableId; @@ -103,13 +104,32 @@ public ChunkSplitter createChunkSplitter(JdbcSourceConfig sourceConfig) { public List discoverDataCollections(JdbcSourceConfig sourceConfig) { PostgresSourceConfig postgresSourceConfig = (PostgresSourceConfig) sourceConfig; try (JdbcConnection jdbcConnection = openJdbcConnection(sourceConfig)) { - return TableDiscoveryUtils.listTables( - jdbcConnection, postgresSourceConfig.getTableFilters()); + List tables = + TableDiscoveryUtils.listTables( + jdbcConnection, postgresSourceConfig.getTableFilters()); + this.checkAllTablesEnabledCapture(jdbcConnection, tables); + return tables; } catch (SQLException e) { throw new SeaTunnelException("Error to discover tables: " + e.getMessage(), e); } } + @Override + public void checkAllTablesEnabledCapture(JdbcConnection jdbcConnection, List tableIds) + throws SQLException { + PostgresConnection postgresConnection = (PostgresConnection) jdbcConnection; + for (TableId tableId : tableIds) { + ServerInfo.ReplicaIdentity replicaIdentity = + postgresConnection.readReplicaIdentityInfo(tableId); + if (!ServerInfo.ReplicaIdentity.FULL.equals(replicaIdentity)) { + throw new SeaTunnelException( + String.format( + "Table %s does not have a full replica identity, please execute: ALTER TABLE %s REPLICA IDENTITY FULL;", + tableId, tableId)); + } + } + } + @Override public TableChanges.TableChange queryTableSchema(JdbcConnection jdbc, TableId tableId) { if (postgresSchema == null) { @@ -155,6 +175,12 @@ public FetchTask createFetchTask(SourceSplitBase sourceSplitBas if (sourceSplitBase.isSnapshotSplit()) { return new PostgresSnapshotFetchTask(sourceSplitBase.asSnapshotSplit()); } else { + try (JdbcConnection jdbcConnection = openJdbcConnection(sourceConfig)) { + List tables = sourceSplitBase.asIncrementalSplit().getTableIds(); + this.checkAllTablesEnabledCapture(jdbcConnection, tables); + } catch (SQLException e) { + throw new SeaTunnelException("Error to check tables: " + e.getMessage(), e); + } postgresWalFetchTask = new PostgresWalFetchTask(sourceSplitBase.asIncrementalSplit()); return postgresWalFetchTask; } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSource.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSource.java index 0f40b73aad3..24546712679 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSource.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSource.java @@ -21,8 +21,6 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.SupportParallelism; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.common.utils.JdbcUrlUtil; import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; @@ -58,11 +56,8 @@ public class PostgresIncrementalSource extends IncrementalSource dataType, - List catalogTables) { - super(options, dataType, catalogTables); + public PostgresIncrementalSource(ReadonlyConfig options, List catalogTables) { + super(options, catalogTables); } @Override @@ -98,12 +93,10 @@ public SourceConfig.Factory createSourceConfigFactory(Readonly @Override public DebeziumDeserializationSchema createDebeziumDeserializationSchema( ReadonlyConfig config) { - SeaTunnelDataType physicalRowType = dataType; String zoneId = config.get(JdbcSourceOptions.SERVER_TIME_ZONE); return (DebeziumDeserializationSchema) SeaTunnelRowDebeziumDeserializeSchema.builder() - .setPhysicalRowType(physicalRowType) - .setResultTypeInfo(physicalRowType) + .setTables(catalogTables) .setServerTimeZone(ZoneId.of(zoneId)) .build(); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSourceFactory.java index e75c3505ef1..ec8a403b3bb 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSourceFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresIncrementalSourceFactory.java @@ -28,8 +28,6 @@ import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceTableConfig; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.StartupMode; @@ -97,10 +95,8 @@ TableSource createSource(TableSourceFactoryContext context) { CatalogTableUtils.mergeCatalogTableConfig( catalogTables, tableConfigs.get(), s -> TablePath.of(s, true)); } - SeaTunnelDataType dataType = - CatalogTableUtil.convertToMultipleRowType(catalogTables); return (SeaTunnelSource) - new PostgresIncrementalSource<>(context.getOptions(), dataType, catalogTables); + new PostgresIncrementalSource<>(context.getOptions(), catalogTables); }; } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresSourceOptions.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresSourceOptions.java index 1be2b9a9606..91fda42d345 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresSourceOptions.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-postgres/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/postgres/source/PostgresSourceOptions.java @@ -38,7 +38,7 @@ public class PostgresSourceOptions { .defaultValue(StartupMode.INITIAL) .withDescription( "Optional startup mode for CDC source, valid enumerations are " - + "\"initial\", \"earliest\", \"latest\", \"timestamp\"\n or \"specific\""); + + "\"initial\", \"earliest\", \"latest\""); public static final SingleChoiceOption STOP_MODE = (SingleChoiceOption) @@ -47,5 +47,5 @@ public class PostgresSourceOptions { .defaultValue(StopMode.NEVER) .withDescription( "Optional stop mode for CDC source, valid enumerations are " - + "\"never\", \"latest\", \"timestamp\"\n or \"specific\""); + + "\"never\""); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/io/debezium/connector/sqlserver/SqlServerConnection.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/io/debezium/connector/sqlserver/SqlServerConnection.java new file mode 100644 index 00000000000..2c38dd1a26e --- /dev/null +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/io/debezium/connector/sqlserver/SqlServerConnection.java @@ -0,0 +1,808 @@ +/* + * 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. + */ + +package io.debezium.connector.sqlserver; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.microsoft.sqlserver.jdbc.SQLServerDriver; +import io.debezium.config.CommonConnectorConfig; +import io.debezium.config.Configuration; +import io.debezium.data.Envelope; +import io.debezium.jdbc.JdbcConfiguration; +import io.debezium.jdbc.JdbcConnection; +import io.debezium.relational.Column; +import io.debezium.relational.ColumnEditor; +import io.debezium.relational.Table; +import io.debezium.relational.TableId; +import io.debezium.relational.Tables; +import io.debezium.schema.DatabaseSchema; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +import static org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.sqlserver.SqlServerCatalog.SELECT_COLUMNS_SQL_TEMPLATE; + +/** + * {@link JdbcConnection} extension to be used with Microsoft SQL Server + * + * @author Horia Chiorean (hchiorea@redhat.com), Jiri Pechanec + */ +public class SqlServerConnection extends JdbcConnection { + + /** + * @deprecated The connector will determine the database server timezone offset automatically. + */ + @Deprecated public static final String SERVER_TIMEZONE_PROP_NAME = "server.timezone"; + + public static final String INSTANCE_NAME = "instance"; + + private static final String GET_DATABASE_NAME = "SELECT name FROM sys.databases WHERE name = ?"; + + private static final Logger LOGGER = LoggerFactory.getLogger(SqlServerConnection.class); + + private static final String STATEMENTS_PLACEHOLDER = "#"; + private static final String DATABASE_NAME_PLACEHOLDER = "#db"; + private static final String GET_MAX_LSN = "SELECT [#db].sys.fn_cdc_get_max_lsn()"; + private static final String GET_MAX_TRANSACTION_LSN = + "SELECT MAX(start_lsn) FROM [#db].cdc.lsn_time_mapping WHERE tran_id <> 0x00"; + private static final String GET_NTH_TRANSACTION_LSN_FROM_BEGINNING = + "SELECT MAX(start_lsn) FROM (SELECT TOP (?) start_lsn FROM [#db].cdc.lsn_time_mapping WHERE tran_id <> 0x00 ORDER BY start_lsn) as next_lsns"; + private static final String GET_NTH_TRANSACTION_LSN_FROM_LAST = + "SELECT MAX(start_lsn) FROM (SELECT TOP (? + 1) start_lsn FROM [#db].cdc.lsn_time_mapping WHERE start_lsn >= ? AND tran_id <> 0x00 ORDER BY start_lsn) as next_lsns"; + + private static final String GET_MIN_LSN = "SELECT [#db].sys.fn_cdc_get_min_lsn('#')"; + private static final String LOCK_TABLE = "SELECT * FROM [#] WITH (TABLOCKX)"; + private static final String INCREMENT_LSN = "SELECT [#db].sys.fn_cdc_increment_lsn(?)"; + private static final String GET_ALL_CHANGES_FOR_TABLE = + "SELECT *# FROM [#db].cdc.[fn_cdc_get_all_changes_#](?, ?, N'all update old') order by [__$start_lsn] ASC, [__$seqval] ASC, [__$operation] ASC"; + private final String get_all_changes_for_table; + protected static final String LSN_TIMESTAMP_SELECT_STATEMENT = + "TODATETIMEOFFSET([#db].sys.fn_cdc_map_lsn_to_time([__$start_lsn]), DATEPART(TZOFFSET, SYSDATETIMEOFFSET()))"; + + /** + * Queries the list of captured column names and their change table identifiers in the given + * database. + */ + private static final String GET_CAPTURED_COLUMNS = + "SELECT object_id, column_name" + + " FROM [#db].cdc.captured_columns" + + " ORDER BY object_id, column_id"; + + /** + * Queries the list of capture instances in the given database. + * + *

If two or more capture instances with the same start LSN are available for a given source + * table, only the newest one will be returned. + * + *

We use a query instead of {@code sys.sp_cdc_help_change_data_capture} because: 1. The + * stored procedure doesn't allow filtering capture instances by start LSN. 2. There is no way + * to use the result returned by a stored procedure in a query. + */ + private static final String GET_CHANGE_TABLES = + "WITH ordered_change_tables" + + " AS (SELECT ROW_NUMBER() OVER (PARTITION BY ct.source_object_id, ct.start_lsn ORDER BY ct.create_date DESC) AS ct_sequence," + + " ct.*" + + " FROM [#db].cdc.change_tables AS ct#)" + + " SELECT OBJECT_SCHEMA_NAME(source_object_id, DB_ID(?))," + + " OBJECT_NAME(source_object_id, DB_ID(?))," + + " capture_instance," + + " object_id," + + " start_lsn" + + " FROM ordered_change_tables WHERE ct_sequence = 1"; + + private static final String GET_NEW_CHANGE_TABLES = + "SELECT * FROM [#db].cdc.change_tables WHERE start_lsn BETWEEN ? AND ?"; + private static final String OPENING_QUOTING_CHARACTER = "["; + private static final String CLOSING_QUOTING_CHARACTER = "]"; + + private static final String URL_PATTERN = + "jdbc:sqlserver://${" + + JdbcConfiguration.HOSTNAME + + "}:${" + + JdbcConfiguration.PORT + + "}"; + + private final boolean multiPartitionMode; + private final String getAllChangesForTable; + private final int queryFetchSize; + + private final SqlServerDefaultValueConverter defaultValueConverter; + + private boolean optionRecompile; + + /** + * Creates a new connection using the supplied configuration. + * + * @param config {@link Configuration} instance, may not be null. + * @param sourceTimestampMode strategy for populating {@code source.ts_ms}. + * @param valueConverters {@link SqlServerValueConverters} instance + * @param classLoaderSupplier class loader supplier + * @param skippedOperations a set of {@link Envelope.Operation} to skip in streaming + */ + public SqlServerConnection( + JdbcConfiguration config, + SourceTimestampMode sourceTimestampMode, + SqlServerValueConverters valueConverters, + Supplier classLoaderSupplier, + Set skippedOperations, + boolean multiPartitionMode) { + super( + config, + createConnectionFactory(multiPartitionMode), + classLoaderSupplier, + OPENING_QUOTING_CHARACTER, + CLOSING_QUOTING_CHARACTER); + + if (config().hasKey(SERVER_TIMEZONE_PROP_NAME)) { + LOGGER.warn( + "The '{}' option is deprecated and is not taken into account", + SERVER_TIMEZONE_PROP_NAME); + } + + defaultValueConverter = + new SqlServerDefaultValueConverter(this::connection, valueConverters); + this.queryFetchSize = config().getInteger(CommonConnectorConfig.QUERY_FETCH_SIZE); + + if (!skippedOperations.isEmpty()) { + Set skippedOps = new HashSet<>(); + StringBuilder getAllChangesForTableStatement = + new StringBuilder( + "SELECT *# FROM [#db].cdc.[fn_cdc_get_all_changes_#](?, ?, N'all update old') WHERE __$operation NOT IN ("); + skippedOperations.forEach( + (Envelope.Operation operation) -> { + // This number are the __$operation number in the SQLServer + // https://docs.microsoft.com/en-us/sql/relational-databases/system-functions/cdc-fn-cdc-get-all-changes-capture-instance-transact-sql?view=sql-server-ver15#table-returned + switch (operation) { + case CREATE: + skippedOps.add("2"); + break; + case UPDATE: + skippedOps.add("3"); + skippedOps.add("4"); + break; + case DELETE: + skippedOps.add("1"); + break; + } + }); + getAllChangesForTableStatement.append(String.join(",", skippedOps)); + getAllChangesForTableStatement.append( + ") order by [__$start_lsn] ASC, [__$seqval] ASC, [__$operation] ASC"); + get_all_changes_for_table = getAllChangesForTableStatement.toString(); + } else { + get_all_changes_for_table = GET_ALL_CHANGES_FOR_TABLE; + } + + getAllChangesForTable = + get_all_changes_for_table.replaceFirst( + STATEMENTS_PLACEHOLDER, + Matcher.quoteReplacement( + sourceTimestampMode.lsnTimestampSelectStatement())); + this.multiPartitionMode = multiPartitionMode; + + this.optionRecompile = false; + } + + /** + * Creates a new connection using the supplied configuration. + * + * @param config {@link Configuration} instance, may not be null. + * @param sourceTimestampMode strategy for populating {@code source.ts_ms}. + * @param valueConverters {@link SqlServerValueConverters} instance + * @param classLoaderSupplier class loader supplier + * @param skippedOperations a set of {@link Envelope.Operation} to skip in streaming + * @param optionRecompile Includes query option RECOMPILE on incremental snapshots + */ + public SqlServerConnection( + JdbcConfiguration config, + SourceTimestampMode sourceTimestampMode, + SqlServerValueConverters valueConverters, + Supplier classLoaderSupplier, + Set skippedOperations, + boolean multiPartitionMode, + boolean optionRecompile) { + this( + config, + sourceTimestampMode, + valueConverters, + classLoaderSupplier, + skippedOperations, + multiPartitionMode); + + this.optionRecompile = optionRecompile; + } + + private static String createUrlPattern(boolean multiPartitionMode) { + String pattern = URL_PATTERN; + if (!multiPartitionMode) { + pattern += ";databaseName=${" + JdbcConfiguration.DATABASE + "}"; + } + + return pattern; + } + + private static ConnectionFactory createConnectionFactory(boolean multiPartitionMode) { + return JdbcConnection.patternBasedFactory( + createUrlPattern(multiPartitionMode), + SQLServerDriver.class.getName(), + SqlServerConnection.class.getClassLoader(), + JdbcConfiguration.PORT.withDefault( + SqlServerConnectorConfig.PORT.defaultValueAsString())); + } + + /** + * Returns a JDBC connection string for the current configuration. + * + * @return a {@code String} where the variables in {@code urlPattern} are replaced with values + * from the configuration + */ + public String connectionString() { + return connectionString(createUrlPattern(multiPartitionMode)); + } + + @Override + public synchronized Connection connection(boolean executeOnConnect) throws SQLException { + boolean connected = isConnected(); + Connection connection = super.connection(executeOnConnect); + + if (!connected) { + connection.setAutoCommit(false); + } + + return connection; + } + + /** @return the current largest log sequence number */ + public Lsn getMaxLsn(String databaseName) throws SQLException { + return queryAndMap( + replaceDatabaseNamePlaceholder(GET_MAX_LSN, databaseName), + singleResultMapper( + rs -> { + final Lsn ret = Lsn.valueOf(rs.getBytes(1)); + LOGGER.trace("Current maximum lsn is {}", ret); + return ret; + }, + "Maximum LSN query must return exactly one value")); + } + + /** + * @return the log sequence number of the most recent transaction that isn't further than {@code + * maxOffset} from the beginning. + */ + public Lsn getNthTransactionLsnFromBeginning(String databaseName, int maxOffset) + throws SQLException { + return prepareQueryAndMap( + replaceDatabaseNamePlaceholder( + GET_NTH_TRANSACTION_LSN_FROM_BEGINNING, databaseName), + statement -> { + statement.setInt(1, maxOffset); + }, + singleResultMapper( + rs -> { + final Lsn ret = Lsn.valueOf(rs.getBytes(1)); + LOGGER.trace("Nth lsn from beginning is {}", ret); + return ret; + }, + "Nth LSN query must return exactly one value")); + } + + /** + * @return the log sequence number of the most recent transaction that isn't further than {@code + * maxOffset} from {@code lastLsn}. + */ + public Lsn getNthTransactionLsnFromLast(String databaseName, Lsn lastLsn, int maxOffset) + throws SQLException { + return prepareQueryAndMap( + replaceDatabaseNamePlaceholder(GET_NTH_TRANSACTION_LSN_FROM_LAST, databaseName), + statement -> { + statement.setInt(1, maxOffset); + statement.setBytes(2, lastLsn.getBinary()); + }, + singleResultMapper( + rs -> { + final Lsn ret = Lsn.valueOf(rs.getBytes(1)); + LOGGER.trace("Nth lsn from last is {}", ret); + return ret; + }, + "Nth LSN query must return exactly one value")); + } + + /** @return the log sequence number of the most recent transaction. */ + public Lsn getMaxTransactionLsn(String databaseName) throws SQLException { + return queryAndMap( + replaceDatabaseNamePlaceholder(GET_MAX_TRANSACTION_LSN, databaseName), + singleResultMapper( + rs -> { + final Lsn ret = Lsn.valueOf(rs.getBytes(1)); + LOGGER.trace("Max transaction lsn is {}", ret); + return ret; + }, + "Max transaction LSN query must return exactly one value")); + } + + /** @return the smallest log sequence number of table */ + public Lsn getMinLsn(String databaseName, String changeTableName) throws SQLException { + String query = + replaceDatabaseNamePlaceholder(GET_MIN_LSN, databaseName) + .replace(STATEMENTS_PLACEHOLDER, changeTableName); + return queryAndMap( + query, + singleResultMapper( + rs -> { + final Lsn ret = Lsn.valueOf(rs.getBytes(1)); + LOGGER.trace("Current minimum lsn is {}", ret); + return ret; + }, + "Minimum LSN query must return exactly one value")); + } + + @Override + protected Optional readTableColumn( + ResultSet columnMetadata, TableId tableId, Tables.ColumnNameFilter columnFilter) + throws SQLException { + return doReadTableColumn(columnMetadata, tableId, columnFilter); + } + + private Optional doReadTableColumn( + ResultSet columnMetadata, TableId tableId, Tables.ColumnNameFilter columnFilter) + throws SQLException { + // Oracle drivers require this for LONG/LONGRAW to be fetched first. + final String defaultValue = columnMetadata.getString(13); + String tableSql = + StringUtils.isNotEmpty(tableId.table()) + ? "AND tbl.name = '" + tableId.table() + "'" + : ""; + + Map columnTypeMapping = new HashMap<>(); + + // Support user-defined types (UDTs) + try (PreparedStatement ps = + connection() + .prepareStatement( + String.format( + SELECT_COLUMNS_SQL_TEMPLATE, + tableId.schema(), + tableSql)); + ResultSet resultSet = ps.executeQuery()) { + while (resultSet.next()) { + String columnName = resultSet.getString("column_name"); + String dataType = resultSet.getString("type"); + columnTypeMapping.put(columnName, dataType); + } + } + final String columnName = columnMetadata.getString(4); + if (columnFilter == null + || columnFilter.matches( + tableId.catalog(), tableId.schema(), tableId.table(), columnName)) { + ColumnEditor column = Column.editor().name(columnName); + column.type( + columnTypeMapping.containsKey(columnName) + ? columnTypeMapping.get(columnName) + : columnMetadata.getString(6)); + column.length(columnMetadata.getInt(7)); + if (columnMetadata.getObject(9) != null) { + column.scale(columnMetadata.getInt(9)); + } + column.optional(isNullable(columnMetadata.getInt(11))); + column.position(columnMetadata.getInt(17)); + column.autoIncremented("YES".equalsIgnoreCase(columnMetadata.getString(23))); + String autogenerated = null; + try { + autogenerated = columnMetadata.getString(24); + } catch (SQLException e) { + // ignore, some drivers don't have this index - e.g. Postgres + } + column.generated("YES".equalsIgnoreCase(autogenerated)); + + column.nativeType(resolveNativeType(column.typeName())); + column.jdbcType(resolveJdbcType(columnMetadata.getInt(5), column.nativeType())); + + // Allow implementation to make column changes if required before being added to table + column = overrideColumn(column); + + if (defaultValue != null) { + column.defaultValueExpression(defaultValue); + } + return Optional.of(column); + } + + return Optional.empty(); + } + + /** + * Provides all changes recorder by the SQL Server CDC capture process for a set of tables. + * + * @param databaseName - the name of the database to query + * @param changeTables - the requested tables to obtain changes for + * @param intervalFromLsn - closed lower bound of interval of changes to be provided + * @param intervalToLsn - closed upper bound of interval of changes to be provided + * @param consumer - the change processor + * @throws SQLException + */ + public void getChangesForTables( + String databaseName, + SqlServerChangeTable[] changeTables, + Lsn intervalFromLsn, + Lsn intervalToLsn, + BlockingMultiResultSetConsumer consumer) + throws SQLException, InterruptedException { + final String[] queries = new String[changeTables.length]; + final StatementPreparer[] preparers = new StatementPreparer[changeTables.length]; + + int idx = 0; + for (SqlServerChangeTable changeTable : changeTables) { + final String query = + replaceDatabaseNamePlaceholder(getAllChangesForTable, databaseName) + .replace(STATEMENTS_PLACEHOLDER, changeTable.getCaptureInstance()); + queries[idx] = query; + // If the table was added in the middle of queried buffer we need + // to adjust from to the first LSN available + final Lsn fromLsn = getFromLsn(databaseName, changeTable, intervalFromLsn); + LOGGER.trace( + "Getting changes for table {} in range[{}, {}]", + changeTable, + fromLsn, + intervalToLsn); + preparers[idx] = + statement -> { + if (queryFetchSize > 0) { + statement.setFetchSize(queryFetchSize); + } + statement.setBytes(1, fromLsn.getBinary()); + statement.setBytes(2, intervalToLsn.getBinary()); + }; + + idx++; + } + prepareQuery(queries, preparers, consumer); + } + + /** Overridden to make sure the prepared statement is closed after the query is executed. */ + @Override + public JdbcConnection prepareQuery( + String[] multiQuery, + StatementPreparer[] preparers, + BlockingMultiResultSetConsumer resultConsumer) + throws SQLException, InterruptedException { + final ResultSet[] resultSets = new ResultSet[multiQuery.length]; + final PreparedStatement[] preparedStatements = new PreparedStatement[multiQuery.length]; + + try { + for (int i = 0; i < multiQuery.length; i++) { + final String query = multiQuery[i]; + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("running '{}'", query); + } + final PreparedStatement statement = connection().prepareStatement(query); + preparedStatements[i] = statement; + preparers[i].accept(statement); + resultSets[i] = statement.executeQuery(); + } + if (resultConsumer != null) { + resultConsumer.accept(resultSets); + } + } finally { + for (ResultSet rs : resultSets) { + if (rs != null) { + try { + rs.close(); + } catch (Exception ei) { + } + } + } + for (PreparedStatement ps : preparedStatements) { + if (ps != null) { + try { + ps.close(); + } catch (Exception ei) { + } + } + } + } + return this; + } + + private Lsn getFromLsn( + String databaseName, SqlServerChangeTable changeTable, Lsn intervalFromLsn) + throws SQLException { + Lsn fromLsn = + changeTable.getStartLsn().compareTo(intervalFromLsn) > 0 + ? changeTable.getStartLsn() + : intervalFromLsn; + return fromLsn.getBinary() != null + ? fromLsn + : getMinLsn(databaseName, changeTable.getCaptureInstance()); + } + + /** + * Obtain the next available position in the database log. + * + * @param databaseName - the name of the database that the LSN belongs to + * @param lsn - LSN of the current position + * @return LSN of the next position in the database + * @throws SQLException + */ + public Lsn incrementLsn(String databaseName, Lsn lsn) throws SQLException { + return prepareQueryAndMap( + replaceDatabaseNamePlaceholder(INCREMENT_LSN, databaseName), + statement -> { + statement.setBytes(1, lsn.getBinary()); + }, + singleResultMapper( + rs -> { + final Lsn ret = Lsn.valueOf(rs.getBytes(1)); + LOGGER.trace("Increasing lsn from {} to {}", lsn, ret); + return ret; + }, + "Increment LSN query must return exactly one value")); + } + + /** + * Creates an exclusive lock for a given table. + * + * @param tableId to be locked + * @throws SQLException + */ + public void lockTable(TableId tableId) throws SQLException { + final String lockTableStmt = LOCK_TABLE.replace(STATEMENTS_PLACEHOLDER, tableId.table()); + execute(lockTableStmt); + } + + private String cdcNameForTable(TableId tableId) { + return tableId.schema() + '_' + tableId.table(); + } + + public static class CdcEnabledTable { + private final String tableId; + private final String captureName; + private final Lsn fromLsn; + + private CdcEnabledTable(String tableId, String captureName, Lsn fromLsn) { + this.tableId = tableId; + this.captureName = captureName; + this.fromLsn = fromLsn; + } + + public String getTableId() { + return tableId; + } + + public String getCaptureName() { + return captureName; + } + + public Lsn getFromLsn() { + return fromLsn; + } + } + + public List getChangeTables(String databaseName) throws SQLException { + return getChangeTables(databaseName, Lsn.NULL); + } + + public List getChangeTables(String databaseName, Lsn toLsn) + throws SQLException { + Map> columns = + queryAndMap( + replaceDatabaseNamePlaceholder(GET_CAPTURED_COLUMNS, databaseName), + rs -> { + Map> result = new HashMap<>(); + while (rs.next()) { + int changeTableObjectId = rs.getInt(1); + if (!result.containsKey(changeTableObjectId)) { + result.put(changeTableObjectId, new LinkedList<>()); + } + + result.get(changeTableObjectId).add(rs.getString(2)); + } + return result; + }); + final ResultSetMapper> mapper = + rs -> { + final List changeTables = new ArrayList<>(); + while (rs.next()) { + int changeTableObjectId = rs.getInt(4); + changeTables.add( + new SqlServerChangeTable( + new TableId(databaseName, rs.getString(1), rs.getString(2)), + rs.getString(3), + changeTableObjectId, + Lsn.valueOf(rs.getBytes(5)), + columns.get(changeTableObjectId))); + } + return changeTables; + }; + + String query = replaceDatabaseNamePlaceholder(GET_CHANGE_TABLES, databaseName); + + if (toLsn.isAvailable()) { + return prepareQueryAndMap( + query.replace(STATEMENTS_PLACEHOLDER, " WHERE ct.start_lsn <= ?"), + ps -> { + ps.setBytes(1, toLsn.getBinary()); + ps.setString(2, databaseName); + ps.setString(3, databaseName); + }, + mapper); + } else { + return prepareQueryAndMap( + query.replace(STATEMENTS_PLACEHOLDER, ""), + ps -> { + ps.setString(1, databaseName); + ps.setString(2, databaseName); + }, + mapper); + } + } + + public List getNewChangeTables( + String databaseName, Lsn fromLsn, Lsn toLsn) throws SQLException { + final String query = replaceDatabaseNamePlaceholder(GET_NEW_CHANGE_TABLES, databaseName); + + return prepareQueryAndMap( + query, + ps -> { + ps.setBytes(1, fromLsn.getBinary()); + ps.setBytes(2, toLsn.getBinary()); + }, + rs -> { + final List changeTables = new ArrayList<>(); + while (rs.next()) { + changeTables.add( + new SqlServerChangeTable( + rs.getString(4), + rs.getInt(1), + Lsn.valueOf(rs.getBytes(5)))); + } + return changeTables; + }); + } + + public Table getTableSchemaFromTable(String databaseName, SqlServerChangeTable changeTable) + throws SQLException { + final DatabaseMetaData metadata = connection().getMetaData(); + + List columns = new ArrayList<>(); + try (ResultSet rs = + metadata.getColumns( + databaseName, + changeTable.getSourceTableId().schema(), + changeTable.getSourceTableId().table(), + null)) { + while (rs.next()) { + readTableColumn(rs, changeTable.getSourceTableId(), null) + .ifPresent( + ce -> { + // Filter out columns not included in the change table. + if (changeTable.getCapturedColumns().contains(ce.name())) { + columns.add(ce.create()); + } + }); + } + } + + final List pkColumnNames = + readPrimaryKeyOrUniqueIndexNames(metadata, changeTable.getSourceTableId()).stream() + .filter(column -> changeTable.getCapturedColumns().contains(column)) + .collect(Collectors.toList()); + Collections.sort(columns); + return Table.editor() + .tableId(changeTable.getSourceTableId()) + .addColumns(columns) + .setPrimaryKeyNames(pkColumnNames) + .create(); + } + + public String getNameOfChangeTable(String captureName) { + return captureName + "_CT"; + } + + /** + * Retrieve the name of the database in the original case as it's defined on the server. + * + *

Although SQL Server supports case-insensitive collations, the connector uses the database + * name to build the produced records' source info and, subsequently, the keys of its committed + * offset messages. This value must remain the same during the lifetime of the connector + * regardless of the case used in the connector configuration. + */ + public String retrieveRealDatabaseName(String databaseName) { + try { + return prepareQueryAndMap( + GET_DATABASE_NAME, + ps -> ps.setString(1, databaseName), + singleResultMapper( + rs -> rs.getString(1), "Could not retrieve exactly one database name")); + } catch (SQLException e) { + throw new RuntimeException("Couldn't obtain database name", e); + } + } + + @Override + protected boolean isTableUniqueIndexIncluded(String indexName, String columnName) { + // SQL Server provides indices also without index name + // so we need to ignore them + return indexName != null; + } + + @Override + public > Object getColumnValue( + ResultSet rs, int columnIndex, Column column, Table table, T schema) + throws SQLException { + final ResultSetMetaData metaData = rs.getMetaData(); + final int columnType = metaData.getColumnType(columnIndex); + + if (columnType == Types.TIME) { + return rs.getTimestamp(columnIndex); + } else { + return super.getColumnValue(rs, columnIndex, column, table, schema); + } + } + + @Override + public String buildSelectWithRowLimits( + TableId tableId, + int limit, + String projection, + Optional condition, + String orderBy) { + final StringBuilder sql = new StringBuilder("SELECT TOP "); + sql.append(limit).append(' ').append(projection).append(" FROM "); + sql.append(quotedTableIdString(tableId)); + if (condition.isPresent()) { + sql.append(" WHERE ").append(condition.get()); + } + sql.append(" ORDER BY ").append(orderBy); + if (this.optionRecompile) { + sql.append(" OPTION(RECOMPILE)"); + } + return sql.toString(); + } + + @Override + public String quotedTableIdString(TableId tableId) { + return "[" + tableId.catalog() + "].[" + tableId.schema() + "].[" + tableId.table() + "]"; + } + + private String replaceDatabaseNamePlaceholder(String sql, String databaseName) { + return sql.replace(DATABASE_NAME_PLACEHOLDER, databaseName); + } + + public SqlServerDefaultValueConverter getDefaultValueConverter() { + return defaultValueConverter; + } +} diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfig.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfig.java index 7d4062134aa..47eaa3d5a0b 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfig.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfig.java @@ -25,6 +25,7 @@ import io.debezium.relational.RelationalTableFilters; import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -41,6 +42,7 @@ public SqlServerSourceConfig( List databaseList, List tableList, int splitSize, + Map splitColumn, double distributionFactorUpper, double distributionFactorLower, int sampleShardingThreshold, @@ -64,6 +66,7 @@ public SqlServerSourceConfig( databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfigFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfigFactory.java index b9224653f35..8fcc6802315 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfigFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/config/SqlServerSourceConfigFactory.java @@ -26,7 +26,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** Factory for creating {@link SqlServerSourceConfig}. */ public class SqlServerSourceConfigFactory extends JdbcSourceConfigFactory { @@ -83,6 +83,7 @@ public SqlServerSourceConfig create(int subtask) { databaseList, tableList, splitSize, + splitColumn, distributionFactorUpper, distributionFactorLower, sampleShardingThreshold, diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerDialect.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerDialect.java index 816ba8eb4b9..55838d16b1c 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerDialect.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerDialect.java @@ -38,6 +38,8 @@ import org.apache.seatunnel.connectors.seatunnel.cdc.sqlserver.utils.TableDiscoveryUtils; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; +import io.debezium.connector.sqlserver.SqlServerChangeTable; +import io.debezium.connector.sqlserver.SqlServerConnection; import io.debezium.jdbc.JdbcConnection; import io.debezium.relational.TableId; import io.debezium.relational.history.TableChanges; @@ -46,6 +48,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; /** The {@link JdbcDataSourceDialect} implementation for MySQL datasource. */ public class SqlServerDialect implements JdbcDataSourceDialect { @@ -88,13 +92,37 @@ public ChunkSplitter createChunkSplitter(JdbcSourceConfig sourceConfig) { public List discoverDataCollections(JdbcSourceConfig sourceConfig) { SqlServerSourceConfig sqlServerSourceConfig = (SqlServerSourceConfig) sourceConfig; try (JdbcConnection jdbcConnection = openJdbcConnection(sourceConfig)) { - return TableDiscoveryUtils.listTables( - jdbcConnection, sqlServerSourceConfig.getTableFilters()); + List tables = + TableDiscoveryUtils.listTables( + jdbcConnection, sqlServerSourceConfig.getTableFilters()); + this.checkAllTablesEnabledCapture(jdbcConnection, tables); + return tables; } catch (SQLException e) { throw new SeaTunnelException("Error to discover tables: " + e.getMessage(), e); } } + @Override + public void checkAllTablesEnabledCapture(JdbcConnection jdbcConnection, List tableIds) + throws SQLException { + Map> databases = + tableIds.stream() + .collect(Collectors.groupingBy(TableId::catalog, Collectors.toList())); + for (String database : databases.keySet()) { + Set tables = + ((SqlServerConnection) jdbcConnection) + .getChangeTables(database).stream() + .map(SqlServerChangeTable::getSourceTableId) + .collect(Collectors.toSet()); + for (TableId tableId : databases.get(database)) { + if (!tables.contains(tableId)) { + throw new SeaTunnelException( + "Table " + tableId + " is not enabled for capture"); + } + } + } + } + @Override public TableChanges.TableChange queryTableSchema(JdbcConnection jdbc, TableId tableId) { if (sqlServerSchema == null) { @@ -115,6 +143,12 @@ public FetchTask createFetchTask(SourceSplitBase sourceSplitBas if (sourceSplitBase.isSnapshotSplit()) { return new SqlServerSnapshotFetchTask(sourceSplitBase.asSnapshotSplit()); } else { + try (JdbcConnection jdbcConnection = openJdbcConnection(sourceConfig)) { + List tables = sourceSplitBase.asIncrementalSplit().getTableIds(); + this.checkAllTablesEnabledCapture(jdbcConnection, tables); + } catch (SQLException e) { + throw new SeaTunnelException("Error to check tables: " + e.getMessage(), e); + } return new SqlServerTransactionLogFetchTask(sourceSplitBase.asIncrementalSplit()); } } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java index a1627c46d32..0c13d0e2d50 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSource.java @@ -21,8 +21,6 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.SupportParallelism; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.common.utils.JdbcUrlUtil; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceConfig; import org.apache.seatunnel.connectors.cdc.base.config.SourceConfig; @@ -49,11 +47,8 @@ public class SqlServerIncrementalSource extends IncrementalSource dataType, - List catalogTables) { - super(options, dataType, catalogTables); + public SqlServerIncrementalSource(ReadonlyConfig options, List catalogTables) { + super(options, catalogTables); } @Override @@ -96,12 +91,10 @@ public DebeziumDeserializationSchema createDebeziumDeserializationSchema( config.get(JdbcSourceOptions.DEBEZIUM_PROPERTIES)); } - SeaTunnelDataType physicalRowType = dataType; String zoneId = config.get(JdbcSourceOptions.SERVER_TIME_ZONE); return (DebeziumDeserializationSchema) SeaTunnelRowDebeziumDeserializeSchema.builder() - .setPhysicalRowType(physicalRowType) - .setResultTypeInfo(physicalRowType) + .setTables(catalogTables) .setServerTimeZone(ZoneId.of(zoneId)) .build(); } diff --git a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java index 99087c9ff52..3a898ca37d8 100644 --- a/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java +++ b/seatunnel-connectors-v2/connector-cdc/connector-cdc-sqlserver/src/main/java/org/apache/seatunnel/connectors/seatunnel/cdc/sqlserver/source/SqlServerIncrementalSourceFactory.java @@ -28,8 +28,6 @@ import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.cdc.base.config.JdbcSourceTableConfig; import org.apache.seatunnel.connectors.cdc.base.option.JdbcSourceOptions; import org.apache.seatunnel.connectors.cdc.base.option.SourceOptions; @@ -115,9 +113,7 @@ TableSource createSource(TableSourceFactoryContext context) { tableConfigs.get(), text -> TablePath.of(text, true)); } - SeaTunnelDataType dataType = - CatalogTableUtil.convertToMultipleRowType(catalogTables); - return new SqlServerIncrementalSource(context.getOptions(), dataType, catalogTables); + return new SqlServerIncrementalSource(context.getOptions(), catalogTables); }; } } diff --git a/seatunnel-connectors-v2/connector-clickhouse/pom.xml b/seatunnel-connectors-v2/connector-clickhouse/pom.xml index 2a4b77a3f45..22d2565a63a 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/pom.xml +++ b/seatunnel-connectors-v2/connector-clickhouse/pom.xml @@ -53,7 +53,7 @@ commons-io commons-io - 2.11.0 + 2.14.0 diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/ClickhouseSinkFactory.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/ClickhouseSinkFactory.java deleted file mode 100644 index 17ffdd2d406..00000000000 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/ClickhouseSinkFactory.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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. - */ - -package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink; - -import org.apache.seatunnel.api.configuration.util.OptionRule; -import org.apache.seatunnel.api.table.factory.Factory; -import org.apache.seatunnel.api.table.factory.TableSinkFactory; - -import com.google.auto.service.AutoService; - -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.BULK_SIZE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.CLICKHOUSE_CONFIG; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.DATABASE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.HOST; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PASSWORD; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PRIMARY_KEY; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SHARDING_KEY; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SPLIT_MODE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SUPPORT_UPSERT; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.TABLE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.USERNAME; - -@AutoService(Factory.class) -public class ClickhouseSinkFactory implements TableSinkFactory { - @Override - public String factoryIdentifier() { - return "Clickhouse"; - } - - @Override - public OptionRule optionRule() { - return OptionRule.builder() - .required(HOST, DATABASE, TABLE) - .optional( - CLICKHOUSE_CONFIG, - BULK_SIZE, - SPLIT_MODE, - SHARDING_KEY, - PRIMARY_KEY, - SUPPORT_UPSERT, - ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE) - .bundled(USERNAME, PASSWORD) - .build(); - } -} diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseBatchStatement.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseBatchStatement.java index 52397229dbc..04ee5755e59 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseBatchStatement.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseBatchStatement.java @@ -18,7 +18,7 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client; import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.executor.JdbcBatchStatementExecutor; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.tool.IntHolder; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.IntHolder; import com.clickhouse.jdbc.internal.ClickHouseConnectionImpl; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSink.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSink.java index d2de6fd182b..22f18694e23 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSink.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSink.java @@ -17,210 +17,35 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client; -import org.apache.seatunnel.shade.com.typesafe.config.Config; -import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; - -import org.apache.seatunnel.api.common.PrepareFailException; -import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.serialization.DefaultSerializer; import org.apache.seatunnel.api.serialization.Serializer; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.common.config.CheckConfigUtil; -import org.apache.seatunnel.common.config.CheckResult; -import org.apache.seatunnel.common.constants.PluginType; -import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ReaderOption; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.ShardMetadata; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.file.ClickhouseTable; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKAggCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.ClickhouseSinkState; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseUtil; - -import com.clickhouse.client.ClickHouseNode; -import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Properties; - -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.BULK_SIZE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.CLICKHOUSE_CONFIG; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.DATABASE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.HOST; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PASSWORD; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PRIMARY_KEY; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SERVER_TIME_ZONE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SHARDING_KEY; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SPLIT_MODE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SUPPORT_UPSERT; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.TABLE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.USERNAME; -@AutoService(SeaTunnelSink.class) public class ClickhouseSink implements SeaTunnelSink { private ReaderOption option; + private CatalogTable catalogTable; - @Override - public String getPluginName() { - return "Clickhouse"; + public ClickhouseSink(ReaderOption option, CatalogTable catalogTable) { + this.option = option; + this.catalogTable = catalogTable; } @Override - public void prepare(Config config) throws PrepareFailException { - CheckResult result = - CheckConfigUtil.checkAllExists(config, HOST.key(), DATABASE.key(), TABLE.key()); - - boolean isCredential = config.hasPath(USERNAME.key()) || config.hasPath(PASSWORD.key()); - - if (isCredential) { - result = CheckConfigUtil.checkAllExists(config, USERNAME.key(), PASSWORD.key()); - } - - if (!result.isSuccess()) { - throw new ClickhouseConnectorException( - SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, - String.format( - "PluginName: %s, PluginType: %s, Message: %s", - getPluginName(), PluginType.SINK, result.getMsg())); - } - Map defaultConfig = - ImmutableMap.builder() - .put(BULK_SIZE.key(), BULK_SIZE.defaultValue()) - .put(SPLIT_MODE.key(), SPLIT_MODE.defaultValue()) - .put(SERVER_TIME_ZONE.key(), SERVER_TIME_ZONE.defaultValue()) - .build(); - - config = config.withFallback(ConfigFactory.parseMap(defaultConfig)); - - List nodes; - if (!isCredential) { - nodes = - ClickhouseUtil.createNodes( - config.getString(HOST.key()), - config.getString(DATABASE.key()), - config.getString(SERVER_TIME_ZONE.key()), - null, - null, - null); - } else { - nodes = - ClickhouseUtil.createNodes( - config.getString(HOST.key()), - config.getString(DATABASE.key()), - config.getString(SERVER_TIME_ZONE.key()), - config.getString(USERNAME.key()), - config.getString(PASSWORD.key()), - null); - } - - Properties clickhouseProperties = new Properties(); - if (CheckConfigUtil.isValidParam(config, CLICKHOUSE_CONFIG.key())) { - config.getObject(CLICKHOUSE_CONFIG.key()) - .forEach( - (key, value) -> - clickhouseProperties.put( - key, String.valueOf(value.unwrapped()))); - } - - if (isCredential) { - clickhouseProperties.put("user", config.getString(USERNAME.key())); - clickhouseProperties.put("password", config.getString(PASSWORD.key())); - } - - ClickhouseProxy proxy = new ClickhouseProxy(nodes.get(0)); - Map tableSchema = - proxy.getClickhouseTableSchema(config.getString(TABLE.key())); - String shardKey = null; - String shardKeyType = null; - ClickhouseTable table = - proxy.getClickhouseTable( - config.getString(DATABASE.key()), config.getString(TABLE.key())); - if (config.getBoolean(SPLIT_MODE.key())) { - if (!"Distributed".equals(table.getEngine())) { - throw new ClickhouseConnectorException( - CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, - "split mode only support table which engine is " - + "'Distributed' engine at now"); - } - if (config.hasPath(SHARDING_KEY.key())) { - shardKey = config.getString(SHARDING_KEY.key()); - shardKeyType = tableSchema.get(shardKey); - } - } - ShardMetadata metadata; - - if (isCredential) { - metadata = - new ShardMetadata( - shardKey, - shardKeyType, - table.getSortingKey(), - config.getString(DATABASE.key()), - config.getString(TABLE.key()), - table.getEngine(), - config.getBoolean(SPLIT_MODE.key()), - new Shard(1, 1, nodes.get(0)), - config.getString(USERNAME.key()), - config.getString(PASSWORD.key())); - } else { - metadata = - new ShardMetadata( - shardKey, - shardKeyType, - table.getSortingKey(), - config.getString(DATABASE.key()), - config.getString(TABLE.key()), - table.getEngine(), - config.getBoolean(SPLIT_MODE.key()), - new Shard(1, 1, nodes.get(0))); - } - - proxy.close(); - - String[] primaryKeys = null; - if (config.hasPath(PRIMARY_KEY.key())) { - String primaryKey = config.getString(PRIMARY_KEY.key()); - if (shardKey != null && !Objects.equals(primaryKey, shardKey)) { - throw new ClickhouseConnectorException( - CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, - "sharding_key and primary_key must be consistent to ensure correct processing of cdc events"); - } - primaryKeys = new String[] {primaryKey}; - } - boolean supportUpsert = SUPPORT_UPSERT.defaultValue(); - if (config.hasPath(SUPPORT_UPSERT.key())) { - supportUpsert = config.getBoolean(SUPPORT_UPSERT.key()); - } - boolean allowExperimentalLightweightDelete = - ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE.defaultValue(); - if (config.hasPath(ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE.key())) { - allowExperimentalLightweightDelete = - config.getBoolean(ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE.key()); - } - this.option = - ReaderOption.builder() - .shardMetadata(metadata) - .properties(clickhouseProperties) - .tableEngine(table.getEngine()) - .tableSchema(tableSchema) - .bulkSize(config.getInt(BULK_SIZE.key())) - .primaryKeys(primaryKeys) - .supportUpsert(supportUpsert) - .allowExperimentalLightweightDelete(allowExperimentalLightweightDelete) - .build(); + public String getPluginName() { + return "Clickhouse"; } @Override @@ -241,7 +66,7 @@ public Optional> getWriterStateSerializer() { } @Override - public void setTypeInfo(SeaTunnelRowType seaTunnelRowType) { - this.option.setSeaTunnelRowType(seaTunnelRowType); + public Optional getWriteCatalogTable() { + return Optional.of(catalogTable); } } diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkFactory.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkFactory.java new file mode 100644 index 00000000000..edc36eabbaf --- /dev/null +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkFactory.java @@ -0,0 +1,167 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.connector.TableSink; +import org.apache.seatunnel.api.table.factory.Factory; +import org.apache.seatunnel.api.table.factory.TableSinkFactory; +import org.apache.seatunnel.api.table.factory.TableSinkFactoryContext; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ReaderOption; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.ShardMetadata; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.file.ClickhouseTable; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKAggCommitInfo; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKCommitInfo; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.ClickhouseSinkState; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseProxy; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseUtil; + +import com.clickhouse.client.ClickHouseNode; +import com.google.auto.service.AutoService; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.BULK_SIZE; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.CLICKHOUSE_CONFIG; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.DATABASE; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.HOST; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PASSWORD; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PRIMARY_KEY; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SHARDING_KEY; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SPLIT_MODE; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SUPPORT_UPSERT; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.TABLE; +import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.USERNAME; + +@AutoService(Factory.class) +public class ClickhouseSinkFactory implements TableSinkFactory { + @Override + public String factoryIdentifier() { + return "Clickhouse"; + } + + @Override + public TableSink createSink( + TableSinkFactoryContext context) { + ReadonlyConfig readonlyConfig = context.getOptions(); + CatalogTable catalogTable = context.getCatalogTable(); + List nodes = ClickhouseUtil.createNodes(readonlyConfig); + Properties clickhouseProperties = new Properties(); + readonlyConfig + .get(CLICKHOUSE_CONFIG) + .forEach((key, value) -> clickhouseProperties.put(key, String.valueOf(value))); + + clickhouseProperties.put("user", readonlyConfig.get(USERNAME)); + clickhouseProperties.put("password", readonlyConfig.get(PASSWORD)); + ClickhouseProxy proxy = new ClickhouseProxy(nodes.get(0)); + try { + Map tableSchema = + proxy.getClickhouseTableSchema(readonlyConfig.get(TABLE)); + String shardKey = null; + String shardKeyType = null; + ClickhouseTable table = + proxy.getClickhouseTable( + readonlyConfig.get(DATABASE), readonlyConfig.get(TABLE)); + if (readonlyConfig.get(SPLIT_MODE)) { + if (!"Distributed".equals(table.getEngine())) { + throw new ClickhouseConnectorException( + CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, + "split mode only support table which engine is " + + "'Distributed' engine at now"); + } + if (readonlyConfig.getOptional(SHARDING_KEY).isPresent()) { + shardKey = readonlyConfig.get(SHARDING_KEY); + shardKeyType = tableSchema.get(shardKey); + } + } + ShardMetadata metadata = + new ShardMetadata( + shardKey, + shardKeyType, + table.getSortingKey(), + readonlyConfig.get(DATABASE), + readonlyConfig.get(TABLE), + table.getEngine(), + readonlyConfig.get(SPLIT_MODE), + new Shard(1, 1, nodes.get(0)), + readonlyConfig.get(USERNAME), + readonlyConfig.get(PASSWORD)); + proxy.close(); + String[] primaryKeys = null; + if (readonlyConfig.getOptional(PRIMARY_KEY).isPresent()) { + String primaryKey = readonlyConfig.get(PRIMARY_KEY); + if (primaryKey == null || primaryKey.trim().isEmpty()) { + throw new ClickhouseConnectorException( + CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, + "primary_key can not be empty"); + } + if (shardKey != null && !Objects.equals(primaryKey, shardKey)) { + throw new ClickhouseConnectorException( + CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, + "sharding_key and primary_key must be consistent to ensure correct processing of cdc events"); + } + primaryKeys = primaryKey.replaceAll("\\s+", "").split(","); + } + boolean supportUpsert = readonlyConfig.get(SUPPORT_UPSERT); + boolean allowExperimentalLightweightDelete = + readonlyConfig.get(ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE); + + ReaderOption option = + ReaderOption.builder() + .shardMetadata(metadata) + .properties(clickhouseProperties) + .seaTunnelRowType(catalogTable.getSeaTunnelRowType()) + .tableEngine(table.getEngine()) + .tableSchema(tableSchema) + .bulkSize(readonlyConfig.get(BULK_SIZE)) + .primaryKeys(primaryKeys) + .supportUpsert(supportUpsert) + .allowExperimentalLightweightDelete(allowExperimentalLightweightDelete) + .build(); + return () -> new ClickhouseSink(option, catalogTable); + } finally { + proxy.close(); + } + } + + @Override + public OptionRule optionRule() { + return OptionRule.builder() + .required(HOST, DATABASE, TABLE) + .optional( + CLICKHOUSE_CONFIG, + BULK_SIZE, + SPLIT_MODE, + SHARDING_KEY, + PRIMARY_KEY, + SUPPORT_UPSERT, + ALLOW_EXPERIMENTAL_LIGHTWEIGHT_DELETE) + .bundled(USERNAME, PASSWORD) + .build(); + } +} diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkWriter.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkWriter.java index b5f1505d112..3f234bc2ee7 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkWriter.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseSinkWriter.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.common.config.Common; @@ -28,12 +30,12 @@ import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.executor.JdbcBatchStatementExecutorBuilder; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.ClickhouseSinkState; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.tool.IntHolder; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseProxy; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.IntHolder; import org.apache.commons.lang3.StringUtils; import com.clickhouse.jdbc.internal.ClickHouseConnectionImpl; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import java.io.IOException; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ShardRouter.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ShardRouter.java index 140e40b3b13..03f6efec311 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ShardRouter.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ShardRouter.java @@ -21,7 +21,8 @@ import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.ShardMetadata; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.DistributedEngine; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseProxy; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.DistributedEngine; import org.apache.commons.lang3.StringUtils; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSink.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSink.java index cc63179f164..8add2fb0860 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSink.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSink.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.file; +import org.apache.seatunnel.shade.com.google.common.collect.ImmutableMap; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; @@ -27,6 +28,7 @@ import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkAggregatedCommitter; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -37,15 +39,14 @@ import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.ShardMetadata; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.ClickhouseProxy; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKFileAggCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKFileCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.ClickhouseSinkState; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseProxy; import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseUtil; import com.clickhouse.client.ClickHouseNode; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.util.ArrayList; @@ -212,4 +213,9 @@ public Optional> getCommitInfoSerializer() { public Optional> getAggregatedCommitInfoSerializer() { return Optional.of(new DefaultSerializer<>()); } + + @Override + public Optional getWriteCatalogTable() { + return SeaTunnelSink.super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkAggCommitter.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkAggCommitter.java index 53e5fcb5ab0..5d69191cac0 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkAggCommitter.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkAggCommitter.java @@ -21,9 +21,9 @@ import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.seatunnel.clickhouse.config.FileReaderOption; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.ClickhouseProxy; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKFileAggCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKFileCommitInfo; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseProxy; import com.clickhouse.client.ClickHouseException; import com.clickhouse.client.ClickHouseRequest; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkWriter.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkWriter.java index 2abeb046470..e705acc7683 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkWriter.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseFileSinkWriter.java @@ -26,10 +26,10 @@ import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.ClickhouseProxy; import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.ShardRouter; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.CKFileCommitInfo; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.ClickhouseSinkState; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseProxy; import org.apache.commons.io.FileUtils; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseTable.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseTable.java index 546f1f74660..2525caeb3d0 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseTable.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/file/ClickhouseTable.java @@ -17,7 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.file; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.DistributedEngine; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.DistributedEngine; import lombok.Getter; import lombok.Setter; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSource.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSource.java index 2cc401dce24..d7c6b438564 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSource.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSource.java @@ -17,142 +17,39 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.source; -import org.apache.seatunnel.shade.com.typesafe.config.Config; -import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; - -import org.apache.seatunnel.api.common.PrepareFailException; -import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.source.Boundedness; import org.apache.seatunnel.api.source.SeaTunnelSource; import org.apache.seatunnel.api.source.SourceReader; import org.apache.seatunnel.api.source.SourceSplitEnumerator; import org.apache.seatunnel.api.source.SupportColumnProjection; import org.apache.seatunnel.api.source.SupportParallelism; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.common.config.CheckConfigUtil; -import org.apache.seatunnel.common.config.CheckResult; -import org.apache.seatunnel.common.constants.PluginType; -import org.apache.seatunnel.common.utils.ExceptionUtils; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; import org.apache.seatunnel.connectors.seatunnel.clickhouse.state.ClickhouseSourceState; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseUtil; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.TypeConvertUtil; -import com.clickhouse.client.ClickHouseClient; -import com.clickhouse.client.ClickHouseException; -import com.clickhouse.client.ClickHouseFormat; import com.clickhouse.client.ClickHouseNode; -import com.clickhouse.client.ClickHouseResponse; -import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableMap; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.Collectors; - -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.CLICKHOUSE_CONFIG; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.DATABASE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.HOST; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.PASSWORD; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SERVER_TIME_ZONE; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.SQL; -import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.USERNAME; -@AutoService(SeaTunnelSource.class) public class ClickhouseSource implements SeaTunnelSource, SupportParallelism, SupportColumnProjection { private List servers; - private SeaTunnelRowType rowTypeInfo; + private CatalogTable catalogTable; private String sql; - @Override - public String getPluginName() { - return "Clickhouse"; + public ClickhouseSource(List servers, CatalogTable catalogTable, String sql) { + this.servers = servers; + this.catalogTable = catalogTable; + this.sql = sql; } @Override - public void prepare(Config config) throws PrepareFailException { - CheckResult result = - CheckConfigUtil.checkAllExists( - config, - HOST.key(), - DATABASE.key(), - SQL.key(), - USERNAME.key(), - PASSWORD.key()); - if (!result.isSuccess()) { - throw new ClickhouseConnectorException( - SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, - String.format( - "PluginName: %s, PluginType: %s, Message: %s", - getPluginName(), PluginType.SOURCE, result.getMsg())); - } - Map defaultConfig = - ImmutableMap.builder() - .put(SERVER_TIME_ZONE.key(), SERVER_TIME_ZONE.defaultValue()) - .build(); - - config = config.withFallback(ConfigFactory.parseMap(defaultConfig)); - - Map customConfig = null; - - if (CheckConfigUtil.isValidParam(config, CLICKHOUSE_CONFIG.key())) { - customConfig = - config.getObject(CLICKHOUSE_CONFIG.key()).entrySet().stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - entrySet -> - entrySet.getValue().unwrapped().toString())); - } - - servers = - ClickhouseUtil.createNodes( - config.getString(HOST.key()), - config.getString(DATABASE.key()), - config.getString(SERVER_TIME_ZONE.key()), - config.getString(USERNAME.key()), - config.getString(PASSWORD.key()), - customConfig); - - sql = config.getString(SQL.key()); - ClickHouseNode currentServer = - servers.get(ThreadLocalRandom.current().nextInt(servers.size())); - try (ClickHouseClient client = ClickHouseClient.newInstance(currentServer.getProtocol()); - ClickHouseResponse response = - client.connect(currentServer) - .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) - .query(modifySQLToLimit1(config.getString(SQL.key()))) - .executeAndWait()) { - - int columnSize = response.getColumns().size(); - String[] fieldNames = new String[columnSize]; - SeaTunnelDataType[] seaTunnelDataTypes = new SeaTunnelDataType[columnSize]; - - for (int i = 0; i < columnSize; i++) { - fieldNames[i] = response.getColumns().get(i).getColumnName(); - seaTunnelDataTypes[i] = TypeConvertUtil.convert(response.getColumns().get(i)); - } - - this.rowTypeInfo = new SeaTunnelRowType(fieldNames, seaTunnelDataTypes); - - } catch (ClickHouseException e) { - throw new ClickhouseConnectorException( - SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, - String.format( - "PluginName: %s, PluginType: %s, Message: %s", - getPluginName(), PluginType.SOURCE, ExceptionUtils.getMessage(e))); - } - } - - private String modifySQLToLimit1(String sql) { - return String.format("SELECT * FROM (%s) s LIMIT 1", sql); + public String getPluginName() { + return "Clickhouse"; } @Override @@ -161,14 +58,15 @@ public Boundedness getBoundedness() { } @Override - public SeaTunnelRowType getProducedType() { - return this.rowTypeInfo; + public List getProducedCatalogTables() { + return Collections.singletonList(catalogTable); } @Override public SourceReader createReader( SourceReader.Context readerContext) throws Exception { - return new ClickhouseSourceReader(servers, readerContext, this.rowTypeInfo, sql); + return new ClickhouseSourceReader( + servers, readerContext, this.catalogTable.getSeaTunnelRowType(), sql); } @Override diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceFactory.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceFactory.java index 4adea4b80ce..bb91d3c05ea 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceFactory.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceFactory.java @@ -17,13 +17,37 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.source; +import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; +import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.configuration.util.OptionRule; import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.source.SourceSplit; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; +import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; +import org.apache.seatunnel.common.constants.PluginType; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.ClickhouseUtil; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.util.TypeConvertUtil; +import com.clickhouse.client.ClickHouseClient; +import com.clickhouse.client.ClickHouseColumn; +import com.clickhouse.client.ClickHouseException; +import com.clickhouse.client.ClickHouseFormat; +import com.clickhouse.client.ClickHouseNode; +import com.clickhouse.client.ClickHouseResponse; import com.google.auto.service.AutoService; +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.CLICKHOUSE_CONFIG; import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.DATABASE; import static org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig.HOST; @@ -38,6 +62,61 @@ public String factoryIdentifier() { return "Clickhouse"; } + @Override + public + TableSource createSource(TableSourceFactoryContext context) { + ReadonlyConfig readonlyConfig = context.getOptions(); + List nodes = ClickhouseUtil.createNodes(readonlyConfig); + + String sql = readonlyConfig.get(SQL); + ClickHouseNode currentServer = nodes.get(ThreadLocalRandom.current().nextInt(nodes.size())); + try (ClickHouseClient client = ClickHouseClient.newInstance(currentServer.getProtocol()); + ClickHouseResponse response = + client.connect(currentServer) + .format(ClickHouseFormat.RowBinaryWithNamesAndTypes) + .query(modifySQLToLimit1(sql)) + .executeAndWait()) { + TableSchema.Builder builder = TableSchema.builder(); + List columns = response.getColumns(); + columns.forEach( + column -> { + PhysicalColumn physicalColumn = + PhysicalColumn.of( + column.getColumnName(), + TypeConvertUtil.convert(column), + (long) column.getEstimatedLength(), + column.getScale(), + column.isNullable(), + null, + null); + builder.column(physicalColumn); + }); + String catalogName = "clickhouse_catalog"; + CatalogTable catalogTable = + CatalogTable.of( + TableIdentifier.of( + catalogName, readonlyConfig.get(DATABASE), "default"), + builder.build(), + Collections.emptyMap(), + Collections.emptyList(), + "", + catalogName); + return () -> + (SeaTunnelSource) + new ClickhouseSource(nodes, catalogTable, sql); + } catch (ClickHouseException e) { + throw new ClickhouseConnectorException( + SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, + String.format( + "PluginName: %s, PluginType: %s, Message: %s", + factoryIdentifier(), PluginType.SOURCE, e.getMessage())); + } + } + + private String modifySQLToLimit1(String sql) { + return String.format("SELECT * FROM (%s) s LIMIT 1", sql); + } + @Override public OptionRule optionRule() { return OptionRule.builder() diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceReader.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceReader.java index 591334d9722..3ad0ec041e6 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceReader.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceReader.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.source; +import org.apache.seatunnel.api.source.Boundedness; import org.apache.seatunnel.api.source.Collector; import org.apache.seatunnel.api.source.SourceReader; import org.apache.seatunnel.api.table.type.SeaTunnelRow; @@ -28,6 +29,7 @@ import com.clickhouse.client.ClickHouseNode; import com.clickhouse.client.ClickHouseRequest; import com.clickhouse.client.ClickHouseResponse; +import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.util.ArrayList; @@ -35,6 +37,7 @@ import java.util.List; import java.util.Random; +@Slf4j public class ClickhouseSourceReader implements SourceReader { private final List servers; @@ -43,6 +46,7 @@ public class ClickhouseSourceReader implements SourceReader request; private final String sql; + private volatile boolean noMoreSplit; private final List splits; @@ -75,31 +79,43 @@ public void close() throws IOException { @Override public void pollNext(Collector output) throws Exception { - if (!splits.isEmpty()) { - try (ClickHouseResponse response = this.request.query(sql).executeAndWait()) { - response.stream() - .forEach( - record -> { - Object[] values = - new Object[this.rowTypeInfo.getFieldNames().length]; - for (int i = 0; i < record.size(); i++) { - if (record.getValue(i).isNullOrEmpty()) { - values[i] = null; - } else { - values[i] = - TypeConvertUtil.valueUnwrap( - this.rowTypeInfo.getFieldType(i), - record.getValue(i)); + synchronized (output.getCheckpointLock()) { + if (!splits.isEmpty()) { + try (ClickHouseResponse response = this.request.query(sql).executeAndWait()) { + response.stream() + .forEach( + record -> { + Object[] values = + new Object[this.rowTypeInfo.getFieldNames().length]; + for (int i = 0; i < record.size(); i++) { + if (record.getValue(i).isNullOrEmpty()) { + values[i] = null; + } else { + values[i] = + TypeConvertUtil.valueUnwrap( + this.rowTypeInfo.getFieldType(i), + record.getValue(i)); + } } - } - output.collect(new SeaTunnelRow(values)); - }); + output.collect(new SeaTunnelRow(values)); + }); + } + signalNoMoreElement(); + } + if (noMoreSplit + && splits.isEmpty() + && Boundedness.BOUNDED.equals(readerContext.getBoundedness())) { + signalNoMoreElement(); } - this.readerContext.signalNoMoreElement(); - this.splits.clear(); } } + private void signalNoMoreElement() { + log.info("Closed the bounded ClickHouse source"); + this.readerContext.signalNoMoreElement(); + this.splits.clear(); + } + @Override public List snapshotState(long checkpointId) throws Exception { return Collections.emptyList(); @@ -111,7 +127,9 @@ public void addSplits(List splits) { } @Override - public void handleNoMoreSplits() {} + public void handleNoMoreSplits() { + noMoreSplit = true; + } @Override public void notifyCheckpointComplete(long checkpointId) throws Exception {} diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceSplitEnumerator.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceSplitEnumerator.java index c0eb4b6c706..f3c1bd0c47b 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceSplitEnumerator.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/source/ClickhouseSourceSplitEnumerator.java @@ -78,6 +78,7 @@ public void registerReader(int subtaskId) { assigned = subtaskId; context.assignSplit(subtaskId, new ClickhouseSourceSplit()); } + context.signalNoMoreSplits(subtaskId); } @Override diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseProxy.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseProxy.java similarity index 98% rename from seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseProxy.java rename to seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseProxy.java index bf0f9a55520..c4178182578 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/client/ClickhouseProxy.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseProxy.java @@ -15,14 +15,13 @@ * limitations under the License. */ -package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client; +package org.apache.seatunnel.connectors.seatunnel.clickhouse.util; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.clickhouse.exception.ClickhouseConnectorException; import org.apache.seatunnel.connectors.seatunnel.clickhouse.shard.Shard; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.DistributedEngine; import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.file.ClickhouseTable; import com.clickhouse.client.ClickHouseClient; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseUtil.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseUtil.java index f787cf5c8fc..13667d0e407 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseUtil.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/ClickhouseUtil.java @@ -17,6 +17,9 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse.util; +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.config.ClickhouseConfig; + import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; @@ -31,6 +34,16 @@ public class ClickhouseUtil { + public static List createNodes(ReadonlyConfig config) { + return createNodes( + config.get(ClickhouseConfig.HOST), + config.get(ClickhouseConfig.DATABASE), + config.get(ClickhouseConfig.SERVER_TIME_ZONE), + config.get(ClickhouseConfig.USERNAME), + config.get(ClickhouseConfig.PASSWORD), + null); + } + public static List createNodes( String nodeAddress, String database, diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/DistributedEngine.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/DistributedEngine.java similarity index 94% rename from seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/DistributedEngine.java rename to seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/DistributedEngine.java index 067f09fdbc2..8974b7cd0c3 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/sink/DistributedEngine.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/DistributedEngine.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.connectors.seatunnel.clickhouse.sink; +package org.apache.seatunnel.connectors.seatunnel.clickhouse.util; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/tool/IntHolder.java b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/IntHolder.java similarity index 94% rename from seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/tool/IntHolder.java rename to seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/IntHolder.java index 02e7be5966d..9913d7a408e 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/tool/IntHolder.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/main/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/util/IntHolder.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.apache.seatunnel.connectors.seatunnel.clickhouse.tool; +package org.apache.seatunnel.connectors.seatunnel.clickhouse.util; import java.io.Serializable; diff --git a/seatunnel-connectors-v2/connector-clickhouse/src/test/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/ClickhouseFactoryTest.java b/seatunnel-connectors-v2/connector-clickhouse/src/test/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/ClickhouseFactoryTest.java index e6c50b0611a..d193b53ea72 100644 --- a/seatunnel-connectors-v2/connector-clickhouse/src/test/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/ClickhouseFactoryTest.java +++ b/seatunnel-connectors-v2/connector-clickhouse/src/test/java/org/apache/seatunnel/connectors/seatunnel/clickhouse/ClickhouseFactoryTest.java @@ -17,7 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.clickhouse; -import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.ClickhouseSinkFactory; +import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.client.ClickhouseSinkFactory; import org.apache.seatunnel.connectors.seatunnel.clickhouse.sink.file.ClickhouseFileSinkFactory; import org.apache.seatunnel.connectors.seatunnel.clickhouse.source.ClickhouseSourceFactory; diff --git a/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/AbstractSingleSplitReader.java b/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/AbstractSingleSplitReader.java index 31385d0d470..d8dd6fae1e7 100644 --- a/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/AbstractSingleSplitReader.java +++ b/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/AbstractSingleSplitReader.java @@ -26,13 +26,11 @@ public abstract class AbstractSingleSplitReader implements SourceReader { - protected final Object lock = new Object(); - protected volatile boolean noMoreSplits = false; @Override public void pollNext(Collector output) throws Exception { - synchronized (lock) { + synchronized (output.getCheckpointLock()) { if (noMoreSplits) { return; } diff --git a/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/RecordsBySplits.java b/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/RecordsBySplits.java index 1724b459eaa..628136f25ce 100644 --- a/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/RecordsBySplits.java +++ b/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/RecordsBySplits.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Set; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; public class RecordsBySplits implements RecordsWithSplitIds { diff --git a/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/SourceReaderBase.java b/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/SourceReaderBase.java index 7ec6cc83e9e..dfdfef22916 100644 --- a/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/SourceReaderBase.java +++ b/seatunnel-connectors-v2/connector-common/src/main/java/org/apache/seatunnel/connectors/seatunnel/common/source/reader/SourceReaderBase.java @@ -39,7 +39,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import static com.google.common.base.Preconditions.checkState; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; /** * An abstract implementation of {@link SourceReader} which provides some synchronization between diff --git a/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSink.java b/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSink.java index 62ebab6a9ff..6a1dbbbbe2c 100644 --- a/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSink.java +++ b/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSink.java @@ -20,23 +20,32 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSink; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.schema.SchemaChangeType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + import static org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkFactory.LOG_PRINT_DATA; import static org.apache.seatunnel.connectors.seatunnel.console.sink.ConsoleSinkFactory.LOG_PRINT_DELAY; public class ConsoleSink extends AbstractSimpleSink - implements SupportMultiTableSink { + implements SupportMultiTableSink, SupportSchemaEvolutionSink { private final SeaTunnelRowType seaTunnelRowType; private final boolean isPrintData; private final int delayMs; + private final CatalogTable catalogTable; - public ConsoleSink(SeaTunnelRowType seaTunnelRowType, ReadonlyConfig options) { - this.seaTunnelRowType = seaTunnelRowType; + public ConsoleSink(CatalogTable catalogTable, ReadonlyConfig options) { + this.catalogTable = catalogTable; this.isPrintData = options.get(LOG_PRINT_DATA); this.delayMs = options.get(LOG_PRINT_DELAY); + this.seaTunnelRowType = catalogTable.getTableSchema().toPhysicalRowDataType(); } @Override @@ -48,4 +57,18 @@ public ConsoleSinkWriter createWriter(SinkWriter.Context context) { public String getPluginName() { return "Console"; } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } + + @Override + public List supports() { + return Arrays.asList( + SchemaChangeType.ADD_COLUMN, + SchemaChangeType.DROP_COLUMN, + SchemaChangeType.RENAME_COLUMN, + SchemaChangeType.UPDATE_COLUMN); + } } diff --git a/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkFactory.java b/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkFactory.java index fa5c7deae9e..72987032bc8 100644 --- a/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkFactory.java +++ b/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkFactory.java @@ -62,9 +62,6 @@ public OptionRule optionRule() { @Override public TableSink createSink(TableSinkFactoryContext context) { ReadonlyConfig options = context.getOptions(); - return () -> - new ConsoleSink( - context.getCatalogTable().getTableSchema().toPhysicalRowDataType(), - options); + return () -> new ConsoleSink(context.getCatalogTable(), options); } } diff --git a/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkWriter.java b/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkWriter.java index d83e8b5c96b..ac6f5d8f4fe 100644 --- a/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkWriter.java +++ b/seatunnel-connectors-v2/connector-console/src/main/java/org/apache/seatunnel/connectors/seatunnel/console/sink/ConsoleSinkWriter.java @@ -19,9 +19,10 @@ import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSinkWriter; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; -import org.apache.seatunnel.api.table.event.handler.DataTypeChangeEventDispatcher; -import org.apache.seatunnel.api.table.event.handler.DataTypeChangeEventHandler; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSinkWriter; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.handler.DataTypeChangeEventDispatcher; +import org.apache.seatunnel.api.table.schema.handler.DataTypeChangeEventHandler; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -40,7 +41,7 @@ @Slf4j public class ConsoleSinkWriter extends AbstractSinkWriter - implements SupportMultiTableSinkWriter { + implements SupportMultiTableSinkWriter, SupportSchemaEvolutionSinkWriter { private SeaTunnelRowType seaTunnelRowType; private final AtomicLong rowCounter = new AtomicLong(0); diff --git a/seatunnel-connectors-v2/connector-datahub/src/main/java/org/apache/seatunnel/connectors/seatunnel/datahub/sink/DataHubSink.java b/seatunnel-connectors-v2/connector-datahub/src/main/java/org/apache/seatunnel/connectors/seatunnel/datahub/sink/DataHubSink.java index 8eeffb3aca5..b22c236e7a3 100644 --- a/seatunnel-connectors-v2/connector-datahub/src/main/java/org/apache/seatunnel/connectors/seatunnel/datahub/sink/DataHubSink.java +++ b/seatunnel-connectors-v2/connector-datahub/src/main/java/org/apache/seatunnel/connectors/seatunnel/datahub/sink/DataHubSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter.Context; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -35,6 +36,7 @@ import com.google.auto.service.AutoService; import java.io.IOException; +import java.util.Optional; import static org.apache.seatunnel.connectors.seatunnel.datahub.config.DataHubConfig.ACCESS_ID; import static org.apache.seatunnel.connectors.seatunnel.datahub.config.DataHubConfig.ACCESS_KEY; @@ -93,4 +95,9 @@ public AbstractSinkWriter createWriter(Context context) thro pluginConfig.getInt(TIMEOUT.key()), pluginConfig.getInt(RETRY_TIMES.key())); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-dingtalk/src/main/java/org/apache/seatunnel/connectors/seatunnel/sink/DingTalkSink.java b/seatunnel-connectors-v2/connector-dingtalk/src/main/java/org/apache/seatunnel/connectors/seatunnel/sink/DingTalkSink.java index 8d9d4b973e9..42ec1e688a1 100644 --- a/seatunnel-connectors-v2/connector-dingtalk/src/main/java/org/apache/seatunnel/connectors/seatunnel/sink/DingTalkSink.java +++ b/seatunnel-connectors-v2/connector-dingtalk/src/main/java/org/apache/seatunnel/connectors/seatunnel/sink/DingTalkSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter.Context; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; @@ -32,6 +33,7 @@ import com.google.auto.service.AutoService; import java.io.IOException; +import java.util.Optional; import static org.apache.seatunnel.connectors.seatunnel.config.DingTalkConfig.SECRET; import static org.apache.seatunnel.connectors.seatunnel.config.DingTalkConfig.URL; @@ -73,4 +75,9 @@ public AbstractSinkWriter createWriter(Context context) thro return new DingTalkWriter( pluginConfig.getString(URL.key()), pluginConfig.getString(SECRET.key())); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/backend/BackendClient.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/backend/BackendClient.java index 31bdb2a78e7..04f96d2d607 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/backend/BackendClient.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/backend/BackendClient.java @@ -25,7 +25,7 @@ import org.apache.seatunnel.shade.org.apache.thrift.transport.TTransport; import org.apache.seatunnel.shade.org.apache.thrift.transport.TTransportException; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.source.serialization.Routing; @@ -55,7 +55,7 @@ public class BackendClient { private final int socketTimeout; private final int connectTimeout; - public BackendClient(Routing routing, DorisConfig readOptions) { + public BackendClient(Routing routing, DorisSourceConfig readOptions) { this.routing = routing; this.connectTimeout = readOptions.getRequestConnectTimeoutMs(); this.socketTimeout = readOptions.getRequestReadTimeoutMs(); diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalog.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalog.java index a7f5eabf63d..ebc8f937851 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalog.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalog.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.doris.catalog; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + import org.apache.seatunnel.api.sink.SaveModePlaceHolder; import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.catalog.CatalogTable; @@ -37,7 +39,6 @@ import org.apache.seatunnel.common.exception.CommonError; import org.apache.seatunnel.common.exception.CommonErrorCode; import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; import org.apache.seatunnel.connectors.doris.config.DorisOptions; import org.apache.seatunnel.connectors.doris.datatype.DorisTypeConverterFactory; import org.apache.seatunnel.connectors.doris.datatype.DorisTypeConverterV2; @@ -49,8 +50,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Preconditions; - import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; @@ -65,7 +64,7 @@ import java.util.Map; import java.util.Optional; -import static com.google.common.base.Preconditions.checkArgument; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkArgument; public class DorisCatalog implements Catalog { @@ -85,7 +84,7 @@ public class DorisCatalog implements Catalog { private Connection conn; - private DorisConfig dorisConfig; + private String createTableTemplate; private String dorisVersion; @@ -110,9 +109,9 @@ public DorisCatalog( Integer queryPort, String username, String password, - DorisConfig config) { + String createTableTemplate) { this(catalogName, frontEndNodes, queryPort, username, password); - this.dorisConfig = config; + this.createTableTemplate = createTableTemplate; } public DorisCatalog( @@ -121,9 +120,9 @@ public DorisCatalog( Integer queryPort, String username, String password, - DorisConfig config, + String createTableTemplate, String defaultDatabase) { - this(catalogName, frontEndNodes, queryPort, username, password, config); + this(catalogName, frontEndNodes, queryPort, username, password, createTableTemplate); this.defaultDatabase = defaultDatabase; } @@ -414,7 +413,7 @@ public void createTable(TablePath tablePath, CatalogTable table, boolean ignoreI String stmt = DorisCatalogUtil.getCreateTableStatement( - dorisConfig.getCreateTableTemplate(), tablePath, table, typeConverter); + createTableTemplate, tablePath, table, typeConverter); try (Statement statement = conn.createStatement()) { statement.execute(stmt); } catch (SQLException e) { @@ -510,7 +509,7 @@ public PreviewResult previewAction( checkArgument(catalogTable.isPresent(), "CatalogTable cannot be null"); return new SQLPreviewResult( DorisCatalogUtil.getCreateTableStatement( - dorisConfig.getCreateTableTemplate(), + createTableTemplate, tablePath, catalogTable.get(), // used for test when typeConverter is null diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalogFactory.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalogFactory.java index 1071b52f05a..7fd1da603e2 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalogFactory.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/catalog/DorisCatalogFactory.java @@ -22,11 +22,14 @@ import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.factory.CatalogFactory; import org.apache.seatunnel.api.table.factory.Factory; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSinkOptions; import com.google.auto.service.AutoService; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.IDENTIFIER; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE; + @AutoService(Factory.class) public class DorisCatalogFactory implements CatalogFactory { @@ -38,13 +41,13 @@ public Catalog createCatalog(String catalogName, ReadonlyConfig options) { options.get(DorisOptions.QUERY_PORT), options.get(DorisOptions.USERNAME), options.get(DorisOptions.PASSWORD), - DorisConfig.of(options), - options.get(DorisOptions.DEFAULT_DATABASE)); + options.get(SAVE_MODE_CREATE_TEMPLATE), + options.get(DorisSinkOptions.DEFAULT_DATABASE)); } @Override public String factoryIdentifier() { - return DorisConfig.IDENTIFIER; + return IDENTIFIER; } @Override diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisConfig.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisConfig.java deleted file mode 100644 index f7155e8a647..00000000000 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisConfig.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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. - */ - -package org.apache.seatunnel.connectors.doris.config; - -import org.apache.seatunnel.shade.com.typesafe.config.Config; - -import org.apache.seatunnel.api.configuration.ReadonlyConfig; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.io.Serializable; -import java.util.Map; -import java.util.Properties; - -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DATABASE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_BATCH_SIZE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_DESERIALIZE_ARROW_ASYNC; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_DESERIALIZE_QUEUE_SIZE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_EXEC_MEM_LIMIT; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_FILTER_QUERY; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_READ_FIELD; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_REQUEST_CONNECT_TIMEOUT_MS; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_REQUEST_QUERY_TIMEOUT_S; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_REQUEST_READ_TIMEOUT_MS; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_REQUEST_RETRIES; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_SINK_CONFIG_PREFIX; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_TABLET_SIZE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.FENODES; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.NEEDS_UNSUPPORTED_TYPE_CASTING; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.PASSWORD; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.QUERY_PORT; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SAVE_MODE_CREATE_TEMPLATE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_BUFFER_COUNT; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_BUFFER_SIZE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_CHECK_INTERVAL; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_ENABLE_2PC; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_ENABLE_DELETE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_LABEL_PREFIX; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.SINK_MAX_RETRIES; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.USERNAME; - -@Setter -@Getter -@ToString -public class DorisConfig implements Serializable { - - public static final String IDENTIFIER = "Doris"; - - // common option - private String frontends; - private String database; - private String table; - private String username; - private String password; - private Integer queryPort; - private int batchSize; - - // source option - private String readField; - private String filterQuery; - private Integer tabletSize; - private Integer requestConnectTimeoutMs; - private Integer requestReadTimeoutMs; - private Integer requestQueryTimeoutS; - private Integer requestRetries; - private Boolean deserializeArrowAsync; - private int deserializeQueueSize; - private Long execMemLimit; - private boolean useOldApi; - - // sink option - private Boolean enable2PC; - private Boolean enableDelete; - private String labelPrefix; - private Integer checkInterval; - private Integer maxRetries; - private Integer bufferSize; - private Integer bufferCount; - private Properties streamLoadProps; - private boolean needsUnsupportedTypeCasting; - - // create table option - private String createTableTemplate; - - public static DorisConfig of(Config pluginConfig) { - return of(ReadonlyConfig.fromConfig(pluginConfig)); - } - - public static DorisConfig of(ReadonlyConfig config) { - - DorisConfig dorisConfig = new DorisConfig(); - - // common option - dorisConfig.setFrontends(config.get(FENODES)); - dorisConfig.setUsername(config.get(USERNAME)); - dorisConfig.setPassword(config.get(PASSWORD)); - dorisConfig.setQueryPort(config.get(QUERY_PORT)); - dorisConfig.setStreamLoadProps(parseStreamLoadProperties(config)); - dorisConfig.setDatabase(config.get(DATABASE)); - dorisConfig.setTable(config.get(TABLE)); - - // source option - dorisConfig.setReadField(config.get(DORIS_READ_FIELD)); - dorisConfig.setFilterQuery(config.get(DORIS_FILTER_QUERY)); - dorisConfig.setTabletSize(config.get(DORIS_TABLET_SIZE)); - dorisConfig.setRequestConnectTimeoutMs(config.get(DORIS_REQUEST_CONNECT_TIMEOUT_MS)); - dorisConfig.setRequestQueryTimeoutS(config.get(DORIS_REQUEST_QUERY_TIMEOUT_S)); - dorisConfig.setRequestReadTimeoutMs(config.get(DORIS_REQUEST_READ_TIMEOUT_MS)); - dorisConfig.setRequestRetries(config.get(DORIS_REQUEST_RETRIES)); - dorisConfig.setDeserializeArrowAsync(config.get(DORIS_DESERIALIZE_ARROW_ASYNC)); - dorisConfig.setDeserializeQueueSize(config.get(DORIS_DESERIALIZE_QUEUE_SIZE)); - dorisConfig.setBatchSize(config.get(DORIS_BATCH_SIZE)); - dorisConfig.setExecMemLimit(config.get(DORIS_EXEC_MEM_LIMIT)); - - // sink option - dorisConfig.setEnable2PC(config.get(SINK_ENABLE_2PC)); - dorisConfig.setLabelPrefix(config.get(SINK_LABEL_PREFIX)); - dorisConfig.setCheckInterval(config.get(SINK_CHECK_INTERVAL)); - dorisConfig.setMaxRetries(config.get(SINK_MAX_RETRIES)); - dorisConfig.setBufferSize(config.get(SINK_BUFFER_SIZE)); - dorisConfig.setBufferCount(config.get(SINK_BUFFER_COUNT)); - dorisConfig.setEnableDelete(config.get(SINK_ENABLE_DELETE)); - dorisConfig.setNeedsUnsupportedTypeCasting(config.get(NEEDS_UNSUPPORTED_TYPE_CASTING)); - - // create table option - dorisConfig.setCreateTableTemplate(config.get(SAVE_MODE_CREATE_TEMPLATE)); - - return dorisConfig; - } - - private static Properties parseStreamLoadProperties(ReadonlyConfig config) { - Properties streamLoadProps = new Properties(); - if (config.getOptional(DORIS_SINK_CONFIG_PREFIX).isPresent()) { - Map map = config.getOptional(DORIS_SINK_CONFIG_PREFIX).get(); - map.forEach( - (key, value) -> { - streamLoadProps.put(key.toLowerCase(), value); - }); - } - return streamLoadProps; - } -} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisOptions.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisOptions.java index ddf1195b6ed..bcdf24c9d7b 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisOptions.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisOptions.java @@ -20,32 +20,12 @@ import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; import org.apache.seatunnel.api.configuration.util.OptionRule; -import org.apache.seatunnel.api.sink.DataSaveMode; -import org.apache.seatunnel.api.sink.SaveModePlaceHolder; -import org.apache.seatunnel.api.sink.SchemaSaveMode; - -import java.util.Map; - -import static org.apache.seatunnel.api.sink.SinkCommonOptions.MULTI_TABLE_SINK_REPLICA; public interface DorisOptions { - int DORIS_TABLET_SIZE_MIN = 1; - int DORIS_TABLET_SIZE_DEFAULT = Integer.MAX_VALUE; - int DORIS_REQUEST_CONNECT_TIMEOUT_MS_DEFAULT = 30 * 1000; - int DORIS_REQUEST_READ_TIMEOUT_MS_DEFAULT = 30 * 1000; - int DORIS_REQUEST_QUERY_TIMEOUT_S_DEFAULT = 3600; - int DORIS_REQUEST_RETRIES_DEFAULT = 3; - Boolean DORIS_DESERIALIZE_ARROW_ASYNC_DEFAULT = false; - int DORIS_DESERIALIZE_QUEUE_SIZE_DEFAULT = 64; - int DORIS_BATCH_SIZE_DEFAULT = 1024; - long DORIS_EXEC_MEM_LIMIT_DEFAULT = 2147483648L; - int DEFAULT_SINK_CHECK_INTERVAL = 10000; - int DEFAULT_SINK_MAX_RETRIES = 3; - int DEFAULT_SINK_BUFFER_SIZE = 256 * 1024; - int DEFAULT_SINK_BUFFER_COUNT = 3; - + String IDENTIFIER = "Doris"; String DORIS_DEFAULT_CLUSTER = "default_cluster"; + int DORIS_BATCH_SIZE_DEFAULT = 1024; // common option Option FENODES = @@ -72,6 +52,7 @@ public interface DorisOptions { .stringType() .noDefaultValue() .withDescription("the doris user name."); + Option PASSWORD = Options.key("password") .stringType() @@ -79,202 +60,17 @@ public interface DorisOptions { .withDescription("the doris password."); Option TABLE = - Options.key("table") - .stringType() - .noDefaultValue() - .withDescription("the doris table name."); + Options.key("table").stringType().noDefaultValue().withDescription("table"); + Option DATABASE = - Options.key("database") - .stringType() - .noDefaultValue() - .withDescription("the doris database name."); + Options.key("database").stringType().noDefaultValue().withDescription("database"); + Option DORIS_BATCH_SIZE = Options.key("doris.batch.size") .intType() .defaultValue(DORIS_BATCH_SIZE_DEFAULT) .withDescription("the batch size of the doris read/write."); - // source config options - Option DORIS_READ_FIELD = - Options.key("doris.read.field") - .stringType() - .noDefaultValue() - .withDescription( - "List of column names in the Doris table, separated by commas"); - Option DORIS_FILTER_QUERY = - Options.key("doris.filter.query") - .stringType() - .noDefaultValue() - .withDescription( - "Filter expression of the query, which is transparently transmitted to Doris. Doris uses this expression to complete source-side data filtering"); - Option DORIS_TABLET_SIZE = - Options.key("doris.request.tablet.size") - .intType() - .defaultValue(DORIS_TABLET_SIZE_DEFAULT) - .withDescription(""); - Option DORIS_REQUEST_CONNECT_TIMEOUT_MS = - Options.key("doris.request.connect.timeout.ms") - .intType() - .defaultValue(DORIS_REQUEST_CONNECT_TIMEOUT_MS_DEFAULT) - .withDescription(""); - Option DORIS_REQUEST_READ_TIMEOUT_MS = - Options.key("doris.request.read.timeout.ms") - .intType() - .defaultValue(DORIS_REQUEST_READ_TIMEOUT_MS_DEFAULT) - .withDescription(""); - Option DORIS_REQUEST_QUERY_TIMEOUT_S = - Options.key("doris.request.query.timeout.s") - .intType() - .defaultValue(DORIS_REQUEST_QUERY_TIMEOUT_S_DEFAULT) - .withDescription(""); - Option DORIS_REQUEST_RETRIES = - Options.key("doris.request.retries") - .intType() - .defaultValue(DORIS_REQUEST_RETRIES_DEFAULT) - .withDescription(""); - Option DORIS_DESERIALIZE_ARROW_ASYNC = - Options.key("doris.deserialize.arrow.async") - .booleanType() - .defaultValue(DORIS_DESERIALIZE_ARROW_ASYNC_DEFAULT) - .withDescription(""); - Option DORIS_DESERIALIZE_QUEUE_SIZE = - Options.key("doris.request.retriesdoris.deserialize.queue.size") - .intType() - .defaultValue(DORIS_DESERIALIZE_QUEUE_SIZE_DEFAULT) - .withDescription(""); - - Option DORIS_EXEC_MEM_LIMIT = - Options.key("doris.exec.mem.limit") - .longType() - .defaultValue(DORIS_EXEC_MEM_LIMIT_DEFAULT) - .withDescription(""); - - // sink config options - Option SINK_ENABLE_2PC = - Options.key("sink.enable-2pc") - .booleanType() - .defaultValue(false) - .withDescription("enable 2PC while loading"); - - Option SINK_CHECK_INTERVAL = - Options.key("sink.check-interval") - .intType() - .defaultValue(DEFAULT_SINK_CHECK_INTERVAL) - .withDescription("check exception with the interval while loading"); - Option SINK_MAX_RETRIES = - Options.key("sink.max-retries") - .intType() - .defaultValue(DEFAULT_SINK_MAX_RETRIES) - .withDescription("the max retry times if writing records to database failed."); - Option SINK_BUFFER_SIZE = - Options.key("sink.buffer-size") - .intType() - .defaultValue(DEFAULT_SINK_BUFFER_SIZE) - .withDescription("the buffer size to cache data for stream load."); - Option SINK_BUFFER_COUNT = - Options.key("sink.buffer-count") - .intType() - .defaultValue(DEFAULT_SINK_BUFFER_COUNT) - .withDescription("the buffer count to cache data for stream load."); - Option SINK_LABEL_PREFIX = - Options.key("sink.label-prefix") - .stringType() - .defaultValue("") - .withDescription("the unique label prefix."); - Option SINK_ENABLE_DELETE = - Options.key("sink.enable-delete") - .booleanType() - .defaultValue(false) - .withDescription("whether to enable the delete function"); - - Option> DORIS_SINK_CONFIG_PREFIX = - Options.key("doris.config") - .mapType() - .noDefaultValue() - .withDescription( - "The parameter of the Stream Load data_desc. " - + "The way to specify the parameter is to add the prefix `doris.config` to the original load parameter name "); - - Option DEFAULT_DATABASE = - Options.key("default-database") - .stringType() - .defaultValue("information_schema") - .withDescription(""); - - Option SCHEMA_SAVE_MODE = - Options.key("schema_save_mode") - .enumType(SchemaSaveMode.class) - .defaultValue(SchemaSaveMode.CREATE_SCHEMA_WHEN_NOT_EXIST) - .withDescription("schema_save_mode"); - - Option DATA_SAVE_MODE = - Options.key("data_save_mode") - .enumType(DataSaveMode.class) - .defaultValue(DataSaveMode.APPEND_DATA) - .withDescription("data_save_mode"); - - Option CUSTOM_SQL = - Options.key("custom_sql").stringType().noDefaultValue().withDescription("custom_sql"); - - Option NEEDS_UNSUPPORTED_TYPE_CASTING = - Options.key("needs_unsupported_type_casting") - .booleanType() - .defaultValue(false) - .withDescription( - "Whether to enable the unsupported type casting, such as Decimal64 to Double"); - - // create table - Option SAVE_MODE_CREATE_TEMPLATE = - Options.key("save_mode_create_template") - .stringType() - .defaultValue( - "CREATE TABLE IF NOT EXISTS `" - + SaveModePlaceHolder.DATABASE.getPlaceHolder() - + "`.`" - + SaveModePlaceHolder.TABLE.getPlaceHolder() - + "` (\n" - + SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder() - + ",\n" - + SaveModePlaceHolder.ROWTYPE_FIELDS.getPlaceHolder() - + "\n" - + ") ENGINE=OLAP\n" - + " UNIQUE KEY (" - + SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder() - + ")\n" - + "DISTRIBUTED BY HASH (" - + SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder() - + ")\n " - + "PROPERTIES (\n" - + "\"replication_allocation\" = \"tag.location.default: 1\",\n" - + "\"in_memory\" = \"false\",\n" - + "\"storage_format\" = \"V2\",\n" - + "\"disable_auto_compaction\" = \"false\"\n" - + ")") - .withDescription("Create table statement template, used to create Doris table"); - - OptionRule.Builder SINK_RULE = - OptionRule.builder() - .required( - FENODES, - USERNAME, - PASSWORD, - SINK_LABEL_PREFIX, - DORIS_SINK_CONFIG_PREFIX, - DATA_SAVE_MODE, - SCHEMA_SAVE_MODE) - .optional( - DATABASE, - TABLE, - TABLE_IDENTIFIER, - QUERY_PORT, - DORIS_BATCH_SIZE, - SINK_ENABLE_2PC, - SINK_ENABLE_DELETE, - MULTI_TABLE_SINK_REPLICA, - SAVE_MODE_CREATE_TEMPLATE, - NEEDS_UNSUPPORTED_TYPE_CASTING) - .conditional(DATA_SAVE_MODE, DataSaveMode.CUSTOM_PROCESSING, CUSTOM_SQL); - OptionRule.Builder CATALOG_RULE = OptionRule.builder().required(FENODES, QUERY_PORT, USERNAME, PASSWORD); } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSinkConfig.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSinkConfig.java new file mode 100644 index 00000000000..8f0d948042f --- /dev/null +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSinkConfig.java @@ -0,0 +1,123 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.doris.config; + +import org.apache.seatunnel.shade.com.typesafe.config.Config; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.io.Serializable; +import java.util.Map; +import java.util.Properties; + +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DATABASE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_BATCH_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.FENODES; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.PASSWORD; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.QUERY_PORT; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.USERNAME; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.DORIS_SINK_CONFIG_PREFIX; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.NEEDS_UNSUPPORTED_TYPE_CASTING; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_BUFFER_COUNT; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_BUFFER_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_CHECK_INTERVAL; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_ENABLE_2PC; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_ENABLE_DELETE; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_LABEL_PREFIX; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.SINK_MAX_RETRIES; + +@Setter +@Getter +@ToString +public class DorisSinkConfig implements Serializable { + + // common option + private String frontends; + private String database; + private String table; + private String username; + private String password; + private Integer queryPort; + private int batchSize; + + // sink option + private Boolean enable2PC; + private Boolean enableDelete; + private String labelPrefix; + private Integer checkInterval; + private Integer maxRetries; + private Integer bufferSize; + private Integer bufferCount; + private Properties streamLoadProps; + private boolean needsUnsupportedTypeCasting; + + // create table option + private String createTableTemplate; + + public static DorisSinkConfig of(Config pluginConfig) { + return of(ReadonlyConfig.fromConfig(pluginConfig)); + } + + public static DorisSinkConfig of(ReadonlyConfig config) { + + DorisSinkConfig dorisSinkConfig = new DorisSinkConfig(); + + // common option + dorisSinkConfig.setFrontends(config.get(FENODES)); + dorisSinkConfig.setUsername(config.get(USERNAME)); + dorisSinkConfig.setPassword(config.get(PASSWORD)); + dorisSinkConfig.setQueryPort(config.get(QUERY_PORT)); + dorisSinkConfig.setStreamLoadProps(parseStreamLoadProperties(config)); + dorisSinkConfig.setDatabase(config.get(DATABASE)); + dorisSinkConfig.setTable(config.get(TABLE)); + dorisSinkConfig.setBatchSize(config.get(DORIS_BATCH_SIZE)); + + // sink option + dorisSinkConfig.setEnable2PC(config.get(SINK_ENABLE_2PC)); + dorisSinkConfig.setLabelPrefix(config.get(SINK_LABEL_PREFIX)); + dorisSinkConfig.setCheckInterval(config.get(SINK_CHECK_INTERVAL)); + dorisSinkConfig.setMaxRetries(config.get(SINK_MAX_RETRIES)); + dorisSinkConfig.setBufferSize(config.get(SINK_BUFFER_SIZE)); + dorisSinkConfig.setBufferCount(config.get(SINK_BUFFER_COUNT)); + dorisSinkConfig.setEnableDelete(config.get(SINK_ENABLE_DELETE)); + dorisSinkConfig.setNeedsUnsupportedTypeCasting(config.get(NEEDS_UNSUPPORTED_TYPE_CASTING)); + + // create table option + dorisSinkConfig.setCreateTableTemplate(config.get(SAVE_MODE_CREATE_TEMPLATE)); + + return dorisSinkConfig; + } + + private static Properties parseStreamLoadProperties(ReadonlyConfig config) { + Properties streamLoadProps = new Properties(); + if (config.getOptional(DORIS_SINK_CONFIG_PREFIX).isPresent()) { + Map map = config.getOptional(DORIS_SINK_CONFIG_PREFIX).get(); + map.forEach( + (key, value) -> { + streamLoadProps.put(key.toLowerCase(), value); + }); + } + return streamLoadProps; + } +} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSinkOptions.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSinkOptions.java new file mode 100644 index 00000000000..372418d12a4 --- /dev/null +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSinkOptions.java @@ -0,0 +1,170 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.doris.config; + +import org.apache.seatunnel.api.configuration.Option; +import org.apache.seatunnel.api.configuration.Options; +import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.sink.DataSaveMode; +import org.apache.seatunnel.api.sink.SaveModePlaceHolder; +import org.apache.seatunnel.api.sink.SchemaSaveMode; + +import java.util.Map; + +import static org.apache.seatunnel.api.sink.SinkCommonOptions.MULTI_TABLE_SINK_REPLICA; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DATABASE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_BATCH_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.FENODES; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.PASSWORD; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.QUERY_PORT; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE_IDENTIFIER; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.USERNAME; + +public interface DorisSinkOptions { + + int DEFAULT_SINK_CHECK_INTERVAL = 10000; + int DEFAULT_SINK_MAX_RETRIES = 3; + int DEFAULT_SINK_BUFFER_SIZE = 256 * 1024; + int DEFAULT_SINK_BUFFER_COUNT = 3; + + Option SINK_ENABLE_2PC = + Options.key("sink.enable-2pc") + .booleanType() + .defaultValue(false) + .withDescription("enable 2PC while loading"); + + Option SINK_CHECK_INTERVAL = + Options.key("sink.check-interval") + .intType() + .defaultValue(DEFAULT_SINK_CHECK_INTERVAL) + .withDescription("check exception with the interval while loading"); + Option SINK_MAX_RETRIES = + Options.key("sink.max-retries") + .intType() + .defaultValue(DEFAULT_SINK_MAX_RETRIES) + .withDescription("the max retry times if writing records to database failed."); + Option SINK_BUFFER_SIZE = + Options.key("sink.buffer-size") + .intType() + .defaultValue(DEFAULT_SINK_BUFFER_SIZE) + .withDescription("the buffer size to cache data for stream load."); + Option SINK_BUFFER_COUNT = + Options.key("sink.buffer-count") + .intType() + .defaultValue(DEFAULT_SINK_BUFFER_COUNT) + .withDescription("the buffer count to cache data for stream load."); + Option SINK_LABEL_PREFIX = + Options.key("sink.label-prefix") + .stringType() + .defaultValue("") + .withDescription("the unique label prefix."); + Option SINK_ENABLE_DELETE = + Options.key("sink.enable-delete") + .booleanType() + .defaultValue(false) + .withDescription("whether to enable the delete function"); + + Option> DORIS_SINK_CONFIG_PREFIX = + Options.key("doris.config") + .mapType() + .noDefaultValue() + .withDescription( + "The parameter of the Stream Load data_desc. " + + "The way to specify the parameter is to add the prefix `doris.config` to the original load parameter name "); + + Option DEFAULT_DATABASE = + Options.key("default-database") + .stringType() + .defaultValue("information_schema") + .withDescription(""); + + Option SCHEMA_SAVE_MODE = + Options.key("schema_save_mode") + .enumType(SchemaSaveMode.class) + .defaultValue(SchemaSaveMode.CREATE_SCHEMA_WHEN_NOT_EXIST) + .withDescription("schema_save_mode"); + + Option DATA_SAVE_MODE = + Options.key("data_save_mode") + .enumType(DataSaveMode.class) + .defaultValue(DataSaveMode.APPEND_DATA) + .withDescription("data_save_mode"); + + Option CUSTOM_SQL = + Options.key("custom_sql").stringType().noDefaultValue().withDescription("custom_sql"); + + Option NEEDS_UNSUPPORTED_TYPE_CASTING = + Options.key("needs_unsupported_type_casting") + .booleanType() + .defaultValue(false) + .withDescription( + "Whether to enable the unsupported type casting, such as Decimal64 to Double"); + + // create table + Option SAVE_MODE_CREATE_TEMPLATE = + Options.key("save_mode_create_template") + .stringType() + .defaultValue( + "CREATE TABLE IF NOT EXISTS `" + + SaveModePlaceHolder.DATABASE.getPlaceHolder() + + "`.`" + + SaveModePlaceHolder.TABLE.getPlaceHolder() + + "` (\n" + + SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder() + + ",\n" + + SaveModePlaceHolder.ROWTYPE_FIELDS.getPlaceHolder() + + "\n" + + ") ENGINE=OLAP\n" + + " UNIQUE KEY (" + + SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder() + + ")\n" + + "DISTRIBUTED BY HASH (" + + SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder() + + ")\n " + + "PROPERTIES (\n" + + "\"replication_allocation\" = \"tag.location.default: 1\",\n" + + "\"in_memory\" = \"false\",\n" + + "\"storage_format\" = \"V2\",\n" + + "\"disable_auto_compaction\" = \"false\"\n" + + ")") + .withDescription("Create table statement template, used to create Doris table"); + + OptionRule.Builder SINK_RULE = + OptionRule.builder() + .required( + FENODES, + USERNAME, + PASSWORD, + SINK_LABEL_PREFIX, + DORIS_SINK_CONFIG_PREFIX, + DATA_SAVE_MODE, + SCHEMA_SAVE_MODE) + .optional( + DATABASE, + TABLE, + TABLE_IDENTIFIER, + QUERY_PORT, + DORIS_BATCH_SIZE, + SINK_ENABLE_2PC, + SINK_ENABLE_DELETE, + MULTI_TABLE_SINK_REPLICA, + SAVE_MODE_CREATE_TEMPLATE, + NEEDS_UNSUPPORTED_TYPE_CASTING) + .conditional(DATA_SAVE_MODE, DataSaveMode.CUSTOM_PROCESSING, CUSTOM_SQL); +} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSourceConfig.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSourceConfig.java new file mode 100644 index 00000000000..999f8fbfeaa --- /dev/null +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSourceConfig.java @@ -0,0 +1,71 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.doris.config; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; + +import lombok.Data; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; +import java.util.List; + +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.FENODES; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.PASSWORD; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.QUERY_PORT; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.USERNAME; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_DESERIALIZE_ARROW_ASYNC; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_DESERIALIZE_QUEUE_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_REQUEST_CONNECT_TIMEOUT_MS; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_REQUEST_QUERY_TIMEOUT_S; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_REQUEST_READ_TIMEOUT_MS; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_REQUEST_RETRIES; + +@Data +@SuperBuilder +public class DorisSourceConfig implements Serializable { + + private String frontends; + private Integer queryPort; + private String username; + private String password; + private Integer requestConnectTimeoutMs; + private Integer requestReadTimeoutMs; + private Integer requestQueryTimeoutS; + private Integer requestRetries; + private Boolean deserializeArrowAsync; + private int deserializeQueueSize; + private boolean useOldApi; + private List tableConfigList; + + public static DorisSourceConfig of(ReadonlyConfig config) { + DorisSourceConfigBuilder builder = DorisSourceConfig.builder(); + builder.tableConfigList(DorisTableConfig.of(config)); + builder.frontends(config.get(FENODES)); + builder.queryPort(config.get(QUERY_PORT)); + builder.username(config.get(USERNAME)); + builder.password(config.get(PASSWORD)); + builder.requestConnectTimeoutMs(config.get(DORIS_REQUEST_CONNECT_TIMEOUT_MS)); + builder.requestReadTimeoutMs(config.get(DORIS_REQUEST_READ_TIMEOUT_MS)); + builder.requestQueryTimeoutS(config.get(DORIS_REQUEST_QUERY_TIMEOUT_S)); + builder.requestRetries(config.get(DORIS_REQUEST_RETRIES)); + builder.deserializeArrowAsync(config.get(DORIS_DESERIALIZE_ARROW_ASYNC)); + builder.deserializeQueueSize(config.get(DORIS_DESERIALIZE_QUEUE_SIZE)); + return builder.build(); + } +} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSourceOptions.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSourceOptions.java new file mode 100644 index 00000000000..2ee852ffccc --- /dev/null +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisSourceOptions.java @@ -0,0 +1,103 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.doris.config; + +import org.apache.seatunnel.api.configuration.Option; +import org.apache.seatunnel.api.configuration.Options; + +import java.util.List; + +public interface DorisSourceOptions { + + int DORIS_TABLET_SIZE_MIN = 1; + int DORIS_TABLET_SIZE_DEFAULT = Integer.MAX_VALUE; + int DORIS_REQUEST_CONNECT_TIMEOUT_MS_DEFAULT = 30 * 1000; + int DORIS_REQUEST_READ_TIMEOUT_MS_DEFAULT = 30 * 1000; + int DORIS_REQUEST_QUERY_TIMEOUT_S_DEFAULT = 3600; + int DORIS_REQUEST_RETRIES_DEFAULT = 3; + Boolean DORIS_DESERIALIZE_ARROW_ASYNC_DEFAULT = false; + int DORIS_DESERIALIZE_QUEUE_SIZE_DEFAULT = 64; + long DORIS_EXEC_MEM_LIMIT_DEFAULT = 2147483648L; + + Option> TABLE_LIST = + Options.key("table_list") + .listType(DorisTableConfig.class) + .noDefaultValue() + .withDescription("table list config."); + + Option DORIS_READ_FIELD = + Options.key("doris.read.field") + .stringType() + .noDefaultValue() + .withDescription( + "List of column names in the Doris table, separated by commas"); + Option DORIS_FILTER_QUERY = + Options.key("doris.filter.query") + .stringType() + .noDefaultValue() + .withDescription( + "Filter expression of the query, which is transparently transmitted to Doris. Doris uses this expression to complete source-side data filtering"); + + Option DORIS_TABLET_SIZE = + Options.key("doris.request.tablet.size") + .intType() + .defaultValue(DORIS_TABLET_SIZE_DEFAULT) + .withDescription(""); + + Option DORIS_REQUEST_CONNECT_TIMEOUT_MS = + Options.key("doris.request.connect.timeout.ms") + .intType() + .defaultValue(DORIS_REQUEST_CONNECT_TIMEOUT_MS_DEFAULT) + .withDescription(""); + + Option DORIS_REQUEST_READ_TIMEOUT_MS = + Options.key("doris.request.read.timeout.ms") + .intType() + .defaultValue(DORIS_REQUEST_READ_TIMEOUT_MS_DEFAULT) + .withDescription(""); + + Option DORIS_REQUEST_QUERY_TIMEOUT_S = + Options.key("doris.request.query.timeout.s") + .intType() + .defaultValue(DORIS_REQUEST_QUERY_TIMEOUT_S_DEFAULT) + .withDescription(""); + + Option DORIS_REQUEST_RETRIES = + Options.key("doris.request.retries") + .intType() + .defaultValue(DORIS_REQUEST_RETRIES_DEFAULT) + .withDescription(""); + + Option DORIS_DESERIALIZE_ARROW_ASYNC = + Options.key("doris.deserialize.arrow.async") + .booleanType() + .defaultValue(DORIS_DESERIALIZE_ARROW_ASYNC_DEFAULT) + .withDescription(""); + + Option DORIS_DESERIALIZE_QUEUE_SIZE = + Options.key("doris.request.retriesdoris.deserialize.queue.size") + .intType() + .defaultValue(DORIS_DESERIALIZE_QUEUE_SIZE_DEFAULT) + .withDescription(""); + + Option DORIS_EXEC_MEM_LIMIT = + Options.key("doris.exec.mem.limit") + .longType() + .defaultValue(DORIS_EXEC_MEM_LIMIT_DEFAULT) + .withDescription(""); +} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisTableConfig.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisTableConfig.java new file mode 100644 index 00000000000..624d25636b2 --- /dev/null +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/config/DorisTableConfig.java @@ -0,0 +1,132 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.doris.config; + +import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; + +import org.apache.commons.lang3.StringUtils; + +import lombok.Builder; +import lombok.Data; +import lombok.experimental.Tolerate; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DATABASE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_BATCH_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_EXEC_MEM_LIMIT; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_FILTER_QUERY; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_READ_FIELD; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_TABLET_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.TABLE_LIST; + +@Data +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class DorisTableConfig implements Serializable { + + @JsonProperty("table") + private String table; + + @JsonProperty("database") + private String database; + + @JsonProperty("doris.read.field") + private String readField; + + @JsonProperty("doris.filter.query") + private String filterQuery; + + @JsonProperty("doris.batch.size") + private int batchSize; + + @JsonProperty("doris.request.tablet.size") + private int tabletSize; + + @JsonProperty("doris.exec.mem.limit") + private long execMemLimit; + + @Tolerate + public DorisTableConfig() {} + + public static List of(ReadonlyConfig connectorConfig) { + List tableList; + if (connectorConfig.getOptional(TABLE_LIST).isPresent()) { + tableList = connectorConfig.get(TABLE_LIST); + } else { + DorisTableConfig tableProperty = + DorisTableConfig.builder() + .table(connectorConfig.get(TABLE)) + .database(connectorConfig.get(DATABASE)) + .readField(connectorConfig.get(DORIS_READ_FIELD)) + .filterQuery(connectorConfig.get(DORIS_FILTER_QUERY)) + .batchSize(connectorConfig.get(DORIS_BATCH_SIZE)) + .tabletSize(connectorConfig.get(DORIS_TABLET_SIZE)) + .execMemLimit(connectorConfig.get(DORIS_EXEC_MEM_LIMIT)) + .build(); + tableList = Collections.singletonList(tableProperty); + } + + if (tableList.size() > 1) { + List tableIds = + tableList.stream() + .map(DorisTableConfig::getTableIdentifier) + .collect(Collectors.toList()); + Set tableIdSet = new HashSet<>(tableIds); + if (tableIdSet.size() < tableList.size() - 1) { + throw new IllegalArgumentException( + "Please configure unique `database`.`table`, not allow null/duplicate: " + + tableIds); + } + } + + for (DorisTableConfig dorisTableConfig : tableList) { + if (StringUtils.isBlank(dorisTableConfig.getDatabase())) { + throw new IllegalArgumentException( + "Please configure `database`, not allow null database in config."); + } + if (StringUtils.isBlank(dorisTableConfig.getTable())) { + throw new IllegalArgumentException( + "Please configure `table`, not allow null table in config."); + } + if (dorisTableConfig.getBatchSize() <= 0) { + dorisTableConfig.setBatchSize(DORIS_BATCH_SIZE.defaultValue()); + } + if (dorisTableConfig.getExecMemLimit() <= 0) { + dorisTableConfig.setExecMemLimit(DORIS_EXEC_MEM_LIMIT.defaultValue()); + } + if (dorisTableConfig.getTabletSize() <= 0) { + dorisTableConfig.setTabletSize(DORIS_TABLET_SIZE.defaultValue()); + } + } + return tableList; + } + + public String getTableIdentifier() { + return String.format("%s.%s", database, table); + } +} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/AbstractDorisTypeConverter.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/AbstractDorisTypeConverter.java index e6b9b95361d..67266b453f5 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/AbstractDorisTypeConverter.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/AbstractDorisTypeConverter.java @@ -26,12 +26,13 @@ import org.apache.seatunnel.api.table.type.DecimalType; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.common.exception.CommonError; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; import lombok.extern.slf4j.Slf4j; import java.util.Locale; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.IDENTIFIER; + @Slf4j public abstract class AbstractDorisTypeConverter implements TypeConverter { public static final String DORIS_NULL = "NULL"; @@ -186,7 +187,7 @@ public void sampleTypeConverter( break; default: throw CommonError.convertToSeaTunnelTypeError( - DorisConfig.IDENTIFIER, dorisColumnType, typeDefine.getName()); + IDENTIFIER, dorisColumnType, typeDefine.getName()); } } @@ -234,7 +235,7 @@ protected void sampleReconvertString( } throw CommonError.convertToConnectorTypeError( - DorisConfig.IDENTIFIER, column.getDataType().getSqlType().name(), column.getName()); + IDENTIFIER, column.getDataType().getSqlType().name(), column.getName()); } protected BasicTypeDefine sampleReconvert( @@ -366,9 +367,7 @@ protected BasicTypeDefine sampleReconvert( break; default: throw CommonError.convertToConnectorTypeError( - DorisConfig.IDENTIFIER, - column.getDataType().getSqlType().name(), - column.getName()); + IDENTIFIER, column.getDataType().getSqlType().name(), column.getName()); } return builder.build(); } @@ -430,7 +429,7 @@ private void reconvertBuildArrayInternal( break; default: throw CommonError.convertToConnectorTypeError( - DorisConfig.IDENTIFIER, elementType.getSqlType().name(), columnName); + IDENTIFIER, elementType.getSqlType().name(), columnName); } } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV1.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV1.java index fb129249702..9b7e98368fb 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV1.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV1.java @@ -23,11 +23,12 @@ import org.apache.seatunnel.api.table.converter.TypeConverter; import org.apache.seatunnel.api.table.type.DecimalType; import org.apache.seatunnel.api.table.type.LocalTimeType; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.IDENTIFIER; + /** Doris type converter for version 1.2.x */ @Slf4j @AutoService(TypeConverter.class) @@ -42,7 +43,7 @@ public class DorisTypeConverterV1 extends AbstractDorisTypeConverter { @Override public String identifier() { - return DorisConfig.IDENTIFIER; + return IDENTIFIER; } @Override diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV2.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV2.java index 3b5ebde0f47..46ae79251e0 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV2.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/datatype/DorisTypeConverterV2.java @@ -28,7 +28,6 @@ import org.apache.seatunnel.api.table.type.MapType; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.common.exception.CommonError; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; import com.google.auto.service.AutoService; import lombok.extern.slf4j.Slf4j; @@ -37,6 +36,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.IDENTIFIER; + /** Doris type converter for version 2.x */ @Slf4j @AutoService(TypeConverter.class) @@ -62,7 +63,7 @@ public class DorisTypeConverterV2 extends AbstractDorisTypeConverter { @Override public String identifier() { - return DorisConfig.IDENTIFIER; + return IDENTIFIER; } @Override @@ -166,7 +167,7 @@ private void convertArray( DecimalArrayType decimalArray = new DecimalArrayType(new DecimalType(20, 0)); builder.dataType(decimalArray); } else { - throw CommonError.convertToSeaTunnelTypeError(DorisConfig.IDENTIFIER, columnType, name); + throw CommonError.convertToSeaTunnelTypeError(IDENTIFIER, columnType, name); } } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/RestService.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/RestService.java index b516157443a..77c23f73418 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/RestService.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/RestService.java @@ -17,13 +17,19 @@ package org.apache.seatunnel.connectors.doris.rest; +import org.apache.seatunnel.shade.com.fasterxml.jackson.core.JsonParseException; +import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.JsonMappingException; +import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; -import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSourceOptions; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.rest.models.QueryPlan; import org.apache.seatunnel.connectors.doris.rest.models.Tablet; +import org.apache.seatunnel.connectors.doris.source.DorisSourceTable; import org.apache.seatunnel.connectors.doris.util.ErrorMessages; import org.apache.commons.io.IOUtils; @@ -37,10 +43,6 @@ import org.slf4j.Logger; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import java.io.BufferedReader; @@ -69,11 +71,12 @@ public class RestService implements Serializable { private static final String QUERY_PLAN = "_query_plan"; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static String send(DorisConfig dorisConfig, HttpRequestBase request, Logger logger) + private static String send( + DorisSourceConfig dorisSourceConfig, HttpRequestBase request, Logger logger) throws DorisConnectorException { - int connectTimeout = dorisConfig.getRequestConnectTimeoutMs(); - int socketTimeout = dorisConfig.getRequestReadTimeoutMs(); - int retries = dorisConfig.getRequestRetries(); + int connectTimeout = dorisSourceConfig.getRequestConnectTimeoutMs(); + int socketTimeout = dorisSourceConfig.getRequestReadTimeoutMs(); + int retries = dorisSourceConfig.getRequestRetries(); logger.trace( "connect timeout set to '{}'. socket timeout set to '{}'. retries set to '{}'.", connectTimeout, @@ -90,7 +93,7 @@ private static String send(DorisConfig dorisConfig, HttpRequestBase request, Log logger.info( "Send request to Doris FE '{}' with user '{}'.", request.getURI(), - dorisConfig.getUsername()); + dorisSourceConfig.getUsername()); IOException ex = null; int statusCode = -1; @@ -102,15 +105,15 @@ private static String send(DorisConfig dorisConfig, HttpRequestBase request, Log response = getConnectionGet( request.getURI().toString(), - dorisConfig.getUsername(), - dorisConfig.getPassword(), + dorisSourceConfig.getUsername(), + dorisSourceConfig.getPassword(), logger); } else { response = getConnectionPost( request, - dorisConfig.getUsername(), - dorisConfig.getPassword(), + dorisSourceConfig.getUsername(), + dorisSourceConfig.getPassword(), logger); } if (StringUtils.isEmpty(response)) { @@ -251,11 +254,16 @@ public static String randomEndpoint(String feNodes, Logger logger) } @VisibleForTesting - static String getUriStr(DorisConfig dorisConfig, Logger logger) throws DorisConnectorException { - String tableIdentifier = dorisConfig.getDatabase() + "." + dorisConfig.getTable(); + static String getUriStr( + DorisSourceConfig dorisSourceConfig, DorisSourceTable dorisSourceTable, Logger logger) + throws DorisConnectorException { + String tableIdentifier = + dorisSourceTable.getTablePath().getDatabaseName() + + "." + + dorisSourceTable.getTablePath().getTableName(); String[] identifier = parseIdentifier(tableIdentifier, logger); return "http://" - + randomEndpoint(dorisConfig.getFrontends(), logger) + + randomEndpoint(dorisSourceConfig.getFrontends(), logger) + API_PREFIX + "/" + identifier[0] @@ -265,9 +273,13 @@ static String getUriStr(DorisConfig dorisConfig, Logger logger) throws DorisConn } public static List findPartitions( - SeaTunnelRowType rowType, DorisConfig dorisConfig, Logger logger) + DorisSourceConfig dorisSourceConfig, DorisSourceTable dorisSourceTable, Logger logger) throws DorisConnectorException { - String tableIdentifier = dorisConfig.getDatabase() + "." + dorisConfig.getTable(); + String tableIdentifier = + dorisSourceTable.getTablePath().getDatabaseName() + + "." + + dorisSourceTable.getTablePath().getTableName(); + SeaTunnelRowType rowType = dorisSourceTable.getCatalogTable().getSeaTunnelRowType(); String[] tableIdentifiers = parseIdentifier(tableIdentifier, logger); String readFields = "*"; if (rowType.getFieldNames().length != 0) { @@ -281,12 +293,13 @@ public static List findPartitions( + "`.`" + tableIdentifiers[1] + "`"; - if (!StringUtils.isEmpty(dorisConfig.getFilterQuery())) { - sql += " where " + dorisConfig.getFilterQuery(); + if (!StringUtils.isEmpty(dorisSourceTable.getFilterQuery())) { + sql += " where " + dorisSourceTable.getFilterQuery(); } logger.debug("Query SQL Sending to Doris FE is: '{}'.", sql); - HttpPost httpPost = new HttpPost(getUriStr(dorisConfig, logger) + QUERY_PLAN); + HttpPost httpPost = + new HttpPost(getUriStr(dorisSourceConfig, dorisSourceTable, logger) + QUERY_PLAN); String entity = "{\"sql\": \"" + sql + "\"}"; logger.debug("Post body Sending to Doris FE is: '{}'.", entity); StringEntity stringEntity = new StringEntity(entity, StandardCharsets.UTF_8); @@ -294,12 +307,12 @@ public static List findPartitions( stringEntity.setContentType("application/json"); httpPost.setEntity(stringEntity); - String resStr = send(dorisConfig, httpPost, logger); + String resStr = send(dorisSourceConfig, httpPost, logger); logger.debug("Find partition response is '{}'.", resStr); QueryPlan queryPlan = getQueryPlan(resStr, logger); Map> be2Tablets = selectBeForTablet(queryPlan, logger); return tabletsMapToPartition( - dorisConfig, + dorisSourceTable, be2Tablets, queryPlan.getOpaqued_query_plan(), tableIdentifiers[0], @@ -397,18 +410,18 @@ static Map> selectBeForTablet(QueryPlan queryPlan, Logger log } @VisibleForTesting - static int tabletCountLimitForOnePartition(DorisConfig dorisConfig, Logger logger) { - int tabletsSize = DorisOptions.DORIS_TABLET_SIZE_DEFAULT; - if (dorisConfig.getTabletSize() != null) { - tabletsSize = dorisConfig.getTabletSize(); + static int tabletCountLimitForOnePartition(DorisSourceTable dorisSourceTable, Logger logger) { + int tabletsSize = DorisSourceOptions.DORIS_TABLET_SIZE_DEFAULT; + if (dorisSourceTable.getTabletSize() != null) { + tabletsSize = dorisSourceTable.getTabletSize(); } - if (tabletsSize < DorisOptions.DORIS_TABLET_SIZE_MIN) { + if (tabletsSize < DorisSourceOptions.DORIS_TABLET_SIZE_MIN) { logger.warn( "{} is less than {}, set to default value {}.", - DorisOptions.DORIS_TABLET_SIZE, - DorisOptions.DORIS_TABLET_SIZE_MIN, - DorisOptions.DORIS_TABLET_SIZE_MIN); - tabletsSize = DorisOptions.DORIS_TABLET_SIZE_MIN; + DorisSourceOptions.DORIS_TABLET_SIZE, + DorisSourceOptions.DORIS_TABLET_SIZE_MIN, + DorisSourceOptions.DORIS_TABLET_SIZE_MIN); + tabletsSize = DorisSourceOptions.DORIS_TABLET_SIZE_MIN; } logger.debug("Tablet size is set to {}.", tabletsSize); return tabletsSize; @@ -416,14 +429,14 @@ static int tabletCountLimitForOnePartition(DorisConfig dorisConfig, Logger logge @VisibleForTesting static List tabletsMapToPartition( - DorisConfig dorisConfig, + DorisSourceTable dorisSourceTable, Map> be2Tablets, String opaquedQueryPlan, String database, String table, Logger logger) throws DorisConnectorException { - int tabletsSize = tabletCountLimitForOnePartition(dorisConfig, logger); + int tabletsSize = tabletCountLimitForOnePartition(dorisSourceTable, logger); List partitions = new ArrayList<>(); for (Map.Entry> beInfo : be2Tablets.entrySet()) { logger.debug("Generate partition with beInfo: '{}'.", beInfo); diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/models/RespContent.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/models/RespContent.java index 7b0230576a9..747a6bddb21 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/models/RespContent.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/rest/models/RespContent.java @@ -17,8 +17,9 @@ package org.apache.seatunnel.connectors.doris.rest.models; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.apache.seatunnel.shade.com.fasterxml.jackson.annotation.JsonProperty; + import lombok.Getter; import lombok.Setter; import lombok.ToString; diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSink.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSink.java index c746fea00c2..deb88a51b11 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSink.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSink.java @@ -33,8 +33,8 @@ import org.apache.seatunnel.api.table.factory.CatalogFactory; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.common.constants.PluginType; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; -import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSinkConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSinkOptions; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.sink.committer.DorisCommitInfo; import org.apache.seatunnel.connectors.doris.sink.committer.DorisCommitInfoSerializer; @@ -55,7 +55,7 @@ public class DorisSink SupportSaveMode, SupportMultiTableSink { - private final DorisConfig dorisConfig; + private final DorisSinkConfig dorisSinkConfig; private final ReadonlyConfig config; private final CatalogTable catalogTable; private String jobId; @@ -63,7 +63,7 @@ public class DorisSink public DorisSink(ReadonlyConfig config, CatalogTable catalogTable) { this.config = config; this.catalogTable = catalogTable; - this.dorisConfig = DorisConfig.of(config); + this.dorisSinkConfig = DorisSinkConfig.of(config); } @Override @@ -79,13 +79,13 @@ public void setJobContext(JobContext jobContext) { @Override public DorisSinkWriter createWriter(SinkWriter.Context context) throws IOException { return new DorisSinkWriter( - context, Collections.emptyList(), catalogTable, dorisConfig, jobId); + context, Collections.emptyList(), catalogTable, dorisSinkConfig, jobId); } @Override public SinkWriter restoreWriter( SinkWriter.Context context, List states) throws IOException { - return new DorisSinkWriter(context, states, catalogTable, dorisConfig, jobId); + return new DorisSinkWriter(context, states, catalogTable, dorisSinkConfig, jobId); } @Override @@ -95,7 +95,7 @@ public Optional> getWriterStateSerializer() { @Override public Optional> createCommitter() throws IOException { - return Optional.of(new DorisCommitter(dorisConfig)); + return Optional.of(new DorisCommitter(dorisSinkConfig)); } @Override @@ -127,10 +127,15 @@ public Optional getSaveModeHandler() { Catalog catalog = catalogFactory.createCatalog(catalogFactory.factoryIdentifier(), config); return Optional.of( new DefaultSaveModeHandler( - config.get(DorisOptions.SCHEMA_SAVE_MODE), - config.get(DorisOptions.DATA_SAVE_MODE), + config.get(DorisSinkOptions.SCHEMA_SAVE_MODE), + config.get(DorisSinkOptions.DATA_SAVE_MODE), catalog, catalogTable, - config.get(DorisOptions.CUSTOM_SQL))); + config.get(DorisSinkOptions.CUSTOM_SQL))); + } + + @Override + public Optional getWriteCatalogTable() { + return Optional.of(catalogTable); } } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSinkFactory.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSinkFactory.java index e1849c39341..9a2ce67be27 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSinkFactory.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/DorisSinkFactory.java @@ -26,7 +26,7 @@ import org.apache.seatunnel.api.table.factory.TableSinkFactory; import org.apache.seatunnel.api.table.factory.TableSinkFactoryContext; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSinkOptions; import org.apache.seatunnel.connectors.doris.sink.committer.DorisCommitInfo; import org.apache.seatunnel.connectors.doris.sink.writer.DorisSinkState; import org.apache.seatunnel.connectors.doris.util.UnsupportedTypeConverterUtils; @@ -39,15 +39,14 @@ import java.util.List; import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DATABASE; -import static org.apache.seatunnel.connectors.doris.config.DorisOptions.NEEDS_UNSUPPORTED_TYPE_CASTING; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.IDENTIFIER; import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE; import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE_IDENTIFIER; +import static org.apache.seatunnel.connectors.doris.config.DorisSinkOptions.NEEDS_UNSUPPORTED_TYPE_CASTING; @AutoService(Factory.class) public class DorisSinkFactory implements TableSinkFactory { - public static final String IDENTIFIER = "Doris"; - @Override public String factoryIdentifier() { return IDENTIFIER; @@ -55,12 +54,12 @@ public String factoryIdentifier() { @Override public OptionRule optionRule() { - return DorisOptions.SINK_RULE.build(); + return DorisSinkOptions.SINK_RULE.build(); } @Override public List excludeTablePlaceholderReplaceKeys() { - return Arrays.asList(DorisOptions.SAVE_MODE_CREATE_TEMPLATE.key()); + return Arrays.asList(DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE.key()); } @Override diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/HttpPutBuilder.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/HttpPutBuilder.java index 73580e2115c..83ae3316192 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/HttpPutBuilder.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/HttpPutBuilder.java @@ -30,7 +30,7 @@ import java.util.Map; import java.util.Properties; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** Builder for HttpPut. */ public class HttpPutBuilder { diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/committer/DorisCommitter.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/committer/DorisCommitter.java index 5c6e81ba7e2..87629ec1c15 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/committer/DorisCommitter.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/committer/DorisCommitter.java @@ -17,8 +17,11 @@ package org.apache.seatunnel.connectors.doris.sink.committer; +import org.apache.seatunnel.shade.com.fasterxml.jackson.core.type.TypeReference; +import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.seatunnel.api.sink.SinkCommitter; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSinkConfig; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.sink.HttpPutBuilder; @@ -30,8 +33,6 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -46,15 +47,15 @@ public class DorisCommitter implements SinkCommitter { private static final String COMMIT_PATTERN = "http://%s/api/%s/_stream_load_2pc"; private static final int HTTP_TEMPORARY_REDIRECT = 200; private final CloseableHttpClient httpClient; - private final DorisConfig dorisConfig; + private final DorisSinkConfig dorisSinkConfig; int maxRetry; - public DorisCommitter(DorisConfig dorisConfig) { - this(dorisConfig, new HttpUtil().getHttpClient()); + public DorisCommitter(DorisSinkConfig dorisSinkConfig) { + this(dorisSinkConfig, new HttpUtil().getHttpClient()); } - public DorisCommitter(DorisConfig dorisConfig, CloseableHttpClient client) { - this.dorisConfig = dorisConfig; + public DorisCommitter(DorisSinkConfig dorisSinkConfig, CloseableHttpClient client) { + this.dorisSinkConfig = dorisSinkConfig; this.httpClient = client; } @@ -80,11 +81,11 @@ private void commitTransaction(DorisCommitInfo committable) int retry = 0; String hostPort = committable.getHostPort(); CloseableHttpResponse response = null; - while (retry++ <= dorisConfig.getMaxRetries()) { + while (retry++ <= dorisSinkConfig.getMaxRetries()) { HttpPutBuilder putBuilder = new HttpPutBuilder(); putBuilder .setUrl(String.format(COMMIT_PATTERN, hostPort, committable.getDb())) - .baseAuth(dorisConfig.getUsername(), dorisConfig.getPassword()) + .baseAuth(dorisSinkConfig.getUsername(), dorisSinkConfig.getPassword()) .addCommonHeader() .addTxnId(committable.getTxbID()) .setEmptyEntity() @@ -93,14 +94,14 @@ private void commitTransaction(DorisCommitInfo committable) response = httpClient.execute(putBuilder.build()); } catch (IOException e) { log.error("commit transaction failed: ", e); - hostPort = dorisConfig.getFrontends(); + hostPort = dorisSinkConfig.getFrontends(); continue; } statusCode = response.getStatusLine().getStatusCode(); reasonPhrase = response.getStatusLine().getReasonPhrase(); if (statusCode != HTTP_TEMPORARY_REDIRECT) { log.warn("commit failed with {}, reason {}", hostPort, reasonPhrase); - hostPort = dorisConfig.getFrontends(); + hostPort = dorisSinkConfig.getFrontends(); } else { break; } @@ -139,7 +140,7 @@ private void abortTransaction(DorisCommitInfo committable) while (retry++ <= maxRetry) { HttpPutBuilder builder = new HttpPutBuilder(); builder.setUrl(String.format(COMMIT_PATTERN, hostPort, committable.getDb())) - .baseAuth(dorisConfig.getUsername(), dorisConfig.getPassword()) + .baseAuth(dorisSinkConfig.getUsername(), dorisSinkConfig.getPassword()) .addCommonHeader() .addTxnId(committable.getTxbID()) .setEmptyEntity() diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java index b5aa5274216..fa0d671e82c 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisSinkWriter.java @@ -17,12 +17,14 @@ package org.apache.seatunnel.connectors.doris.sink.writer; +import org.apache.seatunnel.shade.com.google.common.util.concurrent.ThreadFactoryBuilder; + import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSinkWriter; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSinkConfig; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.rest.RestService; @@ -34,7 +36,6 @@ import org.apache.seatunnel.connectors.doris.util.HttpUtil; import org.apache.seatunnel.connectors.doris.util.UnsupportedTypeConverterUtils; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -48,7 +49,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Preconditions.checkState; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; @Slf4j public class DorisSinkWriter @@ -59,7 +60,7 @@ public class DorisSinkWriter new ArrayList<>(Arrays.asList(LoadStatus.SUCCESS, LoadStatus.PUBLISH_TIMEOUT)); private long lastCheckpointId; private DorisStreamLoad dorisStreamLoad; - private final DorisConfig dorisConfig; + private final DorisSinkConfig dorisSinkConfig; private final String labelPrefix; private final LabelGenerator labelGenerator; private final int intervalTime; @@ -72,41 +73,41 @@ public DorisSinkWriter( SinkWriter.Context context, List state, CatalogTable catalogTable, - DorisConfig dorisConfig, + DorisSinkConfig dorisSinkConfig, String jobId) { - this.dorisConfig = dorisConfig; + this.dorisSinkConfig = dorisSinkConfig; this.catalogTable = catalogTable; this.lastCheckpointId = !state.isEmpty() ? state.get(0).getCheckpointId() : 0; log.info("restore checkpointId {}", lastCheckpointId); - log.info("labelPrefix " + dorisConfig.getLabelPrefix()); + log.info("labelPrefix " + dorisSinkConfig.getLabelPrefix()); this.labelPrefix = - dorisConfig.getLabelPrefix() + dorisSinkConfig.getLabelPrefix() + "_" + catalogTable.getTablePath().getFullName().replaceAll("\\.", "_") + "_" + jobId + "_" + context.getIndexOfSubtask(); - this.labelGenerator = new LabelGenerator(labelPrefix, dorisConfig.getEnable2PC()); + this.labelGenerator = new LabelGenerator(labelPrefix, dorisSinkConfig.getEnable2PC()); this.scheduledExecutorService = new ScheduledThreadPoolExecutor( 1, new ThreadFactoryBuilder().setNameFormat("stream-load-check").build()); - this.serializer = createSerializer(dorisConfig, catalogTable.getSeaTunnelRowType()); - this.intervalTime = dorisConfig.getCheckInterval(); + this.serializer = createSerializer(dorisSinkConfig, catalogTable.getSeaTunnelRowType()); + this.intervalTime = dorisSinkConfig.getCheckInterval(); this.initializeLoad(); } private void initializeLoad() { - String backend = RestService.randomEndpoint(dorisConfig.getFrontends(), log); + String backend = RestService.randomEndpoint(dorisSinkConfig.getFrontends(), log); try { this.dorisStreamLoad = new DorisStreamLoad( backend, catalogTable.getTablePath(), - dorisConfig, + dorisSinkConfig, labelGenerator, new HttpUtil().getHttpClient()); - if (dorisConfig.getEnable2PC()) { + if (dorisSinkConfig.getEnable2PC()) { dorisStreamLoad.abortPreCommit(labelPrefix, lastCheckpointId + 1); } } catch (Exception e) { @@ -124,15 +125,15 @@ public void write(SeaTunnelRow element) throws IOException { checkLoadException(); byte[] serialize = serializer.serialize( - dorisConfig.isNeedsUnsupportedTypeCasting() + dorisSinkConfig.isNeedsUnsupportedTypeCasting() ? UnsupportedTypeConverterUtils.convertRow(element) : element); if (Objects.isNull(serialize)) { return; } dorisStreamLoad.writeRecord(serialize); - if (!dorisConfig.getEnable2PC() - && dorisStreamLoad.getRecordCount() >= dorisConfig.getBatchSize()) { + if (!dorisSinkConfig.getEnable2PC() + && dorisStreamLoad.getRecordCount() >= dorisSinkConfig.getBatchSize()) { flush(); startLoad(labelGenerator.generateLabel(lastCheckpointId)); } @@ -141,7 +142,7 @@ public void write(SeaTunnelRow element) throws IOException { @Override public Optional prepareCommit() throws IOException { RespContent respContent = flush(); - if (!dorisConfig.getEnable2PC() || respContent == null) { + if (!dorisSinkConfig.getEnable2PC() || respContent == null) { return Optional.empty(); } long txnId = respContent.getTxnId(); @@ -178,7 +179,7 @@ private void startLoad(String label) { @Override public void abortPrepare() { - if (dorisConfig.getEnable2PC()) { + if (dorisSinkConfig.getEnable2PC()) { try { dorisStreamLoad.abortPreCommit(labelPrefix, lastCheckpointId + 1); } catch (Exception e) { @@ -208,7 +209,7 @@ private void checkLoadException() { @Override public void close() throws IOException { - if (!dorisConfig.getEnable2PC()) { + if (!dorisSinkConfig.getEnable2PC()) { flush(); } if (scheduledExecutorService != null) { @@ -220,14 +221,14 @@ public void close() throws IOException { } private DorisSerializer createSerializer( - DorisConfig dorisConfig, SeaTunnelRowType seaTunnelRowType) { + DorisSinkConfig dorisSinkConfig, SeaTunnelRowType seaTunnelRowType) { return new SeaTunnelRowSerializer( - dorisConfig + dorisSinkConfig .getStreamLoadProps() .getProperty(LoadConstants.FORMAT_KEY) .toLowerCase(), seaTunnelRowType, - dorisConfig.getStreamLoadProps().getProperty(LoadConstants.FIELD_DELIMITER_KEY), - dorisConfig.getEnableDelete()); + dorisSinkConfig.getStreamLoadProps().getProperty(LoadConstants.FIELD_DELIMITER_KEY), + dorisSinkConfig.getEnableDelete()); } } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisStreamLoad.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisStreamLoad.java index 8ec59e81ece..6bc6ab9146d 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisStreamLoad.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/DorisStreamLoad.java @@ -18,11 +18,13 @@ package org.apache.seatunnel.connectors.doris.sink.writer; import org.apache.seatunnel.shade.com.fasterxml.jackson.core.type.TypeReference; +import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.seatunnel.shade.com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.common.utils.ExceptionUtils; import org.apache.seatunnel.common.utils.JsonUtils; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSinkConfig; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.rest.models.RespContent; @@ -35,8 +37,6 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -52,10 +52,10 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; -import static com.google.common.base.Preconditions.checkState; import static org.apache.seatunnel.connectors.doris.sink.writer.LoadConstants.LINE_DELIMITER_DEFAULT; import static org.apache.seatunnel.connectors.doris.sink.writer.LoadConstants.LINE_DELIMITER_KEY; import static org.apache.seatunnel.connectors.doris.util.ResponseUtil.LABEL_EXIST_PATTERN; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; /** load data to doris. */ @Slf4j @@ -89,20 +89,20 @@ public class DorisStreamLoad implements Serializable { public DorisStreamLoad( String hostPort, TablePath tablePath, - DorisConfig dorisConfig, + DorisSinkConfig dorisSinkConfig, LabelGenerator labelGenerator, CloseableHttpClient httpClient) { this.hostPort = hostPort; this.db = tablePath.getDatabaseName(); this.table = tablePath.getTableName(); - this.user = dorisConfig.getUsername(); - this.passwd = dorisConfig.getPassword(); + this.user = dorisSinkConfig.getUsername(); + this.passwd = dorisSinkConfig.getPassword(); this.labelGenerator = labelGenerator; this.loadUrlStr = String.format(LOAD_URL_PATTERN, hostPort, db, table); this.abortUrlStr = String.format(ABORT_URL_PATTERN, hostPort, db); - this.enable2PC = dorisConfig.getEnable2PC(); - this.streamLoadProp = dorisConfig.getStreamLoadProps(); - this.enableDelete = dorisConfig.getEnableDelete(); + this.enable2PC = dorisSinkConfig.getEnable2PC(); + this.streamLoadProp = dorisSinkConfig.getStreamLoadProps(); + this.enableDelete = dorisSinkConfig.getEnableDelete(); this.httpClient = httpClient; this.executorService = new ThreadPoolExecutor( @@ -113,7 +113,7 @@ public DorisStreamLoad( new LinkedBlockingQueue<>(), new ThreadFactoryBuilder().setNameFormat("stream-load-upload").build()); this.recordStream = - new RecordStream(dorisConfig.getBufferSize(), dorisConfig.getBufferCount()); + new RecordStream(dorisSinkConfig.getBufferSize(), dorisSinkConfig.getBufferCount()); lineDelimiter = streamLoadProp.getProperty(LINE_DELIMITER_KEY, LINE_DELIMITER_DEFAULT).getBytes(); loadBatchFirstRecord = true; diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/RecordBuffer.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/RecordBuffer.java index 183227fa6cd..39bf45f6e2a 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/RecordBuffer.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/sink/writer/RecordBuffer.java @@ -31,7 +31,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; -import static com.google.common.base.Preconditions.checkState; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; /** Channel of record stream and HTTP data stream. */ @Slf4j diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSource.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSource.java index c04f074021a..8b5f168a2d5 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSource.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSource.java @@ -17,34 +17,36 @@ package org.apache.seatunnel.connectors.doris.source; -import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.Boundedness; import org.apache.seatunnel.api.source.SeaTunnelSource; import org.apache.seatunnel.api.source.SourceReader; import org.apache.seatunnel.api.source.SourceSplitEnumerator; import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; import org.apache.seatunnel.connectors.doris.source.reader.DorisSourceReader; import org.apache.seatunnel.connectors.doris.source.split.DorisSourceSplit; import org.apache.seatunnel.connectors.doris.source.split.DorisSourceSplitEnumerator; import lombok.extern.slf4j.Slf4j; -import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @Slf4j public class DorisSource implements SeaTunnelSource { private static final long serialVersionUID = 6139826339248788618L; - private final DorisConfig config; - private final CatalogTable catalogTable; + private final DorisSourceConfig config; + private final Map dorisSourceTables; - public DorisSource(ReadonlyConfig config, CatalogTable catalogTable) { - this.config = DorisConfig.of(config); - this.catalogTable = catalogTable; + public DorisSource( + DorisSourceConfig config, Map dorisSourceTables) { + this.config = config; + this.dorisSourceTables = dorisSourceTables; } @Override @@ -59,20 +61,21 @@ public Boundedness getBoundedness() { @Override public List getProducedCatalogTables() { - return Collections.singletonList(catalogTable); + return dorisSourceTables.values().stream() + .map(DorisSourceTable::getCatalogTable) + .collect(Collectors.toList()); } @Override public SourceReader createReader( SourceReader.Context readerContext) { - return new DorisSourceReader(readerContext, config, catalogTable.getSeaTunnelRowType()); + return new DorisSourceReader(readerContext, config, dorisSourceTables); } @Override public SourceSplitEnumerator createEnumerator( SourceSplitEnumerator.Context enumeratorContext) { - return new DorisSourceSplitEnumerator( - enumeratorContext, config, catalogTable.getSeaTunnelRowType()); + return new DorisSourceSplitEnumerator(enumeratorContext, config, dorisSourceTables); } @Override @@ -80,6 +83,6 @@ public SourceSplitEnumerator restoreEnumerat SourceSplitEnumerator.Context enumeratorContext, DorisSourceState checkpointState) { return new DorisSourceSplitEnumerator( - enumeratorContext, config, catalogTable.getSeaTunnelRowType(), checkpointState); + enumeratorContext, config, dorisSourceTables, checkpointState); } } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceFactory.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceFactory.java index 75cc266edad..506a7c97dc8 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceFactory.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceFactory.java @@ -17,7 +17,6 @@ package org.apache.seatunnel.connectors.doris.source; -import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.configuration.util.OptionRule; import org.apache.seatunnel.api.source.SeaTunnelSource; import org.apache.seatunnel.api.source.SourceSplit; @@ -29,8 +28,8 @@ import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; import org.apache.seatunnel.connectors.doris.catalog.DorisCatalog; import org.apache.seatunnel.connectors.doris.catalog.DorisCatalogFactory; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; -import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; +import org.apache.seatunnel.connectors.doris.config.DorisTableConfig; import org.apache.commons.lang3.StringUtils; @@ -39,62 +38,88 @@ import java.io.Serializable; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DATABASE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.DORIS_BATCH_SIZE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.FENODES; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.IDENTIFIER; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.PASSWORD; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.QUERY_PORT; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.TABLE; +import static org.apache.seatunnel.connectors.doris.config.DorisOptions.USERNAME; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_FILTER_QUERY; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.DORIS_READ_FIELD; +import static org.apache.seatunnel.connectors.doris.config.DorisSourceOptions.TABLE_LIST; + @Slf4j @AutoService(Factory.class) public class DorisSourceFactory implements TableSourceFactory { @Override public String factoryIdentifier() { - return DorisConfig.IDENTIFIER; + return IDENTIFIER; } @Override public OptionRule optionRule() { return OptionRule.builder() - .required( - DorisOptions.FENODES, - DorisOptions.USERNAME, - DorisOptions.PASSWORD, - DorisOptions.DATABASE, - DorisOptions.TABLE) - .optional(DorisOptions.DORIS_FILTER_QUERY) - .optional(DorisOptions.DORIS_READ_FIELD) - .optional(DorisOptions.QUERY_PORT) - .optional(DorisOptions.DORIS_BATCH_SIZE) + .required(FENODES, USERNAME, PASSWORD) + .optional(TABLE_LIST) + .optional(DATABASE) + .optional(TABLE) + .optional(DORIS_FILTER_QUERY) + .optional(DORIS_READ_FIELD) + .optional(QUERY_PORT) + .optional(DORIS_BATCH_SIZE) .build(); } @Override public TableSource createSource(TableSourceFactoryContext context) { - ReadonlyConfig options = context.getOptions(); - CatalogTable table; - DorisCatalogFactory dorisCatalogFactory = new DorisCatalogFactory(); - DorisCatalog catalog = (DorisCatalog) dorisCatalogFactory.createCatalog("doris", options); - catalog.open(); - String tableIdentifier = - options.get(DorisOptions.DATABASE) + "." + options.get(DorisOptions.TABLE); - TablePath tablePath = TablePath.of(tableIdentifier); + DorisSourceConfig dorisSourceConfig = DorisSourceConfig.of(context.getOptions()); + List dorisTableConfigList = dorisSourceConfig.getTableConfigList(); + Map dorisSourceTables = new HashMap<>(); + for (DorisTableConfig dorisTableConfig : dorisTableConfigList) { + CatalogTable table; + DorisCatalogFactory dorisCatalogFactory = new DorisCatalogFactory(); + DorisCatalog catalog = + (DorisCatalog) dorisCatalogFactory.createCatalog("doris", context.getOptions()); + catalog.open(); + TablePath tablePath = TablePath.of(dorisTableConfig.getTableIdentifier()); + String readFields = dorisTableConfig.getReadField(); + try { + List readFiledList = null; + if (StringUtils.isNotBlank(readFields)) { + readFiledList = + Arrays.stream(readFields.split(",")) + .map(String::trim) + .collect(Collectors.toList()); + } - try { - String read_fields = options.get(DorisOptions.DORIS_READ_FIELD); - List readFiledList = null; - if (StringUtils.isNotBlank(read_fields)) { - readFiledList = - Arrays.stream(read_fields.split(",")) - .map(String::trim) - .collect(Collectors.toList()); + table = catalog.getTable(tablePath, readFiledList); + } catch (Exception e) { + log.error("create source error"); + throw e; } - - table = catalog.getTable(tablePath, readFiledList); - } catch (Exception e) { - log.error("create source error"); - throw e; + dorisSourceTables.put( + tablePath, + DorisSourceTable.builder() + .catalogTable(table) + .tablePath(tablePath) + .readField(readFields) + .filterQuery(dorisTableConfig.getFilterQuery()) + .batchSize(dorisTableConfig.getBatchSize()) + .tabletSize(dorisTableConfig.getTabletSize()) + .execMemLimit(dorisTableConfig.getExecMemLimit()) + .build()); } - CatalogTable finalTable = table; - return () -> (SeaTunnelSource) new DorisSource(options, finalTable); + return () -> + (SeaTunnelSource) + new DorisSource(dorisSourceConfig, dorisSourceTables); } @Override diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceTable.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceTable.java new file mode 100644 index 00000000000..b09568db9ed --- /dev/null +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/DorisSourceTable.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.doris.source; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.TablePath; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +@Data +@Builder +public class DorisSourceTable implements Serializable { + private static final long serialVersionUID = 1L; + + private final TablePath tablePath; + private String readField; + private String filterQuery; + private int batchSize; + private Integer tabletSize; + private Long execMemLimit; + private final CatalogTable catalogTable; +} diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisSourceReader.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisSourceReader.java index 66c4e1f269f..ffe1d0e54a0 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisSourceReader.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisSourceReader.java @@ -20,10 +20,13 @@ import org.apache.seatunnel.api.source.Boundedness; import org.apache.seatunnel.api.source.Collector; import org.apache.seatunnel.api.source.SourceReader; +import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; +import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; +import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.rest.PartitionDefinition; +import org.apache.seatunnel.connectors.doris.source.DorisSourceTable; import org.apache.seatunnel.connectors.doris.source.split.DorisSourceSplit; import lombok.extern.slf4j.Slf4j; @@ -32,27 +35,30 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Queue; @Slf4j public class DorisSourceReader implements SourceReader { private final Context context; - private final DorisConfig dorisConfig; + private final DorisSourceConfig dorisSourceConfig; private final Queue splitsQueue; private volatile boolean noMoreSplits; private DorisValueReader valueReader; - private SeaTunnelRowType seaTunnelRowType; + private final Map tables; public DorisSourceReader( - Context context, DorisConfig dorisConfig, SeaTunnelRowType seaTunnelRowType) { + Context context, + DorisSourceConfig dorisSourceConfig, + Map tables) { this.splitsQueue = new ArrayDeque<>(); this.context = context; - this.dorisConfig = dorisConfig; - this.seaTunnelRowType = seaTunnelRowType; + this.dorisSourceConfig = dorisSourceConfig; + this.tables = tables; } @Override @@ -71,7 +77,16 @@ public void pollNext(Collector output) throws Exception { DorisSourceSplit nextSplit = splitsQueue.poll(); if (nextSplit != null) { PartitionDefinition partition = nextSplit.getPartitionDefinition(); - valueReader = new DorisValueReader(partition, dorisConfig, seaTunnelRowType); + DorisSourceTable dorisSourceTable = + tables.get(TablePath.of(partition.getDatabase(), partition.getTable())); + if (dorisSourceTable == null) { + throw new DorisConnectorException( + DorisConnectorErrorCode.SHOULD_NEVER_HAPPEN, + String.format( + "the table '%s.%s' cannot be found in table_list of job configuration.", + partition.getDatabase(), partition.getTable())); + } + valueReader = new DorisValueReader(partition, dorisSourceConfig, dorisSourceTable); while (valueReader.hasNext()) { SeaTunnelRow record = valueReader.next(); output.collect(record); diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisValueReader.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisValueReader.java index 18d3d004d94..68d2eecfb51 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisValueReader.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/reader/DorisValueReader.java @@ -20,11 +20,12 @@ import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.doris.backend.BackendClient; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorErrorCode; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.rest.PartitionDefinition; import org.apache.seatunnel.connectors.doris.rest.models.Schema; +import org.apache.seatunnel.connectors.doris.source.DorisSourceTable; import org.apache.seatunnel.connectors.doris.source.serialization.Routing; import org.apache.seatunnel.connectors.doris.source.serialization.RowBatch; import org.apache.seatunnel.connectors.doris.util.SchemaUtils; @@ -54,7 +55,8 @@ public class DorisValueReader { protected Lock clientLock = new ReentrantLock(); private PartitionDefinition partition; - private DorisConfig config; + private DorisSourceTable dorisSourceTable; + private DorisSourceConfig config; protected int offset = 0; protected AtomicBoolean eos = new AtomicBoolean(false); @@ -72,12 +74,15 @@ public class DorisValueReader { protected boolean asyncThreadStarted; public DorisValueReader( - PartitionDefinition partition, DorisConfig config, SeaTunnelRowType seaTunnelRowType) { + PartitionDefinition partition, + DorisSourceConfig config, + DorisSourceTable dorisSourceTable) { this.partition = partition; this.config = config; + this.dorisSourceTable = dorisSourceTable; this.client = backendClient(); this.deserializeArrowToRowBatchAsync = config.getDeserializeArrowAsync(); - this.seaTunnelRowType = seaTunnelRowType; + this.seaTunnelRowType = dorisSourceTable.getCatalogTable().getSeaTunnelRowType(); int blockingQueueSize = config.getDeserializeQueueSize(); if (this.deserializeArrowToRowBatchAsync) { this.rowBatchBlockingQueue = new ArrayBlockingQueue<>(blockingQueueSize); @@ -117,9 +122,9 @@ private TScanOpenParams openParams() { params.tablet_ids = Arrays.asList(partition.getTabletIds().toArray(new Long[] {})); params.opaqued_query_plan = partition.getQueryPlan(); // max row number of one read batch - Integer batchSize = config.getBatchSize(); + Integer batchSize = dorisSourceTable.getBatchSize(); Integer queryDorisTimeout = config.getRequestQueryTimeoutS(); - Long execMemLimit = config.getExecMemLimit(); + Long execMemLimit = dorisSourceTable.getExecMemLimit(); params.setBatchSize(batchSize); params.setQueryTimeout(queryDorisTimeout); params.setMemLimit(execMemLimit); @@ -250,7 +255,9 @@ public SeaTunnelRow next() { throw new DorisConnectorException( DorisConnectorErrorCode.SHOULD_NEVER_HAPPEN, "never happen error."); } - return rowBatch.next(); + SeaTunnelRow next = rowBatch.next(); + next.setTableId(dorisSourceTable.getTablePath().toString()); + return next; } public void close() { diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/serialization/RowBatch.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/serialization/RowBatch.java index 930e83c5686..a1f1c678aa4 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/serialization/RowBatch.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/serialization/RowBatch.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.doris.source.serialization; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; import org.apache.seatunnel.shade.org.apache.arrow.memory.RootAllocator; import org.apache.seatunnel.shade.org.apache.arrow.vector.BigIntVector; import org.apache.seatunnel.shade.org.apache.arrow.vector.BitVector; @@ -55,7 +56,6 @@ import org.apache.doris.sdk.thrift.TScanBatchResult; -import com.google.common.base.Preconditions; import lombok.extern.slf4j.Slf4j; import java.io.ByteArrayInputStream; diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/split/DorisSourceSplitEnumerator.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/split/DorisSourceSplitEnumerator.java index d2d2e61d7e1..1aa10a88b54 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/split/DorisSourceSplitEnumerator.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/source/split/DorisSourceSplitEnumerator.java @@ -18,13 +18,14 @@ package org.apache.seatunnel.connectors.doris.source.split; import org.apache.seatunnel.api.source.SourceSplitEnumerator; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; -import org.apache.seatunnel.connectors.doris.config.DorisConfig; +import org.apache.seatunnel.connectors.doris.config.DorisSourceConfig; import org.apache.seatunnel.connectors.doris.exception.DorisConnectorException; import org.apache.seatunnel.connectors.doris.rest.PartitionDefinition; import org.apache.seatunnel.connectors.doris.rest.RestService; import org.apache.seatunnel.connectors.doris.source.DorisSourceState; +import org.apache.seatunnel.connectors.doris.source.DorisSourceTable; import lombok.extern.slf4j.Slf4j; @@ -41,31 +42,31 @@ public class DorisSourceSplitEnumerator implements SourceSplitEnumerator { - private Context context; - private DorisConfig dorisConfig; + private final Context context; + private final DorisSourceConfig dorisSourceConfig; private volatile boolean shouldEnumerate; private final Map> pendingSplit; - private SeaTunnelRowType seaTunnelRowType; + private final Map dorisSourceTables; private final Object stateLock = new Object(); public DorisSourceSplitEnumerator( Context context, - DorisConfig dorisConfig, - SeaTunnelRowType seaTunnelRowType) { - this(context, dorisConfig, seaTunnelRowType, null); + DorisSourceConfig dorisSourceConfig, + Map dorisSourceTables) { + this(context, dorisSourceConfig, dorisSourceTables, null); } public DorisSourceSplitEnumerator( Context context, - DorisConfig dorisConfig, - SeaTunnelRowType rowType, + DorisSourceConfig dorisSourceConfig, + Map dorisSourceTables, DorisSourceState dorisSourceState) { this.context = context; - this.dorisConfig = dorisConfig; - this.seaTunnelRowType = rowType; + this.dorisSourceConfig = dorisSourceConfig; + this.dorisSourceTables = dorisSourceTables; this.pendingSplit = new ConcurrentHashMap<>(); this.shouldEnumerate = (dorisSourceState == null); if (dorisSourceState != null) { @@ -149,10 +150,12 @@ public void notifyCheckpointComplete(long checkpointId) {} private List getDorisSourceSplit() { List splits = new ArrayList<>(); - List partitions = - RestService.findPartitions(seaTunnelRowType, dorisConfig, log); - for (PartitionDefinition partition : partitions) { - splits.add(new DorisSourceSplit(partition, String.valueOf(partition.hashCode()))); + for (DorisSourceTable dorisSourceTable : dorisSourceTables.values()) { + List partitions = + RestService.findPartitions(dorisSourceConfig, dorisSourceTable, log); + for (PartitionDefinition partition : partitions) { + splits.add(new DorisSourceSplit(partition, String.valueOf(partition.hashCode()))); + } } return splits; } diff --git a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/util/DorisCatalogUtil.java b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/util/DorisCatalogUtil.java index 5025caed21c..c7ad6f65052 100644 --- a/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/util/DorisCatalogUtil.java +++ b/seatunnel-connectors-v2/connector-doris/src/main/java/org/apache/seatunnel/connectors/doris/util/DorisCatalogUtil.java @@ -24,7 +24,7 @@ import org.apache.seatunnel.api.table.catalog.TableSchema; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; import org.apache.seatunnel.api.table.converter.TypeConverter; -import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSinkOptions; import org.apache.seatunnel.connectors.seatunnel.common.sql.template.SqlTemplate; import org.apache.commons.lang3.StringUtils; @@ -39,7 +39,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Slf4j public class DorisCatalogUtil { @@ -155,7 +155,7 @@ public static String getCreateTableStatement( SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getPlaceHolder(), primaryKey, tablePath.getFullName(), - DorisOptions.SAVE_MODE_CREATE_TEMPLATE.key()); + DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE.key()); template = template.replaceAll( SaveModePlaceHolder.ROWTYPE_PRIMARY_KEY.getReplacePlaceHolder(), @@ -165,7 +165,7 @@ public static String getCreateTableStatement( SaveModePlaceHolder.ROWTYPE_UNIQUE_KEY.getPlaceHolder(), uniqueKey, tablePath.getFullName(), - DorisOptions.SAVE_MODE_CREATE_TEMPLATE.key()); + DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE.key()); template = template.replaceAll( SaveModePlaceHolder.ROWTYPE_UNIQUE_KEY.getReplacePlaceHolder(), uniqueKey); @@ -174,7 +174,7 @@ public static String getCreateTableStatement( SaveModePlaceHolder.ROWTYPE_DUPLICATE_KEY.getPlaceHolder(), dupKey, tablePath.getFullName(), - DorisOptions.SAVE_MODE_CREATE_TEMPLATE.key()); + DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE.key()); template = template.replaceAll( SaveModePlaceHolder.ROWTYPE_DUPLICATE_KEY.getReplacePlaceHolder(), dupKey); @@ -252,9 +252,12 @@ private static String columnToDorisType( Column column, TypeConverter typeConverter) { checkNotNull(column, "The column is required."); return String.format( - "`%s` %s %s ", + "`%s` %s %s %s", column.getName(), typeConverter.reconvert(column).getColumnType(), - column.isNullable() ? "NULL" : "NOT NULL"); + column.isNullable() ? "NULL" : "NOT NULL", + StringUtils.isEmpty(column.getComment()) + ? "" + : "COMMENT '" + column.getComment() + "'"); } } diff --git a/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/DorisCreateTableTest.java b/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/DorisCreateTableTest.java index 09a5b6a3293..cdaa55487c6 100644 --- a/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/DorisCreateTableTest.java +++ b/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/DorisCreateTableTest.java @@ -31,7 +31,7 @@ import org.apache.seatunnel.api.table.type.LocalTimeType; import org.apache.seatunnel.common.exception.CommonError; import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException; -import org.apache.seatunnel.connectors.doris.config.DorisOptions; +import org.apache.seatunnel.connectors.doris.config.DorisSinkOptions; import org.apache.seatunnel.connectors.doris.datatype.DorisTypeConverterV1; import org.apache.seatunnel.connectors.doris.util.DorisCatalogUtil; @@ -57,7 +57,9 @@ public void test() { columns.add(PhysicalColumn.of("id", BasicType.LONG_TYPE, (Long) null, true, null, "")); columns.add(PhysicalColumn.of("name", BasicType.STRING_TYPE, (Long) null, true, null, "")); - columns.add(PhysicalColumn.of("age", BasicType.INT_TYPE, (Long) null, true, null, "")); + columns.add( + PhysicalColumn.of( + "age", BasicType.INT_TYPE, (Long) null, true, null, "test comment")); columns.add(PhysicalColumn.of("score", BasicType.INT_TYPE, (Long) null, true, null, "")); columns.add(PhysicalColumn.of("gender", BasicType.BYTE_TYPE, (Long) null, true, null, "")); columns.add( @@ -122,7 +124,7 @@ public void test() { Assertions.assertEquals( result, "CREATE TABLE IF NOT EXISTS `test1`.`test2` ( \n" - + "`id` BIGINT NULL ,`age` INT NULL , \n" + + "`id` BIGINT NULL ,`age` INT NULL COMMENT 'test comment' , \n" + "`name` STRING NULL ,`score` INT NULL , \n" + "`create_time` DATETIME NOT NULL , \n" + "`gender` TINYINT NULL \n" @@ -139,7 +141,7 @@ public void test() { + "\"disable_auto_compaction\" = \"false\"\n" + ")"); - String createTemplate = DorisOptions.SAVE_MODE_CREATE_TEMPLATE.defaultValue(); + String createTemplate = DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE.defaultValue(); CatalogTable catalogTable = CatalogTable.of( TableIdentifier.of("test", "test1", "test2"), @@ -169,7 +171,7 @@ public void test() { SaveModePlaceHolder.getDisplay(primaryKeyHolder), createTemplate, primaryKeyHolder, - DorisOptions.SAVE_MODE_CREATE_TEMPLATE.key()); + DorisSinkOptions.SAVE_MODE_CREATE_TEMPLATE.key()); Assertions.assertEquals( exceptSeaTunnelRuntimeException.getMessage(), actualSeaTunnelRuntimeException.getMessage()); diff --git a/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/PreviewActionTest.java b/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/PreviewActionTest.java index e7b52b5963d..96d6e90f970 100644 --- a/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/PreviewActionTest.java +++ b/seatunnel-connectors-v2/connector-doris/src/test/java/org/apache/seatunnel/connectors/doris/catalog/PreviewActionTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.doris.catalog; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.catalog.CatalogTable; @@ -32,8 +34,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.util.Collections; import java.util.HashMap; import java.util.Optional; diff --git a/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidSink.java b/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidSink.java index ad515aeeb7c..786ab56e2cd 100644 --- a/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidSink.java +++ b/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidSink.java @@ -27,6 +27,7 @@ import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; import java.io.IOException; +import java.util.Optional; import static org.apache.seatunnel.connectors.druid.config.DruidConfig.BATCH_SIZE; import static org.apache.seatunnel.connectors.druid.config.DruidConfig.COORDINATOR_URL; @@ -58,4 +59,9 @@ public DruidWriter createWriter(SinkWriter.Context context) throws IOException { config.get(DATASOURCE), config.get(BATCH_SIZE)); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidWriter.java b/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidWriter.java index 0ebb1c49adb..af8e3a330fc 100644 --- a/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidWriter.java +++ b/seatunnel-connectors-v2/connector-druid/src/main/java/org/apache/seatunnel/connectors/druid/sink/DruidWriter.java @@ -18,6 +18,8 @@ package org.apache.seatunnel.connectors.druid.sink; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.sink.SupportMultiTableSinkWriter; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; @@ -57,7 +59,6 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.datatype.joda.JodaModule; -import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.ArrayList; diff --git a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchCatalog.java b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchCatalog.java index 0b213cfae00..042a6d06c1d 100644 --- a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchCatalog.java +++ b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchCatalog.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.easysearch.catalog; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.configuration.util.ConfigUtil; @@ -38,14 +39,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Lists; - import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** * Easysearch catalog implementation. diff --git a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchDataTypeConvertor.java b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchDataTypeConvertor.java index 400fbd1c4d1..52ce700cd64 100644 --- a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/catalog/EasysearchDataTypeConvertor.java @@ -27,7 +27,7 @@ import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @AutoService(DataTypeConvertor.class) public class EasysearchDataTypeConvertor implements DataTypeConvertor { diff --git a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSink.java b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSink.java index d96f7eb6b56..0eb4fbcd48d 100644 --- a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSink.java +++ b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/sink/EasysearchSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.easysearch.state.EasysearchAggregatedCommitInfo; @@ -30,6 +31,8 @@ import com.google.auto.service.AutoService; +import java.util.Optional; + import static org.apache.seatunnel.connectors.seatunnel.easysearch.config.SinkConfig.MAX_BATCH_SIZE; import static org.apache.seatunnel.connectors.seatunnel.easysearch.config.SinkConfig.MAX_RETRY_COUNT; @@ -75,4 +78,9 @@ public SinkWriter creat return new EasysearchSinkWriter( context, seaTunnelRowType, pluginConfig, maxBatchSize, maxRetryCount); } + + @Override + public Optional getWriteCatalogTable() { + return SeaTunnelSink.super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/source/EasysearchSource.java b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/source/EasysearchSource.java index 0650186961b..ea2b40f8539 100644 --- a/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/source/EasysearchSource.java +++ b/seatunnel-connectors-v2/connector-easysearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/easysearch/source/EasysearchSource.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.easysearch.source; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.common.PrepareFailException; @@ -38,7 +39,6 @@ import org.apache.commons.collections4.CollectionUtils; import com.google.auto.service.AutoService; -import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Arrays; diff --git a/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/EasysearchSourceTest.java b/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/EasysearchSourceTest.java index 69ba405ad59..20c2bfb5080 100644 --- a/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/EasysearchSourceTest.java +++ b/seatunnel-connectors-v2/connector-easysearch/src/test/java/org/apache/seatunnel/connectors/seatunnel/easysearch/EasysearchSourceTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.easysearch; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.table.type.BasicType; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; @@ -28,8 +30,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchCatalog.java b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchCatalog.java index 3219293fd60..2ce303a0714 100644 --- a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchCatalog.java +++ b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchCatalog.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.elasticsearch.catalog; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.configuration.util.ConfigUtil; import org.apache.seatunnel.api.table.catalog.Catalog; @@ -41,7 +43,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import java.util.Collections; @@ -50,7 +51,7 @@ import java.util.Map; import java.util.Optional; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** * Elasticsearch catalog implementation. diff --git a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchDataTypeConvertor.java b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchDataTypeConvertor.java index 7aecdfb9ea6..bb088bf5d29 100644 --- a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/catalog/ElasticSearchDataTypeConvertor.java @@ -28,7 +28,7 @@ import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** @deprecated instead by {@link ElasticSearchTypeConverter} */ @Deprecated diff --git a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/dto/ElasticsearchClusterInfo.java b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/dto/ElasticsearchClusterInfo.java index 592375a65ae..7c6311c9775 100644 --- a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/dto/ElasticsearchClusterInfo.java +++ b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/dto/ElasticsearchClusterInfo.java @@ -17,9 +17,10 @@ package org.apache.seatunnel.connectors.seatunnel.elasticsearch.dto; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.connectors.seatunnel.elasticsearch.constant.ElasticsearchVersion; -import com.google.common.base.Strings; import lombok.Builder; import lombok.Getter; import lombok.ToString; diff --git a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/sink/ElasticsearchSink.java b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/sink/ElasticsearchSink.java index fed65733e01..ffe2b0520b2 100644 --- a/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/sink/ElasticsearchSink.java +++ b/seatunnel-connectors-v2/connector-elasticsearch/src/main/java/org/apache/seatunnel/connectors/seatunnel/elasticsearch/sink/ElasticsearchSink.java @@ -95,4 +95,9 @@ public Optional getSaveModeHandler() { new DefaultSaveModeHandler( schemaSaveMode, dataSaveMode, catalog, tablePath, null, null)); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-email/src/main/java/org/apache/seatunnel/connectors/seatunnel/email/sink/EmailSink.java b/seatunnel-connectors-v2/connector-email/src/main/java/org/apache/seatunnel/connectors/seatunnel/email/sink/EmailSink.java index 0a3df90a120..24f9c2295f7 100644 --- a/seatunnel-connectors-v2/connector-email/src/main/java/org/apache/seatunnel/connectors/seatunnel/email/sink/EmailSink.java +++ b/seatunnel-connectors-v2/connector-email/src/main/java/org/apache/seatunnel/connectors/seatunnel/email/sink/EmailSink.java @@ -27,13 +27,17 @@ import org.apache.seatunnel.connectors.seatunnel.email.config.EmailConfig; import org.apache.seatunnel.connectors.seatunnel.email.config.EmailSinkConfig; +import lombok.Getter; + +import java.util.Optional; + public class EmailSink extends AbstractSimpleSink implements SupportMultiTableSink { - private SeaTunnelRowType seaTunnelRowType; - private ReadonlyConfig readonlyConfig; - private CatalogTable catalogTable; - private EmailSinkConfig pluginConfig; + private final SeaTunnelRowType seaTunnelRowType; + @Getter private ReadonlyConfig readonlyConfig; + private final CatalogTable catalogTable; + private final EmailSinkConfig pluginConfig; public EmailSink(ReadonlyConfig config, CatalogTable table) { this.readonlyConfig = config; @@ -51,4 +55,9 @@ public EmailSinkWriter createWriter(SinkWriter.Context context) { public String getPluginName() { return EmailConfig.CONNECTOR_IDENTITY; } + + @Override + public Optional getWriteCatalogTable() { + return Optional.of(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-email/src/main/resources/fake_to_emailsink_flink.conf b/seatunnel-connectors-v2/connector-email/src/main/resources/fake_to_emailsink_flink.conf index 2c11415ccd1..7e61284c098 100644 --- a/seatunnel-connectors-v2/connector-email/src/main/resources/fake_to_emailsink_flink.conf +++ b/seatunnel-connectors-v2/connector-email/src/main/resources/fake_to_emailsink_flink.conf @@ -27,7 +27,7 @@ env { source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { - result_table_name = "fake" + plugin_output = "fake" field_name = "name,age" } diff --git a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/FakeOption.java b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/FakeOption.java index fe956152a88..9c05c86bb67 100644 --- a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/FakeOption.java +++ b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/FakeOption.java @@ -27,12 +27,6 @@ public class FakeOption { - public static final Option>> TABLES_CONFIGS = - Options.key("tables_configs") - .type(new TypeReference>>() {}) - .noDefaultValue() - .withDescription("The multiple table config list of fake source"); - public static final Option>> ROWS = Options.key("rows") .type(new TypeReference>>() {}) diff --git a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/MultipleTableFakeSourceConfig.java b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/MultipleTableFakeSourceConfig.java index 051d88a88f9..21339c2b334 100644 --- a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/MultipleTableFakeSourceConfig.java +++ b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/config/MultipleTableFakeSourceConfig.java @@ -17,11 +17,13 @@ package org.apache.seatunnel.connectors.seatunnel.fake.config; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; import org.apache.commons.collections4.CollectionUtils; -import com.google.common.collect.Lists; import lombok.Getter; import java.io.Serializable; @@ -36,7 +38,7 @@ public class MultipleTableFakeSourceConfig implements Serializable { @Getter private List fakeConfigs; public MultipleTableFakeSourceConfig(ReadonlyConfig fakeSourceRootConfig) { - if (fakeSourceRootConfig.getOptional(FakeOption.TABLES_CONFIGS).isPresent()) { + if (fakeSourceRootConfig.getOptional(TableSchemaOptions.TABLE_CONFIGS).isPresent()) { parseFromConfigs(fakeSourceRootConfig); } else { parseFromConfig(fakeSourceRootConfig); @@ -56,7 +58,7 @@ public MultipleTableFakeSourceConfig(ReadonlyConfig fakeSourceRootConfig) { private void parseFromConfigs(ReadonlyConfig readonlyConfig) { List readonlyConfigs = - readonlyConfig.getOptional(FakeOption.TABLES_CONFIGS).get().stream() + readonlyConfig.getOptional(TableSchemaOptions.TABLE_CONFIGS).get().stream() .map(ReadonlyConfig::fromMap) .collect(Collectors.toList()); // Use the config outside if it's not set in sub config diff --git a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeDataGenerator.java b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeDataGenerator.java index 017b2d29469..4f385754d59 100644 --- a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeDataGenerator.java +++ b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeDataGenerator.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper; import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.node.ArrayNode; import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; @@ -51,6 +52,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.function.Consumer; import java.util.function.Function; import static org.apache.seatunnel.api.table.type.SqlType.TIME; @@ -105,26 +107,36 @@ private SeaTunnelRow randomRow() { return seaTunnelRow; } + @VisibleForTesting + public List generateFakedRows(int rowNum) { + List rows = new ArrayList<>(); + generateFakedRows(rowNum, rows::add); + return rows; + } + /** * @param rowNum The number of pieces of data to be generated by the current task - * @return The generated data + * @param consumer The generated data is sent to consumer + * @return The number of generated data row count */ - public List generateFakedRows(int rowNum) { + public long generateFakedRows(int rowNum, Consumer consumer) { // Use manual configuration data preferentially - List seaTunnelRows = new ArrayList<>(); + long rowCount = 0; if (fakeConfig.getFakeRows() != null) { SeaTunnelDataType[] fieldTypes = catalogTable.getSeaTunnelRowType().getFieldTypes(); String[] fieldNames = catalogTable.getSeaTunnelRowType().getFieldNames(); for (FakeConfig.RowData rowData : fakeConfig.getFakeRows()) { customField(rowData, fieldTypes, fieldNames); - seaTunnelRows.add(convertRow(rowData)); + consumer.accept(convertRow(rowData)); + rowCount++; } } else { for (int i = 0; i < rowNum; i++) { - seaTunnelRows.add(randomRow()); + consumer.accept(randomRow()); + rowCount++; } } - return seaTunnelRows; + return rowCount; } private void customField( diff --git a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceFactory.java b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceFactory.java index 73af0b0cd53..4ea71dda5b4 100644 --- a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceFactory.java +++ b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceFactory.java @@ -54,7 +54,6 @@ import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.SPLIT_READ_INTERVAL; import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.STRING_FAKE_MODE; import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.STRING_TEMPLATE; -import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.TABLES_CONFIGS; import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.TIME_HOUR_TEMPLATE; import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.TIME_MINUTE_TEMPLATE; import static org.apache.seatunnel.connectors.seatunnel.fake.config.FakeOption.TIME_SECOND_TEMPLATE; @@ -72,7 +71,7 @@ public String factoryIdentifier() { @Override public OptionRule optionRule() { return OptionRule.builder() - .optional(TABLES_CONFIGS) + .optional(TableSchemaOptions.TABLE_CONFIGS) .optional(TableSchemaOptions.SCHEMA) .optional(STRING_FAKE_MODE) .conditional(STRING_FAKE_MODE, FakeOption.FakeMode.TEMPLATE, STRING_TEMPLATE) diff --git a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceReader.java b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceReader.java index 063ece63d2e..e3309c6be60 100644 --- a/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceReader.java +++ b/seatunnel-connectors-v2/connector-fake/src/main/java/org/apache/seatunnel/connectors/seatunnel/fake/source/FakeSourceReader.java @@ -89,12 +89,11 @@ public void pollNext(Collector output) throws InterruptedException if (null != split) { FakeDataGenerator fakeDataGenerator = fakeDataGeneratorMap.get(split.getTableId()); // Randomly generated data are sent directly to the downstream operator - List seaTunnelRows = - fakeDataGenerator.generateFakedRows(split.getRowNum()); - seaTunnelRows.forEach(output::collect); + long rowCount = + fakeDataGenerator.generateFakedRows(split.getRowNum(), output::collect); log.info( "{} rows of data have been generated in split({}) for table {}. Generation time: {}", - seaTunnelRows.size(), + rowCount, split.splitId(), split.getTableId(), latestTimestamp); diff --git a/seatunnel-connectors-v2/connector-fake/src/test/resources/complex.schema.conf b/seatunnel-connectors-v2/connector-fake/src/test/resources/complex.schema.conf index e3f0d7ee267..f38d02e5946 100644 --- a/seatunnel-connectors-v2/connector-fake/src/test/resources/complex.schema.conf +++ b/seatunnel-connectors-v2/connector-fake/src/test/resources/complex.schema.conf @@ -57,5 +57,5 @@ FakeSource { } } } - result_table_name = "fake" + plugin_output = "fake" } diff --git a/seatunnel-connectors-v2/connector-fake/src/test/resources/multiple_table.conf b/seatunnel-connectors-v2/connector-fake/src/test/resources/multiple_table.conf index d42413934e2..766a53b0ea6 100644 --- a/seatunnel-connectors-v2/connector-fake/src/test/resources/multiple_table.conf +++ b/seatunnel-connectors-v2/connector-fake/src/test/resources/multiple_table.conf @@ -72,5 +72,5 @@ FakeSource { } } ] - result_table_name = "fake" + plugin_output = "fake" } \ No newline at end of file diff --git a/seatunnel-connectors-v2/connector-fake/src/test/resources/simple.schema.conf b/seatunnel-connectors-v2/connector-fake/src/test/resources/simple.schema.conf index 58848a91c7b..2460a8dc490 100644 --- a/seatunnel-connectors-v2/connector-fake/src/test/resources/simple.schema.conf +++ b/seatunnel-connectors-v2/connector-fake/src/test/resources/simple.schema.conf @@ -40,5 +40,5 @@ FakeSource { c_timestamp = timestamp } } - result_table_name = "fake" + plugin_output = "fake" } \ No newline at end of file diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/BaseHdfsFileSource.java b/seatunnel-connectors-v2/connector-file/connector-file-base-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/BaseHdfsFileSource.java index 9af2721e220..9cf7cace0ba 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/BaseHdfsFileSource.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/BaseHdfsFileSource.java @@ -21,9 +21,9 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -74,6 +74,10 @@ public void prepare(Config pluginConfig) throws PrepareFailException { pluginConfig.getString(HdfsSourceConfigOptions.REMOTE_USER.key())); } + if (pluginConfig.hasPath(HdfsSourceConfigOptions.KRB5_PATH.key())) { + hadoopConf.setKrb5Path(pluginConfig.getString(HdfsSourceConfigOptions.KRB5_PATH.key())); + } + if (pluginConfig.hasPath(HdfsSourceConfigOptions.KERBEROS_PRINCIPAL.key())) { hadoopConf.setKerberosPrincipal( pluginConfig.getString(HdfsSourceConfigOptions.KERBEROS_PRINCIPAL.key())); @@ -109,9 +113,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { case JSON: case EXCEL: case XML: - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - readStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); + CatalogTable userDefinedCatalogTable = + CatalogTableUtil.buildWithConfig(pluginConfig); + readStrategy.setCatalogTable(userDefinedCatalogTable); rowType = readStrategy.getActualSeaTunnelRowTypeInfo(); break; case ORC: diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/ArchiveCompressFormat.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/ArchiveCompressFormat.java index da30887a824..b90990d4348 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/ArchiveCompressFormat.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/ArchiveCompressFormat.java @@ -35,6 +35,7 @@ public enum ArchiveCompressFormat { ZIP(".zip"), TAR(".tar"), TAR_GZ(".tar.gz"), + GZ(".gz"), ; private final String archiveCompressCodec; diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSinkConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSinkConfig.java index 3a6513e993d..bbbf4553115 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSinkConfig.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSinkConfig.java @@ -32,7 +32,7 @@ import java.io.Serializable; import java.util.Locale; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Data public class BaseFileSinkConfig implements DelimiterConfig, Serializable { diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java index 373ada564a8..10b969b0086 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseFileSourceConfig.java @@ -95,7 +95,7 @@ private CatalogTable parseCatalogTable(ReadonlyConfig readonlyConfig) { case JSON: case EXCEL: case XML: - readStrategy.setSeaTunnelRowTypeInfo(catalogTable.getSeaTunnelRowType()); + readStrategy.setCatalogTable(catalogTable); return newCatalogTable(catalogTable, readStrategy.getActualSeaTunnelRowTypeInfo()); case ORC: case PARQUET: diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseMultipleTableFileSourceConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseMultipleTableFileSourceConfig.java index 0cda71d091f..f16c2abf065 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseMultipleTableFileSourceConfig.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseMultipleTableFileSourceConfig.java @@ -17,9 +17,11 @@ package org.apache.seatunnel.connectors.seatunnel.file.config; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import com.google.common.collect.Lists; import lombok.Getter; import java.io.Serializable; @@ -33,7 +35,7 @@ public abstract class BaseMultipleTableFileSourceConfig implements Serializable @Getter private List fileSourceConfigs; public BaseMultipleTableFileSourceConfig(ReadonlyConfig fileSourceRootConfig) { - if (fileSourceRootConfig.getOptional(BaseSourceConfigOptions.TABLE_CONFIGS).isPresent()) { + if (fileSourceRootConfig.getOptional(TableSchemaOptions.TABLE_CONFIGS).isPresent()) { parseFromFileSourceConfigs(fileSourceRootConfig); } else { parseFromFileSourceConfig(fileSourceRootConfig); @@ -42,7 +44,7 @@ public BaseMultipleTableFileSourceConfig(ReadonlyConfig fileSourceRootConfig) { private void parseFromFileSourceConfigs(ReadonlyConfig fileSourceRootConfig) { this.fileSourceConfigs = - fileSourceRootConfig.get(BaseSourceConfigOptions.TABLE_CONFIGS).stream() + fileSourceRootConfig.get(TableSchemaOptions.TABLE_CONFIGS).stream() .map(ReadonlyConfig::fromMap) .map(this::getBaseSourceConfig) .collect(Collectors.toList()); diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseSourceConfigOptions.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseSourceConfigOptions.java index ddcc13d47d4..dedddeacbb1 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseSourceConfigOptions.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/BaseSourceConfigOptions.java @@ -17,8 +17,6 @@ package org.apache.seatunnel.connectors.seatunnel.file.config; -import org.apache.seatunnel.shade.com.fasterxml.jackson.core.type.TypeReference; - import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; import org.apache.seatunnel.common.utils.DateTimeUtils; @@ -27,7 +25,6 @@ import org.apache.seatunnel.format.text.constant.TextFormatConstant; import java.util.List; -import java.util.Map; public class BaseSourceConfigOptions { public static final Option FILE_FORMAT_TYPE = @@ -51,6 +48,12 @@ public class BaseSourceConfigOptions { .withDescription( "The separator between columns in a row of data. Only needed by `text` file format"); + public static final Option NULL_FORMAT = + Options.key("null_format") + .stringType() + .noDefaultValue() + .withDescription("The string that represents a null value"); + public static final Option ENCODING = Options.key("encoding") .stringType() @@ -169,11 +172,4 @@ public class BaseSourceConfigOptions { .enumType(ArchiveCompressFormat.class) .defaultValue(ArchiveCompressFormat.NONE) .withDescription("Archive compression codec"); - - public static final Option>> TABLE_CONFIGS = - Options.key("tables_configs") - .type(new TypeReference>>() {}) - .noDefaultValue() - .withDescription( - "Local file source configs, used to create multiple local file source."); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hadoop/HadoopFileSystemProxy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hadoop/HadoopFileSystemProxy.java index 79df7ef3fa7..8f8aef05481 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hadoop/HadoopFileSystemProxy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hadoop/HadoopFileSystemProxy.java @@ -18,6 +18,7 @@ package org.apache.seatunnel.connectors.seatunnel.file.hadoop; import org.apache.seatunnel.common.exception.CommonError; +import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException; import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; import org.apache.commons.lang3.StringUtils; @@ -39,6 +40,7 @@ import java.io.Closeable; import java.io.IOException; import java.io.Serializable; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.List; @@ -46,34 +48,47 @@ public class HadoopFileSystemProxy implements Serializable, Closeable { private transient UserGroupInformation userGroupInformation; - - private transient Configuration configuration; - private transient FileSystem fileSystem; + private transient Configuration configuration; private final HadoopConf hadoopConf; + private boolean isAuthTypeKerberos; public HadoopFileSystemProxy(@NonNull HadoopConf hadoopConf) { this.hadoopConf = hadoopConf; + // eager initialization + initialize(); } public boolean fileExist(@NonNull String filePath) throws IOException { - return getFileSystem().exists(new Path(filePath)); + return execute(() -> getFileSystem().exists(new Path(filePath))); + } + + public boolean isFile(@NonNull String filePath) throws IOException { + return execute(() -> getFileSystem().getFileStatus(new Path(filePath)).isFile()); } public void createFile(@NonNull String filePath) throws IOException { - if (!getFileSystem().createNewFile(new Path(filePath))) { - throw CommonError.fileOperationFailed("SeaTunnel", "create", filePath); - } + execute( + () -> { + if (!getFileSystem().createNewFile(new Path(filePath))) { + throw CommonError.fileOperationFailed("SeaTunnel", "create", filePath); + } + return Void.class; + }); } public void deleteFile(@NonNull String filePath) throws IOException { - Path path = new Path(filePath); - if (getFileSystem().exists(path)) { - if (!getFileSystem().delete(path, true)) { - throw CommonError.fileOperationFailed("SeaTunnel", "delete", filePath); - } - } + execute( + () -> { + Path path = new Path(filePath); + if (getFileSystem().exists(path)) { + if (!getFileSystem().delete(path, true)) { + throw CommonError.fileOperationFailed("SeaTunnel", "delete", filePath); + } + } + return Void.class; + }); } public void renameFile( @@ -81,89 +96,103 @@ public void renameFile( @NonNull String newFilePath, boolean removeWhenNewFilePathExist) throws IOException { - Path oldPath = new Path(oldFilePath); - Path newPath = new Path(newFilePath); - - if (!fileExist(oldPath.toString())) { - log.warn( - "rename file :[" - + oldPath - + "] to [" - + newPath - + "] already finished in the last commit, skip"); - return; - } - - if (removeWhenNewFilePathExist) { - if (fileExist(newFilePath)) { - getFileSystem().delete(newPath, true); - log.info("Delete already file: {}", newPath); - } - } - if (!fileExist(newPath.getParent().toString())) { - createDir(newPath.getParent().toString()); - } - - if (getFileSystem().rename(oldPath, newPath)) { - log.info("rename file :[" + oldPath + "] to [" + newPath + "] finish"); - } else { - throw CommonError.fileOperationFailed( - "SeaTunnel", "rename", oldFilePath + " -> " + newFilePath); - } + execute( + () -> { + Path oldPath = new Path(oldFilePath); + Path newPath = new Path(newFilePath); + + if (!fileExist(oldPath.toString())) { + log.warn( + "rename file :[" + + oldPath + + "] to [" + + newPath + + "] already finished in the last commit, skip"); + return Void.class; + } + + if (removeWhenNewFilePathExist) { + if (fileExist(newFilePath)) { + getFileSystem().delete(newPath, true); + log.info("Delete already file: {}", newPath); + } + } + if (!fileExist(newPath.getParent().toString())) { + createDir(newPath.getParent().toString()); + } + + if (getFileSystem().rename(oldPath, newPath)) { + log.info("rename file :[" + oldPath + "] to [" + newPath + "] finish"); + } else { + throw CommonError.fileOperationFailed( + "SeaTunnel", "rename", oldFilePath + " -> " + newFilePath); + } + return Void.class; + }); } public void createDir(@NonNull String filePath) throws IOException { - Path dfs = new Path(filePath); - if (!getFileSystem().mkdirs(dfs)) { - throw CommonError.fileOperationFailed("SeaTunnel", "create", filePath); - } + execute( + () -> { + Path dfs = new Path(filePath); + if (!getFileSystem().mkdirs(dfs)) { + throw CommonError.fileOperationFailed("SeaTunnel", "create", filePath); + } + return Void.class; + }); } public List listFile(String path) throws IOException { - List fileList = new ArrayList<>(); - if (!fileExist(path)) { - return fileList; - } - Path fileName = new Path(path); - RemoteIterator locatedFileStatusRemoteIterator = - getFileSystem().listFiles(fileName, false); - while (locatedFileStatusRemoteIterator.hasNext()) { - fileList.add(locatedFileStatusRemoteIterator.next()); - } - return fileList; + return execute( + () -> { + List fileList = new ArrayList<>(); + if (!fileExist(path)) { + return fileList; + } + Path fileName = new Path(path); + RemoteIterator locatedFileStatusRemoteIterator = + getFileSystem().listFiles(fileName, false); + while (locatedFileStatusRemoteIterator.hasNext()) { + fileList.add(locatedFileStatusRemoteIterator.next()); + } + return fileList; + }); } public List getAllSubFiles(@NonNull String filePath) throws IOException { - List pathList = new ArrayList<>(); - if (!fileExist(filePath)) { - return pathList; - } - Path fileName = new Path(filePath); - FileStatus[] status = getFileSystem().listStatus(fileName); - if (status != null) { - for (FileStatus fileStatus : status) { - if (fileStatus.isDirectory()) { - pathList.add(fileStatus.getPath()); - } - } - } - return pathList; + return execute( + () -> { + List pathList = new ArrayList<>(); + if (!fileExist(filePath)) { + return pathList; + } + Path fileName = new Path(filePath); + FileStatus[] status = getFileSystem().listStatus(fileName); + if (status != null) { + for (FileStatus fileStatus : status) { + if (fileStatus.isDirectory()) { + pathList.add(fileStatus.getPath()); + } + } + } + return pathList; + }); } public FileStatus[] listStatus(String filePath) throws IOException { - return getFileSystem().listStatus(new Path(filePath)); + return execute(() -> getFileSystem().listStatus(new Path(filePath))); } public FileStatus getFileStatus(String filePath) throws IOException { - return getFileSystem().getFileStatus(new Path(filePath)); + return execute(() -> getFileSystem().getFileStatus(new Path(filePath))); } public FSDataOutputStream getOutputStream(String filePath) throws IOException { - return getFileSystem().create(new Path(filePath), true); + return execute(() -> getFileSystem().create(new Path(filePath), true)); } public FSDataInputStream getInputStream(String filePath) throws IOException { - return getFileSystem().open(new Path(filePath)); + return execute(() -> getFileSystem().open(new Path(filePath))); } public FileSystem getFileSystem() { @@ -197,7 +226,7 @@ public T doWithHadoopAuth(HadoopLoginFactory.LoginFunction loginFunction) @Override public void close() throws IOException { try { - if (userGroupInformation != null && enableKerberos()) { + if (userGroupInformation != null && isAuthTypeKerberos) { userGroupInformation.logoutUserFromKeytab(); } } finally { @@ -213,14 +242,17 @@ private void initialize() { if (enableKerberos()) { configuration.set("hadoop.security.authentication", "kerberos"); initializeWithKerberosLogin(); + isAuthTypeKerberos = true; return; } if (enableRemoteUser()) { initializeWithRemoteUserLogin(); + isAuthTypeKerberos = true; return; } - this.fileSystem = FileSystem.get(configuration); - this.fileSystem.setWriteChecksum(false); + fileSystem = FileSystem.get(configuration); + fileSystem.setWriteChecksum(false); + isAuthTypeKerberos = false; } private Configuration createConfiguration() { @@ -256,10 +288,9 @@ private void initializeWithKerberosLogin() throws IOException, InterruptedExcept this.fileSystem = FileSystem.get(configuration); return Pair.of(userGroupInformation, fileSystem); }); - // todo: Use a daemon thread to reloginFromTicketCache - this.userGroupInformation = pair.getKey(); - this.fileSystem = pair.getValue(); - this.fileSystem.setWriteChecksum(false); + userGroupInformation = pair.getKey(); + fileSystem = pair.getValue(); + fileSystem.setWriteChecksum(false); log.info("Create FileSystem success with Kerberos: {}.", hadoopConf.getKerberosPrincipal()); } @@ -273,12 +304,45 @@ private void initializeWithRemoteUserLogin() throws Exception { configuration, hadoopConf.getRemoteUser(), (configuration, userGroupInformation) -> { - final FileSystem fileSystem = FileSystem.get(configuration); + this.userGroupInformation = userGroupInformation; + this.fileSystem = FileSystem.get(configuration); return Pair.of(userGroupInformation, fileSystem); }); log.info("Create FileSystem success with RemoteUser: {}.", hadoopConf.getRemoteUser()); - this.userGroupInformation = pair.getKey(); - this.fileSystem = pair.getValue(); - this.fileSystem.setWriteChecksum(false); + userGroupInformation = pair.getKey(); + fileSystem = pair.getValue(); + fileSystem.setWriteChecksum(false); + } + + private T execute(PrivilegedExceptionAction action) throws IOException { + // The execute method is used to handle privileged actions, ensuring that the correct + // user context (Kerberos or otherwise) is applied when performing file system operations. + // This is necessary to maintain security and proper access control in a Hadoop environment. + // If kerberos is disabled, the action is run directly. If kerberos is enabled, the action + // is run as a privileged action using the doAsPrivileged method. + if (isAuthTypeKerberos) { + return doAsPrivileged(action); + } else { + try { + return action.run(); + } catch (IOException | SeaTunnelRuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private T doAsPrivileged(PrivilegedExceptionAction action) throws IOException { + if (fileSystem == null || userGroupInformation == null) { + initialize(); + } + + try { + return userGroupInformation.doAs(action); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException(e); + } } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseFileSink.java index 6686da98806..af6003c79ce 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseFileSink.java @@ -26,6 +26,8 @@ import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkAggregatedCommitter; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; +import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; @@ -110,7 +112,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { protected WriteStrategy createWriteStrategy() { WriteStrategy writeStrategy = WriteStrategyFactory.of(fileSinkConfig.getFileFormat(), fileSinkConfig); - writeStrategy.setSeaTunnelRowTypeInfo(seaTunnelRowType); + writeStrategy.setCatalogTable( + CatalogTableUtil.getCatalogTable( + "file", null, null, TablePath.DEFAULT.getTableName(), seaTunnelRowType)); return writeStrategy; } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseMultipleTableFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseMultipleTableFileSink.java index a48368be448..b35c113f8da 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseMultipleTableFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/BaseMultipleTableFileSink.java @@ -112,7 +112,7 @@ public Optional> getWriterStateSerializer() { protected WriteStrategy createWriteStrategy() { WriteStrategy writeStrategy = WriteStrategyFactory.of(fileSinkConfig.getFileFormat(), fileSinkConfig); - writeStrategy.setSeaTunnelRowTypeInfo(catalogTable.getSeaTunnelRowType()); + writeStrategy.setCatalogTable(catalogTable); return writeStrategy; } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/AbstractWriteStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/AbstractWriteStrategy.java index 68476488a55..6091ee52819 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/AbstractWriteStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/AbstractWriteStrategy.java @@ -17,6 +17,9 @@ package org.apache.seatunnel.connectors.seatunnel.file.sink.writer; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -40,7 +43,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.Lists; import lombok.NonNull; import java.io.File; @@ -150,11 +152,11 @@ public Configuration getConfiguration(HadoopConf hadoopConf) { /** * set seaTunnelRowTypeInfo in writer * - * @param seaTunnelRowType seaTunnelRowType + * @param catalogTable seaTunnelRowType */ @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - this.seaTunnelRowType = seaTunnelRowType; + public void setCatalogTable(CatalogTable catalogTable) { + this.seaTunnelRowType = catalogTable.getSeaTunnelRowType(); } /** diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/BinaryWriteStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/BinaryWriteStrategy.java index 7f496b2927d..06d05d62505 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/BinaryWriteStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/BinaryWriteStrategy.java @@ -17,8 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.file.sink.writer; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.exception.CommonError; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorErrorCode; @@ -46,9 +46,9 @@ public BinaryWriteStrategy(FileSinkConfig fileSinkConfig) { } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - super.setSeaTunnelRowTypeInfo(seaTunnelRowType); - if (!seaTunnelRowType.equals(BinaryReadStrategy.binaryRowType)) { + public void setCatalogTable(CatalogTable catalogTable) { + super.setCatalogTable(catalogTable); + if (!catalogTable.getSeaTunnelRowType().equals(BinaryReadStrategy.binaryRowType)) { throw new FileConnectorException( FileConnectorErrorCode.FORMAT_NOT_SUPPORT, "BinaryWriteStrategy only supports binary format, please read file with `BINARY` format, and do not change schema in the transform."); diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/JsonWriteStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/JsonWriteStrategy.java index f95973f4cfc..23fb7893a8f 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/JsonWriteStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/JsonWriteStrategy.java @@ -18,8 +18,8 @@ package org.apache.seatunnel.connectors.seatunnel.file.sink.writer; import org.apache.seatunnel.api.serialization.SerializationSchema; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.exception.CommonError; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.common.utils.EncodingUtils; @@ -55,11 +55,13 @@ public JsonWriteStrategy(FileSinkConfig textFileSinkConfig) { } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - super.setSeaTunnelRowTypeInfo(seaTunnelRowType); + public void setCatalogTable(CatalogTable catalogTable) { + super.setCatalogTable(catalogTable); this.serializationSchema = new JsonSerializationSchema( - buildSchemaWithRowType(seaTunnelRowType, sinkColumnsIndexInRow), charset); + buildSchemaWithRowType( + catalogTable.getSeaTunnelRowType(), sinkColumnsIndexInRow), + charset); } @Override diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/TextWriteStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/TextWriteStrategy.java index 621048fb39a..77e2eb5c5b0 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/TextWriteStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/TextWriteStrategy.java @@ -18,8 +18,8 @@ package org.apache.seatunnel.connectors.seatunnel.file.sink.writer; import org.apache.seatunnel.api.serialization.SerializationSchema; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.exception.CommonError; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.common.utils.DateTimeUtils; @@ -71,12 +71,13 @@ public TextWriteStrategy(FileSinkConfig fileSinkConfig) { } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - super.setSeaTunnelRowTypeInfo(seaTunnelRowType); + public void setCatalogTable(CatalogTable catalogTable) { + super.setCatalogTable(catalogTable); this.serializationSchema = TextSerializationSchema.builder() .seaTunnelRowType( - buildSchemaWithRowType(seaTunnelRowType, sinkColumnsIndexInRow)) + buildSchemaWithRowType( + catalogTable.getSeaTunnelRowType(), sinkColumnsIndexInRow)) .delimiter(fieldDelimiter) .dateFormatter(dateFormat) .dateTimeFormatter(dateTimeFormat) diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/WriteStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/WriteStrategy.java index 6a1b1840b4d..24b23c9bfc3 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/WriteStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sink/writer/WriteStrategy.java @@ -17,8 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.file.sink.writer; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorException; import org.apache.seatunnel.connectors.seatunnel.file.hadoop.HadoopFileSystemProxy; @@ -56,11 +56,11 @@ public interface WriteStrategy extends Transaction, Serializable, Closeable { void write(SeaTunnelRow seaTunnelRow) throws FileConnectorException; /** - * set seaTunnelRowTypeInfo in writer + * set catalog table to write strategy * - * @param seaTunnelRowType seaTunnelRowType + * @param catalogTable catalogTable */ - void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType); + void setCatalogTable(CatalogTable catalogTable); /** * use seaTunnelRow generate partition directory diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/AbstractReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/AbstractReadStrategy.java index 3e71a3b2932..a1a99d32cac 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/AbstractReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/AbstractReadStrategy.java @@ -20,6 +20,7 @@ import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.BasicType; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; @@ -92,10 +93,10 @@ public void init(HadoopConf conf) { } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - this.seaTunnelRowType = seaTunnelRowType; + public void setCatalogTable(CatalogTable catalogTable) { + this.seaTunnelRowType = catalogTable.getSeaTunnelRowType(); this.seaTunnelRowTypeWithPartition = - mergePartitionTypes(fileNames.get(0), seaTunnelRowType); + mergePartitionTypes(fileNames.get(0), catalogTable.getSeaTunnelRowType()); } boolean checkFileType(String path) { @@ -237,6 +238,11 @@ protected void resolveArchiveCompressedInputStream( } } break; + case GZ: + GzipCompressorInputStream gzipIn = + new GzipCompressorInputStream(hadoopFileSystemProxy.getInputStream(path)); + readProcess(path, tableId, output, copyInputStream(gzipIn), partitionsMap, path); + break; case NONE: readProcess( path, diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/BinaryReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/BinaryReadStrategy.java index 3bbb90c774b..7849415b32d 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/BinaryReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/BinaryReadStrategy.java @@ -55,7 +55,7 @@ public void read(String path, String tableId, Collector output) throws IOException, FileConnectorException { try (InputStream inputStream = hadoopFileSystemProxy.getInputStream(path)) { String relativePath; - if (basePath.isFile()) { + if (hadoopFileSystemProxy.isFile(basePath.getAbsolutePath())) { relativePath = basePath.getName(); } else { relativePath = diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java index b794879ee70..b3d789be57e 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java @@ -21,6 +21,7 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -36,7 +37,6 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; -import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.DateUtil; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; @@ -146,15 +146,15 @@ protected void readProcess( } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - if (isNullOrEmpty(seaTunnelRowType.getFieldNames()) - || isNullOrEmpty(seaTunnelRowType.getFieldTypes())) { + public void setCatalogTable(CatalogTable catalogTable) { + SeaTunnelRowType rowType = catalogTable.getSeaTunnelRowType(); + if (isNullOrEmpty(rowType.getFieldNames()) || isNullOrEmpty(rowType.getFieldTypes())) { throw new FileConnectorException( CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION, - "Schmea information is not set or incorrect schmea settings"); + "Schema information is not set or incorrect Schema settings"); } SeaTunnelRowType userDefinedRowTypeWithPartition = - mergePartitionTypes(fileNames.get(0), seaTunnelRowType); + mergePartitionTypes(fileNames.get(0), rowType); // column projection if (pluginConfig.hasPath(BaseSourceConfigOptions.READ_COLUMNS.key())) { // get the read column index from user-defined row type @@ -162,15 +162,15 @@ public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { String[] fields = new String[readColumns.size()]; SeaTunnelDataType[] types = new SeaTunnelDataType[readColumns.size()]; for (int i = 0; i < indexes.length; i++) { - indexes[i] = seaTunnelRowType.indexOf(readColumns.get(i)); - fields[i] = seaTunnelRowType.getFieldName(indexes[i]); - types[i] = seaTunnelRowType.getFieldType(indexes[i]); + indexes[i] = rowType.indexOf(readColumns.get(i)); + fields[i] = rowType.getFieldName(indexes[i]); + types[i] = rowType.getFieldType(indexes[i]); } this.seaTunnelRowType = new SeaTunnelRowType(fields, types); this.seaTunnelRowTypeWithPartition = mergePartitionTypes(fileNames.get(0), this.seaTunnelRowType); } else { - this.seaTunnelRowType = seaTunnelRowType; + this.seaTunnelRowType = rowType; this.seaTunnelRowTypeWithPartition = userDefinedRowTypeWithPartition; } } @@ -190,10 +190,11 @@ private Object getCellValue(CellType cellType, Cell cell) { return cell.getBooleanCellValue(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { - DataFormatter formatter = new DataFormatter(); - return formatter.formatCellValue(cell); + return cell.getLocalDateTimeCellValue(); } return cell.getNumericCellValue(); + case BLANK: + return ""; case ERROR: break; default: @@ -207,15 +208,26 @@ private Object getCellValue(CellType cellType, Cell cell) { @SneakyThrows private Object convert(Object field, SeaTunnelDataType fieldType) { if (field == null) { - return ""; + return null; } + SqlType sqlType = fieldType.getSqlType(); + if (!(SqlType.STRING.equals(sqlType)) && "".equals(field)) { + return null; + } switch (sqlType) { case MAP: case ARRAY: return objectMapper.readValue((String) field, fieldType.getTypeClass()); case STRING: - return field; + if (field instanceof Double) { + String stringValue = field.toString(); + if (stringValue.endsWith(".0")) { + return stringValue.substring(0, stringValue.length() - 2); + } + return stringValue; + } + return String.valueOf(field); case DOUBLE: return Double.parseDouble(field.toString()); case BOOLEAN: @@ -233,16 +245,25 @@ private Object convert(Object field, SeaTunnelDataType fieldType) { case DECIMAL: return BigDecimal.valueOf(Double.parseDouble(field.toString())); case DATE: + if (field instanceof LocalDateTime) { + return ((LocalDateTime) field).toLocalDate(); + } return LocalDate.parse( (String) field, DateTimeFormatter.ofPattern(dateFormat.getValue())); case TIME: + if (field instanceof LocalDateTime) { + return ((LocalDateTime) field).toLocalTime(); + } return LocalTime.parse( (String) field, DateTimeFormatter.ofPattern(timeFormat.getValue())); case TIMESTAMP: + if (field instanceof LocalDateTime) { + return field; + } return LocalDateTime.parse( (String) field, DateTimeFormatter.ofPattern(datetimeFormat.getValue())); case NULL: - return ""; + return null; case BYTES: return field.toString().getBytes(StandardCharsets.UTF_8); case ROW: diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/JsonReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/JsonReadStrategy.java index 982419266f5..dfd57363d9d 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/JsonReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/JsonReadStrategy.java @@ -20,6 +20,7 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.serialization.DeserializationSchema; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; @@ -62,8 +63,8 @@ public void init(HadoopConf conf) { } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - super.setSeaTunnelRowTypeInfo(seaTunnelRowType); + public void setCatalogTable(CatalogTable catalogTable) { + super.setCatalogTable(catalogTable); if (isMergePartition) { deserializationSchema = new JsonDeserializationSchema(false, false, this.seaTunnelRowTypeWithPartition); diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ReadStrategy.java index c5bdf281244..9389223814a 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ReadStrategy.java @@ -20,6 +20,7 @@ import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -56,8 +57,7 @@ default SeaTunnelRowType getSeaTunnelRowTypeInfoWithUserConfigRowType( return getSeaTunnelRowTypeInfo(path); } - // todo: use CatalogTable - void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType); + void setCatalogTable(CatalogTable catalogTable); List getFileNamesByPath(String path) throws IOException; diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/TextReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/TextReadStrategy.java index 2b722593770..b13aad765a5 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/TextReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/TextReadStrategy.java @@ -21,6 +21,7 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.serialization.DeserializationSchema; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; @@ -156,10 +157,15 @@ public SeaTunnelRowType getSeaTunnelRowTypeInfo(String path) { "When reading json/text/csv files, if user has not specified schema information, " + "SeaTunnel will not support column projection"); } + ReadonlyConfig readonlyConfig = ReadonlyConfig.fromConfig(pluginConfig); TextDeserializationSchema.Builder builder = TextDeserializationSchema.builder() .delimiter(TextFormatConstant.PLACEHOLDER) - .textLineSplitor(textLineSplitor); + .textLineSplitor(textLineSplitor) + .nullFormat( + readonlyConfig + .getOptional(BaseSourceConfigOptions.NULL_FORMAT) + .orElse(null)); if (isMergePartition) { deserializationSchema = builder.seaTunnelRowType(this.seaTunnelRowTypeWithPartition).build(); @@ -170,14 +176,15 @@ public SeaTunnelRowType getSeaTunnelRowTypeInfo(String path) { } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { + public void setCatalogTable(CatalogTable catalogTable) { + SeaTunnelRowType rowType = catalogTable.getSeaTunnelRowType(); SeaTunnelRowType userDefinedRowTypeWithPartition = - mergePartitionTypes(fileNames.get(0), seaTunnelRowType); + mergePartitionTypes(fileNames.get(0), rowType); + ReadonlyConfig readonlyConfig = ReadonlyConfig.fromConfig(pluginConfig); Optional fieldDelimiterOptional = - ReadonlyConfig.fromConfig(pluginConfig) - .getOptional(BaseSourceConfigOptions.FIELD_DELIMITER); + readonlyConfig.getOptional(BaseSourceConfigOptions.FIELD_DELIMITER); encoding = - ReadonlyConfig.fromConfig(pluginConfig) + readonlyConfig .getOptional(BaseSourceConfigOptions.ENCODING) .orElse(StandardCharsets.UTF_8.name()); if (fieldDelimiterOptional.isPresent()) { @@ -196,12 +203,16 @@ public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { TextDeserializationSchema.Builder builder = TextDeserializationSchema.builder() .delimiter(fieldDelimiter) - .textLineSplitor(textLineSplitor); + .textLineSplitor(textLineSplitor) + .nullFormat( + readonlyConfig + .getOptional(BaseSourceConfigOptions.NULL_FORMAT) + .orElse(null)); if (isMergePartition) { deserializationSchema = builder.seaTunnelRowType(userDefinedRowTypeWithPartition).build(); } else { - deserializationSchema = builder.seaTunnelRowType(seaTunnelRowType).build(); + deserializationSchema = builder.seaTunnelRowType(rowType).build(); } // column projection if (pluginConfig.hasPath(BaseSourceConfigOptions.READ_COLUMNS.key())) { @@ -210,15 +221,15 @@ public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { String[] fields = new String[readColumns.size()]; SeaTunnelDataType[] types = new SeaTunnelDataType[readColumns.size()]; for (int i = 0; i < indexes.length; i++) { - indexes[i] = seaTunnelRowType.indexOf(readColumns.get(i)); - fields[i] = seaTunnelRowType.getFieldName(indexes[i]); - types[i] = seaTunnelRowType.getFieldType(indexes[i]); + indexes[i] = rowType.indexOf(readColumns.get(i)); + fields[i] = rowType.getFieldName(indexes[i]); + types[i] = rowType.getFieldType(indexes[i]); } this.seaTunnelRowType = new SeaTunnelRowType(fields, types); this.seaTunnelRowTypeWithPartition = mergePartitionTypes(fileNames.get(0), this.seaTunnelRowType); } else { - this.seaTunnelRowType = seaTunnelRowType; + this.seaTunnelRowType = rowType; this.seaTunnelRowTypeWithPartition = userDefinedRowTypeWithPartition; } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/XmlReadStrategy.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/XmlReadStrategy.java index a553a4f9d06..e012c46bdf5 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/XmlReadStrategy.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/XmlReadStrategy.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -173,20 +174,20 @@ public SeaTunnelRowType getSeaTunnelRowTypeInfo(String path) throws FileConnecto } @Override - public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { - if (ArrayUtils.isEmpty(seaTunnelRowType.getFieldNames()) - || ArrayUtils.isEmpty(seaTunnelRowType.getFieldTypes())) { + public void setCatalogTable(CatalogTable catalogTable) { + SeaTunnelRowType rowType = catalogTable.getSeaTunnelRowType(); + if (ArrayUtils.isEmpty(rowType.getFieldNames()) + || ArrayUtils.isEmpty(rowType.getFieldTypes())) { throw new FileConnectorException( CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, "Schema information is undefined or misconfigured, please check your configuration file."); } if (readColumns.isEmpty()) { - this.seaTunnelRowType = seaTunnelRowType; - this.seaTunnelRowTypeWithPartition = - mergePartitionTypes(fileNames.get(0), seaTunnelRowType); + this.seaTunnelRowType = rowType; + this.seaTunnelRowTypeWithPartition = mergePartitionTypes(fileNames.get(0), rowType); } else { - if (readColumns.retainAll(Arrays.asList(seaTunnelRowType.getFieldNames()))) { + if (readColumns.retainAll(Arrays.asList(rowType.getFieldNames()))) { log.warn( "The read columns configuration will be filtered by the schema configuration, this may cause the actual results to be inconsistent with expectations. This is due to read columns not being a subset of the schema, " + "maybe you should check the schema and read_columns!"); @@ -195,9 +196,9 @@ public void setSeaTunnelRowTypeInfo(SeaTunnelRowType seaTunnelRowType) { String[] fields = new String[readColumns.size()]; SeaTunnelDataType[] types = new SeaTunnelDataType[readColumns.size()]; for (int i = 0; i < readColumns.size(); i++) { - indexes[i] = seaTunnelRowType.indexOf(readColumns.get(i)); - fields[i] = seaTunnelRowType.getFieldName(indexes[i]); - types[i] = seaTunnelRowType.getFieldType(indexes[i]); + indexes[i] = rowType.indexOf(readColumns.get(i)); + fields[i] = rowType.getFieldName(indexes[i]); + types[i] = rowType.getFieldType(indexes[i]); } this.seaTunnelRowType = new SeaTunnelRowType(fields, types); this.seaTunnelRowTypeWithPartition = diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ExcelReadStrategyTest.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ExcelReadStrategyTest.java new file mode 100644 index 00000000000..6edc55d56cd --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ExcelReadStrategyTest.java @@ -0,0 +1,263 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.writer; + +import org.apache.seatunnel.shade.com.typesafe.config.Config; +import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; + +import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.common.utils.DateTimeUtils; +import org.apache.seatunnel.common.utils.DateUtils; +import org.apache.seatunnel.common.utils.TimeUtils; +import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; +import org.apache.seatunnel.connectors.seatunnel.file.source.reader.ExcelReadStrategy; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import lombok.Getter; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.FS_DEFAULT_NAME_DEFAULT; + +public class ExcelReadStrategyTest { + + @Test + public void testExcelRead() throws IOException, URISyntaxException { + URL excelFile = ExcelReadStrategyTest.class.getResource("/excel/test_read_excel.xlsx"); + URL conf = ExcelReadStrategyTest.class.getResource("/excel/test_read_excel.conf"); + Assertions.assertNotNull(excelFile); + Assertions.assertNotNull(conf); + String excelFilePath = Paths.get(excelFile.toURI()).toString(); + String confPath = Paths.get(conf.toURI()).toString(); + Config pluginConfig = ConfigFactory.parseFile(new File(confPath)); + ExcelReadStrategy excelReadStrategy = new ExcelReadStrategy(); + LocalConf localConf = new LocalConf(FS_DEFAULT_NAME_DEFAULT); + excelReadStrategy.setPluginConfig(pluginConfig); + excelReadStrategy.init(localConf); + + List fileNamesByPath = excelReadStrategy.getFileNamesByPath(excelFilePath); + CatalogTable userDefinedCatalogTable = CatalogTableUtil.buildWithConfig(pluginConfig); + excelReadStrategy.setCatalogTable(userDefinedCatalogTable); + TestCollector testCollector = new TestCollector(); + excelReadStrategy.read(fileNamesByPath.get(0), "", testCollector); + + SeaTunnelRow seaTunnelRow = testCollector.getRows().get(0); + + Assertions.assertEquals(seaTunnelRow.getArity(), 14); + Assertions.assertEquals(seaTunnelRow.getField(0).getClass(), Byte.class); + Assertions.assertEquals(seaTunnelRow.getField(1).getClass(), Short.class); + Assertions.assertEquals(seaTunnelRow.getField(2).getClass(), Integer.class); + Assertions.assertEquals(seaTunnelRow.getField(3).getClass(), Long.class); + Assertions.assertEquals(seaTunnelRow.getField(4).getClass(), String.class); + Assertions.assertEquals(seaTunnelRow.getField(5).getClass(), Double.class); + Assertions.assertEquals(seaTunnelRow.getField(6).getClass(), Float.class); + Assertions.assertEquals(seaTunnelRow.getField(7).getClass(), BigDecimal.class); + Assertions.assertEquals(seaTunnelRow.getField(8).getClass(), Boolean.class); + Assertions.assertEquals(seaTunnelRow.getField(9).getClass(), LinkedHashMap.class); + Assertions.assertEquals(seaTunnelRow.getField(10).getClass(), String[].class); + Assertions.assertEquals(seaTunnelRow.getField(11).getClass(), LocalDate.class); + Assertions.assertEquals(seaTunnelRow.getField(12).getClass(), LocalDateTime.class); + Assertions.assertEquals(seaTunnelRow.getField(13).getClass(), LocalTime.class); + + Assertions.assertEquals(seaTunnelRow.getField(0), (byte) 1); + Assertions.assertEquals(seaTunnelRow.getField(1), (short) 22); + Assertions.assertEquals(seaTunnelRow.getField(2), 333); + Assertions.assertEquals(seaTunnelRow.getField(3), 4444L); + Assertions.assertEquals(seaTunnelRow.getField(4), "Cosmos"); + Assertions.assertEquals(seaTunnelRow.getField(5), 5.555); + Assertions.assertEquals(seaTunnelRow.getField(6), (float) 6.666); + Assertions.assertEquals(seaTunnelRow.getField(7), new BigDecimal("7.78")); + Assertions.assertEquals(seaTunnelRow.getField(8), Boolean.FALSE); + Assertions.assertEquals( + seaTunnelRow.getField(9), + new LinkedHashMap() { + { + put("name", "Ivan"); + put("age", "26"); + } + }); + Assertions.assertArrayEquals( + (String[]) seaTunnelRow.getField(10), new String[] {"Ivan", "Dusayi"}); + Assertions.assertEquals( + seaTunnelRow.getField(11), + DateUtils.parse("2024-01-31", DateUtils.Formatter.YYYY_MM_DD)); + Assertions.assertEquals( + seaTunnelRow.getField(12), + DateTimeUtils.parse( + "2024-01-31 16:00:48", DateTimeUtils.Formatter.YYYY_MM_DD_HH_MM_SS)); + Assertions.assertEquals( + seaTunnelRow.getField(13), + TimeUtils.parse("16:00:48", TimeUtils.Formatter.HH_MM_SS)); + + SeaTunnelRow row2 = testCollector.getRows().get(1); + Assertions.assertEquals(row2.getArity(), 14); + // check number blank + Assertions.assertEquals(row2.getField(0).getClass(), Byte.class); + Assertions.assertNull(row2.getField(1)); + Assertions.assertNull(row2.getField(2)); + Assertions.assertNull(row2.getField(3)); + Assertions.assertEquals(row2.getField(4), "1"); + Assertions.assertNull(row2.getField(5)); + Assertions.assertNull(row2.getField(6)); + Assertions.assertNull(row2.getField(7)); + Assertions.assertNull(row2.getField(8)); + Assertions.assertNull(row2.getField(9)); + Assertions.assertNull(row2.getField(10)); + Assertions.assertNull(row2.getField(11)); + Assertions.assertNull(row2.getField(12)); + Assertions.assertNull(row2.getField(13)); + + SeaTunnelRow row3 = testCollector.getRows().get(2); + Assertions.assertEquals(row3.getArity(), 14); + Assertions.assertEquals(row3.getField(0).getClass(), Byte.class); + Assertions.assertNull(row3.getField(1)); + Assertions.assertNull(row3.getField(2)); + Assertions.assertNull(row3.getField(3)); + // check string blank + Assertions.assertEquals(row3.getField(4), ""); + Assertions.assertNull(row3.getField(5)); + Assertions.assertNull(row3.getField(6)); + Assertions.assertNull(row3.getField(7)); + Assertions.assertNull(row3.getField(8)); + Assertions.assertNull(row3.getField(9)); + Assertions.assertNull(row3.getField(10)); + Assertions.assertNull(row3.getField(11)); + Assertions.assertNull(row3.getField(12)); + Assertions.assertNull(row3.getField(13)); + } + + @Test + public void testExcelReadDateString() throws IOException, URISyntaxException { + URL excelFile = + ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx"); + URL conf = ExcelReadStrategyTest.class.getResource("/excel/test_read_excel.conf"); + Assertions.assertNotNull(excelFile); + Assertions.assertNotNull(conf); + String excelFilePath = Paths.get(excelFile.toURI()).toString(); + String confPath = Paths.get(conf.toURI()).toString(); + Config pluginConfig = ConfigFactory.parseFile(new File(confPath)); + ExcelReadStrategy excelReadStrategy = new ExcelReadStrategy(); + LocalConf localConf = new LocalConf(FS_DEFAULT_NAME_DEFAULT); + excelReadStrategy.setPluginConfig(pluginConfig); + excelReadStrategy.init(localConf); + + List fileNamesByPath = excelReadStrategy.getFileNamesByPath(excelFilePath); + CatalogTable userDefinedCatalogTable = CatalogTableUtil.buildWithConfig(pluginConfig); + excelReadStrategy.setCatalogTable(userDefinedCatalogTable); + TestCollector testCollector = new TestCollector(); + excelReadStrategy.read(fileNamesByPath.get(0), "", testCollector); + + for (SeaTunnelRow seaTunnelRow : testCollector.getRows()) { + Assertions.assertEquals(seaTunnelRow.getArity(), 14); + Assertions.assertEquals(seaTunnelRow.getField(0).getClass(), Byte.class); + Assertions.assertEquals(seaTunnelRow.getField(1).getClass(), Short.class); + Assertions.assertEquals(seaTunnelRow.getField(2).getClass(), Integer.class); + Assertions.assertEquals(seaTunnelRow.getField(3).getClass(), Long.class); + Assertions.assertEquals(seaTunnelRow.getField(4).getClass(), String.class); + Assertions.assertEquals(seaTunnelRow.getField(5).getClass(), Double.class); + Assertions.assertEquals(seaTunnelRow.getField(6).getClass(), Float.class); + Assertions.assertEquals(seaTunnelRow.getField(7).getClass(), BigDecimal.class); + Assertions.assertEquals(seaTunnelRow.getField(8).getClass(), Boolean.class); + Assertions.assertEquals(seaTunnelRow.getField(9).getClass(), LinkedHashMap.class); + Assertions.assertEquals(seaTunnelRow.getField(10).getClass(), String[].class); + Assertions.assertEquals(seaTunnelRow.getField(11).getClass(), LocalDate.class); + Assertions.assertEquals(seaTunnelRow.getField(12).getClass(), LocalDateTime.class); + Assertions.assertEquals(seaTunnelRow.getField(13).getClass(), LocalTime.class); + + Assertions.assertEquals(seaTunnelRow.getField(0), (byte) 1); + Assertions.assertEquals(seaTunnelRow.getField(1), (short) 22); + Assertions.assertEquals(seaTunnelRow.getField(2), 333); + Assertions.assertEquals(seaTunnelRow.getField(3), 4444L); + Assertions.assertEquals(seaTunnelRow.getField(4), "Cosmos"); + Assertions.assertEquals(seaTunnelRow.getField(5), 5.555); + Assertions.assertEquals(seaTunnelRow.getField(6), (float) 6.666); + Assertions.assertEquals(seaTunnelRow.getField(7), new BigDecimal("7.78")); + Assertions.assertEquals(seaTunnelRow.getField(8), Boolean.FALSE); + Assertions.assertEquals( + seaTunnelRow.getField(9), + new LinkedHashMap() { + { + put("name", "Ivan"); + put("age", "26"); + } + }); + Assertions.assertArrayEquals( + (String[]) seaTunnelRow.getField(10), new String[] {"Ivan", "Dusayi"}); + Assertions.assertEquals( + seaTunnelRow.getField(11), + DateUtils.parse("2024-01-31", DateUtils.Formatter.YYYY_MM_DD)); + Assertions.assertEquals( + seaTunnelRow.getField(12), + DateTimeUtils.parse( + "2024-01-31 16:00:48", DateTimeUtils.Formatter.YYYY_MM_DD_HH_MM_SS)); + Assertions.assertEquals( + seaTunnelRow.getField(13), + TimeUtils.parse("16:00:48", TimeUtils.Formatter.HH_MM_SS)); + } + } + + @Getter + public static class TestCollector implements Collector { + private final List rows = new ArrayList<>(); + + @Override + public void collect(SeaTunnelRow record) { + rows.add(record); + } + + @Override + public Object getCheckpointLock() { + return null; + } + } + + public static class LocalConf extends HadoopConf { + private static final String HDFS_IMPL = "org.apache.hadoop.fs.LocalFileSystem"; + private static final String SCHEMA = "file"; + + public LocalConf(String hdfsNameKey) { + super(hdfsNameKey); + } + + @Override + public String getFsHdfsImpl() { + return HDFS_IMPL; + } + + @Override + public String getSchema() { + return SCHEMA; + } + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java index 236d6f5a037..e692d7294b7 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ParquetWriteStrategyTest.java @@ -20,6 +20,7 @@ import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.type.BasicType; import org.apache.seatunnel.api.table.type.LocalTimeType; import org.apache.seatunnel.api.table.type.PrimitiveByteArrayType; @@ -82,7 +83,8 @@ public void testParquetWriteInt96() throws Exception { ParquetWriteStrategy writeStrategy = new ParquetWriteStrategy(writeSinkConfig); ParquetReadStrategyTest.LocalConf hadoopConf = new ParquetReadStrategyTest.LocalConf(FS_DEFAULT_NAME_DEFAULT); - writeStrategy.setSeaTunnelRowTypeInfo(writeRowType); + writeStrategy.setCatalogTable( + CatalogTableUtil.getCatalogTable("test", null, null, "test", writeRowType)); writeStrategy.init(hadoopConf, "test1", "test1", 0); writeStrategy.beginTransaction(1L); writeStrategy.write( diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ReadStrategyEncodingTest.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ReadStrategyEncodingTest.java index 736ae590963..ad23dd0186f 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ReadStrategyEncodingTest.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/ReadStrategyEncodingTest.java @@ -21,9 +21,9 @@ import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; import org.apache.seatunnel.connectors.seatunnel.file.source.reader.AbstractReadStrategy; import org.apache.seatunnel.connectors.seatunnel.file.source.reader.JsonReadStrategy; @@ -121,11 +121,10 @@ private static void testRead( readStrategy.init(localConf); readStrategy.getFileNamesByPath(sourceFilePath); testCollector = new TestCollector(); - SeaTunnelRowType seaTunnelRowTypeInfo = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - Assertions.assertNotNull(seaTunnelRowTypeInfo); - readStrategy.setSeaTunnelRowTypeInfo(seaTunnelRowTypeInfo); - log.info(seaTunnelRowTypeInfo.toString()); + CatalogTable catalogTable = CatalogTableUtil.buildWithConfig(pluginConfig); + Assertions.assertNotNull(catalogTable.getSeaTunnelRowType()); + readStrategy.setCatalogTable(catalogTable); + log.info(catalogTable.getSeaTunnelRowType().toString()); readStrategy.read(sourceFilePath, "", testCollector); assertRows(testCollector); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/XmlReadStrategyTest.java b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/XmlReadStrategyTest.java index 8bb2e483896..fca8f68fd2c 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/XmlReadStrategyTest.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/writer/XmlReadStrategyTest.java @@ -21,9 +21,9 @@ import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory; import org.apache.seatunnel.api.source.Collector; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.utils.DateTimeUtils; import org.apache.seatunnel.common.utils.DateUtils; import org.apache.seatunnel.common.utils.TimeUtils; @@ -66,9 +66,8 @@ public void testXmlRead() throws IOException, URISyntaxException { xmlReadStrategy.setPluginConfig(pluginConfig); xmlReadStrategy.init(localConf); List fileNamesByPath = xmlReadStrategy.getFileNamesByPath(xmlFilePath); - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - xmlReadStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); + CatalogTable catalogTable = CatalogTableUtil.buildWithConfig(pluginConfig); + xmlReadStrategy.setCatalogTable(catalogTable); TestCollector testCollector = new TestCollector(); xmlReadStrategy.read(fileNamesByPath.get(0), "", testCollector); for (SeaTunnelRow seaTunnelRow : testCollector.getRows()) { diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel.conf b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel.conf new file mode 100644 index 00000000000..295640031cc --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel.conf @@ -0,0 +1,39 @@ +# +# 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. + + +{ + sheet_name = "Sheet1" + skip_header_row_number = 1 + schema = { + fields { + c_bytes = "tinyint" + c_short = "smallint" + c_int = "int" + c_bigint = "bigint" + c_string = "string" + c_double = "double" + c_float = "float" + c_decimal = "decimal(10, 2)" + c_boolean = "boolean" + c_map = "map" + c_array = "array" + c_date = "date" + c_datetime = "timestamp" + c_time = "time" + } + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel.xlsx b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel.xlsx new file mode 100644 index 00000000000..87f72cfb28d Binary files /dev/null and b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel.xlsx differ diff --git a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel_date_string.xlsx b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel_date_string.xlsx new file mode 100644 index 00000000000..4314f1587b2 Binary files /dev/null and b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/resources/excel/test_read_excel_date_string.xlsx differ diff --git a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/sink/CosFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/sink/CosFileSink.java index 9a1885da4fb..8783c268baf 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/sink/CosFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/sink/CosFileSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -33,6 +34,8 @@ import com.google.auto.service.AutoService; +import java.util.Optional; + @AutoService(SeaTunnelSink.class) public class CosFileSink extends BaseFileSink { @Override @@ -60,4 +63,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { } hadoopConf = CosConf.buildWithConfig(pluginConfig); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSource.java b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSource.java index 0690b2acebb..bd8df0261cb 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSource.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSource.java @@ -22,9 +22,9 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -95,9 +95,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { case JSON: case EXCEL: case XML: - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - readStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); + CatalogTable userDefinedCatalogTable = + CatalogTableUtil.buildWithConfig(pluginConfig); + readStrategy.setCatalogTable(userDefinedCatalogTable); rowType = readStrategy.getActualSeaTunnelRowTypeInfo(); break; case ORC: diff --git a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java index 388d245047b..ab519d299da 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java @@ -72,6 +72,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.COMPRESS_CODEC) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FTPFileSourceConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FTPFileSourceConfig.java new file mode 100644 index 00000000000..8677ed29d4e --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FTPFileSourceConfig.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.ftp.config; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseFileSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; +import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; + +import lombok.Getter; + +@Getter +public class FTPFileSourceConfig extends BaseFileSourceConfig { + + private static final long serialVersionUID = 1L; + + @Override + public HadoopConf getHadoopConfig() { + return FtpConf.buildWithConfig(getBaseFileSourceConfig()); + } + + @Override + public String getPluginName() { + return FileSystemType.FTP.getFileSystemPluginName(); + } + + public FTPFileSourceConfig(ReadonlyConfig readonlyConfig) { + super(readonlyConfig); + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConfigOptions.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConfigOptions.java index 1f00a56abfd..645225b9eac 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConfigOptions.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/FtpConfigOptions.java @@ -22,7 +22,7 @@ import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions; import org.apache.seatunnel.connectors.seatunnel.file.ftp.system.FtpConnectionMode; -import static org.apache.seatunnel.connectors.seatunnel.file.ftp.system.FtpConnectionMode.ACTIVE_LOCAL_DATA_CONNECTION_MODE; +import static org.apache.seatunnel.connectors.seatunnel.file.ftp.system.FtpConnectionMode.ACTIVE_LOCAL; public class FtpConfigOptions extends BaseSourceConfigOptions { public static final Option FTP_PASSWORD = @@ -42,6 +42,6 @@ public class FtpConfigOptions extends BaseSourceConfigOptions { public static final Option FTP_CONNECTION_MODE = Options.key("connection_mode") .enumType(FtpConnectionMode.class) - .defaultValue(ACTIVE_LOCAL_DATA_CONNECTION_MODE) + .defaultValue(ACTIVE_LOCAL) .withDescription("FTP server connection mode "); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/MultipleTableFTPFileSourceConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/MultipleTableFTPFileSourceConfig.java new file mode 100644 index 00000000000..78a04c648ea --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/config/MultipleTableFTPFileSourceConfig.java @@ -0,0 +1,34 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.ftp.config; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseFileSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseMultipleTableFileSourceConfig; + +public class MultipleTableFTPFileSourceConfig extends BaseMultipleTableFileSourceConfig { + + public MultipleTableFTPFileSourceConfig(ReadonlyConfig ossFileSourceRootConfig) { + super(ossFileSourceRootConfig); + } + + @Override + public BaseFileSourceConfig getBaseSourceConfig(ReadonlyConfig readonlyConfig) { + return new FTPFileSourceConfig(readonlyConfig); + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSink.java index f4b271e0356..ac481be25b4 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/sink/FtpFileSink.java @@ -23,7 +23,12 @@ import org.apache.seatunnel.connectors.seatunnel.file.ftp.config.FtpConf; import org.apache.seatunnel.connectors.seatunnel.file.sink.BaseMultipleTableFileSink; +import java.util.Optional; + public class FtpFileSink extends BaseMultipleTableFileSink { + + private final CatalogTable catalogTable; + @Override public String getPluginName() { return FileSystemType.FTP.getFileSystemPluginName(); @@ -31,5 +36,11 @@ public String getPluginName() { public FtpFileSink(ReadonlyConfig readonlyConfig, CatalogTable catalogTable) { super(FtpConf.buildWithConfig(readonlyConfig), readonlyConfig, catalogTable); + this.catalogTable = catalogTable; + } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSource.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSource.java index d6f0f64abb6..b8e798ba0ad 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSource.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSource.java @@ -17,121 +17,18 @@ package org.apache.seatunnel.connectors.seatunnel.file.ftp.source; -import org.apache.seatunnel.shade.com.typesafe.config.Config; - -import org.apache.seatunnel.api.common.PrepareFailException; -import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.configuration.ReadonlyConfig; -import org.apache.seatunnel.api.source.SeaTunnelSource; -import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; -import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.common.config.CheckConfigUtil; -import org.apache.seatunnel.common.config.CheckResult; -import org.apache.seatunnel.common.constants.PluginType; -import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; -import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; -import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorErrorCode; -import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorException; -import org.apache.seatunnel.connectors.seatunnel.file.ftp.config.FtpConf; -import org.apache.seatunnel.connectors.seatunnel.file.ftp.config.FtpConfigOptions; -import org.apache.seatunnel.connectors.seatunnel.file.source.BaseFileSource; -import org.apache.seatunnel.connectors.seatunnel.file.source.reader.ReadStrategyFactory; - -import com.google.auto.service.AutoService; +import org.apache.seatunnel.connectors.seatunnel.file.ftp.config.MultipleTableFTPFileSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.file.source.BaseMultipleTableFileSource; -import java.io.IOException; +public class FtpFileSource extends BaseMultipleTableFileSource { + public FtpFileSource(ReadonlyConfig readonlyConfig) { + super(new MultipleTableFTPFileSourceConfig(readonlyConfig)); + } -@AutoService(SeaTunnelSource.class) -public class FtpFileSource extends BaseFileSource { @Override public String getPluginName() { return FileSystemType.FTP.getFileSystemPluginName(); } - - @Override - public void prepare(Config pluginConfig) throws PrepareFailException { - CheckResult result = - CheckConfigUtil.checkAllExists( - pluginConfig, - FtpConfigOptions.FILE_PATH.key(), - FtpConfigOptions.FILE_FORMAT_TYPE.key(), - FtpConfigOptions.FTP_HOST.key(), - FtpConfigOptions.FTP_PORT.key(), - FtpConfigOptions.FTP_USERNAME.key(), - FtpConfigOptions.FTP_PASSWORD.key()); - if (!result.isSuccess()) { - throw new FileConnectorException( - SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, - String.format( - "PluginName: %s, PluginType: %s, Message: %s", - getPluginName(), PluginType.SOURCE, result.getMsg())); - } - FileFormat fileFormat = - FileFormat.valueOf( - pluginConfig - .getString(FtpConfigOptions.FILE_FORMAT_TYPE.key()) - .toUpperCase()); - if (fileFormat == FileFormat.ORC || fileFormat == FileFormat.PARQUET) { - throw new FileConnectorException( - CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, - "Ftp file source connector only support read [text, csv, json] files"); - } - String path = pluginConfig.getString(FtpConfigOptions.FILE_PATH.key()); - hadoopConf = FtpConf.buildWithConfig(ReadonlyConfig.fromConfig(pluginConfig)); - readStrategy = - ReadStrategyFactory.of( - pluginConfig.getString(FtpConfigOptions.FILE_FORMAT_TYPE.key())); - readStrategy.setPluginConfig(pluginConfig); - readStrategy.init(hadoopConf); - try { - filePaths = readStrategy.getFileNamesByPath(path); - } catch (IOException e) { - String errorMsg = String.format("Get file list from this path [%s] failed", path); - throw new FileConnectorException( - FileConnectorErrorCode.FILE_LIST_GET_FAILED, errorMsg, e); - } - // support user-defined schema - // only json type support user-defined schema now - if (pluginConfig.hasPath(TableSchemaOptions.SCHEMA.key())) { - switch (fileFormat) { - case CSV: - case TEXT: - case JSON: - case EXCEL: - case XML: - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - readStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); - rowType = readStrategy.getActualSeaTunnelRowTypeInfo(); - break; - case ORC: - case PARQUET: - case BINARY: - throw new FileConnectorException( - CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION, - "SeaTunnel does not support user-defined schema for [parquet, orc, binary] files"); - default: - // never got in there - throw new FileConnectorException( - CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, - "SeaTunnel does not supported this file format"); - } - } else { - if (filePaths.isEmpty()) { - // When the directory is empty, distribute default behavior schema - rowType = CatalogTableUtil.buildSimpleTextSchema(); - return; - } - try { - rowType = readStrategy.getSeaTunnelRowTypeInfo(filePaths.get(0)); - } catch (FileConnectorException e) { - String errorMsg = - String.format("Get table schema from file [%s] failed", filePaths.get(0)); - throw new FileConnectorException( - CommonErrorCodeDeprecated.TABLE_SCHEMA_GET_FAILED, errorMsg, e); - } - } - } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java index 112cccc3afa..3411e9c407b 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java @@ -19,9 +19,12 @@ import org.apache.seatunnel.api.configuration.util.OptionRule; import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.source.SourceSplit; import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; +import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; +import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions; import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; @@ -29,6 +32,7 @@ import com.google.auto.service.AutoService; +import java.io.Serializable; import java.util.Arrays; @AutoService(Factory.class) @@ -38,15 +42,21 @@ public String factoryIdentifier() { return FileSystemType.FTP.getFileSystemPluginName(); } + @Override + public + TableSource createSource(TableSourceFactoryContext context) { + return () -> (SeaTunnelSource) new FtpFileSource(context.getOptions()); + } + @Override public OptionRule optionRule() { return OptionRule.builder() - .required(FtpConfigOptions.FILE_PATH) - .required(FtpConfigOptions.FTP_HOST) - .required(FtpConfigOptions.FTP_PORT) - .required(FtpConfigOptions.FTP_USERNAME) - .required(FtpConfigOptions.FTP_PASSWORD) - .required(FtpConfigOptions.FILE_FORMAT_TYPE) + .optional(FtpConfigOptions.FILE_PATH) + .optional(FtpConfigOptions.FTP_HOST) + .optional(FtpConfigOptions.FTP_PORT) + .optional(FtpConfigOptions.FTP_USERNAME) + .optional(FtpConfigOptions.FTP_PASSWORD) + .optional(FtpConfigOptions.FILE_FORMAT_TYPE) .conditional( BaseSourceConfigOptions.FILE_FORMAT_TYPE, FileFormat.TEXT, @@ -73,6 +83,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.COMPRESS_CODEC) .optional(FtpConfigOptions.FTP_CONNECTION_MODE) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/FtpConnectionMode.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/FtpConnectionMode.java index 068aa5974c1..44f2264fb2c 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/FtpConnectionMode.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/FtpConnectionMode.java @@ -21,10 +21,10 @@ public enum FtpConnectionMode { /** ACTIVE_LOCAL_DATA_CONNECTION_MODE */ - ACTIVE_LOCAL_DATA_CONNECTION_MODE("active_local"), + ACTIVE_LOCAL("active_local"), /** PASSIVE_LOCAL_DATA_CONNECTION_MODE */ - PASSIVE_LOCAL_DATA_CONNECTION_MODE("passive_local"); + PASSIVE_LOCAL("passive_local"); private final String mode; @@ -38,7 +38,7 @@ public String getMode() { public static FtpConnectionMode fromMode(String mode) { for (FtpConnectionMode ftpConnectionModeEnum : FtpConnectionMode.values()) { - if (ftpConnectionModeEnum.getMode().equals(mode)) { + if (ftpConnectionModeEnum.getMode().equals(mode.toLowerCase())) { return ftpConnectionModeEnum; } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java index 04ba218e455..029890918da 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/system/SeaTunnelFTPFileSystem.java @@ -40,6 +40,8 @@ import org.apache.hadoop.net.NetUtils; import org.apache.hadoop.util.Progressable; +import lombok.extern.slf4j.Slf4j; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -52,6 +54,7 @@ */ @InterfaceAudience.Public @InterfaceStability.Stable +@Slf4j public class SeaTunnelFTPFileSystem extends FileSystem { public static final Log LOG = LogFactory.getLog(SeaTunnelFTPFileSystem.class); @@ -156,10 +159,7 @@ private FTPClient connect() throws IOException { } setFsFtpConnectionMode( - client, - conf.get( - FS_FTP_CONNECTION_MODE, - FtpConnectionMode.ACTIVE_LOCAL_DATA_CONNECTION_MODE.getMode())); + client, conf.get(FS_FTP_CONNECTION_MODE, FtpConnectionMode.ACTIVE_LOCAL.getMode())); return client; } @@ -172,13 +172,18 @@ private FTPClient connect() throws IOException { */ private void setFsFtpConnectionMode(FTPClient client, String mode) { switch (FtpConnectionMode.fromMode(mode)) { - case ACTIVE_LOCAL_DATA_CONNECTION_MODE: - client.enterLocalActiveMode(); - break; - case PASSIVE_LOCAL_DATA_CONNECTION_MODE: + case PASSIVE_LOCAL: client.enterLocalPassiveMode(); break; + case ACTIVE_LOCAL: + client.enterLocalActiveMode(); + break; default: + log.warn( + "Unsupported FTP connection mode: " + mode, + " Using default FTP connection mode: " + + FtpConnectionMode.ACTIVE_LOCAL.getMode()); + client.enterLocalActiveMode(); break; } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/sink/HdfsFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/sink/HdfsFileSink.java index 26c0f4f0490..5e098ea2d21 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/sink/HdfsFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/sink/HdfsFileSink.java @@ -21,10 +21,13 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.sink.SeaTunnelSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; import com.google.auto.service.AutoService; +import java.util.Optional; + @AutoService(SeaTunnelSink.class) public class HdfsFileSink extends BaseHdfsFileSink { @@ -37,4 +40,9 @@ public String getPluginName() { public void prepare(Config pluginConfig) throws PrepareFailException { super.prepare(pluginConfig); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java index 88e46841801..f09dba2c1f8 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java @@ -69,6 +69,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.COMPRESS_CODEC) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/sink/OssFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/sink/OssFileSink.java index ac6ee94992f..03663d5c76e 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/sink/OssFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/sink/OssFileSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -33,6 +34,8 @@ import com.google.auto.service.AutoService; +import java.util.Optional; + @AutoService(SeaTunnelSink.class) public class OssFileSink extends BaseFileSink { @Override @@ -60,4 +63,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { } hadoopConf = OssConf.buildWithConfig(pluginConfig); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSource.java b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSource.java index 335e3967808..ed9807729f1 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSource.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSource.java @@ -22,9 +22,9 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -96,9 +96,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { case JSON: case EXCEL: case XML: - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - readStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); + CatalogTable userDefinedCatalogTable = + CatalogTableUtil.buildWithConfig(pluginConfig); + readStrategy.setCatalogTable(userDefinedCatalogTable); rowType = readStrategy.getActualSeaTunnelRowTypeInfo(); break; case ORC: diff --git a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java index a6c9276c76e..f195ac30714 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java @@ -71,6 +71,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.TIME_FORMAT) .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/sink/LocalFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/sink/LocalFileSink.java index 94741941bf1..4042843d50b 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/sink/LocalFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/sink/LocalFileSink.java @@ -23,14 +23,24 @@ import org.apache.seatunnel.connectors.seatunnel.file.local.config.LocalFileHadoopConf; import org.apache.seatunnel.connectors.seatunnel.file.sink.BaseMultipleTableFileSink; +import java.util.Optional; + public class LocalFileSink extends BaseMultipleTableFileSink { + private final CatalogTable catalogTable; + public LocalFileSink(ReadonlyConfig readonlyConfig, CatalogTable catalogTable) { super(new LocalFileHadoopConf(), readonlyConfig, catalogTable); + this.catalogTable = catalogTable; } @Override public String getPluginName() { return FileSystemType.LOCAL.getFileSystemPluginName(); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java index fb76d276d58..1c66cc6f8b6 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java @@ -50,7 +50,7 @@ TableSource createSource(TableSourceFactoryContext context) { @Override public OptionRule optionRule() { return OptionRule.builder() - .optional(BaseSourceConfigOptions.TABLE_CONFIGS) + .optional(TableSchemaOptions.TABLE_CONFIGS) .optional(BaseSourceConfigOptions.FILE_PATH) .optional(BaseSourceConfigOptions.FILE_FORMAT_TYPE) .optional(BaseSourceConfigOptions.ENCODING) @@ -78,6 +78,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.TIME_FORMAT) .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/sink/ObsFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/sink/ObsFileSink.java index 8f303b6a457..67a17fc954c 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/sink/ObsFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/sink/ObsFileSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -33,6 +34,8 @@ import com.google.auto.service.AutoService; +import java.util.Optional; + @AutoService(SeaTunnelSink.class) public class ObsFileSink extends BaseFileSink { @Override @@ -60,4 +63,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { } hadoopConf = ObsConf.buildWithConfig(pluginConfig); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSource.java b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSource.java index cf3061a44a3..8d2ae3d90bb 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSource.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSource.java @@ -22,9 +22,9 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; import org.apache.seatunnel.common.config.CheckResult; import org.apache.seatunnel.common.constants.PluginType; @@ -91,9 +91,9 @@ public void prepare(Config pluginConfig) throws PrepareFailException { case TEXT: case JSON: case EXCEL: - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - readStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); + CatalogTable userDefinedCatalogTable = + CatalogTableUtil.buildWithConfig(pluginConfig); + readStrategy.setCatalogTable(userDefinedCatalogTable); rowType = readStrategy.getActualSeaTunnelRowTypeInfo(); break; case ORC: diff --git a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java index e1cd0ee97ba..586718b6410 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java @@ -60,6 +60,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.DATE_FORMAT) .optional(BaseSourceConfigOptions.DATETIME_FORMAT) .optional(BaseSourceConfigOptions.TIME_FORMAT) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/sink/OssFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/sink/OssFileSink.java index de4726fd5ce..11a3df2942d 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/sink/OssFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/sink/OssFileSink.java @@ -23,13 +23,24 @@ import org.apache.seatunnel.connectors.seatunnel.file.oss.config.OssHadoopConf; import org.apache.seatunnel.connectors.seatunnel.file.sink.BaseMultipleTableFileSink; +import java.util.Optional; + public class OssFileSink extends BaseMultipleTableFileSink { + + private final CatalogTable catalogTable; + public OssFileSink(ReadonlyConfig readonlyConfig, CatalogTable catalogTable) { super(OssHadoopConf.buildWithConfig(readonlyConfig), readonlyConfig, catalogTable); + this.catalogTable = catalogTable; } @Override public String getPluginName() { return FileSystemType.OSS.getFileSystemPluginName(); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java index 0eddf05693a..9e70249be72 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java @@ -51,9 +51,7 @@ TableSource createSource(TableSourceFactoryContext context) { @Override public OptionRule optionRule() { return OptionRule.builder() - .optional( - org.apache.seatunnel.connectors.seatunnel.file.config - .BaseSourceConfigOptions.TABLE_CONFIGS) + .optional(TableSchemaOptions.TABLE_CONFIGS) .optional(OssConfigOptions.FILE_PATH) .optional(OssConfigOptions.BUCKET) .optional(OssConfigOptions.ACCESS_KEY) @@ -85,6 +83,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.COMPRESS_CODEC) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-s3/pom.xml b/seatunnel-connectors-v2/connector-file/connector-file-s3/pom.xml index fbf0016fced..82ddae46e6c 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-s3/pom.xml +++ b/seatunnel-connectors-v2/connector-file/connector-file-s3/pom.xml @@ -29,21 +29,6 @@ connector-file-s3 SeaTunnel : Connectors V2 : File : S3 - - 3.1.4 - 27.0-jre - - - - - - com.google.guava - guava - ${guava.version} - - - - @@ -67,14 +52,10 @@ - com.google.guava - guava - - - - org.apache.hadoop - hadoop-aws - ${hadoop-aws.version} + org.apache.seatunnel + seatunnel-hadoop-aws + ${project.version} + optional provided diff --git a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/sink/S3FileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/sink/S3FileSink.java index 2a636bcbcc9..b0b6d9fbbb1 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/sink/S3FileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/sink/S3FileSink.java @@ -44,8 +44,8 @@ public class S3FileSink extends BaseMultipleTableFileSink implements SupportSaveMode { - private CatalogTable catalogTable; - private ReadonlyConfig readonlyConfig; + private final CatalogTable catalogTable; + private final ReadonlyConfig readonlyConfig; private static final String S3 = "S3"; @@ -89,4 +89,9 @@ public Optional getSaveModeHandler() { new DefaultSaveModeHandler( schemaSaveMode, dataSaveMode, catalog, catalogTable, null)); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java index d1107d46cf7..a3376e745e6 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java @@ -87,6 +87,7 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.COMPRESS_CODEC) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/catalog/SftpFileCatalog.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/catalog/SftpFileCatalog.java new file mode 100644 index 00000000000..d6c1d4d11b5 --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/catalog/SftpFileCatalog.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.sftp.catalog; + +import org.apache.seatunnel.connectors.seatunnel.file.catalog.AbstractFileCatalog; +import org.apache.seatunnel.connectors.seatunnel.file.hadoop.HadoopFileSystemProxy; + +public class SftpFileCatalog extends AbstractFileCatalog { + + public SftpFileCatalog( + HadoopFileSystemProxy hadoopFileSystemProxy, String filePath, String catalogName) { + super(hadoopFileSystemProxy, filePath, catalogName); + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/catalog/SftpFileCatalogFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/catalog/SftpFileCatalogFactory.java new file mode 100644 index 00000000000..283169c2506 --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/catalog/SftpFileCatalogFactory.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.sftp.catalog; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.table.catalog.Catalog; +import org.apache.seatunnel.api.table.factory.CatalogFactory; +import org.apache.seatunnel.api.table.factory.Factory; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions; +import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; +import org.apache.seatunnel.connectors.seatunnel.file.hadoop.HadoopFileSystemProxy; +import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.SftpConf; + +import com.google.auto.service.AutoService; + +@AutoService(Factory.class) +public class SftpFileCatalogFactory implements CatalogFactory { + @Override + public Catalog createCatalog(String catalogName, ReadonlyConfig options) { + HadoopFileSystemProxy fileSystemUtils = + new HadoopFileSystemProxy(SftpConf.buildWithConfig(options)); + return new SftpFileCatalog( + fileSystemUtils, + options.get(BaseSourceConfigOptions.FILE_PATH), + factoryIdentifier()); + } + + @Override + public String factoryIdentifier() { + return FileSystemType.SFTP.getFileSystemPluginName(); + } + + @Override + public OptionRule optionRule() { + return OptionRule.builder().build(); + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/MultipleTableSFTPFileSourceConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/MultipleTableSFTPFileSourceConfig.java new file mode 100644 index 00000000000..4ae5f89f8a3 --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/MultipleTableSFTPFileSourceConfig.java @@ -0,0 +1,34 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.sftp.config; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseFileSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseMultipleTableFileSourceConfig; + +public class MultipleTableSFTPFileSourceConfig extends BaseMultipleTableFileSourceConfig { + + public MultipleTableSFTPFileSourceConfig(ReadonlyConfig ossFileSourceRootConfig) { + super(ossFileSourceRootConfig); + } + + @Override + public BaseFileSourceConfig getBaseSourceConfig(ReadonlyConfig readonlyConfig) { + return new SFTPFileSourceConfig(readonlyConfig); + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SFTPFileSourceConfig.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SFTPFileSourceConfig.java new file mode 100644 index 00000000000..b2d2c3084ea --- /dev/null +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SFTPFileSourceConfig.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.file.sftp.config; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.BaseFileSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; +import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; + +import lombok.Getter; + +@Getter +public class SFTPFileSourceConfig extends BaseFileSourceConfig { + + private static final long serialVersionUID = 1L; + + @Override + public HadoopConf getHadoopConfig() { + return SftpConf.buildWithConfig(getBaseFileSourceConfig()); + } + + @Override + public String getPluginName() { + return FileSystemType.SFTP.getFileSystemPluginName(); + } + + public SFTPFileSourceConfig(ReadonlyConfig readonlyConfig) { + super(readonlyConfig); + } +} diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SftpConf.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SftpConf.java index 78b4e1ef71b..5353fecae53 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SftpConf.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/config/SftpConf.java @@ -17,8 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.file.sftp.config; -import org.apache.seatunnel.shade.com.typesafe.config.Config; - +import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.connectors.seatunnel.file.config.HadoopConf; import java.util.HashMap; @@ -42,20 +41,16 @@ public String getSchema() { return SCHEMA; } - public static HadoopConf buildWithConfig(Config config) { - String host = config.getString(SftpConfigOptions.SFTP_HOST.key()); - int port = config.getInt(SftpConfigOptions.SFTP_PORT.key()); + public static HadoopConf buildWithConfig(ReadonlyConfig config) { + String host = config.get(SftpConfigOptions.SFTP_HOST); + int port = config.get(SftpConfigOptions.SFTP_PORT); String defaultFS = String.format("sftp://%s:%s", host, port); HadoopConf hadoopConf = new SftpConf(defaultFS); HashMap sftpOptions = new HashMap<>(); + sftpOptions.put("fs.sftp.user." + host, config.get(SftpConfigOptions.SFTP_USER)); sftpOptions.put( - "fs.sftp.user." + host, config.getString(SftpConfigOptions.SFTP_USER.key())); - sftpOptions.put( - "fs.sftp.password." - + host - + "." - + config.getString(SftpConfigOptions.SFTP_USER.key()), - config.getString(SftpConfigOptions.SFTP_PASSWORD.key())); + "fs.sftp.password." + host + "." + config.get(SftpConfigOptions.SFTP_USER), + config.get(SftpConfigOptions.SFTP_PASSWORD)); hadoopConf.setExtraOptions(sftpOptions); return hadoopConf; } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSink.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSink.java index 56a8d879918..415ae3a5d62 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSink.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSink.java @@ -17,46 +17,30 @@ package org.apache.seatunnel.connectors.seatunnel.file.sftp.sink; -import org.apache.seatunnel.shade.com.typesafe.config.Config; - -import org.apache.seatunnel.api.common.PrepareFailException; -import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; -import org.apache.seatunnel.api.sink.SeaTunnelSink; -import org.apache.seatunnel.common.config.CheckConfigUtil; -import org.apache.seatunnel.common.config.CheckResult; -import org.apache.seatunnel.common.constants.PluginType; +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; -import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorException; import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.SftpConf; -import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.SftpConfigOptions; -import org.apache.seatunnel.connectors.seatunnel.file.sink.BaseFileSink; +import org.apache.seatunnel.connectors.seatunnel.file.sink.BaseMultipleTableFileSink; -import com.google.auto.service.AutoService; +import java.util.Optional; + +public class SftpFileSink extends BaseMultipleTableFileSink { + + private final CatalogTable catalogTable; + + public SftpFileSink(ReadonlyConfig readonlyConfig, CatalogTable catalogTable) { + super(SftpConf.buildWithConfig(readonlyConfig), readonlyConfig, catalogTable); + this.catalogTable = catalogTable; + } -@AutoService(SeaTunnelSink.class) -public class SftpFileSink extends BaseFileSink { @Override public String getPluginName() { return FileSystemType.SFTP.getFileSystemPluginName(); } @Override - public void prepare(Config pluginConfig) throws PrepareFailException { - CheckResult result = - CheckConfigUtil.checkAllExists( - pluginConfig, - SftpConfigOptions.SFTP_HOST.key(), - SftpConfigOptions.SFTP_PORT.key(), - SftpConfigOptions.SFTP_USER.key(), - SftpConfigOptions.SFTP_PASSWORD.key()); - if (!result.isSuccess()) { - throw new FileConnectorException( - SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, - String.format( - "PluginName: %s, PluginType: %s, Message: %s", - getPluginName(), PluginType.SINK, result.getMsg())); - } - super.prepare(pluginConfig); - hadoopConf = SftpConf.buildWithConfig(pluginConfig); + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSinkFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSinkFactory.java index e8116c35f18..22f42d2bf9e 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSinkFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/sink/SftpFileSinkFactory.java @@ -17,18 +17,27 @@ package org.apache.seatunnel.connectors.seatunnel.file.sftp.sink; +import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.sink.SinkCommonOptions; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.connector.TableSink; import org.apache.seatunnel.api.table.factory.Factory; -import org.apache.seatunnel.api.table.factory.TableSinkFactory; +import org.apache.seatunnel.api.table.factory.TableSinkFactoryContext; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSinkConfig; import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; +import org.apache.seatunnel.connectors.seatunnel.file.factory.BaseMultipleTableFileSinkFactory; import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.SftpConfigOptions; +import org.apache.seatunnel.connectors.seatunnel.file.sink.commit.FileAggregatedCommitInfo; +import org.apache.seatunnel.connectors.seatunnel.file.sink.commit.FileCommitInfo; +import org.apache.seatunnel.connectors.seatunnel.file.sink.state.FileSinkState; import com.google.auto.service.AutoService; @AutoService(Factory.class) -public class SftpFileSinkFactory implements TableSinkFactory { +public class SftpFileSinkFactory extends BaseMultipleTableFileSinkFactory { @Override public String factoryIdentifier() { return FileSystemType.SFTP.getFileSystemPluginName(); @@ -43,6 +52,9 @@ public OptionRule optionRule() { .required(SftpConfigOptions.SFTP_USER) .required(SftpConfigOptions.SFTP_PASSWORD) .optional(BaseSinkConfig.FILE_FORMAT_TYPE) + .optional(BaseSinkConfig.SCHEMA_SAVE_MODE) + .optional(BaseSinkConfig.DATA_SAVE_MODE) + .optional(SinkCommonOptions.MULTI_TABLE_SINK_REPLICA) .conditional( BaseSinkConfig.FILE_FORMAT_TYPE, FileFormat.TEXT, @@ -93,4 +105,12 @@ public OptionRule optionRule() { .optional(BaseSinkConfig.TIME_FORMAT) .build(); } + + @Override + public TableSink + createSink(TableSinkFactoryContext context) { + ReadonlyConfig readonlyConfig = context.getOptions(); + CatalogTable catalogTable = context.getCatalogTable(); + return () -> new SftpFileSink(readonlyConfig, catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSource.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSource.java index d20823adc19..bc984383a6b 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSource.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSource.java @@ -17,120 +17,18 @@ package org.apache.seatunnel.connectors.seatunnel.file.sftp.source; -import org.apache.seatunnel.shade.com.typesafe.config.Config; - -import org.apache.seatunnel.api.common.PrepareFailException; -import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; -import org.apache.seatunnel.api.source.SeaTunnelSource; -import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; -import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.common.config.CheckConfigUtil; -import org.apache.seatunnel.common.config.CheckResult; -import org.apache.seatunnel.common.constants.PluginType; -import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; -import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat; +import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; -import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorErrorCode; -import org.apache.seatunnel.connectors.seatunnel.file.exception.FileConnectorException; -import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.SftpConf; -import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.SftpConfigOptions; -import org.apache.seatunnel.connectors.seatunnel.file.source.BaseFileSource; -import org.apache.seatunnel.connectors.seatunnel.file.source.reader.ReadStrategyFactory; - -import com.google.auto.service.AutoService; +import org.apache.seatunnel.connectors.seatunnel.file.sftp.config.MultipleTableSFTPFileSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.file.source.BaseMultipleTableFileSource; -import java.io.IOException; +public class SftpFileSource extends BaseMultipleTableFileSource { + public SftpFileSource(ReadonlyConfig config) { + super(new MultipleTableSFTPFileSourceConfig(config)); + } -@AutoService(SeaTunnelSource.class) -public class SftpFileSource extends BaseFileSource { @Override public String getPluginName() { return FileSystemType.SFTP.getFileSystemPluginName(); } - - @Override - public void prepare(Config pluginConfig) throws PrepareFailException { - CheckResult result = - CheckConfigUtil.checkAllExists( - pluginConfig, - SftpConfigOptions.FILE_PATH.key(), - SftpConfigOptions.FILE_FORMAT_TYPE.key(), - SftpConfigOptions.SFTP_HOST.key(), - SftpConfigOptions.SFTP_PORT.key(), - SftpConfigOptions.SFTP_USER.key(), - SftpConfigOptions.SFTP_PASSWORD.key()); - if (!result.isSuccess()) { - throw new FileConnectorException( - SeaTunnelAPIErrorCode.CONFIG_VALIDATION_FAILED, - String.format( - "PluginName: %s, PluginType: %s, Message: %s", - getPluginName(), PluginType.SOURCE, result.getMsg())); - } - FileFormat fileFormat = - FileFormat.valueOf( - pluginConfig - .getString(SftpConfigOptions.FILE_FORMAT_TYPE.key()) - .toUpperCase()); - if (fileFormat == FileFormat.ORC || fileFormat == FileFormat.PARQUET) { - throw new FileConnectorException( - CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, - "Sftp file source connector only support read [text, csv, json, xml] files"); - } - String path = pluginConfig.getString(SftpConfigOptions.FILE_PATH.key()); - hadoopConf = SftpConf.buildWithConfig(pluginConfig); - readStrategy = - ReadStrategyFactory.of( - pluginConfig.getString(SftpConfigOptions.FILE_FORMAT_TYPE.key())); - readStrategy.setPluginConfig(pluginConfig); - readStrategy.init(hadoopConf); - try { - filePaths = readStrategy.getFileNamesByPath(path); - } catch (IOException e) { - String errorMsg = String.format("Get file list from this path [%s] failed", path); - throw new FileConnectorException( - FileConnectorErrorCode.FILE_LIST_GET_FAILED, errorMsg, e); - } - // support user-defined schema - // only json csv text type support user-defined schema now - if (pluginConfig.hasPath(TableSchemaOptions.SCHEMA.key())) { - switch (fileFormat) { - case CSV: - case TEXT: - case JSON: - case EXCEL: - case XML: - SeaTunnelRowType userDefinedSchema = - CatalogTableUtil.buildWithConfig(pluginConfig).getSeaTunnelRowType(); - readStrategy.setSeaTunnelRowTypeInfo(userDefinedSchema); - rowType = readStrategy.getActualSeaTunnelRowTypeInfo(); - break; - case ORC: - case PARQUET: - case BINARY: - throw new FileConnectorException( - CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION, - "SeaTunnel does not support user-defined schema for [parquet, orc, binary] files"); - default: - // never got in there - throw new FileConnectorException( - CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT, - "SeaTunnel does not supported this file format"); - } - } else { - if (filePaths.isEmpty()) { - // When the directory is empty, distribute default behavior schema - rowType = CatalogTableUtil.buildSimpleTextSchema(); - return; - } - try { - rowType = readStrategy.getSeaTunnelRowTypeInfo(filePaths.get(0)); - } catch (FileConnectorException e) { - String errorMsg = - String.format("Get table schema from file [%s] failed", filePaths.get(0)); - throw new FileConnectorException( - CommonErrorCodeDeprecated.TABLE_SCHEMA_GET_FAILED, errorMsg, e); - } - } - } } diff --git a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java index c0f6aefda30..8e5fd9ade81 100644 --- a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java +++ b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java @@ -19,9 +19,12 @@ import org.apache.seatunnel.api.configuration.util.OptionRule; import org.apache.seatunnel.api.source.SeaTunnelSource; +import org.apache.seatunnel.api.source.SourceSplit; import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; +import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; +import org.apache.seatunnel.api.table.factory.TableSourceFactoryContext; import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions; import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat; import org.apache.seatunnel.connectors.seatunnel.file.config.FileSystemType; @@ -29,6 +32,7 @@ import com.google.auto.service.AutoService; +import java.io.Serializable; import java.util.Arrays; @AutoService(Factory.class) @@ -41,12 +45,12 @@ public String factoryIdentifier() { @Override public OptionRule optionRule() { return OptionRule.builder() - .required(SftpConfigOptions.FILE_PATH) - .required(SftpConfigOptions.SFTP_HOST) - .required(SftpConfigOptions.SFTP_PORT) - .required(SftpConfigOptions.SFTP_USER) - .required(SftpConfigOptions.SFTP_PASSWORD) - .required(BaseSourceConfigOptions.FILE_FORMAT_TYPE) + .optional(SftpConfigOptions.FILE_PATH) + .optional(SftpConfigOptions.SFTP_HOST) + .optional(SftpConfigOptions.SFTP_PORT) + .optional(SftpConfigOptions.SFTP_USER) + .optional(SftpConfigOptions.SFTP_PASSWORD) + .optional(BaseSourceConfigOptions.FILE_FORMAT_TYPE) .conditional( BaseSourceConfigOptions.FILE_FORMAT_TYPE, FileFormat.TEXT, @@ -72,9 +76,16 @@ public OptionRule optionRule() { .optional(BaseSourceConfigOptions.FILE_FILTER_PATTERN) .optional(BaseSourceConfigOptions.COMPRESS_CODEC) .optional(BaseSourceConfigOptions.ARCHIVE_COMPRESS_CODEC) + .optional(BaseSourceConfigOptions.NULL_FORMAT) .build(); } + @Override + public + TableSource createSource(TableSourceFactoryContext context) { + return () -> (SeaTunnelSource) new SftpFileSource(context.getOptions()); + } + @Override public Class getSourceClass() { return SftpFileSource.class; diff --git a/seatunnel-connectors-v2/connector-google-firestore/src/main/java/org/apache/seatunnel/connectors/seatunnel/google/firestore/sink/FirestoreSink.java b/seatunnel-connectors-v2/connector-google-firestore/src/main/java/org/apache/seatunnel/connectors/seatunnel/google/firestore/sink/FirestoreSink.java index ab7c02057da..6149ba93583 100644 --- a/seatunnel-connectors-v2/connector-google-firestore/src/main/java/org/apache/seatunnel/connectors/seatunnel/google/firestore/sink/FirestoreSink.java +++ b/seatunnel-connectors-v2/connector-google-firestore/src/main/java/org/apache/seatunnel/connectors/seatunnel/google/firestore/sink/FirestoreSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -36,6 +37,7 @@ import com.google.auto.service.AutoService; import java.io.IOException; +import java.util.Optional; import static org.apache.seatunnel.connectors.seatunnel.google.firestore.config.FirestoreConfig.COLLECTION; import static org.apache.seatunnel.connectors.seatunnel.google.firestore.config.FirestoreConfig.PROJECT_ID; @@ -76,4 +78,9 @@ public AbstractSinkWriter createWriter(SinkWriter.Context co throws IOException { return new FirestoreSinkWriter(rowType, firestoreParameters); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/catalog/HbaseCatalog.java b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/catalog/HbaseCatalog.java index ec07bb3fcad..d779cd663f6 100644 --- a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/catalog/HbaseCatalog.java +++ b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/catalog/HbaseCatalog.java @@ -39,7 +39,7 @@ import java.util.Optional; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** Hbase catalog implementation. */ @Slf4j diff --git a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/sink/HbaseSink.java b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/sink/HbaseSink.java index 0a46b1baefa..e8d7b8b2053 100644 --- a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/sink/HbaseSink.java +++ b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/sink/HbaseSink.java @@ -52,15 +52,15 @@ public class HbaseSink SupportMultiTableSink, SupportSaveMode { - private ReadonlyConfig config; + private final ReadonlyConfig config; - private CatalogTable catalogTable; + private final CatalogTable catalogTable; private final HbaseParameters hbaseParameters; - private SeaTunnelRowType seaTunnelRowType; + private final SeaTunnelRowType seaTunnelRowType; - private List rowkeyColumnIndexes = new ArrayList<>(); + private final List rowkeyColumnIndexes = new ArrayList<>(); private int versionColumnIndex = -1; @@ -110,4 +110,9 @@ public Optional getSaveModeHandler() { new DefaultSaveModeHandler( schemaSaveMode, dataSaveMode, catalog, tablePath, null, null)); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSource.java b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSource.java index 1a597eea133..1178878aa7d 100644 --- a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSource.java +++ b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSource.java @@ -18,6 +18,7 @@ package org.apache.seatunnel.connectors.seatunnel.hbase.source; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; @@ -38,8 +39,6 @@ import org.apache.seatunnel.connectors.seatunnel.hbase.constant.HbaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.hbase.exception.HbaseConnectorException; -import com.google.common.collect.Lists; - import java.util.List; import static org.apache.seatunnel.connectors.seatunnel.hbase.config.HbaseConfig.TABLE; diff --git a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSourceReader.java b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSourceReader.java index 2f78fb280c0..aa648126326 100644 --- a/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSourceReader.java +++ b/seatunnel-connectors-v2/connector-hbase/src/main/java/org/apache/seatunnel/connectors/seatunnel/hbase/source/HbaseSourceReader.java @@ -18,6 +18,9 @@ package org.apache.seatunnel.connectors.seatunnel.hbase.source; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; +import org.apache.seatunnel.shade.com.google.common.collect.Maps; + import org.apache.seatunnel.api.source.Collector; import org.apache.seatunnel.api.source.SourceReader; import org.apache.seatunnel.api.table.type.SeaTunnelRow; @@ -29,8 +32,6 @@ import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import java.io.IOException; diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/config/BaseHiveOptions.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/config/HiveOptions.java similarity index 96% rename from seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/config/BaseHiveOptions.java rename to seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/config/HiveOptions.java index efed4e91c58..6fe55e2e718 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/config/BaseHiveOptions.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/config/HiveOptions.java @@ -21,7 +21,7 @@ import org.apache.seatunnel.api.configuration.Options; import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions; -public class BaseHiveOptions extends BaseSourceConfigOptions { +public class HiveOptions extends BaseSourceConfigOptions { public static final Option TABLE_NAME = Options.key("table_name") diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSink.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSink.java index b5602c13f88..13f48823b2e 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSink.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSink.java @@ -41,9 +41,9 @@ import org.apache.seatunnel.connectors.seatunnel.file.sink.writer.WriteStrategyFactory; import org.apache.seatunnel.connectors.seatunnel.hive.commit.HiveSinkAggregatedCommitter; import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveConstants; +import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveOptions; import org.apache.seatunnel.connectors.seatunnel.hive.exception.HiveConnectorException; import org.apache.seatunnel.connectors.seatunnel.hive.sink.writter.HiveSinkWriter; -import org.apache.seatunnel.connectors.seatunnel.hive.source.config.HiveSourceOptions; import org.apache.seatunnel.connectors.seatunnel.hive.storage.StorageFactory; import org.apache.seatunnel.connectors.seatunnel.hive.utils.HiveTableUtils; @@ -216,16 +216,14 @@ private HadoopConf createHadoopConf(ReadonlyConfig readonlyConfig) { StorageFactory.getStorageType(hdfsLocation) .buildHadoopConfWithReadOnlyConfig(readonlyConfig); readonlyConfig - .getOptional(HiveSourceOptions.HDFS_SITE_PATH) + .getOptional(HiveOptions.HDFS_SITE_PATH) .ifPresent(hadoopConf::setHdfsSitePath); + readonlyConfig.getOptional(HiveOptions.REMOTE_USER).ifPresent(hadoopConf::setRemoteUser); readonlyConfig - .getOptional(HiveSourceOptions.REMOTE_USER) - .ifPresent(hadoopConf::setRemoteUser); - readonlyConfig - .getOptional(HiveSourceOptions.KERBEROS_PRINCIPAL) + .getOptional(HiveOptions.KERBEROS_PRINCIPAL) .ifPresent(hadoopConf::setKerberosPrincipal); readonlyConfig - .getOptional(HiveSourceOptions.KERBEROS_KEYTAB_PATH) + .getOptional(HiveOptions.KERBEROS_KEYTAB_PATH) .ifPresent(hadoopConf::setKerberosKeytabPath); return hadoopConf; } @@ -240,8 +238,13 @@ private Table getTableInformation() { private WriteStrategy getWriteStrategy() { if (writeStrategy == null) { writeStrategy = WriteStrategyFactory.of(fileSinkConfig.getFileFormat(), fileSinkConfig); - writeStrategy.setSeaTunnelRowTypeInfo(catalogTable.getSeaTunnelRowType()); + writeStrategy.setCatalogTable(catalogTable); } return writeStrategy; } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSinkOptions.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSinkOptions.java index a241717a448..404244b4112 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSinkOptions.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/sink/HiveSinkOptions.java @@ -19,9 +19,9 @@ import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; -import org.apache.seatunnel.connectors.seatunnel.hive.config.BaseHiveOptions; +import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveOptions; -public class HiveSinkOptions extends BaseHiveOptions { +public class HiveSinkOptions extends HiveOptions { public static final Option ABORT_DROP_PARTITION_METADATA = Options.key("abort_drop_partition_metadata") diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/HiveSourceFactory.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/HiveSourceFactory.java index 07adfef106f..63e235d3dcc 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/HiveSourceFactory.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/HiveSourceFactory.java @@ -20,6 +20,8 @@ import org.apache.seatunnel.api.configuration.util.OptionRule; import org.apache.seatunnel.api.source.SeaTunnelSource; import org.apache.seatunnel.api.source.SourceSplit; +import org.apache.seatunnel.api.table.catalog.CatalogOptions; +import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; @@ -27,7 +29,6 @@ import org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions; import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveConfig; import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveConstants; -import org.apache.seatunnel.connectors.seatunnel.hive.source.config.HiveSourceOptions; import com.google.auto.service.AutoService; @@ -51,7 +52,7 @@ public OptionRule optionRule() { return OptionRule.builder() .optional(HiveConfig.TABLE_NAME) .optional(HiveConfig.METASTORE_URI) - .optional(HiveSourceOptions.TABLE_CONFIGS) + .optional(TableSchemaOptions.TABLE_CONFIGS, CatalogOptions.TABLE_LIST) .optional(BaseSourceConfigOptions.READ_PARTITIONS) .optional(BaseSourceConfigOptions.READ_COLUMNS) .optional(BaseSourceConfigOptions.KERBEROS_PRINCIPAL) diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/HiveSourceConfig.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/HiveSourceConfig.java index e98143fcf0e..dd3d9478c13 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/HiveSourceConfig.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/HiveSourceConfig.java @@ -46,6 +46,7 @@ import org.apache.seatunnel.connectors.seatunnel.hive.utils.HiveTypeConvertor; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.hive.metastore.api.FieldSchema; import org.apache.hadoop.hive.metastore.api.Table; @@ -63,6 +64,7 @@ import static org.apache.seatunnel.connectors.seatunnel.file.config.BaseSinkConfig.FIELD_DELIMITER; import static org.apache.seatunnel.connectors.seatunnel.file.config.BaseSinkConfig.FILE_FORMAT_TYPE; import static org.apache.seatunnel.connectors.seatunnel.file.config.BaseSinkConfig.ROW_DELIMITER; +import static org.apache.seatunnel.connectors.seatunnel.file.config.BaseSourceConfigOptions.NULL_FORMAT; @Getter public class HiveSourceConfig implements Serializable { @@ -122,6 +124,19 @@ private ReadStrategy parseReadStrategy( case TEXT: // if the file format is text, we set the delim. Map parameters = table.getSd().getSerdeInfo().getParameters(); + if (!readonlyConfig.getOptional(NULL_FORMAT).isPresent()) { + String nullFormatKey = "serialization.null.format"; + String nullFormat = table.getParameters().get(nullFormatKey); + if (StringUtils.isEmpty(nullFormat)) { + nullFormat = parameters.get(nullFormatKey); + } + if (StringUtils.isEmpty(nullFormat)) { + nullFormat = "\\N"; + } + config = + config.withValue( + NULL_FORMAT.key(), ConfigValueFactory.fromAnyRef(nullFormat)); + } config = config.withValue( FIELD_DELIMITER.key(), @@ -279,7 +294,9 @@ private CatalogTable parseCatalogTableFromTable( } SeaTunnelRowType seaTunnelRowType = new SeaTunnelRowType(fieldNames, fieldTypes); - readStrategy.setSeaTunnelRowTypeInfo(seaTunnelRowType); + readStrategy.setCatalogTable( + CatalogTableUtil.getCatalogTable( + "hive", table.getDbName(), null, table.getTableName(), seaTunnelRowType)); final SeaTunnelRowType finalSeatunnelRowType = readStrategy.getActualSeaTunnelRowTypeInfo(); CatalogTable catalogTable = buildEmptyCatalogTable(readonlyConfig, table); diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/MultipleTableHiveSourceConfig.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/MultipleTableHiveSourceConfig.java index 9db899ca8c7..6a77e1b98e3 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/MultipleTableHiveSourceConfig.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/source/config/MultipleTableHiveSourceConfig.java @@ -17,9 +17,12 @@ package org.apache.seatunnel.connectors.seatunnel.hive.source.config; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.CatalogOptions; +import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; -import com.google.common.collect.Lists; import lombok.Getter; import java.io.Serializable; @@ -33,16 +36,27 @@ public class MultipleTableHiveSourceConfig implements Serializable { @Getter private List hiveSourceConfigs; public MultipleTableHiveSourceConfig(ReadonlyConfig readonlyConfig) { - if (readonlyConfig.getOptional(HiveSourceOptions.TABLE_CONFIGS).isPresent()) { - parseFromLocalFileSourceConfigs(readonlyConfig); + if (readonlyConfig.getOptional(CatalogOptions.TABLE_LIST).isPresent()) { + parseFromLocalFileSourceByTableList(readonlyConfig); + } else if (readonlyConfig.getOptional(TableSchemaOptions.TABLE_CONFIGS).isPresent()) { + parseFromLocalFileSourceByTableConfigs(readonlyConfig); } else { parseFromLocalFileSourceConfig(readonlyConfig); } } - private void parseFromLocalFileSourceConfigs(ReadonlyConfig readonlyConfig) { + private void parseFromLocalFileSourceByTableList(ReadonlyConfig readonlyConfig) { + this.hiveSourceConfigs = + readonlyConfig.get(CatalogOptions.TABLE_LIST).stream() + .map(ReadonlyConfig::fromMap) + .map(HiveSourceConfig::new) + .collect(Collectors.toList()); + } + // hive is structured, should use table_list + @Deprecated + private void parseFromLocalFileSourceByTableConfigs(ReadonlyConfig readonlyConfig) { this.hiveSourceConfigs = - readonlyConfig.get(HiveSourceOptions.TABLE_CONFIGS).stream() + readonlyConfig.get(TableSchemaOptions.TABLE_CONFIGS).stream() .map(ReadonlyConfig::fromMap) .map(HiveSourceConfig::new) .collect(Collectors.toList()); diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/AbstractStorage.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/AbstractStorage.java index 7f453c936bc..87e76697a01 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/AbstractStorage.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/AbstractStorage.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.hive.storage; +import org.apache.seatunnel.shade.com.google.common.collect.ImmutableList; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.shade.com.typesafe.config.ConfigValueFactory; @@ -32,7 +33,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.conf.Configuration; -import com.google.common.collect.ImmutableList; import lombok.extern.slf4j.Slf4j; import java.io.File; diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageFactory.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageFactory.java index 8a08f2be97f..926e7d70b95 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageFactory.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageFactory.java @@ -18,6 +18,7 @@ package org.apache.seatunnel.connectors.seatunnel.hive.storage; public class StorageFactory { + public static Storage getStorageType(String hiveSdLocation) { if (hiveSdLocation.startsWith(StorageType.S3.name().toLowerCase())) { return new S3Storage(); @@ -25,6 +26,10 @@ public static Storage getStorageType(String hiveSdLocation) { return new OSSStorage(); } else if (hiveSdLocation.startsWith(StorageType.COS.name().toLowerCase())) { return new COSStorage(); + } else if (hiveSdLocation.startsWith(StorageType.FILE.name().toLowerCase())) { + // Currently used in e2e, When Hive uses local files as storage, "file:" needs to be + // replaced with "file:/" to avoid being recognized as HDFS storage. + return new HDFSStorage(hiveSdLocation.replace("file:", "file:/")); } else { return new HDFSStorage(hiveSdLocation); } diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageType.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageType.java index 49a76019778..b8b195a87e8 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageType.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/storage/StorageType.java @@ -21,5 +21,6 @@ public enum StorageType { S3, OSS, COS, + FILE, HDFS } diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveMetaStoreProxy.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveMetaStoreProxy.java index 62d917ca0d1..18482aa2c76 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveMetaStoreProxy.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveMetaStoreProxy.java @@ -23,9 +23,9 @@ import org.apache.seatunnel.connectors.seatunnel.file.hadoop.HadoopLoginFactory; import org.apache.seatunnel.connectors.seatunnel.file.hdfs.source.config.HdfsSourceConfigOptions; import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveConfig; +import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveOptions; import org.apache.seatunnel.connectors.seatunnel.hive.exception.HiveConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.hive.exception.HiveConnectorException; -import org.apache.seatunnel.connectors.seatunnel.hive.source.config.HiveSourceOptions; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.conf.Configuration; @@ -54,7 +54,7 @@ public class HiveMetaStoreProxy { private static final List HADOOP_CONF_FILES = ImmutableList.of("hive-site.xml"); private HiveMetaStoreProxy(ReadonlyConfig readonlyConfig) { - String metastoreUri = readonlyConfig.get(HiveSourceOptions.METASTORE_URI); + String metastoreUri = readonlyConfig.get(HiveOptions.METASTORE_URI); String hiveHadoopConfigPath = readonlyConfig.get(HiveConfig.HADOOP_CONF_PATH); String hiveSitePath = readonlyConfig.get(HiveConfig.HIVE_SITE_PATH); HiveConf hiveConf = new HiveConf(); @@ -121,7 +121,7 @@ private HiveMetaStoreProxy(ReadonlyConfig readonlyConfig) { String.format( "Using this hive uris [%s], hive conf [%s] to initialize " + "hive metastore client instance failed", - metastoreUri, readonlyConfig.get(HiveSourceOptions.HIVE_SITE_PATH)); + metastoreUri, readonlyConfig.get(HiveOptions.HIVE_SITE_PATH)); throw new HiveConnectorException( HiveConnectorErrorCode.INITIALIZE_HIVE_METASTORE_CLIENT_FAILED, errorMsg, e); } catch (Exception e) { diff --git a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveTableUtils.java b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveTableUtils.java index 7b9192ea645..0805fe04f39 100644 --- a/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveTableUtils.java +++ b/seatunnel-connectors-v2/connector-hive/src/main/java/org/apache/seatunnel/connectors/seatunnel/hive/utils/HiveTableUtils.java @@ -23,16 +23,16 @@ import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException; import org.apache.seatunnel.connectors.seatunnel.file.config.FileFormat; import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveConstants; +import org.apache.seatunnel.connectors.seatunnel.hive.config.HiveOptions; import org.apache.seatunnel.connectors.seatunnel.hive.exception.HiveConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.hive.exception.HiveConnectorException; -import org.apache.seatunnel.connectors.seatunnel.hive.source.config.HiveSourceOptions; import org.apache.hadoop.hive.metastore.api.Table; public class HiveTableUtils { public static Table getTableInfo(ReadonlyConfig readonlyConfig) { - String table = readonlyConfig.get(HiveSourceOptions.TABLE_NAME); + String table = readonlyConfig.get(HiveOptions.TABLE_NAME); TablePath tablePath = TablePath.of(table); if (tablePath.getDatabaseName() == null || tablePath.getTableName() == null) { throw new SeaTunnelRuntimeException( diff --git a/seatunnel-connectors-v2/connector-hive/src/test/resources/fakesource_to_hive.conf b/seatunnel-connectors-v2/connector-hive/src/test/resources/fakesource_to_hive.conf index 731c3f6490b..8065695be9b 100644 --- a/seatunnel-connectors-v2/connector-hive/src/test/resources/fakesource_to_hive.conf +++ b/seatunnel-connectors-v2/connector-hive/src/test/resources/fakesource_to_hive.conf @@ -26,7 +26,7 @@ env { source { # This is a example source plugin **only for test and demonstrate the feature source plugin** FakeSource { - result_table_name = "fake" + plugin_output = "fake" field_name = "name,age" } diff --git a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/client/HttpClientProvider.java b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/client/HttpClientProvider.java index b666058ce13..cbea79a15a4 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/client/HttpClientProvider.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/client/HttpClientProvider.java @@ -33,6 +33,7 @@ import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; @@ -263,6 +264,31 @@ public HttpResponse doPost(String url, Map headers, String body) return getResponse(httpPost); } + /** + * Send a post request with request headers and request body + * + * @param url request address + * @param headers request header map + * @param byteArrayEntity request snappy body content + * @return http response result + * @throws Exception information + */ + public HttpResponse doPost( + String url, Map headers, ByteArrayEntity byteArrayEntity) + throws Exception { + // create a new http post + HttpPost httpPost = new HttpPost(url); + // set default request config + httpPost.setConfig(requestConfig); + // set request header + addHeaders(httpPost, headers); + // add body in request + httpPost.getRequestLine(); + httpPost.setEntity(byteArrayEntity); + // return http response + return getResponse(httpPost); + } + /** * Send a post request with request headers , request parameters and request body * diff --git a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSink.java b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSink.java index 9dfe688c118..5ac6b927ca4 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSink.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -34,15 +35,17 @@ import java.io.IOException; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; public class HttpSink extends AbstractSimpleSink implements SupportMultiTableSink { protected final HttpParameter httpParameter = new HttpParameter(); + protected CatalogTable catalogTable; protected SeaTunnelRowType seaTunnelRowType; protected Config pluginConfig; - public HttpSink(Config pluginConfig, SeaTunnelRowType rowType) { + public HttpSink(Config pluginConfig, CatalogTable catalogTable) { this.pluginConfig = pluginConfig; CheckResult result = CheckConfigUtil.checkAllExists(pluginConfig, HttpConfig.URL.key()); if (!result.isSuccess()) { @@ -71,7 +74,8 @@ public HttpSink(Config pluginConfig, SeaTunnelRowType rowType) { entry -> String.valueOf(entry.getValue().unwrapped()), (v1, v2) -> v2))); } - this.seaTunnelRowType = rowType; + this.catalogTable = catalogTable; + this.seaTunnelRowType = catalogTable.getSeaTunnelRowType(); } @Override @@ -83,4 +87,9 @@ public String getPluginName() { public HttpSinkWriter createWriter(SinkWriter.Context context) throws IOException { return new HttpSinkWriter(seaTunnelRowType, httpParameter); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java index 313d26dd3f7..6ed6765d570 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/sink/HttpSinkFactory.java @@ -38,8 +38,7 @@ public String factoryIdentifier() { @Override public TableSink createSink(TableSinkFactoryContext context) { CatalogTable catalogTable = context.getCatalogTable(); - return () -> - new HttpSink(context.getOptions().toConfig(), catalogTable.getSeaTunnelRowType()); + return () -> new HttpSink(context.getOptions().toConfig(), catalogTable); } @Override diff --git a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSource.java b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSource.java index 69c87e4b913..432d37b76c4 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSource.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSource.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.http.source; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.shade.com.typesafe.config.ConfigRenderOptions; @@ -49,8 +50,6 @@ import org.apache.seatunnel.connectors.seatunnel.http.exception.HttpConnectorException; import org.apache.seatunnel.format.json.JsonDeserializationSchema; -import com.google.common.collect.Lists; - import java.util.Collections; import java.util.List; import java.util.Locale; diff --git a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java index 051507e8e3f..73456905691 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/http/source/HttpSourceReader.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.http.source; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.api.serialization.DeserializationSchema; import org.apache.seatunnel.api.source.Boundedness; import org.apache.seatunnel.api.source.Collector; @@ -32,7 +34,6 @@ import org.apache.seatunnel.connectors.seatunnel.http.exception.HttpConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.http.exception.HttpConnectorException; -import com.google.common.base.Strings; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; @@ -153,6 +154,13 @@ private void updateRequestParam(PageInfo pageInfo) { .put(pageInfo.getPageField(), pageInfo.getPageIndex().toString()); } + @Override + public void pollNext(Collector output) throws Exception { + synchronized (output.getCheckpointLock()) { + internalPollNext(output); + } + } + @Override public void internalPollNext(Collector output) throws Exception { try { diff --git a/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSink.java b/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSink.java index b3fbaa6a5ba..25af9636c8a 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSink.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSink.java @@ -19,16 +19,23 @@ import org.apache.seatunnel.shade.com.typesafe.config.Config; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.seatunnel.http.sink.HttpSink; +import java.util.Optional; + public class FeishuSink extends HttpSink { - public FeishuSink(Config pluginConfig, SeaTunnelRowType rowType) { - super(pluginConfig, rowType); + public FeishuSink(Config pluginConfig, CatalogTable catalogTable) { + super(pluginConfig, catalogTable); } @Override public String getPluginName() { return "Feishu"; } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSinkFactory.java b/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSinkFactory.java index f9cd6ee01ca..3052ba78d13 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSinkFactory.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-feishu/src/main/java/org/apache/seatunnel/connectors/seatunnel/feishu/sink/FeishuSinkFactory.java @@ -31,8 +31,7 @@ public class FeishuSinkFactory extends HttpSinkFactory { @Override public TableSink createSink(TableSinkFactoryContext context) { CatalogTable catalogTable = context.getCatalogTable(); - return () -> - new FeishuSink(context.getOptions().toConfig(), catalogTable.getSeaTunnelRowType()); + return () -> new FeishuSink(context.getOptions().toConfig(), catalogTable); } @Override diff --git a/seatunnel-connectors-v2/connector-http/connector-http-myhours/src/main/java/org/apache/seatunnel/connectors/seatunnel/myhours/source/MyHoursSource.java b/seatunnel-connectors-v2/connector-http/connector-http-myhours/src/main/java/org/apache/seatunnel/connectors/seatunnel/myhours/source/MyHoursSource.java index 31f606afdd3..6ac7e882581 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-myhours/src/main/java/org/apache/seatunnel/connectors/seatunnel/myhours/source/MyHoursSource.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-myhours/src/main/java/org/apache/seatunnel/connectors/seatunnel/myhours/source/MyHoursSource.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.myhours.source; +import org.apache.seatunnel.shade.com.google.common.base.Strings; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; @@ -36,7 +37,6 @@ import org.apache.seatunnel.connectors.seatunnel.myhours.source.exception.MyHoursConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.myhours.source.exception.MyHoursConnectorException; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import java.io.IOException; diff --git a/seatunnel-connectors-v2/connector-http/connector-http-wechat/src/main/java/org/apache/seatunnel/connectors/seatunnel/wechat/sink/WeChatSink.java b/seatunnel-connectors-v2/connector-http/connector-http-wechat/src/main/java/org/apache/seatunnel/connectors/seatunnel/wechat/sink/WeChatSink.java index f438167c39d..a3e910b620a 100644 --- a/seatunnel-connectors-v2/connector-http/connector-http-wechat/src/main/java/org/apache/seatunnel/connectors/seatunnel/wechat/sink/WeChatSink.java +++ b/seatunnel-connectors-v2/connector-http/connector-http-wechat/src/main/java/org/apache/seatunnel/connectors/seatunnel/wechat/sink/WeChatSink.java @@ -20,15 +20,17 @@ import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.sink.SinkWriter; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.seatunnel.http.sink.HttpSink; import org.apache.seatunnel.connectors.seatunnel.http.sink.HttpSinkWriter; import org.apache.seatunnel.connectors.seatunnel.wechat.sink.config.WeChatSinkConfig; +import java.util.Optional; + public class WeChatSink extends HttpSink { - public WeChatSink(Config pluginConfig, SeaTunnelRowType rowType) { - super(pluginConfig, rowType); + public WeChatSink(Config pluginConfig, CatalogTable catalogTable) { + super(pluginConfig, catalogTable); } @Override @@ -44,4 +46,9 @@ public HttpSinkWriter createWriter(SinkWriter.Context context) { new WeChatBotMessageSerializationSchema( new WeChatSinkConfig(pluginConfig), seaTunnelRowType)); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-hudi/pom.xml b/seatunnel-connectors-v2/connector-hudi/pom.xml index 35fc0b0459a..1a11d34f47e 100644 --- a/seatunnel-connectors-v2/connector-hudi/pom.xml +++ b/seatunnel-connectors-v2/connector-hudi/pom.xml @@ -102,4 +102,27 @@ + + + + maven-shade-plugin + + + + shade + + package + + + + org.apache.avro + ${seatunnel.shade.package}.${connector.name}.org.apache.avro + + + + + + + + diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalog.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalog.java index e0a25bfd85b..0d238c193d8 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalog.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalog.java @@ -35,6 +35,7 @@ import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hudi.avro.AvroSchemaUtils; import org.apache.hudi.common.model.HoodieAvroPayload; import org.apache.hudi.common.model.HoodieTableType; import org.apache.hudi.common.table.HoodieTableConfig; @@ -53,6 +54,7 @@ import java.util.stream.Collectors; import static org.apache.hbase.thirdparty.com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.CDC_ENABLED; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.RECORD_KEY_FIELDS; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.TABLE_TYPE; import static org.apache.seatunnel.connectors.seatunnel.hudi.sink.convert.AvroSchemaConverter.convertToSchema; @@ -195,6 +197,7 @@ public CatalogTable getTable(TablePath tablePath) String.join(",", tableConfig.getRecordKeyFields().get())); } options.put(TABLE_TYPE.key(), tableType.name()); + options.put(CDC_ENABLED.key(), String.valueOf(tableConfig.isCDCEnabled())); return CatalogTable.of( TableIdentifier.of( catalogName, tablePath.getDatabaseName(), tablePath.getTableName()), @@ -218,10 +221,16 @@ public void createTable(TablePath tablePath, CatalogTable table, boolean ignoreI .setTableType(table.getOptions().get(TABLE_TYPE.key())) .setRecordKeyFields(table.getOptions().get(RECORD_KEY_FIELDS.key())) .setTableCreateSchema( - convertToSchema(table.getSeaTunnelRowType()).toString()) + convertToSchema( + table.getSeaTunnelRowType(), + AvroSchemaUtils.getAvroRecordQualifiedName( + table.getTableId().getTableName())) + .toString()) .setTableName(tablePath.getTableName()) .setPartitionFields(String.join(",", table.getPartitionKeys())) .setPayloadClassName(HoodieAvroPayload.class.getName()) + .setCDCEnabled( + Boolean.parseBoolean(table.getOptions().get(CDC_ENABLED.key()))) .initTable(new HadoopStorageConfiguration(hadoopConf), tablePathStr); } } catch (IOException e) { diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiOptions.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiOptions.java index 38450e2dfdd..745e78eaf93 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiOptions.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiOptions.java @@ -44,12 +44,6 @@ public interface HudiOptions { .noDefaultValue() .withDescription("table_list"); - Option AUTO_COMMIT = - Options.key("auto_commit") - .booleanType() - .defaultValue(true) - .withDescription("auto commit"); - Option SCHEMA_SAVE_MODE = Options.key("schema_save_mode") .enumType(SchemaSaveMode.class) diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiSinkConfig.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiSinkConfig.java index 06650e87c03..bcb4efe77b8 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiSinkConfig.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiSinkConfig.java @@ -40,15 +40,12 @@ public class HudiSinkConfig implements Serializable { private String confFilesPath; - private boolean autoCommit; - private SchemaSaveMode schemaSaveMode; private DataSaveMode dataSaveMode; public static HudiSinkConfig of(ReadonlyConfig config) { Builder builder = HudiSinkConfig.builder(); - Optional optionalAutoCommit = config.getOptional(HudiOptions.AUTO_COMMIT); Optional optionalSchemaSaveMode = config.getOptional(HudiOptions.SCHEMA_SAVE_MODE); Optional optionalDataSaveMode = @@ -58,7 +55,6 @@ public static HudiSinkConfig of(ReadonlyConfig config) { builder.confFilesPath(config.get(HudiOptions.CONF_FILES_PATH)); builder.tableList(HudiTableConfig.of(config)); - builder.autoCommit(optionalAutoCommit.orElseGet(HudiOptions.AUTO_COMMIT::defaultValue)); builder.schemaSaveMode( optionalSchemaSaveMode.orElseGet(HudiOptions.SCHEMA_SAVE_MODE::defaultValue)); builder.dataSaveMode( diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableConfig.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableConfig.java index ba0ae33efdb..1ae612c9cb5 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableConfig.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableConfig.java @@ -40,6 +40,7 @@ import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.BATCH_INTERVAL_MS; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.BATCH_SIZE; +import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.CDC_ENABLED; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.DATABASE; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.INDEX_CLASS_NAME; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.INDEX_TYPE; @@ -108,6 +109,9 @@ public HudiTableConfig() {} @JsonProperty("max_commits_to_keep") private int maxCommitsToKeep; + @JsonProperty("cdc_enabled") + private boolean cdcEnabled; + public static List of(ReadonlyConfig connectorConfig) { List tableList; if (connectorConfig.getOptional(HudiOptions.TABLE_LIST).isPresent()) { @@ -132,6 +136,7 @@ public static List of(ReadonlyConfig connectorConfig) { connectorConfig.get(UPSERT_SHUFFLE_PARALLELISM)) .minCommitsToKeep(connectorConfig.get(MIN_COMMITS_TO_KEEP)) .maxCommitsToKeep(connectorConfig.get(MAX_COMMITS_TO_KEEP)) + .cdcEnabled(connectorConfig.get(CDC_ENABLED)) .build(); tableList = Collections.singletonList(hudiTableConfig); } diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableOptions.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableOptions.java index e48ef7be56e..2a2c7e01b35 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableOptions.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/config/HudiTableOptions.java @@ -46,6 +46,13 @@ public interface HudiTableOptions { .defaultValue(HoodieTableType.COPY_ON_WRITE) .withDescription("hudi table type"); + Option CDC_ENABLED = + Options.key("cdc_enabled") + .booleanType() + .defaultValue(false) + .withDescription( + "When enable, persist the change data if necessary, and can be queried as a CDC query mode."); + Option RECORD_KEY_FIELDS = Options.key("record_key_fields") .stringType() @@ -76,7 +83,7 @@ public interface HudiTableOptions { Options.key("record_byte_size") .intType() .defaultValue(1024) - .withDescription("auto commit"); + .withDescription("The byte size of each record"); Option OP_TYPE = Options.key("op_type") diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSink.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSink.java index 5bdc3b8c3ae..11a402ab101 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSink.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSink.java @@ -24,7 +24,6 @@ import org.apache.seatunnel.api.sink.DefaultSaveModeHandler; import org.apache.seatunnel.api.sink.SaveModeHandler; import org.apache.seatunnel.api.sink.SeaTunnelSink; -import org.apache.seatunnel.api.sink.SinkAggregatedCommitter; import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSink; import org.apache.seatunnel.api.sink.SupportSaveMode; @@ -38,14 +37,12 @@ import org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiSinkConfig; import org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableConfig; import org.apache.seatunnel.connectors.seatunnel.hudi.exception.HudiConnectorException; -import org.apache.seatunnel.connectors.seatunnel.hudi.sink.commiter.HudiSinkAggregatedCommitter; import org.apache.seatunnel.connectors.seatunnel.hudi.sink.state.HudiAggregatedCommitInfo; import org.apache.seatunnel.connectors.seatunnel.hudi.sink.state.HudiCommitInfo; import org.apache.seatunnel.connectors.seatunnel.hudi.sink.state.HudiSinkState; import org.apache.seatunnel.connectors.seatunnel.hudi.sink.writer.HudiSinkWriter; import java.io.IOException; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -82,15 +79,13 @@ public String getPluginName() { @Override public HudiSinkWriter createWriter(SinkWriter.Context context) throws IOException { - return new HudiSinkWriter( - context, seaTunnelRowType, hudiSinkConfig, hudiTableConfig, new ArrayList<>()); + return new HudiSinkWriter(context, seaTunnelRowType, hudiSinkConfig, hudiTableConfig); } @Override public SinkWriter restoreWriter( SinkWriter.Context context, List states) throws IOException { - return new HudiSinkWriter( - context, seaTunnelRowType, hudiSinkConfig, hudiTableConfig, states); + return SeaTunnelSink.super.restoreWriter(context, states); } @Override @@ -103,18 +98,6 @@ public Optional> getCommitInfoSerializer() { return Optional.of(new DefaultSerializer<>()); } - @Override - public Optional> - createAggregatedCommitter() throws IOException { - return Optional.of( - new HudiSinkAggregatedCommitter(hudiTableConfig, hudiSinkConfig, seaTunnelRowType)); - } - - @Override - public Optional> getAggregatedCommitInfoSerializer() { - return Optional.of(new DefaultSerializer<>()); - } - @Override public Optional getSaveModeHandler() { TablePath tablePath = @@ -143,4 +126,9 @@ public Optional getSaveModeHandler() { catalogTable, null)); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSinkFactory.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSinkFactory.java index ed21b15166a..7e6d9826d95 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSinkFactory.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/HudiSinkFactory.java @@ -37,12 +37,12 @@ import java.util.List; import java.util.Optional; -import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiOptions.AUTO_COMMIT; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiOptions.CONF_FILES_PATH; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiOptions.TABLE_DFS_PATH; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiOptions.TABLE_LIST; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.BATCH_INTERVAL_MS; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.BATCH_SIZE; +import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.CDC_ENABLED; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.INDEX_CLASS_NAME; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.INDEX_TYPE; import static org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableOptions.INSERT_SHUFFLE_PARALLELISM; @@ -85,7 +85,7 @@ public OptionRule optionRule() { UPSERT_SHUFFLE_PARALLELISM, MIN_COMMITS_TO_KEEP, MAX_COMMITS_TO_KEEP, - AUTO_COMMIT, + CDC_ENABLED, SinkCommonOptions.MULTI_TABLE_SINK_REPLICA) .build(); } @@ -121,6 +121,10 @@ public TableSink createSink(TableSinkFactoryContext context) { } // table type catalogTable.getOptions().put(TABLE_TYPE.key(), hudiTableConfig.getTableType().name()); + // cdc enabled + catalogTable + .getOptions() + .put(CDC_ENABLED.key(), String.valueOf(hudiTableConfig.isCdcEnabled())); catalogTable = CatalogTable.of( newTableId, diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/commiter/HudiSinkAggregatedCommitter.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/commiter/HudiSinkAggregatedCommitter.java deleted file mode 100644 index beba719c76d..00000000000 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/commiter/HudiSinkAggregatedCommitter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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. - */ - -package org.apache.seatunnel.connectors.seatunnel.hudi.sink.commiter; - -import org.apache.seatunnel.api.sink.SinkAggregatedCommitter; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiSinkConfig; -import org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableConfig; -import org.apache.seatunnel.connectors.seatunnel.hudi.sink.client.HudiWriteClientProvider; -import org.apache.seatunnel.connectors.seatunnel.hudi.sink.state.HudiAggregatedCommitInfo; -import org.apache.seatunnel.connectors.seatunnel.hudi.sink.state.HudiCommitInfo; - -import lombok.extern.slf4j.Slf4j; - -import java.io.IOException; -import java.util.List; -import java.util.Stack; -import java.util.stream.Collectors; - -@Slf4j -public class HudiSinkAggregatedCommitter - implements SinkAggregatedCommitter { - - private final HudiTableConfig tableConfig; - - private final HudiWriteClientProvider writeClientProvider; - - public HudiSinkAggregatedCommitter( - HudiTableConfig tableConfig, - HudiSinkConfig sinkConfig, - SeaTunnelRowType seaTunnelRowType) { - this.tableConfig = tableConfig; - this.writeClientProvider = - new HudiWriteClientProvider( - sinkConfig, tableConfig.getTableName(), seaTunnelRowType); - } - - @Override - public List commit( - List aggregatedCommitInfo) throws IOException { - aggregatedCommitInfo = - aggregatedCommitInfo.stream() - .filter( - commit -> - commit.getHudiCommitInfoList().stream() - .anyMatch( - aggregateCommit -> - !aggregateCommit - .getWriteStatusList() - .isEmpty() - && !writeClientProvider - .getOrCreateClient() - .commit( - aggregateCommit - .getWriteInstantTime(), - aggregateCommit - .getWriteStatusList()))) - .collect(Collectors.toList()); - log.debug( - "hudi records have been committed, error commit info are {}", aggregatedCommitInfo); - return aggregatedCommitInfo; - } - - @Override - public HudiAggregatedCommitInfo combine(List commitInfos) { - return new HudiAggregatedCommitInfo(commitInfos); - } - - @Override - public void abort(List aggregatedCommitInfo) throws Exception { - writeClientProvider.getOrCreateClient().rollbackFailedWrites(); - // rollback force commit - for (HudiAggregatedCommitInfo hudiAggregatedCommitInfo : aggregatedCommitInfo) { - for (HudiCommitInfo commitInfo : hudiAggregatedCommitInfo.getHudiCommitInfoList()) { - Stack forceCommitTime = commitInfo.getForceCommitTime(); - while (!forceCommitTime.isEmpty()) { - writeClientProvider.getOrCreateClient().rollback(forceCommitTime.pop()); - } - } - } - } - - @Override - public void close() { - writeClientProvider.close(); - } -} diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/AvroSchemaConverter.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/AvroSchemaConverter.java index addbf8491f9..acb10212757 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/AvroSchemaConverter.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/AvroSchemaConverter.java @@ -50,13 +50,13 @@ private AvroSchemaConverter() { * @return Avro's {@link Schema} matching this logical type. */ public static Schema convertToSchema(SeaTunnelDataType schema) { - return convertToSchema(schema, "org.apache.seatunnel.avro.generated.record"); + return convertToSchema(schema, "record"); } /** * Converts Seatunnel {@link SeaTunnelDataType} (can be nested) into an Avro schema. * - *

The "{rowName}_" is used as the nested row type name prefix in order to generate the right + *

The "{rowName}." is used as the nested row type name prefix in order to generate the right * schema. Nested record type that only differs with type name is still compatible. * * @param dataType logical type @@ -105,10 +105,15 @@ public static Schema convertToSchema(SeaTunnelDataType dataType, String rowNa return nullableSchema(time); case DECIMAL: DecimalType decimalType = (DecimalType) dataType; - // store BigDecimal as byte[] + // store BigDecimal as Fixed + // for spark compatibility. Schema decimal = LogicalTypes.decimal(decimalType.getPrecision(), decimalType.getScale()) - .addToSchema(SchemaBuilder.builder().bytesType()); + .addToSchema( + SchemaBuilder.fixed(String.format("%s.fixed", rowName)) + .size( + computeMinBytesForDecimalPrecision( + decimalType.getPrecision()))); return nullableSchema(decimal); case ROW: SeaTunnelRowType rowType = (SeaTunnelRowType) dataType; @@ -121,7 +126,7 @@ public static Schema convertToSchema(SeaTunnelDataType dataType, String rowNa SeaTunnelDataType fieldType = rowType.getFieldType(i); SchemaBuilder.GenericDefault fieldBuilder = builder.name(fieldName) - .type(convertToSchema(fieldType, rowName + "_" + fieldName)); + .type(convertToSchema(fieldType, rowName + "." + fieldName)); builder = fieldBuilder.withDefault(null); } @@ -166,4 +171,12 @@ public static SeaTunnelDataType extractValueTypeToAvroMap(SeaTunnelDataType convertRow( seaTunnelRowType.getFieldNames()[i], createConverter(seaTunnelRowType.getFieldType(i)) .convert( - convertToSchema(seaTunnelRowType.getFieldType(i)), + convertToSchema( + seaTunnelRowType.getFieldType(i), + AvroSchemaUtils.getAvroRecordQualifiedName( + hudiTableConfig.getTableName()) + + "." + + seaTunnelRowType.getFieldNames()[i]), element.getField(i))); } return new HoodieAvroRecord<>( diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/RowDataToAvroConverters.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/RowDataToAvroConverters.java index a48179fdb7a..5c063626693 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/RowDataToAvroConverters.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/convert/RowDataToAvroConverters.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.avro.Conversions; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericRecord; @@ -45,6 +46,8 @@ /** Tool class used to convert from {@link SeaTunnelRow} to Avro {@link GenericRecord}. */ public class RowDataToAvroConverters implements Serializable { + private static final Conversions.DecimalConversion DECIMAL_CONVERSION = + new Conversions.DecimalConversion(); // -------------------------------------------------------------------------------- // Runtime Converters // -------------------------------------------------------------------------------- @@ -166,8 +169,9 @@ public Object convert(Schema schema, Object object) { @Override public Object convert(Schema schema, Object object) { - return ByteBuffer.wrap( - ((BigDecimal) object).unscaledValue().toByteArray()); + BigDecimal javaDecimal = (BigDecimal) object; + return DECIMAL_CONVERSION.toFixed( + javaDecimal, schema, schema.getLogicalType()); } }; break; diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiAggregatedCommitInfo.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiAggregatedCommitInfo.java index 348a040be65..065fed72ad7 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiAggregatedCommitInfo.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiAggregatedCommitInfo.java @@ -17,15 +17,6 @@ package org.apache.seatunnel.connectors.seatunnel.hudi.sink.state; -import lombok.AllArgsConstructor; -import lombok.Data; - import java.io.Serializable; -import java.util.List; - -@Data -@AllArgsConstructor -public class HudiAggregatedCommitInfo implements Serializable { - private final List hudiCommitInfoList; -} +public class HudiAggregatedCommitInfo implements Serializable {} diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiCommitInfo.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiCommitInfo.java index 0357931bb08..808cc4d942a 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiCommitInfo.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/state/HudiCommitInfo.java @@ -17,22 +17,6 @@ package org.apache.seatunnel.connectors.seatunnel.hudi.sink.state; -import org.apache.hudi.client.WriteStatus; - -import lombok.AllArgsConstructor; -import lombok.Data; - import java.io.Serializable; -import java.util.List; -import java.util.Stack; - -@Data -@AllArgsConstructor -public class HudiCommitInfo implements Serializable { - - private final String writeInstantTime; - - private final List writeStatusList; - private final Stack forceCommitTime; -} +public class HudiCommitInfo implements Serializable {} diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiRecordWriter.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiRecordWriter.java index 7eb3ab546b7..b98e2228707 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiRecordWriter.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiRecordWriter.java @@ -17,23 +17,23 @@ package org.apache.seatunnel.connectors.seatunnel.hudi.sink.writer; +import org.apache.seatunnel.api.table.type.RowKind; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; -import org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiSinkConfig; import org.apache.seatunnel.connectors.seatunnel.hudi.config.HudiTableConfig; import org.apache.seatunnel.connectors.seatunnel.hudi.exception.HudiConnectorException; import org.apache.seatunnel.connectors.seatunnel.hudi.exception.HudiErrorCode; import org.apache.seatunnel.connectors.seatunnel.hudi.sink.client.WriteClientProvider; import org.apache.seatunnel.connectors.seatunnel.hudi.sink.convert.HudiRecordConverter; -import org.apache.seatunnel.connectors.seatunnel.hudi.sink.state.HudiCommitInfo; import org.apache.avro.Schema; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.hudi.avro.AvroSchemaUtils; import org.apache.hudi.client.HoodieJavaWriteClient; -import org.apache.hudi.client.WriteStatus; import org.apache.hudi.common.model.HoodieAvroPayload; +import org.apache.hudi.common.model.HoodieKey; import org.apache.hudi.common.model.HoodieRecord; -import org.apache.hudi.common.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,10 +42,10 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Stack; +import java.util.Map; +import java.util.Set; import static org.apache.seatunnel.connectors.seatunnel.hudi.sink.convert.AvroSchemaConverter.convertToSchema; @@ -64,60 +64,44 @@ public class HudiRecordWriter implements Serializable { private final SeaTunnelRowType seaTunnelRowType; - private final boolean autoCommit; - private Schema schema; private transient int batchCount = 0; private final List> writeRecords; - private Stack forceCommitTime; - - private String writeInstantTime; + private final List deleteRecordKeys; - private List writeStatusList; + private final LinkedHashMap>> buffer = + new LinkedHashMap<>(); private transient volatile boolean closed = false; private transient volatile Exception flushException; public HudiRecordWriter( - HudiSinkConfig hudiSinkConfig, HudiTableConfig hudiTableConfig, WriteClientProvider clientProvider, SeaTunnelRowType seaTunnelRowType) { this.hudiTableConfig = hudiTableConfig; - this.autoCommit = hudiSinkConfig.isAutoCommit(); this.clientProvider = clientProvider; this.seaTunnelRowType = seaTunnelRowType; this.writeRecords = new ArrayList<>(); - this.writeStatusList = new ArrayList<>(); - this.forceCommitTime = new Stack<>(); + this.deleteRecordKeys = new ArrayList<>(); this.recordConverter = new HudiRecordConverter(); } - public HudiRecordWriter( - HudiSinkConfig sinkConfig, - HudiTableConfig tableConfig, - WriteClientProvider writeClientProvider, - SeaTunnelRowType seaTunnelRowType, - HudiCommitInfo hudiCommitInfo) { - this(sinkConfig, tableConfig, writeClientProvider, seaTunnelRowType); - this.writeInstantTime = hudiCommitInfo.getWriteInstantTime(); - this.writeStatusList = hudiCommitInfo.getWriteStatusList(); - } - public void open() { - this.schema = new Schema.Parser().parse(convertToSchema(seaTunnelRowType).toString()); + this.schema = + new Schema.Parser() + .parse( + convertToSchema( + seaTunnelRowType, + AvroSchemaUtils.getAvroRecordQualifiedName( + hudiTableConfig.getTableName())) + .toString()); try { - HoodieJavaWriteClient writeClient = - clientProvider.getOrCreateClient(); - if (StringUtils.nonEmpty(writeInstantTime) && Objects.nonNull(writeStatusList)) { - if (!writeClient.commit(writeInstantTime, writeStatusList)) { - LOG.warn("Failed to commit history data."); - } - } + clientProvider.getOrCreateClient(); } catch (Exception e) { throw new HudiConnectorException( CommonErrorCodeDeprecated.WRITER_OPERATION_FAILED, @@ -133,7 +117,7 @@ public void writeRecord(SeaTunnelRow record) { batchCount++; if (hudiTableConfig.getBatchSize() > 0 && batchCount >= hudiTableConfig.getBatchSize()) { - flush(true); + flush(); } } catch (Exception e) { throw new HudiConnectorException( @@ -143,92 +127,89 @@ public void writeRecord(SeaTunnelRow record) { } } - public synchronized void flush(boolean isNeedForceCommit) { + public synchronized void flush() { if (batchCount == 0) { log.debug("No data needs to be refreshed, waiting for incoming data."); return; } checkFlushException(); - HoodieJavaWriteClient writeClient = clientProvider.getOrCreateClient(); - if (autoCommit || writeInstantTime == null) { - writeInstantTime = writeClient.startCommit(); + Boolean preChangeFlag = null; + Set>>> entries = + buffer.entrySet(); + for (Map.Entry>> entry : entries) { + boolean currentChangeFlag = entry.getValue().getKey(); + if (currentChangeFlag) { + if (preChangeFlag != null && !preChangeFlag) { + executeDelete(); + } + writeRecords.add(entry.getValue().getValue()); + } else { + if (preChangeFlag != null && preChangeFlag) { + executeWrite(); + } + deleteRecordKeys.add(entry.getKey()); + } + preChangeFlag = currentChangeFlag; } - List currentWriteStatusList; + + if (preChangeFlag != null) { + if (preChangeFlag) { + executeWrite(); + } else { + executeDelete(); + } + } + batchCount = 0; + buffer.clear(); + } + + private void executeWrite() { + HoodieJavaWriteClient writeClient = clientProvider.getOrCreateClient(); + String writeInstantTime = writeClient.startCommit(); // write records switch (hudiTableConfig.getOpType()) { case INSERT: - currentWriteStatusList = writeClient.insert(writeRecords, writeInstantTime); + writeClient.insert(writeRecords, writeInstantTime); break; case UPSERT: - currentWriteStatusList = writeClient.upsert(writeRecords, writeInstantTime); + writeClient.upsert(writeRecords, writeInstantTime); break; case BULK_INSERT: - currentWriteStatusList = writeClient.bulkInsert(writeRecords, writeInstantTime); + writeClient.bulkInsert(writeRecords, writeInstantTime); break; default: throw new HudiConnectorException( HudiErrorCode.UNSUPPORTED_OPERATION, "Unsupported operation type: " + hudiTableConfig.getOpType()); } - if (!autoCommit) { - this.writeStatusList.addAll(currentWriteStatusList); - } - /** - * when the batch size of temporary records is reached, commit is forced here, even if - * configured not to be auto commit. because a timeline supports only one commit. - */ - forceCommit(isNeedForceCommit, autoCommit); writeRecords.clear(); - batchCount = 0; - } - - public Optional prepareCommit() { - flush(false); - if (!autoCommit) { - return Optional.of( - new HudiCommitInfo(writeInstantTime, writeStatusList, forceCommitTime)); - } - return Optional.empty(); - } - - private void commit() { - if (StringUtils.nonEmpty(writeInstantTime) && !writeStatusList.isEmpty()) { - log.debug( - "Commit hudi records, the instant time is {} and write status are {}", - writeInstantTime, - writeStatusList); - clientProvider.getOrCreateClient().commit(writeInstantTime, writeStatusList); - resetUpsertCommitInfo(); - } - } - - private void forceCommit(boolean isNeedForceCommit, boolean isAutoCommit) { - if (isNeedForceCommit && !isAutoCommit) { - clientProvider.getOrCreateClient().commit(writeInstantTime, writeStatusList); - forceCommitTime.add(writeInstantTime); - resetUpsertCommitInfo(); - } } - public HudiCommitInfo snapshotState() { - HudiCommitInfo hudiCommitInfo = - new HudiCommitInfo(writeInstantTime, writeStatusList, forceCommitTime); - // reset commit info in here, because the commit info will be committed in committer. - resetUpsertCommitInfo(); - // reset the force commit stack. - forceCommitTime = new Stack<>(); - return hudiCommitInfo; - } - - protected void resetUpsertCommitInfo() { - writeInstantTime = null; - writeStatusList = new ArrayList<>(); + private void executeDelete() { + HoodieJavaWriteClient writeClient = clientProvider.getOrCreateClient(); + writeClient.delete(deleteRecordKeys, writeClient.startCommit()); + deleteRecordKeys.clear(); } protected void prepareRecords(SeaTunnelRow element) { HoodieRecord hoodieAvroPayloadHoodieRecord = recordConverter.convertRow(schema, seaTunnelRowType, element, hudiTableConfig); - writeRecords.add(hoodieAvroPayloadHoodieRecord); + HoodieKey recordKey = hoodieAvroPayloadHoodieRecord.getKey(); + boolean changeFlag = changeFlag(element.getRowKind()); + buffer.put(recordKey, Pair.of(changeFlag, hoodieAvroPayloadHoodieRecord)); + } + + private boolean changeFlag(RowKind rowKind) { + switch (rowKind) { + case DELETE: + case UPDATE_BEFORE: + return false; + case INSERT: + case UPDATE_AFTER: + return true; + default: + throw new UnsupportedOperationException("Unknown row kind: " + rowKind); + } } protected void checkFlushException() { @@ -245,10 +226,7 @@ public synchronized void close() { if (!closed) { closed = true; try { - flush(false); - if (!autoCommit) { - commit(); - } + flush(); } catch (Exception e) { LOG.warn("Flush records to Hudi failed.", e); flushException = diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiSinkWriter.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiSinkWriter.java index 317215861a2..130a79adab5 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiSinkWriter.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/sink/writer/HudiSinkWriter.java @@ -35,8 +35,6 @@ import lombok.extern.slf4j.Slf4j; import java.io.IOException; -import java.util.Collections; -import java.util.List; import java.util.Optional; @Slf4j @@ -60,27 +58,15 @@ public HudiSinkWriter( Context context, SeaTunnelRowType seaTunnelRowType, HudiSinkConfig sinkConfig, - HudiTableConfig tableConfig, - List hudiSinkState) { + HudiTableConfig tableConfig) { this.sinkConfig = sinkConfig; this.tableConfig = tableConfig; this.seaTunnelRowType = seaTunnelRowType; this.writeClientProvider = new HudiWriteClientProvider( sinkConfig, tableConfig.getTableName(), seaTunnelRowType); - if (!hudiSinkState.isEmpty()) { - this.hudiRecordWriter = - new HudiRecordWriter( - sinkConfig, - tableConfig, - writeClientProvider, - seaTunnelRowType, - hudiSinkState.get(0).getHudiCommitInfo()); - } else { - this.hudiRecordWriter = - new HudiRecordWriter( - sinkConfig, tableConfig, writeClientProvider, seaTunnelRowType); - } + this.hudiRecordWriter = + new HudiRecordWriter(tableConfig, writeClientProvider, seaTunnelRowType); } @Override @@ -89,16 +75,11 @@ public void write(SeaTunnelRow element) throws IOException { hudiRecordWriter.writeRecord(element); } - @Override - public List snapshotState(long checkpointId) throws IOException { - return Collections.singletonList( - new HudiSinkState(checkpointId, hudiRecordWriter.snapshotState())); - } - @Override public Optional prepareCommit() throws IOException { tryOpen(); - return hudiRecordWriter.prepareCommit(); + hudiRecordWriter.flush(); + return Optional.empty(); } @Override @@ -128,8 +109,7 @@ public void setMultiTableResourceManager( queueIndex, tableConfig.getTableName()); this.hudiRecordWriter = - new HudiRecordWriter( - sinkConfig, tableConfig, writeClientProvider, seaTunnelRowType); + new HudiRecordWriter(tableConfig, writeClientProvider, seaTunnelRowType); } private void tryOpen() { diff --git a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/util/HudiUtil.java b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/util/HudiUtil.java index fe6cbe3e206..ef49c28a213 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/util/HudiUtil.java +++ b/seatunnel-connectors-v2/connector-hudi/src/main/java/org/apache/seatunnel/connectors/seatunnel/hudi/util/HudiUtil.java @@ -31,6 +31,7 @@ import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hudi.avro.AvroSchemaUtils; import org.apache.hudi.client.HoodieJavaWriteClient; import org.apache.hudi.client.common.HoodieJavaEngineContext; import org.apache.hudi.common.config.HoodieStorageConfig; @@ -173,7 +174,12 @@ public static HoodieJavaWriteClient createHoodieJavaWriteClie hudiSinkConfig.getTableDfsPath(), hudiTable.getDatabase(), hudiTable.getTableName())) - .withSchema(convertToSchema(seaTunnelRowType).toString()) + .withSchema( + convertToSchema( + seaTunnelRowType, + AvroSchemaUtils.getAvroRecordQualifiedName( + tableName)) + .toString()) .withParallelism( hudiTable.getInsertShuffleParallelism(), hudiTable.getUpsertShuffleParallelism()) @@ -184,7 +190,6 @@ public static HoodieJavaWriteClient createHoodieJavaWriteClie hudiTable.getMinCommitsToKeep(), hudiTable.getMaxCommitsToKeep()) .build()) - .withAutoCommit(hudiSinkConfig.isAutoCommit()) .withCleanConfig( HoodieCleanConfig.newBuilder() .withAutoClean(true) diff --git a/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/HudiTest.java b/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/HudiTest.java index 82e85fcf4e2..7dbfc402b6f 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/HudiTest.java +++ b/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/HudiTest.java @@ -17,6 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.hudi; +import org.apache.seatunnel.api.table.type.DecimalType; import org.apache.seatunnel.api.table.type.LocalTimeType; import org.apache.seatunnel.api.table.type.MapType; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; @@ -27,6 +28,7 @@ import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericRecord; import org.apache.hadoop.conf.Configuration; +import org.apache.hudi.avro.AvroSchemaUtils; import org.apache.hudi.client.HoodieJavaWriteClient; import org.apache.hudi.client.WriteStatus; import org.apache.hudi.client.common.HoodieJavaEngineContext; @@ -52,6 +54,7 @@ import org.junit.jupiter.api.io.TempDir; import java.io.IOException; +import java.math.BigDecimal; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalTime; @@ -95,7 +98,8 @@ public class HudiTest { "date", "time", "timestamp3", - "map" + "map", + "decimal" }, new SeaTunnelDataType[] { BOOLEAN_TYPE, @@ -107,16 +111,19 @@ public class HudiTest { LocalTimeType.LOCAL_TIME_TYPE, LocalTimeType.LOCAL_DATE_TIME_TYPE, new MapType(STRING_TYPE, LONG_TYPE), + new DecimalType(10, 5), }); private String getSchema() { - return convertToSchema(seaTunnelRowType).toString(); + return convertToSchema( + seaTunnelRowType, AvroSchemaUtils.getAvroRecordQualifiedName(tableName)) + .toString(); } @Test void testSchema() { Assertions.assertEquals( - "{\"type\":\"record\",\"name\":\"record\",\"namespace\":\"org.apache.seatunnel.avro.generated\",\"fields\":[{\"name\":\"bool\",\"type\":[\"null\",\"boolean\"],\"default\":null},{\"name\":\"int\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"longValue\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"float\",\"type\":[\"null\",\"float\"],\"default\":null},{\"name\":\"name\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"date\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}],\"default\":null},{\"name\":\"time\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"time-millis\"}],\"default\":null},{\"name\":\"timestamp3\",\"type\":[\"null\",{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}],\"default\":null},{\"name\":\"map\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",\"long\"]}],\"default\":null}]}", + "{\"type\":\"record\",\"name\":\"hudi_record\",\"namespace\":\"hoodie.hudi\",\"fields\":[{\"name\":\"bool\",\"type\":[\"null\",\"boolean\"],\"default\":null},{\"name\":\"int\",\"type\":[\"null\",\"int\"],\"default\":null},{\"name\":\"longValue\",\"type\":[\"null\",\"long\"],\"default\":null},{\"name\":\"float\",\"type\":[\"null\",\"float\"],\"default\":null},{\"name\":\"name\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"date\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"date\"}],\"default\":null},{\"name\":\"time\",\"type\":[\"null\",{\"type\":\"int\",\"logicalType\":\"time-millis\"}],\"default\":null},{\"name\":\"timestamp3\",\"type\":[\"null\",{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}],\"default\":null},{\"name\":\"map\",\"type\":[\"null\",{\"type\":\"map\",\"values\":[\"null\",\"long\"]}],\"default\":null},{\"name\":\"decimal\",\"type\":[\"null\",{\"type\":\"fixed\",\"name\":\"fixed\",\"namespace\":\"hoodie.hudi.hudi_record.decimal\",\"size\":5,\"logicalType\":\"decimal\",\"precision\":10,\"scale\":5}],\"default\":null}]}", getSchema()); } @@ -165,7 +172,8 @@ void testWriteData() throws IOException { expected.setField(7, timestamp3.toLocalDateTime()); Map map = new HashMap<>(); map.put("element", 123L); - expected.setField(9, map); + expected.setField(8, map); + expected.setField(9, BigDecimal.valueOf(10.121)); String instantTime = javaWriteClient.startCommit(); List> hoodieRecords = new ArrayList<>(); hoodieRecords.add(convertRow(expected)); @@ -178,13 +186,23 @@ void testWriteData() throws IOException { private HoodieRecord convertRow(SeaTunnelRow element) { GenericRecord rec = new GenericData.Record( - new Schema.Parser().parse(convertToSchema(seaTunnelRowType).toString())); + new Schema.Parser() + .parse( + convertToSchema( + seaTunnelRowType, + AvroSchemaUtils.getAvroRecordQualifiedName( + tableName)) + .toString())); for (int i = 0; i < seaTunnelRowType.getTotalFields(); i++) { rec.put( seaTunnelRowType.getFieldNames()[i], createConverter(seaTunnelRowType.getFieldType(i)) .convert( - convertToSchema(seaTunnelRowType.getFieldType(i)), + convertToSchema( + seaTunnelRowType.getFieldType(i), + AvroSchemaUtils.getAvroRecordQualifiedName(tableName) + + "." + + seaTunnelRowType.getFieldNames()[i]), element.getField(i))); } diff --git a/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalogTest.java b/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalogTest.java index 7be81e89ba8..d3524c85c41 100644 --- a/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalogTest.java +++ b/seatunnel-connectors-v2/connector-hudi/src/test/java/org/apache/seatunnel/connectors/seatunnel/hudi/catalog/HudiCatalogTest.java @@ -168,6 +168,7 @@ CatalogTable buildAllTypesTable(TableIdentifier tableIdentifier) { TableSchema schema = builder.build(); HashMap options = new HashMap<>(); options.put("record_key_fields", "id,boolean_col"); + options.put("cdc_enabled", "false"); options.put("table_type", "MERGE_ON_READ"); return CatalogTable.of( tableIdentifier, schema, options, Collections.singletonList("dt_col"), "null"); diff --git a/seatunnel-connectors-v2/connector-iceberg/pom.xml b/seatunnel-connectors-v2/connector-iceberg/pom.xml index 309900b09c7..fd8efba2456 100644 --- a/seatunnel-connectors-v2/connector-iceberg/pom.xml +++ b/seatunnel-connectors-v2/connector-iceberg/pom.xml @@ -53,6 +53,11 @@ connector-common ${project.version} + + com.github.jsqlparser + jsqlparser + ${jsqlparser.version} + org.apache.iceberg @@ -244,6 +249,11 @@ ${seatunnel.shade.package}.${connector.name}.shaded.parquet + + net.sf.jsqlparser + + ${seatunnel.shade.package}.${connector.name}.net.sf.jsqlparser + diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/IcebergTableLoader.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/IcebergTableLoader.java index 5e396047402..7fa5c2380d1 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/IcebergTableLoader.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/IcebergTableLoader.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.iceberg; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.connectors.seatunnel.iceberg.config.CommonConfig; @@ -27,7 +29,6 @@ import org.apache.iceberg.catalog.Namespace; import org.apache.iceberg.catalog.TableIdentifier; -import com.google.common.annotations.VisibleForTesting; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalog.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalog.java index 60591d9893c..ee87eba7aa8 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalog.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalog.java @@ -34,6 +34,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.connectors.seatunnel.iceberg.IcebergCatalogLoader; import org.apache.seatunnel.connectors.seatunnel.iceberg.config.CommonConfig; +import org.apache.seatunnel.connectors.seatunnel.iceberg.utils.ExpressionUtils; import org.apache.seatunnel.connectors.seatunnel.iceberg.utils.SchemaUtils; import org.apache.iceberg.PartitionField; @@ -44,9 +45,13 @@ import org.apache.iceberg.catalog.SupportsNamespaces; import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.exceptions.NoSuchTableException; +import org.apache.iceberg.expressions.Expression; import org.apache.iceberg.types.Types; import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.delete.Delete; import java.io.Closeable; import java.io.IOException; @@ -57,12 +62,14 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static com.google.common.base.Preconditions.checkArgument; import static org.apache.seatunnel.connectors.seatunnel.iceberg.utils.SchemaUtils.toIcebergTableIdentifier; import static org.apache.seatunnel.connectors.seatunnel.iceberg.utils.SchemaUtils.toTablePath; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkArgument; @Slf4j public class IcebergCatalog implements Catalog { + public static final String PROPS_TABLE_COMMENT = "comment"; + private final String catalogName; private final ReadonlyConfig readonlyConfig; private final IcebergCatalogLoader icebergCatalogLoader; @@ -210,7 +217,39 @@ public boolean isExistsData(TablePath tablePath) { @Override public void executeSql(TablePath tablePath, String sql) { - throw new UnsupportedOperationException("Does not support executing custom SQL"); + Delete delete; + try { + Statement statement = CCJSqlParserUtil.parse(sql); + delete = (Delete) statement; + } catch (Throwable e) { + throw new IllegalArgumentException( + "Only support sql: delete from ... where ..., Not support: " + sql, e); + } + + TablePath targetTablePath = TablePath.of(delete.getTable().getFullyQualifiedName(), false); + if (targetTablePath.getDatabaseName() == null) { + targetTablePath = + TablePath.of(tablePath.getDatabaseName(), targetTablePath.getTableName()); + } + if (!targetTablePath.equals(tablePath)) { + log.warn( + "The delete table {} is not equal to the target table {}", + targetTablePath, + tablePath); + } + + TableIdentifier icebergTableIdentifier = toIcebergTableIdentifier(targetTablePath); + Table table = catalog.loadTable(icebergTableIdentifier); + Expression expression = ExpressionUtils.convert(delete.getWhere(), table.schema()); + catalog.loadTable(icebergTableIdentifier) + .newDelete() + .deleteFromRowFilter(expression) + .commit(); + log.info( + "Delete table {} data success, sql [{}] to deleteFromRowFilter: {}", + targetTablePath, + sql, + expression); } public void truncateTable(TablePath tablePath, boolean ignoreIfNotExists) @@ -257,14 +296,17 @@ public CatalogTable toCatalogTable(Table icebergTable, TablePath tablePath) { icebergTable.spec().fields().stream() .map(PartitionField::name) .collect(Collectors.toList()); - + String comment = + Optional.ofNullable(icebergTable.properties()) + .map(e -> e.get(PROPS_TABLE_COMMENT)) + .orElse(null); return CatalogTable.of( org.apache.seatunnel.api.table.catalog.TableIdentifier.of( catalogName, tablePath.getDatabaseName(), tablePath.getTableName()), builder.build(), icebergTable.properties(), partitionKeys, - null, + comment, catalogName); } diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/config/SinkConfig.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/config/SinkConfig.java index 0c0dc1b138c..7f17510aa9c 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/config/SinkConfig.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/config/SinkConfig.java @@ -109,6 +109,12 @@ public class SinkConfig extends CommonConfig { .defaultValue(DataSaveMode.APPEND_DATA) .withDescription("data save mode"); + public static final Option DATA_SAVE_MODE_CUSTOM_SQL = + Options.key("custom_sql") + .stringType() + .noDefaultValue() + .withDescription("custom delete data sql for data save mode"); + public static final Option TABLES_DEFAULT_COMMIT_BRANCH = Options.key("iceberg.table.commit-branch") .stringType() @@ -128,6 +134,7 @@ public class SinkConfig extends CommonConfig { private boolean tableSchemaEvolutionEnabled; private SchemaSaveMode schemaSaveMode; private DataSaveMode dataSaveMode; + private String dataSaveModeSQL; public SinkConfig(ReadonlyConfig readonlyConfig) { super(readonlyConfig); @@ -140,6 +147,7 @@ public SinkConfig(ReadonlyConfig readonlyConfig) { this.tableSchemaEvolutionEnabled = readonlyConfig.get(TABLE_SCHEMA_EVOLUTION_ENABLED_PROP); this.schemaSaveMode = readonlyConfig.get(SCHEMA_SAVE_MODE); this.dataSaveMode = readonlyConfig.get(DATA_SAVE_MODE); + this.dataSaveModeSQL = readonlyConfig.get(DATA_SAVE_MODE_CUSTOM_SQL); this.commitBranch = readonlyConfig.get(TABLES_DEFAULT_COMMIT_BRANCH); } diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/data/RowConverter.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/data/RowConverter.java index f46928456fb..11a20795217 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/data/RowConverter.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/data/RowConverter.java @@ -19,6 +19,7 @@ package org.apache.seatunnel.connectors.seatunnel.iceberg.data; +import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper; import org.apache.seatunnel.shade.com.google.common.base.Preconditions; import org.apache.seatunnel.shade.com.google.common.collect.Maps; @@ -41,8 +42,6 @@ import org.apache.iceberg.types.Types; import org.apache.iceberg.util.DateTimeUtil; -import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.IOException; import java.io.UncheckedIOException; import java.math.BigDecimal; diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSink.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSink.java index 417d92ac491..f327c3ae50d 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSink.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSink.java @@ -28,9 +28,11 @@ import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSink; import org.apache.seatunnel.api.sink.SupportSaveMode; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSink; import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.factory.CatalogFactory; +import org.apache.seatunnel.api.table.schema.SchemaChangeType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.common.constants.PluginType; import org.apache.seatunnel.connectors.seatunnel.iceberg.config.SinkConfig; @@ -41,6 +43,7 @@ import org.apache.seatunnel.connectors.seatunnel.iceberg.sink.state.IcebergSinkState; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -54,7 +57,8 @@ public class IcebergSink IcebergCommitInfo, IcebergAggregatedCommitInfo>, SupportSaveMode, - SupportMultiTableSink { + SupportMultiTableSink, + SupportSchemaEvolutionSink { private static String PLUGIN_NAME = "Iceberg"; private final SinkConfig config; private final ReadonlyConfig readonlyConfig; @@ -131,6 +135,20 @@ public Optional getSaveModeHandler() { config.getDataSaveMode(), catalog, catalogTable, - null)); + config.getDataSaveModeSQL())); + } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } + + @Override + public List supports() { + return Arrays.asList( + SchemaChangeType.ADD_COLUMN, + SchemaChangeType.DROP_COLUMN, + SchemaChangeType.RENAME_COLUMN, + SchemaChangeType.UPDATE_COLUMN); } } diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkFactory.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkFactory.java index 47cfa331087..5c3c87d82c0 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkFactory.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkFactory.java @@ -19,6 +19,7 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.sink.DataSaveMode; import org.apache.seatunnel.api.sink.SinkCommonOptions; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TableIdentifier; @@ -56,6 +57,8 @@ public OptionRule optionRule() { SinkConfig.KERBEROS_KEYTAB_PATH, SinkConfig.KRB5_PATH, SinkConfig.WRITE_PROPS, + SinkConfig.SCHEMA_SAVE_MODE, + SinkConfig.DATA_SAVE_MODE, SinkConfig.AUTO_CREATE_PROPS, SinkConfig.TABLE_PRIMARY_KEYS, SinkConfig.TABLE_DEFAULT_PARTITION_KEYS, @@ -63,6 +66,10 @@ public OptionRule optionRule() { SinkConfig.TABLE_SCHEMA_EVOLUTION_ENABLED_PROP, SinkConfig.TABLES_DEFAULT_COMMIT_BRANCH, SinkCommonOptions.MULTI_TABLE_SINK_REPLICA) + .conditional( + SinkConfig.DATA_SAVE_MODE, + DataSaveMode.CUSTOM_PROCESSING, + SinkConfig.DATA_SAVE_MODE_CUSTOM_SQL) .build(); } diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkWriter.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkWriter.java index 6f316269138..2175080ac7f 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkWriter.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/IcebergSinkWriter.java @@ -19,13 +19,16 @@ package org.apache.seatunnel.connectors.seatunnel.iceberg.sink; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSinkWriter; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSinkWriter; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TableSchema; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; -import org.apache.seatunnel.api.table.event.handler.DataTypeChangeEventDispatcher; -import org.apache.seatunnel.api.table.event.handler.DataTypeChangeEventHandler; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.handler.DataTypeChangeEventDispatcher; +import org.apache.seatunnel.api.table.schema.handler.DataTypeChangeEventHandler; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.iceberg.IcebergTableLoader; @@ -39,7 +42,6 @@ import org.apache.commons.lang3.StringUtils; -import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -53,7 +55,8 @@ @Slf4j public class IcebergSinkWriter implements SinkWriter, - SupportMultiTableSinkWriter { + SupportMultiTableSinkWriter, + SupportSchemaEvolutionSinkWriter { private TableSchema tableSchema; private SeaTunnelRowType rowType; private final SinkConfig config; diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/commit/IcebergAggregatedCommitter.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/commit/IcebergAggregatedCommitter.java index dcd1e6201f2..008289690a9 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/commit/IcebergAggregatedCommitter.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/commit/IcebergAggregatedCommitter.java @@ -36,7 +36,7 @@ public class IcebergAggregatedCommitter private final IcebergFilesCommitter filesCommitter; public IcebergAggregatedCommitter(SinkConfig config, CatalogTable catalogTable) { - IcebergTableLoader tableLoader = IcebergTableLoader.create(config, catalogTable).open(); + IcebergTableLoader tableLoader = IcebergTableLoader.create(config, catalogTable); this.filesCommitter = IcebergFilesCommitter.of(config, tableLoader); } @@ -51,7 +51,8 @@ public List commit( private void commitFiles(List commitInfos) { for (IcebergCommitInfo icebergCommitInfo : commitInfos) { - if (icebergCommitInfo.getResults() == null) { + if (icebergCommitInfo.getResults() == null + || icebergCommitInfo.getResults().isEmpty()) { continue; } filesCommitter.doCommit(icebergCommitInfo.getResults()); diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/IcebergRecordWriter.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/IcebergRecordWriter.java index ce83a8641ec..9c8949ba1db 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/IcebergRecordWriter.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/IcebergRecordWriter.java @@ -22,11 +22,11 @@ import org.apache.seatunnel.shade.com.google.common.collect.Lists; import org.apache.seatunnel.api.table.catalog.Column; -import org.apache.seatunnel.api.table.event.AlterTableAddColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableChangeColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableDropColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableModifyColumnEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.iceberg.config.SinkConfig; diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/RecordWriter.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/RecordWriter.java index 8c093db9866..fb37cccc1f6 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/RecordWriter.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/sink/writer/RecordWriter.java @@ -20,7 +20,7 @@ import org.apache.seatunnel.shade.com.google.common.collect.ImmutableList; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/source/reader/IcebergFileScanTaskReader.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/source/reader/IcebergFileScanTaskReader.java index e178dc481ed..fe42b33be22 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/source/reader/IcebergFileScanTaskReader.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/source/reader/IcebergFileScanTaskReader.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.iceberg.source.reader; +import org.apache.seatunnel.shade.com.google.common.collect.Sets; + import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.connectors.seatunnel.iceberg.data.IcebergRecordProjection; import org.apache.seatunnel.connectors.seatunnel.iceberg.exception.IcebergConnectorException; @@ -44,7 +46,6 @@ import org.apache.iceberg.types.TypeUtil; import org.apache.iceberg.util.PartitionUtil; -import com.google.common.collect.Sets; import lombok.Builder; import lombok.NonNull; diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/ExpressionUtils.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/ExpressionUtils.java new file mode 100644 index 00000000000..7f9b6a10273 --- /dev/null +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/ExpressionUtils.java @@ -0,0 +1,254 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.iceberg.utils; + +import org.apache.iceberg.expressions.Expression; +import org.apache.iceberg.expressions.Expressions; +import org.apache.iceberg.types.Types; +import org.apache.iceberg.util.DateTimeUtil; + +import lombok.SneakyThrows; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.DoubleValue; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.GreaterThan; +import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.IsBooleanExpression; +import net.sf.jsqlparser.expression.operators.relational.IsNullExpression; +import net.sf.jsqlparser.expression.operators.relational.MinorThan; +import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals; +import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.delete.Delete; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.List; +import java.util.stream.Collectors; + +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME; + +public class ExpressionUtils { + private static final DateTimeFormatter LOCAL_DATE_TIME_FORMATTER = + new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO_LOCAL_DATE) + .appendLiteral(' ') + .append(ISO_LOCAL_TIME) + .toFormatter(); + + public static Expression convertDeleteSQL(String sql) throws JSQLParserException { + Statement statement = CCJSqlParserUtil.parse(sql); + Delete delete = (Delete) statement; + return convert(delete.getWhere(), null); + } + + public static Expression convert(net.sf.jsqlparser.expression.Expression condition) { + return convert(condition, null); + } + + public static Expression convert( + net.sf.jsqlparser.expression.Expression condition, org.apache.iceberg.Schema schema) { + if (condition == null) { + return Expressions.alwaysTrue(); + } + + if (condition instanceof AndExpression) { + return Expressions.and( + convert(((AndExpression) condition).getLeftExpression(), schema), + convert(((AndExpression) condition).getRightExpression(), schema)); + } + if (condition instanceof OrExpression) { + return Expressions.or( + convert(((OrExpression) condition).getLeftExpression(), schema), + convert(((OrExpression) condition).getRightExpression(), schema)); + } + if (condition instanceof Parenthesis) { + return convert(((Parenthesis) condition).getExpression(), schema); + } + + if (condition instanceof EqualsTo) { + EqualsTo equalsTo = (EqualsTo) condition; + Column column = (Column) equalsTo.getLeftExpression(); + Object value = + schema == null + ? convertValueExpression(equalsTo.getRightExpression()) + : convertValueExpression( + equalsTo.getRightExpression(), + schema.findField(column.getColumnName())); + return Expressions.equal(column.getColumnName(), value); + } + if (condition instanceof NotEqualsTo) { + NotEqualsTo notEqualsTo = (NotEqualsTo) condition; + Column column = (Column) notEqualsTo.getLeftExpression(); + Object value = + schema == null + ? convertValueExpression(notEqualsTo.getRightExpression()) + : convertValueExpression( + notEqualsTo.getRightExpression(), + schema.findField(column.getColumnName())); + return Expressions.notEqual(column.getColumnName(), value); + } + if (condition instanceof GreaterThan) { + GreaterThan greaterThan = (GreaterThan) condition; + Column column = (Column) greaterThan.getLeftExpression(); + Object value = + schema == null + ? convertValueExpression(greaterThan.getRightExpression()) + : convertValueExpression( + greaterThan.getRightExpression(), + schema.findField(column.getColumnName())); + return Expressions.greaterThan(column.getColumnName(), value); + } + if (condition instanceof GreaterThanEquals) { + GreaterThanEquals greaterThanEquals = (GreaterThanEquals) condition; + Column column = (Column) greaterThanEquals.getLeftExpression(); + Object value = + schema == null + ? convertValueExpression(greaterThanEquals.getRightExpression()) + : convertValueExpression( + greaterThanEquals.getRightExpression(), + schema.findField(column.getColumnName())); + return Expressions.greaterThanOrEqual(column.getColumnName(), value); + } + if (condition instanceof MinorThan) { + MinorThan minorThan = (MinorThan) condition; + Column column = (Column) minorThan.getLeftExpression(); + Object value = + schema == null + ? convertValueExpression(minorThan.getRightExpression()) + : convertValueExpression( + minorThan.getRightExpression(), + schema.findField(column.getColumnName())); + return Expressions.lessThan(column.getColumnName(), value); + } + if (condition instanceof MinorThanEquals) { + MinorThanEquals minorThanEquals = (MinorThanEquals) condition; + Column column = (Column) minorThanEquals.getLeftExpression(); + Object value = + schema == null + ? convertValueExpression(minorThanEquals.getRightExpression()) + : convertValueExpression( + minorThanEquals.getRightExpression(), + schema.findField(column.getColumnName())); + return Expressions.lessThanOrEqual(column.getColumnName(), value); + } + if (condition instanceof IsNullExpression) { + IsNullExpression isNullExpression = (IsNullExpression) condition; + Column column = (Column) isNullExpression.getLeftExpression(); + if (isNullExpression.isNot()) { + return Expressions.notNull(column.getColumnName()); + } + return Expressions.isNull(column.getColumnName()); + } + if (condition instanceof InExpression) { + InExpression inExpression = (InExpression) condition; + Column column = (Column) inExpression.getLeftExpression(); + ExpressionList itemsList = + (ExpressionList) inExpression.getRightExpression(); + List values = + itemsList.getExpressions().stream() + .map( + e -> + schema == null + ? convertValueExpression(e) + : convertValueExpression( + e, + schema.findField( + column.getColumnName()))) + .collect(Collectors.toList()); + if (inExpression.isNot()) { + return Expressions.notIn(column.getColumnName(), values); + } + return Expressions.in(column.getColumnName(), values); + } + if (condition instanceof IsBooleanExpression) { + IsBooleanExpression booleanExpression = (IsBooleanExpression) condition; + Column column = (Column) booleanExpression.getLeftExpression(); + if (booleanExpression.isNot()) { + return Expressions.notEqual(column.getColumnName(), booleanExpression.isTrue()); + } + return Expressions.equal(column.getColumnName(), booleanExpression.isTrue()); + } + + throw new UnsupportedOperationException( + "Unsupported condition: " + condition.getClass().getName()); + } + + @SneakyThrows + private static Object convertValueExpression( + net.sf.jsqlparser.expression.Expression valueExpression, + Types.NestedField icebergColumn) { + switch (icebergColumn.type().typeId()) { + case DECIMAL: + return new BigDecimal(valueExpression.toString()); + case DATE: + if (valueExpression instanceof StringValue) { + LocalDate date = + LocalDate.parse( + ((StringValue) valueExpression).getValue(), ISO_LOCAL_DATE); + return DateTimeUtil.daysFromDate(date); + } + case TIME: + if (valueExpression instanceof StringValue) { + LocalTime time = + LocalTime.parse( + ((StringValue) valueExpression).getValue(), ISO_LOCAL_TIME); + return DateTimeUtil.microsFromTime(time); + } + case TIMESTAMP: + if (valueExpression instanceof StringValue) { + LocalDateTime dateTime = + LocalDateTime.parse( + ((StringValue) valueExpression).getValue(), + LOCAL_DATE_TIME_FORMATTER); + return DateTimeUtil.microsFromTimestamp(dateTime); + } + default: + return convertValueExpression(valueExpression); + } + } + + private static Object convertValueExpression( + net.sf.jsqlparser.expression.Expression valueExpression) { + if (valueExpression instanceof LongValue) { + return ((LongValue) valueExpression).getValue(); + } + if (valueExpression instanceof DoubleValue) { + return ((DoubleValue) valueExpression).getValue(); + } + if (valueExpression instanceof StringValue) { + return ((StringValue) valueExpression).getValue(); + } + return valueExpression.toString(); + } +} diff --git a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/SchemaUtils.java b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/SchemaUtils.java index 9aba4a777d8..356575fbf8d 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/SchemaUtils.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/main/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/SchemaUtils.java @@ -19,6 +19,8 @@ package org.apache.seatunnel.connectors.seatunnel.iceberg.utils; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; @@ -28,6 +30,7 @@ import org.apache.seatunnel.api.table.catalog.exception.DatabaseNotExistException; import org.apache.seatunnel.api.table.catalog.exception.TableAlreadyExistException; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.connectors.seatunnel.iceberg.catalog.IcebergCatalog; import org.apache.seatunnel.connectors.seatunnel.iceberg.config.SinkConfig; import org.apache.seatunnel.connectors.seatunnel.iceberg.data.IcebergTypeMapper; import org.apache.seatunnel.connectors.seatunnel.iceberg.sink.schema.SchemaAddColumn; @@ -49,7 +52,6 @@ import org.jetbrains.annotations.NotNull; -import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; @@ -105,6 +107,8 @@ public static Table autoCreateTable( SinkConfig config = new SinkConfig(readonlyConfig); // build auto create table Map options = new HashMap<>(table.getOptions()); + Optional.ofNullable(table.getComment()) + .map(e -> options.put(IcebergCatalog.PROPS_TABLE_COMMENT, e)); // override options.putAll(config.getAutoCreateProps()); return createTable(catalog, toIcebergTableIdentifier(tablePath), config, schema, options); diff --git a/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalogTest.java b/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalogTest.java index 6ec5ae5783f..cfed7ab6080 100644 --- a/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalogTest.java +++ b/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/catalog/IcebergCatalogTest.java @@ -145,6 +145,17 @@ void getTable() { @Test @Order(8) + void executeDeleteSQL() { + CatalogTable table = icebergCatalog.getTable(tablePath); + icebergCatalog.executeSql( + tablePath, + "DELETE FROM " + + tablePath.getFullName() + + " WHERE id > 1 and timestamp_col = '2024-01-01 01:01:01.999'"); + } + + @Test + @Order(9) void dropTable() { icebergCatalog.dropTable(tablePath, false); Assertions.assertFalse(icebergCatalog.tableExists(tablePath)); @@ -194,7 +205,8 @@ CatalogTable buildAllTypesTable(TableIdentifier tableIdentifier) { TableSchema schema = builder.build(); HashMap options = new HashMap<>(); options.put("write.parquet.compression-codec", "zstd"); + options.put("comment", "test"); return CatalogTable.of( - tableIdentifier, schema, options, Collections.singletonList("dt_col"), "null"); + tableIdentifier, schema, options, Collections.singletonList("dt_col"), "test"); } } diff --git a/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/ExpressionUtilsTest.java b/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/ExpressionUtilsTest.java new file mode 100644 index 00000000000..a2b8658390f --- /dev/null +++ b/seatunnel-connectors-v2/connector-iceberg/src/test/java/org/apache/seatunnel/connectors/seatunnel/iceberg/utils/ExpressionUtilsTest.java @@ -0,0 +1,131 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.iceberg.utils; + +import org.apache.iceberg.Schema; +import org.apache.iceberg.expressions.Expression; +import org.apache.iceberg.expressions.Expressions; +import org.apache.iceberg.types.Types; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.delete.Delete; + +public class ExpressionUtilsTest { + + @Test + public void testSqlToExpression() throws JSQLParserException { + String sql = "delete from test.a where id = 1"; + + Expression expression = ExpressionUtils.convertDeleteSQL(sql); + Assertions.assertEquals(Expressions.equal("id", 1).toString(), expression.toString()); + + sql = "delete from test.a where id != 1"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.notEqual("id", 1).toString(), expression.toString()); + + sql = "delete from test.a where id > 1"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.greaterThan("id", 1).toString(), expression.toString()); + + sql = "delete from test.a where id >= 1"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals( + Expressions.greaterThanOrEqual("id", 1).toString(), expression.toString()); + + sql = "delete from test.a where id < 1"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.lessThan("id", 1).toString(), expression.toString()); + + sql = "delete from test.a where id <= 1"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals( + Expressions.lessThanOrEqual("id", 1).toString(), expression.toString()); + + sql = "delete from test.a where id is null"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.isNull("id").toString(), expression.toString()); + + sql = "delete from test.a where id is not null"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.notNull("id").toString(), expression.toString()); + + sql = "delete from test.a where id in (1,2,3)"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.in("id", 1, 2, 3).toString(), expression.toString()); + + sql = "delete from test.a where id not in (1,2,3)"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.notIn("id", 1, 2, 3).toString(), expression.toString()); + + sql = "delete from test.a where id is true"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.equal("id", true).toString(), expression.toString()); + + sql = "delete from test.a where id = 1 and name = a or (age >=1 and age < 1)"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals( + Expressions.or( + Expressions.and( + Expressions.equal("id", 1), Expressions.equal("name", "a")), + Expressions.and( + Expressions.greaterThanOrEqual("age", 1), + Expressions.lessThan("age", 1))) + .toString(), + expression.toString()); + + sql = "delete from test.a where id = 'a'"; + expression = ExpressionUtils.convertDeleteSQL(sql); + + Assertions.assertEquals(Expressions.equal("id", "a").toString(), expression.toString()); + + sql = + "delete from test.a where f1 = '2024-01-01' and f2 = '12:00:00.001' and f3 = '2024-01-01 12:00:00.001'"; + Statement statement = CCJSqlParserUtil.parse(sql); + Delete delete = (Delete) statement; + Schema schema = + new Schema( + Types.NestedField.optional(1, "f1", Types.DateType.get()), + Types.NestedField.optional(2, "f2", Types.TimeType.get()), + Types.NestedField.optional(3, "f3", Types.TimestampType.withoutZone())); + expression = ExpressionUtils.convert(delete.getWhere(), schema); + + Assertions.assertEquals( + Expressions.and( + Expressions.equal("f1", 19723), + Expressions.equal("f2", 43200001000L), + Expressions.equal("f3", 1704110400001000L)) + .toString(), + expression.toString()); + } +} diff --git a/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/config/InfluxDBConfig.java b/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/config/InfluxDBConfig.java index fa43f8fa6df..eb05f53671b 100644 --- a/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/config/InfluxDBConfig.java +++ b/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/config/InfluxDBConfig.java @@ -17,12 +17,12 @@ package org.apache.seatunnel.connectors.seatunnel.influxdb.config; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; import org.apache.seatunnel.shade.com.typesafe.config.Config; import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; -import com.google.common.annotations.VisibleForTesting; import lombok.Data; import java.io.Serializable; diff --git a/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/serialize/DefaultSerializer.java b/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/serialize/DefaultSerializer.java index d14e311a971..29976d13e3e 100644 --- a/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/serialize/DefaultSerializer.java +++ b/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/serialize/DefaultSerializer.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.influxdb.serialize; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -28,8 +30,6 @@ import org.influxdb.dto.Point; -import com.google.common.base.Strings; - import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; diff --git a/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/sink/InfluxDBSink.java b/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/sink/InfluxDBSink.java index 4d940f63cc3..840379f7bdb 100644 --- a/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/sink/InfluxDBSink.java +++ b/seatunnel-connectors-v2/connector-influxdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/influxdb/sink/InfluxDBSink.java @@ -26,12 +26,14 @@ import org.apache.seatunnel.connectors.seatunnel.influxdb.config.SinkConfig; import java.io.IOException; +import java.util.Optional; public class InfluxDBSink extends AbstractSimpleSink implements SupportMultiTableSink { - private SeaTunnelRowType seaTunnelRowType; - private SinkConfig sinkConfig; + private final SeaTunnelRowType seaTunnelRowType; + private final SinkConfig sinkConfig; + private final CatalogTable catalogTable; @Override public String getPluginName() { @@ -41,10 +43,16 @@ public String getPluginName() { public InfluxDBSink(SinkConfig sinkConfig, CatalogTable catalogTable) { this.sinkConfig = sinkConfig; this.seaTunnelRowType = catalogTable.getTableSchema().toPhysicalRowDataType(); + this.catalogTable = catalogTable; } @Override public InfluxDBSinkWriter createWriter(SinkWriter.Context context) throws IOException { return new InfluxDBSinkWriter(sinkConfig, seaTunnelRowType); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/serialize/DefaultSeaTunnelRowSerializer.java b/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/serialize/DefaultSeaTunnelRowSerializer.java index 200df46ee2d..aff34c01f8b 100644 --- a/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/serialize/DefaultSeaTunnelRowSerializer.java +++ b/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/serialize/DefaultSeaTunnelRowSerializer.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.iotdb.serialize; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; @@ -25,7 +27,6 @@ import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType; -import com.google.common.base.Strings; import lombok.NonNull; import java.time.LocalDateTime; diff --git a/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/sink/IoTDBSink.java b/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/sink/IoTDBSink.java index 263ab049732..65c29d57cb7 100644 --- a/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/sink/IoTDBSink.java +++ b/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/sink/IoTDBSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.common.SeaTunnelAPIErrorCode; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.config.CheckConfigUtil; @@ -34,6 +35,8 @@ import com.google.auto.service.AutoService; +import java.util.Optional; + import static org.apache.seatunnel.connectors.seatunnel.iotdb.config.SinkConfig.KEY_DEVICE; import static org.apache.seatunnel.connectors.seatunnel.iotdb.config.SinkConfig.NODE_URLS; import static org.apache.seatunnel.connectors.seatunnel.iotdb.config.SinkConfig.PASSWORD; @@ -78,4 +81,9 @@ public void setTypeInfo(SeaTunnelRowType seaTunnelRowType) { public AbstractSinkWriter createWriter(SinkWriter.Context context) { return new IoTDBSinkWriter(pluginConfig, seaTunnelRowType); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/source/IoTDBSourceSplitEnumerator.java b/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/source/IoTDBSourceSplitEnumerator.java index 7327ea478da..31f6370219a 100644 --- a/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/source/IoTDBSourceSplitEnumerator.java +++ b/seatunnel-connectors-v2/connector-iotdb/src/main/java/org/apache/seatunnel/connectors/seatunnel/iotdb/source/IoTDBSourceSplitEnumerator.java @@ -17,12 +17,13 @@ package org.apache.seatunnel.connectors.seatunnel.iotdb.source; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.api.source.SourceSplitEnumerator; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.connectors.seatunnel.iotdb.exception.IotdbConnectorException; import org.apache.seatunnel.connectors.seatunnel.iotdb.state.IoTDBSourceState; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; diff --git a/seatunnel-connectors-v2/connector-jdbc/pom.xml b/seatunnel-connectors-v2/connector-jdbc/pom.xml index 7b4199c462f..1f09fa2168f 100644 --- a/seatunnel-connectors-v2/connector-jdbc/pom.xml +++ b/seatunnel-connectors-v2/connector-jdbc/pom.xml @@ -49,10 +49,13 @@ 2.5.1 8.6.0 3.1.3 - 2.4.11 + 2.4.12 12.2.0 3.0.0 3.2.0 + 5.1.0-og + 3.5.1 + @@ -203,11 +206,21 @@ ${iris.jdbc.version} provided - org.tikv tikv-client-java ${tikv.version} + + + org.opengauss + opengauss-jdbc + ${opengauss.jdbc.version} + provided + + + org.mariadb.jdbc + mariadb-java-client + ${mariadb.jdbc.version} provided @@ -316,11 +329,18 @@ com.intersystems intersystems-jdbc - org.tikv tikv-client-java + + org.opengauss + opengauss-jdbc + + + org.mariadb.jdbc + mariadb-java-client + diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/AbstractJdbcCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/AbstractJdbcCatalog.java index 772cc3bf77f..b5f73f437fb 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/AbstractJdbcCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/AbstractJdbcCatalog.java @@ -62,9 +62,9 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.seatunnel.common.exception.CommonErrorCode.UNSUPPORTED_METHOD; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkArgument; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Slf4j public abstract class AbstractJdbcCatalog implements Catalog { @@ -178,24 +178,21 @@ public CatalogTable getTable(TablePath tablePath) DatabaseMetaData metaData = conn.getMetaData(); Optional primaryKey = getPrimaryKey(metaData, tablePath); List constraintKeys = getConstraintKeys(metaData, tablePath); - try (PreparedStatement ps = conn.prepareStatement(getSelectColumnsSql(tablePath)); - ResultSet resultSet = ps.executeQuery()) { - - TableSchema.Builder builder = TableSchema.builder(); - buildColumnsWithErrorCheck(tablePath, resultSet, builder); - // add primary key - primaryKey.ifPresent(builder::primaryKey); - // add constraint key - constraintKeys.forEach(builder::constraintKey); - TableIdentifier tableIdentifier = getTableIdentifier(tablePath); - return CatalogTable.of( - tableIdentifier, - builder.build(), - buildConnectorOptions(tablePath), - Collections.emptyList(), - "", - catalogName); - } + TableSchema.Builder tableSchemaBuilder = + buildColumnsReturnTablaSchemaBuilder(tablePath, conn); + // add primary key + primaryKey.ifPresent(tableSchemaBuilder::primaryKey); + // add constraint key + constraintKeys.forEach(tableSchemaBuilder::constraintKey); + TableIdentifier tableIdentifier = getTableIdentifier(tablePath); + return CatalogTable.of( + tableIdentifier, + tableSchemaBuilder.build(), + buildConnectorOptions(tablePath), + Collections.emptyList(), + "", + catalogName); + } catch (SeaTunnelRuntimeException e) { throw e; } catch (Exception e) { @@ -204,6 +201,16 @@ public CatalogTable getTable(TablePath tablePath) } } + protected TableSchema.Builder buildColumnsReturnTablaSchemaBuilder( + TablePath tablePath, Connection conn) throws SQLException { + TableSchema.Builder columnsBuilder = TableSchema.builder(); + try (PreparedStatement ps = conn.prepareStatement(getSelectColumnsSql(tablePath)); + ResultSet resultSet = ps.executeQuery()) { + buildColumnsWithErrorCheck(tablePath, resultSet, columnsBuilder); + } + return columnsBuilder; + } + protected void buildColumnsWithErrorCheck( TablePath tablePath, ResultSet resultSet, TableSchema.Builder builder) throws SQLException { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCatalog.java index 13dcdf0783a..04b15dfe1c7 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCatalog.java @@ -21,6 +21,7 @@ import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.TableSchema; import org.apache.seatunnel.api.table.catalog.exception.CatalogException; import org.apache.seatunnel.api.table.catalog.exception.DatabaseNotExistException; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; @@ -33,6 +34,7 @@ import lombok.extern.slf4j.Slf4j; import java.sql.Connection; +import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -42,18 +44,6 @@ @Slf4j public class DamengCatalog extends AbstractJdbcCatalog { - private static final String SELECT_COLUMNS_SQL = - "SELECT COLUMNS.COLUMN_NAME, COLUMNS.DATA_TYPE, COLUMNS.DATA_LENGTH, COLUMNS.DATA_PRECISION, COLUMNS.DATA_SCALE " - + ", COLUMNS.NULLABLE, COLUMNS.DATA_DEFAULT, COMMENTS.COMMENTS " - + "FROM ALL_TAB_COLUMNS COLUMNS " - + "LEFT JOIN ALL_COL_COMMENTS COMMENTS " - + "ON COLUMNS.OWNER = COMMENTS.SCHEMA_NAME " - + "AND COLUMNS.TABLE_NAME = COMMENTS.TABLE_NAME " - + "AND COLUMNS.COLUMN_NAME = COMMENTS.COLUMN_NAME " - + "WHERE COLUMNS.OWNER = '%s' " - + "AND COLUMNS.TABLE_NAME = '%s' " - + "ORDER BY COLUMNS.COLUMN_ID ASC"; - public DamengCatalog( String catalogName, String username, @@ -85,7 +75,7 @@ protected String getListDatabaseSql() { @Override protected String getCreateTableSql( TablePath tablePath, CatalogTable table, boolean createIndex) { - throw new UnsupportedOperationException(); + return new DamengCreateTableSqlBuilder(table, createIndex).build(tablePath); } @Override @@ -95,7 +85,7 @@ protected String getDropTableSql(TablePath tablePath) { @Override protected String getTableName(TablePath tablePath) { - return tablePath.getSchemaAndTableName().toUpperCase(); + return tablePath.getSchemaAndTableName("\""); } @Override @@ -108,27 +98,19 @@ protected String getTableName(ResultSet rs) throws SQLException { return rs.getString(1) + "." + rs.getString(2); } - @Override - protected String getSelectColumnsSql(TablePath tablePath) { - return String.format( - SELECT_COLUMNS_SQL, tablePath.getSchemaName(), tablePath.getTableName()); - } - @Override protected Column buildColumn(ResultSet resultSet) throws SQLException { String columnName = resultSet.getString("COLUMN_NAME"); - String typeName = resultSet.getString("DATA_TYPE"); - long columnLength = resultSet.getLong("DATA_LENGTH"); - long columnPrecision = resultSet.getLong("DATA_PRECISION"); - int columnScale = resultSet.getInt("DATA_SCALE"); - String columnComment = resultSet.getString("COMMENTS"); - Object defaultValue = resultSet.getObject("DATA_DEFAULT"); - boolean isNullable = resultSet.getString("NULLABLE").equals("Y"); - + String typeName = resultSet.getString("TYPE_NAME"); + Long columnLength = resultSet.getLong("COLUMN_SIZE"); + Long columnPrecision = columnLength; + Integer columnScale = resultSet.getObject("DECIMAL_DIGITS", Integer.class); + String columnComment = resultSet.getString("REMARKS"); + Object defaultValue = resultSet.getObject("COLUMN_DEF"); + boolean isNullable = (resultSet.getInt("NULLABLE") == DatabaseMetaData.columnNullable); BasicTypeDefine typeDefine = BasicTypeDefine.builder() .name(columnName) - .columnType(typeName) .dataType(typeName) .length(columnLength) .precision(columnPrecision) @@ -150,11 +132,6 @@ protected String getOptionTableName(TablePath tablePath) { return tablePath.getSchemaAndTableName(); } - private List listTables() { - List databases = listDatabases(); - return listTables(databases.get(0)); - } - @Override public List listTables(String databaseName) throws CatalogException, DatabaseNotExistException { @@ -184,4 +161,31 @@ public CatalogTable getTable(String sqlQuery) throws SQLException { Connection defaultConnection = getConnection(defaultUrl); return CatalogUtils.getCatalogTable(defaultConnection, sqlQuery, new DmdbTypeMapper()); } + + @Override + protected TableSchema.Builder buildColumnsReturnTablaSchemaBuilder( + TablePath tablePath, Connection conn) throws SQLException { + TableSchema.Builder columnsBuilder = TableSchema.builder(); + DatabaseMetaData metaData = conn.getMetaData(); + try (ResultSet resultSet = + metaData.getColumns( + null, tablePath.getSchemaName(), tablePath.getTableName(), null)) { + buildColumnsWithErrorCheck(tablePath, resultSet, columnsBuilder); + } + return columnsBuilder; + } + + @Override + protected String getTruncateTableSql(TablePath tablePath) { + return String.format( + "TRUNCATE TABLE \"%s\".\"%s\"", + tablePath.getSchemaName(), tablePath.getTableName()); + } + + @Override + protected String getExistDataSql(TablePath tablePath) { + return String.format( + "select * from \"%s\".\"%s\" WHERE rownum = 1", + tablePath.getSchemaName(), tablePath.getTableName()); + } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCreateTableSqlBuilder.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCreateTableSqlBuilder.java new file mode 100644 index 00000000000..98e0361702c --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCreateTableSqlBuilder.java @@ -0,0 +1,211 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.dm; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.ConstraintKey; +import org.apache.seatunnel.api.table.catalog.PrimaryKey; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.AbstractJdbcCreateTableSqlBuilder; +import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.utils.CatalogUtils; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dm.DmdbTypeConverter; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public class DamengCreateTableSqlBuilder extends AbstractJdbcCreateTableSqlBuilder { + private final List columns; + private final PrimaryKey primaryKey; + private final String sourceCatalogName; + private final String fieldIde; + private final List constraintKeys; + private boolean createIndex; + + public DamengCreateTableSqlBuilder(CatalogTable catalogTable, boolean createIndex) { + this.columns = catalogTable.getTableSchema().getColumns(); + this.primaryKey = catalogTable.getTableSchema().getPrimaryKey(); + this.sourceCatalogName = catalogTable.getCatalogName(); + this.fieldIde = catalogTable.getOptions().get("fieldIde"); + constraintKeys = catalogTable.getTableSchema().getConstraintKeys(); + this.createIndex = createIndex; + } + + public String build(TablePath tablePath) { + StringBuilder createTableSql = new StringBuilder(); + createTableSql + .append("CREATE TABLE ") + .append(tablePath.getSchemaAndTableName("\"")) + .append(" (\n"); + + List columnSqls = + columns.stream() + .map(column -> CatalogUtils.getFieldIde(buildColumnSql(column), fieldIde)) + .collect(Collectors.toList()); + + if (createIndex + && primaryKey != null + && CollectionUtils.isNotEmpty(primaryKey.getColumnNames())) { + columnSqls.add(buildPrimaryKeySql(primaryKey)); + } + + if (createIndex && CollectionUtils.isNotEmpty(constraintKeys)) { + for (ConstraintKey constraintKey : constraintKeys) { + if (StringUtils.isBlank(constraintKey.getConstraintName()) + || (primaryKey != null + && (StringUtils.equals( + primaryKey.getPrimaryKey(), + constraintKey.getConstraintName()) + || primaryContainsAllConstrainKey( + primaryKey, constraintKey)))) { + continue; + } + String constraintKeySql = buildConstraintKeySql(constraintKey); + if (StringUtils.isNotEmpty(constraintKeySql)) { + columnSqls.add("\t" + constraintKeySql); + } + } + } + + createTableSql.append(String.join(",\n", columnSqls)); + createTableSql.append("\n)"); + + List commentSqls = + columns.stream() + .filter(column -> StringUtils.isNotBlank(column.getComment())) + .map( + column -> + buildColumnCommentSql( + column, tablePath.getSchemaAndTableName("\""))) + .collect(Collectors.toList()); + + if (!commentSqls.isEmpty()) { + createTableSql.append(";\n"); + createTableSql.append(String.join(";\n", commentSqls)); + createTableSql.append(";"); + } + + return createTableSql.toString(); + } + + private String buildColumnSql(Column column) { + StringBuilder columnSql = new StringBuilder(); + columnSql.append("\"").append(column.getName()).append("\" "); + + String columnType = + StringUtils.equals(DatabaseIdentifier.DAMENG, sourceCatalogName) + ? column.getSourceType() + : DmdbTypeConverter.INSTANCE.reconvert(column).getColumnType(); + columnSql.append(columnType); + + if (!column.isNullable()) { + columnSql.append(" NOT NULL"); + } + + return columnSql.toString(); + } + + private String buildPrimaryKeySql(PrimaryKey primaryKey) { + String randomSuffix = UUID.randomUUID().toString().replace("-", "").substring(0, 4); + String columnNamesString = + primaryKey.getColumnNames().stream() + .map(columnName -> "\"" + columnName + "\"") + .collect(Collectors.joining(", ")); + + String primaryKeyStr = primaryKey.getPrimaryKey(); + if (primaryKeyStr.length() > 25) { + primaryKeyStr = primaryKeyStr.substring(0, 25); + } + + return CatalogUtils.getFieldIde( + "CONSTRAINT " + + primaryKeyStr + + "_" + + randomSuffix + + " PRIMARY KEY (" + + columnNamesString + + ")", + fieldIde); + } + + private String buildColumnCommentSql(Column column, String tableName) { + StringBuilder columnCommentSql = new StringBuilder(); + columnCommentSql + .append(CatalogUtils.quoteIdentifier("COMMENT ON COLUMN ", fieldIde)) + .append(CatalogUtils.quoteIdentifier(tableName, fieldIde)) + .append("."); + columnCommentSql + .append(CatalogUtils.quoteIdentifier(column.getName(), fieldIde, "\"")) + .append(CatalogUtils.quoteIdentifier(" IS '", fieldIde)) + .append(column.getComment()) + .append("'"); + return columnCommentSql.toString(); + } + + private String buildConstraintKeySql(ConstraintKey constraintKey) { + ConstraintKey.ConstraintType constraintType = constraintKey.getConstraintType(); + String randomSuffix = UUID.randomUUID().toString().replace("-", "").substring(0, 4); + + String constraintName = constraintKey.getConstraintName(); + if (constraintName.length() > 25) { + constraintName = constraintName.substring(0, 25); + } + String indexColumns = + constraintKey.getColumnNames().stream() + .map( + constraintKeyColumn -> + String.format( + "\"%s\"", + CatalogUtils.getFieldIde( + constraintKeyColumn.getColumnName(), + fieldIde))) + .collect(Collectors.joining(", ")); + + String keyName; + switch (constraintType) { + case INDEX_KEY: + keyName = "KEY"; + break; + case UNIQUE_KEY: + keyName = "UNIQUE"; + break; + case FOREIGN_KEY: + keyName = "FOREIGN KEY"; + break; + default: + throw new UnsupportedOperationException( + "Unsupported constraint type: " + constraintType); + } + + if (StringUtils.equals(keyName, "UNIQUE")) { + return "CONSTRAINT " + + constraintName + + "_" + + randomSuffix + + " UNIQUE (" + + indexColumns + + ")"; + } + return null; + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCatalog.java index b4d60a22d3d..e91006fdca9 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCatalog.java @@ -18,6 +18,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.iris; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.ConstraintKey; @@ -40,7 +42,6 @@ import org.apache.commons.lang3.StringUtils; -import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import java.sql.Connection; @@ -51,7 +52,7 @@ import java.util.List; import java.util.Optional; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Slf4j public class IrisCatalog extends AbstractJdbcCatalog { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java index 1c414c56e27..323556e1379 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MySqlCatalog.java @@ -18,6 +18,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.ConstraintKey; @@ -32,7 +34,6 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql.MySqlTypeMapper; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql.MySqlVersion; -import com.google.common.base.Preconditions; import com.mysql.cj.MysqlType; import lombok.extern.slf4j.Slf4j; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlDataTypeConvertor.java index d0a1fe7df0a..675f538fd6c 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlDataTypeConvertor.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql; +import org.apache.seatunnel.shade.com.google.common.collect.ImmutableMap; + import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.DataTypeConvertor; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -28,13 +30,12 @@ import org.apache.commons.collections4.MapUtils; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableMap; import com.mysql.cj.MysqlType; import java.util.Collections; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** @deprecated instead by {@link MySqlTypeConverter} */ @Deprecated diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java index 33aa2f8ccd4..b251b088e12 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oceanbase/OceanBaseMySqlCatalog.java @@ -17,29 +17,36 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.oceanbase; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.ConstraintKey; +import org.apache.seatunnel.api.table.catalog.PrimaryKey; import org.apache.seatunnel.api.table.catalog.TableIdentifier; import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.TableSchema; import org.apache.seatunnel.api.table.catalog.exception.CatalogException; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; import org.apache.seatunnel.common.utils.JdbcUrlUtil; import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.AbstractJdbcCatalog; -import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.utils.CatalogUtils; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oceanbase.OceanBaseMySqlTypeConverter; -import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oceanbase.OceanBaseMySqlTypeMapper; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oceanbase.OceanBaseMysqlType; -import com.google.common.base.Preconditions; +import org.apache.commons.lang.StringUtils; + import lombok.extern.slf4j.Slf4j; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -197,12 +204,54 @@ protected String getDropDatabaseSql(String databaseName) { @Override public CatalogTable getTable(String sqlQuery) throws SQLException { - Connection defaultConnection = getConnection(defaultUrl); - try (Statement statement = defaultConnection.createStatement(); - ResultSet resultSet = statement.executeQuery(sqlQuery)) { - ResultSetMetaData metaData = resultSet.getMetaData(); - return CatalogUtils.getCatalogTable( - metaData, new OceanBaseMySqlTypeMapper(typeConverter), sqlQuery); + try (Connection connection = getConnection(defaultUrl)) { + String tableName = null; + String databaseName = null; + String schemaName = null; + String catalogName = "jdbc_catalog"; + TableSchema.Builder schemaBuilder = TableSchema.builder(); + + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(sqlQuery)) { + ResultSetMetaData metaData = resultSet.getMetaData(); + tableName = metaData.getTableName(1); + databaseName = metaData.getCatalogName(1); + schemaName = metaData.getSchemaName(1); + catalogName = metaData.getCatalogName(1); + } + databaseName = StringUtils.defaultIfBlank(databaseName, null); + schemaName = StringUtils.defaultIfBlank(schemaName, null); + + TablePath tablePath = + StringUtils.isBlank(tableName) + ? TablePath.DEFAULT + : TablePath.of(databaseName, schemaName, tableName); + + try (PreparedStatement ps = + connection.prepareStatement(getSelectColumnsSql(tablePath)); + ResultSet columnResultSet = ps.executeQuery(); + ResultSet primaryKeys = + connection + .getMetaData() + .getPrimaryKeys(catalogName, schemaName, tableName)) { + while (primaryKeys.next()) { + String primaryKeyColumnName = primaryKeys.getString("COLUMN_NAME"); + schemaBuilder.primaryKey( + PrimaryKey.of( + primaryKeyColumnName, + Collections.singletonList(primaryKeyColumnName))); + } + while (columnResultSet.next()) { + schemaBuilder.column(buildColumn(columnResultSet)); + } + } + return CatalogTable.of( + TableIdentifier.of(catalogName, tablePath), + schemaBuilder.build(), + new HashMap<>(), + new ArrayList<>(), + "", + catalogName); } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/opengauss/OpenGaussCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/opengauss/OpenGaussCatalog.java new file mode 100644 index 00000000000..805701cc45d --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/opengauss/OpenGaussCatalog.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.opengauss; + +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + +import org.apache.seatunnel.common.utils.JdbcUrlUtil; +import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.psql.PostgresCatalog; + +import lombok.extern.slf4j.Slf4j; + +import java.sql.Connection; + +@Slf4j +public class OpenGaussCatalog extends PostgresCatalog { + + public OpenGaussCatalog( + String catalogName, + String username, + String pwd, + JdbcUrlUtil.UrlInfo urlInfo, + String defaultSchema) { + super(catalogName, username, pwd, urlInfo, defaultSchema); + } + + @VisibleForTesting + public void setConnection(String url, Connection connection) { + this.connectionMap.put(url, connection); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/opengauss/OpenGaussCatalogFactory.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/opengauss/OpenGaussCatalogFactory.java new file mode 100644 index 00000000000..bff96ff6d30 --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/opengauss/OpenGaussCatalogFactory.java @@ -0,0 +1,62 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.opengauss; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.configuration.util.OptionValidationException; +import org.apache.seatunnel.api.table.catalog.Catalog; +import org.apache.seatunnel.api.table.factory.CatalogFactory; +import org.apache.seatunnel.api.table.factory.Factory; +import org.apache.seatunnel.common.utils.JdbcUrlUtil; +import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.JdbcCatalogOptions; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; + +import com.google.auto.service.AutoService; + +import java.util.Optional; + +@AutoService(Factory.class) +public class OpenGaussCatalogFactory implements CatalogFactory { + + @Override + public String factoryIdentifier() { + return DatabaseIdentifier.OPENGAUSS; + } + + @Override + public Catalog createCatalog(String catalogName, ReadonlyConfig options) { + String urlWithDatabase = options.get(JdbcCatalogOptions.BASE_URL); + JdbcUrlUtil.UrlInfo urlInfo = JdbcUrlUtil.getUrlInfo(urlWithDatabase); + Optional defaultDatabase = urlInfo.getDefaultDatabase(); + if (!defaultDatabase.isPresent()) { + throw new OptionValidationException(JdbcCatalogOptions.BASE_URL); + } + return new OpenGaussCatalog( + catalogName, + options.get(JdbcCatalogOptions.USERNAME), + options.get(JdbcCatalogOptions.PASSWORD), + urlInfo, + options.get(JdbcCatalogOptions.SCHEMA)); + } + + @Override + public OptionRule optionRule() { + return JdbcCatalogOptions.BASE_RULE.build(); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleDataTypeConvertor.java index fc1efc0c1a0..6b79d47c4ef 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleDataTypeConvertor.java @@ -34,7 +34,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** @deprecated instead by {@link OracleTypeConverter} */ @Deprecated diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilder.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilder.java index f7b98c1bb17..1fbfd7c095e 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilder.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilder.java @@ -30,11 +30,14 @@ import org.apache.commons.lang3.StringUtils; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; +@Slf4j public class PostgresCreateTableSqlBuilder { private List columns; private PrimaryKey primaryKey; @@ -161,10 +164,7 @@ private String buildColumnCommentSql(Column column, String tableName) { } private String buildUniqueKeySql(ConstraintKey constraintKey) { - String constraintName = constraintKey.getConstraintName(); - if (constraintName.length() > 25) { - constraintName = constraintName.substring(0, 25); - } + String constraintName = UUID.randomUUID().toString().replace("-", ""); String indexColumns = constraintKey.getColumnNames().stream() .map( @@ -175,16 +175,12 @@ private String buildUniqueKeySql(ConstraintKey constraintKey) { constraintKeyColumn.getColumnName(), fieldIde))) .collect(Collectors.joining(", ")); - return "CONSTRAINT " + constraintName + " UNIQUE (" + indexColumns + ")"; + return "CONSTRAINT \"" + constraintName + "\" UNIQUE (" + indexColumns + ")"; } private String buildIndexKeySql(TablePath tablePath, ConstraintKey constraintKey) { - // We add table name to index name to avoid name conflict in PG - // Since index name in PG should unique in the schema - String constraintName = tablePath.getTableName() + "_" + constraintKey.getConstraintName(); - if (constraintName.length() > 25) { - constraintName = constraintName.substring(0, 25); - } + // If the index name is omitted, PostgreSQL will choose an appropriate name based on table + // name and indexed columns. String indexColumns = constraintKey.getColumnNames().stream() .map( @@ -196,9 +192,7 @@ private String buildIndexKeySql(TablePath tablePath, ConstraintKey constraintKey fieldIde))) .collect(Collectors.joining(", ")); - return "CREATE INDEX " - + constraintName - + " ON " + return "CREATE INDEX ON " + tablePath.getSchemaAndTableName("\"") + "(" + indexColumns diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresDataTypeConvertor.java index 4c2df2559d0..fc0c8e2da23 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresDataTypeConvertor.java @@ -33,7 +33,7 @@ import java.util.HashMap; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** @deprecated instead by {@link PostgresTypeConverter} */ @Deprecated diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java index 330ea6b28de..743a26a5c9a 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftDataTypeConvertor.java @@ -33,7 +33,7 @@ import java.util.Collections; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** @deprecated instead by {@link RedshiftTypeConverter} */ @Deprecated diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/snowflake/SnowflakeDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/snowflake/SnowflakeDataTypeConvertor.java index 638980bb122..52dd03cef0d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/snowflake/SnowflakeDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/snowflake/SnowflakeDataTypeConvertor.java @@ -35,7 +35,7 @@ import java.util.Collections; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @AutoService(DataTypeConvertor.class) public class SnowflakeDataTypeConvertor implements DataTypeConvertor { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCatalog.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCatalog.java index dc6b42a4568..7c759e2eda6 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCatalog.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCatalog.java @@ -40,7 +40,7 @@ @Slf4j public class SqlServerCatalog extends AbstractJdbcCatalog { - private static final String SELECT_COLUMNS_SQL_TEMPLATE = + public static final String SELECT_COLUMNS_SQL_TEMPLATE = "SELECT tbl.name AS table_name,\n" + " col.name AS column_name,\n" + " ext.value AS comment,\n" @@ -53,7 +53,7 @@ public class SqlServerCatalog extends AbstractJdbcCatalog { + " def.definition AS default_value\n" + "FROM sys.tables tbl\n" + " INNER JOIN sys.columns col ON tbl.object_id = col.object_id\n" - + " LEFT JOIN sys.types types ON col.user_type_id = types.user_type_id\n" + + " LEFT JOIN sys.types types ON col.system_type_id = types.user_type_id\n" + " LEFT JOIN sys.extended_properties ext ON ext.major_id = col.object_id AND ext.minor_id = col.column_id\n" + " LEFT JOIN sys.default_constraints def ON col.default_object_id = def.object_id AND ext.minor_id = col.column_id AND ext.name = 'MS_Description'\n" + "WHERE schema_name(tbl.schema_id) = '%s' %s\n" diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerType.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerType.java index 6a2365083c3..36ac163be8a 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerType.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerType.java @@ -17,9 +17,9 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.sqlserver; -import org.apache.commons.lang3.tuple.Pair; +import org.apache.seatunnel.shade.com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap; +import org.apache.commons.lang3.tuple.Pair; import java.math.BigDecimal; import java.sql.SQLType; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBDataTypeConvertor.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBDataTypeConvertor.java index 79c4910d44a..9d3173e3a29 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBDataTypeConvertor.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/tidb/TiDBDataTypeConvertor.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.tidb; +import org.apache.seatunnel.shade.com.google.common.collect.ImmutableMap; + import org.apache.seatunnel.api.table.catalog.DataTypeConvertor; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.common.exception.CommonError; @@ -27,13 +29,12 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql.MySqlTypeConverter; import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableMap; import com.mysql.cj.MysqlType; import java.util.Collections; import java.util.Map; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** @deprecated instead by {@link MySqlTypeConverter} */ @Deprecated diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/JdbcOutputFormat.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/JdbcOutputFormat.java index 32dee1786b2..1097c2dcda2 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/JdbcOutputFormat.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/JdbcOutputFormat.java @@ -33,7 +33,7 @@ import java.sql.SQLException; import java.util.function.Supplier; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** A JDBC outputFormat */ public class JdbcOutputFormat> implements Serializable { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/DataSourceUtils.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/DataSourceUtils.java index 1f35b3d2b9e..1f74d0a8c71 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/DataSourceUtils.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/DataSourceUtils.java @@ -17,11 +17,12 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.connection; +import org.apache.seatunnel.shade.com.google.common.base.CaseFormat; + import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcConnectionConfig; import org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException; -import com.google.common.base.CaseFormat; import lombok.NonNull; import javax.sql.CommonDataSource; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/SimpleJdbcConnectionProvider.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/SimpleJdbcConnectionProvider.java index af329a99d4e..f9f36325af3 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/SimpleJdbcConnectionProvider.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/connection/SimpleJdbcConnectionProvider.java @@ -34,7 +34,7 @@ import java.util.Enumeration; import java.util.Properties; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; /** Simple JDBC connection provider. */ public class SimpleJdbcConnectionProvider implements JdbcConnectionProvider, Serializable { diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/DatabaseIdentifier.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/DatabaseIdentifier.java index 45f849c28bd..6f442e76724 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/DatabaseIdentifier.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/DatabaseIdentifier.java @@ -18,6 +18,7 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; public class DatabaseIdentifier { + public static final String GENERIC = "Generic"; public static final String DB_2 = "DB2"; public static final String DAMENG = "Dameng"; public static final String GBASE_8A = "Gbase8a"; @@ -42,4 +43,5 @@ public class DatabaseIdentifier { public static final String XUGU = "XUGU"; public static final String IRIS = "IRIS"; public static final String INCEPTOR = "Inceptor"; + public static final String OPENGAUSS = "OpenGauss"; } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericDialect.java new file mode 100644 index 00000000000..36c93d65469 --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericDialect.java @@ -0,0 +1,85 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; + +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.AbstractJdbcRowConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.JdbcRowConverter; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dialectenum.FieldIdeEnum; + +import lombok.extern.slf4j.Slf4j; + +import java.util.Optional; + +@Slf4j +public class GenericDialect implements JdbcDialect { + + public String fieldIde = FieldIdeEnum.ORIGINAL.getValue(); + + public GenericDialect() {} + + public GenericDialect(String fieldIde) { + this.fieldIde = fieldIde; + } + + @Override + public String dialectName() { + return DatabaseIdentifier.GENERIC; + } + + @Override + public JdbcRowConverter getRowConverter() { + return new AbstractJdbcRowConverter() { + @Override + public String converterName() { + return DatabaseIdentifier.GENERIC; + } + }; + } + + @Override + public JdbcDialectTypeMapper getJdbcDialectTypeMapper() { + return new GenericTypeMapper(); + } + + @Override + public String quoteIdentifier(String identifier) { + return getFieldIde(identifier, fieldIde); + } + + @Override + public String quoteDatabaseIdentifier(String identifier) { + return identifier; + } + + @Override + public String tableIdentifier(TablePath tablePath) { + return tableIdentifier(tablePath.getDatabaseName(), tablePath.getTableName()); + } + + @Override + public Optional getUpsertStatement( + String database, String tableName, String[] fieldNames, String[] uniqueKeyFields) { + throw new UnsupportedOperationException(); + } + + @Override + public TablePath parse(String tablePath) { + return TablePath.of(tablePath, false); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericDialectFactory.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericDialectFactory.java new file mode 100644 index 00000000000..f0d32988bf9 --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericDialectFactory.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; + +import com.google.auto.service.AutoService; + +import javax.annotation.Nonnull; + +/** Factory for {@link GenericDialect}. */ +@AutoService(JdbcDialectFactory.class) +public class GenericDialectFactory implements JdbcDialectFactory { + + // GenericDialect does not have any special requirements. + @Override + public boolean acceptsURL(String url) { + return true; + } + + @Override + public JdbcDialect create() { + return new GenericDialect(); + } + + @Override + public JdbcDialect create(@Nonnull String compatibleMode, String fieldIde) { + return new GenericDialect(fieldIde); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericTypeConverter.java new file mode 100644 index 00000000000..4143367d9ff --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericTypeConverter.java @@ -0,0 +1,200 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; + +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.converter.BasicTypeDefine; +import org.apache.seatunnel.api.table.converter.TypeConverter; +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.DecimalType; +import org.apache.seatunnel.api.table.type.LocalTimeType; +import org.apache.seatunnel.api.table.type.PrimitiveByteArrayType; + +import com.google.auto.service.AutoService; +import lombok.extern.slf4j.Slf4j; + +import java.sql.Types; + +@Slf4j +@AutoService(TypeConverter.class) +public class GenericTypeConverter implements TypeConverter { + + public static final GenericTypeConverter DEFAULT_INSTANCE = new GenericTypeConverter(); + + public static final int MAX_PRECISION = 65; + public static final int DEFAULT_PRECISION = 38; + public static final int MAX_SCALE = MAX_PRECISION - 1; + public static final int DEFAULT_SCALE = 18; + + @Override + public String identifier() { + return DatabaseIdentifier.GENERIC; + } + + /** + * Convert an external system's type definition to {@link Column}. + * + * @param typeDefine type define + * @return column + */ + @Override + public Column convert(BasicTypeDefine typeDefine) { + PhysicalColumn.PhysicalColumnBuilder builder = + PhysicalColumn.builder() + .name(typeDefine.getName()) + .nullable(typeDefine.isNullable()) + .defaultValue(typeDefine.getDefaultValue()) + .comment(typeDefine.getComment()); + int sqlType = typeDefine.getSqlType(); + switch (sqlType) { + case Types.NULL: + builder.dataType(BasicType.VOID_TYPE); + break; + case Types.BOOLEAN: + builder.dataType(BasicType.BOOLEAN_TYPE); + break; + case Types.BIT: + if (typeDefine.getLength() == null + || typeDefine.getLength() <= 0 + || typeDefine.getLength() == 1) { + builder.dataType(BasicType.BOOLEAN_TYPE); + } else { + builder.dataType(PrimitiveByteArrayType.INSTANCE); + // BIT(M) -> BYTE(M/8) + long byteLength = typeDefine.getLength() / 8; + byteLength += typeDefine.getLength() % 8 > 0 ? 1 : 0; + builder.columnLength(byteLength); + } + break; + case Types.TINYINT: + builder.dataType(BasicType.BYTE_TYPE); + break; + case Types.SMALLINT: + builder.dataType(BasicType.SHORT_TYPE); + break; + case Types.INTEGER: + builder.dataType(BasicType.INT_TYPE); + break; + case Types.BIGINT: + builder.dataType(BasicType.LONG_TYPE); + break; + case Types.REAL: + case Types.FLOAT: + builder.dataType(BasicType.FLOAT_TYPE); + break; + case Types.DOUBLE: + builder.dataType(BasicType.DOUBLE_TYPE); + break; + case Types.NUMERIC: + DecimalType decimalTypeForNumeric; + if (typeDefine.getPrecision() != null && typeDefine.getPrecision() > 0) { + decimalTypeForNumeric = + new DecimalType( + typeDefine.getPrecision().intValue(), typeDefine.getScale()); + } else { + decimalTypeForNumeric = new DecimalType(DEFAULT_PRECISION, DEFAULT_SCALE); + } + builder.dataType(decimalTypeForNumeric); + break; + case Types.DECIMAL: + Preconditions.checkArgument(typeDefine.getPrecision() > 0); + DecimalType decimalType; + if (typeDefine.getPrecision() > DEFAULT_PRECISION) { + decimalType = new DecimalType(DEFAULT_PRECISION, DEFAULT_SCALE); + } else { + decimalType = + new DecimalType( + typeDefine.getPrecision().intValue(), + typeDefine.getScale() == null + ? 0 + : typeDefine.getScale().intValue()); + } + builder.dataType(decimalType); + builder.columnLength(Long.valueOf(decimalType.getPrecision())); + builder.scale(decimalType.getScale()); + break; + + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.NCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + case Types.CLOB: + case Types.DATALINK: + case Types.NCLOB: + case Types.SQLXML: + builder.dataType(BasicType.STRING_TYPE); + break; + + case Types.BINARY: + case Types.BLOB: + case Types.VARBINARY: + case Types.LONGVARBINARY: + if (typeDefine.getLength() == null || typeDefine.getLength() <= 0) { + builder.columnLength(1L); + } else { + builder.columnLength(typeDefine.getLength()); + } + builder.dataType(PrimitiveByteArrayType.INSTANCE); + break; + case Types.DATE: + builder.dataType(LocalTimeType.LOCAL_DATE_TYPE); + break; + case Types.TIME: + builder.dataType(LocalTimeType.LOCAL_TIME_TYPE); + builder.scale(typeDefine.getScale()); + break; + case Types.TIMESTAMP: + builder.dataType(LocalTimeType.LOCAL_DATE_TIME_TYPE); + builder.scale(typeDefine.getScale()); + break; + + case Types.OTHER: + case Types.ARRAY: + case Types.JAVA_OBJECT: + case Types.DISTINCT: + case Types.STRUCT: + case Types.REF: + case Types.ROWID: + default: + log.warn( + "JDBC type {} ({}) not currently supported", + sqlType, + typeDefine.getNativeType()); + } + return builder.build(); + } + + /** + * Convert {@link Column} to an external system's type definition. + * + * @param column + * @return + */ + @Override + public BasicTypeDefine reconvert(Column column) { + throw new UnsupportedOperationException( + String.format( + "%s (%s) type doesn't have a mapping to the SQL database column type", + column.getName(), column.getDataType().getSqlType().name())); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericTypeMapper.java new file mode 100644 index 00000000000..bf33c40527c --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/GenericTypeMapper.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; + +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.converter.BasicTypeDefine; + +public class GenericTypeMapper implements JdbcDialectTypeMapper { + + private GenericTypeConverter typeConverter; + + public GenericTypeMapper() { + this(GenericTypeConverter.DEFAULT_INSTANCE); + } + + public GenericTypeMapper(GenericTypeConverter typeConverter) { + this.typeConverter = typeConverter; + } + + @Override + public Column mappingColumn(BasicTypeDefine typeDefine) { + return typeConverter.convert(typeDefine); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java index 2fc0fe8dca8..eeb2723a77a 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java @@ -17,19 +17,17 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; -import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; -import org.apache.seatunnel.api.table.converter.ConverterLoader; import org.apache.seatunnel.api.table.converter.TypeConverter; -import org.apache.seatunnel.api.table.event.AlterTableAddColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableChangeColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnsEvent; -import org.apache.seatunnel.api.table.event.AlterTableDropColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableModifyColumnEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; -import org.apache.seatunnel.common.utils.SeaTunnelException; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnsEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableDropColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.type.SqlType; import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcConnectionConfig; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.connection.JdbcConnectionProvider; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.connection.SimpleJdbcConnectionProvider; @@ -82,6 +80,15 @@ public interface JdbcDialect extends Serializable { */ JdbcRowConverter getRowConverter(); + /** + * Get converter that convert type object to seatunnel internal type. + * + * @return a type converter for the database + */ + default TypeConverter getTypeConverter() { + throw new UnsupportedOperationException("TypeConverter is not supported"); + } + /** * get jdbc meta-information type to seatunnel data type mapper. * @@ -441,16 +448,16 @@ default String convertType(String columnName, String columnType) { /** * Refresh physical table schema by schema change event * - * @param event schema change event * @param connection jdbc connection * @param tablePath sink table path + * @param event schema change event */ default void applySchemaChange( - SchemaChangeEvent event, Connection connection, TablePath tablePath) + Connection connection, TablePath tablePath, SchemaChangeEvent event) throws SQLException { if (event instanceof AlterTableColumnsEvent) { for (AlterTableColumnEvent columnEvent : ((AlterTableColumnsEvent) event).getEvents()) { - applySchemaChange(columnEvent, connection, tablePath); + applySchemaChange(connection, tablePath, columnEvent); } } else { if (event instanceof AlterTableChangeColumnEvent) { @@ -497,8 +504,7 @@ && columnExists( } applySchemaChange(connection, tablePath, dropColumnEvent); } else { - throw new SeaTunnelException( - "Unsupported schemaChangeEvent : " + event.getEventType()); + throw new UnsupportedOperationException("Unsupported schemaChangeEvent: " + event); } } } @@ -527,18 +533,60 @@ default boolean columnExists(Connection connection, TablePath tablePath, String default void applySchemaChange( Connection connection, TablePath tablePath, AlterTableAddColumnEvent event) throws SQLException { - String tableIdentifierWithQuoted = tableIdentifier(tablePath); - Column addColumn = event.getColumn(); - String addColumnSQL = - buildAlterTableSql( - event.getSourceDialectName(), - addColumn.getSourceType(), - AlterType.ADD.name(), - addColumn, - tableIdentifierWithQuoted, - StringUtils.EMPTY); + boolean sameCatalog = StringUtils.equals(dialectName(), event.getSourceDialectName()); + BasicTypeDefine typeDefine = getTypeConverter().reconvert(event.getColumn()); + String columnType = + sameCatalog ? event.getColumn().getSourceType() : typeDefine.getColumnType(); + StringBuilder sqlBuilder = + new StringBuilder() + .append("ALTER TABLE") + .append(" ") + .append(tableIdentifier(tablePath)) + .append(" ") + .append("ADD COLUMN") + .append(" ") + .append(quoteIdentifier(event.getColumn().getName())) + .append(" ") + .append(columnType); + + // Only decorate with default value when source dialect is same as sink dialect + // Todo Support for cross-database default values for ddl statements + if (event.getColumn().getDefaultValue() == null) { + sqlBuilder.append(" ").append(event.getColumn().isNullable() ? "NULL" : "NOT NULL"); + } else { + if (event.getColumn().isNullable()) { + sqlBuilder.append(" NULL"); + } else if (sameCatalog) { + sqlBuilder.append(" ").append(event.getColumn().isNullable() ? "NULL" : "NOT NULL"); + } else if (SqlType.TIMESTAMP.equals(event.getColumn().getDataType().getSqlType())) { + log.warn( + "Default value is not supported for column {} in table {}. Skipping add column operation. event: {}", + event.getColumn().getName(), + tablePath.getFullName(), + event); + } else { + sqlBuilder.append(" NOT NULL"); + } + if (sameCatalog) { + sqlBuilder.append(" ").append(sqlClauseWithDefaultValue(typeDefine)); + } + } + + if (event.getColumn().getComment() != null) { + sqlBuilder + .append(" ") + .append("COMMENT ") + .append("'") + .append(event.getColumn().getComment()) + .append("'"); + } + if (event.getAfterColumn() != null) { + sqlBuilder.append(" ").append("AFTER ").append(quoteIdentifier(event.getAfterColumn())); + } + + String addColumnSQL = sqlBuilder.toString(); try (Statement statement = connection.createStatement()) { - log.info("Executing add column SQL: " + addColumnSQL); + log.info("Executing add column SQL: {}", addColumnSQL); statement.execute(addColumnSQL); } } @@ -546,20 +594,79 @@ default void applySchemaChange( default void applySchemaChange( Connection connection, TablePath tablePath, AlterTableChangeColumnEvent event) throws SQLException { - String tableIdentifierWithQuoted = tableIdentifier(tablePath); - Column changeColumn = event.getColumn(); - String oldColumnName = event.getOldColumn(); - String changeColumnSQL = - buildAlterTableSql( - event.getSourceDialectName(), - changeColumn.getSourceType(), - AlterType.CHANGE.name(), - changeColumn, - tableIdentifierWithQuoted, - oldColumnName); + if (event.getColumn().getDataType() == null) { + StringBuilder sqlBuilder = + new StringBuilder() + .append("ALTER TABLE") + .append(" ") + .append(tableIdentifier(tablePath)) + .append(" ") + .append("RENAME COLUMN") + .append(" ") + .append(quoteIdentifier(event.getOldColumn())) + .append(" TO ") + .append(quoteIdentifier(event.getColumn().getName())); + try (Statement statement = connection.createStatement()) { + log.info("Executing rename column SQL: {}", sqlBuilder); + statement.execute(sqlBuilder.toString()); + } + return; + } + + boolean sameCatalog = StringUtils.equals(dialectName(), event.getSourceDialectName()); + BasicTypeDefine typeDefine = getTypeConverter().reconvert(event.getColumn()); + String columnType = + sameCatalog ? event.getColumn().getSourceType() : typeDefine.getColumnType(); + StringBuilder sqlBuilder = + new StringBuilder() + .append("ALTER TABLE") + .append(" ") + .append(tableIdentifier(tablePath)) + .append(" ") + .append("CHANGE COLUMN") + .append(" ") + .append(quoteIdentifier(event.getOldColumn())) + .append(" ") + .append(quoteIdentifier(event.getColumn().getName())) + .append(" ") + .append(columnType); + // Only decorate with default value when source dialect is same as sink dialect + // Todo Support for cross-database default values for ddl statements + if (event.getColumn().getDefaultValue() == null) { + sqlBuilder.append(" ").append(event.getColumn().isNullable() ? "NULL" : "NOT NULL"); + } else { + if (event.getColumn().isNullable()) { + sqlBuilder.append(" NULL"); + } else if (sameCatalog) { + sqlBuilder.append(" ").append(event.getColumn().isNullable() ? "NULL" : "NOT NULL"); + } else if (SqlType.TIMESTAMP.equals(event.getColumn().getDataType().getSqlType())) { + log.warn( + "Default value is not supported for column {} in table {}. Skipping add column operation. event: {}", + event.getColumn().getName(), + tablePath.getFullName(), + event); + } else { + sqlBuilder.append(" NOT NULL"); + } + if (sameCatalog) { + sqlBuilder.append(" ").append(sqlClauseWithDefaultValue(typeDefine)); + } + } + if (event.getColumn().getComment() != null) { + sqlBuilder + .append(" ") + .append("COMMENT ") + .append("'") + .append(event.getColumn().getComment()) + .append("'"); + } + if (event.getAfterColumn() != null) { + sqlBuilder.append(" ").append("AFTER ").append(quoteIdentifier(event.getAfterColumn())); + } + String changeColumnSQL = sqlBuilder.toString(); try (Statement statement = connection.createStatement()) { - log.info("Executing change column SQL: " + changeColumnSQL); + log.info("Executing change column SQL: {}", changeColumnSQL); statement.execute(changeColumnSQL); } } @@ -567,19 +674,60 @@ default void applySchemaChange( default void applySchemaChange( Connection connection, TablePath tablePath, AlterTableModifyColumnEvent event) throws SQLException { - String tableIdentifierWithQuoted = tableIdentifier(tablePath); - Column modifyColumn = event.getColumn(); - String modifyColumnSQL = - buildAlterTableSql( - event.getSourceDialectName(), - modifyColumn.getSourceType(), - AlterType.MODIFY.name(), - modifyColumn, - tableIdentifierWithQuoted, - StringUtils.EMPTY); + boolean sameCatalog = StringUtils.equals(dialectName(), event.getSourceDialectName()); + BasicTypeDefine typeDefine = getTypeConverter().reconvert(event.getColumn()); + String columnType = + sameCatalog ? event.getColumn().getSourceType() : typeDefine.getColumnType(); + StringBuilder sqlBuilder = + new StringBuilder() + .append("ALTER TABLE") + .append(" ") + .append(tableIdentifier(tablePath)) + .append(" ") + .append("MODIFY COLUMN") + .append(" ") + .append(quoteIdentifier(event.getColumn().getName())) + .append(" ") + .append(columnType); + + // Only decorate with default value when source dialect is same as sink dialect + // Todo Support for cross-database default values for ddl statements + if (event.getColumn().getDefaultValue() == null) { + sqlBuilder.append(" ").append(event.getColumn().isNullable() ? "NULL" : "NOT NULL"); + } else { + if (event.getColumn().isNullable()) { + sqlBuilder.append(" NULL"); + } else if (sameCatalog) { + sqlBuilder.append(" ").append(event.getColumn().isNullable() ? "NULL" : "NOT NULL"); + } else if (SqlType.TIMESTAMP.equals(event.getColumn().getDataType().getSqlType())) { + log.warn( + "Default value is not supported for column {} in table {}. Skipping add column operation. event: {}", + event.getColumn().getName(), + tablePath.getFullName(), + event); + } else { + sqlBuilder.append(" NOT NULL"); + } + if (sameCatalog) { + sqlBuilder.append(" ").append(sqlClauseWithDefaultValue(typeDefine)); + } + } + if (event.getColumn().getComment() != null) { + sqlBuilder + .append(" ") + .append("COMMENT ") + .append("'") + .append(event.getColumn().getComment()) + .append("'"); + } + if (event.getAfterColumn() != null) { + sqlBuilder.append(" ").append("AFTER ").append(quoteIdentifier(event.getAfterColumn())); + } + + String modifyColumnSQL = sqlBuilder.toString(); try (Statement statement = connection.createStatement()) { - log.info("Executing modify column SQL: " + modifyColumnSQL); + log.info("Executing modify column SQL: {}", modifyColumnSQL); statement.execute(modifyColumnSQL); } } @@ -587,173 +735,40 @@ default void applySchemaChange( default void applySchemaChange( Connection connection, TablePath tablePath, AlterTableDropColumnEvent event) throws SQLException { - String tableIdentifierWithQuoted = tableIdentifier(tablePath); - String dropColumn = event.getColumn(); String dropColumnSQL = - buildAlterTableSql( - event.getSourceDialectName(), - null, - AlterType.DROP.name(), - null, - tableIdentifierWithQuoted, - dropColumn); + String.format( + "ALTER TABLE %s DROP COLUMN %s", + tableIdentifier(tablePath), quoteIdentifier(event.getColumn())); try (Statement statement = connection.createStatement()) { - log.info("Executing drop column SQL: " + dropColumnSQL); + log.info("Executing drop column SQL: {}", dropColumnSQL); statement.execute(dropColumnSQL); } } /** - * build alter table sql + * Get the SQL clause for define column default value * - * @param sourceDialectName source dialect name - * @param sourceColumnType source column type - * @param alterOperation alter operation of ddl - * @param newColumn new column after ddl - * @param tableName table name of sink table - * @param oldColumnName old column name before ddl - * @return alter table sql for sink table after schema change + * @param columnDefine column define + * @return SQL clause for define default value */ - default String buildAlterTableSql( - String sourceDialectName, - String sourceColumnType, - String alterOperation, - Column newColumn, - String tableName, - String oldColumnName) { - if (StringUtils.equals(alterOperation, AlterType.DROP.name())) { - return String.format( - "ALTER TABLE %s drop column %s", tableName, quoteIdentifier(oldColumnName)); - } - TypeConverter typeConverter = ConverterLoader.loadTypeConverter(dialectName()); - BasicTypeDefine typeBasicTypeDefine = (BasicTypeDefine) typeConverter.reconvert(newColumn); - - String basicSql = buildAlterTableBasicSql(alterOperation, tableName); - basicSql = - decorateWithColumnNameAndType( - sourceDialectName, - sourceColumnType, - basicSql, - alterOperation, - newColumn, - oldColumnName, - typeBasicTypeDefine.getColumnType()); - basicSql = decorateWithNullable(basicSql, typeBasicTypeDefine); - basicSql = decorateWithDefaultValue(basicSql, typeBasicTypeDefine); - basicSql = decorateWithComment(basicSql, typeBasicTypeDefine); - return basicSql + ";"; - } - - /** - * build the body of alter table sql - * - * @param alterOperation alter operation of ddl - * @param tableName table name of sink table - * @return basic sql of alter table for sink table - */ - default String buildAlterTableBasicSql(String alterOperation, String tableName) { - StringBuilder sql = - new StringBuilder( - "ALTER TABLE " - + tableName - + StringUtils.SPACE - + alterOperation - + StringUtils.SPACE); - return sql.toString(); - } - - /** - * decorate the sql with column name and type - * - * @param sourceDialectName source dialect name - * @param sourceColumnType source column type - * @param basicSql basic sql of alter table for sink table - * @param alterOperation alter operation of ddl - * @param newColumn new column after ddl - * @param oldColumnName old column name before ddl - * @param columnType column type of new column - * @return basic sql with column name and type of alter table for sink table - */ - default String decorateWithColumnNameAndType( - String sourceDialectName, - String sourceColumnType, - String basicSql, - String alterOperation, - Column newColumn, - String oldColumnName, - String columnType) { - StringBuilder sql = new StringBuilder(basicSql); - String oldColumnNameWithQuoted = quoteIdentifier(oldColumnName); - String newColumnNameWithQuoted = quoteIdentifier(newColumn.getName()); - if (alterOperation.equals(AlterType.CHANGE.name())) { - sql.append(oldColumnNameWithQuoted) - .append(StringUtils.SPACE) - .append(newColumnNameWithQuoted) - .append(StringUtils.SPACE); - } else { - sql.append(newColumnNameWithQuoted).append(StringUtils.SPACE); - } - if (sourceDialectName.equals(dialectName())) { - sql.append(sourceColumnType); - } else { - sql.append(columnType); - } - sql.append(StringUtils.SPACE); - return sql.toString(); - } - - /** - * decorate with nullable - * - * @param basicSql alter table sql for sink table - * @param typeBasicTypeDefine type basic type define of new column - * @return alter table sql with nullable for sink table - */ - default String decorateWithNullable(String basicSql, BasicTypeDefine typeBasicTypeDefine) { - StringBuilder sql = new StringBuilder(basicSql); - if (typeBasicTypeDefine.isNullable()) { - sql.append("NULL "); - } else { - sql.append("NOT NULL "); - } - return sql.toString(); - } - - /** - * decorate with default value - * - * @param basicSql alter table sql for sink table - * @param typeBasicTypeDefine type basic type define of new column - * @return alter table sql with default value for sink table - */ - default String decorateWithDefaultValue(String basicSql, BasicTypeDefine typeBasicTypeDefine) { - Object defaultValue = typeBasicTypeDefine.getDefaultValue(); + default String sqlClauseWithDefaultValue(BasicTypeDefine columnDefine) { + Object defaultValue = columnDefine.getDefaultValue(); if (Objects.nonNull(defaultValue) - && needsQuotesWithDefaultValue(typeBasicTypeDefine.getColumnType()) + && needsQuotesWithDefaultValue(columnDefine.getColumnType()) && !isSpecialDefaultValue(defaultValue)) { defaultValue = quotesDefaultValue(defaultValue); } - StringBuilder sql = new StringBuilder(basicSql); - if (Objects.nonNull(defaultValue)) { - sql.append("DEFAULT ").append(defaultValue).append(StringUtils.SPACE); - } - return sql.toString(); + return "DEFAULT " + defaultValue; } /** - * decorate with comment + * Whether support default value * - * @param basicSql alter table sql for sink table - * @param typeBasicTypeDefine type basic type define of new column - * @return alter table sql with comment for sink table + * @param columnDefine column define + * @return whether support set default value */ - default String decorateWithComment(String basicSql, BasicTypeDefine typeBasicTypeDefine) { - String comment = typeBasicTypeDefine.getComment(); - StringBuilder sql = new StringBuilder(basicSql); - if (StringUtils.isNotBlank(comment)) { - sql.append("COMMENT '").append(comment).append("'"); - } - return sql.toString(); + default boolean supportDefaultValue(BasicTypeDefine columnDefine) { + return true; } /** @@ -785,11 +800,4 @@ default boolean isSpecialDefaultValue(Object defaultValue) { default String quotesDefaultValue(Object defaultValue) { return "'" + defaultValue + "'"; } - - enum AlterType { - ADD, - DROP, - MODIFY, - CHANGE - } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoader.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoader.java index 350a22e20c6..7dc71835aec 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoader.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoader.java @@ -61,24 +61,17 @@ public static JdbcDialect load(String url, String compatibleMode, String fieldId JdbcDialectFactory.class.getName())); } - final List matchingFactories = + List matchingFactories = foundFactories.stream().filter(f -> f.acceptsURL(url)).collect(Collectors.toList()); - if (matchingFactories.isEmpty()) { - throw new JdbcConnectorException( - JdbcConnectorErrorCode.NO_SUITABLE_DIALECT_FACTORY, - String.format( - "Could not find any jdbc dialect factory that can handle url '%s' that implements '%s' in the classpath.\n\n" - + "Available factories are:\n\n" - + "%s", - url, - JdbcDialectFactory.class.getName(), - foundFactories.stream() - .map(f -> f.getClass().getName()) - .distinct() - .sorted() - .collect(Collectors.joining("\n")))); + // filter out generic dialect factory + if (matchingFactories.size() > 1) { + matchingFactories = + matchingFactories.stream() + .filter(f -> !(f instanceof GenericDialectFactory)) + .collect(Collectors.toList()); } + if (matchingFactories.size() > 1) { throw new JdbcConnectorException( JdbcConnectorErrorCode.NO_SUITABLE_DIALECT_FACTORY, diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectTypeMapper.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectTypeMapper.java index 7da3fdb8431..0b87f7b0d97 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectTypeMapper.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectTypeMapper.java @@ -68,6 +68,7 @@ default SeaTunnelDataType mapping(ResultSetMetaData metadata, int colIndex) .name(columnName) .columnType(nativeType) .dataType(nativeType) + .sqlType(metadata.getColumnType(colIndex)) .nullable(isNullable == ResultSetMetaData.columnNullable) .length((long) precision) .precision((long) precision) @@ -93,6 +94,7 @@ default List mappingColumn( while (rs.next()) { String columnName = rs.getString("COLUMN_NAME"); String nativeType = rs.getString("TYPE_NAME"); + int sqlType = rs.getInt("DATA_TYPE"); int columnSize = rs.getInt("COLUMN_SIZE"); int decimalDigits = rs.getInt("DECIMAL_DIGITS"); int nullable = rs.getInt("NULLABLE"); @@ -102,6 +104,7 @@ default List mappingColumn( .name(columnName) .columnType(nativeType) .dataType(nativeType) + .sqlType(sqlType) .length((long) columnSize) .precision((long) columnSize) .scale(decimalDigits) diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2Dialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2Dialect.java index 6150dd4330d..5af57bf1045 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2Dialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/db2/DB2Dialect.java @@ -22,7 +22,9 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialect; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialectTypeMapper; +import java.util.Arrays; import java.util.Optional; +import java.util.stream.Collectors; public class DB2Dialect implements JdbcDialect { @@ -44,6 +46,56 @@ public JdbcDialectTypeMapper getJdbcDialectTypeMapper() { @Override public Optional getUpsertStatement( String database, String tableName, String[] fieldNames, String[] uniqueKeyFields) { - return Optional.empty(); + // Generate field list for USING and INSERT clauses + String fieldList = String.join(", ", fieldNames); + + // Generate placeholder list for VALUES clause + String placeholderList = + Arrays.stream(fieldNames).map(field -> "?").collect(Collectors.joining(", ")); + + // Generate ON clause + String onClause = + Arrays.stream(uniqueKeyFields) + .map(field -> "target." + field + " = source." + field) + .collect(Collectors.joining(" AND ")); + + // Generate WHEN MATCHED clause + String whenMatchedClause = + Arrays.stream(fieldNames) + .map(field -> "target." + field + " <> source." + field) + .collect(Collectors.joining(" OR ")); + + // Generate UPDATE SET clause + String updateSetClause = + Arrays.stream(fieldNames) + .map(field -> "target." + field + " = source." + field) + .collect(Collectors.joining(", ")); + + // Generate WHEN NOT MATCHED clause + String insertClause = + "INSERT (" + + fieldList + + ") VALUES (" + + Arrays.stream(fieldNames) + .map(field -> "source." + field) + .collect(Collectors.joining(", ")) + + ")"; + + // Combine all parts to form the final SQL statement + String mergeStatement = + String.format( + "MERGE INTO %s.%s AS target USING (VALUES (%s)) AS source (%s) ON %s " + + "WHEN MATCHED AND (%s) THEN UPDATE SET %s " + + "WHEN NOT MATCHED THEN %s;", + database, + tableName, + placeholderList, + fieldList, + onClause, + whenMatchedClause, + updateSetClause, + insertClause); + + return Optional.of(mergeStatement); } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbDialect.java index da652d6b142..dd3965e8434 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/dm/DmdbDialect.java @@ -94,12 +94,7 @@ public Optional getUpsertStatement( // If there is a schema in the sql of dm, an error will be reported. // This is compatible with the case that the schema is written or not written in the conf // configuration file - String databaseName = - database == null - ? quoteIdentifier(tableName) - : (tableName.contains(".") - ? quoteIdentifier(tableName) - : tableIdentifier(database, tableName)); + String databaseName = tableIdentifier(database, tableName); String upsertSQL = String.format( " MERGE INTO %s TARGET" @@ -137,6 +132,9 @@ public String tableIdentifier(TablePath tablePath) { // Compatibility Both database = mode and table-names = schema.tableName are configured @Override public String tableIdentifier(String database, String tableName) { + if (database == null) { + return quoteIdentifier(tableName); + } if (tableName.contains(".")) { return quoteIdentifier(tableName); } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MySqlTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MySqlTypeConverter.java index f6534cd3695..50f81f18835 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MySqlTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MySqlTypeConverter.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; @@ -30,7 +32,6 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import com.google.auto.service.AutoService; -import com.google.common.base.Preconditions; import com.mysql.cj.MysqlType; import lombok.extern.slf4j.Slf4j; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java index 22431b0d96f..cc3f1300b5d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java @@ -19,6 +19,7 @@ import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; +import org.apache.seatunnel.api.table.converter.TypeConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.JdbcRowConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialect; @@ -70,6 +71,12 @@ public JdbcRowConverter getRowConverter() { return new MysqlJdbcRowConverter(); } + @Override + public TypeConverter getTypeConverter() { + TypeConverter typeConverter = MySqlTypeConverter.DEFAULT_INSTANCE; + return typeConverter; + } + @Override public JdbcDialectTypeMapper getJdbcDialectTypeMapper() { return new MySqlTypeMapper(); @@ -228,12 +235,9 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta } @Override - public String decorateWithComment(String basicSql, BasicTypeDefine typeBasicTypeDefine) { + public boolean supportDefaultValue(BasicTypeDefine typeBasicTypeDefine) { MysqlType nativeType = (MysqlType) typeBasicTypeDefine.getNativeType(); - if (NOT_SUPPORTED_DEFAULT_VALUES.contains(nativeType)) { - return basicSql; - } - return JdbcDialect.super.decorateWithComment(basicSql, typeBasicTypeDefine); + return !(NOT_SUPPORTED_DEFAULT_VALUES.contains(nativeType)); } @Override diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMySqlTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMySqlTypeConverter.java index 4e9fa04d0d3..f7790ff178c 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMySqlTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMySqlTypeConverter.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oceanbase; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; @@ -25,12 +27,12 @@ import org.apache.seatunnel.api.table.type.DecimalType; import org.apache.seatunnel.api.table.type.LocalTimeType; import org.apache.seatunnel.api.table.type.PrimitiveByteArrayType; +import org.apache.seatunnel.api.table.type.VectorType; import org.apache.seatunnel.common.exception.CommonError; import org.apache.seatunnel.connectors.seatunnel.common.source.TypeDefineUtils; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import com.google.auto.service.AutoService; -import com.google.common.base.Preconditions; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -100,6 +102,11 @@ public class OceanBaseMySqlTypeConverter public static final long POWER_2_32 = (long) Math.pow(2, 32); public static final long MAX_VARBINARY_LENGTH = POWER_2_16 - 4; + private static final String VECTOR_TYPE_NAME = ""; + private static final String VECTOR_NAME = "VECTOR"; + + public static final OceanBaseMySqlTypeConverter INSTANCE = new OceanBaseMySqlTypeConverter(); + @Override public String identifier() { return DatabaseIdentifier.OCENABASE; @@ -289,6 +296,17 @@ public Column convert(BasicTypeDefine typeDefine) { builder.dataType(LocalTimeType.LOCAL_DATE_TIME_TYPE); builder.scale(typeDefine.getScale()); break; + case VECTOR_TYPE_NAME: + String columnType = typeDefine.getColumnType(); + if (columnType.startsWith("vector(") && columnType.endsWith(")")) { + Integer number = + Integer.parseInt( + columnType.substring( + columnType.indexOf("(") + 1, columnType.indexOf(")"))); + builder.dataType(VectorType.VECTOR_FLOAT_TYPE); + builder.scale(number); + } + break; default: throw CommonError.convertToSeaTunnelTypeError( DatabaseIdentifier.OCENABASE, mysqlDataType, typeDefine.getName()); @@ -501,6 +519,11 @@ public BasicTypeDefine reconvert(Column column) { builder.columnType(MYSQL_DATETIME); } break; + case FLOAT_VECTOR: + builder.nativeType(VECTOR_NAME); + builder.columnType(String.format("%s(%s)", VECTOR_NAME, column.getScale())); + builder.dataType(VECTOR_NAME); + break; default: throw CommonError.convertToConnectorTypeError( DatabaseIdentifier.OCENABASE, diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java index 1c5d7734fb2..315eccaa52d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlDialect.java @@ -19,6 +19,7 @@ import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.converter.BasicTypeDefine; +import org.apache.seatunnel.api.table.converter.TypeConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.JdbcRowConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialect; @@ -73,6 +74,12 @@ public JdbcRowConverter getRowConverter() { return new OceanBaseMysqlJdbcRowConverter(); } + @Override + public TypeConverter getTypeConverter() { + TypeConverter typeConverter = OceanBaseMySqlTypeConverter.INSTANCE; + return typeConverter; + } + @Override public JdbcDialectTypeMapper getJdbcDialectTypeMapper() { return new OceanBaseMySqlTypeMapper(); @@ -231,12 +238,9 @@ public Long approximateRowCntStatement(Connection connection, JdbcSourceTable ta } @Override - public String decorateWithComment(String basicSql, BasicTypeDefine typeBasicTypeDefine) { + public boolean supportDefaultValue(BasicTypeDefine typeBasicTypeDefine) { OceanBaseMysqlType nativeType = (OceanBaseMysqlType) typeBasicTypeDefine.getNativeType(); - if (NOT_SUPPORTED_DEFAULT_VALUES.contains(nativeType)) { - return basicSql; - } - return JdbcDialect.super.decorateWithComment(basicSql, typeBasicTypeDefine); + return !(NOT_SUPPORTED_DEFAULT_VALUES.contains(nativeType)); } @Override diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlJdbcRowConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlJdbcRowConverter.java index a498879138d..0a52e6a90be 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlJdbcRowConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oceanbase/OceanBaseMysqlJdbcRowConverter.java @@ -32,6 +32,8 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.jdbc.utils.JdbcFieldTypeUtils; +import org.apache.commons.lang3.StringUtils; + import java.math.BigDecimal; import java.nio.ByteBuffer; import java.sql.Date; @@ -89,12 +91,16 @@ public SeaTunnelRow toInternal(ResultSet rs, TableSchema tableSchema) throws SQL fields[fieldIndex] = JdbcFieldTypeUtils.getFloat(rs, resultSetIndex); break; case FLOAT_VECTOR: - Object[] objects = (Object[]) rs.getObject(fieldIndex); - Float[] arrays = new Float[objects.length]; - for (int i = 0; i < objects.length; i++) { - arrays[i] = Float.parseFloat(objects[i].toString()); + String result = JdbcFieldTypeUtils.getString(rs, resultSetIndex); + if (StringUtils.isNotBlank(result)) { + result = result.replace("[", "").replace("]", ""); + String[] stringArray = result.split(","); + Float[] arrays = new Float[stringArray.length]; + for (int i = 0; i < stringArray.length; i++) { + arrays[i] = Float.parseFloat(stringArray[i]); + } + fields[fieldIndex] = BufferUtils.toByteBuffer(arrays); } - fields[fieldIndex] = BufferUtils.toByteBuffer(arrays); break; case DOUBLE: fields[fieldIndex] = JdbcFieldTypeUtils.getDouble(rs, resultSetIndex); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/opengauss/OpenGaussDialectFactory.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/opengauss/OpenGaussDialectFactory.java new file mode 100644 index 00000000000..b1ceed51e9b --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/opengauss/OpenGaussDialectFactory.java @@ -0,0 +1,31 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.opengauss; + +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialectFactory; +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.psql.PostgresDialectFactory; + +import com.google.auto.service.AutoService; + +@AutoService(JdbcDialectFactory.class) +public class OpenGaussDialectFactory extends PostgresDialectFactory { + + @Override + public boolean acceptsURL(String url) { + return url.startsWith("jdbc:opengauss:"); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java index b6a35dba0c1..b3d9b6334ab 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleDialect.java @@ -17,7 +17,14 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle; +import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.converter.BasicTypeDefine; +import org.apache.seatunnel.api.table.converter.TypeConverter; +import org.apache.seatunnel.api.table.schema.event.AlterTableAddColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableChangeColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableColumnEvent; +import org.apache.seatunnel.api.table.schema.event.AlterTableModifyColumnEvent; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.JdbcRowConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDialect; @@ -63,6 +70,11 @@ public JdbcRowConverter getRowConverter() { return new OracleJdbcRowConverter(); } + @Override + public TypeConverter getTypeConverter() { + return OracleTypeConverter.INSTANCE; + } + @Override public String hashModForField(String fieldName, int mod) { return "MOD(ORA_HASH(" + quoteIdentifier(fieldName) + ")," + mod + ")"; @@ -326,4 +338,148 @@ public Object[] sampleDataFromColumn( } } } + + @Override + public void applySchemaChange( + Connection connection, TablePath tablePath, AlterTableAddColumnEvent event) + throws SQLException { + List ddlSQL = new ArrayList<>(); + ddlSQL.add(buildUpdateColumnSQL(connection, tablePath, event)); + + if (event.getColumn().getComment() != null) { + ddlSQL.add(buildUpdateColumnCommentSQL(tablePath, event.getColumn())); + } + + try (Statement statement = connection.createStatement()) { + for (String sql : ddlSQL) { + log.info("Executing add column SQL: {}", sql); + statement.execute(sql); + } + } + } + + @Override + public void applySchemaChange( + Connection connection, TablePath tablePath, AlterTableChangeColumnEvent event) + throws SQLException { + List ddlSQL = new ArrayList<>(); + if (event.getOldColumn() != null + && !(event.getColumn().getName().equals(event.getOldColumn()))) { + StringBuilder sqlBuilder = + new StringBuilder() + .append("ALTER TABLE ") + .append(tableIdentifier(tablePath)) + .append(" RENAME COLUMN ") + .append(quoteIdentifier(event.getOldColumn())) + .append(" TO ") + .append(quoteIdentifier(event.getColumn().getName())); + ddlSQL.add(sqlBuilder.toString()); + } + + try (Statement statement = connection.createStatement()) { + for (String sql : ddlSQL) { + log.info("Executing change column SQL: {}", sql); + statement.execute(sql); + } + } + + if (event.getColumn().getDataType() != null) { + applySchemaChange( + connection, + tablePath, + AlterTableModifyColumnEvent.modify(event.tableIdentifier(), event.getColumn())); + } + } + + @Override + public void applySchemaChange( + Connection connection, TablePath tablePath, AlterTableModifyColumnEvent event) + throws SQLException { + List ddlSQL = new ArrayList<>(); + ddlSQL.add(buildUpdateColumnSQL(connection, tablePath, event)); + + if (event.getColumn().getComment() != null) { + ddlSQL.add(buildUpdateColumnCommentSQL(tablePath, event.getColumn())); + } + + try (Statement statement = connection.createStatement()) { + for (String sql : ddlSQL) { + log.info("Executing modify column SQL: {}", sql); + statement.execute(sql); + } + } + } + + private String buildUpdateColumnSQL( + Connection connection, TablePath tablePath, AlterTableColumnEvent event) + throws SQLException { + String actionType; + Column column; + if (event instanceof AlterTableModifyColumnEvent) { + actionType = "MODIFY"; + column = ((AlterTableModifyColumnEvent) event).getColumn(); + } else if (event instanceof AlterTableAddColumnEvent) { + actionType = "ADD"; + column = ((AlterTableAddColumnEvent) event).getColumn(); + } else { + throw new IllegalArgumentException("Unsupported AlterTableColumnEvent: " + event); + } + + boolean sameCatalog = StringUtils.equals(dialectName(), event.getSourceDialectName()); + BasicTypeDefine typeDefine = getTypeConverter().reconvert(column); + String columnType = sameCatalog ? column.getSourceType() : typeDefine.getColumnType(); + StringBuilder sqlBuilder = + new StringBuilder() + .append("ALTER TABLE ") + .append(tableIdentifier(tablePath)) + .append(" ") + .append(actionType) + .append(" ") + .append(quoteIdentifier(column.getName())) + .append(" ") + .append(columnType); + // Only decorate with default value when source dialect is same as sink dialect + // Todo Support for cross-database default values for ddl statements + if (column.getDefaultValue() != null && sameCatalog) { + sqlBuilder.append(" ").append(sqlClauseWithDefaultValue(typeDefine)); + } + if (event instanceof AlterTableModifyColumnEvent) { + boolean targetColumnNullable = + columnIsNullable(connection, tablePath, column.getName()); + if (column.isNullable() != targetColumnNullable) { + sqlBuilder.append(" ").append(column.isNullable() ? "NULL" : "NOT NULL"); + } + } else { + sqlBuilder.append(" ").append(column.isNullable() ? "NULL" : "NOT NULL"); + } + return sqlBuilder.toString(); + } + + private String buildUpdateColumnCommentSQL(TablePath tablePath, Column column) { + return String.format( + "COMMENT ON COLUMN %s.%s IS '%s'", + tableIdentifier(tablePath), quoteIdentifier(column.getName()), column.getComment()); + } + + private boolean columnIsNullable(Connection connection, TablePath tablePath, String column) + throws SQLException { + String selectColumnSQL = + "SELECT" + + " NULLABLE FROM" + + " ALL_TAB_COLUMNS c" + + " WHERE c.owner = '" + + tablePath.getSchemaName() + + "'" + + " AND c.table_name = '" + + tablePath.getTableName() + + "'" + + " AND c.column_name = '" + + column + + "'"; + try (Statement statement = connection.createStatement()) { + ResultSet rs = statement.executeQuery(selectColumnSQL); + rs.next(); + return rs.getString("NULLABLE").equals("Y"); + } + } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresJdbcRowConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresJdbcRowConverter.java index f1cd4f8ec98..071e8ec6e1d 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresJdbcRowConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresJdbcRowConverter.java @@ -17,26 +17,38 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.psql; +import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.TableSchema; import org.apache.seatunnel.api.table.type.ArrayType; import org.apache.seatunnel.api.table.type.SeaTunnelDataType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.api.table.type.SqlType; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; +import org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.AbstractJdbcRowConverter; import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.DatabaseIdentifier; import org.apache.seatunnel.connectors.seatunnel.jdbc.utils.JdbcFieldTypeUtils; +import org.postgresql.util.PGobject; + +import java.math.BigDecimal; import java.sql.Array; import java.sql.Date; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Locale; import java.util.Optional; +import static org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.psql.PostgresTypeConverter.PG_INET; + public class PostgresJdbcRowConverter extends AbstractJdbcRowConverter { private static final String PG_GEOMETRY = "GEOMETRY"; @@ -143,4 +155,114 @@ public SeaTunnelRow toInternal(ResultSet rs, TableSchema tableSchema) throws SQL } return new SeaTunnelRow(fields); } + + @Override + public PreparedStatement toExternal( + TableSchema tableSchema, SeaTunnelRow row, PreparedStatement statement) + throws SQLException { + SeaTunnelRowType rowType = tableSchema.toPhysicalRowDataType(); + String[] sourceTypes = + tableSchema.getColumns().stream() + .filter(Column::isPhysical) + .map(Column::getSourceType) + .toArray(String[]::new); + for (int fieldIndex = 0; fieldIndex < rowType.getTotalFields(); fieldIndex++) { + try { + SeaTunnelDataType seaTunnelDataType = rowType.getFieldType(fieldIndex); + int statementIndex = fieldIndex + 1; + Object fieldValue = row.getField(fieldIndex); + if (fieldValue == null) { + statement.setObject(statementIndex, null); + continue; + } + + switch (seaTunnelDataType.getSqlType()) { + case STRING: + String sourceType = sourceTypes[fieldIndex]; + if (PG_INET.equalsIgnoreCase(sourceType)) { + PGobject inetObject = new PGobject(); + inetObject.setType(PG_INET); + inetObject.setValue(String.valueOf(row.getField(fieldIndex))); + statement.setObject(statementIndex, inetObject); + } else { + statement.setString(statementIndex, (String) row.getField(fieldIndex)); + } + break; + case BOOLEAN: + statement.setBoolean(statementIndex, (Boolean) row.getField(fieldIndex)); + break; + case TINYINT: + statement.setByte(statementIndex, (Byte) row.getField(fieldIndex)); + break; + case SMALLINT: + statement.setShort(statementIndex, (Short) row.getField(fieldIndex)); + break; + case INT: + statement.setInt(statementIndex, (Integer) row.getField(fieldIndex)); + break; + case BIGINT: + statement.setLong(statementIndex, (Long) row.getField(fieldIndex)); + break; + case FLOAT: + statement.setFloat(statementIndex, (Float) row.getField(fieldIndex)); + break; + case DOUBLE: + statement.setDouble(statementIndex, (Double) row.getField(fieldIndex)); + break; + case DECIMAL: + statement.setBigDecimal( + statementIndex, (BigDecimal) row.getField(fieldIndex)); + break; + case DATE: + LocalDate localDate = (LocalDate) row.getField(fieldIndex); + statement.setDate(statementIndex, java.sql.Date.valueOf(localDate)); + break; + case TIME: + writeTime(statement, statementIndex, (LocalTime) row.getField(fieldIndex)); + break; + case TIMESTAMP: + LocalDateTime localDateTime = (LocalDateTime) row.getField(fieldIndex); + statement.setTimestamp( + statementIndex, java.sql.Timestamp.valueOf(localDateTime)); + break; + case BYTES: + statement.setBytes(statementIndex, (byte[]) row.getField(fieldIndex)); + break; + case NULL: + statement.setNull(statementIndex, java.sql.Types.NULL); + break; + case ARRAY: + SeaTunnelDataType elementType = + ((ArrayType) seaTunnelDataType).getElementType(); + Object[] array = (Object[]) row.getField(fieldIndex); + if (array == null) { + statement.setNull(statementIndex, java.sql.Types.ARRAY); + break; + } + if (SqlType.TINYINT.equals(elementType.getSqlType())) { + Short[] shortArray = new Short[array.length]; + for (int i = 0; i < array.length; i++) { + shortArray[i] = Short.valueOf(array[i].toString()); + } + statement.setObject(statementIndex, shortArray); + } else { + statement.setObject(statementIndex, array); + } + break; + case MAP: + case ROW: + default: + throw new JdbcConnectorException( + CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE, + "Unexpected value: " + seaTunnelDataType); + } + } catch (Exception e) { + throw new JdbcConnectorException( + JdbcConnectorErrorCode.DATA_TYPE_CAST_FAILED, + "error field:" + rowType.getFieldNames()[fieldIndex], + e); + } + } + return statement; + } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java index 322bdc2a99e..980dd760e9f 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/psql/PostgresTypeConverter.java @@ -81,6 +81,7 @@ public class PostgresTypeConverter implements TypeConverter { public static final String PG_CHAR_ARRAY = "_bpchar"; // character varying <=> varchar public static final String PG_VARCHAR = "varchar"; + public static final String PG_INET = "inet"; public static final String PG_CHARACTER_VARYING = "character varying"; // character varying[] <=> varchar[] <=> _varchar public static final String PG_VARCHAR_ARRAY = "_varchar"; @@ -221,7 +222,9 @@ public Column convert(BasicTypeDefine typeDefine) { case PG_XML: case PG_GEOMETRY: case PG_GEOGRAPHY: + case PG_INET: builder.dataType(BasicType.STRING_TYPE); + builder.sourceType(pgDataType); break; case PG_CHAR_ARRAY: case PG_VARCHAR_ARRAY: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java index 89344b43cad..a6f3791a694 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverter.java @@ -252,9 +252,9 @@ public Column convert(BasicTypeDefine typeDefine) { ? typeDefine.getLength().intValue() : MAX_PRECISION - 4; if (scale == null) { - builder.dataType(new DecimalType((int) precision, MAX_SCALE)); + builder.dataType(new DecimalType((int) precision, 0)); builder.columnLength(precision); - builder.scale(MAX_SCALE); + builder.scale(0); } else if (scale < 0) { int newPrecision = (int) (precision - scale); if (newPrecision == 1) { @@ -277,16 +277,17 @@ public Column convert(BasicTypeDefine typeDefine) { } break; case HANA_SMALLDECIMAL: + int smallDecimalScale = typeDefine.getScale() != null ? typeDefine.getScale() : 0; if (typeDefine.getPrecision() == null) { - builder.dataType(new DecimalType(DEFAULT_PRECISION, MAX_SMALL_DECIMAL_SCALE)); + builder.dataType(new DecimalType(DEFAULT_PRECISION, smallDecimalScale)); builder.columnLength((long) DEFAULT_PRECISION); - builder.scale(MAX_SMALL_DECIMAL_SCALE); + builder.scale(smallDecimalScale); } else { builder.dataType( new DecimalType( - typeDefine.getPrecision().intValue(), MAX_SMALL_DECIMAL_SCALE)); + typeDefine.getPrecision().intValue(), smallDecimalScale)); builder.columnLength(typeDefine.getPrecision()); - builder.scale(MAX_SMALL_DECIMAL_SCALE); + builder.scale(smallDecimalScale); } break; case HANA_REAL: @@ -297,6 +298,7 @@ public Column convert(BasicTypeDefine typeDefine) { break; case HANA_ST_POINT: case HANA_ST_GEOMETRY: + builder.columnLength(typeDefine.getLength()); builder.dataType(PrimitiveByteArrayType.INSTANCE); break; default: diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaFacadeImplAutoLoad.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaFacadeImplAutoLoad.java index 0fba9a47a1c..5dedc0e4755 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaFacadeImplAutoLoad.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaFacadeImplAutoLoad.java @@ -45,8 +45,6 @@ import java.util.function.Consumer; import java.util.function.Function; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; import static java.util.Optional.empty; import static java.util.Optional.of; import static javax.transaction.xa.XAException.XAER_NOTA; @@ -61,6 +59,8 @@ import static javax.transaction.xa.XAResource.TMENDRSCAN; import static javax.transaction.xa.XAResource.TMNOFLAGS; import static javax.transaction.xa.XAResource.TMSTARTRSCAN; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkState; /** * Default {@link org.apache.seatunnel.connectors.seatunnel.jdbc.internal.xa.XaFacade} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/AbstractJdbcSinkWriter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/AbstractJdbcSinkWriter.java index f894999a42f..7ab289edc18 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/AbstractJdbcSinkWriter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/AbstractJdbcSinkWriter.java @@ -19,18 +19,12 @@ import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSinkWriter; -import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSinkWriter; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.catalog.TableSchema; -import org.apache.seatunnel.api.table.event.AlterTableAddColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableChangeColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableColumnsEvent; -import org.apache.seatunnel.api.table.event.AlterTableDropColumnEvent; -import org.apache.seatunnel.api.table.event.AlterTableModifyColumnEvent; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.handler.TableSchemaChangeEventDispatcher; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcSinkConfig; import org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException; @@ -42,18 +36,16 @@ import org.apache.seatunnel.connectors.seatunnel.jdbc.state.JdbcSinkState; import org.apache.seatunnel.connectors.seatunnel.jdbc.state.XidInfo; -import org.apache.commons.lang3.StringUtils; - import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.sql.Connection; -import java.util.List; @Slf4j public abstract class AbstractJdbcSinkWriter implements SinkWriter, - SupportMultiTableSinkWriter { + SupportMultiTableSinkWriter, + SupportSchemaEvolutionSinkWriter { protected JdbcDialect dialect; protected TablePath sinkTablePath; @@ -62,64 +54,22 @@ public abstract class AbstractJdbcSinkWriter protected JdbcConnectionProvider connectionProvider; protected JdbcSinkConfig jdbcSinkConfig; protected JdbcOutputFormat> outputFormat; + protected TableSchemaChangeEventDispatcher tableSchemaChanger = + new TableSchemaChangeEventDispatcher(); @Override public void applySchemaChange(SchemaChangeEvent event) throws IOException { - if (event instanceof AlterTableColumnsEvent) { - AlterTableColumnsEvent alterTableColumnsEvent = (AlterTableColumnsEvent) event; - List events = alterTableColumnsEvent.getEvents(); - for (AlterTableColumnEvent alterTableColumnEvent : events) { - String sourceDialectName = alterTableColumnEvent.getSourceDialectName(); - if (StringUtils.isBlank(sourceDialectName)) { - throw new SeaTunnelException( - "The sourceDialectName in AlterTableColumnEvent can not be empty. event: " - + event); - } - processSchemaChangeEvent(alterTableColumnEvent); - } - } else { - log.warn("We only support AlterTableColumnsEvent, but actual event is " + event); - } - } - - protected void processSchemaChangeEvent(AlterTableColumnEvent event) throws IOException { - TableSchema newTableSchema = this.tableSchema.copy(); - List columns = newTableSchema.getColumns(); - switch (event.getEventType()) { - case SCHEMA_CHANGE_ADD_COLUMN: - Column addColumn = ((AlterTableAddColumnEvent) event).getColumn(); - columns.add(addColumn); - break; - case SCHEMA_CHANGE_DROP_COLUMN: - String dropColumn = ((AlterTableDropColumnEvent) event).getColumn(); - columns.removeIf(column -> column.getName().equalsIgnoreCase(dropColumn)); - break; - case SCHEMA_CHANGE_MODIFY_COLUMN: - Column modifyColumn = ((AlterTableModifyColumnEvent) event).getColumn(); - replaceColumnByIndex(columns, modifyColumn.getName(), modifyColumn); - break; - case SCHEMA_CHANGE_CHANGE_COLUMN: - AlterTableChangeColumnEvent alterTableChangeColumnEvent = - (AlterTableChangeColumnEvent) event; - Column changeColumn = alterTableChangeColumnEvent.getColumn(); - String oldColumnName = alterTableChangeColumnEvent.getOldColumn(); - replaceColumnByIndex(columns, oldColumnName, changeColumn); - break; - default: - throw new SeaTunnelException( - "Unsupported schemaChangeEvent for event type: " + event.getEventType()); - } - this.tableSchema = newTableSchema; + this.tableSchema = tableSchemaChanger.reset(tableSchema).apply(event); reOpenOutputFormat(event); } - protected void reOpenOutputFormat(AlterTableColumnEvent event) throws IOException { + protected void reOpenOutputFormat(SchemaChangeEvent event) throws IOException { this.prepareCommit(); JdbcConnectionProvider refreshTableSchemaConnectionProvider = dialect.getJdbcConnectionProvider(jdbcSinkConfig.getJdbcConnectionConfig()); try (Connection connection = refreshTableSchemaConnectionProvider.getOrEstablishConnection()) { - dialect.applySchemaChange(event, connection, sinkTablePath); + dialect.applySchemaChange(connection, sinkTablePath, event); } catch (Throwable e) { throw new JdbcConnectorException( JdbcConnectorErrorCode.REFRESH_PHYSICAL_TABLESCHEMA_BY_SCHEMA_CHANGE_EVENT, e); @@ -130,13 +80,4 @@ protected void reOpenOutputFormat(AlterTableColumnEvent event) throws IOExceptio .build(); this.outputFormat.open(); } - - protected void replaceColumnByIndex( - List columns, String oldColumnName, Column newColumn) { - for (int i = 0; i < columns.size(); i++) { - if (columns.get(i).getName().equalsIgnoreCase(oldColumnName)) { - columns.set(i, newColumn); - } - } - } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java index 1fe8d915826..d14cf592113 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.sink; +import org.apache.seatunnel.shade.com.google.common.base.Throwables; + import org.apache.seatunnel.api.common.JobContext; import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.table.catalog.TablePath; @@ -40,8 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Throwables; - import javax.transaction.xa.Xid; import java.io.IOException; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSink.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSink.java index 2ccfba19f29..2213a90808b 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSink.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSink.java @@ -29,10 +29,12 @@ import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.sink.SupportMultiTableSink; import org.apache.seatunnel.api.sink.SupportSaveMode; +import org.apache.seatunnel.api.sink.SupportSchemaEvolutionSink; import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.schema.SchemaChangeType; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.iris.IrisCatalog; import org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.iris.savemode.IrisSaveModeHandler; @@ -52,6 +54,7 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -60,7 +63,8 @@ public class JdbcSink implements SeaTunnelSink, SupportSaveMode, - SupportMultiTableSink { + SupportMultiTableSink, + SupportSchemaEvolutionSink { private final TableSchema tableSchema; @@ -237,4 +241,18 @@ public Optional getSaveModeHandler() { } return Optional.empty(); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } + + @Override + public List supports() { + return Arrays.asList( + SchemaChangeType.ADD_COLUMN, + SchemaChangeType.DROP_COLUMN, + SchemaChangeType.RENAME_COLUMN, + SchemaChangeType.UPDATE_COLUMN); + } } diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSinkFactory.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSinkFactory.java index 214afcba068..5b64a8b4dfb 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSinkFactory.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcSinkFactory.java @@ -108,7 +108,7 @@ public TableSink createSink(TableSinkFactoryContext context) { TableIdentifier tableId = catalogTable.getTableId(); String sourceDatabaseName = tableId.getDatabaseName(); String sourceSchemaName = tableId.getSchemaName(); - String sourceTableName = tableId.getTableName(); + String pluginInputIdentifier = tableId.getTableName(); // get sink table relevant information String sinkDatabaseName = optionalDatabase.orElse(REPLACE_DATABASE_NAME_KEY); String sinkTableNameBefore = optionalTable.get(); @@ -152,8 +152,8 @@ public TableSink createSink(TableSinkFactoryContext context) { finalSchemaName = null; } String finalTableName = sinkTableName; - if (StringUtils.isNotEmpty(sourceTableName)) { - finalTableName = tempTableName.replace(REPLACE_TABLE_NAME_KEY, sourceTableName); + if (StringUtils.isNotEmpty(pluginInputIdentifier)) { + finalTableName = tempTableName.replace(REPLACE_TABLE_NAME_KEY, pluginInputIdentifier); } // rebuild TableIdentifier and catalogTable diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java index edeef96f0a2..72a4e061ac5 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java @@ -82,8 +82,8 @@ protected Collection createSplits( partitionEnd = range.getRight(); } if (partitionStart == null || partitionEnd == null) { - JdbcSourceSplit spilt = createSingleSplit(table); - return Collections.singletonList(spilt); + JdbcSourceSplit split = createSingleSplit(table); + return Collections.singletonList(split); } return createNumberColumnSplits( diff --git a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtils.java b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtils.java index 6eabba1edc1..73773b559bc 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtils.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtils.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.utils; +import org.apache.seatunnel.shade.com.google.common.base.Strings; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.catalog.CatalogTable; @@ -43,7 +45,6 @@ import org.apache.commons.lang3.StringUtils; -import com.google.common.base.Strings; import lombok.extern.slf4j.Slf4j; import java.sql.Connection; @@ -232,10 +233,12 @@ static CatalogTable mergeCatalogTable(CatalogTable tableOfPath, CatalogTable tab && columnsOfPath .get(column.getName()) .getDataType() + .getSqlType() .equals( columnsOfQuery .get(column.getName()) - .getDataType())) + .getDataType() + .getSqlType())) .map(column -> columnsOfPath.get(column.getName())) .collect(Collectors.toList()); boolean schemaIncludeAllColumns = columnsOfMerge.size() == columnKeysOfQuery.size(); diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/PreviewActionTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/PreviewActionTest.java index 5f4e239d6f2..06d85551a18 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/PreviewActionTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/PreviewActionTest.java @@ -109,7 +109,7 @@ public void testDMPreviewAction() { DamengCatalogFactory factory = new DamengCatalogFactory(); Catalog catalog = factory.createCatalog( - "test", + "Dameng", ReadonlyConfig.fromMap( new HashMap() { { @@ -124,7 +124,7 @@ public void testDMPreviewAction() { assertPreviewResult( catalog, Catalog.ActionType.CREATE_DATABASE, - "CREATE DATABASE `testddatabase`;", + "CREATE DATABASE \"testddatabase\";", Optional.empty())); Assertions.assertThrows( UnsupportedOperationException.class, @@ -132,28 +132,24 @@ public void testDMPreviewAction() { assertPreviewResult( catalog, Catalog.ActionType.DROP_DATABASE, - "DROP DATABASE `testddatabase`;", - Optional.empty())); - Assertions.assertThrows( - UnsupportedOperationException.class, - () -> - assertPreviewResult( - catalog, - Catalog.ActionType.TRUNCATE_TABLE, - "TRUNCATE TABLE `testddatabase`.`testtable`;", + "DROP DATABASE \"testddatabase\";", Optional.empty())); assertPreviewResult( - catalog, Catalog.ActionType.DROP_TABLE, "DROP TABLE TESTTABLE", Optional.empty()); - Assertions.assertThrows( - UnsupportedOperationException.class, - () -> - assertPreviewResult( - catalog, - Catalog.ActionType.CREATE_TABLE, - "CREATE TABLE `testtable` (\n" - + "\t`test` LONGTEXT NULL COMMENT ''\n" - + ") COMMENT = 'comment';", - Optional.of(CATALOG_TABLE))); + catalog, + Catalog.ActionType.TRUNCATE_TABLE, + "TRUNCATE TABLE \"null\".\"testtable\"", + Optional.empty()); + assertPreviewResult( + catalog, + Catalog.ActionType.DROP_TABLE, + "DROP TABLE \"testtable\"", + Optional.empty()); + + assertPreviewResult( + catalog, + Catalog.ActionType.CREATE_TABLE, + "CREATE TABLE \"testtable\" (\n" + "\"test\" TEXT\n" + ")", + Optional.of(CATALOG_TABLE)); } @Test diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCreateTableSqlBuilderTest.java new file mode 100644 index 00000000000..3e8abf64a28 --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengCreateTableSqlBuilderTest.java @@ -0,0 +1,137 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.dm; + +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.ConstraintKey; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.PrimaryKey; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.LocalTimeType; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +public class DamengCreateTableSqlBuilderTest { + + @Test + public void TestCreateTableSqlBuilder() { + TablePath tablePath = TablePath.of("test_database", "test_schema", "test_table"); + TableSchema tableSchema = + TableSchema.builder() + .column(PhysicalColumn.of("id", BasicType.LONG_TYPE, 22, false, null, "id")) + .column( + PhysicalColumn.of( + "name", BasicType.STRING_TYPE, 128, false, null, "name")) + .column( + PhysicalColumn.of( + "age", BasicType.INT_TYPE, (Long) null, true, null, "age")) + .column( + PhysicalColumn.of( + "createTime", + LocalTimeType.LOCAL_DATE_TIME_TYPE, + 3, + true, + null, + "createTime")) + .column( + PhysicalColumn.of( + "lastUpdateTime", + LocalTimeType.LOCAL_DATE_TIME_TYPE, + 3, + true, + null, + "lastUpdateTime")) + .primaryKey(PrimaryKey.of("id", Lists.newArrayList("id"))) + .constraintKey( + Arrays.asList( + ConstraintKey.of( + ConstraintKey.ConstraintType.UNIQUE_KEY, + "name", + Lists.newArrayList( + ConstraintKey.ConstraintKeyColumn.of( + "name", null))), + ConstraintKey.of( + ConstraintKey.ConstraintType.INDEX_KEY, + "age", + Lists.newArrayList( + ConstraintKey.ConstraintKeyColumn.of( + "age", null))))) + .build(); + + CatalogTable catalogTable = + CatalogTable.of( + TableIdentifier.of("test_catalog", tablePath), + tableSchema, + new HashMap<>(), + new ArrayList<>(), + "User table"); + + String createTableSql = + new DamengCreateTableSqlBuilder(catalogTable, true).build(tablePath); + String expect = + "CREATE TABLE \"test_schema\".\"test_table\" (\n" + + "\"id\" BIGINT NOT NULL,\n" + + "\"name\" VARCHAR2(128) NOT NULL,\n" + + "\"age\" INT,\n" + + "\"createTime\" TIMESTAMP,\n" + + "\"lastUpdateTime\" TIMESTAMP,\n" + + "CONSTRAINT id_63d5 PRIMARY KEY (\"id\"),\n" + + "\tCONSTRAINT name_49b6 UNIQUE (\"name\")\n" + + ");\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"id\" IS 'id';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"name\" IS 'name';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"age\" IS 'age';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"createTime\" IS 'createTime';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"lastUpdateTime\" IS 'lastUpdateTime';"; + + String regex1 = "id_\\w+"; + String regex2 = "name_\\w+"; + String replacedStr1 = createTableSql.replaceAll(regex1, "id_").replaceAll(regex2, "name_"); + String replacedStr2 = expect.replaceAll(regex1, "id_").replaceAll(regex2, "name_"); + Assertions.assertEquals(replacedStr2, replacedStr1); + + // skip index + String createTableSqlSkipIndex = + new DamengCreateTableSqlBuilder(catalogTable, false).build(tablePath); + // create table sql is change; The old unit tests are no longer applicable + String expectSkipIndex = + "CREATE TABLE \"test_schema\".\"test_table\" (\n" + + "\"id\" BIGINT NOT NULL,\n" + + "\"name\" VARCHAR2(128) NOT NULL,\n" + + "\"age\" INT,\n" + + "\"createTime\" TIMESTAMP,\n" + + "\"lastUpdateTime\" TIMESTAMP\n" + + ");\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"id\" IS 'id';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"name\" IS 'name';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"age\" IS 'age';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"createTime\" IS 'createTime';\n" + + "COMMENT ON COLUMN \"test_schema\".\"test_table\".\"lastUpdateTime\" IS 'lastUpdateTime';"; + Assertions.assertEquals(expectSkipIndex, createTableSqlSkipIndex); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengJdbcTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengJdbcTest.java new file mode 100644 index 00000000000..b0f6d42235f --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/dm/DamengJdbcTest.java @@ -0,0 +1,156 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.dm; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.exception.DatabaseAlreadyExistException; +import org.apache.seatunnel.api.table.catalog.exception.DatabaseNotExistException; +import org.apache.seatunnel.common.utils.JdbcUrlUtil; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@Disabled("Please Test it in your local environment") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class DamengJdbcTest { + + private static final JdbcUrlUtil.UrlInfo DM_URL_INFO = + JdbcUrlUtil.getUrlInfo("jdbc:dm://172.16.17.156:30236"); + + private static final String DATABASE_NAME = "DAMENG"; + private static final String SCHEMA_NAME = "DM_USER01"; + private static final String TABLE_NAME = "STUDENT_INFO"; + + private static final TablePath TABLE_PATH_DM = + TablePath.of(DATABASE_NAME, SCHEMA_NAME, TABLE_NAME); + + private static DamengCatalog DAMENG_CATALOG; + + private static CatalogTable DM_CATALOGTABLE; + + @BeforeAll + static void before() { + DAMENG_CATALOG = + new DamengCatalog("DAMENG_CATALOG", "DM_USER01", "Te$Dt_1234", DM_URL_INFO, null); + DAMENG_CATALOG.open(); + } + + @Test + @Order(1) + void exists() { + Assertions.assertTrue(DAMENG_CATALOG.databaseExists(DATABASE_NAME)); + Assertions.assertTrue(DAMENG_CATALOG.tableExists(TABLE_PATH_DM)); + } + + @Test + @Order(2) + void createTableInternal() { + Assertions.assertDoesNotThrow( + () -> DM_CATALOGTABLE = DAMENG_CATALOG.getTable(TABLE_PATH_DM)); + Assertions.assertDoesNotThrow( + () -> + DAMENG_CATALOG.createTable( + TablePath.of(DATABASE_NAME, SCHEMA_NAME, TABLE_NAME + "_test"), + DM_CATALOGTABLE, + false, + true)); + } + + @Test + @Order(3) + void dropTableInternal() { + Assertions.assertDoesNotThrow( + () -> + DAMENG_CATALOG.dropTable( + TablePath.of(DATABASE_NAME, SCHEMA_NAME, TABLE_NAME + "_test"), + false)); + } + + @Test + @Order(4) + void createDatabaseInternal() { + Assertions.assertDoesNotThrow(() -> DAMENG_CATALOG.createDatabase(TABLE_PATH_DM, true)); + Assertions.assertThrows( + DatabaseAlreadyExistException.class, + () -> DAMENG_CATALOG.createDatabase(TABLE_PATH_DM, false)); + RuntimeException catalogException = + Assertions.assertThrows( + RuntimeException.class, + () -> + DAMENG_CATALOG.createDatabase( + TablePath.of("test_db.test.test1"), true)); + Assertions.assertInstanceOf( + UnsupportedOperationException.class, catalogException.getCause()); + RuntimeException runtimeException = + Assertions.assertThrows( + RuntimeException.class, + () -> + DAMENG_CATALOG.createDatabase( + TablePath.of("test_db.test.test1"), false)); + Assertions.assertInstanceOf( + UnsupportedOperationException.class, runtimeException.getCause()); + } + + @Test + @Order(5) + void dropDatabaseInternal() { + Assertions.assertDoesNotThrow( + () -> DAMENG_CATALOG.dropDatabase(TablePath.of("test_db.test.test1"), true)); + Assertions.assertThrows( + DatabaseNotExistException.class, + () -> DAMENG_CATALOG.dropDatabase(TablePath.of("test_db.test.test1"), false)); + RuntimeException runtimeException = + Assertions.assertThrows( + RuntimeException.class, + () -> DAMENG_CATALOG.dropDatabase(TABLE_PATH_DM, true)); + Assertions.assertInstanceOf( + UnsupportedOperationException.class, runtimeException.getCause()); + RuntimeException catalogException = + Assertions.assertThrows( + RuntimeException.class, + () -> DAMENG_CATALOG.dropDatabase(TABLE_PATH_DM, false)); + Assertions.assertInstanceOf( + UnsupportedOperationException.class, catalogException.getCause()); + } + + @Test + @Order(6) + void truncateTableInternal() { + Assertions.assertDoesNotThrow(() -> DAMENG_CATALOG.truncateTable(TABLE_PATH_DM, false)); + Assertions.assertDoesNotThrow(() -> DAMENG_CATALOG.truncateTable(TABLE_PATH_DM, true)); + } + + @Test + @Order(7) + void listTablesInternal() { + Assertions.assertDoesNotThrow(() -> DAMENG_CATALOG.listTables(DATABASE_NAME)); + } + + @Test + @Order(8) + void existsData() { + Assertions.assertFalse(DAMENG_CATALOG.isExistsData(TABLE_PATH_DM)); + Assertions.assertTrue(DAMENG_CATALOG.isExistsData(TablePath.of("DAMENG.HIS.DEPARTMENTS"))); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCreateTableSqlBuilderTest.java index 0c1108b5760..a5fc7d1e92a 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/iris/IrisCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.iris; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -30,8 +32,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlCreateTableSqlBuilderTest.java index 3c433959316..9b0fed30322 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/mysql/MysqlCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.mysql; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -33,8 +35,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleCreateTableSqlBuilderTest.java index 6005aa0b262..52477b02459 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/oracle/OracleCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.oracle; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -31,8 +33,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilderTest.java index 37049eced38..03b99b1ca0a 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/psql/PostgresCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.psql; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.Column; import org.apache.seatunnel.api.table.catalog.ConstraintKey; @@ -30,11 +32,10 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.regex.Pattern; class PostgresCreateTableSqlBuilderTest { @@ -49,17 +50,18 @@ void build() { String createTableSql = postgresCreateTableSqlBuilder.build( catalogTable.getTableId().toTablePath()); - Assertions.assertEquals( - "CREATE TABLE \"test\" (\n" + String pattern = + "CREATE TABLE \"test\" \\(\n" + "\"id\" int4 NOT NULL PRIMARY KEY,\n" + "\"name\" text NOT NULL,\n" + "\"age\" int4 NOT NULL,\n" - + "\tCONSTRAINT unique_name UNIQUE (\"name\")\n" - + ");", - createTableSql); + + "\tCONSTRAINT \"([a-zA-Z0-9]+)\" UNIQUE \\(\"name\"\\)\n" + + "\\);"; + Assertions.assertTrue( + Pattern.compile(pattern).matcher(createTableSql).find()); + Assertions.assertEquals( - Lists.newArrayList( - "CREATE INDEX test_index_age ON \"test\"(\"age\");"), + Lists.newArrayList("CREATE INDEX ON \"test\"(\"age\");"), postgresCreateTableSqlBuilder.getCreateIndexSqls()); // skip index diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCreateTableSqlBuilderTest.java index 84d9e937117..a369f310098 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/redshift/RedshiftCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.redshift; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -31,8 +33,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/saphana/SapHanaCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/saphana/SapHanaCreateTableSqlBuilderTest.java index 03699896b58..4317ada68e2 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/saphana/SapHanaCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/saphana/SapHanaCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.saphana; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -30,8 +32,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.util.ArrayList; import java.util.HashMap; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCreateTableSqlBuilderTest.java index 04f765f4e5a..b1483af9b47 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/sqlserver/SqlServerCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.sqlserver; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -31,8 +33,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCreateTableSqlBuilderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCreateTableSqlBuilderTest.java index 8c8de29cace..7aca9eff332 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCreateTableSqlBuilderTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/catalog/xugu/XuguCreateTableSqlBuilderTest.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.catalog.xugu; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; @@ -31,8 +33,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import com.google.common.collect.Lists; - import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoaderTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoaderTest.java new file mode 100644 index 00000000000..84dd36acfbb --- /dev/null +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialectLoaderTest.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect; + +import org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql.MysqlDialect; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** Test for {@link JdbcDialectLoader} */ +public class JdbcDialectLoaderTest { + @Test + public void shouldFindGenericDialect() throws Exception { + JdbcDialect jdbcDialect = JdbcDialectLoader.load("jdbc:someting:", ""); + Assertions.assertTrue(jdbcDialect instanceof GenericDialect); + } + + @Test + public void shouldFindMysqlDialect() throws Exception { + JdbcDialect jdbcDialect = JdbcDialectLoader.load("jdbc:mysql://localhost:3306/test", ""); + Assertions.assertTrue(jdbcDialect instanceof MysqlDialect); + } +} diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverterTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverterTest.java index 69d01d32b05..9f672bbeeeb 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverterTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/saphana/SapHanaTypeConverterTest.java @@ -126,7 +126,7 @@ public void testConvertSmallDecimal() { .build(); Column column = SapHanaTypeConverter.INSTANCE.convert(typeDefine); Assertions.assertEquals(typeDefine.getName(), column.getName()); - Assertions.assertEquals(new DecimalType(38, 368), column.getDataType()); + Assertions.assertEquals(new DecimalType(38, 0), column.getDataType()); Assertions.assertEquals(typeDefine.getColumnType(), column.getSourceType()); typeDefine = @@ -139,7 +139,7 @@ public void testConvertSmallDecimal() { .build(); column = SapHanaTypeConverter.INSTANCE.convert(typeDefine); Assertions.assertEquals(typeDefine.getName(), column.getName()); - Assertions.assertEquals(new DecimalType(10, 368), column.getDataType()); + Assertions.assertEquals(new DecimalType(10, 5), column.getDataType()); Assertions.assertEquals(typeDefine.getColumnType(), column.getSourceType()); } @@ -153,7 +153,7 @@ public void testConvertDecimal() { .build(); Column column = SapHanaTypeConverter.INSTANCE.convert(typeDefine); Assertions.assertEquals(typeDefine.getName(), column.getName()); - Assertions.assertEquals(new DecimalType(34, 6176), column.getDataType()); + Assertions.assertEquals(new DecimalType(34, 0), column.getDataType()); Assertions.assertEquals(typeDefine.getColumnType(), column.getSourceType()); BasicTypeDefine typeDefine2 = @@ -382,6 +382,37 @@ public void testConvertDatetime() { Assertions.assertEquals(typeDefine.getColumnType(), column.getSourceType()); } + @Test + public void testConvertSpecialType() { + BasicTypeDefine typeDefine = + BasicTypeDefine.builder() + .name("test") + .columnType("ST_POINT") + .length(8L) + .dataType("ST_POINT") + .build(); + Column column = SapHanaTypeConverter.INSTANCE.convert(typeDefine); + + Assertions.assertEquals(typeDefine.getName(), column.getName()); + Assertions.assertEquals(PrimitiveByteArrayType.INSTANCE, column.getDataType()); + Assertions.assertEquals(8, column.getColumnLength()); + Assertions.assertEquals(typeDefine.getColumnType(), column.getSourceType()); + + typeDefine = + BasicTypeDefine.builder() + .name("test") + .columnType("ST_GEOMETRY") + .length(8L) + .dataType("ST_GEOMETRY") + .build(); + column = SapHanaTypeConverter.INSTANCE.convert(typeDefine); + + Assertions.assertEquals(typeDefine.getName(), column.getName()); + Assertions.assertEquals(PrimitiveByteArrayType.INSTANCE, column.getDataType()); + Assertions.assertEquals(8, column.getColumnLength()); + Assertions.assertEquals(typeDefine.getColumnType(), column.getSourceType()); + } + @Test public void testReconvertUnsupported() { Column column = diff --git a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtilsTest.java b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtilsTest.java index 4162bce30bb..872dc26f8f0 100644 --- a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtilsTest.java +++ b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/utils/JdbcCatalogUtilsTest.java @@ -25,6 +25,7 @@ import org.apache.seatunnel.api.table.catalog.TableIdentifier; import org.apache.seatunnel.api.table.catalog.TableSchema; import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.DecimalType; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -314,4 +315,59 @@ public void testColumnNotIncludeMerge() { tableOfQuery.getTableSchema().getColumns(), mergeTable.getTableSchema().getColumns()); } + + @Test + public void testDecimalColumnMerge() { + CatalogTable tableOfQuery = + CatalogTable.of( + TableIdentifier.of("default", null, null, "default"), + TableSchema.builder() + .column( + PhysicalColumn.of( + "f1", + new DecimalType(10, 1), + null, + true, + null, + null, + null, + false, + false, + null, + null, + null)) + .build(), + Collections.emptyMap(), + Collections.emptyList(), + null); + + CatalogTable tableOfPath = + CatalogTable.of( + TableIdentifier.of("default", null, null, "default"), + TableSchema.builder() + .column( + PhysicalColumn.of( + "f1", + new DecimalType(10, 2), + null, + true, + null, + null, + null, + false, + false, + null, + null, + null)) + .build(), + Collections.emptyMap(), + Collections.emptyList(), + null); + + CatalogTable mergeTable = JdbcCatalogUtils.mergeCatalogTable(tableOfPath, tableOfQuery); + // When column type is decimal, the precision and scale should not affect the merge result + Assertions.assertEquals( + tableOfPath.getTableSchema().getColumns().get(0), + mergeTable.getTableSchema().getColumns().get(0)); + } } diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/config/Config.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/config/Config.java index 293821e0edc..c01dc3e88d6 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/config/Config.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/config/Config.java @@ -184,13 +184,6 @@ public class Config { .withDescription( "Semantics that can be chosen EXACTLY_ONCE/AT_LEAST_ONCE/NON, default NON."); - public static final Option>> TABLE_LIST = - Options.key("table_list") - .type(new TypeReference>>() {}) - .noDefaultValue() - .withDescription( - "Topic list config. You can configure only one `table_list` or one `topic` at the same time"); - public static final Option PROTOBUF_SCHEMA = Options.key("protobuf_schema") .stringType() diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializer.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializer.java index 2fb251571f1..2f6559a1698 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializer.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializer.java @@ -132,7 +132,7 @@ private static Function> headersExtractor() { private static Function topicExtractor( String topic, SeaTunnelRowType rowType, MessageFormat format) { - if (MessageFormat.COMPATIBLE_DEBEZIUM_JSON.equals(format)) { + if (MessageFormat.COMPATIBLE_DEBEZIUM_JSON.equals(format) && topic == null) { int topicFieldIndex = rowType.indexOf(CompatibleDebeziumJsonDeserializationSchema.FIELD_TOPIC); return row -> row.getField(topicFieldIndex).toString(); diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSink.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSink.java index e7945d9ed1b..4deb30f547c 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSink.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSink.java @@ -23,6 +23,7 @@ import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkCommitter; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.kafka.state.KafkaAggregatedCommitInfo; @@ -43,10 +44,12 @@ public class KafkaSink private final ReadonlyConfig pluginConfig; private final SeaTunnelRowType seaTunnelRowType; + private final CatalogTable catalogTable; - public KafkaSink(ReadonlyConfig pluginConfig, SeaTunnelRowType rowType) { + public KafkaSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { this.pluginConfig = pluginConfig; - this.seaTunnelRowType = rowType; + this.catalogTable = catalogTable; + this.seaTunnelRowType = catalogTable.getTableSchema().toPhysicalRowDataType(); } @Override @@ -81,4 +84,9 @@ public Optional> getCommitInfoSerializer() { public String getPluginName() { return org.apache.seatunnel.connectors.seatunnel.kafka.config.Config.CONNECTOR_IDENTITY; } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSinkFactory.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSinkFactory.java index fe6965132d2..ed3278602a1 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSinkFactory.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaSinkFactory.java @@ -50,9 +50,6 @@ public OptionRule optionRule() { @Override public TableSink createSink(TableSinkFactoryContext context) { - return () -> - new KafkaSink( - context.getOptions(), - context.getCatalogTable().getTableSchema().toPhysicalRowDataType()); + return () -> new KafkaSink(context.getOptions(), context.getCatalogTable()); } } diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaTransactionSender.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaTransactionSender.java index bfb2685595f..213bb9db575 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaTransactionSender.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/sink/KafkaTransactionSender.java @@ -17,13 +17,14 @@ package org.apache.seatunnel.connectors.seatunnel.kafka.sink; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.connectors.seatunnel.kafka.state.KafkaCommitInfo; import org.apache.seatunnel.connectors.seatunnel.kafka.state.KafkaSinkState; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; -import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import java.util.List; diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaRecordEmitter.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaRecordEmitter.java index 6593137aff7..7d4f38a3cdf 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaRecordEmitter.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaRecordEmitter.java @@ -20,7 +20,7 @@ import org.apache.seatunnel.api.serialization.DeserializationSchema; import org.apache.seatunnel.api.source.Collector; import org.apache.seatunnel.api.table.catalog.TablePath; -import org.apache.seatunnel.api.table.event.SchemaChangeEvent; +import org.apache.seatunnel.api.table.schema.event.SchemaChangeEvent; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.seatunnel.common.source.reader.RecordEmitter; import org.apache.seatunnel.connectors.seatunnel.kafka.config.MessageFormatErrorHandleWay; @@ -31,7 +31,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.util.Map; public class KafkaRecordEmitter @@ -71,13 +70,14 @@ public void emitRecord( // consumerRecord.offset + 1 is the offset commit to Kafka and also the start offset // for the next run splitState.setCurrentOffset(consumerRecord.offset() + 1); - } catch (IOException e) { + } catch (Exception e) { if (this.messageFormatErrorHandleWay == MessageFormatErrorHandleWay.SKIP) { logger.warn( "Deserialize message failed, skip this message, message: {}", new String(consumerRecord.value())); + } else { + throw e; } - throw e; } } diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSource.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSource.java index 5688fde5b64..271adb8e7fe 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSource.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSource.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.kafka.source; +import org.apache.seatunnel.shade.com.google.common.base.Supplier; + import org.apache.seatunnel.api.common.JobContext; import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.source.Boundedness; @@ -34,8 +36,6 @@ import org.apache.kafka.clients.consumer.ConsumerRecord; -import com.google.common.base.Supplier; - import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -104,7 +104,11 @@ public SourceReader createReader( @Override public SourceSplitEnumerator createEnumerator( SourceSplitEnumerator.Context enumeratorContext) { - return new KafkaSourceSplitEnumerator(kafkaSourceConfig, enumeratorContext, null); + return new KafkaSourceSplitEnumerator( + kafkaSourceConfig, + enumeratorContext, + null, + getBoundedness() == Boundedness.UNBOUNDED); } @Override @@ -112,7 +116,10 @@ public SourceSplitEnumerator restoreEnumerat SourceSplitEnumerator.Context enumeratorContext, KafkaSourceState checkpointState) { return new KafkaSourceSplitEnumerator( - kafkaSourceConfig, enumeratorContext, checkpointState); + kafkaSourceConfig, + enumeratorContext, + checkpointState, + getBoundedness() == Boundedness.UNBOUNDED); } @Override diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceConfig.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceConfig.java index 0f645d72182..1093d3f2f28 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceConfig.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceConfig.java @@ -19,6 +19,7 @@ import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.serialization.DeserializationSchema; +import org.apache.seatunnel.api.table.catalog.CatalogOptions; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.PhysicalColumn; import org.apache.seatunnel.api.table.catalog.TableIdentifier; @@ -31,7 +32,6 @@ import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; -import org.apache.seatunnel.connectors.seatunnel.kafka.config.Config; import org.apache.seatunnel.connectors.seatunnel.kafka.config.MessageFormat; import org.apache.seatunnel.connectors.seatunnel.kafka.config.MessageFormatErrorHandleWay; import org.apache.seatunnel.connectors.seatunnel.kafka.config.StartMode; @@ -114,11 +114,17 @@ private Properties createKafkaProperties(ReadonlyConfig readonlyConfig) { private Map createMapConsumerMetadata( ReadonlyConfig readonlyConfig) { List consumerMetadataList; - if (readonlyConfig.getOptional(Config.TABLE_LIST).isPresent()) { + if (readonlyConfig.getOptional(TableSchemaOptions.TABLE_CONFIGS).isPresent()) { consumerMetadataList = - readonlyConfig.get(Config.TABLE_LIST).stream() + readonlyConfig.get(TableSchemaOptions.TABLE_CONFIGS).stream() .map(ReadonlyConfig::fromMap) - .map(config -> createConsumerMetadata(config)) + .map(this::createConsumerMetadata) + .collect(Collectors.toList()); + } else if (readonlyConfig.getOptional(CatalogOptions.TABLE_LIST).isPresent()) { + consumerMetadataList = + readonlyConfig.get(CatalogOptions.TABLE_LIST).stream() + .map(ReadonlyConfig::fromMap) + .map(this::createConsumerMetadata) .collect(Collectors.toList()); } else { consumerMetadataList = diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceFactory.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceFactory.java index 431e9a8c195..fe6f50a8ea4 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceFactory.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceFactory.java @@ -20,6 +20,8 @@ import org.apache.seatunnel.api.configuration.util.OptionRule; import org.apache.seatunnel.api.source.SeaTunnelSource; import org.apache.seatunnel.api.source.SourceSplit; +import org.apache.seatunnel.api.table.catalog.CatalogOptions; +import org.apache.seatunnel.api.table.catalog.schema.TableSchemaOptions; import org.apache.seatunnel.api.table.connector.TableSource; import org.apache.seatunnel.api.table.factory.Factory; import org.apache.seatunnel.api.table.factory.TableSourceFactory; @@ -43,7 +45,8 @@ public String factoryIdentifier() { public OptionRule optionRule() { return OptionRule.builder() .required(Config.BOOTSTRAP_SERVERS) - .exclusive(Config.TOPIC, Config.TABLE_LIST) + .exclusive( + Config.TOPIC, TableSchemaOptions.TABLE_CONFIGS, CatalogOptions.TABLE_LIST) .optional( Config.START_MODE, Config.PATTERN, diff --git a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumerator.java b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumerator.java index 06ce4565c3b..3883d3007e0 100644 --- a/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumerator.java +++ b/seatunnel-connectors-v2/connector-kafka/src/main/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumerator.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.kafka.source; +import org.apache.seatunnel.shade.com.google.common.annotations.VisibleForTesting; + import org.apache.seatunnel.api.source.SourceSplitEnumerator; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.common.config.Common; @@ -30,7 +32,6 @@ import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.common.TopicPartition; -import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -70,10 +71,13 @@ public class KafkaSourceSplitEnumerator private final Map topicMappingTablePathMap = new HashMap<>(); + private boolean isStreamingMode; + KafkaSourceSplitEnumerator( KafkaSourceConfig kafkaSourceConfig, Context context, - KafkaSourceState sourceState) { + KafkaSourceState sourceState, + boolean isStreamingMode) { this.kafkaSourceConfig = kafkaSourceConfig; this.tablePathMetadataMap = kafkaSourceConfig.getMapMetadata(); this.context = context; @@ -81,10 +85,11 @@ public class KafkaSourceSplitEnumerator this.pendingSplit = new HashMap<>(); this.adminClient = initAdminClient(this.kafkaSourceConfig.getProperties()); this.discoveryIntervalMillis = kafkaSourceConfig.getDiscoveryIntervalMillis(); + this.isStreamingMode = isStreamingMode; } @VisibleForTesting - protected KafkaSourceSplitEnumerator( + public KafkaSourceSplitEnumerator( AdminClient adminClient, Map pendingSplit, Map assignedSplit) { @@ -97,6 +102,16 @@ protected KafkaSourceSplitEnumerator( this.assignedSplit = assignedSplit; } + @VisibleForTesting + public KafkaSourceSplitEnumerator( + AdminClient adminClient, + Map pendingSplit, + Map assignedSplit, + boolean isStreamingMode) { + this(adminClient, pendingSplit, assignedSplit); + this.isStreamingMode = isStreamingMode; + } + @Override public void open() { if (discoveryIntervalMillis > 0) { @@ -204,7 +219,7 @@ public void addSplitsBack(List splits, int subtaskId) { private Map convertToNextSplit( List splits) { try { - Map listOffsets = + Map latestOffsets = listOffsets( splits.stream() .map(KafkaSourceSplit::getTopicPartition) @@ -214,7 +229,10 @@ public void addSplitsBack(List splits, int subtaskId) { splits.forEach( split -> { split.setStartOffset(split.getEndOffset() + 1); - split.setEndOffset(listOffsets.get(split.getTopicPartition())); + split.setEndOffset( + isStreamingMode + ? Long.MAX_VALUE + : latestOffsets.get(split.getTopicPartition())); }); return splits.stream() .collect(Collectors.toMap(KafkaSourceSplit::getTopicPartition, split -> split)); @@ -305,7 +323,10 @@ private Set getTopicInfo() throws ExecutionException, Interrup // Obtain the corresponding topic TablePath from kafka topic TablePath tablePath = topicMappingTablePathMap.get(partition.topic()); KafkaSourceSplit split = new KafkaSourceSplit(tablePath, partition); - split.setEndOffset(latestOffsets.get(split.getTopicPartition())); + split.setEndOffset( + isStreamingMode + ? Long.MAX_VALUE + : latestOffsets.get(partition)); return split; }) .collect(Collectors.toSet()); @@ -344,6 +365,7 @@ private static int getSplitOwner(TopicPartition tp, int numReaders) { private Map listOffsets( Collection partitions, OffsetSpec offsetSpec) throws ExecutionException, InterruptedException { + Map topicPartitionOffsets = partitions.stream() .collect(Collectors.toMap(partition -> partition, __ -> offsetSpec)); @@ -391,7 +413,8 @@ private void discoverySplits() throws ExecutionException, InterruptedException { assignSplit(); } - private void fetchPendingPartitionSplit() throws ExecutionException, InterruptedException { + @VisibleForTesting + public void fetchPendingPartitionSplit() throws ExecutionException, InterruptedException { getTopicInfo() .forEach( split -> { diff --git a/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/kafka/clients/admin/KafkaSourceSplitEnumeratorTest.java b/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/kafka/clients/admin/KafkaSourceSplitEnumeratorTest.java new file mode 100644 index 00000000000..00e059ecfe9 --- /dev/null +++ b/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/kafka/clients/admin/KafkaSourceSplitEnumeratorTest.java @@ -0,0 +1,156 @@ +/* + * 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. + */ + +package org.apache.kafka.clients.admin; + +import org.apache.seatunnel.connectors.seatunnel.kafka.source.KafkaSourceSplit; +import org.apache.seatunnel.connectors.seatunnel.kafka.source.KafkaSourceSplitEnumerator; + +import org.apache.kafka.common.KafkaFuture; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.TopicPartitionInfo; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +class KafkaSourceSplitEnumeratorTest { + + AdminClient adminClient = Mockito.mock(KafkaAdminClient.class); + // prepare + TopicPartition partition = new TopicPartition("test", 0); + + @BeforeEach + void init() { + + Mockito.when(adminClient.listOffsets(Mockito.any(java.util.Map.class))) + .thenReturn( + new ListOffsetsResult( + new HashMap< + TopicPartition, + KafkaFuture>() { + { + put( + partition, + KafkaFuture.completedFuture( + new ListOffsetsResult.ListOffsetsResultInfo( + 0, 0, Optional.of(0)))); + } + })); + Mockito.when(adminClient.describeTopics(Mockito.any(java.util.Collection.class))) + .thenReturn( + DescribeTopicsResult.ofTopicNames( + new HashMap>() { + { + put( + partition.topic(), + KafkaFuture.completedFuture( + new TopicDescription( + partition.topic(), + false, + Collections.singletonList( + new TopicPartitionInfo( + 0, + null, + Collections + .emptyList(), + Collections + .emptyList()))))); + } + })); + } + + @Test + void addSplitsBack() { + // test + Map assignedSplit = + new HashMap() { + { + put(partition, new KafkaSourceSplit(null, partition)); + } + }; + Map pendingSplit = new HashMap<>(); + List splits = Arrays.asList(new KafkaSourceSplit(null, partition)); + KafkaSourceSplitEnumerator enumerator = + new KafkaSourceSplitEnumerator(adminClient, pendingSplit, assignedSplit); + enumerator.addSplitsBack(splits, 1); + Assertions.assertTrue(pendingSplit.size() == splits.size()); + Assertions.assertNull(assignedSplit.get(partition)); + Assertions.assertTrue(pendingSplit.get(partition).getEndOffset() == 0); + } + + @Test + void addStreamingSplitsBack() { + // test + Map assignedSplit = + new HashMap() { + { + put(partition, new KafkaSourceSplit(null, partition)); + } + }; + Map pendingSplit = new HashMap<>(); + List splits = + Collections.singletonList(new KafkaSourceSplit(null, partition)); + KafkaSourceSplitEnumerator enumerator = + new KafkaSourceSplitEnumerator(adminClient, pendingSplit, assignedSplit, true); + enumerator.addSplitsBack(splits, 1); + Assertions.assertEquals(pendingSplit.size(), splits.size()); + Assertions.assertNull(assignedSplit.get(partition)); + Assertions.assertTrue(pendingSplit.get(partition).getEndOffset() == Long.MAX_VALUE); + } + + @Test + void addStreamingSplits() throws ExecutionException, InterruptedException { + // test + Map assignedSplit = + new HashMap(); + Map pendingSplit = new HashMap<>(); + List splits = + Collections.singletonList(new KafkaSourceSplit(null, partition)); + KafkaSourceSplitEnumerator enumerator = + new KafkaSourceSplitEnumerator(adminClient, pendingSplit, assignedSplit, true); + enumerator.fetchPendingPartitionSplit(); + Assertions.assertEquals(pendingSplit.size(), splits.size()); + Assertions.assertNotNull(pendingSplit.get(partition)); + Assertions.assertTrue(pendingSplit.get(partition).getEndOffset() == Long.MAX_VALUE); + } + + @Test + void addplits() throws ExecutionException, InterruptedException { + // test + Map assignedSplit = + new HashMap(); + Map pendingSplit = new HashMap<>(); + List splits = + Collections.singletonList(new KafkaSourceSplit(null, partition)); + KafkaSourceSplitEnumerator enumerator = + new KafkaSourceSplitEnumerator(adminClient, pendingSplit, assignedSplit, false); + enumerator.fetchPendingPartitionSplit(); + Assertions.assertEquals(pendingSplit.size(), splits.size()); + Assertions.assertNotNull(pendingSplit.get(partition)); + Assertions.assertTrue(pendingSplit.get(partition).getEndOffset() == 0); + } +} diff --git a/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializerTest.java b/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializerTest.java new file mode 100644 index 00000000000..a60a3202816 --- /dev/null +++ b/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/seatunnel/connectors/seatunnel/kafka/serialize/DefaultSeaTunnelRowSerializerTest.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.kafka.serialize; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.connectors.seatunnel.kafka.config.MessageFormat; +import org.apache.seatunnel.format.compatible.debezium.json.CompatibleDebeziumJsonDeserializationSchema; + +import org.apache.kafka.clients.producer.ProducerRecord; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +public class DefaultSeaTunnelRowSerializerTest { + + @Test + public void testCustomTopic() { + String topic = null; + SeaTunnelRowType rowType = + CompatibleDebeziumJsonDeserializationSchema.DEBEZIUM_DATA_ROW_TYPE; + MessageFormat format = MessageFormat.COMPATIBLE_DEBEZIUM_JSON; + String delimiter = null; + ReadonlyConfig pluginConfig = ReadonlyConfig.fromMap(Collections.emptyMap()); + + DefaultSeaTunnelRowSerializer serializer = + DefaultSeaTunnelRowSerializer.create( + topic, rowType, format, delimiter, pluginConfig); + ProducerRecord record = + serializer.serializeRow( + new SeaTunnelRow(new Object[] {"test.database1.table1", "key1", "value1"})); + + Assertions.assertEquals("test.database1.table1", record.topic()); + Assertions.assertEquals("key1", new String(record.key())); + Assertions.assertEquals("value1", new String(record.value())); + + topic = "test_topic"; + serializer = + DefaultSeaTunnelRowSerializer.create( + topic, rowType, format, delimiter, pluginConfig); + record = + serializer.serializeRow( + new SeaTunnelRow(new Object[] {"test.database1.table1", "key1", "value1"})); + + Assertions.assertEquals("test_topic", record.topic()); + Assertions.assertEquals("key1", new String(record.key())); + Assertions.assertEquals("value1", new String(record.value())); + } +} diff --git a/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumeratorTest.java b/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumeratorTest.java deleted file mode 100644 index 6a8de812d31..00000000000 --- a/seatunnel-connectors-v2/connector-kafka/src/test/java/org/apache/seatunnel/connectors/seatunnel/kafka/source/KafkaSourceSplitEnumeratorTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - */ - -package org.apache.seatunnel.connectors.seatunnel.kafka.source; - -import org.apache.kafka.clients.admin.AdminClient; -import org.apache.kafka.clients.admin.KafkaAdminClient; -import org.apache.kafka.clients.admin.ListOffsetsResult; -import org.apache.kafka.common.KafkaFuture; -import org.apache.kafka.common.TopicPartition; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -class KafkaSourceSplitEnumeratorTest { - - @Test - void addSplitsBack() { - // prepare - TopicPartition partition = new TopicPartition("test", 0); - - AdminClient adminClient = Mockito.mock(KafkaAdminClient.class); - Mockito.when(adminClient.listOffsets(Mockito.any(java.util.Map.class))) - .thenReturn( - new ListOffsetsResult( - new HashMap< - TopicPartition, - KafkaFuture>() { - { - put( - partition, - KafkaFuture.completedFuture( - new ListOffsetsResult.ListOffsetsResultInfo( - 0, 0, Optional.of(0)))); - } - })); - - // test - Map assignedSplit = - new HashMap() { - { - put(partition, new KafkaSourceSplit(null, partition)); - } - }; - Map pendingSplit = new HashMap<>(); - List splits = Arrays.asList(new KafkaSourceSplit(null, partition)); - KafkaSourceSplitEnumerator enumerator = - new KafkaSourceSplitEnumerator(adminClient, pendingSplit, assignedSplit); - enumerator.addSplitsBack(splits, 1); - Assertions.assertTrue(pendingSplit.size() == splits.size()); - Assertions.assertNull(assignedSplit.get(partition)); - } -} diff --git a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceConfig.java b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceConfig.java index 5abc62ad72a..3fd783bbf8d 100644 --- a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceConfig.java +++ b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceConfig.java @@ -17,8 +17,6 @@ package org.apache.seatunnel.connectors.seatunnel.kudu.config; -import org.apache.seatunnel.shade.com.fasterxml.jackson.core.type.TypeReference; - import org.apache.seatunnel.api.configuration.Option; import org.apache.seatunnel.api.configuration.Options; import org.apache.seatunnel.api.configuration.ReadonlyConfig; @@ -29,7 +27,6 @@ import lombok.ToString; import java.util.List; -import java.util.Map; @Getter @ToString @@ -55,12 +52,6 @@ public class KuduSourceConfig extends CommonConfig { .noDefaultValue() .withDescription("Kudu scan filter expressions"); - public static final Option>> TABLE_LIST = - Options.key("table_list") - .type(new TypeReference>>() {}) - .noDefaultValue() - .withDescription("table list config"); - private int batchSizeBytes; protected Long queryTimeout; diff --git a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceTableConfig.java b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceTableConfig.java index b741b7474b3..30898787811 100644 --- a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceTableConfig.java +++ b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/config/KuduSourceTableConfig.java @@ -17,8 +17,11 @@ package org.apache.seatunnel.connectors.seatunnel.kudu.config; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.table.catalog.Catalog; +import org.apache.seatunnel.api.table.catalog.CatalogOptions; import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.CatalogTableUtil; import org.apache.seatunnel.api.table.catalog.TablePath; @@ -27,7 +30,6 @@ import org.apache.seatunnel.connectors.seatunnel.kudu.catalog.KuduCatalog; import org.apache.seatunnel.connectors.seatunnel.kudu.catalog.KuduCatalogFactory; -import com.google.common.collect.Lists; import lombok.Getter; import java.io.Serializable; @@ -59,8 +61,8 @@ public static List of(ReadonlyConfig config) { try (KuduCatalog kuduCatalog = (KuduCatalog) optionalCatalog.get()) { kuduCatalog.open(); - if (config.getOptional(KuduSourceConfig.TABLE_LIST).isPresent()) { - return config.get(KuduSourceConfig.TABLE_LIST).stream() + if (config.getOptional(CatalogOptions.TABLE_LIST).isPresent()) { + return config.get(CatalogOptions.TABLE_LIST).stream() .map(ReadonlyConfig::fromMap) .map(readonlyConfig -> parseKuduSourceConfig(readonlyConfig, kuduCatalog)) .collect(Collectors.toList()); diff --git a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/sink/KuduSink.java b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/sink/KuduSink.java index def4a2b3668..d56a08db435 100644 --- a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/sink/KuduSink.java +++ b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/sink/KuduSink.java @@ -30,6 +30,7 @@ import org.apache.seatunnel.connectors.seatunnel.kudu.state.KuduSinkState; import java.io.IOException; +import java.util.Optional; /** * Kudu Sink implementation by using SeaTunnel sink API. This class contains the method to create @@ -40,11 +41,14 @@ public class KuduSink SeaTunnelRow, KuduSinkState, KuduCommitInfo, KuduAggregatedCommitInfo>, SupportMultiTableSink { - private KuduSinkConfig kuduSinkConfig; - private SeaTunnelRowType seaTunnelRowType; + private final KuduSinkConfig kuduSinkConfig; + private final SeaTunnelRowType seaTunnelRowType; + + private final CatalogTable catalogTable; public KuduSink(KuduSinkConfig kuduSinkConfig, CatalogTable catalogTable) { this.kuduSinkConfig = kuduSinkConfig; + this.catalogTable = catalogTable; this.seaTunnelRowType = catalogTable.getTableSchema().toPhysicalRowDataType(); } @@ -57,4 +61,9 @@ public String getPluginName() { public KuduSinkWriter createWriter(SinkWriter.Context context) throws IOException { return new KuduSinkWriter(seaTunnelRowType, kuduSinkConfig); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/source/KuduSourceFactory.java b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/source/KuduSourceFactory.java index b1bdb7e4ab6..78002a93908 100644 --- a/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/source/KuduSourceFactory.java +++ b/seatunnel-connectors-v2/connector-kudu/src/main/java/org/apache/seatunnel/connectors/seatunnel/kudu/source/KuduSourceFactory.java @@ -33,8 +33,8 @@ import java.io.Serializable; +import static org.apache.seatunnel.api.table.catalog.CatalogOptions.TABLE_LIST; import static org.apache.seatunnel.connectors.seatunnel.kudu.config.KuduSourceConfig.MASTER; -import static org.apache.seatunnel.connectors.seatunnel.kudu.config.KuduSourceConfig.TABLE_LIST; import static org.apache.seatunnel.connectors.seatunnel.kudu.config.KuduSourceConfig.TABLE_NAME; @AutoService(Factory.class) diff --git a/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_flink.conf b/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_flink.conf index c566c4907d8..a6add26b2c5 100644 --- a/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_flink.conf +++ b/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_flink.conf @@ -26,7 +26,7 @@ env { source { # This is a example source plugin **only for test and demonstrate the feature source plugin** KuduSource { - result_table_name = "studentlyh2" + plugin_output = "studentlyh2" kudu_master = "192.168.88.110:7051" kudu_table = "studentlyh2" columnsList = "id,name,age,sex" diff --git a/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_spark.conf b/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_spark.conf index 4f19c1738bf..5625a5a53aa 100644 --- a/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_spark.conf +++ b/seatunnel-connectors-v2/connector-kudu/src/main/resources/kudu_to_kudu_spark.conf @@ -31,7 +31,7 @@ env { source { # This is a example source plugin **only for test and demonstrate the feature source plugin** KuduSource { - result_table_name = "studentlyh2" + plugin_output = "studentlyh2" kudu_master = "192.168.88.110:7051" kudu_table = "studentlyh2" columnsList = "id,name,age,sex" diff --git a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java index 6477eb2e365..9bd27081725 100644 --- a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java +++ b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/catalog/MaxComputeCatalog.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.maxcompute.catalog; +import org.apache.seatunnel.shade.com.google.common.collect.Lists; + import org.apache.seatunnel.api.configuration.ReadonlyConfig; import org.apache.seatunnel.api.table.catalog.Catalog; import org.apache.seatunnel.api.table.catalog.CatalogTable; @@ -33,7 +35,6 @@ import com.aliyun.odps.Tables; import com.aliyun.odps.account.Account; import com.aliyun.odps.account.AliyunAccount; -import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; diff --git a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSink.java b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSink.java index 6abce7e4178..91e8c12dca3 100644 --- a/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSink.java +++ b/seatunnel-connectors-v2/connector-maxcompute/src/main/java/org/apache/seatunnel/connectors/seatunnel/maxcompute/sink/MaxcomputeSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; @@ -33,6 +34,8 @@ import com.google.auto.service.AutoService; +import java.util.Optional; + import static org.apache.seatunnel.connectors.seatunnel.maxcompute.config.MaxcomputeConfig.PLUGIN_NAME; @AutoService(SeaTunnelSink.class) @@ -61,4 +64,9 @@ public void setTypeInfo(SeaTunnelRowType seaTunnelRowType) { public AbstractSinkWriter createWriter(SinkWriter.Context context) { return new MaxcomputeWriter(this.pluginConfig, this.typeInfo); } + + @Override + public Optional getWriteCatalogTable() { + return super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-maxcompute/src/main/resources/maxcompute_to_maxcompute.conf b/seatunnel-connectors-v2/connector-maxcompute/src/main/resources/maxcompute_to_maxcompute.conf index 9293e45542f..97374ef1528 100644 --- a/seatunnel-connectors-v2/connector-maxcompute/src/main/resources/maxcompute_to_maxcompute.conf +++ b/seatunnel-connectors-v2/connector-maxcompute/src/main/resources/maxcompute_to_maxcompute.conf @@ -71,7 +71,7 @@ source { transform { sql { - source_table_name = "fake" + plugin_input = "fake" sql = "select * from fake" } diff --git a/seatunnel-connectors-v2/connector-milvus/pom.xml b/seatunnel-connectors-v2/connector-milvus/pom.xml index fc972ce1968..9a5fed37ab2 100644 --- a/seatunnel-connectors-v2/connector-milvus/pom.xml +++ b/seatunnel-connectors-v2/connector-milvus/pom.xml @@ -28,12 +28,20 @@ connector-milvus SeaTunnel : Connectors V2 : Milvus - + + + + com.google.code.gson + gson + 2.10.1 + + + io.milvus milvus-sdk-java - 2.4.3 + 2.4.5 org.slf4j @@ -42,19 +50,6 @@ - - org.mockito - mockito-core - 4.11.0 - test - - - org.mockito - mockito-inline - 4.11.0 - test - - diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java index c1e1ac292da..99717b75fa0 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusCatalog.java @@ -24,7 +24,6 @@ import org.apache.seatunnel.api.table.catalog.ConstraintKey; import org.apache.seatunnel.api.table.catalog.InfoPreviewResult; import org.apache.seatunnel.api.table.catalog.PreviewResult; -import org.apache.seatunnel.api.table.catalog.PrimaryKey; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.catalog.TableSchema; import org.apache.seatunnel.api.table.catalog.VectorIndex; @@ -33,20 +32,21 @@ import org.apache.seatunnel.api.table.catalog.exception.DatabaseNotExistException; import org.apache.seatunnel.api.table.catalog.exception.TableAlreadyExistException; import org.apache.seatunnel.api.table.catalog.exception.TableNotExistException; -import org.apache.seatunnel.api.table.type.ArrayType; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.CommonOptions; import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig; -import org.apache.seatunnel.connectors.seatunnel.milvus.convert.MilvusConvertUtils; import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; +import org.apache.seatunnel.connectors.seatunnel.milvus.utils.sink.MilvusSinkConverter; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import com.google.protobuf.ProtocolStringList; import io.milvus.client.MilvusServiceClient; import io.milvus.common.clientenum.ConsistencyLevelEnum; -import io.milvus.grpc.DataType; import io.milvus.grpc.ListDatabasesResponse; import io.milvus.grpc.ShowCollectionsResponse; +import io.milvus.grpc.ShowPartitionsResponse; import io.milvus.grpc.ShowType; import io.milvus.param.ConnectParam; import io.milvus.param.IndexType; @@ -61,6 +61,8 @@ import io.milvus.param.collection.HasCollectionParam; import io.milvus.param.collection.ShowCollectionsParam; import io.milvus.param.index.CreateIndexParam; +import io.milvus.param.partition.CreatePartitionParam; +import io.milvus.param.partition.ShowPartitionsParam; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; @@ -69,7 +71,8 @@ import java.util.Objects; import java.util.Optional; -import static com.google.common.base.Preconditions.checkNotNull; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.CREATE_INDEX; +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkNotNull; @Slf4j public class MilvusCatalog implements Catalog { @@ -196,7 +199,8 @@ public void createTable(TablePath tablePath, CatalogTable catalogTable, boolean checkNotNull(tableSchema, "tableSchema must not be null"); createTableInternal(tablePath, catalogTable); - if (CollectionUtils.isNotEmpty(tableSchema.getConstraintKeys())) { + if (CollectionUtils.isNotEmpty(tableSchema.getConstraintKeys()) + && config.get(CREATE_INDEX)) { for (ConstraintKey constraintKey : tableSchema.getConstraintKeys()) { if (constraintKey .getConstraintType() @@ -231,27 +235,61 @@ private void createIndexInternal( public void createTableInternal(TablePath tablePath, CatalogTable catalogTable) { try { + Map options = catalogTable.getOptions(); + + // partition key logic + boolean existPartitionKeyField = options.containsKey(MilvusOptions.PARTITION_KEY_FIELD); + String partitionKeyField = + existPartitionKeyField ? options.get(MilvusOptions.PARTITION_KEY_FIELD) : null; + // if options set, will overwrite aut read + if (StringUtils.isNotEmpty(config.get(MilvusSinkConfig.PARTITION_KEY))) { + existPartitionKeyField = true; + partitionKeyField = config.get(MilvusSinkConfig.PARTITION_KEY); + } + TableSchema tableSchema = catalogTable.getTableSchema(); List fieldTypes = new ArrayList<>(); for (Column column : tableSchema.getColumns()) { - fieldTypes.add(convertToFieldType(column, tableSchema.getPrimaryKey())); + if (column.getOptions() != null + && column.getOptions().containsKey(CommonOptions.METADATA.getName()) + && (Boolean) column.getOptions().get(CommonOptions.METADATA.getName())) { + // skip dynamic field + continue; + } + FieldType fieldType = + MilvusSinkConverter.convertToFieldType( + column, + tableSchema.getPrimaryKey(), + partitionKeyField, + config.get(MilvusSinkConfig.ENABLE_AUTO_ID)); + fieldTypes.add(fieldType); } - Map options = catalogTable.getOptions(); Boolean enableDynamicField = (options.containsKey(MilvusOptions.ENABLE_DYNAMIC_FIELD)) ? Boolean.valueOf(options.get(MilvusOptions.ENABLE_DYNAMIC_FIELD)) : config.get(MilvusSinkConfig.ENABLE_DYNAMIC_FIELD); - + String collectionDescription = ""; + if (config.get(MilvusSinkConfig.COLLECTION_DESCRIPTION) != null + && config.get(MilvusSinkConfig.COLLECTION_DESCRIPTION) + .containsKey(tablePath.getTableName())) { + // use description from config first + collectionDescription = + config.get(MilvusSinkConfig.COLLECTION_DESCRIPTION) + .get(tablePath.getTableName()); + } else if (null != catalogTable.getComment()) { + collectionDescription = catalogTable.getComment(); + } CreateCollectionParam.Builder builder = CreateCollectionParam.newBuilder() .withDatabaseName(tablePath.getDatabaseName()) .withCollectionName(tablePath.getTableName()) + .withDescription(collectionDescription) .withFieldTypes(fieldTypes) .withEnableDynamicField(enableDynamicField) .withConsistencyLevel(ConsistencyLevelEnum.BOUNDED); - if (null != catalogTable.getComment()) { - builder.withDescription(catalogTable.getComment()); + if (StringUtils.isNotEmpty(options.get(MilvusOptions.SHARDS_NUM))) { + builder.withShardsNum(Integer.parseInt(options.get(MilvusOptions.SHARDS_NUM))); } CreateCollectionParam createCollectionParam = builder.build(); @@ -260,89 +298,51 @@ public void createTableInternal(TablePath tablePath, CatalogTable catalogTable) throw new MilvusConnectorException( MilvusConnectionErrorCode.CREATE_COLLECTION_ERROR, response.getMessage()); } + + // not exist partition key field, will read show partitions to create + if (!existPartitionKeyField && options.containsKey(MilvusOptions.PARTITION_KEY_FIELD)) { + createPartitionInternal(options.get(MilvusOptions.PARTITION_KEY_FIELD), tablePath); + } + } catch (Exception e) { throw new MilvusConnectorException( MilvusConnectionErrorCode.CREATE_COLLECTION_ERROR, e); } } - private FieldType convertToFieldType(Column column, PrimaryKey primaryKey) { - SeaTunnelDataType seaTunnelDataType = column.getDataType(); - FieldType.Builder build = - FieldType.newBuilder() - .withName(column.getName()) - .withDataType( - MilvusConvertUtils.convertSqlTypeToDataType( - seaTunnelDataType.getSqlType())); - switch (seaTunnelDataType.getSqlType()) { - case ROW: - build.withMaxLength(65535); - break; - case DATE: - build.withMaxLength(20); - break; - case INT: - build.withDataType(DataType.Int32); - break; - case SMALLINT: - build.withDataType(DataType.Int16); - break; - case TINYINT: - build.withDataType(DataType.Int8); - break; - case FLOAT: - build.withDataType(DataType.Float); - break; - case DOUBLE: - build.withDataType(DataType.Double); - break; - case MAP: - build.withDataType(DataType.JSON); - break; - case BOOLEAN: - build.withDataType(DataType.Bool); - break; - case STRING: - if (column.getColumnLength() == 0) { - build.withMaxLength(512); - } else { - build.withMaxLength((int) (column.getColumnLength() / 4)); - } - break; - case ARRAY: - ArrayType arrayType = (ArrayType) column.getDataType(); - SeaTunnelDataType elementType = arrayType.getElementType(); - build.withElementType( - MilvusConvertUtils.convertSqlTypeToDataType(elementType.getSqlType())); - build.withMaxCapacity(4095); - switch (elementType.getSqlType()) { - case STRING: - if (column.getColumnLength() == 0) { - build.withMaxLength(512); - } else { - build.withMaxLength((int) (column.getColumnLength() / 4)); - } - break; - } - break; - case BINARY_VECTOR: - case FLOAT_VECTOR: - case FLOAT16_VECTOR: - case BFLOAT16_VECTOR: - build.withDimension(column.getScale()); - break; + private void createPartitionInternal(String partitionNames, TablePath tablePath) { + R showPartitionsResponseR = + this.client.showPartitions( + ShowPartitionsParam.newBuilder() + .withDatabaseName(tablePath.getDatabaseName()) + .withCollectionName(tablePath.getTableName()) + .build()); + if (!Objects.equals(showPartitionsResponseR.getStatus(), R.success().getStatus())) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.SHOW_PARTITION_ERROR, + showPartitionsResponseR.getMessage()); } - - if (null != primaryKey && primaryKey.getColumnNames().contains(column.getName())) { - build.withPrimaryKey(true); - if (null != primaryKey.getEnableAutoId()) { - build.withAutoID(primaryKey.getEnableAutoId()); - } else { - build.withAutoID(config.get(MilvusSinkConfig.ENABLE_AUTO_ID)); + ProtocolStringList existPartitionNames = + showPartitionsResponseR.getData().getPartitionNamesList(); + + // start to loop create partition + String[] partitionNameArray = partitionNames.split(","); + for (String partitionName : partitionNameArray) { + if (existPartitionNames.contains(partitionName)) { + continue; + } + R response = + this.client.createPartition( + CreatePartitionParam.newBuilder() + .withDatabaseName(tablePath.getDatabaseName()) + .withCollectionName(tablePath.getTableName()) + .withPartitionName(partitionName) + .build()); + if (!R.success().getStatus().equals(response.getStatus())) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.CREATE_PARTITION_ERROR, response.getMessage()); } } - - return build.build(); } @Override diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusOptions.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusOptions.java index b589b21d3da..96241546f6c 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusOptions.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/catalog/MilvusOptions.java @@ -14,9 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.seatunnel.connectors.seatunnel.milvus.catalog; public class MilvusOptions { public static final String ENABLE_DYNAMIC_FIELD = "enableDynamicField"; + public static final String SHARDS_NUM = "shardsNum"; + public static final String PARTITION_KEY_FIELD = "partitionKeyField"; + public static final String PARTITION_NAMES = "partitionNames"; } diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkConfig.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkConfig.java index cd286c987df..8d874fc0ae3 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkConfig.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSinkConfig.java @@ -23,6 +23,8 @@ import org.apache.seatunnel.api.sink.SchemaSaveMode; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import static org.apache.seatunnel.api.sink.DataSaveMode.APPEND_DATA; import static org.apache.seatunnel.api.sink.DataSaveMode.DROP_DATA; @@ -32,6 +34,16 @@ public class MilvusSinkConfig extends MilvusCommonConfig { public static final Option DATABASE = Options.key("database").stringType().noDefaultValue().withDescription("database"); + public static final Option> COLLECTION_DESCRIPTION = + Options.key("collection_description") + .mapType() + .defaultValue(new HashMap<>()) + .withDescription("collection description"); + public static final Option PARTITION_KEY = + Options.key("partition_key") + .stringType() + .noDefaultValue() + .withDescription("Milvus partition key field"); public static final Option SCHEMA_SAVE_MODE = Options.key("schema_save_mode") @@ -70,4 +82,19 @@ public class MilvusSinkConfig extends MilvusCommonConfig { .intType() .defaultValue(1000) .withDescription("writer batch size"); + public static final Option RATE_LIMIT = + Options.key("rate_limit") + .intType() + .defaultValue(100000) + .withDescription("writer rate limit"); + public static final Option LOAD_COLLECTION = + Options.key("load_collection") + .booleanType() + .defaultValue(false) + .withDescription("if load collection"); + public static final Option CREATE_INDEX = + Options.key("create_index") + .booleanType() + .defaultValue(false) + .withDescription("if load collection"); } diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSourceConfig.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSourceConfig.java index b3efba279dc..94b98548386 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSourceConfig.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/config/MilvusSourceConfig.java @@ -33,4 +33,16 @@ public class MilvusSourceConfig extends MilvusCommonConfig { .stringType() .noDefaultValue() .withDescription("Milvus collection to read"); + + public static final Option BATCH_SIZE = + Options.key("batch_size") + .intType() + .defaultValue(1000) + .withDescription("writer batch size"); + + public static final Option RATE_LIMIT = + Options.key("rate_limit") + .intType() + .defaultValue(1000000) + .withDescription("writer rate limit"); } diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/convert/MilvusConvertUtils.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/convert/MilvusConvertUtils.java deleted file mode 100644 index 65027077957..00000000000 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/convert/MilvusConvertUtils.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * 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. - */ - -package org.apache.seatunnel.connectors.seatunnel.milvus.convert; - -import org.apache.seatunnel.api.configuration.ReadonlyConfig; -import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.catalog.Column; -import org.apache.seatunnel.api.table.catalog.ConstraintKey; -import org.apache.seatunnel.api.table.catalog.PhysicalColumn; -import org.apache.seatunnel.api.table.catalog.PrimaryKey; -import org.apache.seatunnel.api.table.catalog.TableIdentifier; -import org.apache.seatunnel.api.table.catalog.TablePath; -import org.apache.seatunnel.api.table.catalog.TableSchema; -import org.apache.seatunnel.api.table.catalog.VectorIndex; -import org.apache.seatunnel.api.table.catalog.exception.CatalogException; -import org.apache.seatunnel.api.table.type.ArrayType; -import org.apache.seatunnel.api.table.type.BasicType; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SqlType; -import org.apache.seatunnel.api.table.type.VectorType; -import org.apache.seatunnel.common.utils.BufferUtils; -import org.apache.seatunnel.common.utils.JsonUtils; -import org.apache.seatunnel.connectors.seatunnel.milvus.catalog.MilvusOptions; -import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig; -import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; -import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.hadoop.util.Lists; - -import com.google.gson.Gson; -import com.google.gson.JsonParser; -import com.google.protobuf.ProtocolStringList; -import io.milvus.client.MilvusServiceClient; -import io.milvus.common.utils.JacksonUtils; -import io.milvus.grpc.CollectionSchema; -import io.milvus.grpc.DataType; -import io.milvus.grpc.DescribeCollectionResponse; -import io.milvus.grpc.DescribeIndexResponse; -import io.milvus.grpc.FieldSchema; -import io.milvus.grpc.IndexDescription; -import io.milvus.grpc.KeyValuePair; -import io.milvus.grpc.ShowCollectionsResponse; -import io.milvus.grpc.ShowType; -import io.milvus.param.ConnectParam; -import io.milvus.param.R; -import io.milvus.param.collection.DescribeCollectionParam; -import io.milvus.param.collection.ShowCollectionsParam; -import io.milvus.param.index.DescribeIndexParam; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class MilvusConvertUtils { - - private static final String CATALOG_NAME = "Milvus"; - - private static final Gson gson = new Gson(); - - public static Map getSourceTables(ReadonlyConfig config) { - MilvusServiceClient client = null; - try { - client = - new MilvusServiceClient( - ConnectParam.newBuilder() - .withUri(config.get(MilvusSourceConfig.URL)) - .withToken(config.get(MilvusSourceConfig.TOKEN)) - .build()); - - String database = config.get(MilvusSourceConfig.DATABASE); - List collectionList = new ArrayList<>(); - if (StringUtils.isNotEmpty(config.get(MilvusSourceConfig.COLLECTION))) { - collectionList.add(config.get(MilvusSourceConfig.COLLECTION)); - } else { - R response = - client.showCollections( - ShowCollectionsParam.newBuilder() - .withDatabaseName(database) - .withShowType(ShowType.All) - .build()); - if (response.getStatus() != R.Status.Success.getCode()) { - throw new MilvusConnectorException( - MilvusConnectionErrorCode.SHOW_COLLECTIONS_ERROR); - } - - ProtocolStringList collections = response.getData().getCollectionNamesList(); - if (CollectionUtils.isEmpty(collections)) { - throw new MilvusConnectorException( - MilvusConnectionErrorCode.DATABASE_NO_COLLECTIONS, database); - } - collectionList.addAll(collections); - } - - Map map = new HashMap<>(); - for (String collection : collectionList) { - CatalogTable catalogTable = getCatalogTable(client, database, collection); - map.put(TablePath.of(database, collection), catalogTable); - } - return map; - } catch (Exception e) { - throw new CatalogException(e.getMessage(), e); - } finally { - if (client != null) { - client.close(); - } - } - } - - public static CatalogTable getCatalogTable( - MilvusServiceClient client, String database, String collection) { - R response = - client.describeCollection( - DescribeCollectionParam.newBuilder() - .withDatabaseName(database) - .withCollectionName(collection) - .build()); - - if (response.getStatus() != R.Status.Success.getCode()) { - throw new MilvusConnectorException(MilvusConnectionErrorCode.DESC_COLLECTION_ERROR); - } - - // collection column - DescribeCollectionResponse data = response.getData(); - CollectionSchema schema = data.getSchema(); - List columns = new ArrayList<>(); - for (FieldSchema fieldSchema : schema.getFieldsList()) { - columns.add(MilvusConvertUtils.convertColumn(fieldSchema)); - } - - // primary key - PrimaryKey primaryKey = buildPrimaryKey(schema.getFieldsList()); - - // index - R describeIndexResponseR = - client.describeIndex( - DescribeIndexParam.newBuilder() - .withDatabaseName(database) - .withCollectionName(collection) - .build()); - if (describeIndexResponseR.getStatus() != R.Status.Success.getCode()) { - throw new MilvusConnectorException(MilvusConnectionErrorCode.DESC_INDEX_ERROR); - } - DescribeIndexResponse indexResponse = describeIndexResponseR.getData(); - List vectorIndexes = buildVectorIndexes(indexResponse); - - // build tableSchema - TableSchema tableSchema = - TableSchema.builder() - .columns(columns) - .primaryKey(primaryKey) - .constraintKey( - ConstraintKey.of( - ConstraintKey.ConstraintType.VECTOR_INDEX_KEY, - "vector_index", - vectorIndexes)) - .build(); - - // build tableId - TableIdentifier tableId = TableIdentifier.of(CATALOG_NAME, database, collection); - - // build options info - Map options = new HashMap<>(); - options.put( - MilvusOptions.ENABLE_DYNAMIC_FIELD, String.valueOf(schema.getEnableDynamicField())); - - return CatalogTable.of( - tableId, tableSchema, options, new ArrayList<>(), schema.getDescription()); - } - - private static List buildVectorIndexes( - DescribeIndexResponse indexResponse) { - if (CollectionUtils.isEmpty(indexResponse.getIndexDescriptionsList())) { - return null; - } - - List list = new ArrayList<>(); - for (IndexDescription per : indexResponse.getIndexDescriptionsList()) { - Map paramsMap = - per.getParamsList().stream() - .collect( - Collectors.toMap(KeyValuePair::getKey, KeyValuePair::getValue)); - - VectorIndex index = - new VectorIndex( - per.getIndexName(), - per.getFieldName(), - paramsMap.get("index_type"), - paramsMap.get("metric_type")); - - list.add(index); - } - - return list; - } - - public static PrimaryKey buildPrimaryKey(List fields) { - for (FieldSchema field : fields) { - if (field.getIsPrimaryKey()) { - return PrimaryKey.of( - field.getName(), Lists.newArrayList(field.getName()), field.getAutoID()); - } - } - - return null; - } - - public static PhysicalColumn convertColumn(FieldSchema fieldSchema) { - DataType dataType = fieldSchema.getDataType(); - PhysicalColumn.PhysicalColumnBuilder builder = PhysicalColumn.builder(); - builder.name(fieldSchema.getName()); - builder.sourceType(dataType.name()); - builder.comment(fieldSchema.getDescription()); - - switch (dataType) { - case Bool: - builder.dataType(BasicType.BOOLEAN_TYPE); - break; - case Int8: - builder.dataType(BasicType.BYTE_TYPE); - break; - case Int16: - builder.dataType(BasicType.SHORT_TYPE); - break; - case Int32: - builder.dataType(BasicType.INT_TYPE); - break; - case Int64: - builder.dataType(BasicType.LONG_TYPE); - break; - case Float: - builder.dataType(BasicType.FLOAT_TYPE); - break; - case Double: - builder.dataType(BasicType.DOUBLE_TYPE); - break; - case VarChar: - builder.dataType(BasicType.STRING_TYPE); - for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { - if (keyValuePair.getKey().equals("max_length")) { - builder.columnLength(Long.parseLong(keyValuePair.getValue()) * 4); - break; - } - } - break; - case String: - case JSON: - builder.dataType(BasicType.STRING_TYPE); - break; - case Array: - builder.dataType(ArrayType.STRING_ARRAY_TYPE); - break; - case FloatVector: - builder.dataType(VectorType.VECTOR_FLOAT_TYPE); - for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { - if (keyValuePair.getKey().equals("dim")) { - builder.scale(Integer.valueOf(keyValuePair.getValue())); - break; - } - } - break; - case BinaryVector: - builder.dataType(VectorType.VECTOR_BINARY_TYPE); - for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { - if (keyValuePair.getKey().equals("dim")) { - builder.scale(Integer.valueOf(keyValuePair.getValue())); - break; - } - } - break; - case SparseFloatVector: - builder.dataType(VectorType.VECTOR_SPARSE_FLOAT_TYPE); - break; - case Float16Vector: - builder.dataType(VectorType.VECTOR_FLOAT16_TYPE); - for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { - if (keyValuePair.getKey().equals("dim")) { - builder.scale(Integer.valueOf(keyValuePair.getValue())); - break; - } - } - break; - case BFloat16Vector: - builder.dataType(VectorType.VECTOR_BFLOAT16_TYPE); - for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { - if (keyValuePair.getKey().equals("dim")) { - builder.scale(Integer.valueOf(keyValuePair.getValue())); - break; - } - } - break; - default: - throw new UnsupportedOperationException("Unsupported data type: " + dataType); - } - - return builder.build(); - } - - public static Object convertBySeaTunnelType(SeaTunnelDataType fieldType, Object value) { - SqlType sqlType = fieldType.getSqlType(); - switch (sqlType) { - case INT: - return Integer.parseInt(value.toString()); - case BIGINT: - return Long.parseLong(value.toString()); - case SMALLINT: - return Short.parseShort(value.toString()); - case STRING: - case DATE: - return value.toString(); - case FLOAT_VECTOR: - ByteBuffer floatVectorBuffer = (ByteBuffer) value; - Float[] floats = BufferUtils.toFloatArray(floatVectorBuffer); - return Arrays.stream(floats).collect(Collectors.toList()); - case BINARY_VECTOR: - case BFLOAT16_VECTOR: - case FLOAT16_VECTOR: - ByteBuffer vector = (ByteBuffer) value; - return gson.toJsonTree(vector.array()); - case SPARSE_FLOAT_VECTOR: - return JsonParser.parseString(JacksonUtils.toJsonString(value)).getAsJsonObject(); - case FLOAT: - return Float.parseFloat(value.toString()); - case BOOLEAN: - return Boolean.parseBoolean(value.toString()); - case DOUBLE: - return Double.parseDouble(value.toString()); - case ARRAY: - ArrayType arrayType = (ArrayType) fieldType; - switch (arrayType.getElementType().getSqlType()) { - case STRING: - String[] stringArray = (String[]) value; - return Arrays.asList(stringArray); - case INT: - Integer[] intArray = (Integer[]) value; - return Arrays.asList(intArray); - case BIGINT: - Long[] longArray = (Long[]) value; - return Arrays.asList(longArray); - case FLOAT: - Float[] floatArray = (Float[]) value; - return Arrays.asList(floatArray); - case DOUBLE: - Double[] doubleArray = (Double[]) value; - return Arrays.asList(doubleArray); - } - case ROW: - SeaTunnelRow row = (SeaTunnelRow) value; - return JsonUtils.toJsonString(row.getFields()); - case MAP: - return JacksonUtils.toJsonString(value); - default: - throw new MilvusConnectorException( - MilvusConnectionErrorCode.NOT_SUPPORT_TYPE, sqlType.name()); - } - } - - public static DataType convertSqlTypeToDataType(SqlType sqlType) { - switch (sqlType) { - case BOOLEAN: - return DataType.Bool; - case TINYINT: - return DataType.Int8; - case SMALLINT: - return DataType.Int16; - case INT: - return DataType.Int32; - case BIGINT: - return DataType.Int64; - case FLOAT: - return DataType.Float; - case DOUBLE: - return DataType.Double; - case STRING: - return DataType.VarChar; - case ARRAY: - return DataType.Array; - case FLOAT_VECTOR: - return DataType.FloatVector; - case BINARY_VECTOR: - return DataType.BinaryVector; - case FLOAT16_VECTOR: - return DataType.Float16Vector; - case BFLOAT16_VECTOR: - return DataType.BFloat16Vector; - case SPARSE_FLOAT_VECTOR: - return DataType.SparseFloatVector; - case DATE: - return DataType.VarChar; - case ROW: - return DataType.VarChar; - } - throw new CatalogException( - String.format("Not support convert to milvus type, sqlType is %s", sqlType)); - } -} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/exception/MilvusConnectionErrorCode.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/exception/MilvusConnectionErrorCode.java index 3acc3de804c..5aaee447ea6 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/exception/MilvusConnectionErrorCode.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/exception/MilvusConnectionErrorCode.java @@ -35,7 +35,12 @@ public enum MilvusConnectionErrorCode implements SeaTunnelErrorCode { CREATE_DATABASE_ERROR("MILVUS-13", "Create database error"), CREATE_COLLECTION_ERROR("MILVUS-14", "Create collection error"), CREATE_INDEX_ERROR("MILVUS-15", "Create index error"), - ; + INIT_CLIENT_ERROR("MILVUS-16", "Init milvus client error"), + WRITE_DATA_FAIL("MILVUS-17", "Write milvus data fail"), + READ_DATA_FAIL("MILVUS-18", "Read milvus data fail"), + LIST_PARTITIONS_FAILED("MILVUS-19", "Failed to list milvus partition"), + SHOW_PARTITION_ERROR("MILVUS-20", "Desc partition error"), + CREATE_PARTITION_ERROR("MILVUS-21", "Create partition error"); private final String code; private final String description; diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusBufferBatchWriter.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusBufferBatchWriter.java new file mode 100644 index 00000000000..36949075f3e --- /dev/null +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusBufferBatchWriter.java @@ -0,0 +1,349 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.milvus.sink; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.PrimaryKey; +import org.apache.seatunnel.api.table.type.CommonOptions; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.common.utils.SeaTunnelException; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; +import org.apache.seatunnel.connectors.seatunnel.milvus.utils.MilvusConnectorUtils; +import org.apache.seatunnel.connectors.seatunnel.milvus.utils.sink.MilvusSinkConverter; + +import org.apache.commons.lang3.StringUtils; + +import com.google.gson.JsonObject; +import io.milvus.v2.client.ConnectConfig; +import io.milvus.v2.client.MilvusClientV2; +import io.milvus.v2.common.IndexParam; +import io.milvus.v2.service.collection.request.AlterCollectionReq; +import io.milvus.v2.service.collection.request.DescribeCollectionReq; +import io.milvus.v2.service.collection.request.GetLoadStateReq; +import io.milvus.v2.service.collection.request.LoadCollectionReq; +import io.milvus.v2.service.collection.response.DescribeCollectionResp; +import io.milvus.v2.service.index.request.CreateIndexReq; +import io.milvus.v2.service.partition.request.CreatePartitionReq; +import io.milvus.v2.service.partition.request.HasPartitionReq; +import io.milvus.v2.service.vector.request.InsertReq; +import io.milvus.v2.service.vector.request.UpsertReq; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; + +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.BATCH_SIZE; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.CREATE_INDEX; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.ENABLE_AUTO_ID; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.ENABLE_UPSERT; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.LOAD_COLLECTION; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.RATE_LIMIT; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.TOKEN; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.URL; + +@Slf4j +public class MilvusBufferBatchWriter { + + private final CatalogTable catalogTable; + private final ReadonlyConfig config; + private final String collectionName; + private final Boolean autoId; + private final Boolean enableUpsert; + private Boolean hasPartitionKey; + + private MilvusClientV2 milvusClient; + private final MilvusSinkConverter milvusSinkConverter; + private int batchSize; + private volatile Map> milvusDataCache; + private final AtomicLong writeCache = new AtomicLong(); + private final AtomicLong writeCount = new AtomicLong(); + + private final List jsonFieldNames; + private final String dynamicFieldName; + + public MilvusBufferBatchWriter(CatalogTable catalogTable, ReadonlyConfig config) + throws SeaTunnelException { + this.catalogTable = catalogTable; + this.config = config; + this.autoId = + getAutoId( + catalogTable.getTableSchema().getPrimaryKey(), config.get(ENABLE_AUTO_ID)); + this.enableUpsert = config.get(ENABLE_UPSERT); + this.batchSize = config.get(BATCH_SIZE); + this.collectionName = catalogTable.getTablePath().getTableName(); + this.milvusDataCache = new HashMap<>(); + this.milvusSinkConverter = new MilvusSinkConverter(); + + this.dynamicFieldName = MilvusConnectorUtils.getDynamicField(catalogTable); + this.jsonFieldNames = MilvusConnectorUtils.getJsonField(catalogTable); + + initMilvusClient(config); + } + /* + * set up the Milvus client + */ + private void initMilvusClient(ReadonlyConfig config) throws SeaTunnelException { + try { + log.info("begin to init Milvus client"); + String dbName = catalogTable.getTablePath().getDatabaseName(); + String collectionName = catalogTable.getTablePath().getTableName(); + + ConnectConfig connectConfig = + ConnectConfig.builder().uri(config.get(URL)).token(config.get(TOKEN)).build(); + this.milvusClient = new MilvusClientV2(connectConfig); + if (StringUtils.isNotEmpty(dbName)) { + milvusClient.useDatabase(dbName); + } + this.hasPartitionKey = + MilvusConnectorUtils.hasPartitionKey(milvusClient, collectionName); + // set rate limit + if (config.get(RATE_LIMIT) > 0) { + log.info("set rate limit for collection: " + collectionName); + Map properties = new HashMap<>(); + properties.put("collection.insertRate.max.mb", config.get(RATE_LIMIT).toString()); + properties.put("collection.upsertRate.max.mb", config.get(RATE_LIMIT).toString()); + AlterCollectionReq alterCollectionReq = + AlterCollectionReq.builder() + .collectionName(collectionName) + .properties(properties) + .build(); + milvusClient.alterCollection(alterCollectionReq); + } + try { + if (config.get(CREATE_INDEX)) { + // create index + log.info("create index for collection: " + collectionName); + DescribeCollectionResp describeCollectionResp = + milvusClient.describeCollection( + DescribeCollectionReq.builder() + .collectionName(collectionName) + .build()); + List indexParams = new ArrayList<>(); + for (String fieldName : describeCollectionResp.getVectorFieldNames()) { + IndexParam indexParam = + IndexParam.builder() + .fieldName(fieldName) + .metricType(IndexParam.MetricType.COSINE) + .build(); + indexParams.add(indexParam); + } + CreateIndexReq createIndexReq = + CreateIndexReq.builder() + .collectionName(collectionName) + .indexParams(indexParams) + .build(); + milvusClient.createIndex(createIndexReq); + } + } catch (Exception e) { + log.warn("create index failed, maybe index already exists"); + } + if (config.get(LOAD_COLLECTION) + && !milvusClient.getLoadState( + GetLoadStateReq.builder().collectionName(collectionName).build())) { + log.info("load collection: " + collectionName); + milvusClient.loadCollection( + LoadCollectionReq.builder().collectionName(collectionName).build()); + } + log.info("init Milvus client success"); + } catch (Exception e) { + log.error("init Milvus client failed", e); + throw new MilvusConnectorException(MilvusConnectionErrorCode.INIT_CLIENT_ERROR, e); + } + } + + private Boolean getAutoId(PrimaryKey primaryKey, Boolean enableAutoId) { + if (null != primaryKey && null != primaryKey.getEnableAutoId()) { + return primaryKey.getEnableAutoId(); + } else { + return enableAutoId; + } + } + + public void addToBatch(SeaTunnelRow element) { + // put data to cache by partition + if (element.getOptions().containsKey(CommonOptions.PARTITION.getName())) { + String partitionName = + element.getOptions().get(CommonOptions.PARTITION.getName()).toString(); + if (!milvusDataCache.containsKey(partitionName)) { + Boolean hasPartition = + milvusClient.hasPartition( + HasPartitionReq.builder() + .collectionName(collectionName) + .partitionName(partitionName) + .build()); + if (!hasPartition) { + log.info("create partition: " + partitionName); + CreatePartitionReq createPartitionReq = + CreatePartitionReq.builder() + .collectionName(collectionName) + .partitionName(partitionName) + .build(); + milvusClient.createPartition(createPartitionReq); + log.info("create partition success"); + } + } + } + JsonObject data = + milvusSinkConverter.buildMilvusData( + catalogTable, config, jsonFieldNames, dynamicFieldName, element); + String partitionName = + element.getOptions() + .getOrDefault(CommonOptions.PARTITION.getName(), "_default") + .toString(); + this.milvusDataCache.computeIfAbsent(partitionName, k -> new ArrayList<>()); + milvusDataCache.get(partitionName).add(data); + writeCache.incrementAndGet(); + } + + public boolean needFlush() { + return this.writeCache.get() >= this.batchSize; + } + + public void flush() throws Exception { + log.info("Starting to put {} records to Milvus.", this.writeCache.get()); + // Flush the batch writer + // Get the number of records completed + if (this.milvusDataCache.isEmpty()) { + return; + } + writeData2Collection(); + log.info( + "Successfully put {} records to Milvus. Total records written: {}", + this.writeCache.get(), + this.writeCount.get()); + this.milvusDataCache = new HashMap<>(); + this.writeCache.set(0L); + } + + public void close() throws Exception { + String collectionName = catalogTable.getTablePath().getTableName(); + // set rate limit + Map properties = new HashMap<>(); + properties.put("collection.insertRate.max.mb", "-1"); + properties.put("collection.upsertRate.max.mb", "-1"); + AlterCollectionReq alterCollectionReq = + AlterCollectionReq.builder() + .collectionName(collectionName) + .properties(properties) + .build(); + milvusClient.alterCollection(alterCollectionReq); + this.milvusClient.close(10); + } + + private void writeData2Collection() throws Exception { + try { + for (String partitionName : milvusDataCache.keySet()) { + // default to use upsertReq, but upsert only works when autoID is disabled + List data = milvusDataCache.get(partitionName); + if (Objects.equals(partitionName, "_default") || hasPartitionKey) { + partitionName = null; + } + if (enableUpsert && !autoId) { + upsertWrite(partitionName, data); + } else { + insertWrite(partitionName, data); + } + } + } catch (Exception e) { + log.error("write data to Milvus failed", e); + log.error("error data: " + milvusDataCache); + throw new MilvusConnectorException(MilvusConnectionErrorCode.WRITE_DATA_FAIL); + } + writeCount.addAndGet(this.writeCache.get()); + } + + private void upsertWrite(String partitionName, List data) + throws InterruptedException { + UpsertReq upsertReq = + UpsertReq.builder().collectionName(this.collectionName).data(data).build(); + if (StringUtils.isNotEmpty(partitionName)) { + upsertReq.setPartitionName(partitionName); + } + try { + milvusClient.upsert(upsertReq); + } catch (Exception e) { + if (e.getMessage().contains("rate limit exceeded") + || e.getMessage().contains("received message larger than max")) { + if (data.size() > 10) { + log.warn("upsert data failed, retry in smaller chunks: {} ", data.size() / 2); + this.batchSize = this.batchSize / 2; + log.info("sleep 1 minute to avoid rate limit"); + // sleep 1 minute to avoid rate limit + Thread.sleep(60000); + log.info("sleep 1 minute success"); + // Split the data and retry in smaller chunks + List firstHalf = data.subList(0, data.size() / 2); + List secondHalf = data.subList(data.size() / 2, data.size()); + upsertWrite(partitionName, firstHalf); + upsertWrite(partitionName, secondHalf); + } else { + // If the data size is 10, throw the exception to avoid infinite recursion + throw new MilvusConnectorException( + MilvusConnectionErrorCode.WRITE_DATA_FAIL, + "upsert data failed," + " size down to 10, break", + e); + } + } else { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.WRITE_DATA_FAIL, + "upsert data failed with unknown exception", + e); + } + } + log.info("upsert data success"); + } + + private void insertWrite(String partitionName, List data) { + InsertReq insertReq = + InsertReq.builder().collectionName(this.collectionName).data(data).build(); + if (StringUtils.isNotEmpty(partitionName)) { + insertReq.setPartitionName(partitionName); + } + try { + milvusClient.insert(insertReq); + } catch (Exception e) { + if (e.getMessage().contains("rate limit exceeded") + || e.getMessage().contains("received message larger than max")) { + if (data.size() > 10) { + log.warn("insert data failed, retry in smaller chunks: {} ", data.size() / 2); + // Split the data and retry in smaller chunks + List firstHalf = data.subList(0, data.size() / 2); + List secondHalf = data.subList(data.size() / 2, data.size()); + this.batchSize = this.batchSize / 2; + insertWrite(partitionName, firstHalf); + insertWrite(partitionName, secondHalf); + } else { + // If the data size is 10, throw the exception to avoid infinite recursion + throw new MilvusConnectorException( + MilvusConnectionErrorCode.WRITE_DATA_FAIL, "insert data failed", e); + } + } else { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.WRITE_DATA_FAIL, + "insert data failed with unknown exception", + e); + } + } + } +} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSink.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSink.java index 2015be19739..9167d806df1 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSink.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSink.java @@ -38,10 +38,13 @@ import org.apache.seatunnel.connectors.seatunnel.milvus.state.MilvusCommitInfo; import org.apache.seatunnel.connectors.seatunnel.milvus.state.MilvusSinkState; +import lombok.extern.slf4j.Slf4j; + import java.util.Collections; import java.util.List; import java.util.Optional; +@Slf4j public class MilvusSink implements SeaTunnelSink< SeaTunnelRow, @@ -61,7 +64,6 @@ public MilvusSink(ReadonlyConfig config, CatalogTable catalogTable) { @Override public SinkWriter createWriter( SinkWriter.Context context) { - return new MilvusSinkWriter(context, catalogTable, config, Collections.emptyList()); } @@ -112,4 +114,9 @@ public Optional getSaveModeHandler() { catalogTable, null)); } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkWriter.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkWriter.java index 8fee6ebc68f..98b2b46c3b4 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkWriter.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/MilvusSinkWriter.java @@ -21,74 +21,53 @@ import org.apache.seatunnel.api.sink.SinkCommitter; import org.apache.seatunnel.api.sink.SinkWriter; import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.catalog.PrimaryKey; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig; -import org.apache.seatunnel.connectors.seatunnel.milvus.sink.batch.MilvusBatchWriter; -import org.apache.seatunnel.connectors.seatunnel.milvus.sink.batch.MilvusBufferBatchWriter; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; import org.apache.seatunnel.connectors.seatunnel.milvus.state.MilvusCommitInfo; import org.apache.seatunnel.connectors.seatunnel.milvus.state.MilvusSinkState; -import io.milvus.v2.client.ConnectConfig; -import io.milvus.v2.client.MilvusClientV2; import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.util.List; import java.util.Optional; -import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.BATCH_SIZE; - -@Slf4j /** MilvusSinkWriter is a sink writer that will write {@link SeaTunnelRow} to Milvus. */ +@Slf4j public class MilvusSinkWriter implements SinkWriter { - private final Context context; - private final ReadonlyConfig config; - private MilvusBatchWriter batchWriter; + private final MilvusBufferBatchWriter batchWriter; + private ReadonlyConfig config; public MilvusSinkWriter( Context context, CatalogTable catalogTable, ReadonlyConfig config, List milvusSinkStates) { - this.context = context; + this.batchWriter = new MilvusBufferBatchWriter(catalogTable, config); this.config = config; - ConnectConfig connectConfig = - ConnectConfig.builder() - .uri(config.get(MilvusSinkConfig.URL)) - .token(config.get(MilvusSinkConfig.TOKEN)) - .dbName(config.get(MilvusSinkConfig.DATABASE)) - .build(); - this.batchWriter = - new MilvusBufferBatchWriter( - catalogTable, - config.get(BATCH_SIZE), - getAutoId(catalogTable.getTableSchema().getPrimaryKey()), - config.get(MilvusSinkConfig.ENABLE_UPSERT), - new MilvusClientV2(connectConfig)); + log.info("create Milvus sink writer success"); + log.info("MilvusSinkWriter config: " + config); } /** * write data to third party data receiver. * * @param element the data need be written. - * @throws IOException throw IOException when write data failed. */ @Override public void write(SeaTunnelRow element) { batchWriter.addToBatch(element); if (batchWriter.needFlush()) { - batchWriter.flush(); - } - } - - private Boolean getAutoId(PrimaryKey primaryKey) { - if (null != primaryKey && null != primaryKey.getEnableAutoId()) { - return primaryKey.getEnableAutoId(); - } else { - return config.get(MilvusSinkConfig.ENABLE_AUTO_ID); + try { + // Flush the batch writer + batchWriter.flush(); + } catch (Exception e) { + log.error("flush Milvus sink writer failed", e); + throw new MilvusConnectorException(MilvusConnectionErrorCode.WRITE_DATA_FAIL, e); + } } } @@ -102,7 +81,6 @@ private Boolean getAutoId(PrimaryKey primaryKey) { */ @Override public Optional prepareCommit() throws IOException { - batchWriter.flush(); return Optional.empty(); } @@ -122,9 +100,14 @@ public void abortPrepare() {} */ @Override public void close() throws IOException { - if (batchWriter != null) { + try { + log.info("Stopping Milvus Client"); batchWriter.flush(); batchWriter.close(); + log.info("Stop Milvus Client success"); + } catch (Exception e) { + log.error("Stop Milvus Client failed", e); + throw new MilvusConnectorException(MilvusConnectionErrorCode.CLOSE_CLIENT_ERROR, e); } } } diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/batch/MilvusBufferBatchWriter.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/batch/MilvusBufferBatchWriter.java deleted file mode 100644 index 46f4e7ce7c7..00000000000 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/batch/MilvusBufferBatchWriter.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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. - */ - -package org.apache.seatunnel.connectors.seatunnel.milvus.sink.batch; - -import org.apache.seatunnel.api.table.catalog.CatalogTable; -import org.apache.seatunnel.api.table.catalog.PrimaryKey; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; -import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.common.utils.SeaTunnelException; -import org.apache.seatunnel.connectors.seatunnel.milvus.convert.MilvusConvertUtils; -import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; -import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; - -import org.apache.commons.collections4.CollectionUtils; - -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import io.milvus.v2.client.MilvusClientV2; -import io.milvus.v2.service.vector.request.InsertReq; -import io.milvus.v2.service.vector.request.UpsertReq; - -import java.util.ArrayList; -import java.util.List; - -import static org.apache.seatunnel.api.table.catalog.PrimaryKey.isPrimaryKeyField; - -public class MilvusBufferBatchWriter implements MilvusBatchWriter { - - private final int batchSize; - private final CatalogTable catalogTable; - private final Boolean autoId; - private final Boolean enableUpsert; - private final String collectionName; - private MilvusClientV2 milvusClient; - - private volatile List milvusDataCache; - private volatile int writeCount = 0; - private static final Gson GSON = new Gson(); - - public MilvusBufferBatchWriter( - CatalogTable catalogTable, - Integer batchSize, - Boolean autoId, - Boolean enableUpsert, - MilvusClientV2 milvusClient) { - this.catalogTable = catalogTable; - this.autoId = autoId; - this.enableUpsert = enableUpsert; - this.milvusClient = milvusClient; - this.collectionName = catalogTable.getTablePath().getTableName(); - this.batchSize = batchSize; - this.milvusDataCache = new ArrayList<>(batchSize); - } - - @Override - public void addToBatch(SeaTunnelRow element) { - JsonObject data = buildMilvusData(element); - milvusDataCache.add(data); - writeCount++; - } - - @Override - public boolean needFlush() { - return this.writeCount >= this.batchSize; - } - - @Override - public synchronized boolean flush() { - if (CollectionUtils.isEmpty(this.milvusDataCache)) { - return true; - } - writeData2Collection(); - this.milvusDataCache = new ArrayList<>(this.batchSize); - this.writeCount = 0; - return true; - } - - @Override - public void close() { - try { - this.milvusClient.close(10); - } catch (InterruptedException e) { - throw new SeaTunnelException(e); - } - } - - private JsonObject buildMilvusData(SeaTunnelRow element) { - SeaTunnelRowType seaTunnelRowType = catalogTable.getSeaTunnelRowType(); - PrimaryKey primaryKey = catalogTable.getTableSchema().getPrimaryKey(); - - JsonObject data = new JsonObject(); - for (int i = 0; i < seaTunnelRowType.getFieldNames().length; i++) { - String fieldName = seaTunnelRowType.getFieldNames()[i]; - - if (autoId && isPrimaryKeyField(primaryKey, fieldName)) { - continue; // if create table open AutoId, then don't need insert data with - // primaryKey field. - } - - SeaTunnelDataType fieldType = seaTunnelRowType.getFieldType(i); - Object value = element.getField(i); - if (null == value) { - throw new MilvusConnectorException( - MilvusConnectionErrorCode.FIELD_IS_NULL, fieldName); - } - - data.add( - fieldName, - GSON.toJsonTree(MilvusConvertUtils.convertBySeaTunnelType(fieldType, value))); - } - return data; - } - - private void writeData2Collection() { - // default to use upsertReq, but upsert only works when autoID is disabled - if (enableUpsert && !autoId) { - UpsertReq upsertReq = - UpsertReq.builder() - .collectionName(this.collectionName) - .data(this.milvusDataCache) - .build(); - milvusClient.upsert(upsertReq); - } else { - InsertReq insertReq = - InsertReq.builder() - .collectionName(this.collectionName) - .data(this.milvusDataCache) - .build(); - milvusClient.insert(insertReq); - } - } -} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSource.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSource.java index 76ccfb743e5..abb7e9c898d 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSource.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSource.java @@ -28,7 +28,7 @@ import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig; -import org.apache.seatunnel.connectors.seatunnel.milvus.convert.MilvusConvertUtils; +import org.apache.seatunnel.connectors.seatunnel.milvus.utils.MilvusConvertUtils; import java.util.ArrayList; import java.util.List; @@ -42,9 +42,10 @@ public class MilvusSource private final ReadonlyConfig config; private final Map sourceTables; - public MilvusSource(ReadonlyConfig sourceConfig) { - this.config = sourceConfig; - this.sourceTables = MilvusConvertUtils.getSourceTables(config); + public MilvusSource(ReadonlyConfig sourceConfing) { + this.config = sourceConfing; + MilvusConvertUtils milvusConvertUtils = new MilvusConvertUtils(sourceConfing); + this.sourceTables = milvusConvertUtils.getSourceTables(); } @Override diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceReader.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceReader.java index 7464c652b31..cd8b0261248 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceReader.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceReader.java @@ -24,44 +24,51 @@ import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.api.table.catalog.TableSchema; -import org.apache.seatunnel.api.table.type.RowKind; -import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.CommonOptions; import org.apache.seatunnel.api.table.type.SeaTunnelRow; -import org.apache.seatunnel.api.table.type.SeaTunnelRowType; -import org.apache.seatunnel.common.exception.CommonErrorCode; -import org.apache.seatunnel.common.utils.BufferUtils; import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig; import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; +import org.apache.seatunnel.connectors.seatunnel.milvus.utils.source.MilvusSourceConverter; import org.apache.curator.shaded.com.google.common.collect.Lists; +import org.codehaus.plexus.util.StringUtils; + import io.milvus.client.MilvusServiceClient; import io.milvus.grpc.GetLoadStateResponse; import io.milvus.grpc.LoadState; +import io.milvus.grpc.QueryResults; import io.milvus.orm.iterator.QueryIterator; import io.milvus.param.ConnectParam; import io.milvus.param.R; +import io.milvus.param.RpcStatus; +import io.milvus.param.collection.AlterCollectionParam; import io.milvus.param.collection.GetLoadStateParam; import io.milvus.param.dml.QueryIteratorParam; +import io.milvus.param.dml.QueryParam; import io.milvus.response.QueryResultsWrapper; import lombok.extern.slf4j.Slf4j; import java.io.IOException; -import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentLinkedDeque; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig.BATCH_SIZE; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig.RATE_LIMIT; + @Slf4j public class MilvusSourceReader implements SourceReader { private final Deque pendingSplits = new ConcurrentLinkedDeque<>(); private final ReadonlyConfig config; private final Context context; - private Map sourceTables; + private final Map sourceTables; private MilvusServiceClient client; @@ -84,11 +91,36 @@ public void open() throws Exception { .withUri(config.get(MilvusSourceConfig.URL)) .withToken(config.get(MilvusSourceConfig.TOKEN)) .build()); + setRateLimit(config.get(RATE_LIMIT).toString()); + } + + private void setRateLimit(String rateLimit) { + log.info("Set rate limit: " + rateLimit); + for (Map.Entry entry : sourceTables.entrySet()) { + TablePath tablePath = entry.getKey(); + String collectionName = tablePath.getTableName(); + + AlterCollectionParam alterCollectionParam = + AlterCollectionParam.newBuilder() + .withDatabaseName(tablePath.getDatabaseName()) + .withCollectionName(collectionName) + .withProperty("collection.queryRate.max.qps", rateLimit) + .build(); + R response = client.alterCollection(alterCollectionParam); + if (response.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.SERVER_RESPONSE_FAILED, response.getException()); + } + } + log.info("Set rate limit success"); } @Override public void close() throws IOException { + log.info("Close milvus source reader"); + setRateLimit("-1"); client.close(); + log.info("Close milvus source reader success"); } @Override @@ -96,7 +128,13 @@ public void pollNext(Collector output) throws Exception { synchronized (output.getCheckpointLock()) { MilvusSourceSplit split = pendingSplits.poll(); if (null != split) { - handleEveryRowInternal(split, output); + try { + log.info("Begin to read data from split: " + split); + pollNextData(split, output); + } catch (Exception e) { + log.error("Read data from split: " + split + " failed", e); + throw new MilvusConnectorException(MilvusConnectionErrorCode.READ_DATA_FAIL, e); + } } else { if (!noMoreSplit) { log.info("Milvus source wait split!"); @@ -113,9 +151,12 @@ public void pollNext(Collector output) throws Exception { Thread.sleep(1000L); } - private void handleEveryRowInternal(MilvusSourceSplit split, Collector output) { + private void pollNextData(MilvusSourceSplit split, Collector output) + throws InterruptedException { TablePath tablePath = split.getTablePath(); + String partitionName = split.getPartitionName(); TableSchema tableSchema = sourceTables.get(tablePath).getTableSchema(); + log.info("begin to read data from milvus, table schema: " + tableSchema); if (null == tableSchema) { throw new MilvusConnectorException( MilvusConnectionErrorCode.SOURCE_TABLE_SCHEMA_IS_NULL); @@ -136,129 +177,117 @@ private void handleEveryRowInternal(MilvusSourceSplit split, Collector response = client.queryIterator(param); - if (response.getStatus() != R.Status.Success.getCode()) { + R queryResultsR = client.query(queryParam.build()); + + if (queryResultsR.getStatus() != R.Status.Success.getCode()) { throw new MilvusConnectorException( MilvusConnectionErrorCode.SERVER_RESPONSE_FAILED, loadStateResponse.getException()); } + QueryResultsWrapper wrapper = new QueryResultsWrapper(queryResultsR.getData()); + List records = wrapper.getRowRecords(); + log.info("Total records num: " + records.get(0).getFieldValues().get("count(*)")); - QueryIterator iterator = response.getData(); - while (true) { - List next = iterator.next(); - if (next == null || next.isEmpty()) { - break; - } else { - for (QueryResultsWrapper.RowRecord record : next) { - SeaTunnelRow seaTunnelRow = - convertToSeaTunnelRow(record, tableSchema, tablePath); - output.collect(seaTunnelRow); - } - } - } + long batchSize = (long) config.get(BATCH_SIZE); + queryIteratorData(tablePath, partitionName, tableSchema, output, batchSize); } - public SeaTunnelRow convertToSeaTunnelRow( - QueryResultsWrapper.RowRecord record, TableSchema tableSchema, TablePath tablePath) { - SeaTunnelRowType typeInfo = tableSchema.toPhysicalRowDataType(); - Object[] fields = new Object[record.getFieldValues().size()]; - Map fieldValuesMap = record.getFieldValues(); - String[] fieldNames = typeInfo.getFieldNames(); - for (int fieldIndex = 0; fieldIndex < typeInfo.getTotalFields(); fieldIndex++) { - SeaTunnelDataType seaTunnelDataType = typeInfo.getFieldType(fieldIndex); - Object filedValues = fieldValuesMap.get(fieldNames[fieldIndex]); - switch (seaTunnelDataType.getSqlType()) { - case STRING: - fields[fieldIndex] = filedValues.toString(); - break; - case BOOLEAN: - if (filedValues instanceof Boolean) { - fields[fieldIndex] = filedValues; - } else { - fields[fieldIndex] = Boolean.valueOf(filedValues.toString()); - } - break; - case INT: - if (filedValues instanceof Integer) { - fields[fieldIndex] = filedValues; - } else { - fields[fieldIndex] = Integer.valueOf(filedValues.toString()); - } - break; - case BIGINT: - if (filedValues instanceof Long) { - fields[fieldIndex] = filedValues; - } else { - fields[fieldIndex] = Long.parseLong(filedValues.toString()); - } - break; - case FLOAT: - if (filedValues instanceof Float) { - fields[fieldIndex] = filedValues; - } else { - fields[fieldIndex] = Float.parseFloat(filedValues.toString()); - } - break; - case DOUBLE: - if (filedValues instanceof Double) { - fields[fieldIndex] = filedValues; - } else { - fields[fieldIndex] = Double.parseDouble(filedValues.toString()); - } - break; - case FLOAT_VECTOR: - if (filedValues instanceof List) { - List list = (List) filedValues; - Float[] arrays = new Float[list.size()]; - for (int i = 0; i < list.size(); i++) { - arrays[i] = Float.parseFloat(list.get(i).toString()); - } - fields[fieldIndex] = BufferUtils.toByteBuffer(arrays); - break; - } else { - throw new MilvusConnectorException( - CommonErrorCode.UNSUPPORTED_DATA_TYPE, - "Unexpected vector value: " + filedValues); - } - case BINARY_VECTOR: - case FLOAT16_VECTOR: - case BFLOAT16_VECTOR: - if (filedValues instanceof ByteBuffer) { - fields[fieldIndex] = filedValues; + private void queryIteratorData( + TablePath tablePath, + String partitionName, + TableSchema tableSchema, + Collector output, + long batchSize) + throws InterruptedException { + try { + MilvusSourceConverter sourceConverter = new MilvusSourceConverter(tableSchema); + + QueryIteratorParam.Builder param = + QueryIteratorParam.newBuilder() + .withDatabaseName(tablePath.getDatabaseName()) + .withCollectionName(tablePath.getTableName()) + .withOutFields(Lists.newArrayList("*")) + .withBatchSize(batchSize); + + if (StringUtils.isNotEmpty(partitionName)) { + param.withPartitionNames(Collections.singletonList(partitionName)); + } + + R response = client.queryIterator(param.build()); + if (response.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.SERVER_RESPONSE_FAILED, response.getException()); + } + int maxFailRetry = 3; + QueryIterator iterator = response.getData(); + while (maxFailRetry > 0) { + try { + List next = iterator.next(); + if (next == null || next.isEmpty()) { break; } else { - throw new MilvusConnectorException( - CommonErrorCode.UNSUPPORTED_DATA_TYPE, - "Unexpected vector value: " + filedValues); + for (QueryResultsWrapper.RowRecord record : next) { + SeaTunnelRow seaTunnelRow = + sourceConverter.convertToSeaTunnelRow( + record, tableSchema, tablePath); + if (StringUtils.isNotEmpty(partitionName)) { + Map options = new HashMap<>(); + options.put(CommonOptions.PARTITION.getName(), partitionName); + seaTunnelRow.setOptions(options); + } + output.collect(seaTunnelRow); + } } - case SPARSE_FLOAT_VECTOR: - if (filedValues instanceof Map) { - fields[fieldIndex] = filedValues; - break; + } catch (Exception e) { + if (e.getMessage().contains("rate limit exceeded")) { + // for rateLimit, we can try iterator again after 30s, no need to update + // batch size directly + maxFailRetry--; + if (maxFailRetry == 0) { + log.error( + "Iterate next data from milvus failed, batchSize = {}, throw exception", + batchSize, + e); + throw new MilvusConnectorException( + MilvusConnectionErrorCode.READ_DATA_FAIL, e); + } + log.error( + "Iterate next data from milvus failed, batchSize = {}, will retry after 30 s, maxRetry: {}", + batchSize, + maxFailRetry, + e); + Thread.sleep(30000); } else { + // if this error, we need to reduce batch size and try again, so throw + // exception here throw new MilvusConnectorException( - CommonErrorCode.UNSUPPORTED_DATA_TYPE, - "Unexpected vector value: " + filedValues); + MilvusConnectionErrorCode.READ_DATA_FAIL, e); } - default: - throw new MilvusConnectorException( - CommonErrorCode.UNSUPPORTED_DATA_TYPE, - "Unexpected value: " + seaTunnelDataType.getSqlType().name()); + } + } + } catch (Exception e) { + if (e.getMessage().contains("rate limit exceeded") && batchSize > 10) { + log.error( + "Query Iterate data from milvus failed, retry from beginning with smaller batch size: {} after 30 s", + batchSize / 2, + e); + Thread.sleep(30000); + queryIteratorData(tablePath, partitionName, tableSchema, output, batchSize / 2); + } else { + throw new MilvusConnectorException(MilvusConnectionErrorCode.READ_DATA_FAIL, e); } } - - SeaTunnelRow seaTunnelRow = new SeaTunnelRow(fields); - seaTunnelRow.setTableId(tablePath.getFullName()); - seaTunnelRow.setRowKind(RowKind.INSERT); - return seaTunnelRow; } @Override @@ -268,7 +297,7 @@ public List snapshotState(long checkpointId) throws Exception @Override public void addSplits(List splits) { - log.info("Adding milvus splits to reader: {}", splits); + log.info("Adding milvus splits to reader: " + splits); pendingSplits.addAll(splits); } diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplit.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplit.java index e79d74b6dc0..d448242d9aa 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplit.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplit.java @@ -29,6 +29,7 @@ public class MilvusSourceSplit implements SourceSplit { private TablePath tablePath; private String splitId; + private String partitionName; @Override public String splitId() { diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplitEnumertor.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplitEnumertor.java index e01e9c8ad5d..1c181baffc1 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplitEnumertor.java +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/source/MilvusSourceSplitEnumertor.java @@ -22,8 +22,19 @@ import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.catalog.TablePath; import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; +import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; +import io.milvus.client.MilvusClient; +import io.milvus.client.MilvusServiceClient; +import io.milvus.grpc.DescribeCollectionResponse; +import io.milvus.grpc.FieldSchema; +import io.milvus.grpc.ShowPartitionsResponse; +import io.milvus.param.ConnectParam; +import io.milvus.param.R; +import io.milvus.param.collection.DescribeCollectionParam; +import io.milvus.param.partition.ShowPartitionsParam; import lombok.extern.slf4j.Slf4j; import java.io.IOException; @@ -45,8 +56,9 @@ public class MilvusSourceSplitEnumertor private final ConcurrentLinkedQueue pendingTables; private final Map> pendingSplits; private final Object stateLock = new Object(); + private MilvusClient client = null; - private ReadonlyConfig config; + private final ReadonlyConfig config; public MilvusSourceSplitEnumertor( Context context, @@ -66,7 +78,14 @@ public MilvusSourceSplitEnumertor( } @Override - public void open() {} + public void open() { + ConnectParam connectParam = + ConnectParam.newBuilder() + .withUri(config.get(MilvusSourceConfig.URL)) + .withToken(config.get(MilvusSourceConfig.TOKEN)) + .build(); + this.client = new MilvusServiceClient(connectParam); + } @Override public void run() throws Exception { @@ -92,17 +111,56 @@ public void run() throws Exception { } private Collection generateSplits(CatalogTable table) { - log.info("Start splitting table {} into chunks...", table.getTablePath()); - MilvusSourceSplit milvusSourceSplit = - MilvusSourceSplit.builder() - .splitId(createSplitId(table.getTablePath(), 0)) - .tablePath(table.getTablePath()) - .build(); - - return Collections.singletonList(milvusSourceSplit); + log.info("Start splitting table {} into chunks by partition...", table.getTablePath()); + String database = table.getTablePath().getDatabaseName(); + String collection = table.getTablePath().getTableName(); + R describeCollectionResponseR = + client.describeCollection( + DescribeCollectionParam.newBuilder() + .withDatabaseName(database) + .withCollectionName(collection) + .build()); + boolean hasPartitionKey = + describeCollectionResponseR.getData().getSchema().getFieldsList().stream() + .anyMatch(FieldSchema::getIsPartitionKey); + List milvusSourceSplits = new ArrayList<>(); + if (!hasPartitionKey) { + ShowPartitionsParam showPartitionsParam = + ShowPartitionsParam.newBuilder() + .withDatabaseName(database) + .withCollectionName(collection) + .build(); + R showPartitionsResponseR = + client.showPartitions(showPartitionsParam); + if (showPartitionsResponseR.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.LIST_PARTITIONS_FAILED, + "Failed to show partitions: " + showPartitionsResponseR.getMessage()); + } + List partitionList = showPartitionsResponseR.getData().getPartitionNamesList(); + for (String partitionName : partitionList) { + MilvusSourceSplit milvusSourceSplit = + MilvusSourceSplit.builder() + .tablePath(table.getTablePath()) + .splitId(createSplitId(table.getTablePath(), partitionName)) + .partitionName(partitionName) + .build(); + log.info("Generated split: {}", milvusSourceSplit); + milvusSourceSplits.add(milvusSourceSplit); + } + } else { + MilvusSourceSplit milvusSourceSplit = + MilvusSourceSplit.builder() + .tablePath(table.getTablePath()) + .splitId(createSplitId(table.getTablePath(), "0")) + .build(); + log.info("Generated split: {}", milvusSourceSplit); + milvusSourceSplits.add(milvusSourceSplit); + } + return milvusSourceSplits; } - protected String createSplitId(TablePath tablePath, int index) { + protected String createSplitId(TablePath tablePath, String index) { return String.format("%s-%s", tablePath, index); } @@ -133,7 +191,11 @@ private void assignSplit(Collection readers) { } @Override - public void close() throws IOException {} + public void close() throws IOException { + if (client != null) { + client.close(); + } + } @Override public void addSplitsBack(List splits, int subtaskId) { diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/MilvusConnectorUtils.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/MilvusConnectorUtils.java new file mode 100644 index 00000000000..e9b762f168c --- /dev/null +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/MilvusConnectorUtils.java @@ -0,0 +1,73 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.milvus.utils; + +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.type.CommonOptions; + +import io.milvus.v2.client.MilvusClientV2; +import io.milvus.v2.service.collection.request.CreateCollectionReq; +import io.milvus.v2.service.collection.request.DescribeCollectionReq; +import io.milvus.v2.service.collection.response.DescribeCollectionResp; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class MilvusConnectorUtils { + + public static Boolean hasPartitionKey(MilvusClientV2 milvusClient, String collectionName) { + + DescribeCollectionResp describeCollectionResp = + milvusClient.describeCollection( + DescribeCollectionReq.builder().collectionName(collectionName).build()); + return describeCollectionResp.getCollectionSchema().getFieldSchemaList().stream() + .anyMatch(CreateCollectionReq.FieldSchema::getIsPartitionKey); + } + + public static String getDynamicField(CatalogTable catalogTable) { + List columns = catalogTable.getTableSchema().getColumns(); + Column dynamicField = null; + for (Column column : columns) { + if (column.getOptions() != null + && (Boolean) + column.getOptions() + .getOrDefault(CommonOptions.METADATA.getName(), false)) { + // skip dynamic field + dynamicField = column; + } + } + return dynamicField == null ? null : dynamicField.getName(); + } + + public static List getJsonField(CatalogTable catalogTable) { + List columns = catalogTable.getTableSchema().getColumns(); + List jsonColumn = new ArrayList<>(); + for (Column column : columns) { + if (column.getOptions() != null + && column.getOptions().containsKey(CommonOptions.JSON.getName()) + && (Boolean) column.getOptions().get(CommonOptions.JSON.getName())) { + // skip dynamic field + jsonColumn.add(column.getName()); + } + } + return jsonColumn; + } +} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/MilvusConvertUtils.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/MilvusConvertUtils.java new file mode 100644 index 00000000000..8c8d9b616ab --- /dev/null +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/MilvusConvertUtils.java @@ -0,0 +1,279 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.milvus.utils; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.ConstraintKey; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.PrimaryKey; +import org.apache.seatunnel.api.table.catalog.TableIdentifier; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.catalog.VectorIndex; +import org.apache.seatunnel.api.table.type.CommonOptions; +import org.apache.seatunnel.connectors.seatunnel.milvus.catalog.MilvusOptions; +import org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSourceConfig; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; +import org.apache.seatunnel.connectors.seatunnel.milvus.utils.source.MilvusSourceConverter; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.util.Lists; + +import com.google.protobuf.ProtocolStringList; +import io.milvus.client.MilvusServiceClient; +import io.milvus.grpc.CollectionSchema; +import io.milvus.grpc.DescribeCollectionResponse; +import io.milvus.grpc.DescribeIndexResponse; +import io.milvus.grpc.FieldSchema; +import io.milvus.grpc.IndexDescription; +import io.milvus.grpc.KeyValuePair; +import io.milvus.grpc.ShowCollectionsResponse; +import io.milvus.grpc.ShowPartitionsResponse; +import io.milvus.grpc.ShowType; +import io.milvus.param.ConnectParam; +import io.milvus.param.R; +import io.milvus.param.collection.DescribeCollectionParam; +import io.milvus.param.collection.ShowCollectionsParam; +import io.milvus.param.index.DescribeIndexParam; +import io.milvus.param.partition.ShowPartitionsParam; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.apache.seatunnel.api.table.type.BasicType.STRING_TYPE; + +@Slf4j +public class MilvusConvertUtils { + private final ReadonlyConfig config; + + public MilvusConvertUtils(ReadonlyConfig config) { + this.config = config; + } + + public Map getSourceTables() { + MilvusServiceClient client = + new MilvusServiceClient( + ConnectParam.newBuilder() + .withUri(config.get(MilvusSourceConfig.URL)) + .withToken(config.get(MilvusSourceConfig.TOKEN)) + .build()); + + String database = config.get(MilvusSourceConfig.DATABASE); + List collectionList = new ArrayList<>(); + if (StringUtils.isNotEmpty(config.get(MilvusSourceConfig.COLLECTION))) { + collectionList.add(config.get(MilvusSourceConfig.COLLECTION)); + } else { + R response = + client.showCollections( + ShowCollectionsParam.newBuilder() + .withDatabaseName(database) + .withShowType(ShowType.All) + .build()); + if (response.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.SHOW_COLLECTIONS_ERROR); + } + + ProtocolStringList collections = response.getData().getCollectionNamesList(); + if (CollectionUtils.isEmpty(collections)) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.DATABASE_NO_COLLECTIONS, database); + } + collectionList.addAll(collections); + } + + Map map = new HashMap<>(); + for (String collection : collectionList) { + CatalogTable catalogTable = getCatalogTable(client, database, collection); + TablePath tablePath = TablePath.of(database, null, collection); + map.put(tablePath, catalogTable); + } + client.close(); + return map; + } + + public CatalogTable getCatalogTable( + MilvusServiceClient client, String database, String collection) { + R response = + client.describeCollection( + DescribeCollectionParam.newBuilder() + .withDatabaseName(database) + .withCollectionName(collection) + .build()); + + if (response.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.DESC_COLLECTION_ERROR, response.getMessage()); + } + log.info( + "describe collection database: {}, collection: {}, response: {}", + database, + collection, + response); + // collection column + DescribeCollectionResponse collectionResponse = response.getData(); + CollectionSchema schema = collectionResponse.getSchema(); + List columns = new ArrayList<>(); + boolean existPartitionKeyField = false; + String partitionKeyField = null; + for (FieldSchema fieldSchema : schema.getFieldsList()) { + PhysicalColumn physicalColumn = MilvusSourceConverter.convertColumn(fieldSchema); + columns.add(physicalColumn); + if (fieldSchema.getIsPartitionKey()) { + existPartitionKeyField = true; + partitionKeyField = fieldSchema.getName(); + } + } + if (collectionResponse.getSchema().getEnableDynamicField()) { + Map options = new HashMap<>(); + + options.put(CommonOptions.METADATA.getName(), true); + PhysicalColumn dynamicColumn = + PhysicalColumn.builder() + .name(CommonOptions.METADATA.getName()) + .dataType(STRING_TYPE) + .options(options) + .build(); + columns.add(dynamicColumn); + } + + // primary key + PrimaryKey primaryKey = buildPrimaryKey(schema.getFieldsList()); + + // index + R describeIndexResponseR = + client.describeIndex( + DescribeIndexParam.newBuilder() + .withDatabaseName(database) + .withCollectionName(collection) + .build()); + if (describeIndexResponseR.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException(MilvusConnectionErrorCode.DESC_INDEX_ERROR); + } + DescribeIndexResponse indexResponse = describeIndexResponseR.getData(); + List vectorIndexes = buildVectorIndexes(indexResponse); + + // build tableSchema + TableSchema tableSchema = + TableSchema.builder() + .columns(columns) + .primaryKey(primaryKey) + .constraintKey( + ConstraintKey.of( + ConstraintKey.ConstraintType.VECTOR_INDEX_KEY, + "vector_index", + vectorIndexes)) + .build(); + + // build tableId + String CATALOG_NAME = "Milvus"; + TableIdentifier tableId = TableIdentifier.of(CATALOG_NAME, database, null, collection); + // build options info + Map options = new HashMap<>(); + options.put( + MilvusOptions.ENABLE_DYNAMIC_FIELD, String.valueOf(schema.getEnableDynamicField())); + options.put(MilvusOptions.SHARDS_NUM, String.valueOf(collectionResponse.getShardsNum())); + if (existPartitionKeyField) { + options.put(MilvusOptions.PARTITION_KEY_FIELD, partitionKeyField); + } else { + fillPartitionNames(options, client, database, collection); + } + + return CatalogTable.of( + tableId, tableSchema, options, new ArrayList<>(), schema.getDescription()); + } + + private static void fillPartitionNames( + Map options, + MilvusServiceClient client, + String database, + String collection) { + // not exist partition key, will read partition + R partitionsResponseR = + client.showPartitions( + ShowPartitionsParam.newBuilder() + .withDatabaseName(database) + .withCollectionName(collection) + .build()); + if (partitionsResponseR.getStatus() != R.Status.Success.getCode()) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.SHOW_PARTITION_ERROR, + partitionsResponseR.getMessage()); + } + + ProtocolStringList partitionNamesList = + partitionsResponseR.getData().getPartitionNamesList(); + List list = new ArrayList<>(); + for (String partition : partitionNamesList) { + if (partition.equals("_default")) { + continue; + } + list.add(partition); + } + if (CollectionUtils.isEmpty(partitionNamesList)) { + return; + } + + options.put(MilvusOptions.PARTITION_NAMES, String.join(",", list)); + } + + private static List buildVectorIndexes( + DescribeIndexResponse indexResponse) { + if (CollectionUtils.isEmpty(indexResponse.getIndexDescriptionsList())) { + return null; + } + + List list = new ArrayList<>(); + for (IndexDescription per : indexResponse.getIndexDescriptionsList()) { + Map paramsMap = + per.getParamsList().stream() + .collect( + Collectors.toMap(KeyValuePair::getKey, KeyValuePair::getValue)); + + VectorIndex index = + new VectorIndex( + per.getIndexName(), + per.getFieldName(), + paramsMap.get("index_type"), + paramsMap.get("metric_type")); + + list.add(index); + } + + return list; + } + + public static PrimaryKey buildPrimaryKey(List fields) { + for (FieldSchema field : fields) { + if (field.getIsPrimaryKey()) { + return PrimaryKey.of( + field.getName(), Lists.newArrayList(field.getName()), field.getAutoID()); + } + } + + return null; + } +} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java new file mode 100644 index 00000000000..0ca373468c4 --- /dev/null +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/sink/MilvusSinkConverter.java @@ -0,0 +1,294 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.milvus.utils.sink; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.PrimaryKey; +import org.apache.seatunnel.api.table.catalog.exception.CatalogException; +import org.apache.seatunnel.api.table.type.ArrayType; +import org.apache.seatunnel.api.table.type.CommonOptions; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.api.table.type.SqlType; +import org.apache.seatunnel.common.utils.BufferUtils; +import org.apache.seatunnel.common.utils.JsonUtils; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectionErrorCode; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; + +import org.apache.commons.lang3.StringUtils; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.milvus.common.utils.JacksonUtils; +import io.milvus.grpc.DataType; +import io.milvus.param.collection.FieldType; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.apache.seatunnel.api.table.catalog.PrimaryKey.isPrimaryKeyField; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.ENABLE_AUTO_ID; +import static org.apache.seatunnel.connectors.seatunnel.milvus.config.MilvusSinkConfig.ENABLE_DYNAMIC_FIELD; + +public class MilvusSinkConverter { + private static final Gson gson = new Gson(); + + public Object convertBySeaTunnelType( + SeaTunnelDataType fieldType, Boolean isJson, Object value) { + SqlType sqlType = fieldType.getSqlType(); + switch (sqlType) { + case INT: + return Integer.parseInt(value.toString()); + case TINYINT: + return Byte.parseByte(value.toString()); + case BIGINT: + return Long.parseLong(value.toString()); + case SMALLINT: + return Short.parseShort(value.toString()); + case STRING: + case DATE: + if (isJson) { + return gson.fromJson(value.toString(), JsonObject.class); + } + return value.toString(); + case FLOAT_VECTOR: + ByteBuffer floatVectorBuffer = (ByteBuffer) value; + Float[] floats = BufferUtils.toFloatArray(floatVectorBuffer); + return Arrays.stream(floats).collect(Collectors.toList()); + case BINARY_VECTOR: + case BFLOAT16_VECTOR: + case FLOAT16_VECTOR: + ByteBuffer binaryVector = (ByteBuffer) value; + return gson.toJsonTree(binaryVector.array()); + case SPARSE_FLOAT_VECTOR: + return JsonParser.parseString(JacksonUtils.toJsonString(value)).getAsJsonObject(); + case FLOAT: + return Float.parseFloat(value.toString()); + case BOOLEAN: + return Boolean.parseBoolean(value.toString()); + case DOUBLE: + return Double.parseDouble(value.toString()); + case ARRAY: + ArrayType arrayType = (ArrayType) fieldType; + switch (arrayType.getElementType().getSqlType()) { + case STRING: + String[] stringArray = (String[]) value; + return Arrays.asList(stringArray); + case SMALLINT: + Short[] shortArray = (Short[]) value; + return Arrays.asList(shortArray); + case TINYINT: + Byte[] byteArray = (Byte[]) value; + return Arrays.asList(byteArray); + case INT: + Integer[] intArray = (Integer[]) value; + return Arrays.asList(intArray); + case BIGINT: + Long[] longArray = (Long[]) value; + return Arrays.asList(longArray); + case FLOAT: + Float[] floatArray = (Float[]) value; + return Arrays.asList(floatArray); + case DOUBLE: + Double[] doubleArray = (Double[]) value; + return Arrays.asList(doubleArray); + } + case ROW: + SeaTunnelRow row = (SeaTunnelRow) value; + return JsonUtils.toJsonString(row.getFields()); + case MAP: + return JacksonUtils.toJsonString(value); + default: + throw new MilvusConnectorException( + MilvusConnectionErrorCode.NOT_SUPPORT_TYPE, sqlType.name()); + } + } + + public static FieldType convertToFieldType( + Column column, PrimaryKey primaryKey, String partitionKeyField, Boolean autoId) { + SeaTunnelDataType seaTunnelDataType = column.getDataType(); + DataType milvusDataType = convertSqlTypeToDataType(seaTunnelDataType.getSqlType()); + FieldType.Builder build = + FieldType.newBuilder().withName(column.getName()).withDataType(milvusDataType); + if (StringUtils.isNotEmpty(column.getComment())) { + build.withDescription(column.getComment()); + } + switch (seaTunnelDataType.getSqlType()) { + case ROW: + build.withMaxLength(65535); + break; + case DATE: + build.withMaxLength(20); + break; + case STRING: + if (column.getOptions() != null + && column.getOptions().get(CommonOptions.JSON.getName()) != null + && (Boolean) column.getOptions().get(CommonOptions.JSON.getName())) { + // check if is json + build.withDataType(DataType.JSON); + } else if (column.getColumnLength() == null || column.getColumnLength() == 0) { + build.withMaxLength(65535); + } else { + build.withMaxLength((int) (column.getColumnLength() / 4)); + } + break; + case ARRAY: + ArrayType arrayType = (ArrayType) column.getDataType(); + SeaTunnelDataType elementType = arrayType.getElementType(); + build.withElementType(convertSqlTypeToDataType(elementType.getSqlType())); + build.withMaxCapacity(4095); + switch (elementType.getSqlType()) { + case STRING: + if (column.getColumnLength() == null || column.getColumnLength() == 0) { + build.withMaxLength(65535); + } else { + build.withMaxLength((int) (column.getColumnLength() / 4)); + } + break; + } + break; + case BINARY_VECTOR: + case FLOAT_VECTOR: + case FLOAT16_VECTOR: + case BFLOAT16_VECTOR: + build.withDimension(column.getScale()); + break; + } + + // check is primaryKey + if (null != primaryKey && primaryKey.getColumnNames().contains(column.getName())) { + build.withPrimaryKey(true); + List integerTypes = new ArrayList<>(); + integerTypes.add(SqlType.INT); + integerTypes.add(SqlType.SMALLINT); + integerTypes.add(SqlType.TINYINT); + integerTypes.add(SqlType.BIGINT); + if (integerTypes.contains(seaTunnelDataType.getSqlType())) { + build.withDataType(DataType.Int64); + } else { + build.withDataType(DataType.VarChar); + build.withMaxLength(65535); + } + if (null != primaryKey.getEnableAutoId()) { + build.withAutoID(primaryKey.getEnableAutoId()); + } else { + build.withAutoID(autoId); + } + } + + // check is partitionKey + if (column.getName().equals(partitionKeyField)) { + build.withPartitionKey(true); + } + + return build.build(); + } + + public static DataType convertSqlTypeToDataType(SqlType sqlType) { + switch (sqlType) { + case BOOLEAN: + return DataType.Bool; + case TINYINT: + return DataType.Int8; + case SMALLINT: + return DataType.Int16; + case INT: + return DataType.Int32; + case BIGINT: + return DataType.Int64; + case FLOAT: + return DataType.Float; + case DOUBLE: + return DataType.Double; + case STRING: + return DataType.VarChar; + case ARRAY: + return DataType.Array; + case MAP: + return DataType.JSON; + case FLOAT_VECTOR: + return DataType.FloatVector; + case BINARY_VECTOR: + return DataType.BinaryVector; + case FLOAT16_VECTOR: + return DataType.Float16Vector; + case BFLOAT16_VECTOR: + return DataType.BFloat16Vector; + case SPARSE_FLOAT_VECTOR: + return DataType.SparseFloatVector; + case DATE: + return DataType.VarChar; + case ROW: + return DataType.VarChar; + } + throw new CatalogException( + String.format("Not support convert to milvus type, sqlType is %s", sqlType)); + } + + public JsonObject buildMilvusData( + CatalogTable catalogTable, + ReadonlyConfig config, + List jsonFields, + String dynamicField, + SeaTunnelRow element) { + SeaTunnelRowType seaTunnelRowType = catalogTable.getSeaTunnelRowType(); + PrimaryKey primaryKey = catalogTable.getTableSchema().getPrimaryKey(); + Boolean autoId = config.get(ENABLE_AUTO_ID); + + JsonObject data = new JsonObject(); + Gson gson = new Gson(); + for (int i = 0; i < seaTunnelRowType.getFieldNames().length; i++) { + String fieldName = seaTunnelRowType.getFieldNames()[i]; + Boolean isJson = jsonFields.contains(fieldName); + if (autoId && isPrimaryKeyField(primaryKey, fieldName)) { + continue; // if create table open AutoId, then don't need insert data with + // primaryKey field. + } + + SeaTunnelDataType fieldType = seaTunnelRowType.getFieldType(i); + Object value = element.getField(i); + if (null == value) { + throw new MilvusConnectorException( + MilvusConnectionErrorCode.FIELD_IS_NULL, fieldName); + } + // if the field is dynamic field, then parse the dynamic field + if (dynamicField != null + && dynamicField.equals(fieldName) + && config.get(ENABLE_DYNAMIC_FIELD)) { + JsonObject dynamicData = gson.fromJson(value.toString(), JsonObject.class); + dynamicData + .entrySet() + .forEach( + entry -> { + data.add(entry.getKey(), entry.getValue()); + }); + continue; + } + Object object = convertBySeaTunnelType(fieldType, isJson, value); + data.add(fieldName, gson.toJsonTree(object)); + } + return data; + } +} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java new file mode 100644 index 00000000000..ff456a955af --- /dev/null +++ b/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/utils/source/MilvusSourceConverter.java @@ -0,0 +1,364 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.milvus.utils.source; + +import org.apache.seatunnel.api.table.catalog.Column; +import org.apache.seatunnel.api.table.catalog.PhysicalColumn; +import org.apache.seatunnel.api.table.catalog.TablePath; +import org.apache.seatunnel.api.table.catalog.TableSchema; +import org.apache.seatunnel.api.table.type.ArrayType; +import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.CommonOptions; +import org.apache.seatunnel.api.table.type.RowKind; +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.api.table.type.SqlType; +import org.apache.seatunnel.api.table.type.VectorType; +import org.apache.seatunnel.common.exception.CommonErrorCode; +import org.apache.seatunnel.common.utils.BufferUtils; +import org.apache.seatunnel.connectors.seatunnel.milvus.exception.MilvusConnectorException; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.milvus.grpc.DataType; +import io.milvus.grpc.FieldSchema; +import io.milvus.grpc.KeyValuePair; +import io.milvus.response.QueryResultsWrapper; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.apache.seatunnel.api.table.type.BasicType.STRING_TYPE; + +public class MilvusSourceConverter { + private final List existField; + private Gson gson = new Gson(); + + public MilvusSourceConverter(TableSchema tableSchema) { + this.existField = + tableSchema.getColumns().stream() + .filter( + column -> + column.getOptions() == null + || !column.getOptions() + .containsValue(CommonOptions.METADATA)) + .map(Column::getName) + .collect(Collectors.toList()); + } + + public SeaTunnelRow convertToSeaTunnelRow( + QueryResultsWrapper.RowRecord record, TableSchema tableSchema, TablePath tablePath) { + // get field names and types + SeaTunnelRowType typeInfo = tableSchema.toPhysicalRowDataType(); + String[] fieldNames = typeInfo.getFieldNames(); + + Object[] seatunnelField = new Object[typeInfo.getTotalFields()]; + // get field values from source milvus + Map fieldValuesMap = record.getFieldValues(); + // filter dynamic field + JsonObject dynamicField = convertDynamicField(fieldValuesMap); + + for (int fieldIndex = 0; fieldIndex < typeInfo.getTotalFields(); fieldIndex++) { + if (fieldNames[fieldIndex].equals(CommonOptions.METADATA.getName())) { + seatunnelField[fieldIndex] = dynamicField.toString(); + continue; + } + SeaTunnelDataType seaTunnelDataType = typeInfo.getFieldType(fieldIndex); + Object filedValues = fieldValuesMap.get(fieldNames[fieldIndex]); + switch (seaTunnelDataType.getSqlType()) { + case STRING: + seatunnelField[fieldIndex] = filedValues.toString(); + break; + case BOOLEAN: + if (filedValues instanceof Boolean) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Boolean.valueOf(filedValues.toString()); + } + break; + case TINYINT: + if (filedValues instanceof Byte) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Byte.parseByte(filedValues.toString()); + } + break; + case SMALLINT: + if (filedValues instanceof Short) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Short.parseShort(filedValues.toString()); + } + case INT: + if (filedValues instanceof Integer) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Integer.valueOf(filedValues.toString()); + } + break; + case BIGINT: + if (filedValues instanceof Long) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Long.parseLong(filedValues.toString()); + } + break; + case FLOAT: + if (filedValues instanceof Float) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Float.parseFloat(filedValues.toString()); + } + break; + case DOUBLE: + if (filedValues instanceof Double) { + seatunnelField[fieldIndex] = filedValues; + } else { + seatunnelField[fieldIndex] = Double.parseDouble(filedValues.toString()); + } + break; + case ARRAY: + if (filedValues instanceof List) { + List list = (List) filedValues; + ArrayType arrayType = (ArrayType) seaTunnelDataType; + SqlType elementType = arrayType.getElementType().getSqlType(); + switch (elementType) { + case STRING: + String[] arrays = new String[list.size()]; + for (int i = 0; i < list.size(); i++) { + arrays[i] = list.get(i).toString(); + } + seatunnelField[fieldIndex] = arrays; + break; + case BOOLEAN: + Boolean[] booleanArrays = new Boolean[list.size()]; + for (int i = 0; i < list.size(); i++) { + booleanArrays[i] = Boolean.valueOf(list.get(i).toString()); + } + seatunnelField[fieldIndex] = booleanArrays; + break; + case TINYINT: + Byte[] byteArrays = new Byte[list.size()]; + for (int i = 0; i < list.size(); i++) { + byteArrays[i] = Byte.parseByte(list.get(i).toString()); + } + seatunnelField[fieldIndex] = byteArrays; + break; + case SMALLINT: + Short[] shortArrays = new Short[list.size()]; + for (int i = 0; i < list.size(); i++) { + shortArrays[i] = Short.parseShort(list.get(i).toString()); + } + seatunnelField[fieldIndex] = shortArrays; + break; + case INT: + Integer[] intArrays = new Integer[list.size()]; + for (int i = 0; i < list.size(); i++) { + intArrays[i] = Integer.valueOf(list.get(i).toString()); + } + seatunnelField[fieldIndex] = intArrays; + break; + case BIGINT: + Long[] longArrays = new Long[list.size()]; + for (int i = 0; i < list.size(); i++) { + longArrays[i] = Long.parseLong(list.get(i).toString()); + } + seatunnelField[fieldIndex] = longArrays; + break; + case FLOAT: + Float[] floatArrays = new Float[list.size()]; + for (int i = 0; i < list.size(); i++) { + floatArrays[i] = Float.parseFloat(list.get(i).toString()); + } + seatunnelField[fieldIndex] = floatArrays; + break; + case DOUBLE: + Double[] doubleArrays = new Double[list.size()]; + for (int i = 0; i < list.size(); i++) { + doubleArrays[i] = Double.parseDouble(list.get(i).toString()); + } + seatunnelField[fieldIndex] = doubleArrays; + break; + default: + throw new MilvusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, + "Unexpected array value: " + filedValues); + } + } else { + throw new MilvusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, + "Unexpected array value: " + filedValues); + } + break; + case FLOAT_VECTOR: + if (filedValues instanceof List) { + List list = (List) filedValues; + Float[] arrays = new Float[list.size()]; + for (int i = 0; i < list.size(); i++) { + arrays[i] = Float.parseFloat(list.get(i).toString()); + } + seatunnelField[fieldIndex] = BufferUtils.toByteBuffer(arrays); + break; + } else { + throw new MilvusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, + "Unexpected vector value: " + filedValues); + } + case BINARY_VECTOR: + case FLOAT16_VECTOR: + case BFLOAT16_VECTOR: + if (filedValues instanceof ByteBuffer) { + seatunnelField[fieldIndex] = filedValues; + break; + } else { + throw new MilvusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, + "Unexpected vector value: " + filedValues); + } + case SPARSE_FLOAT_VECTOR: + if (filedValues instanceof Map) { + seatunnelField[fieldIndex] = filedValues; + break; + } else { + throw new MilvusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, + "Unexpected vector value: " + filedValues); + } + default: + throw new MilvusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, + "Unexpected value: " + seaTunnelDataType.getSqlType().name()); + } + } + + SeaTunnelRow seaTunnelRow = new SeaTunnelRow(seatunnelField); + seaTunnelRow.setTableId(tablePath.getFullName()); + seaTunnelRow.setRowKind(RowKind.INSERT); + return seaTunnelRow; + } + + public static PhysicalColumn convertColumn(FieldSchema fieldSchema) { + DataType dataType = fieldSchema.getDataType(); + PhysicalColumn.PhysicalColumnBuilder builder = PhysicalColumn.builder(); + builder.name(fieldSchema.getName()); + builder.sourceType(dataType.name()); + builder.comment(fieldSchema.getDescription()); + + switch (dataType) { + case Bool: + builder.dataType(BasicType.BOOLEAN_TYPE); + break; + case Int8: + builder.dataType(BasicType.BYTE_TYPE); + break; + case Int16: + builder.dataType(BasicType.SHORT_TYPE); + break; + case Int32: + builder.dataType(BasicType.INT_TYPE); + break; + case Int64: + builder.dataType(BasicType.LONG_TYPE); + break; + case Float: + builder.dataType(BasicType.FLOAT_TYPE); + break; + case Double: + builder.dataType(BasicType.DOUBLE_TYPE); + break; + case VarChar: + builder.dataType(BasicType.STRING_TYPE); + for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { + if (keyValuePair.getKey().equals("max_length")) { + builder.columnLength(Long.parseLong(keyValuePair.getValue()) * 4); + break; + } + } + break; + case String: + builder.dataType(BasicType.STRING_TYPE); + break; + case JSON: + builder.dataType(STRING_TYPE); + Map options = new HashMap<>(); + options.put(CommonOptions.JSON.getName(), true); + builder.options(options); + break; + case Array: + builder.dataType(ArrayType.STRING_ARRAY_TYPE); + break; + case FloatVector: + builder.dataType(VectorType.VECTOR_FLOAT_TYPE); + for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { + if (keyValuePair.getKey().equals("dim")) { + builder.scale(Integer.valueOf(keyValuePair.getValue())); + break; + } + } + break; + case BinaryVector: + builder.dataType(VectorType.VECTOR_BINARY_TYPE); + for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { + if (keyValuePair.getKey().equals("dim")) { + builder.scale(Integer.valueOf(keyValuePair.getValue())); + break; + } + } + break; + case SparseFloatVector: + builder.dataType(VectorType.VECTOR_SPARSE_FLOAT_TYPE); + break; + case Float16Vector: + builder.dataType(VectorType.VECTOR_FLOAT16_TYPE); + for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { + if (keyValuePair.getKey().equals("dim")) { + builder.scale(Integer.valueOf(keyValuePair.getValue())); + break; + } + } + break; + case BFloat16Vector: + builder.dataType(VectorType.VECTOR_BFLOAT16_TYPE); + for (KeyValuePair keyValuePair : fieldSchema.getTypeParamsList()) { + if (keyValuePair.getKey().equals("dim")) { + builder.scale(Integer.valueOf(keyValuePair.getValue())); + break; + } + } + break; + default: + throw new UnsupportedOperationException("Unsupported data type: " + dataType); + } + + return builder.build(); + } + + private JsonObject convertDynamicField(Map fieldValuesMap) { + JsonObject dynamicField = new JsonObject(); + for (Map.Entry entry : fieldValuesMap.entrySet()) { + if (!existField.contains(entry.getKey())) { + dynamicField.add(entry.getKey(), gson.toJsonTree(entry.getValue())); + } + } + return dynamicField; + } +} diff --git a/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbCollectionProvider.java b/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbCollectionProvider.java index 5ebdab91c70..c68da4ea27b 100644 --- a/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbCollectionProvider.java +++ b/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbCollectionProvider.java @@ -17,7 +17,7 @@ package org.apache.seatunnel.connectors.seatunnel.mongodb.internal; -import com.google.common.base.Preconditions; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; /** A builder class for creating {@link MongodbClientProvider}. */ public class MongodbCollectionProvider { diff --git a/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbSingleCollectionProvider.java b/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbSingleCollectionProvider.java index 4a7b550cb4a..0891a5b78fa 100644 --- a/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbSingleCollectionProvider.java +++ b/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/internal/MongodbSingleCollectionProvider.java @@ -17,9 +17,10 @@ package org.apache.seatunnel.connectors.seatunnel.mongodb.internal; +import org.apache.seatunnel.shade.com.google.common.base.Preconditions; + import org.bson.BsonDocument; -import com.google.common.base.Preconditions; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; diff --git a/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConverters.java b/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConverters.java index 8eda6612c70..4993a0db46e 100644 --- a/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConverters.java +++ b/seatunnel-connectors-v2/connector-mongodb/src/main/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConverters.java @@ -178,6 +178,15 @@ public Object apply(BsonValue bsonValue) { return convertToLocalDateTime(bsonValue).toLocalDate(); } }; + case TIME: + return new SerializableFunction() { + private static final long serialVersionUID = 1L; + + @Override + public Object apply(BsonValue bsonValue) { + return convertToLocalDateTime(bsonValue).toLocalTime(); + } + }; case TIMESTAMP: return new SerializableFunction() { private static final long serialVersionUID = 1L; @@ -217,7 +226,7 @@ public Object apply(BsonValue bsonValue) { private static LocalDateTime convertToLocalDateTime(BsonValue bsonValue) { Instant instant; if (bsonValue.isTimestamp()) { - instant = Instant.ofEpochSecond(bsonValue.asTimestamp().getTime()); + instant = Instant.ofEpochMilli(bsonValue.asTimestamp().getValue()); } else if (bsonValue.isDateTime()) { instant = Instant.ofEpochMilli(bsonValue.asDateTime().getValue()); } else { @@ -366,7 +375,18 @@ private static double convertToDouble(BsonValue bsonValue) { private static int convertToInt(BsonValue bsonValue) { if (bsonValue.isInt32()) { - return bsonValue.asNumber().intValue(); + return bsonValue.asInt32().getValue(); + } else if (bsonValue.isNumber()) { + long longValue = bsonValue.asNumber().longValue(); + if (longValue > Integer.MAX_VALUE || longValue < Integer.MIN_VALUE) { + throw new MongodbConnectorException( + UNSUPPORTED_DATA_TYPE, + "Unable to convert to integer from unexpected value '" + + bsonValue + + "' of type " + + bsonValue.getBsonType()); + } + return (int) longValue; } throw new MongodbConnectorException( UNSUPPORTED_DATA_TYPE, @@ -403,6 +423,17 @@ private static byte[] convertToBinary(BsonValue bsonValue) { private static long convertToLong(BsonValue bsonValue) { if (bsonValue.isInt64() || bsonValue.isInt32()) { return bsonValue.asNumber().longValue(); + } else if (bsonValue.isDouble()) { + double value = bsonValue.asNumber().doubleValue(); + if (value > Long.MAX_VALUE || value < Long.MIN_VALUE) { + throw new MongodbConnectorException( + UNSUPPORTED_DATA_TYPE, + "Unable to convert to long from unexpected value '" + + bsonValue + + "' of type " + + bsonValue.getBsonType()); + } + return bsonValue.asNumber().longValue(); } throw new MongodbConnectorException( UNSUPPORTED_DATA_TYPE, diff --git a/seatunnel-connectors-v2/connector-mongodb/src/test/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConvertersTest.java b/seatunnel-connectors-v2/connector-mongodb/src/test/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConvertersTest.java index b47769c0aca..26c268a4e7b 100644 --- a/seatunnel-connectors-v2/connector-mongodb/src/test/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConvertersTest.java +++ b/seatunnel-connectors-v2/connector-mongodb/src/test/java/org/apache/seatunnel/connectors/seatunnel/mongodb/serde/BsonToRowDataConvertersTest.java @@ -18,13 +18,29 @@ package org.apache.seatunnel.connectors.seatunnel.mongodb.serde; import org.apache.seatunnel.api.table.type.BasicType; +import org.apache.seatunnel.api.table.type.DecimalType; +import org.apache.seatunnel.api.table.type.LocalTimeType; +import org.apache.seatunnel.connectors.seatunnel.mongodb.exception.MongodbConnectorException; +import org.bson.BsonDateTime; +import org.bson.BsonDecimal128; +import org.bson.BsonDocument; import org.bson.BsonDouble; import org.bson.BsonInt32; import org.bson.BsonInt64; +import org.bson.BsonObjectId; +import org.bson.BsonString; +import org.bson.BsonTimestamp; +import org.bson.types.Decimal128; +import org.bson.types.ObjectId; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; + public class BsonToRowDataConvertersTest { private final BsonToRowDataConverters converterFactory = new BsonToRowDataConverters(); @@ -42,7 +58,7 @@ public void testConvertAnyNumberToDouble() { } @Test - public void testConvertBsonIntToBigInt() { + public void testConvertBsonNumberToLong() { // It covered #7567 BsonToRowDataConverters.BsonToRowDataConverter converter = converterFactory.createConverter(BasicType.LONG_TYPE); @@ -51,5 +67,80 @@ public void testConvertBsonIntToBigInt() { Assertions.assertEquals( (long) Integer.MAX_VALUE, converter.convert(new BsonInt64(Integer.MAX_VALUE))); + + Assertions.assertEquals(123456L, converter.convert(new BsonDouble(123456))); + + Assertions.assertThrowsExactly( + MongodbConnectorException.class, + () -> converter.convert(new BsonDouble(12345678901234567891234567890123456789.0d))); + } + + @Test + public void testConvertBsonNumberToInt() { + // It covered #8042 + BsonToRowDataConverters.BsonToRowDataConverter converter = + converterFactory.createConverter(BasicType.INT_TYPE); + Assertions.assertEquals(123456, converter.convert(new BsonInt32(123456))); + Assertions.assertEquals( + Integer.MAX_VALUE, converter.convert(new BsonInt64(Integer.MAX_VALUE))); + Assertions.assertEquals(123456, converter.convert(new BsonDouble(123456))); + Assertions.assertThrowsExactly( + MongodbConnectorException.class, + () -> converter.convert(new BsonDouble(1234567890123456789.0d))); + } + + @Test + public void testConvertBsonDecimal128ToDecimal() { + BsonToRowDataConverters.BsonToRowDataConverter converter = + converterFactory.createConverter(new DecimalType(10, 2)); + Assertions.assertEquals( + new BigDecimal("3.14"), + converter.convert(new BsonDecimal128(Decimal128.parse("3.1415926")))); + } + + @Test + public void testConvertBsonToString() { + BsonToRowDataConverters.BsonToRowDataConverter converter = + converterFactory.createConverter(BasicType.STRING_TYPE); + Assertions.assertEquals("123456", converter.convert(new BsonString("123456"))); + + Assertions.assertEquals( + "507f191e810c19729de860ea", + converter.convert(new BsonObjectId(new ObjectId("507f191e810c19729de860ea")))); + + BsonDocument document = + new BsonDocument() + .append("key", new BsonString("123456")) + .append("value", new BsonInt64(123456789L)); + Assertions.assertEquals( + "{\"key\": \"123456\", \"value\": 123456789}", converter.convert(document)); + } + + @Test + public void testConvertBsonToLocalDateTime() { + LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS); + long epochMilli = now.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(); + + // localDataTime converter + BsonToRowDataConverters.BsonToRowDataConverter localDataTimeConverter = + converterFactory.createConverter(LocalTimeType.LOCAL_DATE_TIME_TYPE); + Assertions.assertEquals(now, localDataTimeConverter.convert(new BsonTimestamp(epochMilli))); + Assertions.assertEquals(now, localDataTimeConverter.convert(new BsonDateTime(epochMilli))); + + // localDate converter + BsonToRowDataConverters.BsonToRowDataConverter localDataConverter = + converterFactory.createConverter(LocalTimeType.LOCAL_DATE_TYPE); + Assertions.assertEquals( + now.toLocalDate(), localDataConverter.convert(new BsonTimestamp(epochMilli))); + Assertions.assertEquals( + now.toLocalDate(), localDataConverter.convert(new BsonDateTime(epochMilli))); + + // localTime converter + BsonToRowDataConverters.BsonToRowDataConverter localTimeConverter = + converterFactory.createConverter(LocalTimeType.LOCAL_TIME_TYPE); + Assertions.assertEquals( + now.toLocalTime(), localTimeConverter.convert(new BsonTimestamp(epochMilli))); + Assertions.assertEquals( + now.toLocalTime(), localTimeConverter.convert(new BsonDateTime(epochMilli))); } } diff --git a/seatunnel-connectors-v2/connector-neo4j/src/main/java/org/apache/seatunnel/connectors/seatunnel/neo4j/sink/Neo4jSink.java b/seatunnel-connectors-v2/connector-neo4j/src/main/java/org/apache/seatunnel/connectors/seatunnel/neo4j/sink/Neo4jSink.java index 26f127509eb..c3af6c7a902 100644 --- a/seatunnel-connectors-v2/connector-neo4j/src/main/java/org/apache/seatunnel/connectors/seatunnel/neo4j/sink/Neo4jSink.java +++ b/seatunnel-connectors-v2/connector-neo4j/src/main/java/org/apache/seatunnel/connectors/seatunnel/neo4j/sink/Neo4jSink.java @@ -22,6 +22,7 @@ import org.apache.seatunnel.api.common.PrepareFailException; import org.apache.seatunnel.api.sink.SeaTunnelSink; import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.table.catalog.CatalogTable; import org.apache.seatunnel.api.table.type.SeaTunnelRow; import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.connectors.seatunnel.neo4j.config.Neo4jSinkQueryInfo; @@ -29,6 +30,7 @@ import com.google.auto.service.AutoService; import java.io.IOException; +import java.util.Optional; import static org.apache.seatunnel.connectors.seatunnel.neo4j.config.Neo4jSinkConfig.PLUGIN_NAME; @@ -58,4 +60,9 @@ public SinkWriter createWriter(SinkWriter.Context cont throws IOException { return new Neo4jSinkWriter(neo4JSinkQueryInfo, rowType); } + + @Override + public Optional getWriteCatalogTable() { + return SeaTunnelSink.super.getWriteCatalogTable(); + } } diff --git a/seatunnel-connectors-v2/connector-paimon/pom.xml b/seatunnel-connectors-v2/connector-paimon/pom.xml index 80934e68a2b..0cd3f535d0b 100644 --- a/seatunnel-connectors-v2/connector-paimon/pom.xml +++ b/seatunnel-connectors-v2/connector-paimon/pom.xml @@ -32,6 +32,7 @@ 0.7.0-incubating 2.3.9 + connector.paimon @@ -47,6 +48,12 @@ ${paimon.version} + + org.apache.paimon + paimon-s3-impl + ${paimon.version} + + org.apache.seatunnel seatunnel-guava @@ -98,4 +105,31 @@ + + + + org.apache.maven.plugins + maven-shade-plugin + + + + shade + + package + + + + org.apache.paimon:paimon-s3-impl + + org/apache/hadoop/** + + + + + + + + + + diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/catalog/PaimonCatalogLoader.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/catalog/PaimonCatalogLoader.java index 774576c408f..ae1f6d675a4 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/catalog/PaimonCatalogLoader.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/catalog/PaimonCatalogLoader.java @@ -44,6 +44,7 @@ public class PaimonCatalogLoader implements Serializable { private static final String HDFS_DEF_FS_NAME = "fs.defaultFS"; private static final String HDFS_PREFIX = "hdfs://"; + private static final String S3A_PREFIX = "s3a://"; /** ********* Hdfs constants ************* */ private static final String HDFS_IMPL = "org.apache.hadoop.hdfs.DistributedFileSystem"; @@ -63,7 +64,7 @@ public PaimonCatalogLoader(PaimonConfig paimonConfig) { } public Catalog loadCatalog() { - // When using the seatunel engine, set the current class loader to prevent loading failures + // When using the seatunnel engine, set the current class loader to prevent loading failures Thread.currentThread().setContextClassLoader(PaimonCatalogLoader.class.getClassLoader()); final Map optionsMap = new HashMap<>(1); optionsMap.put(CatalogOptions.WAREHOUSE.key(), warehouse); @@ -71,12 +72,12 @@ public Catalog loadCatalog() { if (warehouse.startsWith(HDFS_PREFIX)) { checkConfiguration(paimonHadoopConfiguration, HDFS_DEF_FS_NAME); paimonHadoopConfiguration.set(HDFS_IMPL_KEY, HDFS_IMPL); + } else if (warehouse.startsWith(S3A_PREFIX)) { + optionsMap.putAll(paimonHadoopConfiguration.getPropsWithPrefix(StringUtils.EMPTY)); } if (PaimonCatalogEnum.HIVE.getType().equals(catalogType.getType())) { optionsMap.put(CatalogOptions.URI.key(), catalogUri); - paimonHadoopConfiguration - .getPropsWithPrefix(StringUtils.EMPTY) - .forEach((k, v) -> optionsMap.put(k, v)); + optionsMap.putAll(paimonHadoopConfiguration.getPropsWithPrefix(StringUtils.EMPTY)); } final Options options = Options.fromMap(optionsMap); PaimonSecurityContext.shouldEnableKerberos(paimonHadoopConfiguration); diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/config/PaimonSinkConfig.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/config/PaimonSinkConfig.java index 9b358a2e8c4..87766ff96b0 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/config/PaimonSinkConfig.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/config/PaimonSinkConfig.java @@ -23,16 +23,22 @@ import org.apache.seatunnel.api.sink.DataSaveMode; import org.apache.seatunnel.api.sink.SchemaSaveMode; +import org.apache.paimon.CoreOptions; + import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; @Getter @Slf4j public class PaimonSinkConfig extends PaimonConfig { + + public static final String CHANGELOG_TMP_PATH = "changelog-tmp-path"; + public static final Option SCHEMA_SAVE_MODE = Options.key("schema_save_mode") .enumType(SchemaSaveMode.class) @@ -44,7 +50,6 @@ public class PaimonSinkConfig extends PaimonConfig { .enumType(DataSaveMode.class) .defaultValue(DataSaveMode.APPEND_DATA) .withDescription("data_save_mode"); - public static final Option PRIMARY_KEYS = Options.key("paimon.table.primary-keys") .stringType() @@ -66,11 +71,13 @@ public class PaimonSinkConfig extends PaimonConfig { .withDescription( "Properties passed through to paimon table initialization, such as 'file.format', 'bucket'(org.apache.paimon.CoreOptions)"); - private SchemaSaveMode schemaSaveMode; - private DataSaveMode dataSaveMode; - private List primaryKeys; - private List partitionKeys; - private Map writeProps; + private final SchemaSaveMode schemaSaveMode; + private final DataSaveMode dataSaveMode; + private final CoreOptions.ChangelogProducer changelogProducer; + private final String changelogTmpPath; + private final List primaryKeys; + private final List partitionKeys; + private final Map writeProps; public PaimonSinkConfig(ReadonlyConfig readonlyConfig) { super(readonlyConfig); @@ -79,6 +86,20 @@ public PaimonSinkConfig(ReadonlyConfig readonlyConfig) { this.primaryKeys = stringToList(readonlyConfig.get(PRIMARY_KEYS), ","); this.partitionKeys = stringToList(readonlyConfig.get(PARTITION_KEYS), ","); this.writeProps = readonlyConfig.get(WRITE_PROPS); + this.changelogProducer = + Stream.of(CoreOptions.ChangelogProducer.values()) + .filter( + cp -> + cp.toString() + .equalsIgnoreCase( + writeProps.getOrDefault( + CoreOptions.CHANGELOG_PRODUCER + .key(), + ""))) + .findFirst() + .orElse(null); + this.changelogTmpPath = + writeProps.getOrDefault(CHANGELOG_TMP_PATH, System.getProperty("java.io.tmpdir")); checkConfig(); } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/filesystem/S3Loader.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/filesystem/S3Loader.java new file mode 100644 index 00000000000..915070c8eac --- /dev/null +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/filesystem/S3Loader.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.paimon.filesystem; + +import org.apache.paimon.fs.FileIO; +import org.apache.paimon.fs.FileIOLoader; +import org.apache.paimon.fs.Path; +import org.apache.paimon.s3.S3FileIO; + +import java.util.ArrayList; +import java.util.List; + +public class S3Loader implements FileIOLoader { + @Override + public String getScheme() { + return "s3a"; + } + + @Override + public List requiredOptions() { + List options = new ArrayList<>(); + options.add(new String[] {"fs.s3a.access-key", "fs.s3a.access.key"}); + options.add(new String[] {"fs.s3a.secret-key", "fs.s3a.secret.key"}); + options.add(new String[] {"fs.s3a.endpoint", "fs.s3a.endpoint"}); + return options; + } + + @Override + public FileIO load(Path path) { + return new S3FileIO(); + } +} diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSink.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSink.java index fbf04a50380..86828c9a587 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSink.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSink.java @@ -94,7 +94,12 @@ public String getPluginName() { @Override public PaimonSinkWriter createWriter(SinkWriter.Context context) throws IOException { return new PaimonSinkWriter( - context, table, seaTunnelRowType, jobContext, paimonHadoopConfiguration); + context, + table, + seaTunnelRowType, + jobContext, + paimonSinkConfig, + paimonHadoopConfiguration); } @Override @@ -108,7 +113,13 @@ public PaimonSinkWriter createWriter(SinkWriter.Context context) throws IOExcept public SinkWriter restoreWriter( SinkWriter.Context context, List states) throws IOException { return new PaimonSinkWriter( - context, table, seaTunnelRowType, states, jobContext, paimonHadoopConfiguration); + context, + table, + seaTunnelRowType, + states, + jobContext, + paimonSinkConfig, + paimonHadoopConfiguration); } @Override @@ -158,4 +169,9 @@ public Optional getSaveModeHandler() { public void setLoadTable(Table table) { this.table = table; } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSinkWriter.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSinkWriter.java index 7a3fe6d0336..e57e62c9814 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSinkWriter.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/PaimonSinkWriter.java @@ -24,6 +24,7 @@ import org.apache.seatunnel.api.table.type.SeaTunnelRowType; import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.seatunnel.paimon.config.PaimonHadoopConfiguration; +import org.apache.seatunnel.connectors.seatunnel.paimon.config.PaimonSinkConfig; import org.apache.seatunnel.connectors.seatunnel.paimon.exception.PaimonConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.paimon.exception.PaimonConnectorException; import org.apache.seatunnel.connectors.seatunnel.paimon.security.PaimonSecurityContext; @@ -33,7 +34,9 @@ import org.apache.seatunnel.connectors.seatunnel.paimon.utils.JobContextUtil; import org.apache.seatunnel.connectors.seatunnel.paimon.utils.RowConverter; +import org.apache.paimon.CoreOptions; import org.apache.paimon.data.InternalRow; +import org.apache.paimon.disk.IOManager; import org.apache.paimon.schema.TableSchema; import org.apache.paimon.table.BucketMode; import org.apache.paimon.table.FileStoreTable; @@ -58,6 +61,8 @@ import java.util.UUID; import java.util.stream.Collectors; +import static org.apache.paimon.disk.IOManagerImpl.splitPaths; + @Slf4j public class PaimonSinkWriter implements SinkWriter, @@ -65,14 +70,14 @@ public class PaimonSinkWriter private String commitUser = UUID.randomUUID().toString(); + private final FileStoreTable table; + private final WriteBuilder tableWriteBuilder; private final TableWrite tableWrite; private List committables = new ArrayList<>(); - private final Table table; - private final SeaTunnelRowType seaTunnelRowType; private final SinkWriter.Context context; @@ -90,18 +95,30 @@ public PaimonSinkWriter( Table table, SeaTunnelRowType seaTunnelRowType, JobContext jobContext, + PaimonSinkConfig paimonSinkConfig, PaimonHadoopConfiguration paimonHadoopConfiguration) { - this.table = table; + this.table = (FileStoreTable) table; + CoreOptions.ChangelogProducer changelogProducer = + this.table.coreOptions().changelogProducer(); + if (Objects.nonNull(paimonSinkConfig.getChangelogProducer()) + && changelogProducer != paimonSinkConfig.getChangelogProducer()) { + log.warn( + "configured the props named 'changelog-producer' which is not compatible with the options in table , so it will use the table's 'changelog-producer'"); + } + String changelogTmpPath = paimonSinkConfig.getChangelogTmpPath(); this.tableWriteBuilder = JobContextUtil.isBatchJob(jobContext) ? this.table.newBatchWriteBuilder() : this.table.newStreamWriteBuilder(); - this.tableWrite = tableWriteBuilder.newWrite(); + this.tableWrite = + tableWriteBuilder + .newWrite() + .withIOManager(IOManager.create(splitPaths(changelogTmpPath))); this.seaTunnelRowType = seaTunnelRowType; this.context = context; this.jobContext = jobContext; - this.tableSchema = ((FileStoreTable) table).schema(); - BucketMode bucketMode = ((FileStoreTable) table).bucketMode(); + this.tableSchema = this.table.schema(); + BucketMode bucketMode = this.table.bucketMode(); this.dynamicBucket = BucketMode.DYNAMIC == bucketMode || BucketMode.GLOBAL_DYNAMIC == bucketMode; int bucket = ((FileStoreTable) table).coreOptions().bucket(); @@ -124,12 +141,20 @@ public PaimonSinkWriter( SeaTunnelRowType seaTunnelRowType, List states, JobContext jobContext, + PaimonSinkConfig paimonSinkConfig, PaimonHadoopConfiguration paimonHadoopConfiguration) { - this(context, table, seaTunnelRowType, jobContext, paimonHadoopConfiguration); + this( + context, + table, + seaTunnelRowType, + jobContext, + paimonSinkConfig, + paimonHadoopConfiguration); if (Objects.isNull(states) || states.isEmpty()) { return; } this.commitUser = states.get(0).getCommitUser(); + long checkpointId = states.get(0).getCheckpointId(); try (TableCommit tableCommit = tableWriteBuilder.newCommit()) { List commitables = states.stream() @@ -142,7 +167,7 @@ public PaimonSinkWriter( ((BatchTableCommit) tableCommit).commit(commitables); } else { log.debug("Trying to recommit states streaming mode"); - ((StreamTableCommit) tableCommit).commit(Objects.hash(commitables), commitables); + ((StreamTableCommit) tableCommit).commit(checkpointId, commitables); } } catch (Exception e) { throw new PaimonConnectorException( @@ -174,20 +199,26 @@ public void write(SeaTunnelRow element) throws IOException { @Override public Optional prepareCommit() throws IOException { + return Optional.empty(); + } + + @Override + public Optional prepareCommit(long checkpointId) throws IOException { try { List fileCommittables; if (JobContextUtil.isBatchJob(jobContext)) { fileCommittables = ((BatchTableWrite) tableWrite).prepareCommit(); } else { fileCommittables = - ((StreamTableWrite) tableWrite).prepareCommit(false, committables.size()); + ((StreamTableWrite) tableWrite) + .prepareCommit(waitCompaction(), checkpointId); } committables.addAll(fileCommittables); - return Optional.of(new PaimonCommitInfo(fileCommittables)); + return Optional.of(new PaimonCommitInfo(fileCommittables, checkpointId)); } catch (Exception e) { throw new PaimonConnectorException( PaimonConnectorErrorCode.TABLE_PRE_COMMIT_FAILED, - "Flink table store failed to prepare commit", + "Paimon pre-commit failed.", e); } } @@ -218,4 +249,11 @@ public void close() throws IOException { committables.clear(); } } + + private boolean waitCompaction() { + CoreOptions.ChangelogProducer changelogProducer = + this.table.coreOptions().changelogProducer(); + return changelogProducer == CoreOptions.ChangelogProducer.LOOKUP + || changelogProducer == CoreOptions.ChangelogProducer.FULL_COMPACTION; + } } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitInfo.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitInfo.java index 8a7ad84a2e8..83ed71f6151 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitInfo.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitInfo.java @@ -24,6 +24,7 @@ import java.io.Serializable; import java.util.List; +import java.util.Map; /** Paimon connector aggregate commit information class. */ @Data @@ -32,5 +33,6 @@ public class PaimonAggregatedCommitInfo implements Serializable { private static final long serialVersionUID = 1; - private List> committables; + // key: checkpointId value: Paimon commit message List + private Map> committablesMap; } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitter.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitter.java index 5c3f68f3365..8009135346c 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitter.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonAggregatedCommitter.java @@ -36,10 +36,11 @@ import lombok.extern.slf4j.Slf4j; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.Objects; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; /** Paimon connector aggregated committer class */ @@ -70,28 +71,39 @@ public PaimonAggregatedCommitter( public List commit( List aggregatedCommitInfo) throws IOException { try (TableCommit tableCommit = tableWriteBuilder.newCommit()) { - List fileCommittables = - aggregatedCommitInfo.stream() - .map(PaimonAggregatedCommitInfo::getCommittables) - .flatMap(List::stream) - .flatMap(List::stream) - .collect(Collectors.toList()); PaimonSecurityContext.runSecured( () -> { if (JobContextUtil.isBatchJob(jobContext)) { log.debug("Trying to commit states batch mode"); + List fileCommittables = + aggregatedCommitInfo.stream() + .flatMap( + info -> + info.getCommittablesMap().values() + .stream()) + .flatMap(List::stream) + .collect(Collectors.toList()); ((BatchTableCommit) tableCommit).commit(fileCommittables); } else { log.debug("Trying to commit states streaming mode"); - ((StreamTableCommit) tableCommit) - .commit(Objects.hash(fileCommittables), fileCommittables); + aggregatedCommitInfo.stream() + .flatMap( + paimonAggregatedCommitInfo -> + paimonAggregatedCommitInfo.getCommittablesMap() + .entrySet().stream()) + .forEach( + entry -> + ((StreamTableCommit) tableCommit) + .commit( + entry.getKey(), + entry.getValue())); } return null; }); } catch (Exception e) { throw new PaimonConnectorException( PaimonConnectorErrorCode.TABLE_WRITE_COMMIT_FAILED, - "Flink table store commit operation failed", + "Paimon table storage write-commit Failed.", e); } return Collections.emptyList(); @@ -99,8 +111,14 @@ public List commit( @Override public PaimonAggregatedCommitInfo combine(List commitInfos) { - List> committables = new ArrayList<>(); - commitInfos.forEach(commitInfo -> committables.add(commitInfo.getCommittables())); + Map> committables = new HashMap<>(); + commitInfos.forEach( + commitInfo -> + committables + .computeIfAbsent( + commitInfo.getCheckpointId(), + id -> new CopyOnWriteArrayList<>()) + .addAll(commitInfo.getCommittables())); return new PaimonAggregatedCommitInfo(committables); } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonCommitInfo.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonCommitInfo.java index 9927973821c..1d9844103fc 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonCommitInfo.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/sink/commit/PaimonCommitInfo.java @@ -32,4 +32,6 @@ public class PaimonCommitInfo implements Serializable { private static final long serialVersionUID = 1L; List committables; + + Long checkpointId; } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/converter/SqlToPaimonPredicateConverter.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/converter/SqlToPaimonPredicateConverter.java index 212bfd6e8b8..0bf47b13105 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/converter/SqlToPaimonPredicateConverter.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/converter/SqlToPaimonPredicateConverter.java @@ -54,8 +54,6 @@ import net.sf.jsqlparser.statement.select.AllColumns; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.statement.select.Select; -import net.sf.jsqlparser.statement.select.SelectBody; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; import net.sf.jsqlparser.statement.select.SelectItem; import java.math.BigDecimal; @@ -83,7 +81,7 @@ public static PlainSelect convertToPlainSelect(String query) { throw new IllegalArgumentException("Only SELECT statements are supported."); } Select select = (Select) statement; - SelectBody selectBody = select.getSelectBody(); + Select selectBody = select.getSelectBody(); if (!(selectBody instanceof PlainSelect)) { throw new IllegalArgumentException("Only simple SELECT statements are supported."); } @@ -101,18 +99,15 @@ public static PlainSelect convertToPlainSelect(String query) { public static int[] convertSqlSelectToPaimonProjectionIndex( String[] fieldNames, PlainSelect plainSelect) { int[] projectionIndex = null; - List selectItems = plainSelect.getSelectItems(); + List> selectItems = plainSelect.getSelectItems(); List columnNames = new ArrayList<>(); for (SelectItem selectItem : selectItems) { - if (selectItem instanceof AllColumns) { + if (selectItem.getExpression() instanceof AllColumns) { return null; - } else if (selectItem instanceof SelectExpressionItem) { - SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem; - String columnName = selectExpressionItem.getExpression().toString(); - columnNames.add(columnName); } else { - throw new IllegalArgumentException("Error encountered parsing query fields."); + String columnName = ((Column) selectItem.getExpression()).getColumnName(); + columnNames.add(columnName); } } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/enumerator/AbstractSplitEnumerator.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/enumerator/AbstractSplitEnumerator.java index 7789b0ca883..887769488b6 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/enumerator/AbstractSplitEnumerator.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/source/enumerator/AbstractSplitEnumerator.java @@ -17,6 +17,8 @@ package org.apache.seatunnel.connectors.seatunnel.paimon.source.enumerator; +import org.apache.seatunnel.shade.com.google.common.util.concurrent.ThreadFactoryBuilder; + import org.apache.seatunnel.api.source.SourceSplitEnumerator; import org.apache.seatunnel.common.utils.SeaTunnelException; import org.apache.seatunnel.connectors.seatunnel.paimon.source.PaimonSourceSplit; @@ -27,7 +29,6 @@ import org.apache.paimon.table.source.StreamTableScan; import org.apache.paimon.table.source.TableScan; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/utils/SchemaUtil.java b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/utils/SchemaUtil.java index fa8ed338208..ca825a269f9 100644 --- a/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/utils/SchemaUtil.java +++ b/seatunnel-connectors-v2/connector-paimon/src/main/java/org/apache/seatunnel/connectors/seatunnel/paimon/utils/SchemaUtil.java @@ -25,6 +25,7 @@ import org.apache.seatunnel.connectors.seatunnel.paimon.exception.PaimonConnectorErrorCode; import org.apache.seatunnel.connectors.seatunnel.paimon.exception.PaimonConnectorException; +import org.apache.paimon.CoreOptions; import org.apache.paimon.schema.Schema; import org.apache.paimon.types.DataField; import org.apache.paimon.types.DataType; @@ -61,6 +62,10 @@ public static Schema toPaimonSchema( paiSchemaBuilder.partitionKeys(partitionKeys); } Map writeProps = paimonSinkConfig.getWriteProps(); + CoreOptions.ChangelogProducer changelogProducer = paimonSinkConfig.getChangelogProducer(); + if (changelogProducer != null) { + writeProps.remove(PaimonSinkConfig.CHANGELOG_TMP_PATH); + } if (!writeProps.isEmpty()) { paiSchemaBuilder.options(writeProps); } diff --git a/seatunnel-connectors-v2/connector-paimon/src/main/resources/META-INF/services/org.apache.paimon.fs.FileIOLoader b/seatunnel-connectors-v2/connector-paimon/src/main/resources/META-INF/services/org.apache.paimon.fs.FileIOLoader new file mode 100644 index 00000000000..0057f404259 --- /dev/null +++ b/seatunnel-connectors-v2/connector-paimon/src/main/resources/META-INF/services/org.apache.paimon.fs.FileIOLoader @@ -0,0 +1,16 @@ +# 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. + +org.apache.seatunnel.connectors.seatunnel.paimon.filesystem.S3Loader diff --git a/seatunnel-connectors-v2/connector-prometheus/pom.xml b/seatunnel-connectors-v2/connector-prometheus/pom.xml new file mode 100644 index 00000000000..f9591dedf1b --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/pom.xml @@ -0,0 +1,118 @@ + + + + 4.0.0 + + org.apache.seatunnel + seatunnel-connectors-v2 + ${revision} + + + connector-prometheus + SeaTunnel : Connectors V2 : Prometheus + + + 0.16.0 + 3.23.2 + 1.1.7.3 + 3.25.4 + + + + org.apache.seatunnel + connector-common + ${project.version} + + + org.apache.seatunnel + connector-http-base + ${project.version} + + + io.prometheus + simpleclient + ${prometheus-client.version} + + + io.prometheus + simpleclient_httpserver + ${prometheus-client.version} + + + + com.google.protobuf + protobuf-java + ${protobuf-java.version} + + + com.google.protobuf + protobuf-java-util + ${protobuf-java.version} + + + + + org.xerial.snappy + snappy-java + ${snappy-java.version} + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + + + + shade + + package + + false + true + false + false + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + com.google.protobuf + ${seatunnel.shade.package}.com.google.protobuf + + + + + + + + + diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/Exception/PrometheusConnectorException.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/Exception/PrometheusConnectorException.java new file mode 100644 index 00000000000..d351f2ba15e --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/Exception/PrometheusConnectorException.java @@ -0,0 +1,38 @@ +/* + * 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 + * + * https://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. + */ + +package org.apache.seatunnel.connectors.seatunnel.prometheus.Exception; + +import org.apache.seatunnel.common.exception.SeaTunnelErrorCode; +import org.apache.seatunnel.common.exception.SeaTunnelRuntimeException; + +public class PrometheusConnectorException extends SeaTunnelRuntimeException { + + public PrometheusConnectorException( + SeaTunnelErrorCode seaTunnelErrorCode, String errorMessage) { + super(seaTunnelErrorCode, errorMessage); + } + + public PrometheusConnectorException( + SeaTunnelErrorCode seaTunnelErrorCode, String errorMessage, Throwable cause) { + super(seaTunnelErrorCode, errorMessage, cause); + } + + public PrometheusConnectorException(SeaTunnelErrorCode seaTunnelErrorCode, Throwable cause) { + super(seaTunnelErrorCode, cause); + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSinkConfig.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSinkConfig.java new file mode 100644 index 00000000000..959cff607ce --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSinkConfig.java @@ -0,0 +1,99 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.config; + +import org.apache.seatunnel.api.configuration.Option; +import org.apache.seatunnel.api.configuration.Options; +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpConfig; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import static org.apache.seatunnel.shade.com.google.common.base.Preconditions.checkArgument; + +@Setter +@Getter +@ToString +public class PrometheusSinkConfig extends HttpConfig { + + private static final int DEFAULT_BATCH_SIZE = 1024; + + private static final Long DEFAULT_FLUSH_INTERVAL = 300000L; + + public static final Option KEY_TIMESTAMP = + Options.key("key_timestamp") + .stringType() + .noDefaultValue() + .withDescription("key timestamp"); + + public static final Option KEY_LABEL = + Options.key("key_label").stringType().noDefaultValue().withDescription("key label"); + + public static final Option KEY_VALUE = + Options.key("key_value").stringType().noDefaultValue().withDescription("key value"); + + public static final Option BATCH_SIZE = + Options.key("batch_size") + .intType() + .defaultValue(DEFAULT_BATCH_SIZE) + .withDescription("the batch size writer to prometheus"); + + public static final Option FLUSH_INTERVAL = + Options.key("flush_interval") + .longType() + .defaultValue(DEFAULT_FLUSH_INTERVAL) + .withDescription("the flush interval writer to prometheus"); + + private String keyTimestamp; + + private String keyValue; + + private String keyLabel; + + private int batchSize = BATCH_SIZE.defaultValue(); + + private long flushInterval = FLUSH_INTERVAL.defaultValue(); + + public static PrometheusSinkConfig loadConfig(ReadonlyConfig pluginConfig) { + PrometheusSinkConfig sinkConfig = new PrometheusSinkConfig(); + if (pluginConfig.getOptional(KEY_VALUE).isPresent()) { + sinkConfig.setKeyValue(pluginConfig.get(KEY_VALUE)); + } + if (pluginConfig.getOptional(KEY_LABEL).isPresent()) { + sinkConfig.setKeyLabel(pluginConfig.get(KEY_LABEL)); + } + if (pluginConfig.getOptional(KEY_TIMESTAMP).isPresent()) { + sinkConfig.setKeyTimestamp(pluginConfig.get(KEY_TIMESTAMP)); + } + if (pluginConfig.getOptional(BATCH_SIZE).isPresent()) { + int batchSize = checkIntArgument(pluginConfig.get(BATCH_SIZE)); + sinkConfig.setBatchSize(batchSize); + } + if (pluginConfig.getOptional(FLUSH_INTERVAL).isPresent()) { + long flushInterval = pluginConfig.get(FLUSH_INTERVAL); + sinkConfig.setFlushInterval(flushInterval); + } + return sinkConfig; + } + + private static int checkIntArgument(int args) { + checkArgument(args > 0); + return args; + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSourceConfig.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSourceConfig.java new file mode 100644 index 00000000000..1c51f6b8f68 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSourceConfig.java @@ -0,0 +1,87 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.prometheus.config; + +import org.apache.seatunnel.api.configuration.Option; +import org.apache.seatunnel.api.configuration.Options; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpConfig; + +/** + * SourceConfig is the configuration for the PrometheusSource. + * + *

please see the following link for more details: + * https://prometheus.io/docs/prometheus/latest/querying/api/ + */ +public class PrometheusSourceConfig extends HttpConfig { + + public static final String INSTANT_QUERY_URL = "/api/v1/query"; + + public static final String RANGE_QUERY = "Range"; + + public static final String INSTANT_QUERY = "Instant"; + + public static final String RANGE_QUERY_URL = "/api/v1/query_range"; + + public static final Option QUERY = + Options.key("query") + .stringType() + .noDefaultValue() + .withDescription("Prometheus expression query string"); + + public static final Option QUERY_TYPE = + Options.key("query_type") + .stringType() + .defaultValue("Instant") + .withDescription("Prometheus expression query string"); + + public static final Option TIMEOUT = + Options.key("timeout") + .longType() + .noDefaultValue() + .withDescription("Evaluation timeout"); + + public static class RangeConfig { + + public static final Option START = + Options.key("start") + .stringType() + .noDefaultValue() + .withDescription("Start timestamp, inclusive."); + + public static final Option END = + Options.key("end") + .stringType() + .noDefaultValue() + .withDescription("End timestamp, inclusive."); + + public static final Option STEP = + Options.key("step") + .stringType() + .noDefaultValue() + .withDescription( + " Query resolution step width in duration format or float number of seconds."); + } + + public static class InstantQueryConfig { + public static final Option TIME = + Options.key("time") + .longType() + .noDefaultValue() + .withDescription("Evaluation timestamp,unix_timestamp"); + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSourceParameter.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSourceParameter.java new file mode 100644 index 00000000000..bec3eb2ea1d --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/config/PrometheusSourceParameter.java @@ -0,0 +1,103 @@ +/* + * 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. + */ + +package org.apache.seatunnel.connectors.seatunnel.prometheus.config; + +import org.apache.seatunnel.shade.com.typesafe.config.Config; + +import org.apache.seatunnel.common.exception.CommonErrorCode; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpParameter; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpRequestMethod; +import org.apache.seatunnel.connectors.seatunnel.prometheus.Exception.PrometheusConnectorException; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.HashMap; + +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.INSTANT_QUERY_URL; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.InstantQueryConfig.TIME; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.QUERY; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.QUERY_TYPE; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.RANGE_QUERY; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.RANGE_QUERY_URL; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.RangeConfig.END; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.RangeConfig.START; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.RangeConfig.STEP; +import static org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSourceConfig.TIMEOUT; + +public class PrometheusSourceParameter extends HttpParameter { + public static final String CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP"; + + public void buildWithConfig(Config pluginConfig) { + super.buildWithConfig(pluginConfig); + + String query = pluginConfig.getString(QUERY.key()); + + String queryType = + pluginConfig.hasPath(QUERY_TYPE.key()) + ? pluginConfig.getString(QUERY_TYPE.key()) + : QUERY_TYPE.defaultValue(); + + this.params = this.getParams() == null ? new HashMap<>() : this.getParams(); + + params.put(PrometheusSourceConfig.QUERY.key(), query); + + this.setMethod(HttpRequestMethod.GET); + + if (pluginConfig.hasPath(TIMEOUT.key())) { + params.put(TIMEOUT.key(), pluginConfig.getString(TIMEOUT.key())); + } + + if (RANGE_QUERY.equals(queryType)) { + this.setUrl(this.getUrl() + RANGE_QUERY_URL); + params.put(START.key(), checkTimeParam(pluginConfig.getString(START.key()))); + params.put(END.key(), checkTimeParam(pluginConfig.getString(END.key()))); + params.put(STEP.key(), pluginConfig.getString(STEP.key())); + + } else { + this.setUrl(this.getUrl() + INSTANT_QUERY_URL); + if (pluginConfig.hasPath(TIME.key())) { + String time = pluginConfig.getString(TIME.key()); + params.put(TIME.key(), time); + } + } + this.setParams(params); + } + + private String checkTimeParam(String time) { + if (CURRENT_TIMESTAMP.equals(time)) { + ZonedDateTime now = ZonedDateTime.now(); + return now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME); + } + if (isValidISO8601(time)) { + return time; + } + throw new PrometheusConnectorException( + CommonErrorCode.UNSUPPORTED_DATA_TYPE, "unsupported time type"); + } + + private boolean isValidISO8601(String dateTimeString) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + ZonedDateTime.parse(dateTimeString, formatter); + return true; + } catch (DateTimeParseException e) { + return false; + } + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/pojo/InstantPoint.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/pojo/InstantPoint.java new file mode 100644 index 00000000000..7e12562b355 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/pojo/InstantPoint.java @@ -0,0 +1,29 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.pojo; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class InstantPoint { + private Map metric; + + private List value; +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/pojo/RangePoint.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/pojo/RangePoint.java new file mode 100644 index 00000000000..a597fd0c98e --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/pojo/RangePoint.java @@ -0,0 +1,30 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.pojo; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class RangePoint { + + private Map metric; + + private List values; +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/serialize/PrometheusSerializer.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/serialize/PrometheusSerializer.java new file mode 100644 index 00000000000..559db6c2517 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/serialize/PrometheusSerializer.java @@ -0,0 +1,149 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.serialize; + +import org.apache.seatunnel.shade.com.google.common.base.Strings; + +import org.apache.seatunnel.api.table.type.SeaTunnelDataType; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; +import org.apache.seatunnel.connectors.seatunnel.prometheus.Exception.PrometheusConnectorException; +import org.apache.seatunnel.connectors.seatunnel.prometheus.sink.Point; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Slf4j +public class PrometheusSerializer implements Serializer { + + private final Function timestampExtractor; + private final Function valueExtractor; + private final Function labelExtractor; + + public PrometheusSerializer( + @NonNull SeaTunnelRowType seaTunnelRowType, + String timestampKey, + String labelKey, + String valueKey) { + this.valueExtractor = createValueExtractor(seaTunnelRowType, valueKey); + this.timestampExtractor = createTimestampExtractor(seaTunnelRowType, timestampKey); + this.labelExtractor = createLabelExtractor(seaTunnelRowType, labelKey); + } + + @Override + public Point serialize(SeaTunnelRow seaTunnelRow) { + Long timestamp = timestampExtractor.apply(seaTunnelRow); + Double value = valueExtractor.apply(seaTunnelRow); + Map label = labelExtractor.apply(seaTunnelRow); + Point point = Point.builder().metric(label).value(value).timestamp(timestamp).build(); + + return point; + } + + private Function createLabelExtractor( + SeaTunnelRowType seaTunnelRowType, String labelKey) { + if (Strings.isNullOrEmpty(labelKey)) { + return row -> new HashMap(); + } + int labelFieldIndex = seaTunnelRowType.indexOf(labelKey); + return row -> { + Object value = row.getField(labelFieldIndex); + if (value == null) { + return new HashMap(); + } + SeaTunnelDataType valueFieldType = seaTunnelRowType.getFieldType(labelFieldIndex); + switch (valueFieldType.getSqlType()) { + case MAP: + return (Map) value; + default: + throw new PrometheusConnectorException( + CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE, + "Unsupported data type: " + valueFieldType); + } + }; + } + + private Function createValueExtractor( + SeaTunnelRowType seaTunnelRowType, String valueKey) { + if (Strings.isNullOrEmpty(valueKey)) { + return row -> Double.NaN; + } + + int valueFieldIndex = seaTunnelRowType.indexOf(valueKey); + return row -> { + Object value = row.getField(valueFieldIndex); + if (value == null) { + return Double.NaN; + } + SeaTunnelDataType valueFieldType = seaTunnelRowType.getFieldType(valueFieldIndex); + switch (valueFieldType.getSqlType()) { + case STRING: + case INT: + case FLOAT: + return Double.parseDouble((String) value); + case DOUBLE: + return (Double) value; + default: + throw new PrometheusConnectorException( + CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE, + "Unsupported data type: " + valueFieldType); + } + }; + } + + private Function createTimestampExtractor( + SeaTunnelRowType seaTunnelRowType, String timestampKey) { + if (Strings.isNullOrEmpty(timestampKey)) { + return row -> System.currentTimeMillis(); + } + + int timestampFieldIndex = seaTunnelRowType.indexOf(timestampKey); + return row -> { + Object timestamp = row.getField(timestampFieldIndex); + if (timestamp == null) { + return System.currentTimeMillis(); + } + SeaTunnelDataType timestampFieldType = + seaTunnelRowType.getFieldType(timestampFieldIndex); + switch (timestampFieldType.getSqlType()) { + case STRING: + return Long.parseLong((String) timestamp); + case TIMESTAMP: + return ((LocalDateTime) timestamp) + .atZone(ZoneId.systemDefault()) + .toInstant() + .toEpochMilli(); + case BIGINT: + return (Long) timestamp; + case DOUBLE: + double timestampDouble = (double) timestamp; + return (long) (timestampDouble * 1000); + default: + throw new PrometheusConnectorException( + CommonErrorCodeDeprecated.UNSUPPORTED_DATA_TYPE, + "Unsupported data type: " + timestampFieldType); + } + }; + } +} diff --git a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/batch/MilvusBatchWriter.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/serialize/Serializer.java similarity index 79% rename from seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/batch/MilvusBatchWriter.java rename to seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/serialize/Serializer.java index 91e04342dc6..ad830f5baa0 100644 --- a/seatunnel-connectors-v2/connector-milvus/src/main/java/org/apache/seatunnel/connectors/seatunnel/milvus/sink/batch/MilvusBatchWriter.java +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/serialize/Serializer.java @@ -15,17 +15,11 @@ * limitations under the License. */ -package org.apache.seatunnel.connectors.seatunnel.milvus.sink.batch; +package org.apache.seatunnel.connectors.seatunnel.prometheus.serialize; import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.connectors.seatunnel.prometheus.sink.Point; -public interface MilvusBatchWriter { - - void addToBatch(SeaTunnelRow element); - - boolean needFlush(); - - boolean flush(); - - void close(); +public interface Serializer { + Point serialize(SeaTunnelRow seaTunnelRow); } diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/Point.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/Point.java new file mode 100644 index 00000000000..fb78bff56fa --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/Point.java @@ -0,0 +1,33 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink; + +import lombok.Builder; +import lombok.Data; + +import java.util.Map; + +@Data +@Builder +public class Point { + + private Map metric; + + private Double value; + + private Long timestamp; +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusSink.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusSink.java new file mode 100644 index 00000000000..35ec257fc93 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusSink.java @@ -0,0 +1,79 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.sink.SinkWriter; +import org.apache.seatunnel.api.sink.SupportMultiTableSink; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.connectors.seatunnel.common.sink.AbstractSimpleSink; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpConfig; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpParameter; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class PrometheusSink extends AbstractSimpleSink + implements SupportMultiTableSink { + + protected final HttpParameter httpParameter = new HttpParameter(); + protected CatalogTable catalogTable; + protected ReadonlyConfig pluginConfig; + + public PrometheusSink(ReadonlyConfig pluginConfig, CatalogTable catalogTable) { + this.pluginConfig = pluginConfig; + httpParameter.setUrl(pluginConfig.get(HttpConfig.URL)); + if (pluginConfig.getOptional(HttpConfig.HEADERS).isPresent()) { + httpParameter.setHeaders(pluginConfig.get(HttpConfig.HEADERS)); + } + if (pluginConfig.getOptional(HttpConfig.PARAMS).isPresent()) { + httpParameter.setHeaders(pluginConfig.get(HttpConfig.PARAMS)); + } + this.catalogTable = catalogTable; + + if (Objects.isNull(httpParameter.getHeaders())) { + Map headers = new HashMap<>(); + headers.put("Content-type", "application/x-protobuf"); + headers.put("Content-Encoding", "snappy"); + headers.put("X-Prometheus-Remote-Write-Version", "0.1.0"); + httpParameter.setHeaders(headers); + } else { + httpParameter.getHeaders().put("Content-type", "application/x-protobuf"); + httpParameter.getHeaders().put("Content-Encoding", "snappy"); + httpParameter.getHeaders().put("X-Prometheus-Remote-Write-Version", "0.1.0"); + } + } + + @Override + public String getPluginName() { + return "Prometheus"; + } + + @Override + public PrometheusWriter createWriter(SinkWriter.Context context) { + return new PrometheusWriter( + catalogTable.getSeaTunnelRowType(), httpParameter, pluginConfig); + } + + @Override + public Optional getWriteCatalogTable() { + return Optional.ofNullable(catalogTable); + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusSinkFactory.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusSinkFactory.java new file mode 100644 index 00000000000..544f17c9a6f --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusSinkFactory.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.configuration.util.OptionRule; +import org.apache.seatunnel.api.sink.SinkCommonOptions; +import org.apache.seatunnel.api.table.catalog.CatalogTable; +import org.apache.seatunnel.api.table.connector.TableSink; +import org.apache.seatunnel.api.table.factory.Factory; +import org.apache.seatunnel.api.table.factory.TableSinkFactoryContext; +import org.apache.seatunnel.connectors.seatunnel.http.sink.HttpSinkFactory; +import org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSinkConfig; + +import com.google.auto.service.AutoService; + +@AutoService(Factory.class) +public class PrometheusSinkFactory extends HttpSinkFactory { + @Override + public String factoryIdentifier() { + return "Prometheus"; + } + + public TableSink createSink(TableSinkFactoryContext context) { + + ReadonlyConfig readonlyConfig = context.getOptions(); + CatalogTable catalogTable = context.getCatalogTable(); + return () -> new PrometheusSink(readonlyConfig, catalogTable); + } + + @Override + public OptionRule optionRule() { + return OptionRule.builder() + .required(PrometheusSinkConfig.URL) + .required(PrometheusSinkConfig.KEY_LABEL) + .required(PrometheusSinkConfig.KEY_VALUE) + .optional(PrometheusSinkConfig.KEY_TIMESTAMP) + .optional(PrometheusSinkConfig.HEADERS) + .optional(PrometheusSinkConfig.RETRY) + .optional(PrometheusSinkConfig.RETRY_BACKOFF_MULTIPLIER_MS) + .optional(PrometheusSinkConfig.RETRY_BACKOFF_MAX_MS) + .optional(PrometheusSinkConfig.BATCH_SIZE) + .optional(PrometheusSinkConfig.FLUSH_INTERVAL) + .optional(SinkCommonOptions.MULTI_TABLE_SINK_REPLICA) + .build(); + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusWriter.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusWriter.java new file mode 100644 index 00000000000..307abb8eed6 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/PrometheusWriter.java @@ -0,0 +1,214 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink; + +import org.apache.seatunnel.api.configuration.ReadonlyConfig; +import org.apache.seatunnel.api.table.type.SeaTunnelRow; +import org.apache.seatunnel.api.table.type.SeaTunnelRowType; +import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated; +import org.apache.seatunnel.connectors.seatunnel.http.client.HttpClientProvider; +import org.apache.seatunnel.connectors.seatunnel.http.client.HttpResponse; +import org.apache.seatunnel.connectors.seatunnel.http.config.HttpParameter; +import org.apache.seatunnel.connectors.seatunnel.http.sink.HttpSinkWriter; +import org.apache.seatunnel.connectors.seatunnel.prometheus.Exception.PrometheusConnectorException; +import org.apache.seatunnel.connectors.seatunnel.prometheus.config.PrometheusSinkConfig; +import org.apache.seatunnel.connectors.seatunnel.prometheus.serialize.PrometheusSerializer; +import org.apache.seatunnel.connectors.seatunnel.prometheus.serialize.Serializer; +import org.apache.seatunnel.connectors.seatunnel.prometheus.sink.proto.Remote; +import org.apache.seatunnel.connectors.seatunnel.prometheus.sink.proto.Types; + +import org.apache.http.HttpStatus; +import org.apache.http.entity.ByteArrayEntity; + +import org.xerial.snappy.Snappy; + +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class PrometheusWriter extends HttpSinkWriter { + private final List batchList; + private volatile Exception flushException; + private final Integer batchSize; + private final long flushInterval; + private PrometheusSinkConfig sinkConfig; + private final Serializer serializer; + protected final HttpClientProvider httpClient; + private ScheduledExecutorService executor; + private ScheduledFuture scheduledFuture; + + public PrometheusWriter( + SeaTunnelRowType seaTunnelRowType, + HttpParameter httpParameter, + ReadonlyConfig pluginConfig) { + + super(seaTunnelRowType, httpParameter); + this.batchList = new ArrayList<>(); + this.sinkConfig = PrometheusSinkConfig.loadConfig(pluginConfig); + this.batchSize = sinkConfig.getBatchSize(); + this.flushInterval = sinkConfig.getFlushInterval(); + this.serializer = + new PrometheusSerializer( + seaTunnelRowType, + sinkConfig.getKeyTimestamp(), + sinkConfig.getKeyLabel(), + sinkConfig.getKeyValue()); + this.httpClient = new HttpClientProvider(httpParameter); + if (flushInterval > 0) { + log.info("start schedule submit message,interval:{}", flushInterval); + this.executor = + Executors.newScheduledThreadPool( + 1, + runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + thread.setName("Prometheus-Metric-Sender"); + return thread; + }); + this.scheduledFuture = + executor.scheduleAtFixedRate( + this::flushSchedule, + flushInterval, + flushInterval, + TimeUnit.MILLISECONDS); + } + } + + @Override + public void write(SeaTunnelRow element) { + Point record = serializer.serialize(element); + this.write(record); + } + + public void write(Point record) { + checkFlushException(); + + synchronized (batchList) { + batchList.add(record); + if (batchSize > 0 && batchList.size() >= batchSize) { + flush(); + } + } + } + + private void flushSchedule() { + synchronized (batchList) { + if (!batchList.isEmpty()) { + flush(); + } + } + } + + private void checkFlushException() { + if (flushException != null) { + throw new PrometheusConnectorException( + CommonErrorCodeDeprecated.FLUSH_DATA_FAILED, + "Writing records to prometheus failed.", + flushException); + } + } + + private void flush() { + checkFlushException(); + if (batchList.isEmpty()) { + return; + } + try { + byte[] body = snappy(batchList); + ByteArrayEntity byteArrayEntity = new ByteArrayEntity(body); + HttpResponse response = + httpClient.doPost( + httpParameter.getUrl(), httpParameter.getHeaders(), byteArrayEntity); + if (HttpStatus.SC_NO_CONTENT == response.getCode()) { + return; + } + log.error( + "http client execute exception, http response status code:[{}], content:[{}]", + response.getCode(), + response.getContent()); + } catch (Exception e) { + log.error(e.getMessage(), e); + } finally { + batchList.clear(); + } + } + + /** + * snappy data + * + * @param points list of series data + * @return byte data + * @throws IOException IOException + */ + private byte[] snappy(List points) throws IOException { + Remote.WriteRequest writeRequest = createRemoteWriteRequest(points); + byte[] serializedData = writeRequest.toByteArray(); + byte[] compressedData = Snappy.compress(serializedData); + return compressedData; + } + + /** + * create Remote Write Request + * + * @param points list of series data + * @return Remote.WriteRequest + */ + private Remote.WriteRequest createRemoteWriteRequest(List points) { + Remote.WriteRequest.Builder writeRequestBuilder = Remote.WriteRequest.newBuilder(); + for (Point point : points) { + List labels = new ArrayList<>(); + Types.TimeSeries.Builder timeSeriesBuilder = Types.TimeSeries.newBuilder(); + for (Map.Entry entry : point.getMetric().entrySet()) { + Types.Label label = + Types.Label.newBuilder() + .setName(entry.getKey()) + .setValue(entry.getValue()) + .build(); + labels.add(label); + } + Types.Sample sample = + Types.Sample.newBuilder() + .setTimestamp(point.getTimestamp()) + .setValue(point.getValue()) + .build(); + timeSeriesBuilder.addAllLabels(labels); + timeSeriesBuilder.addSamples(sample); + writeRequestBuilder.addTimeseries(timeSeriesBuilder); + } + return writeRequestBuilder.build(); + } + + @Override + public void close() throws IOException { + super.close(); + if (scheduledFuture != null) { + scheduledFuture.cancel(false); + if (executor != null) { + executor.shutdownNow(); + } + } + this.flush(); + } +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/GoGoProtos.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/GoGoProtos.java new file mode 100644 index 00000000000..2ebbc9d97f1 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/GoGoProtos.java @@ -0,0 +1,919 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink.proto; + +public final class GoGoProtos { + private GoGoProtos() {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) { + registry.add(GoGoProtos.goprotoEnumPrefix); + registry.add(GoGoProtos.goprotoEnumStringer); + registry.add(GoGoProtos.enumStringer); + registry.add(GoGoProtos.enumCustomname); + registry.add(GoGoProtos.enumdecl); + registry.add(GoGoProtos.enumvalueCustomname); + registry.add(GoGoProtos.goprotoGettersAll); + registry.add(GoGoProtos.goprotoEnumPrefixAll); + registry.add(GoGoProtos.goprotoStringerAll); + registry.add(GoGoProtos.verboseEqualAll); + registry.add(GoGoProtos.faceAll); + registry.add(GoGoProtos.gostringAll); + registry.add(GoGoProtos.populateAll); + registry.add(GoGoProtos.stringerAll); + registry.add(GoGoProtos.onlyoneAll); + registry.add(GoGoProtos.equalAll); + registry.add(GoGoProtos.descriptionAll); + registry.add(GoGoProtos.testgenAll); + registry.add(GoGoProtos.benchgenAll); + registry.add(GoGoProtos.marshalerAll); + registry.add(GoGoProtos.unmarshalerAll); + registry.add(GoGoProtos.stableMarshalerAll); + registry.add(GoGoProtos.sizerAll); + registry.add(GoGoProtos.goprotoEnumStringerAll); + registry.add(GoGoProtos.enumStringerAll); + registry.add(GoGoProtos.unsafeMarshalerAll); + registry.add(GoGoProtos.unsafeUnmarshalerAll); + registry.add(GoGoProtos.goprotoExtensionsMapAll); + registry.add(GoGoProtos.goprotoUnrecognizedAll); + registry.add(GoGoProtos.gogoprotoImport); + registry.add(GoGoProtos.protosizerAll); + registry.add(GoGoProtos.compareAll); + registry.add(GoGoProtos.typedeclAll); + registry.add(GoGoProtos.enumdeclAll); + registry.add(GoGoProtos.goprotoRegistration); + registry.add(GoGoProtos.messagenameAll); + registry.add(GoGoProtos.goprotoSizecacheAll); + registry.add(GoGoProtos.goprotoUnkeyedAll); + registry.add(GoGoProtos.goprotoGetters); + registry.add(GoGoProtos.goprotoStringer); + registry.add(GoGoProtos.verboseEqual); + registry.add(GoGoProtos.face); + registry.add(GoGoProtos.gostring); + registry.add(GoGoProtos.populate); + registry.add(GoGoProtos.stringer); + registry.add(GoGoProtos.onlyone); + registry.add(GoGoProtos.equal); + registry.add(GoGoProtos.description); + registry.add(GoGoProtos.testgen); + registry.add(GoGoProtos.benchgen); + registry.add(GoGoProtos.marshaler); + registry.add(GoGoProtos.unmarshaler); + registry.add(GoGoProtos.stableMarshaler); + registry.add(GoGoProtos.sizer); + registry.add(GoGoProtos.unsafeMarshaler); + registry.add(GoGoProtos.unsafeUnmarshaler); + registry.add(GoGoProtos.goprotoExtensionsMap); + registry.add(GoGoProtos.goprotoUnrecognized); + registry.add(GoGoProtos.protosizer); + registry.add(GoGoProtos.compare); + registry.add(GoGoProtos.typedecl); + registry.add(GoGoProtos.messagename); + registry.add(GoGoProtos.goprotoSizecache); + registry.add(GoGoProtos.goprotoUnkeyed); + registry.add(GoGoProtos.nullable); + registry.add(GoGoProtos.embed); + registry.add(GoGoProtos.customtype); + registry.add(GoGoProtos.customname); + registry.add(GoGoProtos.jsontag); + registry.add(GoGoProtos.moretags); + registry.add(GoGoProtos.casttype); + registry.add(GoGoProtos.castkey); + registry.add(GoGoProtos.castvalue); + registry.add(GoGoProtos.stdtime); + registry.add(GoGoProtos.stdduration); + registry.add(GoGoProtos.wktpointer); + } + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry); + } + + public static final int GOPROTO_ENUM_PREFIX_FIELD_NUMBER = 62001; + /** extend .google.protobuf.EnumOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.EnumOptions, Boolean> + goprotoEnumPrefix = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_ENUM_STRINGER_FIELD_NUMBER = 62021; + /** extend .google.protobuf.EnumOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.EnumOptions, Boolean> + goprotoEnumStringer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ENUM_STRINGER_FIELD_NUMBER = 62022; + /** extend .google.protobuf.EnumOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.EnumOptions, Boolean> + enumStringer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ENUM_CUSTOMNAME_FIELD_NUMBER = 62023; + /** extend .google.protobuf.EnumOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.EnumOptions, String> + enumCustomname = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int ENUMDECL_FIELD_NUMBER = 62024; + /** extend .google.protobuf.EnumOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.EnumOptions, Boolean> + enumdecl = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ENUMVALUE_CUSTOMNAME_FIELD_NUMBER = 66001; + /** extend .google.protobuf.EnumValueOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.EnumValueOptions, String> + enumvalueCustomname = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int GOPROTO_GETTERS_ALL_FIELD_NUMBER = 63001; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoGettersAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_ENUM_PREFIX_ALL_FIELD_NUMBER = 63002; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoEnumPrefixAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_STRINGER_ALL_FIELD_NUMBER = 63003; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoStringerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int VERBOSE_EQUAL_ALL_FIELD_NUMBER = 63004; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + verboseEqualAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int FACE_ALL_FIELD_NUMBER = 63005; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + faceAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOSTRING_ALL_FIELD_NUMBER = 63006; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + gostringAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int POPULATE_ALL_FIELD_NUMBER = 63007; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + populateAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int STRINGER_ALL_FIELD_NUMBER = 63008; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + stringerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ONLYONE_ALL_FIELD_NUMBER = 63009; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + onlyoneAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int EQUAL_ALL_FIELD_NUMBER = 63013; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + equalAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int DESCRIPTION_ALL_FIELD_NUMBER = 63014; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + descriptionAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int TESTGEN_ALL_FIELD_NUMBER = 63015; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + testgenAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int BENCHGEN_ALL_FIELD_NUMBER = 63016; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + benchgenAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int MARSHALER_ALL_FIELD_NUMBER = 63017; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + marshalerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int UNMARSHALER_ALL_FIELD_NUMBER = 63018; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + unmarshalerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int STABLE_MARSHALER_ALL_FIELD_NUMBER = 63019; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + stableMarshalerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int SIZER_ALL_FIELD_NUMBER = 63020; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + sizerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_ENUM_STRINGER_ALL_FIELD_NUMBER = 63021; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoEnumStringerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ENUM_STRINGER_ALL_FIELD_NUMBER = 63022; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + enumStringerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int UNSAFE_MARSHALER_ALL_FIELD_NUMBER = 63023; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + unsafeMarshalerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int UNSAFE_UNMARSHALER_ALL_FIELD_NUMBER = 63024; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + unsafeUnmarshalerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_EXTENSIONS_MAP_ALL_FIELD_NUMBER = 63025; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoExtensionsMapAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_UNRECOGNIZED_ALL_FIELD_NUMBER = 63026; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoUnrecognizedAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOGOPROTO_IMPORT_FIELD_NUMBER = 63027; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + gogoprotoImport = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int PROTOSIZER_ALL_FIELD_NUMBER = 63028; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + protosizerAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int COMPARE_ALL_FIELD_NUMBER = 63029; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + compareAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int TYPEDECL_ALL_FIELD_NUMBER = 63030; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + typedeclAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ENUMDECL_ALL_FIELD_NUMBER = 63031; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + enumdeclAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_REGISTRATION_FIELD_NUMBER = 63032; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoRegistration = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int MESSAGENAME_ALL_FIELD_NUMBER = 63033; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + messagenameAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_SIZECACHE_ALL_FIELD_NUMBER = 63034; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoSizecacheAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_UNKEYED_ALL_FIELD_NUMBER = 63035; + /** extend .google.protobuf.FileOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FileOptions, Boolean> + goprotoUnkeyedAll = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_GETTERS_FIELD_NUMBER = 64001; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + goprotoGetters = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_STRINGER_FIELD_NUMBER = 64003; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + goprotoStringer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int VERBOSE_EQUAL_FIELD_NUMBER = 64004; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + verboseEqual = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int FACE_FIELD_NUMBER = 64005; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + face = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOSTRING_FIELD_NUMBER = 64006; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + gostring = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int POPULATE_FIELD_NUMBER = 64007; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + populate = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int STRINGER_FIELD_NUMBER = 67008; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + stringer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int ONLYONE_FIELD_NUMBER = 64009; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + onlyone = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int EQUAL_FIELD_NUMBER = 64013; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + equal = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int DESCRIPTION_FIELD_NUMBER = 64014; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + description = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int TESTGEN_FIELD_NUMBER = 64015; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + testgen = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int BENCHGEN_FIELD_NUMBER = 64016; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + benchgen = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int MARSHALER_FIELD_NUMBER = 64017; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + marshaler = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int UNMARSHALER_FIELD_NUMBER = 64018; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + unmarshaler = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int STABLE_MARSHALER_FIELD_NUMBER = 64019; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + stableMarshaler = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int SIZER_FIELD_NUMBER = 64020; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + sizer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int UNSAFE_MARSHALER_FIELD_NUMBER = 64023; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + unsafeMarshaler = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int UNSAFE_UNMARSHALER_FIELD_NUMBER = 64024; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + unsafeUnmarshaler = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_EXTENSIONS_MAP_FIELD_NUMBER = 64025; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + goprotoExtensionsMap = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_UNRECOGNIZED_FIELD_NUMBER = 64026; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + goprotoUnrecognized = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int PROTOSIZER_FIELD_NUMBER = 64028; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + protosizer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int COMPARE_FIELD_NUMBER = 64029; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + compare = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int TYPEDECL_FIELD_NUMBER = 64030; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + typedecl = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int MESSAGENAME_FIELD_NUMBER = 64033; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + messagename = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_SIZECACHE_FIELD_NUMBER = 64034; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + goprotoSizecache = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int GOPROTO_UNKEYED_FIELD_NUMBER = 64035; + /** extend .google.protobuf.MessageOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.MessageOptions, Boolean> + goprotoUnkeyed = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int NULLABLE_FIELD_NUMBER = 65001; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, Boolean> + nullable = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int EMBED_FIELD_NUMBER = 65002; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, Boolean> + embed = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int CUSTOMTYPE_FIELD_NUMBER = 65003; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + customtype = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int CUSTOMNAME_FIELD_NUMBER = 65004; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + customname = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int JSONTAG_FIELD_NUMBER = 65005; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + jsontag = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int MORETAGS_FIELD_NUMBER = 65006; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + moretags = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int CASTTYPE_FIELD_NUMBER = 65007; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + casttype = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int CASTKEY_FIELD_NUMBER = 65008; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + castkey = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int CASTVALUE_FIELD_NUMBER = 65009; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, String> + castvalue = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + String.class, null); + + public static final int STDTIME_FIELD_NUMBER = 65010; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, Boolean> + stdtime = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int STDDURATION_FIELD_NUMBER = 65011; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, Boolean> + stdduration = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static final int WKTPOINTER_FIELD_NUMBER = 65012; + /** extend .google.protobuf.FieldOptions { ... } */ + public static final com.google.protobuf.GeneratedMessage.GeneratedExtension< + com.google.protobuf.DescriptorProtos.FieldOptions, Boolean> + wktpointer = + com.google.protobuf.GeneratedMessage.newFileScopedGeneratedExtension( + Boolean.class, null); + + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + return descriptor; + } + + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; + + static { + String[] descriptorData = { + "\n\ngogo.proto\022\tgogoproto\032 google/protobuf" + + "/descriptor.proto:;\n\023goproto_enum_prefix" + + "\022\034.google.protobuf.EnumOptions\030\261\344\003 \001(\010:=" + + "\n\025goproto_enum_stringer\022\034.google.protobu" + + "f.EnumOptions\030\305\344\003 \001(\010:5\n\renum_stringer\022\034" + + ".google.protobuf.EnumOptions\030\306\344\003 \001(\010:7\n\017" + + "enum_customname\022\034.google.protobuf.EnumOp" + + "tions\030\307\344\003 \001(\t:0\n\010enumdecl\022\034.google.proto" + + "buf.EnumOptions\030\310\344\003 \001(\010:A\n\024enumvalue_cus" + + "tomname\022!.google.protobuf.EnumValueOptio" + + "ns\030\321\203\004 \001(\t:;\n\023goproto_getters_all\022\034.goog" + + "le.protobuf.FileOptions\030\231\354\003 \001(\010:?\n\027gopro" + + "to_enum_prefix_all\022\034.google.protobuf.Fil" + + "eOptions\030\232\354\003 \001(\010:<\n\024goproto_stringer_all" + + "\022\034.google.protobuf.FileOptions\030\233\354\003 \001(\010:9" + + "\n\021verbose_equal_all\022\034.google.protobuf.Fi" + + "leOptions\030\234\354\003 \001(\010:0\n\010face_all\022\034.google.p" + + "rotobuf.FileOptions\030\235\354\003 \001(\010:4\n\014gostring_" + + "all\022\034.google.protobuf.FileOptions\030\236\354\003 \001(" + + "\010:4\n\014populate_all\022\034.google.protobuf.File" + + "Options\030\237\354\003 \001(\010:4\n\014stringer_all\022\034.google" + + ".protobuf.FileOptions\030\240\354\003 \001(\010:3\n\013onlyone" + + "_all\022\034.google.protobuf.FileOptions\030\241\354\003 \001" + + "(\010:1\n\tequal_all\022\034.google.protobuf.FileOp" + + "tions\030\245\354\003 \001(\010:7\n\017description_all\022\034.googl" + + "e.protobuf.FileOptions\030\246\354\003 \001(\010:3\n\013testge" + + "n_all\022\034.google.protobuf.FileOptions\030\247\354\003 " + + "\001(\010:4\n\014benchgen_all\022\034.google.protobuf.Fi" + + "leOptions\030\250\354\003 \001(\010:5\n\rmarshaler_all\022\034.goo" + + "gle.protobuf.FileOptions\030\251\354\003 \001(\010:7\n\017unma" + + "rshaler_all\022\034.google.protobuf.FileOption" + + "s\030\252\354\003 \001(\010:<\n\024stable_marshaler_all\022\034.goog" + + "le.protobuf.FileOptions\030\253\354\003 \001(\010:1\n\tsizer" + + "_all\022\034.google.protobuf.FileOptions\030\254\354\003 \001" + + "(\010:A\n\031goproto_enum_stringer_all\022\034.google" + + ".protobuf.FileOptions\030\255\354\003 \001(\010:9\n\021enum_st" + + "ringer_all\022\034.google.protobuf.FileOptions" + + "\030\256\354\003 \001(\010:<\n\024unsafe_marshaler_all\022\034.googl" + + "e.protobuf.FileOptions\030\257\354\003 \001(\010:>\n\026unsafe" + + "_unmarshaler_all\022\034.google.protobuf.FileO" + + "ptions\030\260\354\003 \001(\010:B\n\032goproto_extensions_map" + + "_all\022\034.google.protobuf.FileOptions\030\261\354\003 \001" + + "(\010:@\n\030goproto_unrecognized_all\022\034.google." + + "protobuf.FileOptions\030\262\354\003 \001(\010:8\n\020gogoprot" + + "o_import\022\034.google.protobuf.FileOptions\030\263" + + "\354\003 \001(\010:6\n\016protosizer_all\022\034.google.protob" + + "uf.FileOptions\030\264\354\003 \001(\010:3\n\013compare_all\022\034." + + "google.protobuf.FileOptions\030\265\354\003 \001(\010:4\n\014t" + + "ypedecl_all\022\034.google.protobuf.FileOption" + + "s\030\266\354\003 \001(\010:4\n\014enumdecl_all\022\034.google.proto" + + "buf.FileOptions\030\267\354\003 \001(\010:<\n\024goproto_regis" + + "tration\022\034.google.protobuf.FileOptions\030\270\354" + + "\003 \001(\010:7\n\017messagename_all\022\034.google.protob" + + "uf.FileOptions\030\271\354\003 \001(\010:=\n\025goproto_sizeca" + + "che_all\022\034.google.protobuf.FileOptions\030\272\354" + + "\003 \001(\010:;\n\023goproto_unkeyed_all\022\034.google.pr" + + "otobuf.FileOptions\030\273\354\003 \001(\010::\n\017goproto_ge" + + "tters\022\037.google.protobuf.MessageOptions\030\201" + + "\364\003 \001(\010:;\n\020goproto_stringer\022\037.google.prot" + + "obuf.MessageOptions\030\203\364\003 \001(\010:8\n\rverbose_e" + + "qual\022\037.google.protobuf.MessageOptions\030\204\364" + + "\003 \001(\010:/\n\004face\022\037.google.protobuf.MessageO" + + "ptions\030\205\364\003 \001(\010:3\n\010gostring\022\037.google.prot" + + "obuf.MessageOptions\030\206\364\003 \001(\010:3\n\010populate\022" + + "\037.google.protobuf.MessageOptions\030\207\364\003 \001(\010" + + ":3\n\010stringer\022\037.google.protobuf.MessageOp" + + "tions\030\300\213\004 \001(\010:2\n\007onlyone\022\037.google.protob" + + "uf.MessageOptions\030\211\364\003 \001(\010:0\n\005equal\022\037.goo" + + "gle.protobuf.MessageOptions\030\215\364\003 \001(\010:6\n\013d" + + "escription\022\037.google.protobuf.MessageOpti" + + "ons\030\216\364\003 \001(\010:2\n\007testgen\022\037.google.protobuf" + + ".MessageOptions\030\217\364\003 \001(\010:3\n\010benchgen\022\037.go" + + "ogle.protobuf.MessageOptions\030\220\364\003 \001(\010:4\n\t" + + "marshaler\022\037.google.protobuf.MessageOptio" + + "ns\030\221\364\003 \001(\010:6\n\013unmarshaler\022\037.google.proto" + + "buf.MessageOptions\030\222\364\003 \001(\010:;\n\020stable_mar" + + "shaler\022\037.google.protobuf.MessageOptions\030" + + "\223\364\003 \001(\010:0\n\005sizer\022\037.google.protobuf.Messa" + + "geOptions\030\224\364\003 \001(\010:;\n\020unsafe_marshaler\022\037." + + "google.protobuf.MessageOptions\030\227\364\003 \001(\010:=" + + "\n\022unsafe_unmarshaler\022\037.google.protobuf.M" + + "essageOptions\030\230\364\003 \001(\010:A\n\026goproto_extensi" + + "ons_map\022\037.google.protobuf.MessageOptions" + + "\030\231\364\003 \001(\010:?\n\024goproto_unrecognized\022\037.googl" + + "e.protobuf.MessageOptions\030\232\364\003 \001(\010:5\n\npro" + + "tosizer\022\037.google.protobuf.MessageOptions" + + "\030\234\364\003 \001(\010:2\n\007compare\022\037.google.protobuf.Me" + + "ssageOptions\030\235\364\003 \001(\010:3\n\010typedecl\022\037.googl" + + "e.protobuf.MessageOptions\030\236\364\003 \001(\010:6\n\013mes" + + "sagename\022\037.google.protobuf.MessageOption" + + "s\030\241\364\003 \001(\010:<\n\021goproto_sizecache\022\037.google." + + "protobuf.MessageOptions\030\242\364\003 \001(\010::\n\017gopro" + + "to_unkeyed\022\037.google.protobuf.MessageOpti" + + "ons\030\243\364\003 \001(\010:1\n\010nullable\022\035.google.protobu" + + "f.FieldOptions\030\351\373\003 \001(\010:.\n\005embed\022\035.google" + + ".protobuf.FieldOptions\030\352\373\003 \001(\010:3\n\ncustom" + + "type\022\035.google.protobuf.FieldOptions\030\353\373\003 " + + "\001(\t:3\n\ncustomname\022\035.google.protobuf.Fiel" + + "dOptions\030\354\373\003 \001(\t:0\n\007jsontag\022\035.google.pro" + + "tobuf.FieldOptions\030\355\373\003 \001(\t:1\n\010moretags\022\035" + + ".google.protobuf.FieldOptions\030\356\373\003 \001(\t:1\n" + + "\010casttype\022\035.google.protobuf.FieldOptions" + + "\030\357\373\003 \001(\t:0\n\007castkey\022\035.google.protobuf.Fi" + + "eldOptions\030\360\373\003 \001(\t:2\n\tcastvalue\022\035.google" + + ".protobuf.FieldOptions\030\361\373\003 \001(\t:0\n\007stdtim" + + "e\022\035.google.protobuf.FieldOptions\030\362\373\003 \001(\010" + + ":4\n\013stdduration\022\035.google.protobuf.FieldO" + + "ptions\030\363\373\003 \001(\010:3\n\nwktpointer\022\035.google.pr" + + "otobuf.FieldOptions\030\364\373\003 \001(\010BE\n\023com.googl" + + "e.protobufB\nGoGoProtosZ\"github.com/gogo/" + + "protobuf/gogoproto" + }; + descriptor = + com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( + descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + com.google.protobuf.DescriptorProtos.getDescriptor(), + }); + goprotoEnumPrefix.internalInit(descriptor.getExtensions().get(0)); + goprotoEnumStringer.internalInit(descriptor.getExtensions().get(1)); + enumStringer.internalInit(descriptor.getExtensions().get(2)); + enumCustomname.internalInit(descriptor.getExtensions().get(3)); + enumdecl.internalInit(descriptor.getExtensions().get(4)); + enumvalueCustomname.internalInit(descriptor.getExtensions().get(5)); + goprotoGettersAll.internalInit(descriptor.getExtensions().get(6)); + goprotoEnumPrefixAll.internalInit(descriptor.getExtensions().get(7)); + goprotoStringerAll.internalInit(descriptor.getExtensions().get(8)); + verboseEqualAll.internalInit(descriptor.getExtensions().get(9)); + faceAll.internalInit(descriptor.getExtensions().get(10)); + gostringAll.internalInit(descriptor.getExtensions().get(11)); + populateAll.internalInit(descriptor.getExtensions().get(12)); + stringerAll.internalInit(descriptor.getExtensions().get(13)); + onlyoneAll.internalInit(descriptor.getExtensions().get(14)); + equalAll.internalInit(descriptor.getExtensions().get(15)); + descriptionAll.internalInit(descriptor.getExtensions().get(16)); + testgenAll.internalInit(descriptor.getExtensions().get(17)); + benchgenAll.internalInit(descriptor.getExtensions().get(18)); + marshalerAll.internalInit(descriptor.getExtensions().get(19)); + unmarshalerAll.internalInit(descriptor.getExtensions().get(20)); + stableMarshalerAll.internalInit(descriptor.getExtensions().get(21)); + sizerAll.internalInit(descriptor.getExtensions().get(22)); + goprotoEnumStringerAll.internalInit(descriptor.getExtensions().get(23)); + enumStringerAll.internalInit(descriptor.getExtensions().get(24)); + unsafeMarshalerAll.internalInit(descriptor.getExtensions().get(25)); + unsafeUnmarshalerAll.internalInit(descriptor.getExtensions().get(26)); + goprotoExtensionsMapAll.internalInit(descriptor.getExtensions().get(27)); + goprotoUnrecognizedAll.internalInit(descriptor.getExtensions().get(28)); + gogoprotoImport.internalInit(descriptor.getExtensions().get(29)); + protosizerAll.internalInit(descriptor.getExtensions().get(30)); + compareAll.internalInit(descriptor.getExtensions().get(31)); + typedeclAll.internalInit(descriptor.getExtensions().get(32)); + enumdeclAll.internalInit(descriptor.getExtensions().get(33)); + goprotoRegistration.internalInit(descriptor.getExtensions().get(34)); + messagenameAll.internalInit(descriptor.getExtensions().get(35)); + goprotoSizecacheAll.internalInit(descriptor.getExtensions().get(36)); + goprotoUnkeyedAll.internalInit(descriptor.getExtensions().get(37)); + goprotoGetters.internalInit(descriptor.getExtensions().get(38)); + goprotoStringer.internalInit(descriptor.getExtensions().get(39)); + verboseEqual.internalInit(descriptor.getExtensions().get(40)); + face.internalInit(descriptor.getExtensions().get(41)); + gostring.internalInit(descriptor.getExtensions().get(42)); + populate.internalInit(descriptor.getExtensions().get(43)); + stringer.internalInit(descriptor.getExtensions().get(44)); + onlyone.internalInit(descriptor.getExtensions().get(45)); + equal.internalInit(descriptor.getExtensions().get(46)); + description.internalInit(descriptor.getExtensions().get(47)); + testgen.internalInit(descriptor.getExtensions().get(48)); + benchgen.internalInit(descriptor.getExtensions().get(49)); + marshaler.internalInit(descriptor.getExtensions().get(50)); + unmarshaler.internalInit(descriptor.getExtensions().get(51)); + stableMarshaler.internalInit(descriptor.getExtensions().get(52)); + sizer.internalInit(descriptor.getExtensions().get(53)); + unsafeMarshaler.internalInit(descriptor.getExtensions().get(54)); + unsafeUnmarshaler.internalInit(descriptor.getExtensions().get(55)); + goprotoExtensionsMap.internalInit(descriptor.getExtensions().get(56)); + goprotoUnrecognized.internalInit(descriptor.getExtensions().get(57)); + protosizer.internalInit(descriptor.getExtensions().get(58)); + compare.internalInit(descriptor.getExtensions().get(59)); + typedecl.internalInit(descriptor.getExtensions().get(60)); + messagename.internalInit(descriptor.getExtensions().get(61)); + goprotoSizecache.internalInit(descriptor.getExtensions().get(62)); + goprotoUnkeyed.internalInit(descriptor.getExtensions().get(63)); + nullable.internalInit(descriptor.getExtensions().get(64)); + embed.internalInit(descriptor.getExtensions().get(65)); + customtype.internalInit(descriptor.getExtensions().get(66)); + customname.internalInit(descriptor.getExtensions().get(67)); + jsontag.internalInit(descriptor.getExtensions().get(68)); + moretags.internalInit(descriptor.getExtensions().get(69)); + casttype.internalInit(descriptor.getExtensions().get(70)); + castkey.internalInit(descriptor.getExtensions().get(71)); + castvalue.internalInit(descriptor.getExtensions().get(72)); + stdtime.internalInit(descriptor.getExtensions().get(73)); + stdduration.internalInit(descriptor.getExtensions().get(74)); + wktpointer.internalInit(descriptor.getExtensions().get(75)); + com.google.protobuf.DescriptorProtos.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/Remote.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/Remote.java new file mode 100644 index 00000000000..17a67d4446b --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/Remote.java @@ -0,0 +1,6998 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink.proto; + +public final class Remote { + private Remote() {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry); + } + + public interface WriteRequestOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.WriteRequest) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getTimeseriesList(); + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + Types.TimeSeries getTimeseries(int index); + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + int getTimeseriesCount(); + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getTimeseriesOrBuilderList(); + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index); + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getMetadataList(); + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + Types.MetricMetadata getMetadata(int index); + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + int getMetadataCount(); + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getMetadataOrBuilderList(); + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + Types.MetricMetadataOrBuilder getMetadataOrBuilder(int index); + } + + /** Protobuf type {@code prometheus.WriteRequest} */ + public static final class WriteRequest extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.WriteRequest) + WriteRequestOrBuilder { + private static final long serialVersionUID = 0L; + + // Use WriteRequest.newBuilder() to construct. + private WriteRequest(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private WriteRequest() { + timeseries_ = java.util.Collections.emptyList(); + metadata_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new WriteRequest(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_WriteRequest_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_WriteRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.WriteRequest.class, Remote.WriteRequest.Builder.class); + } + + public static final int TIMESERIES_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List timeseries_; + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getTimeseriesList() { + return timeseries_; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getTimeseriesOrBuilderList() { + return timeseries_; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + @Override + public int getTimeseriesCount() { + return timeseries_.size(); + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.TimeSeries getTimeseries(int index) { + return timeseries_.get(index); + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) { + return timeseries_.get(index); + } + + public static final int METADATA_FIELD_NUMBER = 3; + + @SuppressWarnings("serial") + private java.util.List metadata_; + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getMetadataList() { + return metadata_; + } + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getMetadataOrBuilderList() { + return metadata_; + } + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + @Override + public int getMetadataCount() { + return metadata_.size(); + } + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.MetricMetadata getMetadata(int index) { + return metadata_.get(index); + } + + /** + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.MetricMetadataOrBuilder getMetadataOrBuilder(int index) { + return metadata_.get(index); + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < timeseries_.size(); i++) { + output.writeMessage(1, timeseries_.get(i)); + } + for (int i = 0; i < metadata_.size(); i++) { + output.writeMessage(3, metadata_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < timeseries_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 1, timeseries_.get(i)); + } + for (int i = 0; i < metadata_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 3, metadata_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Remote.WriteRequest)) { + return super.equals(obj); + } + Remote.WriteRequest other = (Remote.WriteRequest) obj; + + if (!getTimeseriesList().equals(other.getTimeseriesList())) { + return false; + } + if (!getMetadataList().equals(other.getMetadataList())) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getTimeseriesCount() > 0) { + hash = (37 * hash) + TIMESERIES_FIELD_NUMBER; + hash = (53 * hash) + getTimeseriesList().hashCode(); + } + if (getMetadataCount() > 0) { + hash = (37 * hash) + METADATA_FIELD_NUMBER; + hash = (53 * hash) + getMetadataList().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Remote.WriteRequest parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.WriteRequest parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.WriteRequest parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.WriteRequest parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.WriteRequest parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.WriteRequest parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.WriteRequest parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.WriteRequest parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.WriteRequest parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Remote.WriteRequest parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.WriteRequest parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.WriteRequest parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Remote.WriteRequest prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.WriteRequest} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.WriteRequest) + Remote.WriteRequestOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_WriteRequest_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_WriteRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.WriteRequest.class, Remote.WriteRequest.Builder.class); + } + + // Construct using Remote.WriteRequest.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (timeseriesBuilder_ == null) { + timeseries_ = java.util.Collections.emptyList(); + } else { + timeseries_ = null; + timeseriesBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + if (metadataBuilder_ == null) { + metadata_ = java.util.Collections.emptyList(); + } else { + metadata_ = null; + metadataBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Remote.internal_static_prometheus_WriteRequest_descriptor; + } + + @Override + public Remote.WriteRequest getDefaultInstanceForType() { + return Remote.WriteRequest.getDefaultInstance(); + } + + @Override + public Remote.WriteRequest build() { + Remote.WriteRequest result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Remote.WriteRequest buildPartial() { + Remote.WriteRequest result = new Remote.WriteRequest(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Remote.WriteRequest result) { + if (timeseriesBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + timeseries_ = java.util.Collections.unmodifiableList(timeseries_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.timeseries_ = timeseries_; + } else { + result.timeseries_ = timeseriesBuilder_.build(); + } + if (metadataBuilder_ == null) { + if (((bitField0_ & 0x00000002) != 0)) { + metadata_ = java.util.Collections.unmodifiableList(metadata_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.metadata_ = metadata_; + } else { + result.metadata_ = metadataBuilder_.build(); + } + } + + private void buildPartial0(Remote.WriteRequest result) { + int from_bitField0_ = bitField0_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Remote.WriteRequest) { + return mergeFrom((Remote.WriteRequest) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Remote.WriteRequest other) { + if (other == Remote.WriteRequest.getDefaultInstance()) { + return this; + } + if (timeseriesBuilder_ == null) { + if (!other.timeseries_.isEmpty()) { + if (timeseries_.isEmpty()) { + timeseries_ = other.timeseries_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureTimeseriesIsMutable(); + timeseries_.addAll(other.timeseries_); + } + onChanged(); + } + } else { + if (!other.timeseries_.isEmpty()) { + if (timeseriesBuilder_.isEmpty()) { + timeseriesBuilder_.dispose(); + timeseriesBuilder_ = null; + timeseries_ = other.timeseries_; + bitField0_ = (bitField0_ & ~0x00000001); + timeseriesBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getTimeseriesFieldBuilder() + : null; + } else { + timeseriesBuilder_.addAllMessages(other.timeseries_); + } + } + } + if (metadataBuilder_ == null) { + if (!other.metadata_.isEmpty()) { + if (metadata_.isEmpty()) { + metadata_ = other.metadata_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureMetadataIsMutable(); + metadata_.addAll(other.metadata_); + } + onChanged(); + } + } else { + if (!other.metadata_.isEmpty()) { + if (metadataBuilder_.isEmpty()) { + metadataBuilder_.dispose(); + metadataBuilder_ = null; + metadata_ = other.metadata_; + bitField0_ = (bitField0_ & ~0x00000002); + metadataBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getMetadataFieldBuilder() + : null; + } else { + metadataBuilder_.addAllMessages(other.metadata_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Types.TimeSeries m = + input.readMessage( + Types.TimeSeries.parser(), extensionRegistry); + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.add(m); + } else { + timeseriesBuilder_.addMessage(m); + } + break; + } // case 10 + case 26: + { + Types.MetricMetadata m = + input.readMessage( + Types.MetricMetadata.parser(), + extensionRegistry); + if (metadataBuilder_ == null) { + ensureMetadataIsMutable(); + metadata_.add(m); + } else { + metadataBuilder_.addMessage(m); + } + break; + } // case 26 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List timeseries_ = + java.util.Collections.emptyList(); + + private void ensureTimeseriesIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + timeseries_ = new java.util.ArrayList(timeseries_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder> + timeseriesBuilder_; + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getTimeseriesList() { + if (timeseriesBuilder_ == null) { + return java.util.Collections.unmodifiableList(timeseries_); + } else { + return timeseriesBuilder_.getMessageList(); + } + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public int getTimeseriesCount() { + if (timeseriesBuilder_ == null) { + return timeseries_.size(); + } else { + return timeseriesBuilder_.getCount(); + } + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Types.TimeSeries getTimeseries(int index) { + if (timeseriesBuilder_ == null) { + return timeseries_.get(index); + } else { + return timeseriesBuilder_.getMessage(index); + } + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder setTimeseries(int index, Types.TimeSeries value) { + if (timeseriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureTimeseriesIsMutable(); + timeseries_.set(index, value); + onChanged(); + } else { + timeseriesBuilder_.setMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder setTimeseries(int index, Types.TimeSeries.Builder builderForValue) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.set(index, builderForValue.build()); + onChanged(); + } else { + timeseriesBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder addTimeseries(Types.TimeSeries value) { + if (timeseriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureTimeseriesIsMutable(); + timeseries_.add(value); + onChanged(); + } else { + timeseriesBuilder_.addMessage(value); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder addTimeseries(int index, Types.TimeSeries value) { + if (timeseriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureTimeseriesIsMutable(); + timeseries_.add(index, value); + onChanged(); + } else { + timeseriesBuilder_.addMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder addTimeseries(Types.TimeSeries.Builder builderForValue) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.add(builderForValue.build()); + onChanged(); + } else { + timeseriesBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder addTimeseries(int index, Types.TimeSeries.Builder builderForValue) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.add(index, builderForValue.build()); + onChanged(); + } else { + timeseriesBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder addAllTimeseries(Iterable values) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, timeseries_); + onChanged(); + } else { + timeseriesBuilder_.addAllMessages(values); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder clearTimeseries() { + if (timeseriesBuilder_ == null) { + timeseries_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + timeseriesBuilder_.clear(); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Builder removeTimeseries(int index) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.remove(index); + onChanged(); + } else { + timeseriesBuilder_.remove(index); + } + return this; + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Types.TimeSeries.Builder getTimeseriesBuilder(int index) { + return getTimeseriesFieldBuilder().getBuilder(index); + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) { + if (timeseriesBuilder_ == null) { + return timeseries_.get(index); + } else { + return timeseriesBuilder_.getMessageOrBuilder(index); + } + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List + getTimeseriesOrBuilderList() { + if (timeseriesBuilder_ != null) { + return timeseriesBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(timeseries_); + } + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Types.TimeSeries.Builder addTimeseriesBuilder() { + return getTimeseriesFieldBuilder() + .addBuilder(Types.TimeSeries.getDefaultInstance()); + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public Types.TimeSeries.Builder addTimeseriesBuilder(int index) { + return getTimeseriesFieldBuilder() + .addBuilder(index, Types.TimeSeries.getDefaultInstance()); + } + + /** + * repeated .prometheus.TimeSeries timeseries = 1 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getTimeseriesBuilderList() { + return getTimeseriesFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder> + getTimeseriesFieldBuilder() { + if (timeseriesBuilder_ == null) { + timeseriesBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.TimeSeries, + Types.TimeSeries.Builder, + Types.TimeSeriesOrBuilder>( + timeseries_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + timeseries_ = null; + } + return timeseriesBuilder_; + } + + private java.util.List metadata_ = + java.util.Collections.emptyList(); + + private void ensureMetadataIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + metadata_ = new java.util.ArrayList(metadata_); + bitField0_ |= 0x00000002; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.MetricMetadata, + Types.MetricMetadata.Builder, + Types.MetricMetadataOrBuilder> + metadataBuilder_; + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getMetadataList() { + if (metadataBuilder_ == null) { + return java.util.Collections.unmodifiableList(metadata_); + } else { + return metadataBuilder_.getMessageList(); + } + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public int getMetadataCount() { + if (metadataBuilder_ == null) { + return metadata_.size(); + } else { + return metadataBuilder_.getCount(); + } + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.MetricMetadata getMetadata(int index) { + if (metadataBuilder_ == null) { + return metadata_.get(index); + } else { + return metadataBuilder_.getMessage(index); + } + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder setMetadata(int index, Types.MetricMetadata value) { + if (metadataBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMetadataIsMutable(); + metadata_.set(index, value); + onChanged(); + } else { + metadataBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder setMetadata(int index, Types.MetricMetadata.Builder builderForValue) { + if (metadataBuilder_ == null) { + ensureMetadataIsMutable(); + metadata_.set(index, builderForValue.build()); + onChanged(); + } else { + metadataBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addMetadata(Types.MetricMetadata value) { + if (metadataBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMetadataIsMutable(); + metadata_.add(value); + onChanged(); + } else { + metadataBuilder_.addMessage(value); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addMetadata(int index, Types.MetricMetadata value) { + if (metadataBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMetadataIsMutable(); + metadata_.add(index, value); + onChanged(); + } else { + metadataBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addMetadata(Types.MetricMetadata.Builder builderForValue) { + if (metadataBuilder_ == null) { + ensureMetadataIsMutable(); + metadata_.add(builderForValue.build()); + onChanged(); + } else { + metadataBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addMetadata(int index, Types.MetricMetadata.Builder builderForValue) { + if (metadataBuilder_ == null) { + ensureMetadataIsMutable(); + metadata_.add(index, builderForValue.build()); + onChanged(); + } else { + metadataBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addAllMetadata(Iterable values) { + if (metadataBuilder_ == null) { + ensureMetadataIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, metadata_); + onChanged(); + } else { + metadataBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder clearMetadata() { + if (metadataBuilder_ == null) { + metadata_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + } else { + metadataBuilder_.clear(); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder removeMetadata(int index) { + if (metadataBuilder_ == null) { + ensureMetadataIsMutable(); + metadata_.remove(index); + onChanged(); + } else { + metadataBuilder_.remove(index); + } + return this; + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.MetricMetadata.Builder getMetadataBuilder(int index) { + return getMetadataFieldBuilder().getBuilder(index); + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.MetricMetadataOrBuilder getMetadataOrBuilder(int index) { + if (metadataBuilder_ == null) { + return metadata_.get(index); + } else { + return metadataBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List + getMetadataOrBuilderList() { + if (metadataBuilder_ != null) { + return metadataBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(metadata_); + } + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.MetricMetadata.Builder addMetadataBuilder() { + return getMetadataFieldBuilder() + .addBuilder(Types.MetricMetadata.getDefaultInstance()); + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.MetricMetadata.Builder addMetadataBuilder(int index) { + return getMetadataFieldBuilder() + .addBuilder(index, Types.MetricMetadata.getDefaultInstance()); + } + + /** + * + * repeated .prometheus.MetricMetadata metadata = 3 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getMetadataBuilderList() { + return getMetadataFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.MetricMetadata, + Types.MetricMetadata.Builder, + Types.MetricMetadataOrBuilder> + getMetadataFieldBuilder() { + if (metadataBuilder_ == null) { + metadataBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.MetricMetadata, + Types.MetricMetadata.Builder, + Types.MetricMetadataOrBuilder>( + metadata_, + ((bitField0_ & 0x00000002) != 0), + getParentForChildren(), + isClean()); + metadata_ = null; + } + return metadataBuilder_; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.WriteRequest) + } + + // @@protoc_insertion_point(class_scope:prometheus.WriteRequest) + private static final Remote.WriteRequest DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Remote.WriteRequest(); + } + + public static Remote.WriteRequest getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public WriteRequest parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Remote.WriteRequest getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface ReadRequestOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.ReadRequest) + com.google.protobuf.MessageOrBuilder { + + /** repeated .prometheus.Query queries = 1; */ + java.util.List getQueriesList(); + + /** repeated .prometheus.Query queries = 1; */ + Remote.Query getQueries(int index); + + /** repeated .prometheus.Query queries = 1; */ + int getQueriesCount(); + + /** repeated .prometheus.Query queries = 1; */ + java.util.List getQueriesOrBuilderList(); + + /** repeated .prometheus.Query queries = 1; */ + Remote.QueryOrBuilder getQueriesOrBuilder(int index); + + /** + * + * + *

+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @return A list containing the acceptedResponseTypes. + */ + java.util.List getAcceptedResponseTypesList(); + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @return The count of acceptedResponseTypes. + */ + int getAcceptedResponseTypesCount(); + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @param index The index of the element to return. + * @return The acceptedResponseTypes at the given index. + */ + Remote.ReadRequest.ResponseType getAcceptedResponseTypes(int index); + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @return A list containing the enum numeric values on the wire for acceptedResponseTypes. + */ + java.util.List getAcceptedResponseTypesValueList(); + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @param index The index of the value to return. + * @return The enum numeric value on the wire of acceptedResponseTypes at the given index. + */ + int getAcceptedResponseTypesValue(int index); + } + + /** + * + * + *
+     * ReadRequest represents a remote read request.
+     * 
+ * + *

Protobuf type {@code prometheus.ReadRequest} + */ + public static final class ReadRequest extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.ReadRequest) + ReadRequestOrBuilder { + private static final long serialVersionUID = 0L; + + // Use ReadRequest.newBuilder() to construct. + private ReadRequest(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private ReadRequest() { + queries_ = java.util.Collections.emptyList(); + acceptedResponseTypes_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new ReadRequest(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_ReadRequest_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_ReadRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.ReadRequest.class, Remote.ReadRequest.Builder.class); + } + + /** Protobuf enum {@code prometheus.ReadRequest.ResponseType} */ + public enum ResponseType implements com.google.protobuf.ProtocolMessageEnum { + /** + * + * + *

+             * Server will return a single ReadResponse message with matched series that includes list of raw samples.
+             * It's recommended to use streamed response types instead.
+             *
+             * Response headers:
+             * Content-Type: "application/x-protobuf"
+             * Content-Encoding: "snappy"
+             * 
+ * + * SAMPLES = 0; + */ + SAMPLES(0), + /** + * + * + *
+             * Server will stream a delimited ChunkedReadResponse message that
+             * contains XOR or HISTOGRAM(!) encoded chunks for a single series.
+             * Each message is following varint size and fixed size bigendian
+             * uint32 for CRC32 Castagnoli checksum.
+             *
+             * Response headers:
+             * Content-Type: "application/x-streamed-protobuf; proto=prometheus.ChunkedReadResponse"
+             * Content-Encoding: ""
+             * 
+ * + * STREAMED_XOR_CHUNKS = 1; + */ + STREAMED_XOR_CHUNKS(1), + UNRECOGNIZED(-1), + ; + + /** + * + * + *
+             * Server will return a single ReadResponse message with matched series that includes list of raw samples.
+             * It's recommended to use streamed response types instead.
+             *
+             * Response headers:
+             * Content-Type: "application/x-protobuf"
+             * Content-Encoding: "snappy"
+             * 
+ * + * SAMPLES = 0; + */ + public static final int SAMPLES_VALUE = 0; + /** + * + * + *
+             * Server will stream a delimited ChunkedReadResponse message that
+             * contains XOR or HISTOGRAM(!) encoded chunks for a single series.
+             * Each message is following varint size and fixed size bigendian
+             * uint32 for CRC32 Castagnoli checksum.
+             *
+             * Response headers:
+             * Content-Type: "application/x-streamed-protobuf; proto=prometheus.ChunkedReadResponse"
+             * Content-Encoding: ""
+             * 
+ * + * STREAMED_XOR_CHUNKS = 1; + */ + public static final int STREAMED_XOR_CHUNKS_VALUE = 1; + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @Deprecated + public static ResponseType valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static ResponseType forNumber(int value) { + switch (value) { + case 0: + return SAMPLES; + case 1: + return STREAMED_XOR_CHUNKS; + default: + return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + + private static final com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public ResponseType findValueByNumber(int number) { + return ResponseType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + + public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { + return getDescriptor(); + } + + public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { + return Remote.ReadRequest.getDescriptor().getEnumTypes().get(0); + } + + private static final ResponseType[] VALUES = values(); + + public static ResponseType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new IllegalArgumentException("EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private ResponseType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:prometheus.ReadRequest.ResponseType) + } + + public static final int QUERIES_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List queries_; + + /** repeated .prometheus.Query queries = 1; */ + @Override + public java.util.List getQueriesList() { + return queries_; + } + + /** repeated .prometheus.Query queries = 1; */ + @Override + public java.util.List getQueriesOrBuilderList() { + return queries_; + } + + /** repeated .prometheus.Query queries = 1; */ + @Override + public int getQueriesCount() { + return queries_.size(); + } + + /** repeated .prometheus.Query queries = 1; */ + @Override + public Remote.Query getQueries(int index) { + return queries_.get(index); + } + + /** repeated .prometheus.Query queries = 1; */ + @Override + public Remote.QueryOrBuilder getQueriesOrBuilder(int index) { + return queries_.get(index); + } + + public static final int ACCEPTED_RESPONSE_TYPES_FIELD_NUMBER = 2; + + @SuppressWarnings("serial") + private java.util.List acceptedResponseTypes_; + + private static final com.google.protobuf.Internal.ListAdapter.Converter< + Integer, Remote.ReadRequest.ResponseType> + acceptedResponseTypes_converter_ = + new com.google.protobuf.Internal.ListAdapter.Converter< + Integer, Remote.ReadRequest.ResponseType>() { + public Remote.ReadRequest.ResponseType convert(Integer from) { + Remote.ReadRequest.ResponseType result = + Remote.ReadRequest.ResponseType.forNumber(from); + return result == null + ? Remote.ReadRequest.ResponseType.UNRECOGNIZED + : result; + } + }; + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @return A list containing the acceptedResponseTypes. + */ + @Override + public java.util.List getAcceptedResponseTypesList() { + return new com.google.protobuf.Internal.ListAdapter< + Integer, Remote.ReadRequest.ResponseType>( + acceptedResponseTypes_, acceptedResponseTypes_converter_); + } + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @return The count of acceptedResponseTypes. + */ + @Override + public int getAcceptedResponseTypesCount() { + return acceptedResponseTypes_.size(); + } + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @param index The index of the element to return. + * @return The acceptedResponseTypes at the given index. + */ + @Override + public Remote.ReadRequest.ResponseType getAcceptedResponseTypes(int index) { + return acceptedResponseTypes_converter_.convert(acceptedResponseTypes_.get(index)); + } + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @return A list containing the enum numeric values on the wire for acceptedResponseTypes. + */ + @Override + public java.util.List getAcceptedResponseTypesValueList() { + return acceptedResponseTypes_; + } + + /** + * + * + *
+         * accepted_response_types allows negotiating the content type of the response.
+         *
+         * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+         * implemented by server, error is returned.
+         * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+         * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * @param index The index of the value to return. + * @return The enum numeric value on the wire of acceptedResponseTypes at the given index. + */ + @Override + public int getAcceptedResponseTypesValue(int index) { + return acceptedResponseTypes_.get(index); + } + + private int acceptedResponseTypesMemoizedSerializedSize; + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + for (int i = 0; i < queries_.size(); i++) { + output.writeMessage(1, queries_.get(i)); + } + if (getAcceptedResponseTypesList().size() > 0) { + output.writeUInt32NoTag(18); + output.writeUInt32NoTag(acceptedResponseTypesMemoizedSerializedSize); + } + for (int i = 0; i < acceptedResponseTypes_.size(); i++) { + output.writeEnumNoTag(acceptedResponseTypes_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < queries_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 1, queries_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < acceptedResponseTypes_.size(); i++) { + dataSize += + com.google.protobuf.CodedOutputStream.computeEnumSizeNoTag( + acceptedResponseTypes_.get(i)); + } + size += dataSize; + if (!getAcceptedResponseTypesList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream.computeUInt32SizeNoTag(dataSize); + } + acceptedResponseTypesMemoizedSerializedSize = dataSize; + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Remote.ReadRequest)) { + return super.equals(obj); + } + Remote.ReadRequest other = (Remote.ReadRequest) obj; + + if (!getQueriesList().equals(other.getQueriesList())) { + return false; + } + if (!acceptedResponseTypes_.equals(other.acceptedResponseTypes_)) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getQueriesCount() > 0) { + hash = (37 * hash) + QUERIES_FIELD_NUMBER; + hash = (53 * hash) + getQueriesList().hashCode(); + } + if (getAcceptedResponseTypesCount() > 0) { + hash = (37 * hash) + ACCEPTED_RESPONSE_TYPES_FIELD_NUMBER; + hash = (53 * hash) + acceptedResponseTypes_.hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Remote.ReadRequest parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ReadRequest parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ReadRequest parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ReadRequest parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ReadRequest parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ReadRequest parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ReadRequest parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.ReadRequest parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.ReadRequest parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Remote.ReadRequest parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.ReadRequest parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.ReadRequest parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Remote.ReadRequest prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** + * + * + *
+         * ReadRequest represents a remote read request.
+         * 
+ * + *

Protobuf type {@code prometheus.ReadRequest} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.ReadRequest) + Remote.ReadRequestOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_ReadRequest_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_ReadRequest_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.ReadRequest.class, Remote.ReadRequest.Builder.class); + } + + // Construct using Remote.ReadRequest.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (queriesBuilder_ == null) { + queries_ = java.util.Collections.emptyList(); + } else { + queries_ = null; + queriesBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + acceptedResponseTypes_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Remote.internal_static_prometheus_ReadRequest_descriptor; + } + + @Override + public Remote.ReadRequest getDefaultInstanceForType() { + return Remote.ReadRequest.getDefaultInstance(); + } + + @Override + public Remote.ReadRequest build() { + Remote.ReadRequest result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Remote.ReadRequest buildPartial() { + Remote.ReadRequest result = new Remote.ReadRequest(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Remote.ReadRequest result) { + if (queriesBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + queries_ = java.util.Collections.unmodifiableList(queries_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.queries_ = queries_; + } else { + result.queries_ = queriesBuilder_.build(); + } + if (((bitField0_ & 0x00000002) != 0)) { + acceptedResponseTypes_ = + java.util.Collections.unmodifiableList(acceptedResponseTypes_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.acceptedResponseTypes_ = acceptedResponseTypes_; + } + + private void buildPartial0(Remote.ReadRequest result) { + int from_bitField0_ = bitField0_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Remote.ReadRequest) { + return mergeFrom((Remote.ReadRequest) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Remote.ReadRequest other) { + if (other == Remote.ReadRequest.getDefaultInstance()) { + return this; + } + if (queriesBuilder_ == null) { + if (!other.queries_.isEmpty()) { + if (queries_.isEmpty()) { + queries_ = other.queries_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureQueriesIsMutable(); + queries_.addAll(other.queries_); + } + onChanged(); + } + } else { + if (!other.queries_.isEmpty()) { + if (queriesBuilder_.isEmpty()) { + queriesBuilder_.dispose(); + queriesBuilder_ = null; + queries_ = other.queries_; + bitField0_ = (bitField0_ & ~0x00000001); + queriesBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getQueriesFieldBuilder() + : null; + } else { + queriesBuilder_.addAllMessages(other.queries_); + } + } + } + if (!other.acceptedResponseTypes_.isEmpty()) { + if (acceptedResponseTypes_.isEmpty()) { + acceptedResponseTypes_ = other.acceptedResponseTypes_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.addAll(other.acceptedResponseTypes_); + } + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Remote.Query m = + input.readMessage( + Remote.Query.parser(), extensionRegistry); + if (queriesBuilder_ == null) { + ensureQueriesIsMutable(); + queries_.add(m); + } else { + queriesBuilder_.addMessage(m); + } + break; + } // case 10 + case 16: + { + int tmpRaw = input.readEnum(); + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.add(tmpRaw); + break; + } // case 16 + case 18: + { + int length = input.readRawVarint32(); + int oldLimit = input.pushLimit(length); + while (input.getBytesUntilLimit() > 0) { + int tmpRaw = input.readEnum(); + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.add(tmpRaw); + } + input.popLimit(oldLimit); + break; + } // case 18 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List queries_ = java.util.Collections.emptyList(); + + private void ensureQueriesIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + queries_ = new java.util.ArrayList(queries_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Remote.Query, Remote.Query.Builder, Remote.QueryOrBuilder> + queriesBuilder_; + + /** repeated .prometheus.Query queries = 1; */ + public java.util.List getQueriesList() { + if (queriesBuilder_ == null) { + return java.util.Collections.unmodifiableList(queries_); + } else { + return queriesBuilder_.getMessageList(); + } + } + + /** repeated .prometheus.Query queries = 1; */ + public int getQueriesCount() { + if (queriesBuilder_ == null) { + return queries_.size(); + } else { + return queriesBuilder_.getCount(); + } + } + + /** repeated .prometheus.Query queries = 1; */ + public Remote.Query getQueries(int index) { + if (queriesBuilder_ == null) { + return queries_.get(index); + } else { + return queriesBuilder_.getMessage(index); + } + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder setQueries(int index, Remote.Query value) { + if (queriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureQueriesIsMutable(); + queries_.set(index, value); + onChanged(); + } else { + queriesBuilder_.setMessage(index, value); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder setQueries(int index, Remote.Query.Builder builderForValue) { + if (queriesBuilder_ == null) { + ensureQueriesIsMutable(); + queries_.set(index, builderForValue.build()); + onChanged(); + } else { + queriesBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder addQueries(Remote.Query value) { + if (queriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureQueriesIsMutable(); + queries_.add(value); + onChanged(); + } else { + queriesBuilder_.addMessage(value); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder addQueries(int index, Remote.Query value) { + if (queriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureQueriesIsMutable(); + queries_.add(index, value); + onChanged(); + } else { + queriesBuilder_.addMessage(index, value); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder addQueries(Remote.Query.Builder builderForValue) { + if (queriesBuilder_ == null) { + ensureQueriesIsMutable(); + queries_.add(builderForValue.build()); + onChanged(); + } else { + queriesBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder addQueries(int index, Remote.Query.Builder builderForValue) { + if (queriesBuilder_ == null) { + ensureQueriesIsMutable(); + queries_.add(index, builderForValue.build()); + onChanged(); + } else { + queriesBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder addAllQueries(Iterable values) { + if (queriesBuilder_ == null) { + ensureQueriesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, queries_); + onChanged(); + } else { + queriesBuilder_.addAllMessages(values); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder clearQueries() { + if (queriesBuilder_ == null) { + queries_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + queriesBuilder_.clear(); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Builder removeQueries(int index) { + if (queriesBuilder_ == null) { + ensureQueriesIsMutable(); + queries_.remove(index); + onChanged(); + } else { + queriesBuilder_.remove(index); + } + return this; + } + + /** repeated .prometheus.Query queries = 1; */ + public Remote.Query.Builder getQueriesBuilder(int index) { + return getQueriesFieldBuilder().getBuilder(index); + } + + /** repeated .prometheus.Query queries = 1; */ + public Remote.QueryOrBuilder getQueriesOrBuilder(int index) { + if (queriesBuilder_ == null) { + return queries_.get(index); + } else { + return queriesBuilder_.getMessageOrBuilder(index); + } + } + + /** repeated .prometheus.Query queries = 1; */ + public java.util.List getQueriesOrBuilderList() { + if (queriesBuilder_ != null) { + return queriesBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(queries_); + } + } + + /** repeated .prometheus.Query queries = 1; */ + public Remote.Query.Builder addQueriesBuilder() { + return getQueriesFieldBuilder().addBuilder(Remote.Query.getDefaultInstance()); + } + + /** repeated .prometheus.Query queries = 1; */ + public Remote.Query.Builder addQueriesBuilder(int index) { + return getQueriesFieldBuilder() + .addBuilder(index, Remote.Query.getDefaultInstance()); + } + + /** repeated .prometheus.Query queries = 1; */ + public java.util.List getQueriesBuilderList() { + return getQueriesFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Remote.Query, Remote.Query.Builder, Remote.QueryOrBuilder> + getQueriesFieldBuilder() { + if (queriesBuilder_ == null) { + queriesBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Remote.Query, Remote.Query.Builder, Remote.QueryOrBuilder>( + queries_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + queries_ = null; + } + return queriesBuilder_; + } + + private java.util.List acceptedResponseTypes_ = + java.util.Collections.emptyList(); + + private void ensureAcceptedResponseTypesIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + acceptedResponseTypes_ = + new java.util.ArrayList(acceptedResponseTypes_); + bitField0_ |= 0x00000002; + } + } + + /** + * + * + *

+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @return A list containing the acceptedResponseTypes. + */ + public java.util.List getAcceptedResponseTypesList() { + return new com.google.protobuf.Internal.ListAdapter< + Integer, Remote.ReadRequest.ResponseType>( + acceptedResponseTypes_, acceptedResponseTypes_converter_); + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @return The count of acceptedResponseTypes. + */ + public int getAcceptedResponseTypesCount() { + return acceptedResponseTypes_.size(); + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param index The index of the element to return. + * @return The acceptedResponseTypes at the given index. + */ + public Remote.ReadRequest.ResponseType getAcceptedResponseTypes(int index) { + return acceptedResponseTypes_converter_.convert(acceptedResponseTypes_.get(index)); + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param index The index to set the value at. + * @param value The acceptedResponseTypes to set. + * @return This builder for chaining. + */ + public Builder setAcceptedResponseTypes( + int index, Remote.ReadRequest.ResponseType value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.set(index, value.getNumber()); + onChanged(); + return this; + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param value The acceptedResponseTypes to add. + * @return This builder for chaining. + */ + public Builder addAcceptedResponseTypes(Remote.ReadRequest.ResponseType value) { + if (value == null) { + throw new NullPointerException(); + } + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.add(value.getNumber()); + onChanged(); + return this; + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param values The acceptedResponseTypes to add. + * @return This builder for chaining. + */ + public Builder addAllAcceptedResponseTypes( + Iterable values) { + ensureAcceptedResponseTypesIsMutable(); + for (Remote.ReadRequest.ResponseType value : values) { + acceptedResponseTypes_.add(value.getNumber()); + } + onChanged(); + return this; + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @return This builder for chaining. + */ + public Builder clearAcceptedResponseTypes() { + acceptedResponseTypes_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @return A list containing the enum numeric values on the wire for + * acceptedResponseTypes. + */ + public java.util.List getAcceptedResponseTypesValueList() { + return java.util.Collections.unmodifiableList(acceptedResponseTypes_); + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param index The index of the value to return. + * @return The enum numeric value on the wire of acceptedResponseTypes at the given + * index. + */ + public int getAcceptedResponseTypesValue(int index) { + return acceptedResponseTypes_.get(index); + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param index The index to set the value at. + * @param value The enum numeric value on the wire for acceptedResponseTypes to set. + * @return This builder for chaining. + */ + public Builder setAcceptedResponseTypesValue(int index, int value) { + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.set(index, value); + onChanged(); + return this; + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param value The enum numeric value on the wire for acceptedResponseTypes to add. + * @return This builder for chaining. + */ + public Builder addAcceptedResponseTypesValue(int value) { + ensureAcceptedResponseTypesIsMutable(); + acceptedResponseTypes_.add(value); + onChanged(); + return this; + } + + /** + * + * + *
+             * accepted_response_types allows negotiating the content type of the response.
+             *
+             * Response types are taken from the list in the FIFO order. If no response type in `accepted_response_types` is
+             * implemented by server, error is returned.
+             * For request that do not contain `accepted_response_types` field the SAMPLES response type will be used.
+             * 
+ * + * repeated .prometheus.ReadRequest.ResponseType accepted_response_types = 2; + * + * + * @param values The enum numeric values on the wire for acceptedResponseTypes to add. + * @return This builder for chaining. + */ + public Builder addAllAcceptedResponseTypesValue(Iterable values) { + ensureAcceptedResponseTypesIsMutable(); + for (int value : values) { + acceptedResponseTypes_.add(value); + } + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.ReadRequest) + } + + // @@protoc_insertion_point(class_scope:prometheus.ReadRequest) + private static final Remote.ReadRequest DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Remote.ReadRequest(); + } + + public static Remote.ReadRequest getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public ReadRequest parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Remote.ReadRequest getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface ReadResponseOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.ReadResponse) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + java.util.List getResultsList(); + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + Remote.QueryResult getResults(int index); + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + int getResultsCount(); + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + java.util.List getResultsOrBuilderList(); + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + Remote.QueryResultOrBuilder getResultsOrBuilder(int index); + } + + /** + * + * + *
+     * ReadResponse is a response when response_type equals SAMPLES.
+     * 
+ * + *

Protobuf type {@code prometheus.ReadResponse} + */ + public static final class ReadResponse extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.ReadResponse) + ReadResponseOrBuilder { + private static final long serialVersionUID = 0L; + + // Use ReadResponse.newBuilder() to construct. + private ReadResponse(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private ReadResponse() { + results_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new ReadResponse(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_ReadResponse_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_ReadResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.ReadResponse.class, Remote.ReadResponse.Builder.class); + } + + public static final int RESULTS_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List results_; + + /** + * + * + *

+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + @Override + public java.util.List getResultsList() { + return results_; + } + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + @Override + public java.util.List getResultsOrBuilderList() { + return results_; + } + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + @Override + public int getResultsCount() { + return results_.size(); + } + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + @Override + public Remote.QueryResult getResults(int index) { + return results_.get(index); + } + + /** + * + * + *
+         * In same order as the request's queries.
+         * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + @Override + public Remote.QueryResultOrBuilder getResultsOrBuilder(int index) { + return results_.get(index); + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < results_.size(); i++) { + output.writeMessage(1, results_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < results_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 1, results_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Remote.ReadResponse)) { + return super.equals(obj); + } + Remote.ReadResponse other = (Remote.ReadResponse) obj; + + if (!getResultsList().equals(other.getResultsList())) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getResultsCount() > 0) { + hash = (37 * hash) + RESULTS_FIELD_NUMBER; + hash = (53 * hash) + getResultsList().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Remote.ReadResponse parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ReadResponse parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ReadResponse parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ReadResponse parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ReadResponse parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ReadResponse parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ReadResponse parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.ReadResponse parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.ReadResponse parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Remote.ReadResponse parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.ReadResponse parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.ReadResponse parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Remote.ReadResponse prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** + * + * + *
+         * ReadResponse is a response when response_type equals SAMPLES.
+         * 
+ * + *

Protobuf type {@code prometheus.ReadResponse} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.ReadResponse) + Remote.ReadResponseOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_ReadResponse_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_ReadResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.ReadResponse.class, Remote.ReadResponse.Builder.class); + } + + // Construct using Remote.ReadResponse.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (resultsBuilder_ == null) { + results_ = java.util.Collections.emptyList(); + } else { + results_ = null; + resultsBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Remote.internal_static_prometheus_ReadResponse_descriptor; + } + + @Override + public Remote.ReadResponse getDefaultInstanceForType() { + return Remote.ReadResponse.getDefaultInstance(); + } + + @Override + public Remote.ReadResponse build() { + Remote.ReadResponse result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Remote.ReadResponse buildPartial() { + Remote.ReadResponse result = new Remote.ReadResponse(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Remote.ReadResponse result) { + if (resultsBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + results_ = java.util.Collections.unmodifiableList(results_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.results_ = results_; + } else { + result.results_ = resultsBuilder_.build(); + } + } + + private void buildPartial0(Remote.ReadResponse result) { + int from_bitField0_ = bitField0_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Remote.ReadResponse) { + return mergeFrom((Remote.ReadResponse) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Remote.ReadResponse other) { + if (other == Remote.ReadResponse.getDefaultInstance()) { + return this; + } + if (resultsBuilder_ == null) { + if (!other.results_.isEmpty()) { + if (results_.isEmpty()) { + results_ = other.results_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureResultsIsMutable(); + results_.addAll(other.results_); + } + onChanged(); + } + } else { + if (!other.results_.isEmpty()) { + if (resultsBuilder_.isEmpty()) { + resultsBuilder_.dispose(); + resultsBuilder_ = null; + results_ = other.results_; + bitField0_ = (bitField0_ & ~0x00000001); + resultsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getResultsFieldBuilder() + : null; + } else { + resultsBuilder_.addAllMessages(other.results_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Remote.QueryResult m = + input.readMessage( + Remote.QueryResult.parser(), extensionRegistry); + if (resultsBuilder_ == null) { + ensureResultsIsMutable(); + results_.add(m); + } else { + resultsBuilder_.addMessage(m); + } + break; + } // case 10 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List results_ = java.util.Collections.emptyList(); + + private void ensureResultsIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + results_ = new java.util.ArrayList(results_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Remote.QueryResult, + Remote.QueryResult.Builder, + Remote.QueryResultOrBuilder> + resultsBuilder_; + + /** + * + * + *

+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public java.util.List getResultsList() { + if (resultsBuilder_ == null) { + return java.util.Collections.unmodifiableList(results_); + } else { + return resultsBuilder_.getMessageList(); + } + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public int getResultsCount() { + if (resultsBuilder_ == null) { + return results_.size(); + } else { + return resultsBuilder_.getCount(); + } + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Remote.QueryResult getResults(int index) { + if (resultsBuilder_ == null) { + return results_.get(index); + } else { + return resultsBuilder_.getMessage(index); + } + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder setResults(int index, Remote.QueryResult value) { + if (resultsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureResultsIsMutable(); + results_.set(index, value); + onChanged(); + } else { + resultsBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder setResults(int index, Remote.QueryResult.Builder builderForValue) { + if (resultsBuilder_ == null) { + ensureResultsIsMutable(); + results_.set(index, builderForValue.build()); + onChanged(); + } else { + resultsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder addResults(Remote.QueryResult value) { + if (resultsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureResultsIsMutable(); + results_.add(value); + onChanged(); + } else { + resultsBuilder_.addMessage(value); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder addResults(int index, Remote.QueryResult value) { + if (resultsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureResultsIsMutable(); + results_.add(index, value); + onChanged(); + } else { + resultsBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder addResults(Remote.QueryResult.Builder builderForValue) { + if (resultsBuilder_ == null) { + ensureResultsIsMutable(); + results_.add(builderForValue.build()); + onChanged(); + } else { + resultsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder addResults(int index, Remote.QueryResult.Builder builderForValue) { + if (resultsBuilder_ == null) { + ensureResultsIsMutable(); + results_.add(index, builderForValue.build()); + onChanged(); + } else { + resultsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder addAllResults(Iterable values) { + if (resultsBuilder_ == null) { + ensureResultsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, results_); + onChanged(); + } else { + resultsBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder clearResults() { + if (resultsBuilder_ == null) { + results_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + resultsBuilder_.clear(); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Builder removeResults(int index) { + if (resultsBuilder_ == null) { + ensureResultsIsMutable(); + results_.remove(index); + onChanged(); + } else { + resultsBuilder_.remove(index); + } + return this; + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Remote.QueryResult.Builder getResultsBuilder(int index) { + return getResultsFieldBuilder().getBuilder(index); + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Remote.QueryResultOrBuilder getResultsOrBuilder(int index) { + if (resultsBuilder_ == null) { + return results_.get(index); + } else { + return resultsBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public java.util.List getResultsOrBuilderList() { + if (resultsBuilder_ != null) { + return resultsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(results_); + } + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Remote.QueryResult.Builder addResultsBuilder() { + return getResultsFieldBuilder().addBuilder(Remote.QueryResult.getDefaultInstance()); + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public Remote.QueryResult.Builder addResultsBuilder(int index) { + return getResultsFieldBuilder() + .addBuilder(index, Remote.QueryResult.getDefaultInstance()); + } + + /** + * + * + *
+             * In same order as the request's queries.
+             * 
+ * + * repeated .prometheus.QueryResult results = 1; + */ + public java.util.List getResultsBuilderList() { + return getResultsFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Remote.QueryResult, + Remote.QueryResult.Builder, + Remote.QueryResultOrBuilder> + getResultsFieldBuilder() { + if (resultsBuilder_ == null) { + resultsBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Remote.QueryResult, + Remote.QueryResult.Builder, + Remote.QueryResultOrBuilder>( + results_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + results_ = null; + } + return resultsBuilder_; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.ReadResponse) + } + + // @@protoc_insertion_point(class_scope:prometheus.ReadResponse) + private static final Remote.ReadResponse DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Remote.ReadResponse(); + } + + public static Remote.ReadResponse getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public ReadResponse parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Remote.ReadResponse getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface QueryOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.Query) + com.google.protobuf.MessageOrBuilder { + + /** + * int64 start_timestamp_ms = 1; + * + * @return The startTimestampMs. + */ + long getStartTimestampMs(); + + /** + * int64 end_timestamp_ms = 2; + * + * @return The endTimestampMs. + */ + long getEndTimestampMs(); + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + java.util.List getMatchersList(); + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + Types.LabelMatcher getMatchers(int index); + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + int getMatchersCount(); + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + java.util.List getMatchersOrBuilderList(); + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + Types.LabelMatcherOrBuilder getMatchersOrBuilder(int index); + + /** + * .prometheus.ReadHints hints = 4; + * + * @return Whether the hints field is set. + */ + boolean hasHints(); + + /** + * .prometheus.ReadHints hints = 4; + * + * @return The hints. + */ + Types.ReadHints getHints(); + + /** .prometheus.ReadHints hints = 4; */ + Types.ReadHintsOrBuilder getHintsOrBuilder(); + } + + /** Protobuf type {@code prometheus.Query} */ + public static final class Query extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.Query) + QueryOrBuilder { + private static final long serialVersionUID = 0L; + + // Use Query.newBuilder() to construct. + private Query(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private Query() { + matchers_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new Query(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_Query_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_Query_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.Query.class, Remote.Query.Builder.class); + } + + private int bitField0_; + public static final int START_TIMESTAMP_MS_FIELD_NUMBER = 1; + private long startTimestampMs_ = 0L; + + /** + * int64 start_timestamp_ms = 1; + * + * @return The startTimestampMs. + */ + @Override + public long getStartTimestampMs() { + return startTimestampMs_; + } + + public static final int END_TIMESTAMP_MS_FIELD_NUMBER = 2; + private long endTimestampMs_ = 0L; + + /** + * int64 end_timestamp_ms = 2; + * + * @return The endTimestampMs. + */ + @Override + public long getEndTimestampMs() { + return endTimestampMs_; + } + + public static final int MATCHERS_FIELD_NUMBER = 3; + + @SuppressWarnings("serial") + private java.util.List matchers_; + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + @Override + public java.util.List getMatchersList() { + return matchers_; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + @Override + public java.util.List getMatchersOrBuilderList() { + return matchers_; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + @Override + public int getMatchersCount() { + return matchers_.size(); + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + @Override + public Types.LabelMatcher getMatchers(int index) { + return matchers_.get(index); + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + @Override + public Types.LabelMatcherOrBuilder getMatchersOrBuilder(int index) { + return matchers_.get(index); + } + + public static final int HINTS_FIELD_NUMBER = 4; + private Types.ReadHints hints_; + + /** + * .prometheus.ReadHints hints = 4; + * + * @return Whether the hints field is set. + */ + @Override + public boolean hasHints() { + return ((bitField0_ & 0x00000001) != 0); + } + + /** + * .prometheus.ReadHints hints = 4; + * + * @return The hints. + */ + @Override + public Types.ReadHints getHints() { + return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_; + } + + /** .prometheus.ReadHints hints = 4; */ + @Override + public Types.ReadHintsOrBuilder getHintsOrBuilder() { + return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_; + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (startTimestampMs_ != 0L) { + output.writeInt64(1, startTimestampMs_); + } + if (endTimestampMs_ != 0L) { + output.writeInt64(2, endTimestampMs_); + } + for (int i = 0; i < matchers_.size(); i++) { + output.writeMessage(3, matchers_.get(i)); + } + if (((bitField0_ & 0x00000001) != 0)) { + output.writeMessage(4, getHints()); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + if (startTimestampMs_ != 0L) { + size += + com.google.protobuf.CodedOutputStream.computeInt64Size( + 1, startTimestampMs_); + } + if (endTimestampMs_ != 0L) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, endTimestampMs_); + } + for (int i = 0; i < matchers_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 3, matchers_.get(i)); + } + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(4, getHints()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Remote.Query)) { + return super.equals(obj); + } + Remote.Query other = (Remote.Query) obj; + + if (getStartTimestampMs() != other.getStartTimestampMs()) { + return false; + } + if (getEndTimestampMs() != other.getEndTimestampMs()) { + return false; + } + if (!getMatchersList().equals(other.getMatchersList())) { + return false; + } + if (hasHints() != other.hasHints()) { + return false; + } + if (hasHints()) { + if (!getHints().equals(other.getHints())) { + return false; + } + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + START_TIMESTAMP_MS_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getStartTimestampMs()); + hash = (37 * hash) + END_TIMESTAMP_MS_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getEndTimestampMs()); + if (getMatchersCount() > 0) { + hash = (37 * hash) + MATCHERS_FIELD_NUMBER; + hash = (53 * hash) + getMatchersList().hashCode(); + } + if (hasHints()) { + hash = (37 * hash) + HINTS_FIELD_NUMBER; + hash = (53 * hash) + getHints().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Remote.Query parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.Query parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.Query parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.Query parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.Query parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.Query parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.Query parseFrom(java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.Query parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.Query parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Remote.Query parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.Query parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.Query parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Remote.Query prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.Query} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.Query) + Remote.QueryOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_Query_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_Query_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.Query.class, Remote.Query.Builder.class); + } + + // Construct using Remote.Query.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders) { + getMatchersFieldBuilder(); + getHintsFieldBuilder(); + } + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + startTimestampMs_ = 0L; + endTimestampMs_ = 0L; + if (matchersBuilder_ == null) { + matchers_ = java.util.Collections.emptyList(); + } else { + matchers_ = null; + matchersBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + hints_ = null; + if (hintsBuilder_ != null) { + hintsBuilder_.dispose(); + hintsBuilder_ = null; + } + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Remote.internal_static_prometheus_Query_descriptor; + } + + @Override + public Remote.Query getDefaultInstanceForType() { + return Remote.Query.getDefaultInstance(); + } + + @Override + public Remote.Query build() { + Remote.Query result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Remote.Query buildPartial() { + Remote.Query result = new Remote.Query(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Remote.Query result) { + if (matchersBuilder_ == null) { + if (((bitField0_ & 0x00000004) != 0)) { + matchers_ = java.util.Collections.unmodifiableList(matchers_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.matchers_ = matchers_; + } else { + result.matchers_ = matchersBuilder_.build(); + } + } + + private void buildPartial0(Remote.Query result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.startTimestampMs_ = startTimestampMs_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.endTimestampMs_ = endTimestampMs_; + } + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000008) != 0)) { + result.hints_ = hintsBuilder_ == null ? hints_ : hintsBuilder_.build(); + to_bitField0_ |= 0x00000001; + } + result.bitField0_ |= to_bitField0_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Remote.Query) { + return mergeFrom((Remote.Query) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Remote.Query other) { + if (other == Remote.Query.getDefaultInstance()) { + return this; + } + if (other.getStartTimestampMs() != 0L) { + setStartTimestampMs(other.getStartTimestampMs()); + } + if (other.getEndTimestampMs() != 0L) { + setEndTimestampMs(other.getEndTimestampMs()); + } + if (matchersBuilder_ == null) { + if (!other.matchers_.isEmpty()) { + if (matchers_.isEmpty()) { + matchers_ = other.matchers_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureMatchersIsMutable(); + matchers_.addAll(other.matchers_); + } + onChanged(); + } + } else { + if (!other.matchers_.isEmpty()) { + if (matchersBuilder_.isEmpty()) { + matchersBuilder_.dispose(); + matchersBuilder_ = null; + matchers_ = other.matchers_; + bitField0_ = (bitField0_ & ~0x00000004); + matchersBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getMatchersFieldBuilder() + : null; + } else { + matchersBuilder_.addAllMessages(other.matchers_); + } + } + } + if (other.hasHints()) { + mergeHints(other.getHints()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + startTimestampMs_ = input.readInt64(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 16: + { + endTimestampMs_ = input.readInt64(); + bitField0_ |= 0x00000002; + break; + } // case 16 + case 26: + { + Types.LabelMatcher m = + input.readMessage( + Types.LabelMatcher.parser(), extensionRegistry); + if (matchersBuilder_ == null) { + ensureMatchersIsMutable(); + matchers_.add(m); + } else { + matchersBuilder_.addMessage(m); + } + break; + } // case 26 + case 34: + { + input.readMessage( + getHintsFieldBuilder().getBuilder(), extensionRegistry); + bitField0_ |= 0x00000008; + break; + } // case 34 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private long startTimestampMs_; + + /** + * int64 start_timestamp_ms = 1; + * + * @return The startTimestampMs. + */ + @Override + public long getStartTimestampMs() { + return startTimestampMs_; + } + + /** + * int64 start_timestamp_ms = 1; + * + * @param value The startTimestampMs to set. + * @return This builder for chaining. + */ + public Builder setStartTimestampMs(long value) { + + startTimestampMs_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + /** + * int64 start_timestamp_ms = 1; + * + * @return This builder for chaining. + */ + public Builder clearStartTimestampMs() { + bitField0_ = (bitField0_ & ~0x00000001); + startTimestampMs_ = 0L; + onChanged(); + return this; + } + + private long endTimestampMs_; + + /** + * int64 end_timestamp_ms = 2; + * + * @return The endTimestampMs. + */ + @Override + public long getEndTimestampMs() { + return endTimestampMs_; + } + + /** + * int64 end_timestamp_ms = 2; + * + * @param value The endTimestampMs to set. + * @return This builder for chaining. + */ + public Builder setEndTimestampMs(long value) { + + endTimestampMs_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * int64 end_timestamp_ms = 2; + * + * @return This builder for chaining. + */ + public Builder clearEndTimestampMs() { + bitField0_ = (bitField0_ & ~0x00000002); + endTimestampMs_ = 0L; + onChanged(); + return this; + } + + private java.util.List matchers_ = + java.util.Collections.emptyList(); + + private void ensureMatchersIsMutable() { + if (!((bitField0_ & 0x00000004) != 0)) { + matchers_ = new java.util.ArrayList(matchers_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.LabelMatcher, + Types.LabelMatcher.Builder, + Types.LabelMatcherOrBuilder> + matchersBuilder_; + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public java.util.List getMatchersList() { + if (matchersBuilder_ == null) { + return java.util.Collections.unmodifiableList(matchers_); + } else { + return matchersBuilder_.getMessageList(); + } + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public int getMatchersCount() { + if (matchersBuilder_ == null) { + return matchers_.size(); + } else { + return matchersBuilder_.getCount(); + } + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Types.LabelMatcher getMatchers(int index) { + if (matchersBuilder_ == null) { + return matchers_.get(index); + } else { + return matchersBuilder_.getMessage(index); + } + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder setMatchers(int index, Types.LabelMatcher value) { + if (matchersBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMatchersIsMutable(); + matchers_.set(index, value); + onChanged(); + } else { + matchersBuilder_.setMessage(index, value); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder setMatchers(int index, Types.LabelMatcher.Builder builderForValue) { + if (matchersBuilder_ == null) { + ensureMatchersIsMutable(); + matchers_.set(index, builderForValue.build()); + onChanged(); + } else { + matchersBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder addMatchers(Types.LabelMatcher value) { + if (matchersBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMatchersIsMutable(); + matchers_.add(value); + onChanged(); + } else { + matchersBuilder_.addMessage(value); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder addMatchers(int index, Types.LabelMatcher value) { + if (matchersBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMatchersIsMutable(); + matchers_.add(index, value); + onChanged(); + } else { + matchersBuilder_.addMessage(index, value); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder addMatchers(Types.LabelMatcher.Builder builderForValue) { + if (matchersBuilder_ == null) { + ensureMatchersIsMutable(); + matchers_.add(builderForValue.build()); + onChanged(); + } else { + matchersBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder addMatchers(int index, Types.LabelMatcher.Builder builderForValue) { + if (matchersBuilder_ == null) { + ensureMatchersIsMutable(); + matchers_.add(index, builderForValue.build()); + onChanged(); + } else { + matchersBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder addAllMatchers(Iterable values) { + if (matchersBuilder_ == null) { + ensureMatchersIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, matchers_); + onChanged(); + } else { + matchersBuilder_.addAllMessages(values); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder clearMatchers() { + if (matchersBuilder_ == null) { + matchers_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + matchersBuilder_.clear(); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Builder removeMatchers(int index) { + if (matchersBuilder_ == null) { + ensureMatchersIsMutable(); + matchers_.remove(index); + onChanged(); + } else { + matchersBuilder_.remove(index); + } + return this; + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Types.LabelMatcher.Builder getMatchersBuilder(int index) { + return getMatchersFieldBuilder().getBuilder(index); + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Types.LabelMatcherOrBuilder getMatchersOrBuilder(int index) { + if (matchersBuilder_ == null) { + return matchers_.get(index); + } else { + return matchersBuilder_.getMessageOrBuilder(index); + } + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public java.util.List + getMatchersOrBuilderList() { + if (matchersBuilder_ != null) { + return matchersBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(matchers_); + } + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Types.LabelMatcher.Builder addMatchersBuilder() { + return getMatchersFieldBuilder() + .addBuilder(Types.LabelMatcher.getDefaultInstance()); + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public Types.LabelMatcher.Builder addMatchersBuilder(int index) { + return getMatchersFieldBuilder() + .addBuilder(index, Types.LabelMatcher.getDefaultInstance()); + } + + /** repeated .prometheus.LabelMatcher matchers = 3; */ + public java.util.List getMatchersBuilderList() { + return getMatchersFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.LabelMatcher, + Types.LabelMatcher.Builder, + Types.LabelMatcherOrBuilder> + getMatchersFieldBuilder() { + if (matchersBuilder_ == null) { + matchersBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.LabelMatcher, + Types.LabelMatcher.Builder, + Types.LabelMatcherOrBuilder>( + matchers_, + ((bitField0_ & 0x00000004) != 0), + getParentForChildren(), + isClean()); + matchers_ = null; + } + return matchersBuilder_; + } + + private Types.ReadHints hints_; + private com.google.protobuf.SingleFieldBuilderV3< + Types.ReadHints, Types.ReadHints.Builder, Types.ReadHintsOrBuilder> + hintsBuilder_; + + /** + * .prometheus.ReadHints hints = 4; + * + * @return Whether the hints field is set. + */ + public boolean hasHints() { + return ((bitField0_ & 0x00000008) != 0); + } + + /** + * .prometheus.ReadHints hints = 4; + * + * @return The hints. + */ + public Types.ReadHints getHints() { + if (hintsBuilder_ == null) { + return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_; + } else { + return hintsBuilder_.getMessage(); + } + } + + /** .prometheus.ReadHints hints = 4; */ + public Builder setHints(Types.ReadHints value) { + if (hintsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + hints_ = value; + } else { + hintsBuilder_.setMessage(value); + } + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + /** .prometheus.ReadHints hints = 4; */ + public Builder setHints(Types.ReadHints.Builder builderForValue) { + if (hintsBuilder_ == null) { + hints_ = builderForValue.build(); + } else { + hintsBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + /** .prometheus.ReadHints hints = 4; */ + public Builder mergeHints(Types.ReadHints value) { + if (hintsBuilder_ == null) { + if (((bitField0_ & 0x00000008) != 0) + && hints_ != null + && hints_ != Types.ReadHints.getDefaultInstance()) { + getHintsBuilder().mergeFrom(value); + } else { + hints_ = value; + } + } else { + hintsBuilder_.mergeFrom(value); + } + if (hints_ != null) { + bitField0_ |= 0x00000008; + onChanged(); + } + return this; + } + + /** .prometheus.ReadHints hints = 4; */ + public Builder clearHints() { + bitField0_ = (bitField0_ & ~0x00000008); + hints_ = null; + if (hintsBuilder_ != null) { + hintsBuilder_.dispose(); + hintsBuilder_ = null; + } + onChanged(); + return this; + } + + /** .prometheus.ReadHints hints = 4; */ + public Types.ReadHints.Builder getHintsBuilder() { + bitField0_ |= 0x00000008; + onChanged(); + return getHintsFieldBuilder().getBuilder(); + } + + /** .prometheus.ReadHints hints = 4; */ + public Types.ReadHintsOrBuilder getHintsOrBuilder() { + if (hintsBuilder_ != null) { + return hintsBuilder_.getMessageOrBuilder(); + } else { + return hints_ == null ? Types.ReadHints.getDefaultInstance() : hints_; + } + } + + /** .prometheus.ReadHints hints = 4; */ + private com.google.protobuf.SingleFieldBuilderV3< + Types.ReadHints, Types.ReadHints.Builder, Types.ReadHintsOrBuilder> + getHintsFieldBuilder() { + if (hintsBuilder_ == null) { + hintsBuilder_ = + new com.google.protobuf.SingleFieldBuilderV3< + Types.ReadHints, + Types.ReadHints.Builder, + Types.ReadHintsOrBuilder>( + getHints(), getParentForChildren(), isClean()); + hints_ = null; + } + return hintsBuilder_; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.Query) + } + + // @@protoc_insertion_point(class_scope:prometheus.Query) + private static final Remote.Query DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Remote.Query(); + } + + public static Remote.Query getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public Query parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Remote.Query getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface QueryResultOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.QueryResult) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + java.util.List getTimeseriesList(); + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + Types.TimeSeries getTimeseries(int index); + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + int getTimeseriesCount(); + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + java.util.List getTimeseriesOrBuilderList(); + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index); + } + + /** Protobuf type {@code prometheus.QueryResult} */ + public static final class QueryResult extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.QueryResult) + QueryResultOrBuilder { + private static final long serialVersionUID = 0L; + + // Use QueryResult.newBuilder() to construct. + private QueryResult(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private QueryResult() { + timeseries_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new QueryResult(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_QueryResult_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_QueryResult_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.QueryResult.class, Remote.QueryResult.Builder.class); + } + + public static final int TIMESERIES_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List timeseries_; + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + @Override + public java.util.List getTimeseriesList() { + return timeseries_; + } + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + @Override + public java.util.List getTimeseriesOrBuilderList() { + return timeseries_; + } + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + @Override + public int getTimeseriesCount() { + return timeseries_.size(); + } + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + @Override + public Types.TimeSeries getTimeseries(int index) { + return timeseries_.get(index); + } + + /** + * + * + *
+         * Samples within a time series must be ordered by time.
+         * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + @Override + public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) { + return timeseries_.get(index); + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < timeseries_.size(); i++) { + output.writeMessage(1, timeseries_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < timeseries_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 1, timeseries_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Remote.QueryResult)) { + return super.equals(obj); + } + Remote.QueryResult other = (Remote.QueryResult) obj; + + if (!getTimeseriesList().equals(other.getTimeseriesList())) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getTimeseriesCount() > 0) { + hash = (37 * hash) + TIMESERIES_FIELD_NUMBER; + hash = (53 * hash) + getTimeseriesList().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Remote.QueryResult parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.QueryResult parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.QueryResult parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.QueryResult parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.QueryResult parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.QueryResult parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.QueryResult parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.QueryResult parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.QueryResult parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Remote.QueryResult parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.QueryResult parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.QueryResult parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Remote.QueryResult prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.QueryResult} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.QueryResult) + Remote.QueryResultOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_QueryResult_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_QueryResult_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.QueryResult.class, Remote.QueryResult.Builder.class); + } + + // Construct using Remote.QueryResult.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (timeseriesBuilder_ == null) { + timeseries_ = java.util.Collections.emptyList(); + } else { + timeseries_ = null; + timeseriesBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Remote.internal_static_prometheus_QueryResult_descriptor; + } + + @Override + public Remote.QueryResult getDefaultInstanceForType() { + return Remote.QueryResult.getDefaultInstance(); + } + + @Override + public Remote.QueryResult build() { + Remote.QueryResult result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Remote.QueryResult buildPartial() { + Remote.QueryResult result = new Remote.QueryResult(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Remote.QueryResult result) { + if (timeseriesBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + timeseries_ = java.util.Collections.unmodifiableList(timeseries_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.timeseries_ = timeseries_; + } else { + result.timeseries_ = timeseriesBuilder_.build(); + } + } + + private void buildPartial0(Remote.QueryResult result) { + int from_bitField0_ = bitField0_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Remote.QueryResult) { + return mergeFrom((Remote.QueryResult) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Remote.QueryResult other) { + if (other == Remote.QueryResult.getDefaultInstance()) { + return this; + } + if (timeseriesBuilder_ == null) { + if (!other.timeseries_.isEmpty()) { + if (timeseries_.isEmpty()) { + timeseries_ = other.timeseries_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureTimeseriesIsMutable(); + timeseries_.addAll(other.timeseries_); + } + onChanged(); + } + } else { + if (!other.timeseries_.isEmpty()) { + if (timeseriesBuilder_.isEmpty()) { + timeseriesBuilder_.dispose(); + timeseriesBuilder_ = null; + timeseries_ = other.timeseries_; + bitField0_ = (bitField0_ & ~0x00000001); + timeseriesBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getTimeseriesFieldBuilder() + : null; + } else { + timeseriesBuilder_.addAllMessages(other.timeseries_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Types.TimeSeries m = + input.readMessage( + Types.TimeSeries.parser(), extensionRegistry); + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.add(m); + } else { + timeseriesBuilder_.addMessage(m); + } + break; + } // case 10 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List timeseries_ = + java.util.Collections.emptyList(); + + private void ensureTimeseriesIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + timeseries_ = new java.util.ArrayList(timeseries_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder> + timeseriesBuilder_; + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public java.util.List getTimeseriesList() { + if (timeseriesBuilder_ == null) { + return java.util.Collections.unmodifiableList(timeseries_); + } else { + return timeseriesBuilder_.getMessageList(); + } + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public int getTimeseriesCount() { + if (timeseriesBuilder_ == null) { + return timeseries_.size(); + } else { + return timeseriesBuilder_.getCount(); + } + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Types.TimeSeries getTimeseries(int index) { + if (timeseriesBuilder_ == null) { + return timeseries_.get(index); + } else { + return timeseriesBuilder_.getMessage(index); + } + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder setTimeseries(int index, Types.TimeSeries value) { + if (timeseriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureTimeseriesIsMutable(); + timeseries_.set(index, value); + onChanged(); + } else { + timeseriesBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder setTimeseries(int index, Types.TimeSeries.Builder builderForValue) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.set(index, builderForValue.build()); + onChanged(); + } else { + timeseriesBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder addTimeseries(Types.TimeSeries value) { + if (timeseriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureTimeseriesIsMutable(); + timeseries_.add(value); + onChanged(); + } else { + timeseriesBuilder_.addMessage(value); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder addTimeseries(int index, Types.TimeSeries value) { + if (timeseriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureTimeseriesIsMutable(); + timeseries_.add(index, value); + onChanged(); + } else { + timeseriesBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder addTimeseries(Types.TimeSeries.Builder builderForValue) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.add(builderForValue.build()); + onChanged(); + } else { + timeseriesBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder addTimeseries(int index, Types.TimeSeries.Builder builderForValue) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.add(index, builderForValue.build()); + onChanged(); + } else { + timeseriesBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder addAllTimeseries(Iterable values) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, timeseries_); + onChanged(); + } else { + timeseriesBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder clearTimeseries() { + if (timeseriesBuilder_ == null) { + timeseries_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + timeseriesBuilder_.clear(); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Builder removeTimeseries(int index) { + if (timeseriesBuilder_ == null) { + ensureTimeseriesIsMutable(); + timeseries_.remove(index); + onChanged(); + } else { + timeseriesBuilder_.remove(index); + } + return this; + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Types.TimeSeries.Builder getTimeseriesBuilder(int index) { + return getTimeseriesFieldBuilder().getBuilder(index); + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Types.TimeSeriesOrBuilder getTimeseriesOrBuilder(int index) { + if (timeseriesBuilder_ == null) { + return timeseries_.get(index); + } else { + return timeseriesBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public java.util.List + getTimeseriesOrBuilderList() { + if (timeseriesBuilder_ != null) { + return timeseriesBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(timeseries_); + } + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Types.TimeSeries.Builder addTimeseriesBuilder() { + return getTimeseriesFieldBuilder() + .addBuilder(Types.TimeSeries.getDefaultInstance()); + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public Types.TimeSeries.Builder addTimeseriesBuilder(int index) { + return getTimeseriesFieldBuilder() + .addBuilder(index, Types.TimeSeries.getDefaultInstance()); + } + + /** + * + * + *
+             * Samples within a time series must be ordered by time.
+             * 
+ * + * repeated .prometheus.TimeSeries timeseries = 1; + */ + public java.util.List getTimeseriesBuilderList() { + return getTimeseriesFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.TimeSeries, Types.TimeSeries.Builder, Types.TimeSeriesOrBuilder> + getTimeseriesFieldBuilder() { + if (timeseriesBuilder_ == null) { + timeseriesBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.TimeSeries, + Types.TimeSeries.Builder, + Types.TimeSeriesOrBuilder>( + timeseries_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + timeseries_ = null; + } + return timeseriesBuilder_; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.QueryResult) + } + + // @@protoc_insertion_point(class_scope:prometheus.QueryResult) + private static final Remote.QueryResult DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Remote.QueryResult(); + } + + public static Remote.QueryResult getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public QueryResult parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Remote.QueryResult getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface ChunkedReadResponseOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.ChunkedReadResponse) + com.google.protobuf.MessageOrBuilder { + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + java.util.List getChunkedSeriesList(); + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + Types.ChunkedSeries getChunkedSeries(int index); + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + int getChunkedSeriesCount(); + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + java.util.List getChunkedSeriesOrBuilderList(); + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + Types.ChunkedSeriesOrBuilder getChunkedSeriesOrBuilder(int index); + + /** + * + * + *
+         * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+         * 
+ * + * int64 query_index = 2; + * + * @return The queryIndex. + */ + long getQueryIndex(); + } + + /** + * + * + *
+     * ChunkedReadResponse is a response when response_type equals STREAMED_XOR_CHUNKS.
+     * We strictly stream full series after series, optionally split by time. This means that a single frame can contain
+     * partition of the single series, but once a new series is started to be streamed it means that no more chunks will
+     * be sent for previous one. Series are returned sorted in the same way TSDB block are internally.
+     * 
+ * + *

Protobuf type {@code prometheus.ChunkedReadResponse} + */ + public static final class ChunkedReadResponse extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.ChunkedReadResponse) + ChunkedReadResponseOrBuilder { + private static final long serialVersionUID = 0L; + + // Use ChunkedReadResponse.newBuilder() to construct. + private ChunkedReadResponse(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private ChunkedReadResponse() { + chunkedSeries_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new ChunkedReadResponse(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_ChunkedReadResponse_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_ChunkedReadResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.ChunkedReadResponse.class, + Remote.ChunkedReadResponse.Builder.class); + } + + public static final int CHUNKED_SERIES_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List chunkedSeries_; + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + @Override + public java.util.List getChunkedSeriesList() { + return chunkedSeries_; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + @Override + public java.util.List + getChunkedSeriesOrBuilderList() { + return chunkedSeries_; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + @Override + public int getChunkedSeriesCount() { + return chunkedSeries_.size(); + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + @Override + public Types.ChunkedSeries getChunkedSeries(int index) { + return chunkedSeries_.get(index); + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + @Override + public Types.ChunkedSeriesOrBuilder getChunkedSeriesOrBuilder(int index) { + return chunkedSeries_.get(index); + } + + public static final int QUERY_INDEX_FIELD_NUMBER = 2; + private long queryIndex_ = 0L; + + /** + * + * + *

+         * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+         * 
+ * + * int64 query_index = 2; + * + * @return The queryIndex. + */ + @Override + public long getQueryIndex() { + return queryIndex_; + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < chunkedSeries_.size(); i++) { + output.writeMessage(1, chunkedSeries_.get(i)); + } + if (queryIndex_ != 0L) { + output.writeInt64(2, queryIndex_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < chunkedSeries_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 1, chunkedSeries_.get(i)); + } + if (queryIndex_ != 0L) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, queryIndex_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Remote.ChunkedReadResponse)) { + return super.equals(obj); + } + Remote.ChunkedReadResponse other = (Remote.ChunkedReadResponse) obj; + + if (!getChunkedSeriesList().equals(other.getChunkedSeriesList())) { + return false; + } + if (getQueryIndex() != other.getQueryIndex()) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getChunkedSeriesCount() > 0) { + hash = (37 * hash) + CHUNKED_SERIES_FIELD_NUMBER; + hash = (53 * hash) + getChunkedSeriesList().hashCode(); + } + hash = (37 * hash) + QUERY_INDEX_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getQueryIndex()); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Remote.ChunkedReadResponse parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ChunkedReadResponse parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ChunkedReadResponse parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ChunkedReadResponse parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ChunkedReadResponse parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Remote.ChunkedReadResponse parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Remote.ChunkedReadResponse parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.ChunkedReadResponse parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.ChunkedReadResponse parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Remote.ChunkedReadResponse parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Remote.ChunkedReadResponse parseFrom( + com.google.protobuf.CodedInputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Remote.ChunkedReadResponse parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Remote.ChunkedReadResponse prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** + * + * + *
+         * ChunkedReadResponse is a response when response_type equals STREAMED_XOR_CHUNKS.
+         * We strictly stream full series after series, optionally split by time. This means that a single frame can contain
+         * partition of the single series, but once a new series is started to be streamed it means that no more chunks will
+         * be sent for previous one. Series are returned sorted in the same way TSDB block are internally.
+         * 
+ * + *

Protobuf type {@code prometheus.ChunkedReadResponse} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.ChunkedReadResponse) + Remote.ChunkedReadResponseOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Remote.internal_static_prometheus_ChunkedReadResponse_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Remote.internal_static_prometheus_ChunkedReadResponse_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Remote.ChunkedReadResponse.class, + Remote.ChunkedReadResponse.Builder.class); + } + + // Construct using Remote.ChunkedReadResponse.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (chunkedSeriesBuilder_ == null) { + chunkedSeries_ = java.util.Collections.emptyList(); + } else { + chunkedSeries_ = null; + chunkedSeriesBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + queryIndex_ = 0L; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Remote.internal_static_prometheus_ChunkedReadResponse_descriptor; + } + + @Override + public Remote.ChunkedReadResponse getDefaultInstanceForType() { + return Remote.ChunkedReadResponse.getDefaultInstance(); + } + + @Override + public Remote.ChunkedReadResponse build() { + Remote.ChunkedReadResponse result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Remote.ChunkedReadResponse buildPartial() { + Remote.ChunkedReadResponse result = new Remote.ChunkedReadResponse(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Remote.ChunkedReadResponse result) { + if (chunkedSeriesBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + chunkedSeries_ = java.util.Collections.unmodifiableList(chunkedSeries_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.chunkedSeries_ = chunkedSeries_; + } else { + result.chunkedSeries_ = chunkedSeriesBuilder_.build(); + } + } + + private void buildPartial0(Remote.ChunkedReadResponse result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000002) != 0)) { + result.queryIndex_ = queryIndex_; + } + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Remote.ChunkedReadResponse) { + return mergeFrom((Remote.ChunkedReadResponse) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Remote.ChunkedReadResponse other) { + if (other == Remote.ChunkedReadResponse.getDefaultInstance()) { + return this; + } + if (chunkedSeriesBuilder_ == null) { + if (!other.chunkedSeries_.isEmpty()) { + if (chunkedSeries_.isEmpty()) { + chunkedSeries_ = other.chunkedSeries_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureChunkedSeriesIsMutable(); + chunkedSeries_.addAll(other.chunkedSeries_); + } + onChanged(); + } + } else { + if (!other.chunkedSeries_.isEmpty()) { + if (chunkedSeriesBuilder_.isEmpty()) { + chunkedSeriesBuilder_.dispose(); + chunkedSeriesBuilder_ = null; + chunkedSeries_ = other.chunkedSeries_; + bitField0_ = (bitField0_ & ~0x00000001); + chunkedSeriesBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getChunkedSeriesFieldBuilder() + : null; + } else { + chunkedSeriesBuilder_.addAllMessages(other.chunkedSeries_); + } + } + } + if (other.getQueryIndex() != 0L) { + setQueryIndex(other.getQueryIndex()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Types.ChunkedSeries m = + input.readMessage( + Types.ChunkedSeries.parser(), + extensionRegistry); + if (chunkedSeriesBuilder_ == null) { + ensureChunkedSeriesIsMutable(); + chunkedSeries_.add(m); + } else { + chunkedSeriesBuilder_.addMessage(m); + } + break; + } // case 10 + case 16: + { + queryIndex_ = input.readInt64(); + bitField0_ |= 0x00000002; + break; + } // case 16 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List chunkedSeries_ = + java.util.Collections.emptyList(); + + private void ensureChunkedSeriesIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + chunkedSeries_ = new java.util.ArrayList(chunkedSeries_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.ChunkedSeries, + Types.ChunkedSeries.Builder, + Types.ChunkedSeriesOrBuilder> + chunkedSeriesBuilder_; + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public java.util.List getChunkedSeriesList() { + if (chunkedSeriesBuilder_ == null) { + return java.util.Collections.unmodifiableList(chunkedSeries_); + } else { + return chunkedSeriesBuilder_.getMessageList(); + } + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public int getChunkedSeriesCount() { + if (chunkedSeriesBuilder_ == null) { + return chunkedSeries_.size(); + } else { + return chunkedSeriesBuilder_.getCount(); + } + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Types.ChunkedSeries getChunkedSeries(int index) { + if (chunkedSeriesBuilder_ == null) { + return chunkedSeries_.get(index); + } else { + return chunkedSeriesBuilder_.getMessage(index); + } + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder setChunkedSeries(int index, Types.ChunkedSeries value) { + if (chunkedSeriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureChunkedSeriesIsMutable(); + chunkedSeries_.set(index, value); + onChanged(); + } else { + chunkedSeriesBuilder_.setMessage(index, value); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder setChunkedSeries( + int index, Types.ChunkedSeries.Builder builderForValue) { + if (chunkedSeriesBuilder_ == null) { + ensureChunkedSeriesIsMutable(); + chunkedSeries_.set(index, builderForValue.build()); + onChanged(); + } else { + chunkedSeriesBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder addChunkedSeries(Types.ChunkedSeries value) { + if (chunkedSeriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureChunkedSeriesIsMutable(); + chunkedSeries_.add(value); + onChanged(); + } else { + chunkedSeriesBuilder_.addMessage(value); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder addChunkedSeries(int index, Types.ChunkedSeries value) { + if (chunkedSeriesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureChunkedSeriesIsMutable(); + chunkedSeries_.add(index, value); + onChanged(); + } else { + chunkedSeriesBuilder_.addMessage(index, value); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder addChunkedSeries(Types.ChunkedSeries.Builder builderForValue) { + if (chunkedSeriesBuilder_ == null) { + ensureChunkedSeriesIsMutable(); + chunkedSeries_.add(builderForValue.build()); + onChanged(); + } else { + chunkedSeriesBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder addChunkedSeries( + int index, Types.ChunkedSeries.Builder builderForValue) { + if (chunkedSeriesBuilder_ == null) { + ensureChunkedSeriesIsMutable(); + chunkedSeries_.add(index, builderForValue.build()); + onChanged(); + } else { + chunkedSeriesBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder addAllChunkedSeries(Iterable values) { + if (chunkedSeriesBuilder_ == null) { + ensureChunkedSeriesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, chunkedSeries_); + onChanged(); + } else { + chunkedSeriesBuilder_.addAllMessages(values); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder clearChunkedSeries() { + if (chunkedSeriesBuilder_ == null) { + chunkedSeries_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + chunkedSeriesBuilder_.clear(); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Builder removeChunkedSeries(int index) { + if (chunkedSeriesBuilder_ == null) { + ensureChunkedSeriesIsMutable(); + chunkedSeries_.remove(index); + onChanged(); + } else { + chunkedSeriesBuilder_.remove(index); + } + return this; + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Types.ChunkedSeries.Builder getChunkedSeriesBuilder(int index) { + return getChunkedSeriesFieldBuilder().getBuilder(index); + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Types.ChunkedSeriesOrBuilder getChunkedSeriesOrBuilder(int index) { + if (chunkedSeriesBuilder_ == null) { + return chunkedSeries_.get(index); + } else { + return chunkedSeriesBuilder_.getMessageOrBuilder(index); + } + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public java.util.List + getChunkedSeriesOrBuilderList() { + if (chunkedSeriesBuilder_ != null) { + return chunkedSeriesBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(chunkedSeries_); + } + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Types.ChunkedSeries.Builder addChunkedSeriesBuilder() { + return getChunkedSeriesFieldBuilder() + .addBuilder(Types.ChunkedSeries.getDefaultInstance()); + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public Types.ChunkedSeries.Builder addChunkedSeriesBuilder(int index) { + return getChunkedSeriesFieldBuilder() + .addBuilder(index, Types.ChunkedSeries.getDefaultInstance()); + } + + /** repeated .prometheus.ChunkedSeries chunked_series = 1; */ + public java.util.List getChunkedSeriesBuilderList() { + return getChunkedSeriesFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.ChunkedSeries, + Types.ChunkedSeries.Builder, + Types.ChunkedSeriesOrBuilder> + getChunkedSeriesFieldBuilder() { + if (chunkedSeriesBuilder_ == null) { + chunkedSeriesBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.ChunkedSeries, + Types.ChunkedSeries.Builder, + Types.ChunkedSeriesOrBuilder>( + chunkedSeries_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + chunkedSeries_ = null; + } + return chunkedSeriesBuilder_; + } + + private long queryIndex_; + + /** + * + * + *

+             * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+             * 
+ * + * int64 query_index = 2; + * + * @return The queryIndex. + */ + @Override + public long getQueryIndex() { + return queryIndex_; + } + + /** + * + * + *
+             * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+             * 
+ * + * int64 query_index = 2; + * + * @param value The queryIndex to set. + * @return This builder for chaining. + */ + public Builder setQueryIndex(long value) { + + queryIndex_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * + * + *
+             * query_index represents an index of the query from ReadRequest.queries these chunks relates to.
+             * 
+ * + * int64 query_index = 2; + * + * @return This builder for chaining. + */ + public Builder clearQueryIndex() { + bitField0_ = (bitField0_ & ~0x00000002); + queryIndex_ = 0L; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.ChunkedReadResponse) + } + + // @@protoc_insertion_point(class_scope:prometheus.ChunkedReadResponse) + private static final Remote.ChunkedReadResponse DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Remote.ChunkedReadResponse(); + } + + public static Remote.ChunkedReadResponse getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public ChunkedReadResponse parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Remote.ChunkedReadResponse getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_prometheus_WriteRequest_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_prometheus_WriteRequest_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_prometheus_ReadRequest_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_prometheus_ReadRequest_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_prometheus_ReadResponse_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_prometheus_ReadResponse_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_prometheus_Query_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_prometheus_Query_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_prometheus_QueryResult_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_prometheus_QueryResult_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_prometheus_ChunkedReadResponse_descriptor; + private static final com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_prometheus_ChunkedReadResponse_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { + return descriptor; + } + + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; + + static { + String[] descriptorData = { + "\n\014remote.proto\022\nprometheus\032\013types.proto\032" + + "\ngogo.proto\"z\n\014WriteRequest\0220\n\ntimeserie" + + "s\030\001 \003(\0132\026.prometheus.TimeSeriesB\004\310\336\037\000\0222\n" + + "\010metadata\030\003 \003(\0132\032.prometheus.MetricMetad" + + "ataB\004\310\336\037\000J\004\010\002\020\003\"\256\001\n\013ReadRequest\022\"\n\007queri" + + "es\030\001 \003(\0132\021.prometheus.Query\022E\n\027accepted_" + + "response_types\030\002 \003(\0162$.prometheus.ReadRe" + + "quest.ResponseType\"4\n\014ResponseType\022\013\n\007SA" + + "MPLES\020\000\022\027\n\023STREAMED_XOR_CHUNKS\020\001\"8\n\014Read" + + "Response\022(\n\007results\030\001 \003(\0132\027.prometheus.Q" + + "ueryResult\"\217\001\n\005Query\022\032\n\022start_timestamp_" + + "ms\030\001 \001(\003\022\030\n\020end_timestamp_ms\030\002 \001(\003\022*\n\010ma" + + "tchers\030\003 \003(\0132\030.prometheus.LabelMatcher\022$" + + "\n\005hints\030\004 \001(\0132\025.prometheus.ReadHints\"9\n\013" + + "QueryResult\022*\n\ntimeseries\030\001 \003(\0132\026.promet" + + "heus.TimeSeries\"]\n\023ChunkedReadResponse\0221" + + "\n\016chunked_series\030\001 \003(\0132\031.prometheus.Chun" + + "kedSeries\022\023\n\013query_index\030\002 \001(\003B\010Z\006prompb" + + "b\006proto3" + }; + descriptor = + com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( + descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + Types.getDescriptor(), GoGoProtos.getDescriptor(), + }); + internal_static_prometheus_WriteRequest_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_prometheus_WriteRequest_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_prometheus_WriteRequest_descriptor, + new String[] { + "Timeseries", "Metadata", + }); + internal_static_prometheus_ReadRequest_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_prometheus_ReadRequest_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_prometheus_ReadRequest_descriptor, + new String[] { + "Queries", "AcceptedResponseTypes", + }); + internal_static_prometheus_ReadResponse_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_prometheus_ReadResponse_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_prometheus_ReadResponse_descriptor, + new String[] { + "Results", + }); + internal_static_prometheus_Query_descriptor = getDescriptor().getMessageTypes().get(3); + internal_static_prometheus_Query_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_prometheus_Query_descriptor, + new String[] { + "StartTimestampMs", "EndTimestampMs", "Matchers", "Hints", + }); + internal_static_prometheus_QueryResult_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_prometheus_QueryResult_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_prometheus_QueryResult_descriptor, + new String[] { + "Timeseries", + }); + internal_static_prometheus_ChunkedReadResponse_descriptor = + getDescriptor().getMessageTypes().get(5); + internal_static_prometheus_ChunkedReadResponse_fieldAccessorTable = + new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_prometheus_ChunkedReadResponse_descriptor, + new String[] { + "ChunkedSeries", "QueryIndex", + }); + com.google.protobuf.ExtensionRegistry registry = + com.google.protobuf.ExtensionRegistry.newInstance(); + registry.add(GoGoProtos.nullable); + com.google.protobuf.Descriptors.FileDescriptor.internalUpdateFileDescriptor( + descriptor, registry); + Types.getDescriptor(); + GoGoProtos.getDescriptor(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/Types.java b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/Types.java new file mode 100644 index 00000000000..0dc5e20d3c4 --- /dev/null +++ b/seatunnel-connectors-v2/connector-prometheus/src/main/java/org/apache/seatunnel/connectors/seatunnel/prometheus/sink/proto/Types.java @@ -0,0 +1,17105 @@ +/* + * 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. + */ +package org.apache.seatunnel.connectors.seatunnel.prometheus.sink.proto; + +public final class Types { + private Types() {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistryLite registry) {} + + public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions((com.google.protobuf.ExtensionRegistryLite) registry); + } + + public interface MetricMetadataOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.MetricMetadata) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
+         * Represents the metric type, these match the set from Prometheus.
+         * Refer to github.com/prometheus/common/model/metadata.go for details.
+         * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return The enum numeric value on the wire for type. + */ + int getTypeValue(); + + /** + * + * + *
+         * Represents the metric type, these match the set from Prometheus.
+         * Refer to github.com/prometheus/common/model/metadata.go for details.
+         * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return The type. + */ + Types.MetricMetadata.MetricType getType(); + + /** + * string metric_family_name = 2; + * + * @return The metricFamilyName. + */ + String getMetricFamilyName(); + + /** + * string metric_family_name = 2; + * + * @return The bytes for metricFamilyName. + */ + com.google.protobuf.ByteString getMetricFamilyNameBytes(); + + /** + * string help = 4; + * + * @return The help. + */ + String getHelp(); + + /** + * string help = 4; + * + * @return The bytes for help. + */ + com.google.protobuf.ByteString getHelpBytes(); + + /** + * string unit = 5; + * + * @return The unit. + */ + String getUnit(); + + /** + * string unit = 5; + * + * @return The bytes for unit. + */ + com.google.protobuf.ByteString getUnitBytes(); + } + + /** Protobuf type {@code prometheus.MetricMetadata} */ + public static final class MetricMetadata extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.MetricMetadata) + MetricMetadataOrBuilder { + private static final long serialVersionUID = 0L; + + // Use MetricMetadata.newBuilder() to construct. + private MetricMetadata(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private MetricMetadata() { + type_ = 0; + metricFamilyName_ = ""; + help_ = ""; + unit_ = ""; + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new MetricMetadata(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_MetricMetadata_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_MetricMetadata_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.MetricMetadata.class, Types.MetricMetadata.Builder.class); + } + + /** Protobuf enum {@code prometheus.MetricMetadata.MetricType} */ + public enum MetricType implements com.google.protobuf.ProtocolMessageEnum { + /** UNKNOWN = 0; */ + UNKNOWN(0), + /** COUNTER = 1; */ + COUNTER(1), + /** GAUGE = 2; */ + GAUGE(2), + /** HISTOGRAM = 3; */ + HISTOGRAM(3), + /** GAUGEHISTOGRAM = 4; */ + GAUGEHISTOGRAM(4), + /** SUMMARY = 5; */ + SUMMARY(5), + /** INFO = 6; */ + INFO(6), + /** STATESET = 7; */ + STATESET(7), + UNRECOGNIZED(-1), + ; + + /** UNKNOWN = 0; */ + public static final int UNKNOWN_VALUE = 0; + /** COUNTER = 1; */ + public static final int COUNTER_VALUE = 1; + /** GAUGE = 2; */ + public static final int GAUGE_VALUE = 2; + /** HISTOGRAM = 3; */ + public static final int HISTOGRAM_VALUE = 3; + /** GAUGEHISTOGRAM = 4; */ + public static final int GAUGEHISTOGRAM_VALUE = 4; + /** SUMMARY = 5; */ + public static final int SUMMARY_VALUE = 5; + /** INFO = 6; */ + public static final int INFO_VALUE = 6; + /** STATESET = 7; */ + public static final int STATESET_VALUE = 7; + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @Deprecated + public static MetricType valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static MetricType forNumber(int value) { + switch (value) { + case 0: + return UNKNOWN; + case 1: + return COUNTER; + case 2: + return GAUGE; + case 3: + return HISTOGRAM; + case 4: + return GAUGEHISTOGRAM; + case 5: + return SUMMARY; + case 6: + return INFO; + case 7: + return STATESET; + default: + return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + + private static final com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public MetricType findValueByNumber(int number) { + return MetricType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + + public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { + return getDescriptor(); + } + + public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { + return Types.MetricMetadata.getDescriptor().getEnumTypes().get(0); + } + + private static final MetricType[] VALUES = values(); + + public static MetricType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new IllegalArgumentException("EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private MetricType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:prometheus.MetricMetadata.MetricType) + } + + public static final int TYPE_FIELD_NUMBER = 1; + private int type_ = 0; + + /** + * + * + *
+         * Represents the metric type, these match the set from Prometheus.
+         * Refer to github.com/prometheus/common/model/metadata.go for details.
+         * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return The enum numeric value on the wire for type. + */ + @Override + public int getTypeValue() { + return type_; + } + + /** + * + * + *
+         * Represents the metric type, these match the set from Prometheus.
+         * Refer to github.com/prometheus/common/model/metadata.go for details.
+         * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return The type. + */ + @Override + public Types.MetricMetadata.MetricType getType() { + Types.MetricMetadata.MetricType result = + Types.MetricMetadata.MetricType.forNumber(type_); + return result == null ? Types.MetricMetadata.MetricType.UNRECOGNIZED : result; + } + + public static final int METRIC_FAMILY_NAME_FIELD_NUMBER = 2; + + @SuppressWarnings("serial") + private volatile Object metricFamilyName_ = ""; + + /** + * string metric_family_name = 2; + * + * @return The metricFamilyName. + */ + @Override + public String getMetricFamilyName() { + Object ref = metricFamilyName_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + metricFamilyName_ = s; + return s; + } + } + + /** + * string metric_family_name = 2; + * + * @return The bytes for metricFamilyName. + */ + @Override + public com.google.protobuf.ByteString getMetricFamilyNameBytes() { + Object ref = metricFamilyName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + metricFamilyName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int HELP_FIELD_NUMBER = 4; + + @SuppressWarnings("serial") + private volatile Object help_ = ""; + + /** + * string help = 4; + * + * @return The help. + */ + @Override + public String getHelp() { + Object ref = help_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + help_ = s; + return s; + } + } + + /** + * string help = 4; + * + * @return The bytes for help. + */ + @Override + public com.google.protobuf.ByteString getHelpBytes() { + Object ref = help_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + help_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int UNIT_FIELD_NUMBER = 5; + + @SuppressWarnings("serial") + private volatile Object unit_ = ""; + + /** + * string unit = 5; + * + * @return The unit. + */ + @Override + public String getUnit() { + Object ref = unit_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + unit_ = s; + return s; + } + } + + /** + * string unit = 5; + * + * @return The bytes for unit. + */ + @Override + public com.google.protobuf.ByteString getUnitBytes() { + Object ref = unit_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + unit_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (type_ != Types.MetricMetadata.MetricType.UNKNOWN.getNumber()) { + output.writeEnum(1, type_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(metricFamilyName_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, metricFamilyName_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(help_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 4, help_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(unit_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 5, unit_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + if (type_ != Types.MetricMetadata.MetricType.UNKNOWN.getNumber()) { + size += com.google.protobuf.CodedOutputStream.computeEnumSize(1, type_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(metricFamilyName_)) { + size += + com.google.protobuf.GeneratedMessageV3.computeStringSize( + 2, metricFamilyName_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(help_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, help_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(unit_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, unit_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.MetricMetadata)) { + return super.equals(obj); + } + Types.MetricMetadata other = (Types.MetricMetadata) obj; + + if (type_ != other.type_) { + return false; + } + if (!getMetricFamilyName().equals(other.getMetricFamilyName())) { + return false; + } + if (!getHelp().equals(other.getHelp())) { + return false; + } + if (!getUnit().equals(other.getUnit())) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + TYPE_FIELD_NUMBER; + hash = (53 * hash) + type_; + hash = (37 * hash) + METRIC_FAMILY_NAME_FIELD_NUMBER; + hash = (53 * hash) + getMetricFamilyName().hashCode(); + hash = (37 * hash) + HELP_FIELD_NUMBER; + hash = (53 * hash) + getHelp().hashCode(); + hash = (37 * hash) + UNIT_FIELD_NUMBER; + hash = (53 * hash) + getUnit().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.MetricMetadata parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.MetricMetadata parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.MetricMetadata parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.MetricMetadata parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.MetricMetadata parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.MetricMetadata parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.MetricMetadata parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.MetricMetadata parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.MetricMetadata parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.MetricMetadata parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.MetricMetadata parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.MetricMetadata parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.MetricMetadata prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.MetricMetadata} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.MetricMetadata) + Types.MetricMetadataOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_MetricMetadata_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_MetricMetadata_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.MetricMetadata.class, Types.MetricMetadata.Builder.class); + } + + // Construct using Types.MetricMetadata.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + type_ = 0; + metricFamilyName_ = ""; + help_ = ""; + unit_ = ""; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_MetricMetadata_descriptor; + } + + @Override + public Types.MetricMetadata getDefaultInstanceForType() { + return Types.MetricMetadata.getDefaultInstance(); + } + + @Override + public Types.MetricMetadata build() { + Types.MetricMetadata result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.MetricMetadata buildPartial() { + Types.MetricMetadata result = new Types.MetricMetadata(this); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartial0(Types.MetricMetadata result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.type_ = type_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.metricFamilyName_ = metricFamilyName_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.help_ = help_; + } + if (((from_bitField0_ & 0x00000008) != 0)) { + result.unit_ = unit_; + } + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.MetricMetadata) { + return mergeFrom((Types.MetricMetadata) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.MetricMetadata other) { + if (other == Types.MetricMetadata.getDefaultInstance()) { + return this; + } + if (other.type_ != 0) { + setTypeValue(other.getTypeValue()); + } + if (!other.getMetricFamilyName().isEmpty()) { + metricFamilyName_ = other.metricFamilyName_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (!other.getHelp().isEmpty()) { + help_ = other.help_; + bitField0_ |= 0x00000004; + onChanged(); + } + if (!other.getUnit().isEmpty()) { + unit_ = other.unit_; + bitField0_ |= 0x00000008; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + type_ = input.readEnum(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 18: + { + metricFamilyName_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 34: + { + help_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 34 + case 42: + { + unit_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000008; + break; + } // case 42 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private int type_ = 0; + + /** + * + * + *
+             * Represents the metric type, these match the set from Prometheus.
+             * Refer to github.com/prometheus/common/model/metadata.go for details.
+             * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return The enum numeric value on the wire for type. + */ + @Override + public int getTypeValue() { + return type_; + } + + /** + * + * + *
+             * Represents the metric type, these match the set from Prometheus.
+             * Refer to github.com/prometheus/common/model/metadata.go for details.
+             * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @param value The enum numeric value on the wire for type to set. + * @return This builder for chaining. + */ + public Builder setTypeValue(int value) { + type_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + /** + * + * + *
+             * Represents the metric type, these match the set from Prometheus.
+             * Refer to github.com/prometheus/common/model/metadata.go for details.
+             * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return The type. + */ + @Override + public Types.MetricMetadata.MetricType getType() { + Types.MetricMetadata.MetricType result = + Types.MetricMetadata.MetricType.forNumber(type_); + return result == null ? Types.MetricMetadata.MetricType.UNRECOGNIZED : result; + } + + /** + * + * + *
+             * Represents the metric type, these match the set from Prometheus.
+             * Refer to github.com/prometheus/common/model/metadata.go for details.
+             * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @param value The type to set. + * @return This builder for chaining. + */ + public Builder setType(Types.MetricMetadata.MetricType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + type_ = value.getNumber(); + onChanged(); + return this; + } + + /** + * + * + *
+             * Represents the metric type, these match the set from Prometheus.
+             * Refer to github.com/prometheus/common/model/metadata.go for details.
+             * 
+ * + * .prometheus.MetricMetadata.MetricType type = 1; + * + * @return This builder for chaining. + */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000001); + type_ = 0; + onChanged(); + return this; + } + + private Object metricFamilyName_ = ""; + + /** + * string metric_family_name = 2; + * + * @return The metricFamilyName. + */ + public String getMetricFamilyName() { + Object ref = metricFamilyName_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + metricFamilyName_ = s; + return s; + } else { + return (String) ref; + } + } + + /** + * string metric_family_name = 2; + * + * @return The bytes for metricFamilyName. + */ + public com.google.protobuf.ByteString getMetricFamilyNameBytes() { + Object ref = metricFamilyName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + metricFamilyName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + /** + * string metric_family_name = 2; + * + * @param value The metricFamilyName to set. + * @return This builder for chaining. + */ + public Builder setMetricFamilyName(String value) { + if (value == null) { + throw new NullPointerException(); + } + metricFamilyName_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * string metric_family_name = 2; + * + * @return This builder for chaining. + */ + public Builder clearMetricFamilyName() { + metricFamilyName_ = getDefaultInstance().getMetricFamilyName(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + + /** + * string metric_family_name = 2; + * + * @param value The bytes for metricFamilyName to set. + * @return This builder for chaining. + */ + public Builder setMetricFamilyNameBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + metricFamilyName_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private Object help_ = ""; + + /** + * string help = 4; + * + * @return The help. + */ + public String getHelp() { + Object ref = help_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + help_ = s; + return s; + } else { + return (String) ref; + } + } + + /** + * string help = 4; + * + * @return The bytes for help. + */ + public com.google.protobuf.ByteString getHelpBytes() { + Object ref = help_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + help_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + /** + * string help = 4; + * + * @param value The help to set. + * @return This builder for chaining. + */ + public Builder setHelp(String value) { + if (value == null) { + throw new NullPointerException(); + } + help_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + /** + * string help = 4; + * + * @return This builder for chaining. + */ + public Builder clearHelp() { + help_ = getDefaultInstance().getHelp(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + + /** + * string help = 4; + * + * @param value The bytes for help to set. + * @return This builder for chaining. + */ + public Builder setHelpBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + help_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + private Object unit_ = ""; + + /** + * string unit = 5; + * + * @return The unit. + */ + public String getUnit() { + Object ref = unit_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + unit_ = s; + return s; + } else { + return (String) ref; + } + } + + /** + * string unit = 5; + * + * @return The bytes for unit. + */ + public com.google.protobuf.ByteString getUnitBytes() { + Object ref = unit_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + unit_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + /** + * string unit = 5; + * + * @param value The unit to set. + * @return This builder for chaining. + */ + public Builder setUnit(String value) { + if (value == null) { + throw new NullPointerException(); + } + unit_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + /** + * string unit = 5; + * + * @return This builder for chaining. + */ + public Builder clearUnit() { + unit_ = getDefaultInstance().getUnit(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + + /** + * string unit = 5; + * + * @param value The bytes for unit to set. + * @return This builder for chaining. + */ + public Builder setUnitBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + unit_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.MetricMetadata) + } + + // @@protoc_insertion_point(class_scope:prometheus.MetricMetadata) + private static final Types.MetricMetadata DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.MetricMetadata(); + } + + public static Types.MetricMetadata getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public MetricMetadata parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Types.MetricMetadata getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface SampleOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.Sample) + com.google.protobuf.MessageOrBuilder { + + /** + * double value = 1; + * + * @return The value. + */ + double getValue(); + + /** + * + * + *
+         * timestamp is in ms format, see model/timestamp/timestamp.go for
+         * conversion from time.Time to Prometheus timestamp.
+         * 
+ * + * int64 timestamp = 2; + * + * @return The timestamp. + */ + long getTimestamp(); + } + + /** Protobuf type {@code prometheus.Sample} */ + public static final class Sample extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.Sample) + SampleOrBuilder { + private static final long serialVersionUID = 0L; + + // Use Sample.newBuilder() to construct. + private Sample(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private Sample() {} + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new Sample(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Sample_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Sample_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Sample.class, Types.Sample.Builder.class); + } + + public static final int VALUE_FIELD_NUMBER = 1; + private double value_ = 0D; + + /** + * double value = 1; + * + * @return The value. + */ + @Override + public double getValue() { + return value_; + } + + public static final int TIMESTAMP_FIELD_NUMBER = 2; + private long timestamp_ = 0L; + + /** + * + * + *
+         * timestamp is in ms format, see model/timestamp/timestamp.go for
+         * conversion from time.Time to Prometheus timestamp.
+         * 
+ * + * int64 timestamp = 2; + * + * @return The timestamp. + */ + @Override + public long getTimestamp() { + return timestamp_; + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (Double.doubleToRawLongBits(value_) != 0) { + output.writeDouble(1, value_); + } + if (timestamp_ != 0L) { + output.writeInt64(2, timestamp_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + if (Double.doubleToRawLongBits(value_) != 0) { + size += com.google.protobuf.CodedOutputStream.computeDoubleSize(1, value_); + } + if (timestamp_ != 0L) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, timestamp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.Sample)) { + return super.equals(obj); + } + Types.Sample other = (Types.Sample) obj; + + if (Double.doubleToLongBits(getValue()) != Double.doubleToLongBits(other.getValue())) { + return false; + } + if (getTimestamp() != other.getTimestamp()) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = + (53 * hash) + + com.google.protobuf.Internal.hashLong( + Double.doubleToLongBits(getValue())); + hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getTimestamp()); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.Sample parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Sample parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Sample parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Sample parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Sample parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Sample parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Sample parseFrom(java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Sample parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Sample parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.Sample parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Sample parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Sample parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.Sample prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.Sample} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.Sample) + Types.SampleOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Sample_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Sample_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Sample.class, Types.Sample.Builder.class); + } + + // Construct using Types.Sample.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + value_ = 0D; + timestamp_ = 0L; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_Sample_descriptor; + } + + @Override + public Types.Sample getDefaultInstanceForType() { + return Types.Sample.getDefaultInstance(); + } + + @Override + public Types.Sample build() { + Types.Sample result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.Sample buildPartial() { + Types.Sample result = new Types.Sample(this); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartial0(Types.Sample result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.value_ = value_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.timestamp_ = timestamp_; + } + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.Sample) { + return mergeFrom((Types.Sample) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.Sample other) { + if (other == Types.Sample.getDefaultInstance()) { + return this; + } + if (other.getValue() != 0D) { + setValue(other.getValue()); + } + if (other.getTimestamp() != 0L) { + setTimestamp(other.getTimestamp()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 9: + { + value_ = input.readDouble(); + bitField0_ |= 0x00000001; + break; + } // case 9 + case 16: + { + timestamp_ = input.readInt64(); + bitField0_ |= 0x00000002; + break; + } // case 16 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private double value_; + + /** + * double value = 1; + * + * @return The value. + */ + @Override + public double getValue() { + return value_; + } + + /** + * double value = 1; + * + * @param value The value to set. + * @return This builder for chaining. + */ + public Builder setValue(double value) { + + value_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + /** + * double value = 1; + * + * @return This builder for chaining. + */ + public Builder clearValue() { + bitField0_ = (bitField0_ & ~0x00000001); + value_ = 0D; + onChanged(); + return this; + } + + private long timestamp_; + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 2; + * + * @return The timestamp. + */ + @Override + public long getTimestamp() { + return timestamp_; + } + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 2; + * + * @param value The timestamp to set. + * @return This builder for chaining. + */ + public Builder setTimestamp(long value) { + + timestamp_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 2; + * + * @return This builder for chaining. + */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000002); + timestamp_ = 0L; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.Sample) + } + + // @@protoc_insertion_point(class_scope:prometheus.Sample) + private static final Types.Sample DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.Sample(); + } + + public static Types.Sample getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public Sample parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Types.Sample getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface ExemplarOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.Exemplar) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + java.util.List getLabelsList(); + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + Types.Label getLabels(int index); + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + int getLabelsCount(); + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + java.util.List getLabelsOrBuilderList(); + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + Types.LabelOrBuilder getLabelsOrBuilder(int index); + + /** + * double value = 2; + * + * @return The value. + */ + double getValue(); + + /** + * + * + *
+         * timestamp is in ms format, see model/timestamp/timestamp.go for
+         * conversion from time.Time to Prometheus timestamp.
+         * 
+ * + * int64 timestamp = 3; + * + * @return The timestamp. + */ + long getTimestamp(); + } + + /** Protobuf type {@code prometheus.Exemplar} */ + public static final class Exemplar extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.Exemplar) + ExemplarOrBuilder { + private static final long serialVersionUID = 0L; + + // Use Exemplar.newBuilder() to construct. + private Exemplar(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private Exemplar() { + labels_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new Exemplar(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Exemplar_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Exemplar_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Exemplar.class, Types.Exemplar.Builder.class); + } + + public static final int LABELS_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List labels_; + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public java.util.List getLabelsList() { + return labels_; + } + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public java.util.List getLabelsOrBuilderList() { + return labels_; + } + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public int getLabelsCount() { + return labels_.size(); + } + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public Types.Label getLabels(int index) { + return labels_.get(index); + } + + /** + * + * + *
+         * Optional, can be empty.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public Types.LabelOrBuilder getLabelsOrBuilder(int index) { + return labels_.get(index); + } + + public static final int VALUE_FIELD_NUMBER = 2; + private double value_ = 0D; + + /** + * double value = 2; + * + * @return The value. + */ + @Override + public double getValue() { + return value_; + } + + public static final int TIMESTAMP_FIELD_NUMBER = 3; + private long timestamp_ = 0L; + + /** + * + * + *
+         * timestamp is in ms format, see model/timestamp/timestamp.go for
+         * conversion from time.Time to Prometheus timestamp.
+         * 
+ * + * int64 timestamp = 3; + * + * @return The timestamp. + */ + @Override + public long getTimestamp() { + return timestamp_; + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < labels_.size(); i++) { + output.writeMessage(1, labels_.get(i)); + } + if (Double.doubleToRawLongBits(value_) != 0) { + output.writeDouble(2, value_); + } + if (timestamp_ != 0L) { + output.writeInt64(3, timestamp_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < labels_.size(); i++) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(1, labels_.get(i)); + } + if (Double.doubleToRawLongBits(value_) != 0) { + size += com.google.protobuf.CodedOutputStream.computeDoubleSize(2, value_); + } + if (timestamp_ != 0L) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(3, timestamp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.Exemplar)) { + return super.equals(obj); + } + Types.Exemplar other = (Types.Exemplar) obj; + + if (!getLabelsList().equals(other.getLabelsList())) { + return false; + } + if (Double.doubleToLongBits(getValue()) != Double.doubleToLongBits(other.getValue())) { + return false; + } + if (getTimestamp() != other.getTimestamp()) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getLabelsCount() > 0) { + hash = (37 * hash) + LABELS_FIELD_NUMBER; + hash = (53 * hash) + getLabelsList().hashCode(); + } + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = + (53 * hash) + + com.google.protobuf.Internal.hashLong( + Double.doubleToLongBits(getValue())); + hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getTimestamp()); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.Exemplar parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Exemplar parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Exemplar parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Exemplar parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Exemplar parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Exemplar parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Exemplar parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Exemplar parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Exemplar parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.Exemplar parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Exemplar parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Exemplar parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.Exemplar prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.Exemplar} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.Exemplar) + Types.ExemplarOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Exemplar_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Exemplar_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Exemplar.class, Types.Exemplar.Builder.class); + } + + // Construct using Types.Exemplar.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (labelsBuilder_ == null) { + labels_ = java.util.Collections.emptyList(); + } else { + labels_ = null; + labelsBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + value_ = 0D; + timestamp_ = 0L; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_Exemplar_descriptor; + } + + @Override + public Types.Exemplar getDefaultInstanceForType() { + return Types.Exemplar.getDefaultInstance(); + } + + @Override + public Types.Exemplar build() { + Types.Exemplar result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.Exemplar buildPartial() { + Types.Exemplar result = new Types.Exemplar(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Types.Exemplar result) { + if (labelsBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + labels_ = java.util.Collections.unmodifiableList(labels_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.labels_ = labels_; + } else { + result.labels_ = labelsBuilder_.build(); + } + } + + private void buildPartial0(Types.Exemplar result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000002) != 0)) { + result.value_ = value_; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.timestamp_ = timestamp_; + } + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.Exemplar) { + return mergeFrom((Types.Exemplar) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.Exemplar other) { + if (other == Types.Exemplar.getDefaultInstance()) { + return this; + } + if (labelsBuilder_ == null) { + if (!other.labels_.isEmpty()) { + if (labels_.isEmpty()) { + labels_ = other.labels_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureLabelsIsMutable(); + labels_.addAll(other.labels_); + } + onChanged(); + } + } else { + if (!other.labels_.isEmpty()) { + if (labelsBuilder_.isEmpty()) { + labelsBuilder_.dispose(); + labelsBuilder_ = null; + labels_ = other.labels_; + bitField0_ = (bitField0_ & ~0x00000001); + labelsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getLabelsFieldBuilder() + : null; + } else { + labelsBuilder_.addAllMessages(other.labels_); + } + } + } + if (other.getValue() != 0D) { + setValue(other.getValue()); + } + if (other.getTimestamp() != 0L) { + setTimestamp(other.getTimestamp()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Types.Label m = + input.readMessage( + Types.Label.parser(), extensionRegistry); + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.add(m); + } else { + labelsBuilder_.addMessage(m); + } + break; + } // case 10 + case 17: + { + value_ = input.readDouble(); + bitField0_ |= 0x00000002; + break; + } // case 17 + case 24: + { + timestamp_ = input.readInt64(); + bitField0_ |= 0x00000004; + break; + } // case 24 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List labels_ = java.util.Collections.emptyList(); + + private void ensureLabelsIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + labels_ = new java.util.ArrayList(labels_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Label, Types.Label.Builder, Types.LabelOrBuilder> + labelsBuilder_; + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public java.util.List getLabelsList() { + if (labelsBuilder_ == null) { + return java.util.Collections.unmodifiableList(labels_); + } else { + return labelsBuilder_.getMessageList(); + } + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public int getLabelsCount() { + if (labelsBuilder_ == null) { + return labels_.size(); + } else { + return labelsBuilder_.getCount(); + } + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label getLabels(int index) { + if (labelsBuilder_ == null) { + return labels_.get(index); + } else { + return labelsBuilder_.getMessage(index); + } + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder setLabels(int index, Types.Label value) { + if (labelsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLabelsIsMutable(); + labels_.set(index, value); + onChanged(); + } else { + labelsBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder setLabels(int index, Types.Label.Builder builderForValue) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.set(index, builderForValue.build()); + onChanged(); + } else { + labelsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(Types.Label value) { + if (labelsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLabelsIsMutable(); + labels_.add(value); + onChanged(); + } else { + labelsBuilder_.addMessage(value); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(int index, Types.Label value) { + if (labelsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLabelsIsMutable(); + labels_.add(index, value); + onChanged(); + } else { + labelsBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(Types.Label.Builder builderForValue) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.add(builderForValue.build()); + onChanged(); + } else { + labelsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(int index, Types.Label.Builder builderForValue) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.add(index, builderForValue.build()); + onChanged(); + } else { + labelsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addAllLabels(Iterable values) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, labels_); + onChanged(); + } else { + labelsBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder clearLabels() { + if (labelsBuilder_ == null) { + labels_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + labelsBuilder_.clear(); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder removeLabels(int index) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.remove(index); + onChanged(); + } else { + labelsBuilder_.remove(index); + } + return this; + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label.Builder getLabelsBuilder(int index) { + return getLabelsFieldBuilder().getBuilder(index); + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.LabelOrBuilder getLabelsOrBuilder(int index) { + if (labelsBuilder_ == null) { + return labels_.get(index); + } else { + return labelsBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public java.util.List getLabelsOrBuilderList() { + if (labelsBuilder_ != null) { + return labelsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(labels_); + } + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label.Builder addLabelsBuilder() { + return getLabelsFieldBuilder().addBuilder(Types.Label.getDefaultInstance()); + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label.Builder addLabelsBuilder(int index) { + return getLabelsFieldBuilder().addBuilder(index, Types.Label.getDefaultInstance()); + } + + /** + * + * + *
+             * Optional, can be empty.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public java.util.List getLabelsBuilderList() { + return getLabelsFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Label, Types.Label.Builder, Types.LabelOrBuilder> + getLabelsFieldBuilder() { + if (labelsBuilder_ == null) { + labelsBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.Label, Types.Label.Builder, Types.LabelOrBuilder>( + labels_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + labels_ = null; + } + return labelsBuilder_; + } + + private double value_; + + /** + * double value = 2; + * + * @return The value. + */ + @Override + public double getValue() { + return value_; + } + + /** + * double value = 2; + * + * @param value The value to set. + * @return This builder for chaining. + */ + public Builder setValue(double value) { + + value_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * double value = 2; + * + * @return This builder for chaining. + */ + public Builder clearValue() { + bitField0_ = (bitField0_ & ~0x00000002); + value_ = 0D; + onChanged(); + return this; + } + + private long timestamp_; + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 3; + * + * @return The timestamp. + */ + @Override + public long getTimestamp() { + return timestamp_; + } + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 3; + * + * @param value The timestamp to set. + * @return This builder for chaining. + */ + public Builder setTimestamp(long value) { + + timestamp_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 3; + * + * @return This builder for chaining. + */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000004); + timestamp_ = 0L; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.Exemplar) + } + + // @@protoc_insertion_point(class_scope:prometheus.Exemplar) + private static final Types.Exemplar DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.Exemplar(); + } + + public static Types.Exemplar getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public Exemplar parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Types.Exemplar getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface HistogramOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.Histogram) + com.google.protobuf.MessageOrBuilder { + + /** + * uint64 count_int = 1; + * + * @return Whether the countInt field is set. + */ + boolean hasCountInt(); + + /** + * uint64 count_int = 1; + * + * @return The countInt. + */ + long getCountInt(); + + /** + * double count_float = 2; + * + * @return Whether the countFloat field is set. + */ + boolean hasCountFloat(); + + /** + * double count_float = 2; + * + * @return The countFloat. + */ + double getCountFloat(); + + /** + * + * + *
+         * Sum of observations in the histogram.
+         * 
+ * + * double sum = 3; + * + * @return The sum. + */ + double getSum(); + + /** + * + * + *
+         * The schema defines the bucket schema. Currently, valid numbers
+         * are -4 <= n <= 8. They are all for base-2 bucket schemas, where 1
+         * is a bucket boundary in each case, and then each power of two is
+         * divided into 2^n logarithmic buckets. Or in other words, each
+         * bucket boundary is the previous boundary times 2^(2^-n). In the
+         * future, more bucket schemas may be added using numbers < -4 or >
+         * 8.
+         * 
+ * + * sint32 schema = 4; + * + * @return The schema. + */ + int getSchema(); + + /** + * + * + *
+         * Breadth of the zero bucket.
+         * 
+ * + * double zero_threshold = 5; + * + * @return The zeroThreshold. + */ + double getZeroThreshold(); + + /** + * uint64 zero_count_int = 6; + * + * @return Whether the zeroCountInt field is set. + */ + boolean hasZeroCountInt(); + + /** + * uint64 zero_count_int = 6; + * + * @return The zeroCountInt. + */ + long getZeroCountInt(); + + /** + * double zero_count_float = 7; + * + * @return Whether the zeroCountFloat field is set. + */ + boolean hasZeroCountFloat(); + + /** + * double zero_count_float = 7; + * + * @return The zeroCountFloat. + */ + double getZeroCountFloat(); + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getNegativeSpansList(); + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + Types.BucketSpan getNegativeSpans(int index); + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + int getNegativeSpansCount(); + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getNegativeSpansOrBuilderList(); + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + Types.BucketSpanOrBuilder getNegativeSpansOrBuilder(int index); + + /** + * + * + *
+         * Use either "negative_deltas" or "negative_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return A list containing the negativeDeltas. + */ + java.util.List getNegativeDeltasList(); + + /** + * + * + *
+         * Use either "negative_deltas" or "negative_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return The count of negativeDeltas. + */ + int getNegativeDeltasCount(); + + /** + * + * + *
+         * Use either "negative_deltas" or "negative_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @param index The index of the element to return. + * @return The negativeDeltas at the given index. + */ + long getNegativeDeltas(int index); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double negative_counts = 10; + * + * @return A list containing the negativeCounts. + */ + java.util.List getNegativeCountsList(); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double negative_counts = 10; + * + * @return The count of negativeCounts. + */ + int getNegativeCountsCount(); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double negative_counts = 10; + * + * @param index The index of the element to return. + * @return The negativeCounts at the given index. + */ + double getNegativeCounts(int index); + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getPositiveSpansList(); + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + Types.BucketSpan getPositiveSpans(int index); + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + int getPositiveSpansCount(); + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getPositiveSpansOrBuilderList(); + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + Types.BucketSpanOrBuilder getPositiveSpansOrBuilder(int index); + + /** + * + * + *
+         * Use either "positive_deltas" or "positive_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return A list containing the positiveDeltas. + */ + java.util.List getPositiveDeltasList(); + + /** + * + * + *
+         * Use either "positive_deltas" or "positive_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return The count of positiveDeltas. + */ + int getPositiveDeltasCount(); + + /** + * + * + *
+         * Use either "positive_deltas" or "positive_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @param index The index of the element to return. + * @return The positiveDeltas at the given index. + */ + long getPositiveDeltas(int index); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double positive_counts = 13; + * + * @return A list containing the positiveCounts. + */ + java.util.List getPositiveCountsList(); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double positive_counts = 13; + * + * @return The count of positiveCounts. + */ + int getPositiveCountsCount(); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double positive_counts = 13; + * + * @param index The index of the element to return. + * @return The positiveCounts at the given index. + */ + double getPositiveCounts(int index); + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return The enum numeric value on the wire for resetHint. + */ + int getResetHintValue(); + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return The resetHint. + */ + Types.Histogram.ResetHint getResetHint(); + + /** + * + * + *
+         * timestamp is in ms format, see model/timestamp/timestamp.go for
+         * conversion from time.Time to Prometheus timestamp.
+         * 
+ * + * int64 timestamp = 15; + * + * @return The timestamp. + */ + long getTimestamp(); + + Types.Histogram.CountCase getCountCase(); + + Types.Histogram.ZeroCountCase getZeroCountCase(); + } + + /** + * + * + *
+     * A native histogram, also known as a sparse histogram.
+     * Original design doc:
+     * https://docs.google.com/document/d/1cLNv3aufPZb3fNfaJgdaRBZsInZKKIHo9E6HinJVbpM/edit
+     * The appendix of this design doc also explains the concept of float
+     * histograms. This Histogram message can represent both, the usual
+     * integer histogram as well as a float histogram.
+     * 
+ * + *

Protobuf type {@code prometheus.Histogram} + */ + public static final class Histogram extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.Histogram) + HistogramOrBuilder { + private static final long serialVersionUID = 0L; + + // Use Histogram.newBuilder() to construct. + private Histogram(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private Histogram() { + negativeSpans_ = java.util.Collections.emptyList(); + negativeDeltas_ = emptyLongList(); + negativeCounts_ = emptyDoubleList(); + positiveSpans_ = java.util.Collections.emptyList(); + positiveDeltas_ = emptyLongList(); + positiveCounts_ = emptyDoubleList(); + resetHint_ = 0; + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new Histogram(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Histogram_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Histogram_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Histogram.class, Types.Histogram.Builder.class); + } + + /** Protobuf enum {@code prometheus.Histogram.ResetHint} */ + public enum ResetHint implements com.google.protobuf.ProtocolMessageEnum { + /** + * + * + *

+             * Need to test for a counter reset explicitly.
+             * 
+ * + * UNKNOWN = 0; + */ + UNKNOWN(0), + /** + * + * + *
+             * This is the 1st histogram after a counter reset.
+             * 
+ * + * YES = 1; + */ + YES(1), + /** + * + * + *
+             * There was no counter reset between this and the previous Histogram.
+             * 
+ * + * NO = 2; + */ + NO(2), + /** + * + * + *
+             * This is a gauge histogram where counter resets don't happen.
+             * 
+ * + * GAUGE = 3; + */ + GAUGE(3), + UNRECOGNIZED(-1), + ; + + /** + * + * + *
+             * Need to test for a counter reset explicitly.
+             * 
+ * + * UNKNOWN = 0; + */ + public static final int UNKNOWN_VALUE = 0; + /** + * + * + *
+             * This is the 1st histogram after a counter reset.
+             * 
+ * + * YES = 1; + */ + public static final int YES_VALUE = 1; + /** + * + * + *
+             * There was no counter reset between this and the previous Histogram.
+             * 
+ * + * NO = 2; + */ + public static final int NO_VALUE = 2; + /** + * + * + *
+             * This is a gauge histogram where counter resets don't happen.
+             * 
+ * + * GAUGE = 3; + */ + public static final int GAUGE_VALUE = 3; + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @Deprecated + public static ResetHint valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static ResetHint forNumber(int value) { + switch (value) { + case 0: + return UNKNOWN; + case 1: + return YES; + case 2: + return NO; + case 3: + return GAUGE; + default: + return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + + private static final com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public ResetHint findValueByNumber(int number) { + return ResetHint.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + + public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { + return getDescriptor(); + } + + public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { + return Types.Histogram.getDescriptor().getEnumTypes().get(0); + } + + private static final ResetHint[] VALUES = values(); + + public static ResetHint valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new IllegalArgumentException("EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private ResetHint(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:prometheus.Histogram.ResetHint) + } + + private int countCase_ = 0; + + @SuppressWarnings("serial") + private Object count_; + + public enum CountCase implements com.google.protobuf.Internal.EnumLite, InternalOneOfEnum { + COUNT_INT(1), + COUNT_FLOAT(2), + COUNT_NOT_SET(0); + private final int value; + + private CountCase(int value) { + this.value = value; + } + + /** + * @param value The number of the enum to look for. + * @return The enum associated with the given number. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @Deprecated + public static CountCase valueOf(int value) { + return forNumber(value); + } + + public static CountCase forNumber(int value) { + switch (value) { + case 1: + return COUNT_INT; + case 2: + return COUNT_FLOAT; + case 0: + return COUNT_NOT_SET; + default: + return null; + } + } + + public int getNumber() { + return this.value; + } + }; + + public CountCase getCountCase() { + return CountCase.forNumber(countCase_); + } + + private int zeroCountCase_ = 0; + + @SuppressWarnings("serial") + private Object zeroCount_; + + public enum ZeroCountCase + implements com.google.protobuf.Internal.EnumLite, InternalOneOfEnum { + ZERO_COUNT_INT(6), + ZERO_COUNT_FLOAT(7), + ZEROCOUNT_NOT_SET(0); + private final int value; + + private ZeroCountCase(int value) { + this.value = value; + } + + /** + * @param value The number of the enum to look for. + * @return The enum associated with the given number. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @Deprecated + public static ZeroCountCase valueOf(int value) { + return forNumber(value); + } + + public static ZeroCountCase forNumber(int value) { + switch (value) { + case 6: + return ZERO_COUNT_INT; + case 7: + return ZERO_COUNT_FLOAT; + case 0: + return ZEROCOUNT_NOT_SET; + default: + return null; + } + } + + public int getNumber() { + return this.value; + } + }; + + public ZeroCountCase getZeroCountCase() { + return ZeroCountCase.forNumber(zeroCountCase_); + } + + public static final int COUNT_INT_FIELD_NUMBER = 1; + + /** + * uint64 count_int = 1; + * + * @return Whether the countInt field is set. + */ + @Override + public boolean hasCountInt() { + return countCase_ == 1; + } + + /** + * uint64 count_int = 1; + * + * @return The countInt. + */ + @Override + public long getCountInt() { + if (countCase_ == 1) { + return (Long) count_; + } + return 0L; + } + + public static final int COUNT_FLOAT_FIELD_NUMBER = 2; + + /** + * double count_float = 2; + * + * @return Whether the countFloat field is set. + */ + @Override + public boolean hasCountFloat() { + return countCase_ == 2; + } + + /** + * double count_float = 2; + * + * @return The countFloat. + */ + @Override + public double getCountFloat() { + if (countCase_ == 2) { + return (Double) count_; + } + return 0D; + } + + public static final int SUM_FIELD_NUMBER = 3; + private double sum_ = 0D; + + /** + * + * + *
+         * Sum of observations in the histogram.
+         * 
+ * + * double sum = 3; + * + * @return The sum. + */ + @Override + public double getSum() { + return sum_; + } + + public static final int SCHEMA_FIELD_NUMBER = 4; + private int schema_ = 0; + + /** + * + * + *
+         * The schema defines the bucket schema. Currently, valid numbers
+         * are -4 <= n <= 8. They are all for base-2 bucket schemas, where 1
+         * is a bucket boundary in each case, and then each power of two is
+         * divided into 2^n logarithmic buckets. Or in other words, each
+         * bucket boundary is the previous boundary times 2^(2^-n). In the
+         * future, more bucket schemas may be added using numbers < -4 or >
+         * 8.
+         * 
+ * + * sint32 schema = 4; + * + * @return The schema. + */ + @Override + public int getSchema() { + return schema_; + } + + public static final int ZERO_THRESHOLD_FIELD_NUMBER = 5; + private double zeroThreshold_ = 0D; + + /** + * + * + *
+         * Breadth of the zero bucket.
+         * 
+ * + * double zero_threshold = 5; + * + * @return The zeroThreshold. + */ + @Override + public double getZeroThreshold() { + return zeroThreshold_; + } + + public static final int ZERO_COUNT_INT_FIELD_NUMBER = 6; + + /** + * uint64 zero_count_int = 6; + * + * @return Whether the zeroCountInt field is set. + */ + @Override + public boolean hasZeroCountInt() { + return zeroCountCase_ == 6; + } + + /** + * uint64 zero_count_int = 6; + * + * @return The zeroCountInt. + */ + @Override + public long getZeroCountInt() { + if (zeroCountCase_ == 6) { + return (Long) zeroCount_; + } + return 0L; + } + + public static final int ZERO_COUNT_FLOAT_FIELD_NUMBER = 7; + + /** + * double zero_count_float = 7; + * + * @return Whether the zeroCountFloat field is set. + */ + @Override + public boolean hasZeroCountFloat() { + return zeroCountCase_ == 7; + } + + /** + * double zero_count_float = 7; + * + * @return The zeroCountFloat. + */ + @Override + public double getZeroCountFloat() { + if (zeroCountCase_ == 7) { + return (Double) zeroCount_; + } + return 0D; + } + + public static final int NEGATIVE_SPANS_FIELD_NUMBER = 8; + + @SuppressWarnings("serial") + private java.util.List negativeSpans_; + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getNegativeSpansList() { + return negativeSpans_; + } + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getNegativeSpansOrBuilderList() { + return negativeSpans_; + } + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + @Override + public int getNegativeSpansCount() { + return negativeSpans_.size(); + } + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.BucketSpan getNegativeSpans(int index) { + return negativeSpans_.get(index); + } + + /** + * + * + *
+         * Negative Buckets.
+         * 
+ * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.BucketSpanOrBuilder getNegativeSpansOrBuilder(int index) { + return negativeSpans_.get(index); + } + + public static final int NEGATIVE_DELTAS_FIELD_NUMBER = 9; + + @SuppressWarnings("serial") + private com.google.protobuf.Internal.LongList negativeDeltas_ = emptyLongList(); + + /** + * + * + *
+         * Use either "negative_deltas" or "negative_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return A list containing the negativeDeltas. + */ + @Override + public java.util.List getNegativeDeltasList() { + return negativeDeltas_; + } + + /** + * + * + *
+         * Use either "negative_deltas" or "negative_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return The count of negativeDeltas. + */ + public int getNegativeDeltasCount() { + return negativeDeltas_.size(); + } + + /** + * + * + *
+         * Use either "negative_deltas" or "negative_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @param index The index of the element to return. + * @return The negativeDeltas at the given index. + */ + public long getNegativeDeltas(int index) { + return negativeDeltas_.getLong(index); + } + + private int negativeDeltasMemoizedSerializedSize = -1; + + public static final int NEGATIVE_COUNTS_FIELD_NUMBER = 10; + + @SuppressWarnings("serial") + private com.google.protobuf.Internal.DoubleList negativeCounts_ = emptyDoubleList(); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double negative_counts = 10; + * + * @return A list containing the negativeCounts. + */ + @Override + public java.util.List getNegativeCountsList() { + return negativeCounts_; + } + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double negative_counts = 10; + * + * @return The count of negativeCounts. + */ + public int getNegativeCountsCount() { + return negativeCounts_.size(); + } + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double negative_counts = 10; + * + * @param index The index of the element to return. + * @return The negativeCounts at the given index. + */ + public double getNegativeCounts(int index) { + return negativeCounts_.getDouble(index); + } + + private int negativeCountsMemoizedSerializedSize = -1; + + public static final int POSITIVE_SPANS_FIELD_NUMBER = 11; + + @SuppressWarnings("serial") + private java.util.List positiveSpans_; + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getPositiveSpansList() { + return positiveSpans_; + } + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getPositiveSpansOrBuilderList() { + return positiveSpans_; + } + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + @Override + public int getPositiveSpansCount() { + return positiveSpans_.size(); + } + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.BucketSpan getPositiveSpans(int index) { + return positiveSpans_.get(index); + } + + /** + * + * + *
+         * Positive Buckets.
+         * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.BucketSpanOrBuilder getPositiveSpansOrBuilder(int index) { + return positiveSpans_.get(index); + } + + public static final int POSITIVE_DELTAS_FIELD_NUMBER = 12; + + @SuppressWarnings("serial") + private com.google.protobuf.Internal.LongList positiveDeltas_ = emptyLongList(); + + /** + * + * + *
+         * Use either "positive_deltas" or "positive_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return A list containing the positiveDeltas. + */ + @Override + public java.util.List getPositiveDeltasList() { + return positiveDeltas_; + } + + /** + * + * + *
+         * Use either "positive_deltas" or "positive_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return The count of positiveDeltas. + */ + public int getPositiveDeltasCount() { + return positiveDeltas_.size(); + } + + /** + * + * + *
+         * Use either "positive_deltas" or "positive_counts", the former for
+         * regular histograms with integer counts, the latter for float
+         * histograms.
+         * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @param index The index of the element to return. + * @return The positiveDeltas at the given index. + */ + public long getPositiveDeltas(int index) { + return positiveDeltas_.getLong(index); + } + + private int positiveDeltasMemoizedSerializedSize = -1; + + public static final int POSITIVE_COUNTS_FIELD_NUMBER = 13; + + @SuppressWarnings("serial") + private com.google.protobuf.Internal.DoubleList positiveCounts_ = emptyDoubleList(); + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double positive_counts = 13; + * + * @return A list containing the positiveCounts. + */ + @Override + public java.util.List getPositiveCountsList() { + return positiveCounts_; + } + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double positive_counts = 13; + * + * @return The count of positiveCounts. + */ + public int getPositiveCountsCount() { + return positiveCounts_.size(); + } + + /** + * + * + *
+         * Absolute count of each bucket.
+         * 
+ * + * repeated double positive_counts = 13; + * + * @param index The index of the element to return. + * @return The positiveCounts at the given index. + */ + public double getPositiveCounts(int index) { + return positiveCounts_.getDouble(index); + } + + private int positiveCountsMemoizedSerializedSize = -1; + + public static final int RESET_HINT_FIELD_NUMBER = 14; + private int resetHint_ = 0; + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return The enum numeric value on the wire for resetHint. + */ + @Override + public int getResetHintValue() { + return resetHint_; + } + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return The resetHint. + */ + @Override + public Types.Histogram.ResetHint getResetHint() { + Types.Histogram.ResetHint result = Types.Histogram.ResetHint.forNumber(resetHint_); + return result == null ? Types.Histogram.ResetHint.UNRECOGNIZED : result; + } + + public static final int TIMESTAMP_FIELD_NUMBER = 15; + private long timestamp_ = 0L; + + /** + * + * + *
+         * timestamp is in ms format, see model/timestamp/timestamp.go for
+         * conversion from time.Time to Prometheus timestamp.
+         * 
+ * + * int64 timestamp = 15; + * + * @return The timestamp. + */ + @Override + public long getTimestamp() { + return timestamp_; + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (countCase_ == 1) { + output.writeUInt64(1, (long) ((Long) count_)); + } + if (countCase_ == 2) { + output.writeDouble(2, (double) ((Double) count_)); + } + if (Double.doubleToRawLongBits(sum_) != 0) { + output.writeDouble(3, sum_); + } + if (schema_ != 0) { + output.writeSInt32(4, schema_); + } + if (Double.doubleToRawLongBits(zeroThreshold_) != 0) { + output.writeDouble(5, zeroThreshold_); + } + if (zeroCountCase_ == 6) { + output.writeUInt64(6, (long) ((Long) zeroCount_)); + } + if (zeroCountCase_ == 7) { + output.writeDouble(7, (double) ((Double) zeroCount_)); + } + for (int i = 0; i < negativeSpans_.size(); i++) { + output.writeMessage(8, negativeSpans_.get(i)); + } + if (getNegativeDeltasList().size() > 0) { + output.writeUInt32NoTag(74); + output.writeUInt32NoTag(negativeDeltasMemoizedSerializedSize); + } + for (int i = 0; i < negativeDeltas_.size(); i++) { + output.writeSInt64NoTag(negativeDeltas_.getLong(i)); + } + if (getNegativeCountsList().size() > 0) { + output.writeUInt32NoTag(82); + output.writeUInt32NoTag(negativeCountsMemoizedSerializedSize); + } + for (int i = 0; i < negativeCounts_.size(); i++) { + output.writeDoubleNoTag(negativeCounts_.getDouble(i)); + } + for (int i = 0; i < positiveSpans_.size(); i++) { + output.writeMessage(11, positiveSpans_.get(i)); + } + if (getPositiveDeltasList().size() > 0) { + output.writeUInt32NoTag(98); + output.writeUInt32NoTag(positiveDeltasMemoizedSerializedSize); + } + for (int i = 0; i < positiveDeltas_.size(); i++) { + output.writeSInt64NoTag(positiveDeltas_.getLong(i)); + } + if (getPositiveCountsList().size() > 0) { + output.writeUInt32NoTag(106); + output.writeUInt32NoTag(positiveCountsMemoizedSerializedSize); + } + for (int i = 0; i < positiveCounts_.size(); i++) { + output.writeDoubleNoTag(positiveCounts_.getDouble(i)); + } + if (resetHint_ != Types.Histogram.ResetHint.UNKNOWN.getNumber()) { + output.writeEnum(14, resetHint_); + } + if (timestamp_ != 0L) { + output.writeInt64(15, timestamp_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + if (countCase_ == 1) { + size += + com.google.protobuf.CodedOutputStream.computeUInt64Size( + 1, (long) ((Long) count_)); + } + if (countCase_ == 2) { + size += + com.google.protobuf.CodedOutputStream.computeDoubleSize( + 2, (double) ((Double) count_)); + } + if (Double.doubleToRawLongBits(sum_) != 0) { + size += com.google.protobuf.CodedOutputStream.computeDoubleSize(3, sum_); + } + if (schema_ != 0) { + size += com.google.protobuf.CodedOutputStream.computeSInt32Size(4, schema_); + } + if (Double.doubleToRawLongBits(zeroThreshold_) != 0) { + size += com.google.protobuf.CodedOutputStream.computeDoubleSize(5, zeroThreshold_); + } + if (zeroCountCase_ == 6) { + size += + com.google.protobuf.CodedOutputStream.computeUInt64Size( + 6, (long) ((Long) zeroCount_)); + } + if (zeroCountCase_ == 7) { + size += + com.google.protobuf.CodedOutputStream.computeDoubleSize( + 7, (double) ((Double) zeroCount_)); + } + for (int i = 0; i < negativeSpans_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 8, negativeSpans_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < negativeDeltas_.size(); i++) { + dataSize += + com.google.protobuf.CodedOutputStream.computeSInt64SizeNoTag( + negativeDeltas_.getLong(i)); + } + size += dataSize; + if (!getNegativeDeltasList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag(dataSize); + } + negativeDeltasMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + dataSize = 8 * getNegativeCountsList().size(); + size += dataSize; + if (!getNegativeCountsList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag(dataSize); + } + negativeCountsMemoizedSerializedSize = dataSize; + } + for (int i = 0; i < positiveSpans_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 11, positiveSpans_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < positiveDeltas_.size(); i++) { + dataSize += + com.google.protobuf.CodedOutputStream.computeSInt64SizeNoTag( + positiveDeltas_.getLong(i)); + } + size += dataSize; + if (!getPositiveDeltasList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag(dataSize); + } + positiveDeltasMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + dataSize = 8 * getPositiveCountsList().size(); + size += dataSize; + if (!getPositiveCountsList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream.computeInt32SizeNoTag(dataSize); + } + positiveCountsMemoizedSerializedSize = dataSize; + } + if (resetHint_ != Types.Histogram.ResetHint.UNKNOWN.getNumber()) { + size += com.google.protobuf.CodedOutputStream.computeEnumSize(14, resetHint_); + } + if (timestamp_ != 0L) { + size += com.google.protobuf.CodedOutputStream.computeInt64Size(15, timestamp_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.Histogram)) { + return super.equals(obj); + } + Types.Histogram other = (Types.Histogram) obj; + + if (Double.doubleToLongBits(getSum()) != Double.doubleToLongBits(other.getSum())) { + return false; + } + if (getSchema() != other.getSchema()) { + return false; + } + if (Double.doubleToLongBits(getZeroThreshold()) + != Double.doubleToLongBits(other.getZeroThreshold())) { + return false; + } + if (!getNegativeSpansList().equals(other.getNegativeSpansList())) { + return false; + } + if (!getNegativeDeltasList().equals(other.getNegativeDeltasList())) { + return false; + } + if (!getNegativeCountsList().equals(other.getNegativeCountsList())) { + return false; + } + if (!getPositiveSpansList().equals(other.getPositiveSpansList())) { + return false; + } + if (!getPositiveDeltasList().equals(other.getPositiveDeltasList())) { + return false; + } + if (!getPositiveCountsList().equals(other.getPositiveCountsList())) { + return false; + } + if (resetHint_ != other.resetHint_) { + return false; + } + if (getTimestamp() != other.getTimestamp()) { + return false; + } + if (!getCountCase().equals(other.getCountCase())) { + return false; + } + switch (countCase_) { + case 1: + if (getCountInt() != other.getCountInt()) { + return false; + } + break; + case 2: + if (Double.doubleToLongBits(getCountFloat()) + != Double.doubleToLongBits(other.getCountFloat())) { + return false; + } + break; + case 0: + default: + } + if (!getZeroCountCase().equals(other.getZeroCountCase())) { + return false; + } + switch (zeroCountCase_) { + case 6: + if (getZeroCountInt() != other.getZeroCountInt()) { + return false; + } + break; + case 7: + if (Double.doubleToLongBits(getZeroCountFloat()) + != Double.doubleToLongBits(other.getZeroCountFloat())) { + return false; + } + break; + case 0: + default: + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + SUM_FIELD_NUMBER; + hash = + (53 * hash) + + com.google.protobuf.Internal.hashLong( + Double.doubleToLongBits(getSum())); + hash = (37 * hash) + SCHEMA_FIELD_NUMBER; + hash = (53 * hash) + getSchema(); + hash = (37 * hash) + ZERO_THRESHOLD_FIELD_NUMBER; + hash = + (53 * hash) + + com.google.protobuf.Internal.hashLong( + Double.doubleToLongBits(getZeroThreshold())); + if (getNegativeSpansCount() > 0) { + hash = (37 * hash) + NEGATIVE_SPANS_FIELD_NUMBER; + hash = (53 * hash) + getNegativeSpansList().hashCode(); + } + if (getNegativeDeltasCount() > 0) { + hash = (37 * hash) + NEGATIVE_DELTAS_FIELD_NUMBER; + hash = (53 * hash) + getNegativeDeltasList().hashCode(); + } + if (getNegativeCountsCount() > 0) { + hash = (37 * hash) + NEGATIVE_COUNTS_FIELD_NUMBER; + hash = (53 * hash) + getNegativeCountsList().hashCode(); + } + if (getPositiveSpansCount() > 0) { + hash = (37 * hash) + POSITIVE_SPANS_FIELD_NUMBER; + hash = (53 * hash) + getPositiveSpansList().hashCode(); + } + if (getPositiveDeltasCount() > 0) { + hash = (37 * hash) + POSITIVE_DELTAS_FIELD_NUMBER; + hash = (53 * hash) + getPositiveDeltasList().hashCode(); + } + if (getPositiveCountsCount() > 0) { + hash = (37 * hash) + POSITIVE_COUNTS_FIELD_NUMBER; + hash = (53 * hash) + getPositiveCountsList().hashCode(); + } + hash = (37 * hash) + RESET_HINT_FIELD_NUMBER; + hash = (53 * hash) + resetHint_; + hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getTimestamp()); + switch (countCase_) { + case 1: + hash = (37 * hash) + COUNT_INT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getCountInt()); + break; + case 2: + hash = (37 * hash) + COUNT_FLOAT_FIELD_NUMBER; + hash = + (53 * hash) + + com.google.protobuf.Internal.hashLong( + Double.doubleToLongBits(getCountFloat())); + break; + case 0: + default: + } + switch (zeroCountCase_) { + case 6: + hash = (37 * hash) + ZERO_COUNT_INT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong(getZeroCountInt()); + break; + case 7: + hash = (37 * hash) + ZERO_COUNT_FLOAT_FIELD_NUMBER; + hash = + (53 * hash) + + com.google.protobuf.Internal.hashLong( + Double.doubleToLongBits(getZeroCountFloat())); + break; + case 0: + default: + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.Histogram parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Histogram parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Histogram parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Histogram parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Histogram parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Histogram parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Histogram parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Histogram parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Histogram parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.Histogram parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Histogram parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Histogram parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.Histogram prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** + * + * + *
+         * A native histogram, also known as a sparse histogram.
+         * Original design doc:
+         * https://docs.google.com/document/d/1cLNv3aufPZb3fNfaJgdaRBZsInZKKIHo9E6HinJVbpM/edit
+         * The appendix of this design doc also explains the concept of float
+         * histograms. This Histogram message can represent both, the usual
+         * integer histogram as well as a float histogram.
+         * 
+ * + *

Protobuf type {@code prometheus.Histogram} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.Histogram) + Types.HistogramOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Histogram_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Histogram_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Histogram.class, Types.Histogram.Builder.class); + } + + // Construct using Types.Histogram.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + sum_ = 0D; + schema_ = 0; + zeroThreshold_ = 0D; + if (negativeSpansBuilder_ == null) { + negativeSpans_ = java.util.Collections.emptyList(); + } else { + negativeSpans_ = null; + negativeSpansBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000080); + negativeDeltas_ = emptyLongList(); + negativeCounts_ = emptyDoubleList(); + if (positiveSpansBuilder_ == null) { + positiveSpans_ = java.util.Collections.emptyList(); + } else { + positiveSpans_ = null; + positiveSpansBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000400); + positiveDeltas_ = emptyLongList(); + positiveCounts_ = emptyDoubleList(); + resetHint_ = 0; + timestamp_ = 0L; + countCase_ = 0; + count_ = null; + zeroCountCase_ = 0; + zeroCount_ = null; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_Histogram_descriptor; + } + + @Override + public Types.Histogram getDefaultInstanceForType() { + return Types.Histogram.getDefaultInstance(); + } + + @Override + public Types.Histogram build() { + Types.Histogram result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.Histogram buildPartial() { + Types.Histogram result = new Types.Histogram(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + buildPartialOneofs(result); + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Types.Histogram result) { + if (negativeSpansBuilder_ == null) { + if (((bitField0_ & 0x00000080) != 0)) { + negativeSpans_ = java.util.Collections.unmodifiableList(negativeSpans_); + bitField0_ = (bitField0_ & ~0x00000080); + } + result.negativeSpans_ = negativeSpans_; + } else { + result.negativeSpans_ = negativeSpansBuilder_.build(); + } + if (positiveSpansBuilder_ == null) { + if (((bitField0_ & 0x00000400) != 0)) { + positiveSpans_ = java.util.Collections.unmodifiableList(positiveSpans_); + bitField0_ = (bitField0_ & ~0x00000400); + } + result.positiveSpans_ = positiveSpans_; + } else { + result.positiveSpans_ = positiveSpansBuilder_.build(); + } + } + + private void buildPartial0(Types.Histogram result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000004) != 0)) { + result.sum_ = sum_; + } + if (((from_bitField0_ & 0x00000008) != 0)) { + result.schema_ = schema_; + } + if (((from_bitField0_ & 0x00000010) != 0)) { + result.zeroThreshold_ = zeroThreshold_; + } + if (((from_bitField0_ & 0x00000100) != 0)) { + negativeDeltas_.makeImmutable(); + result.negativeDeltas_ = negativeDeltas_; + } + if (((from_bitField0_ & 0x00000200) != 0)) { + negativeCounts_.makeImmutable(); + result.negativeCounts_ = negativeCounts_; + } + if (((from_bitField0_ & 0x00000800) != 0)) { + positiveDeltas_.makeImmutable(); + result.positiveDeltas_ = positiveDeltas_; + } + if (((from_bitField0_ & 0x00001000) != 0)) { + positiveCounts_.makeImmutable(); + result.positiveCounts_ = positiveCounts_; + } + if (((from_bitField0_ & 0x00002000) != 0)) { + result.resetHint_ = resetHint_; + } + if (((from_bitField0_ & 0x00004000) != 0)) { + result.timestamp_ = timestamp_; + } + } + + private void buildPartialOneofs(Types.Histogram result) { + result.countCase_ = countCase_; + result.count_ = this.count_; + result.zeroCountCase_ = zeroCountCase_; + result.zeroCount_ = this.zeroCount_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.Histogram) { + return mergeFrom((Types.Histogram) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.Histogram other) { + if (other == Types.Histogram.getDefaultInstance()) { + return this; + } + if (other.getSum() != 0D) { + setSum(other.getSum()); + } + if (other.getSchema() != 0) { + setSchema(other.getSchema()); + } + if (other.getZeroThreshold() != 0D) { + setZeroThreshold(other.getZeroThreshold()); + } + if (negativeSpansBuilder_ == null) { + if (!other.negativeSpans_.isEmpty()) { + if (negativeSpans_.isEmpty()) { + negativeSpans_ = other.negativeSpans_; + bitField0_ = (bitField0_ & ~0x00000080); + } else { + ensureNegativeSpansIsMutable(); + negativeSpans_.addAll(other.negativeSpans_); + } + onChanged(); + } + } else { + if (!other.negativeSpans_.isEmpty()) { + if (negativeSpansBuilder_.isEmpty()) { + negativeSpansBuilder_.dispose(); + negativeSpansBuilder_ = null; + negativeSpans_ = other.negativeSpans_; + bitField0_ = (bitField0_ & ~0x00000080); + negativeSpansBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getNegativeSpansFieldBuilder() + : null; + } else { + negativeSpansBuilder_.addAllMessages(other.negativeSpans_); + } + } + } + if (!other.negativeDeltas_.isEmpty()) { + if (negativeDeltas_.isEmpty()) { + negativeDeltas_ = other.negativeDeltas_; + negativeDeltas_.makeImmutable(); + bitField0_ |= 0x00000100; + } else { + ensureNegativeDeltasIsMutable(); + negativeDeltas_.addAll(other.negativeDeltas_); + } + onChanged(); + } + if (!other.negativeCounts_.isEmpty()) { + if (negativeCounts_.isEmpty()) { + negativeCounts_ = other.negativeCounts_; + negativeCounts_.makeImmutable(); + bitField0_ |= 0x00000200; + } else { + ensureNegativeCountsIsMutable(); + negativeCounts_.addAll(other.negativeCounts_); + } + onChanged(); + } + if (positiveSpansBuilder_ == null) { + if (!other.positiveSpans_.isEmpty()) { + if (positiveSpans_.isEmpty()) { + positiveSpans_ = other.positiveSpans_; + bitField0_ = (bitField0_ & ~0x00000400); + } else { + ensurePositiveSpansIsMutable(); + positiveSpans_.addAll(other.positiveSpans_); + } + onChanged(); + } + } else { + if (!other.positiveSpans_.isEmpty()) { + if (positiveSpansBuilder_.isEmpty()) { + positiveSpansBuilder_.dispose(); + positiveSpansBuilder_ = null; + positiveSpans_ = other.positiveSpans_; + bitField0_ = (bitField0_ & ~0x00000400); + positiveSpansBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getPositiveSpansFieldBuilder() + : null; + } else { + positiveSpansBuilder_.addAllMessages(other.positiveSpans_); + } + } + } + if (!other.positiveDeltas_.isEmpty()) { + if (positiveDeltas_.isEmpty()) { + positiveDeltas_ = other.positiveDeltas_; + positiveDeltas_.makeImmutable(); + bitField0_ |= 0x00000800; + } else { + ensurePositiveDeltasIsMutable(); + positiveDeltas_.addAll(other.positiveDeltas_); + } + onChanged(); + } + if (!other.positiveCounts_.isEmpty()) { + if (positiveCounts_.isEmpty()) { + positiveCounts_ = other.positiveCounts_; + positiveCounts_.makeImmutable(); + bitField0_ |= 0x00001000; + } else { + ensurePositiveCountsIsMutable(); + positiveCounts_.addAll(other.positiveCounts_); + } + onChanged(); + } + if (other.resetHint_ != 0) { + setResetHintValue(other.getResetHintValue()); + } + if (other.getTimestamp() != 0L) { + setTimestamp(other.getTimestamp()); + } + switch (other.getCountCase()) { + case COUNT_INT: + { + setCountInt(other.getCountInt()); + break; + } + case COUNT_FLOAT: + { + setCountFloat(other.getCountFloat()); + break; + } + case COUNT_NOT_SET: + { + break; + } + } + switch (other.getZeroCountCase()) { + case ZERO_COUNT_INT: + { + setZeroCountInt(other.getZeroCountInt()); + break; + } + case ZERO_COUNT_FLOAT: + { + setZeroCountFloat(other.getZeroCountFloat()); + break; + } + case ZEROCOUNT_NOT_SET: + { + break; + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + count_ = input.readUInt64(); + countCase_ = 1; + break; + } // case 8 + case 17: + { + count_ = input.readDouble(); + countCase_ = 2; + break; + } // case 17 + case 25: + { + sum_ = input.readDouble(); + bitField0_ |= 0x00000004; + break; + } // case 25 + case 32: + { + schema_ = input.readSInt32(); + bitField0_ |= 0x00000008; + break; + } // case 32 + case 41: + { + zeroThreshold_ = input.readDouble(); + bitField0_ |= 0x00000010; + break; + } // case 41 + case 48: + { + zeroCount_ = input.readUInt64(); + zeroCountCase_ = 6; + break; + } // case 48 + case 57: + { + zeroCount_ = input.readDouble(); + zeroCountCase_ = 7; + break; + } // case 57 + case 66: + { + Types.BucketSpan m = + input.readMessage( + Types.BucketSpan.parser(), extensionRegistry); + if (negativeSpansBuilder_ == null) { + ensureNegativeSpansIsMutable(); + negativeSpans_.add(m); + } else { + negativeSpansBuilder_.addMessage(m); + } + break; + } // case 66 + case 72: + { + long v = input.readSInt64(); + ensureNegativeDeltasIsMutable(); + negativeDeltas_.addLong(v); + break; + } // case 72 + case 74: + { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + ensureNegativeDeltasIsMutable(); + while (input.getBytesUntilLimit() > 0) { + negativeDeltas_.addLong(input.readSInt64()); + } + input.popLimit(limit); + break; + } // case 74 + case 81: + { + double v = input.readDouble(); + ensureNegativeCountsIsMutable(); + negativeCounts_.addDouble(v); + break; + } // case 81 + case 82: + { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + int alloc = length > 4096 ? 4096 : length; + ensureNegativeCountsIsMutable(alloc / 8); + while (input.getBytesUntilLimit() > 0) { + negativeCounts_.addDouble(input.readDouble()); + } + input.popLimit(limit); + break; + } // case 82 + case 90: + { + Types.BucketSpan m = + input.readMessage( + Types.BucketSpan.parser(), extensionRegistry); + if (positiveSpansBuilder_ == null) { + ensurePositiveSpansIsMutable(); + positiveSpans_.add(m); + } else { + positiveSpansBuilder_.addMessage(m); + } + break; + } // case 90 + case 96: + { + long v = input.readSInt64(); + ensurePositiveDeltasIsMutable(); + positiveDeltas_.addLong(v); + break; + } // case 96 + case 98: + { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + ensurePositiveDeltasIsMutable(); + while (input.getBytesUntilLimit() > 0) { + positiveDeltas_.addLong(input.readSInt64()); + } + input.popLimit(limit); + break; + } // case 98 + case 105: + { + double v = input.readDouble(); + ensurePositiveCountsIsMutable(); + positiveCounts_.addDouble(v); + break; + } // case 105 + case 106: + { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + int alloc = length > 4096 ? 4096 : length; + ensurePositiveCountsIsMutable(alloc / 8); + while (input.getBytesUntilLimit() > 0) { + positiveCounts_.addDouble(input.readDouble()); + } + input.popLimit(limit); + break; + } // case 106 + case 112: + { + resetHint_ = input.readEnum(); + bitField0_ |= 0x00002000; + break; + } // case 112 + case 120: + { + timestamp_ = input.readInt64(); + bitField0_ |= 0x00004000; + break; + } // case 120 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int countCase_ = 0; + private Object count_; + + public CountCase getCountCase() { + return CountCase.forNumber(countCase_); + } + + public Builder clearCount() { + countCase_ = 0; + count_ = null; + onChanged(); + return this; + } + + private int zeroCountCase_ = 0; + private Object zeroCount_; + + public ZeroCountCase getZeroCountCase() { + return ZeroCountCase.forNumber(zeroCountCase_); + } + + public Builder clearZeroCount() { + zeroCountCase_ = 0; + zeroCount_ = null; + onChanged(); + return this; + } + + private int bitField0_; + + /** + * uint64 count_int = 1; + * + * @return Whether the countInt field is set. + */ + public boolean hasCountInt() { + return countCase_ == 1; + } + + /** + * uint64 count_int = 1; + * + * @return The countInt. + */ + public long getCountInt() { + if (countCase_ == 1) { + return (Long) count_; + } + return 0L; + } + + /** + * uint64 count_int = 1; + * + * @param value The countInt to set. + * @return This builder for chaining. + */ + public Builder setCountInt(long value) { + + countCase_ = 1; + count_ = value; + onChanged(); + return this; + } + + /** + * uint64 count_int = 1; + * + * @return This builder for chaining. + */ + public Builder clearCountInt() { + if (countCase_ == 1) { + countCase_ = 0; + count_ = null; + onChanged(); + } + return this; + } + + /** + * double count_float = 2; + * + * @return Whether the countFloat field is set. + */ + public boolean hasCountFloat() { + return countCase_ == 2; + } + + /** + * double count_float = 2; + * + * @return The countFloat. + */ + public double getCountFloat() { + if (countCase_ == 2) { + return (Double) count_; + } + return 0D; + } + + /** + * double count_float = 2; + * + * @param value The countFloat to set. + * @return This builder for chaining. + */ + public Builder setCountFloat(double value) { + + countCase_ = 2; + count_ = value; + onChanged(); + return this; + } + + /** + * double count_float = 2; + * + * @return This builder for chaining. + */ + public Builder clearCountFloat() { + if (countCase_ == 2) { + countCase_ = 0; + count_ = null; + onChanged(); + } + return this; + } + + private double sum_; + + /** + * + * + *

+             * Sum of observations in the histogram.
+             * 
+ * + * double sum = 3; + * + * @return The sum. + */ + @Override + public double getSum() { + return sum_; + } + + /** + * + * + *
+             * Sum of observations in the histogram.
+             * 
+ * + * double sum = 3; + * + * @param value The sum to set. + * @return This builder for chaining. + */ + public Builder setSum(double value) { + + sum_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + /** + * + * + *
+             * Sum of observations in the histogram.
+             * 
+ * + * double sum = 3; + * + * @return This builder for chaining. + */ + public Builder clearSum() { + bitField0_ = (bitField0_ & ~0x00000004); + sum_ = 0D; + onChanged(); + return this; + } + + private int schema_; + + /** + * + * + *
+             * The schema defines the bucket schema. Currently, valid numbers
+             * are -4 <= n <= 8. They are all for base-2 bucket schemas, where 1
+             * is a bucket boundary in each case, and then each power of two is
+             * divided into 2^n logarithmic buckets. Or in other words, each
+             * bucket boundary is the previous boundary times 2^(2^-n). In the
+             * future, more bucket schemas may be added using numbers < -4 or >
+             * 8.
+             * 
+ * + * sint32 schema = 4; + * + * @return The schema. + */ + @Override + public int getSchema() { + return schema_; + } + + /** + * + * + *
+             * The schema defines the bucket schema. Currently, valid numbers
+             * are -4 <= n <= 8. They are all for base-2 bucket schemas, where 1
+             * is a bucket boundary in each case, and then each power of two is
+             * divided into 2^n logarithmic buckets. Or in other words, each
+             * bucket boundary is the previous boundary times 2^(2^-n). In the
+             * future, more bucket schemas may be added using numbers < -4 or >
+             * 8.
+             * 
+ * + * sint32 schema = 4; + * + * @param value The schema to set. + * @return This builder for chaining. + */ + public Builder setSchema(int value) { + + schema_ = value; + bitField0_ |= 0x00000008; + onChanged(); + return this; + } + + /** + * + * + *
+             * The schema defines the bucket schema. Currently, valid numbers
+             * are -4 <= n <= 8. They are all for base-2 bucket schemas, where 1
+             * is a bucket boundary in each case, and then each power of two is
+             * divided into 2^n logarithmic buckets. Or in other words, each
+             * bucket boundary is the previous boundary times 2^(2^-n). In the
+             * future, more bucket schemas may be added using numbers < -4 or >
+             * 8.
+             * 
+ * + * sint32 schema = 4; + * + * @return This builder for chaining. + */ + public Builder clearSchema() { + bitField0_ = (bitField0_ & ~0x00000008); + schema_ = 0; + onChanged(); + return this; + } + + private double zeroThreshold_; + + /** + * + * + *
+             * Breadth of the zero bucket.
+             * 
+ * + * double zero_threshold = 5; + * + * @return The zeroThreshold. + */ + @Override + public double getZeroThreshold() { + return zeroThreshold_; + } + + /** + * + * + *
+             * Breadth of the zero bucket.
+             * 
+ * + * double zero_threshold = 5; + * + * @param value The zeroThreshold to set. + * @return This builder for chaining. + */ + public Builder setZeroThreshold(double value) { + + zeroThreshold_ = value; + bitField0_ |= 0x00000010; + onChanged(); + return this; + } + + /** + * + * + *
+             * Breadth of the zero bucket.
+             * 
+ * + * double zero_threshold = 5; + * + * @return This builder for chaining. + */ + public Builder clearZeroThreshold() { + bitField0_ = (bitField0_ & ~0x00000010); + zeroThreshold_ = 0D; + onChanged(); + return this; + } + + /** + * uint64 zero_count_int = 6; + * + * @return Whether the zeroCountInt field is set. + */ + public boolean hasZeroCountInt() { + return zeroCountCase_ == 6; + } + + /** + * uint64 zero_count_int = 6; + * + * @return The zeroCountInt. + */ + public long getZeroCountInt() { + if (zeroCountCase_ == 6) { + return (Long) zeroCount_; + } + return 0L; + } + + /** + * uint64 zero_count_int = 6; + * + * @param value The zeroCountInt to set. + * @return This builder for chaining. + */ + public Builder setZeroCountInt(long value) { + + zeroCountCase_ = 6; + zeroCount_ = value; + onChanged(); + return this; + } + + /** + * uint64 zero_count_int = 6; + * + * @return This builder for chaining. + */ + public Builder clearZeroCountInt() { + if (zeroCountCase_ == 6) { + zeroCountCase_ = 0; + zeroCount_ = null; + onChanged(); + } + return this; + } + + /** + * double zero_count_float = 7; + * + * @return Whether the zeroCountFloat field is set. + */ + public boolean hasZeroCountFloat() { + return zeroCountCase_ == 7; + } + + /** + * double zero_count_float = 7; + * + * @return The zeroCountFloat. + */ + public double getZeroCountFloat() { + if (zeroCountCase_ == 7) { + return (Double) zeroCount_; + } + return 0D; + } + + /** + * double zero_count_float = 7; + * + * @param value The zeroCountFloat to set. + * @return This builder for chaining. + */ + public Builder setZeroCountFloat(double value) { + + zeroCountCase_ = 7; + zeroCount_ = value; + onChanged(); + return this; + } + + /** + * double zero_count_float = 7; + * + * @return This builder for chaining. + */ + public Builder clearZeroCountFloat() { + if (zeroCountCase_ == 7) { + zeroCountCase_ = 0; + zeroCount_ = null; + onChanged(); + } + return this; + } + + private java.util.List negativeSpans_ = + java.util.Collections.emptyList(); + + private void ensureNegativeSpansIsMutable() { + if (!((bitField0_ & 0x00000080) != 0)) { + negativeSpans_ = new java.util.ArrayList(negativeSpans_); + bitField0_ |= 0x00000080; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.BucketSpan, Types.BucketSpan.Builder, Types.BucketSpanOrBuilder> + negativeSpansBuilder_; + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getNegativeSpansList() { + if (negativeSpansBuilder_ == null) { + return java.util.Collections.unmodifiableList(negativeSpans_); + } else { + return negativeSpansBuilder_.getMessageList(); + } + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public int getNegativeSpansCount() { + if (negativeSpansBuilder_ == null) { + return negativeSpans_.size(); + } else { + return negativeSpansBuilder_.getCount(); + } + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan getNegativeSpans(int index) { + if (negativeSpansBuilder_ == null) { + return negativeSpans_.get(index); + } else { + return negativeSpansBuilder_.getMessage(index); + } + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder setNegativeSpans(int index, Types.BucketSpan value) { + if (negativeSpansBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureNegativeSpansIsMutable(); + negativeSpans_.set(index, value); + onChanged(); + } else { + negativeSpansBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder setNegativeSpans(int index, Types.BucketSpan.Builder builderForValue) { + if (negativeSpansBuilder_ == null) { + ensureNegativeSpansIsMutable(); + negativeSpans_.set(index, builderForValue.build()); + onChanged(); + } else { + negativeSpansBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder addNegativeSpans(Types.BucketSpan value) { + if (negativeSpansBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureNegativeSpansIsMutable(); + negativeSpans_.add(value); + onChanged(); + } else { + negativeSpansBuilder_.addMessage(value); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder addNegativeSpans(int index, Types.BucketSpan value) { + if (negativeSpansBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureNegativeSpansIsMutable(); + negativeSpans_.add(index, value); + onChanged(); + } else { + negativeSpansBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder addNegativeSpans(Types.BucketSpan.Builder builderForValue) { + if (negativeSpansBuilder_ == null) { + ensureNegativeSpansIsMutable(); + negativeSpans_.add(builderForValue.build()); + onChanged(); + } else { + negativeSpansBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder addNegativeSpans(int index, Types.BucketSpan.Builder builderForValue) { + if (negativeSpansBuilder_ == null) { + ensureNegativeSpansIsMutable(); + negativeSpans_.add(index, builderForValue.build()); + onChanged(); + } else { + negativeSpansBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder addAllNegativeSpans(Iterable values) { + if (negativeSpansBuilder_ == null) { + ensureNegativeSpansIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, negativeSpans_); + onChanged(); + } else { + negativeSpansBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder clearNegativeSpans() { + if (negativeSpansBuilder_ == null) { + negativeSpans_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + } else { + negativeSpansBuilder_.clear(); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Builder removeNegativeSpans(int index) { + if (negativeSpansBuilder_ == null) { + ensureNegativeSpansIsMutable(); + negativeSpans_.remove(index); + onChanged(); + } else { + negativeSpansBuilder_.remove(index); + } + return this; + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan.Builder getNegativeSpansBuilder(int index) { + return getNegativeSpansFieldBuilder().getBuilder(index); + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpanOrBuilder getNegativeSpansOrBuilder(int index) { + if (negativeSpansBuilder_ == null) { + return negativeSpans_.get(index); + } else { + return negativeSpansBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List + getNegativeSpansOrBuilderList() { + if (negativeSpansBuilder_ != null) { + return negativeSpansBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(negativeSpans_); + } + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan.Builder addNegativeSpansBuilder() { + return getNegativeSpansFieldBuilder() + .addBuilder(Types.BucketSpan.getDefaultInstance()); + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan.Builder addNegativeSpansBuilder(int index) { + return getNegativeSpansFieldBuilder() + .addBuilder(index, Types.BucketSpan.getDefaultInstance()); + } + + /** + * + * + *
+             * Negative Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan negative_spans = 8 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getNegativeSpansBuilderList() { + return getNegativeSpansFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.BucketSpan, Types.BucketSpan.Builder, Types.BucketSpanOrBuilder> + getNegativeSpansFieldBuilder() { + if (negativeSpansBuilder_ == null) { + negativeSpansBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.BucketSpan, + Types.BucketSpan.Builder, + Types.BucketSpanOrBuilder>( + negativeSpans_, + ((bitField0_ & 0x00000080) != 0), + getParentForChildren(), + isClean()); + negativeSpans_ = null; + } + return negativeSpansBuilder_; + } + + private com.google.protobuf.Internal.LongList negativeDeltas_ = emptyLongList(); + + private void ensureNegativeDeltasIsMutable() { + if (!negativeDeltas_.isModifiable()) { + negativeDeltas_ = makeMutableCopy(negativeDeltas_); + } + bitField0_ |= 0x00000100; + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return A list containing the negativeDeltas. + */ + public java.util.List getNegativeDeltasList() { + negativeDeltas_.makeImmutable(); + return negativeDeltas_; + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return The count of negativeDeltas. + */ + public int getNegativeDeltasCount() { + return negativeDeltas_.size(); + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @param index The index of the element to return. + * @return The negativeDeltas at the given index. + */ + public long getNegativeDeltas(int index) { + return negativeDeltas_.getLong(index); + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @param index The index to set the value at. + * @param value The negativeDeltas to set. + * @return This builder for chaining. + */ + public Builder setNegativeDeltas(int index, long value) { + + ensureNegativeDeltasIsMutable(); + negativeDeltas_.setLong(index, value); + bitField0_ |= 0x00000100; + onChanged(); + return this; + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @param value The negativeDeltas to add. + * @return This builder for chaining. + */ + public Builder addNegativeDeltas(long value) { + + ensureNegativeDeltasIsMutable(); + negativeDeltas_.addLong(value); + bitField0_ |= 0x00000100; + onChanged(); + return this; + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @param values The negativeDeltas to add. + * @return This builder for chaining. + */ + public Builder addAllNegativeDeltas(Iterable values) { + ensureNegativeDeltasIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, negativeDeltas_); + bitField0_ |= 0x00000100; + onChanged(); + return this; + } + + /** + * + * + *
+             * Use either "negative_deltas" or "negative_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 negative_deltas = 9; + * + * @return This builder for chaining. + */ + public Builder clearNegativeDeltas() { + negativeDeltas_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000100); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.DoubleList negativeCounts_ = emptyDoubleList(); + + private void ensureNegativeCountsIsMutable() { + if (!negativeCounts_.isModifiable()) { + negativeCounts_ = makeMutableCopy(negativeCounts_); + } + bitField0_ |= 0x00000200; + } + + private void ensureNegativeCountsIsMutable(int capacity) { + if (!negativeCounts_.isModifiable()) { + negativeCounts_ = makeMutableCopy(negativeCounts_, capacity); + } + bitField0_ |= 0x00000200; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @return A list containing the negativeCounts. + */ + public java.util.List getNegativeCountsList() { + negativeCounts_.makeImmutable(); + return negativeCounts_; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @return The count of negativeCounts. + */ + public int getNegativeCountsCount() { + return negativeCounts_.size(); + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @param index The index of the element to return. + * @return The negativeCounts at the given index. + */ + public double getNegativeCounts(int index) { + return negativeCounts_.getDouble(index); + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @param index The index to set the value at. + * @param value The negativeCounts to set. + * @return This builder for chaining. + */ + public Builder setNegativeCounts(int index, double value) { + + ensureNegativeCountsIsMutable(); + negativeCounts_.setDouble(index, value); + bitField0_ |= 0x00000200; + onChanged(); + return this; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @param value The negativeCounts to add. + * @return This builder for chaining. + */ + public Builder addNegativeCounts(double value) { + + ensureNegativeCountsIsMutable(); + negativeCounts_.addDouble(value); + bitField0_ |= 0x00000200; + onChanged(); + return this; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @param values The negativeCounts to add. + * @return This builder for chaining. + */ + public Builder addAllNegativeCounts(Iterable values) { + ensureNegativeCountsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, negativeCounts_); + bitField0_ |= 0x00000200; + onChanged(); + return this; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double negative_counts = 10; + * + * @return This builder for chaining. + */ + public Builder clearNegativeCounts() { + negativeCounts_ = emptyDoubleList(); + bitField0_ = (bitField0_ & ~0x00000200); + onChanged(); + return this; + } + + private java.util.List positiveSpans_ = + java.util.Collections.emptyList(); + + private void ensurePositiveSpansIsMutable() { + if (!((bitField0_ & 0x00000400) != 0)) { + positiveSpans_ = new java.util.ArrayList(positiveSpans_); + bitField0_ |= 0x00000400; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.BucketSpan, Types.BucketSpan.Builder, Types.BucketSpanOrBuilder> + positiveSpansBuilder_; + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getPositiveSpansList() { + if (positiveSpansBuilder_ == null) { + return java.util.Collections.unmodifiableList(positiveSpans_); + } else { + return positiveSpansBuilder_.getMessageList(); + } + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public int getPositiveSpansCount() { + if (positiveSpansBuilder_ == null) { + return positiveSpans_.size(); + } else { + return positiveSpansBuilder_.getCount(); + } + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan getPositiveSpans(int index) { + if (positiveSpansBuilder_ == null) { + return positiveSpans_.get(index); + } else { + return positiveSpansBuilder_.getMessage(index); + } + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder setPositiveSpans(int index, Types.BucketSpan value) { + if (positiveSpansBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePositiveSpansIsMutable(); + positiveSpans_.set(index, value); + onChanged(); + } else { + positiveSpansBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder setPositiveSpans(int index, Types.BucketSpan.Builder builderForValue) { + if (positiveSpansBuilder_ == null) { + ensurePositiveSpansIsMutable(); + positiveSpans_.set(index, builderForValue.build()); + onChanged(); + } else { + positiveSpansBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder addPositiveSpans(Types.BucketSpan value) { + if (positiveSpansBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePositiveSpansIsMutable(); + positiveSpans_.add(value); + onChanged(); + } else { + positiveSpansBuilder_.addMessage(value); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder addPositiveSpans(int index, Types.BucketSpan value) { + if (positiveSpansBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePositiveSpansIsMutable(); + positiveSpans_.add(index, value); + onChanged(); + } else { + positiveSpansBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder addPositiveSpans(Types.BucketSpan.Builder builderForValue) { + if (positiveSpansBuilder_ == null) { + ensurePositiveSpansIsMutable(); + positiveSpans_.add(builderForValue.build()); + onChanged(); + } else { + positiveSpansBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder addPositiveSpans(int index, Types.BucketSpan.Builder builderForValue) { + if (positiveSpansBuilder_ == null) { + ensurePositiveSpansIsMutable(); + positiveSpans_.add(index, builderForValue.build()); + onChanged(); + } else { + positiveSpansBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder addAllPositiveSpans(Iterable values) { + if (positiveSpansBuilder_ == null) { + ensurePositiveSpansIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, positiveSpans_); + onChanged(); + } else { + positiveSpansBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder clearPositiveSpans() { + if (positiveSpansBuilder_ == null) { + positiveSpans_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000400); + onChanged(); + } else { + positiveSpansBuilder_.clear(); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Builder removePositiveSpans(int index) { + if (positiveSpansBuilder_ == null) { + ensurePositiveSpansIsMutable(); + positiveSpans_.remove(index); + onChanged(); + } else { + positiveSpansBuilder_.remove(index); + } + return this; + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan.Builder getPositiveSpansBuilder(int index) { + return getPositiveSpansFieldBuilder().getBuilder(index); + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpanOrBuilder getPositiveSpansOrBuilder(int index) { + if (positiveSpansBuilder_ == null) { + return positiveSpans_.get(index); + } else { + return positiveSpansBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List + getPositiveSpansOrBuilderList() { + if (positiveSpansBuilder_ != null) { + return positiveSpansBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(positiveSpans_); + } + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan.Builder addPositiveSpansBuilder() { + return getPositiveSpansFieldBuilder() + .addBuilder(Types.BucketSpan.getDefaultInstance()); + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public Types.BucketSpan.Builder addPositiveSpansBuilder(int index) { + return getPositiveSpansFieldBuilder() + .addBuilder(index, Types.BucketSpan.getDefaultInstance()); + } + + /** + * + * + *
+             * Positive Buckets.
+             * 
+ * + * + * repeated .prometheus.BucketSpan positive_spans = 11 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getPositiveSpansBuilderList() { + return getPositiveSpansFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.BucketSpan, Types.BucketSpan.Builder, Types.BucketSpanOrBuilder> + getPositiveSpansFieldBuilder() { + if (positiveSpansBuilder_ == null) { + positiveSpansBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.BucketSpan, + Types.BucketSpan.Builder, + Types.BucketSpanOrBuilder>( + positiveSpans_, + ((bitField0_ & 0x00000400) != 0), + getParentForChildren(), + isClean()); + positiveSpans_ = null; + } + return positiveSpansBuilder_; + } + + private com.google.protobuf.Internal.LongList positiveDeltas_ = emptyLongList(); + + private void ensurePositiveDeltasIsMutable() { + if (!positiveDeltas_.isModifiable()) { + positiveDeltas_ = makeMutableCopy(positiveDeltas_); + } + bitField0_ |= 0x00000800; + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return A list containing the positiveDeltas. + */ + public java.util.List getPositiveDeltasList() { + positiveDeltas_.makeImmutable(); + return positiveDeltas_; + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return The count of positiveDeltas. + */ + public int getPositiveDeltasCount() { + return positiveDeltas_.size(); + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @param index The index of the element to return. + * @return The positiveDeltas at the given index. + */ + public long getPositiveDeltas(int index) { + return positiveDeltas_.getLong(index); + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @param index The index to set the value at. + * @param value The positiveDeltas to set. + * @return This builder for chaining. + */ + public Builder setPositiveDeltas(int index, long value) { + + ensurePositiveDeltasIsMutable(); + positiveDeltas_.setLong(index, value); + bitField0_ |= 0x00000800; + onChanged(); + return this; + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @param value The positiveDeltas to add. + * @return This builder for chaining. + */ + public Builder addPositiveDeltas(long value) { + + ensurePositiveDeltasIsMutable(); + positiveDeltas_.addLong(value); + bitField0_ |= 0x00000800; + onChanged(); + return this; + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @param values The positiveDeltas to add. + * @return This builder for chaining. + */ + public Builder addAllPositiveDeltas(Iterable values) { + ensurePositiveDeltasIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, positiveDeltas_); + bitField0_ |= 0x00000800; + onChanged(); + return this; + } + + /** + * + * + *
+             * Use either "positive_deltas" or "positive_counts", the former for
+             * regular histograms with integer counts, the latter for float
+             * histograms.
+             * 
+ * + * repeated sint64 positive_deltas = 12; + * + * @return This builder for chaining. + */ + public Builder clearPositiveDeltas() { + positiveDeltas_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000800); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.DoubleList positiveCounts_ = emptyDoubleList(); + + private void ensurePositiveCountsIsMutable() { + if (!positiveCounts_.isModifiable()) { + positiveCounts_ = makeMutableCopy(positiveCounts_); + } + bitField0_ |= 0x00001000; + } + + private void ensurePositiveCountsIsMutable(int capacity) { + if (!positiveCounts_.isModifiable()) { + positiveCounts_ = makeMutableCopy(positiveCounts_, capacity); + } + bitField0_ |= 0x00001000; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @return A list containing the positiveCounts. + */ + public java.util.List getPositiveCountsList() { + positiveCounts_.makeImmutable(); + return positiveCounts_; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @return The count of positiveCounts. + */ + public int getPositiveCountsCount() { + return positiveCounts_.size(); + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @param index The index of the element to return. + * @return The positiveCounts at the given index. + */ + public double getPositiveCounts(int index) { + return positiveCounts_.getDouble(index); + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @param index The index to set the value at. + * @param value The positiveCounts to set. + * @return This builder for chaining. + */ + public Builder setPositiveCounts(int index, double value) { + + ensurePositiveCountsIsMutable(); + positiveCounts_.setDouble(index, value); + bitField0_ |= 0x00001000; + onChanged(); + return this; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @param value The positiveCounts to add. + * @return This builder for chaining. + */ + public Builder addPositiveCounts(double value) { + + ensurePositiveCountsIsMutable(); + positiveCounts_.addDouble(value); + bitField0_ |= 0x00001000; + onChanged(); + return this; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @param values The positiveCounts to add. + * @return This builder for chaining. + */ + public Builder addAllPositiveCounts(Iterable values) { + ensurePositiveCountsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, positiveCounts_); + bitField0_ |= 0x00001000; + onChanged(); + return this; + } + + /** + * + * + *
+             * Absolute count of each bucket.
+             * 
+ * + * repeated double positive_counts = 13; + * + * @return This builder for chaining. + */ + public Builder clearPositiveCounts() { + positiveCounts_ = emptyDoubleList(); + bitField0_ = (bitField0_ & ~0x00001000); + onChanged(); + return this; + } + + private int resetHint_ = 0; + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return The enum numeric value on the wire for resetHint. + */ + @Override + public int getResetHintValue() { + return resetHint_; + } + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @param value The enum numeric value on the wire for resetHint to set. + * @return This builder for chaining. + */ + public Builder setResetHintValue(int value) { + resetHint_ = value; + bitField0_ |= 0x00002000; + onChanged(); + return this; + } + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return The resetHint. + */ + @Override + public Types.Histogram.ResetHint getResetHint() { + Types.Histogram.ResetHint result = Types.Histogram.ResetHint.forNumber(resetHint_); + return result == null ? Types.Histogram.ResetHint.UNRECOGNIZED : result; + } + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @param value The resetHint to set. + * @return This builder for chaining. + */ + public Builder setResetHint(Types.Histogram.ResetHint value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00002000; + resetHint_ = value.getNumber(); + onChanged(); + return this; + } + + /** + * .prometheus.Histogram.ResetHint reset_hint = 14; + * + * @return This builder for chaining. + */ + public Builder clearResetHint() { + bitField0_ = (bitField0_ & ~0x00002000); + resetHint_ = 0; + onChanged(); + return this; + } + + private long timestamp_; + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 15; + * + * @return The timestamp. + */ + @Override + public long getTimestamp() { + return timestamp_; + } + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 15; + * + * @param value The timestamp to set. + * @return This builder for chaining. + */ + public Builder setTimestamp(long value) { + + timestamp_ = value; + bitField0_ |= 0x00004000; + onChanged(); + return this; + } + + /** + * + * + *
+             * timestamp is in ms format, see model/timestamp/timestamp.go for
+             * conversion from time.Time to Prometheus timestamp.
+             * 
+ * + * int64 timestamp = 15; + * + * @return This builder for chaining. + */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00004000); + timestamp_ = 0L; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.Histogram) + } + + // @@protoc_insertion_point(class_scope:prometheus.Histogram) + private static final Types.Histogram DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.Histogram(); + } + + public static Types.Histogram getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public Histogram parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Types.Histogram getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface BucketSpanOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.BucketSpan) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
+         * Gap to previous span, or starting point for 1st span (which can be negative).
+         * 
+ * + * sint32 offset = 1; + * + * @return The offset. + */ + int getOffset(); + + /** + * + * + *
+         * Length of consecutive buckets.
+         * 
+ * + * uint32 length = 2; + * + * @return The length. + */ + int getLength(); + } + + /** + * + * + *
+     * A BucketSpan defines a number of consecutive buckets with their
+     * offset. Logically, it would be more straightforward to include the
+     * bucket counts in the Span. However, the protobuf representation is
+     * more compact in the way the data is structured here (with all the
+     * buckets in a single array separate from the Spans).
+     * 
+ * + *

Protobuf type {@code prometheus.BucketSpan} + */ + public static final class BucketSpan extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.BucketSpan) + BucketSpanOrBuilder { + private static final long serialVersionUID = 0L; + + // Use BucketSpan.newBuilder() to construct. + private BucketSpan(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private BucketSpan() {} + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new BucketSpan(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_BucketSpan_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_BucketSpan_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.BucketSpan.class, Types.BucketSpan.Builder.class); + } + + public static final int OFFSET_FIELD_NUMBER = 1; + private int offset_ = 0; + + /** + * + * + *

+         * Gap to previous span, or starting point for 1st span (which can be negative).
+         * 
+ * + * sint32 offset = 1; + * + * @return The offset. + */ + @Override + public int getOffset() { + return offset_; + } + + public static final int LENGTH_FIELD_NUMBER = 2; + private int length_ = 0; + + /** + * + * + *
+         * Length of consecutive buckets.
+         * 
+ * + * uint32 length = 2; + * + * @return The length. + */ + @Override + public int getLength() { + return length_; + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (offset_ != 0) { + output.writeSInt32(1, offset_); + } + if (length_ != 0) { + output.writeUInt32(2, length_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + if (offset_ != 0) { + size += com.google.protobuf.CodedOutputStream.computeSInt32Size(1, offset_); + } + if (length_ != 0) { + size += com.google.protobuf.CodedOutputStream.computeUInt32Size(2, length_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.BucketSpan)) { + return super.equals(obj); + } + Types.BucketSpan other = (Types.BucketSpan) obj; + + if (getOffset() != other.getOffset()) { + return false; + } + if (getLength() != other.getLength()) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + OFFSET_FIELD_NUMBER; + hash = (53 * hash) + getOffset(); + hash = (37 * hash) + LENGTH_FIELD_NUMBER; + hash = (53 * hash) + getLength(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.BucketSpan parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.BucketSpan parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.BucketSpan parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.BucketSpan parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.BucketSpan parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.BucketSpan parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.BucketSpan parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.BucketSpan parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.BucketSpan parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.BucketSpan parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.BucketSpan parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.BucketSpan parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.BucketSpan prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** + * + * + *
+         * A BucketSpan defines a number of consecutive buckets with their
+         * offset. Logically, it would be more straightforward to include the
+         * bucket counts in the Span. However, the protobuf representation is
+         * more compact in the way the data is structured here (with all the
+         * buckets in a single array separate from the Spans).
+         * 
+ * + *

Protobuf type {@code prometheus.BucketSpan} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.BucketSpan) + Types.BucketSpanOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_BucketSpan_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_BucketSpan_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.BucketSpan.class, Types.BucketSpan.Builder.class); + } + + // Construct using Types.BucketSpan.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + offset_ = 0; + length_ = 0; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_BucketSpan_descriptor; + } + + @Override + public Types.BucketSpan getDefaultInstanceForType() { + return Types.BucketSpan.getDefaultInstance(); + } + + @Override + public Types.BucketSpan build() { + Types.BucketSpan result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.BucketSpan buildPartial() { + Types.BucketSpan result = new Types.BucketSpan(this); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartial0(Types.BucketSpan result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.offset_ = offset_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.length_ = length_; + } + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.BucketSpan) { + return mergeFrom((Types.BucketSpan) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.BucketSpan other) { + if (other == Types.BucketSpan.getDefaultInstance()) { + return this; + } + if (other.getOffset() != 0) { + setOffset(other.getOffset()); + } + if (other.getLength() != 0) { + setLength(other.getLength()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: + { + offset_ = input.readSInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 16: + { + length_ = input.readUInt32(); + bitField0_ |= 0x00000002; + break; + } // case 16 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private int offset_; + + /** + * + * + *

+             * Gap to previous span, or starting point for 1st span (which can be negative).
+             * 
+ * + * sint32 offset = 1; + * + * @return The offset. + */ + @Override + public int getOffset() { + return offset_; + } + + /** + * + * + *
+             * Gap to previous span, or starting point for 1st span (which can be negative).
+             * 
+ * + * sint32 offset = 1; + * + * @param value The offset to set. + * @return This builder for chaining. + */ + public Builder setOffset(int value) { + + offset_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + /** + * + * + *
+             * Gap to previous span, or starting point for 1st span (which can be negative).
+             * 
+ * + * sint32 offset = 1; + * + * @return This builder for chaining. + */ + public Builder clearOffset() { + bitField0_ = (bitField0_ & ~0x00000001); + offset_ = 0; + onChanged(); + return this; + } + + private int length_; + + /** + * + * + *
+             * Length of consecutive buckets.
+             * 
+ * + * uint32 length = 2; + * + * @return The length. + */ + @Override + public int getLength() { + return length_; + } + + /** + * + * + *
+             * Length of consecutive buckets.
+             * 
+ * + * uint32 length = 2; + * + * @param value The length to set. + * @return This builder for chaining. + */ + public Builder setLength(int value) { + + length_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * + * + *
+             * Length of consecutive buckets.
+             * 
+ * + * uint32 length = 2; + * + * @return This builder for chaining. + */ + public Builder clearLength() { + bitField0_ = (bitField0_ & ~0x00000002); + length_ = 0; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.BucketSpan) + } + + // @@protoc_insertion_point(class_scope:prometheus.BucketSpan) + private static final Types.BucketSpan DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.BucketSpan(); + } + + public static Types.BucketSpan getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public BucketSpan parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Types.BucketSpan getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface TimeSeriesOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.TimeSeries) + com.google.protobuf.MessageOrBuilder { + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + java.util.List getLabelsList(); + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + Types.Label getLabels(int index); + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + int getLabelsCount(); + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + java.util.List getLabelsOrBuilderList(); + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + Types.LabelOrBuilder getLabelsOrBuilder(int index); + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + java.util.List getSamplesList(); + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + Types.Sample getSamples(int index); + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + int getSamplesCount(); + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + java.util.List getSamplesOrBuilderList(); + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + Types.SampleOrBuilder getSamplesOrBuilder(int index); + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + java.util.List getExemplarsList(); + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + Types.Exemplar getExemplars(int index); + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + int getExemplarsCount(); + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + java.util.List getExemplarsOrBuilderList(); + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + Types.ExemplarOrBuilder getExemplarsOrBuilder(int index); + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getHistogramsList(); + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + Types.Histogram getHistograms(int index); + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + int getHistogramsCount(); + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + java.util.List getHistogramsOrBuilderList(); + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + Types.HistogramOrBuilder getHistogramsOrBuilder(int index); + } + + /** + * + * + *
+     * TimeSeries represents samples and labels for a single time series.
+     * 
+ * + *

Protobuf type {@code prometheus.TimeSeries} + */ + public static final class TimeSeries extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.TimeSeries) + TimeSeriesOrBuilder { + private static final long serialVersionUID = 0L; + + // Use TimeSeries.newBuilder() to construct. + private TimeSeries(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private TimeSeries() { + labels_ = java.util.Collections.emptyList(); + samples_ = java.util.Collections.emptyList(); + exemplars_ = java.util.Collections.emptyList(); + histograms_ = java.util.Collections.emptyList(); + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new TimeSeries(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_TimeSeries_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_TimeSeries_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.TimeSeries.class, Types.TimeSeries.Builder.class); + } + + public static final int LABELS_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private java.util.List labels_; + + /** + * + * + *

+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public java.util.List getLabelsList() { + return labels_; + } + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public java.util.List getLabelsOrBuilderList() { + return labels_; + } + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public int getLabelsCount() { + return labels_.size(); + } + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public Types.Label getLabels(int index) { + return labels_.get(index); + } + + /** + * + * + *
+         * For a timeseries to be valid, and for the samples and exemplars
+         * to be ingested by the remote system properly, the labels field is required.
+         * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + @Override + public Types.LabelOrBuilder getLabelsOrBuilder(int index) { + return labels_.get(index); + } + + public static final int SAMPLES_FIELD_NUMBER = 2; + + @SuppressWarnings("serial") + private java.util.List samples_; + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + @Override + public java.util.List getSamplesList() { + return samples_; + } + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + @Override + public java.util.List getSamplesOrBuilderList() { + return samples_; + } + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + @Override + public int getSamplesCount() { + return samples_.size(); + } + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + @Override + public Types.Sample getSamples(int index) { + return samples_.get(index); + } + + /** repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; */ + @Override + public Types.SampleOrBuilder getSamplesOrBuilder(int index) { + return samples_.get(index); + } + + public static final int EXEMPLARS_FIELD_NUMBER = 3; + + @SuppressWarnings("serial") + private java.util.List exemplars_; + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + @Override + public java.util.List getExemplarsList() { + return exemplars_; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + @Override + public java.util.List getExemplarsOrBuilderList() { + return exemplars_; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + @Override + public int getExemplarsCount() { + return exemplars_.size(); + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + @Override + public Types.Exemplar getExemplars(int index) { + return exemplars_.get(index); + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + */ + @Override + public Types.ExemplarOrBuilder getExemplarsOrBuilder(int index) { + return exemplars_.get(index); + } + + public static final int HISTOGRAMS_FIELD_NUMBER = 4; + + @SuppressWarnings("serial") + private java.util.List histograms_; + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getHistogramsList() { + return histograms_; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + @Override + public java.util.List getHistogramsOrBuilderList() { + return histograms_; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + @Override + public int getHistogramsCount() { + return histograms_.size(); + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.Histogram getHistograms(int index) { + return histograms_.get(index); + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + @Override + public Types.HistogramOrBuilder getHistogramsOrBuilder(int index) { + return histograms_.get(index); + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < labels_.size(); i++) { + output.writeMessage(1, labels_.get(i)); + } + for (int i = 0; i < samples_.size(); i++) { + output.writeMessage(2, samples_.get(i)); + } + for (int i = 0; i < exemplars_.size(); i++) { + output.writeMessage(3, exemplars_.get(i)); + } + for (int i = 0; i < histograms_.size(); i++) { + output.writeMessage(4, histograms_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + for (int i = 0; i < labels_.size(); i++) { + size += com.google.protobuf.CodedOutputStream.computeMessageSize(1, labels_.get(i)); + } + for (int i = 0; i < samples_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 2, samples_.get(i)); + } + for (int i = 0; i < exemplars_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 3, exemplars_.get(i)); + } + for (int i = 0; i < histograms_.size(); i++) { + size += + com.google.protobuf.CodedOutputStream.computeMessageSize( + 4, histograms_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.TimeSeries)) { + return super.equals(obj); + } + Types.TimeSeries other = (Types.TimeSeries) obj; + + if (!getLabelsList().equals(other.getLabelsList())) { + return false; + } + if (!getSamplesList().equals(other.getSamplesList())) { + return false; + } + if (!getExemplarsList().equals(other.getExemplarsList())) { + return false; + } + if (!getHistogramsList().equals(other.getHistogramsList())) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getLabelsCount() > 0) { + hash = (37 * hash) + LABELS_FIELD_NUMBER; + hash = (53 * hash) + getLabelsList().hashCode(); + } + if (getSamplesCount() > 0) { + hash = (37 * hash) + SAMPLES_FIELD_NUMBER; + hash = (53 * hash) + getSamplesList().hashCode(); + } + if (getExemplarsCount() > 0) { + hash = (37 * hash) + EXEMPLARS_FIELD_NUMBER; + hash = (53 * hash) + getExemplarsList().hashCode(); + } + if (getHistogramsCount() > 0) { + hash = (37 * hash) + HISTOGRAMS_FIELD_NUMBER; + hash = (53 * hash) + getHistogramsList().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.TimeSeries parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.TimeSeries parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.TimeSeries parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.TimeSeries parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.TimeSeries parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.TimeSeries parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.TimeSeries parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.TimeSeries parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.TimeSeries parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.TimeSeries parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.TimeSeries parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.TimeSeries parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.TimeSeries prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** + * + * + *
+         * TimeSeries represents samples and labels for a single time series.
+         * 
+ * + *

Protobuf type {@code prometheus.TimeSeries} + */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.TimeSeries) + Types.TimeSeriesOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_TimeSeries_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_TimeSeries_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.TimeSeries.class, Types.TimeSeries.Builder.class); + } + + // Construct using Types.TimeSeries.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (labelsBuilder_ == null) { + labels_ = java.util.Collections.emptyList(); + } else { + labels_ = null; + labelsBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + if (samplesBuilder_ == null) { + samples_ = java.util.Collections.emptyList(); + } else { + samples_ = null; + samplesBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + if (exemplarsBuilder_ == null) { + exemplars_ = java.util.Collections.emptyList(); + } else { + exemplars_ = null; + exemplarsBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + if (histogramsBuilder_ == null) { + histograms_ = java.util.Collections.emptyList(); + } else { + histograms_ = null; + histogramsBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_TimeSeries_descriptor; + } + + @Override + public Types.TimeSeries getDefaultInstanceForType() { + return Types.TimeSeries.getDefaultInstance(); + } + + @Override + public Types.TimeSeries build() { + Types.TimeSeries result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.TimeSeries buildPartial() { + Types.TimeSeries result = new Types.TimeSeries(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(Types.TimeSeries result) { + if (labelsBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + labels_ = java.util.Collections.unmodifiableList(labels_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.labels_ = labels_; + } else { + result.labels_ = labelsBuilder_.build(); + } + if (samplesBuilder_ == null) { + if (((bitField0_ & 0x00000002) != 0)) { + samples_ = java.util.Collections.unmodifiableList(samples_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.samples_ = samples_; + } else { + result.samples_ = samplesBuilder_.build(); + } + if (exemplarsBuilder_ == null) { + if (((bitField0_ & 0x00000004) != 0)) { + exemplars_ = java.util.Collections.unmodifiableList(exemplars_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.exemplars_ = exemplars_; + } else { + result.exemplars_ = exemplarsBuilder_.build(); + } + if (histogramsBuilder_ == null) { + if (((bitField0_ & 0x00000008) != 0)) { + histograms_ = java.util.Collections.unmodifiableList(histograms_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.histograms_ = histograms_; + } else { + result.histograms_ = histogramsBuilder_.build(); + } + } + + private void buildPartial0(Types.TimeSeries result) { + int from_bitField0_ = bitField0_; + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.TimeSeries) { + return mergeFrom((Types.TimeSeries) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.TimeSeries other) { + if (other == Types.TimeSeries.getDefaultInstance()) { + return this; + } + if (labelsBuilder_ == null) { + if (!other.labels_.isEmpty()) { + if (labels_.isEmpty()) { + labels_ = other.labels_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureLabelsIsMutable(); + labels_.addAll(other.labels_); + } + onChanged(); + } + } else { + if (!other.labels_.isEmpty()) { + if (labelsBuilder_.isEmpty()) { + labelsBuilder_.dispose(); + labelsBuilder_ = null; + labels_ = other.labels_; + bitField0_ = (bitField0_ & ~0x00000001); + labelsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getLabelsFieldBuilder() + : null; + } else { + labelsBuilder_.addAllMessages(other.labels_); + } + } + } + if (samplesBuilder_ == null) { + if (!other.samples_.isEmpty()) { + if (samples_.isEmpty()) { + samples_ = other.samples_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureSamplesIsMutable(); + samples_.addAll(other.samples_); + } + onChanged(); + } + } else { + if (!other.samples_.isEmpty()) { + if (samplesBuilder_.isEmpty()) { + samplesBuilder_.dispose(); + samplesBuilder_ = null; + samples_ = other.samples_; + bitField0_ = (bitField0_ & ~0x00000002); + samplesBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getSamplesFieldBuilder() + : null; + } else { + samplesBuilder_.addAllMessages(other.samples_); + } + } + } + if (exemplarsBuilder_ == null) { + if (!other.exemplars_.isEmpty()) { + if (exemplars_.isEmpty()) { + exemplars_ = other.exemplars_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureExemplarsIsMutable(); + exemplars_.addAll(other.exemplars_); + } + onChanged(); + } + } else { + if (!other.exemplars_.isEmpty()) { + if (exemplarsBuilder_.isEmpty()) { + exemplarsBuilder_.dispose(); + exemplarsBuilder_ = null; + exemplars_ = other.exemplars_; + bitField0_ = (bitField0_ & ~0x00000004); + exemplarsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getExemplarsFieldBuilder() + : null; + } else { + exemplarsBuilder_.addAllMessages(other.exemplars_); + } + } + } + if (histogramsBuilder_ == null) { + if (!other.histograms_.isEmpty()) { + if (histograms_.isEmpty()) { + histograms_ = other.histograms_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureHistogramsIsMutable(); + histograms_.addAll(other.histograms_); + } + onChanged(); + } + } else { + if (!other.histograms_.isEmpty()) { + if (histogramsBuilder_.isEmpty()) { + histogramsBuilder_.dispose(); + histogramsBuilder_ = null; + histograms_ = other.histograms_; + bitField0_ = (bitField0_ & ~0x00000008); + histogramsBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders + ? getHistogramsFieldBuilder() + : null; + } else { + histogramsBuilder_.addAllMessages(other.histograms_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + Types.Label m = + input.readMessage( + Types.Label.parser(), extensionRegistry); + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.add(m); + } else { + labelsBuilder_.addMessage(m); + } + break; + } // case 10 + case 18: + { + Types.Sample m = + input.readMessage( + Types.Sample.parser(), extensionRegistry); + if (samplesBuilder_ == null) { + ensureSamplesIsMutable(); + samples_.add(m); + } else { + samplesBuilder_.addMessage(m); + } + break; + } // case 18 + case 26: + { + Types.Exemplar m = + input.readMessage( + Types.Exemplar.parser(), extensionRegistry); + if (exemplarsBuilder_ == null) { + ensureExemplarsIsMutable(); + exemplars_.add(m); + } else { + exemplarsBuilder_.addMessage(m); + } + break; + } // case 26 + case 34: + { + Types.Histogram m = + input.readMessage( + Types.Histogram.parser(), extensionRegistry); + if (histogramsBuilder_ == null) { + ensureHistogramsIsMutable(); + histograms_.add(m); + } else { + histogramsBuilder_.addMessage(m); + } + break; + } // case 34 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private java.util.List labels_ = java.util.Collections.emptyList(); + + private void ensureLabelsIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + labels_ = new java.util.ArrayList(labels_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Label, Types.Label.Builder, Types.LabelOrBuilder> + labelsBuilder_; + + /** + * + * + *

+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public java.util.List getLabelsList() { + if (labelsBuilder_ == null) { + return java.util.Collections.unmodifiableList(labels_); + } else { + return labelsBuilder_.getMessageList(); + } + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public int getLabelsCount() { + if (labelsBuilder_ == null) { + return labels_.size(); + } else { + return labelsBuilder_.getCount(); + } + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label getLabels(int index) { + if (labelsBuilder_ == null) { + return labels_.get(index); + } else { + return labelsBuilder_.getMessage(index); + } + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder setLabels(int index, Types.Label value) { + if (labelsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLabelsIsMutable(); + labels_.set(index, value); + onChanged(); + } else { + labelsBuilder_.setMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder setLabels(int index, Types.Label.Builder builderForValue) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.set(index, builderForValue.build()); + onChanged(); + } else { + labelsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(Types.Label value) { + if (labelsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLabelsIsMutable(); + labels_.add(value); + onChanged(); + } else { + labelsBuilder_.addMessage(value); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(int index, Types.Label value) { + if (labelsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLabelsIsMutable(); + labels_.add(index, value); + onChanged(); + } else { + labelsBuilder_.addMessage(index, value); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(Types.Label.Builder builderForValue) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.add(builderForValue.build()); + onChanged(); + } else { + labelsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addLabels(int index, Types.Label.Builder builderForValue) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.add(index, builderForValue.build()); + onChanged(); + } else { + labelsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder addAllLabels(Iterable values) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, labels_); + onChanged(); + } else { + labelsBuilder_.addAllMessages(values); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder clearLabels() { + if (labelsBuilder_ == null) { + labels_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + labelsBuilder_.clear(); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Builder removeLabels(int index) { + if (labelsBuilder_ == null) { + ensureLabelsIsMutable(); + labels_.remove(index); + onChanged(); + } else { + labelsBuilder_.remove(index); + } + return this; + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label.Builder getLabelsBuilder(int index) { + return getLabelsFieldBuilder().getBuilder(index); + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.LabelOrBuilder getLabelsOrBuilder(int index) { + if (labelsBuilder_ == null) { + return labels_.get(index); + } else { + return labelsBuilder_.getMessageOrBuilder(index); + } + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public java.util.List getLabelsOrBuilderList() { + if (labelsBuilder_ != null) { + return labelsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(labels_); + } + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label.Builder addLabelsBuilder() { + return getLabelsFieldBuilder().addBuilder(Types.Label.getDefaultInstance()); + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public Types.Label.Builder addLabelsBuilder(int index) { + return getLabelsFieldBuilder().addBuilder(index, Types.Label.getDefaultInstance()); + } + + /** + * + * + *
+             * For a timeseries to be valid, and for the samples and exemplars
+             * to be ingested by the remote system properly, the labels field is required.
+             * 
+ * + * repeated .prometheus.Label labels = 1 [(.gogoproto.nullable) = false]; + */ + public java.util.List getLabelsBuilderList() { + return getLabelsFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Label, Types.Label.Builder, Types.LabelOrBuilder> + getLabelsFieldBuilder() { + if (labelsBuilder_ == null) { + labelsBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.Label, Types.Label.Builder, Types.LabelOrBuilder>( + labels_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + labels_ = null; + } + return labelsBuilder_; + } + + private java.util.List samples_ = java.util.Collections.emptyList(); + + private void ensureSamplesIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + samples_ = new java.util.ArrayList(samples_); + bitField0_ |= 0x00000002; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Sample, Types.Sample.Builder, Types.SampleOrBuilder> + samplesBuilder_; + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public java.util.List getSamplesList() { + if (samplesBuilder_ == null) { + return java.util.Collections.unmodifiableList(samples_); + } else { + return samplesBuilder_.getMessageList(); + } + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public int getSamplesCount() { + if (samplesBuilder_ == null) { + return samples_.size(); + } else { + return samplesBuilder_.getCount(); + } + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Types.Sample getSamples(int index) { + if (samplesBuilder_ == null) { + return samples_.get(index); + } else { + return samplesBuilder_.getMessage(index); + } + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder setSamples(int index, Types.Sample value) { + if (samplesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSamplesIsMutable(); + samples_.set(index, value); + onChanged(); + } else { + samplesBuilder_.setMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder setSamples(int index, Types.Sample.Builder builderForValue) { + if (samplesBuilder_ == null) { + ensureSamplesIsMutable(); + samples_.set(index, builderForValue.build()); + onChanged(); + } else { + samplesBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder addSamples(Types.Sample value) { + if (samplesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSamplesIsMutable(); + samples_.add(value); + onChanged(); + } else { + samplesBuilder_.addMessage(value); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder addSamples(int index, Types.Sample value) { + if (samplesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSamplesIsMutable(); + samples_.add(index, value); + onChanged(); + } else { + samplesBuilder_.addMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder addSamples(Types.Sample.Builder builderForValue) { + if (samplesBuilder_ == null) { + ensureSamplesIsMutable(); + samples_.add(builderForValue.build()); + onChanged(); + } else { + samplesBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder addSamples(int index, Types.Sample.Builder builderForValue) { + if (samplesBuilder_ == null) { + ensureSamplesIsMutable(); + samples_.add(index, builderForValue.build()); + onChanged(); + } else { + samplesBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder addAllSamples(Iterable values) { + if (samplesBuilder_ == null) { + ensureSamplesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, samples_); + onChanged(); + } else { + samplesBuilder_.addAllMessages(values); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder clearSamples() { + if (samplesBuilder_ == null) { + samples_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + } else { + samplesBuilder_.clear(); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Builder removeSamples(int index) { + if (samplesBuilder_ == null) { + ensureSamplesIsMutable(); + samples_.remove(index); + onChanged(); + } else { + samplesBuilder_.remove(index); + } + return this; + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Types.Sample.Builder getSamplesBuilder(int index) { + return getSamplesFieldBuilder().getBuilder(index); + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Types.SampleOrBuilder getSamplesOrBuilder(int index) { + if (samplesBuilder_ == null) { + return samples_.get(index); + } else { + return samplesBuilder_.getMessageOrBuilder(index); + } + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public java.util.List getSamplesOrBuilderList() { + if (samplesBuilder_ != null) { + return samplesBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(samples_); + } + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Types.Sample.Builder addSamplesBuilder() { + return getSamplesFieldBuilder().addBuilder(Types.Sample.getDefaultInstance()); + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public Types.Sample.Builder addSamplesBuilder(int index) { + return getSamplesFieldBuilder() + .addBuilder(index, Types.Sample.getDefaultInstance()); + } + + /** + * repeated .prometheus.Sample samples = 2 [(.gogoproto.nullable) = false]; + */ + public java.util.List getSamplesBuilderList() { + return getSamplesFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Sample, Types.Sample.Builder, Types.SampleOrBuilder> + getSamplesFieldBuilder() { + if (samplesBuilder_ == null) { + samplesBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.Sample, Types.Sample.Builder, Types.SampleOrBuilder>( + samples_, + ((bitField0_ & 0x00000002) != 0), + getParentForChildren(), + isClean()); + samples_ = null; + } + return samplesBuilder_; + } + + private java.util.List exemplars_ = java.util.Collections.emptyList(); + + private void ensureExemplarsIsMutable() { + if (!((bitField0_ & 0x00000004) != 0)) { + exemplars_ = new java.util.ArrayList(exemplars_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Exemplar, Types.Exemplar.Builder, Types.ExemplarOrBuilder> + exemplarsBuilder_; + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getExemplarsList() { + if (exemplarsBuilder_ == null) { + return java.util.Collections.unmodifiableList(exemplars_); + } else { + return exemplarsBuilder_.getMessageList(); + } + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public int getExemplarsCount() { + if (exemplarsBuilder_ == null) { + return exemplars_.size(); + } else { + return exemplarsBuilder_.getCount(); + } + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.Exemplar getExemplars(int index) { + if (exemplarsBuilder_ == null) { + return exemplars_.get(index); + } else { + return exemplarsBuilder_.getMessage(index); + } + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder setExemplars(int index, Types.Exemplar value) { + if (exemplarsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureExemplarsIsMutable(); + exemplars_.set(index, value); + onChanged(); + } else { + exemplarsBuilder_.setMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder setExemplars(int index, Types.Exemplar.Builder builderForValue) { + if (exemplarsBuilder_ == null) { + ensureExemplarsIsMutable(); + exemplars_.set(index, builderForValue.build()); + onChanged(); + } else { + exemplarsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addExemplars(Types.Exemplar value) { + if (exemplarsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureExemplarsIsMutable(); + exemplars_.add(value); + onChanged(); + } else { + exemplarsBuilder_.addMessage(value); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addExemplars(int index, Types.Exemplar value) { + if (exemplarsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureExemplarsIsMutable(); + exemplars_.add(index, value); + onChanged(); + } else { + exemplarsBuilder_.addMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addExemplars(Types.Exemplar.Builder builderForValue) { + if (exemplarsBuilder_ == null) { + ensureExemplarsIsMutable(); + exemplars_.add(builderForValue.build()); + onChanged(); + } else { + exemplarsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addExemplars(int index, Types.Exemplar.Builder builderForValue) { + if (exemplarsBuilder_ == null) { + ensureExemplarsIsMutable(); + exemplars_.add(index, builderForValue.build()); + onChanged(); + } else { + exemplarsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder addAllExemplars(Iterable values) { + if (exemplarsBuilder_ == null) { + ensureExemplarsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, exemplars_); + onChanged(); + } else { + exemplarsBuilder_.addAllMessages(values); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder clearExemplars() { + if (exemplarsBuilder_ == null) { + exemplars_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + exemplarsBuilder_.clear(); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Builder removeExemplars(int index) { + if (exemplarsBuilder_ == null) { + ensureExemplarsIsMutable(); + exemplars_.remove(index); + onChanged(); + } else { + exemplarsBuilder_.remove(index); + } + return this; + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.Exemplar.Builder getExemplarsBuilder(int index) { + return getExemplarsFieldBuilder().getBuilder(index); + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.ExemplarOrBuilder getExemplarsOrBuilder(int index) { + if (exemplarsBuilder_ == null) { + return exemplars_.get(index); + } else { + return exemplarsBuilder_.getMessageOrBuilder(index); + } + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getExemplarsOrBuilderList() { + if (exemplarsBuilder_ != null) { + return exemplarsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(exemplars_); + } + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.Exemplar.Builder addExemplarsBuilder() { + return getExemplarsFieldBuilder().addBuilder(Types.Exemplar.getDefaultInstance()); + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public Types.Exemplar.Builder addExemplarsBuilder(int index) { + return getExemplarsFieldBuilder() + .addBuilder(index, Types.Exemplar.getDefaultInstance()); + } + + /** + * repeated .prometheus.Exemplar exemplars = 3 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getExemplarsBuilderList() { + return getExemplarsFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Exemplar, Types.Exemplar.Builder, Types.ExemplarOrBuilder> + getExemplarsFieldBuilder() { + if (exemplarsBuilder_ == null) { + exemplarsBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.Exemplar, + Types.Exemplar.Builder, + Types.ExemplarOrBuilder>( + exemplars_, + ((bitField0_ & 0x00000004) != 0), + getParentForChildren(), + isClean()); + exemplars_ = null; + } + return exemplarsBuilder_; + } + + private java.util.List histograms_ = java.util.Collections.emptyList(); + + private void ensureHistogramsIsMutable() { + if (!((bitField0_ & 0x00000008) != 0)) { + histograms_ = new java.util.ArrayList(histograms_); + bitField0_ |= 0x00000008; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Histogram, Types.Histogram.Builder, Types.HistogramOrBuilder> + histogramsBuilder_; + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getHistogramsList() { + if (histogramsBuilder_ == null) { + return java.util.Collections.unmodifiableList(histograms_); + } else { + return histogramsBuilder_.getMessageList(); + } + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public int getHistogramsCount() { + if (histogramsBuilder_ == null) { + return histograms_.size(); + } else { + return histogramsBuilder_.getCount(); + } + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Types.Histogram getHistograms(int index) { + if (histogramsBuilder_ == null) { + return histograms_.get(index); + } else { + return histogramsBuilder_.getMessage(index); + } + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder setHistograms(int index, Types.Histogram value) { + if (histogramsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureHistogramsIsMutable(); + histograms_.set(index, value); + onChanged(); + } else { + histogramsBuilder_.setMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder setHistograms(int index, Types.Histogram.Builder builderForValue) { + if (histogramsBuilder_ == null) { + ensureHistogramsIsMutable(); + histograms_.set(index, builderForValue.build()); + onChanged(); + } else { + histogramsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder addHistograms(Types.Histogram value) { + if (histogramsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureHistogramsIsMutable(); + histograms_.add(value); + onChanged(); + } else { + histogramsBuilder_.addMessage(value); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder addHistograms(int index, Types.Histogram value) { + if (histogramsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureHistogramsIsMutable(); + histograms_.add(index, value); + onChanged(); + } else { + histogramsBuilder_.addMessage(index, value); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder addHistograms(Types.Histogram.Builder builderForValue) { + if (histogramsBuilder_ == null) { + ensureHistogramsIsMutable(); + histograms_.add(builderForValue.build()); + onChanged(); + } else { + histogramsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder addHistograms(int index, Types.Histogram.Builder builderForValue) { + if (histogramsBuilder_ == null) { + ensureHistogramsIsMutable(); + histograms_.add(index, builderForValue.build()); + onChanged(); + } else { + histogramsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder addAllHistograms(Iterable values) { + if (histogramsBuilder_ == null) { + ensureHistogramsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll(values, histograms_); + onChanged(); + } else { + histogramsBuilder_.addAllMessages(values); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder clearHistograms() { + if (histogramsBuilder_ == null) { + histograms_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + } else { + histogramsBuilder_.clear(); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Builder removeHistograms(int index) { + if (histogramsBuilder_ == null) { + ensureHistogramsIsMutable(); + histograms_.remove(index); + onChanged(); + } else { + histogramsBuilder_.remove(index); + } + return this; + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Types.Histogram.Builder getHistogramsBuilder(int index) { + return getHistogramsFieldBuilder().getBuilder(index); + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Types.HistogramOrBuilder getHistogramsOrBuilder(int index) { + if (histogramsBuilder_ == null) { + return histograms_.get(index); + } else { + return histogramsBuilder_.getMessageOrBuilder(index); + } + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getHistogramsOrBuilderList() { + if (histogramsBuilder_ != null) { + return histogramsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(histograms_); + } + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Types.Histogram.Builder addHistogramsBuilder() { + return getHistogramsFieldBuilder().addBuilder(Types.Histogram.getDefaultInstance()); + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public Types.Histogram.Builder addHistogramsBuilder(int index) { + return getHistogramsFieldBuilder() + .addBuilder(index, Types.Histogram.getDefaultInstance()); + } + + /** + * repeated .prometheus.Histogram histograms = 4 [(.gogoproto.nullable) = false]; + * + */ + public java.util.List getHistogramsBuilderList() { + return getHistogramsFieldBuilder().getBuilderList(); + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + Types.Histogram, Types.Histogram.Builder, Types.HistogramOrBuilder> + getHistogramsFieldBuilder() { + if (histogramsBuilder_ == null) { + histogramsBuilder_ = + new com.google.protobuf.RepeatedFieldBuilderV3< + Types.Histogram, + Types.Histogram.Builder, + Types.HistogramOrBuilder>( + histograms_, + ((bitField0_ & 0x00000008) != 0), + getParentForChildren(), + isClean()); + histograms_ = null; + } + return histogramsBuilder_; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.TimeSeries) + } + + // @@protoc_insertion_point(class_scope:prometheus.TimeSeries) + private static final Types.TimeSeries DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.TimeSeries(); + } + + public static Types.TimeSeries getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + @Override + public TimeSeries parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException() + .setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @Override + public Types.TimeSeries getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + } + + public interface LabelOrBuilder + extends + // @@protoc_insertion_point(interface_extends:prometheus.Label) + com.google.protobuf.MessageOrBuilder { + + /** + * string name = 1; + * + * @return The name. + */ + String getName(); + + /** + * string name = 1; + * + * @return The bytes for name. + */ + com.google.protobuf.ByteString getNameBytes(); + + /** + * string value = 2; + * + * @return The value. + */ + String getValue(); + + /** + * string value = 2; + * + * @return The bytes for value. + */ + com.google.protobuf.ByteString getValueBytes(); + } + + /** Protobuf type {@code prometheus.Label} */ + public static final class Label extends com.google.protobuf.GeneratedMessageV3 + implements + // @@protoc_insertion_point(message_implements:prometheus.Label) + LabelOrBuilder { + private static final long serialVersionUID = 0L; + + // Use Label.newBuilder() to construct. + private Label(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + + private Label() { + name_ = ""; + value_ = ""; + } + + @Override + @SuppressWarnings({"unused"}) + protected Object newInstance(UnusedPrivateParameter unused) { + return new Label(); + } + + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Label_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Label_fieldAccessorTable + .ensureFieldAccessorsInitialized(Types.Label.class, Types.Label.Builder.class); + } + + public static final int NAME_FIELD_NUMBER = 1; + + @SuppressWarnings("serial") + private volatile Object name_ = ""; + + /** + * string name = 1; + * + * @return The name. + */ + @Override + public String getName() { + Object ref = name_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + + /** + * string name = 1; + * + * @return The bytes for name. + */ + @Override + public com.google.protobuf.ByteString getNameBytes() { + Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int VALUE_FIELD_NUMBER = 2; + + @SuppressWarnings("serial") + private volatile Object value_ = ""; + + /** + * string value = 2; + * + * @return The value. + */ + @Override + public String getValue() { + Object ref = value_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + value_ = s; + return s; + } + } + + /** + * string value = 2; + * + * @return The bytes for value. + */ + @Override + public com.google.protobuf.ByteString getValueBytes() { + Object ref = value_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + value_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + + @Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) { + return true; + } + if (isInitialized == 0) { + return false; + } + + memoizedIsInitialized = 1; + return true; + } + + @Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(name_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(value_)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, value_); + } + getUnknownFields().writeTo(output); + } + + @Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) { + return size; + } + + size = 0; + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(name_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_); + } + if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(value_)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, value_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof Types.Label)) { + return super.equals(obj); + } + Types.Label other = (Types.Label) obj; + + if (!getName().equals(other.getName())) { + return false; + } + if (!getValue().equals(other.getValue())) { + return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) { + return false; + } + return true; + } + + @Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static Types.Label parseFrom(java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Label parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Label parseFrom(com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Label parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Label parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + + public static Types.Label parseFrom( + byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + + public static Types.Label parseFrom(java.io.InputStream input) throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Label parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Label parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input); + } + + public static Types.Label parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseDelimitedWithIOException( + PARSER, input, extensionRegistry); + } + + public static Types.Label parseFrom(com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException(PARSER, input); + } + + public static Types.Label parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3.parseWithIOException( + PARSER, input, extensionRegistry); + } + + @Override + public Builder newBuilderForType() { + return newBuilder(); + } + + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + + public static Builder newBuilder(Types.Label prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + + @Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE ? new Builder() : new Builder().mergeFrom(this); + } + + @Override + protected Builder newBuilderForType(BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + + /** Protobuf type {@code prometheus.Label} */ + public static final class Builder + extends com.google.protobuf.GeneratedMessageV3.Builder + implements + // @@protoc_insertion_point(builder_implements:prometheus.Label) + Types.LabelOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { + return Types.internal_static_prometheus_Label_descriptor; + } + + @Override + protected FieldAccessorTable internalGetFieldAccessorTable() { + return Types.internal_static_prometheus_Label_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Types.Label.class, Types.Label.Builder.class); + } + + // Construct using Types.Label.newBuilder() + private Builder() {} + + private Builder(BuilderParent parent) { + super(parent); + } + + @Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + name_ = ""; + value_ = ""; + return this; + } + + @Override + public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { + return Types.internal_static_prometheus_Label_descriptor; + } + + @Override + public Types.Label getDefaultInstanceForType() { + return Types.Label.getDefaultInstance(); + } + + @Override + public Types.Label build() { + Types.Label result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @Override + public Types.Label buildPartial() { + Types.Label result = new Types.Label(this); + if (bitField0_ != 0) { + buildPartial0(result); + } + onBuilt(); + return result; + } + + private void buildPartial0(Types.Label result) { + int from_bitField0_ = bitField0_; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.name_ = name_; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.value_ = value_; + } + } + + @Override + public Builder clone() { + return super.clone(); + } + + @Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.setField(field, value); + } + + @Override + public Builder clearField(com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + + @Override + public Builder clearOneof(com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + + @Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, + Object value) { + return super.setRepeatedField(field, index, value); + } + + @Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, Object value) { + return super.addRepeatedField(field, value); + } + + @Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Types.Label) { + return mergeFrom((Types.Label) other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Types.Label other) { + if (other == Types.Label.getDefaultInstance()) { + return this; + } + if (!other.getName().isEmpty()) { + name_ = other.name_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (!other.getValue().isEmpty()) { + value_ = other.value_; + bitField0_ |= 0x00000002; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @Override + public final boolean isInitialized() { + return true; + } + + @Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: + { + name_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: + { + value_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + default: + { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + + private int bitField0_; + + private Object name_ = ""; + + /** + * string name = 1; + * + * @return The name. + */ + public String getName() { + Object ref = name_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (String) ref; + } + } + + /** + * string name = 1; + * + * @return The bytes for name. + */ + public com.google.protobuf.ByteString getNameBytes() { + Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + /** + * string name = 1; + * + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName(String value) { + if (value == null) { + throw new NullPointerException(); + } + name_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + /** + * string name = 1; + * + * @return This builder for chaining. + */ + public Builder clearName() { + name_ = getDefaultInstance().getName(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + + /** + * string name = 1; + * + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + name_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private Object value_ = ""; + + /** + * string value = 2; + * + * @return The value. + */ + public String getValue() { + Object ref = value_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + value_ = s; + return s; + } else { + return (String) ref; + } + } + + /** + * string value = 2; + * + * @return The bytes for value. + */ + public com.google.protobuf.ByteString getValueBytes() { + Object ref = value_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + value_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + /** + * string value = 2; + * + * @param value The value to set. + * @return This builder for chaining. + */ + public Builder setValue(String value) { + if (value == null) { + throw new NullPointerException(); + } + value_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + /** + * string value = 2; + * + * @return This builder for chaining. + */ + public Builder clearValue() { + value_ = getDefaultInstance().getValue(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + + /** + * string value = 2; + * + * @param value The bytes for value to set. + * @return This builder for chaining. + */ + public Builder setValueBytes(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + value_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + @Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + // @@protoc_insertion_point(builder_scope:prometheus.Label) + } + + // @@protoc_insertion_point(class_scope:prometheus.Label) + private static final Types.Label DEFAULT_INSTANCE; + + static { + DEFAULT_INSTANCE = new Types.Label(); + } + + public static Types.Label getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser