diff --git a/.cargo/config.toml b/.cargo/config.toml index c65cd3ac1a4fa..c9165c404ef92 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -32,6 +32,3 @@ rustflags = [ # Flags for all targets. [target.'cfg(all())'] rustflags = ["--cfg", "tokio_unstable"] - -[unstable] -lints = true diff --git a/.github/workflows/auto-update-helm-and-operator-version-by-release.yml b/.github/workflows/auto-update-helm-and-operator-version-by-release.yml index 1641caa3301fd..b136267841172 100644 --- a/.github/workflows/auto-update-helm-and-operator-version-by-release.yml +++ b/.github/workflows/auto-update-helm-and-operator-version-by-release.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v3 with: repository: 'risingwavelabs/helm-charts' - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PR_TOKEN }} path: 'helm-charts' - name: Update values.yaml @@ -37,13 +37,16 @@ jobs: echo "NEW_CHART_VERSION=$NEW_VERSION" >> $GITHUB_ENV - name: Create Pull Request - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v6 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PR_TOKEN }} commit-message: 'chore: bump risingwave to ${{ env.NEW_APP_VERSION }}, release chart ${{ env.NEW_CHART_VERSION }}' title: 'chore: bump risingwave to ${{ env.NEW_APP_VERSION }}, release chart ${{ env.NEW_CHART_VERSION }}' body: 'This is an automated pull request to update the chart versions' branch: 'auto-update-${{ env.NEW_APP_VERSION }}' + path: 'helm-charts' + reviewers: arkbriar + delete-branch: true update-risingwave-operator: runs-on: ubuntu-latest @@ -52,7 +55,7 @@ jobs: uses: actions/checkout@v3 with: repository: 'risingwavelabs/risingwave-operator' - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PR_TOKEN }} path: 'risingwave-operator' - name: Update risingwave-operator image tags @@ -62,10 +65,13 @@ jobs: grep -rl "risingwavelabs/risingwave:$PREV_VERSION" . | xargs sed -i "s|risingwavelabs/risingwave:$PREV_VERSION|risingwavelabs/risingwave:${{ env.NEW_APP_VERSION }}|g" - name: Create Pull Request for risingwave-operator - uses: peter-evans/create-pull-request@v4 + uses: peter-evans/create-pull-request@v6 with: - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.PR_TOKEN }} commit-message: 'chore: bump risingwave image tags to ${{ env.NEW_APP_VERSION }}' title: 'chore: bump risingwave image tags to ${{ env.NEW_APP_VERSION }}' body: 'This is an automated pull request to update the risingwave image tags' branch: 'auto-update-${{ env.NEW_APP_VERSION }}' + path: 'risingwave-operator' + reviewers: arkbriar + delete-branch: true diff --git a/.github/workflows/connector-node-integration.yml b/.github/workflows/connector-node-integration.yml index f20f209a3953d..abc1942055fb5 100644 --- a/.github/workflows/connector-node-integration.yml +++ b/.github/workflows/connector-node-integration.yml @@ -2,11 +2,11 @@ name: Connector Node Integration Tests on: push: - branches: [main] + branches: [ main ] pull_request: - branches: [main] + branches: [ main ] merge_group: - types: [checks_requested] + types: [ checks_requested ] jobs: build: @@ -42,4 +42,8 @@ jobs: echo "--- build connector node" cd ${RISINGWAVE_ROOT}/java # run unit test - mvn --batch-mode --update-snapshots clean package -Dno-build-rust + # WARN: `testOnNext_writeValidation` is skipped because it relies on Rust code to decode message, + # while we don't build Rust code (`-Dno-build-rust`) here to save time + mvn --batch-mode --update-snapshots clean package -Dno-build-rust \ + '-Dtest=!com.risingwave.connector.sink.SinkStreamObserverTest#testOnNext_writeValidation' \ + -Dsurefire.failIfNoSpecifiedTests=false diff --git a/.github/workflows/dashboard_main.yml b/.github/workflows/dashboard.yml similarity index 77% rename from .github/workflows/dashboard_main.yml rename to .github/workflows/dashboard.yml index 0b9571b35154d..c722d4704e00e 100644 --- a/.github/workflows/dashboard_main.yml +++ b/.github/workflows/dashboard.yml @@ -1,8 +1,11 @@ -name: Dashboard (main) +name: Dashboard on: push: branches: [main] paths: [dashboard/**, proto/**] + pull_request: + branches: [main] + paths: [dashboard/**, proto/**] workflow_dispatch: jobs: dashboard-ui-deploy: @@ -19,11 +22,14 @@ jobs: - name: build working-directory: ./dashboard run: | + echo "::group::npm install" npm install + echo "::endgroup::" npm run lint npm run build - name: Deploy uses: s0/git-publish-subdir-action@develop + if: github.event_name == 'push' && github.ref == 'refs/heads/main' env: REPO: self BRANCH: dashboard-artifact diff --git a/.github/workflows/dashboard_pr.yml b/.github/workflows/dashboard_pr.yml deleted file mode 100644 index c7245a2119d99..0000000000000 --- a/.github/workflows/dashboard_pr.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Dashboard -on: - pull_request: - branches: [main] - paths: [dashboard/**, proto/**] - -concurrency: - group: dashboard-build-${{ github.ref }} - cancel-in-progress: true - -jobs: - dashboard-ui-build: - runs-on: ubuntu-latest - steps: - - name: Checkout forked repo - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - uses: actions/setup-node@v2 - with: - node-version: "18" - - uses: arduino/setup-protoc@v1 - with: - version: "3.x" - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: build - working-directory: ./dashboard - run: | - echo "::group::npm install" - npm install - echo "::endgroup::" - npm run gen-proto - - git add -A - if ! git diff --quiet --staged; then - git reset - echo "::error::Generated proto files are not up to date." - - echo "::group::check if the branch is out-of-date" - git pull origin $GITHUB_HEAD_REF --unshallow - git remote add upstream https://github.com/risingwavelabs/risingwave.git - git fetch upstream $GITHUB_BASE_REF:$GITHUB_BASE_REF - - echo "origin/$GITHUB_HEAD_REF: $(git rev-parse origin/$GITHUB_HEAD_REF)" - echo "upstream/$GITHUB_BASE_REF: $(git rev-parse upstream/$GITHUB_BASE_REF)" - - git merge-base origin/$GITHUB_HEAD_REF upstream/$GITHUB_BASE_REF - - FORK_POINT=$(git merge-base origin/$GITHUB_HEAD_REF upstream/$GITHUB_BASE_REF) - echo "FORK_POINT: $FORK_POINT" - echo "::endgroup::" - - if ! git diff --quiet $FORK_POINT..upstream/$GITHUB_BASE_REF -- ../proto; then - echo "::error::Your branch is out-of-date. Please update your branch first and then run 'npm i && npm run gen-proto' at dashboard/ and commit the changes." - else - echo "::error::Please run 'npm i && npm run gen-proto' at dashboard/ and commit the changes." - fi - exit 1 - fi - - npm run lint - npm run build diff --git a/.github/workflows/nightly-rust.yml b/.github/workflows/nightly-rust.yml index eab964fafa177..5fa90ae1138c8 100644 --- a/.github/workflows/nightly-rust.yml +++ b/.github/workflows/nightly-rust.yml @@ -5,22 +5,25 @@ name: Build with Latest Nightly Rust on: schedule: - cron: "0 0 * * *" + push: + branches: + - xxchan/latest-nightly-rust workflow_dispatch: jobs: build: runs-on: ubuntu-latest steps: - - name: Maximize build space - uses: easimon/maximize-build-space@master - with: - remove-dotnet: 'true' - remove-android: 'true' - remove-haskell: 'true' - remove-codeql: 'true' - remove-docker-images: 'true' - root-reserve-mb: 2048 - swap-size-mb: 8192 + - name: Maximize build space + uses: easimon/maximize-build-space@master + with: + remove-dotnet: 'true' + remove-android: 'true' + remove-haskell: 'true' + remove-codeql: 'true' + remove-docker-images: 'true' + root-reserve-mb: 10240 + temp-reserve-mb: 10240 - uses: actions/checkout@v3 if: ${{ github.event_name == 'schedule' }} with: @@ -40,3 +43,5 @@ jobs: export CARGO_INCREMENTAL=0 export CARGO_PROFILE_DEV_DEBUG=false cargo check + - name: Show available storage + run: df -h diff --git a/Cargo.lock b/Cargo.lock index b80a3eb55657a..1f9358de037cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,9 +79,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -90,9 +90,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "const-random", @@ -314,7 +314,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8919668503a4f2d8b6da96fa7c16e93046bfb3412ffcfa1e5dc7d2e3adcb378" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-arith 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", @@ -385,7 +385,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6eaf89041fa5937940ae390294ece29e1db584f46d995608d6e5fe65a2e0e9b" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-buffer 48.0.1", "arrow-data 48.0.1", "arrow-schema 48.0.1", @@ -402,7 +402,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d390feeb7f21b78ec997a4081a025baef1e2e0d6069e181939b61864c9779609" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-buffer 50.0.0", "arrow-data 50.0.0", "arrow-schema 50.0.0", @@ -617,7 +617,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5d664318bc05f930559fc088888f0f7174d3c5bc888c0f4f9ae8f23aa398ba3" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-data 48.0.1", @@ -632,7 +632,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007035e17ae09c4e8993e4cb8b5b96edf0afb927cd38e2dff27189b274d83dcf" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 50.0.0", "arrow-buffer 50.0.0", "arrow-data 50.0.0", @@ -662,7 +662,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374c4c3b812ecc2118727b892252a4a4308f87a8aca1dbf09f3ce4bc578e668a" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-data 48.0.1", @@ -676,7 +676,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce20973c1912de6514348e064829e50947e35977bb9d7fb637dc99ea9ffd78c" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 50.0.0", "arrow-buffer 50.0.0", "arrow-data 50.0.0", @@ -716,11 +716,28 @@ dependencies = [ "regex-syntax 0.8.2", ] +[[package]] +name = "arrow-udf-flight" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4adb3a066bd22fb520bc3d040d9d59ee54f320c21faeb6df815ea20445c80c54" +dependencies = [ + "arrow-array 50.0.0", + "arrow-flight", + "arrow-schema 50.0.0", + "arrow-select 50.0.0", + "futures-util", + "thiserror", + "tokio", + "tonic 0.10.2", + "tracing", +] + [[package]] name = "arrow-udf-js" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252b6355ad1e57eb6454b705c51652de55aa22eb018cdb95be0dbf62ee3ec78f" +checksum = "0519711e77180c5fe9891b81d912d937864894c77932b5df52169966f4a948bb" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -732,7 +749,7 @@ dependencies = [ [[package]] name = "arrow-udf-js-deno" version = "0.0.1" -source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=23fe0dd#23fe0dd41616f4646f9139e22a335518e6cc9a47" +source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=fa36365#fa3636559de986aa592da6e8b3fbfac7bdd4bb78" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -754,7 +771,7 @@ dependencies = [ [[package]] name = "arrow-udf-js-deno-runtime" version = "0.0.1" -source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=23fe0dd#23fe0dd41616f4646f9139e22a335518e6cc9a47" +source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=fa36365#fa3636559de986aa592da6e8b3fbfac7bdd4bb78" dependencies = [ "anyhow", "deno_ast", @@ -782,7 +799,8 @@ dependencies = [ [[package]] name = "arrow-udf-python" version = "0.1.0" -source = "git+https://github.com/risingwavelabs/arrow-udf.git?rev=6c32f71#6c32f710b5948147f8214797fc334a4a3cadef0d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41eaaa010b9cf07bedda6f1dafa050496e96fff7ae4b9602fb77c25c24c64cb7" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -796,9 +814,9 @@ dependencies = [ [[package]] name = "arrow-udf-wasm" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a51355b8ca4de8ae028e5efb45c248dad4568cde6707f23b89f9b86a907f36" +checksum = "eb829e25925161d93617d4b053bae03fe51e708f2cce088d85df856011d4f369" dependencies = [ "anyhow", "arrow-array 50.0.0", @@ -848,10 +866,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ "concurrent-queue", - "event-listener", + "event-listener 2.5.3", "futures-core", ] +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener 5.2.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compression" version = "0.4.5" @@ -891,7 +922,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-executor", "async-io", "async-lock", @@ -927,7 +958,7 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ - "event-listener", + "event-listener 2.5.3", ] [[package]] @@ -981,7 +1012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", - "async-channel", + "async-channel 1.9.0", "async-global-executor", "async-io", "async-lock", @@ -1197,6 +1228,24 @@ dependencies = [ "paste", ] +[[package]] +name = "aws-msk-iam-sasl-signer" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7036b8409ffe698dfdc5ae08722999d960092aeb738026ea99c3071c94831668" +dependencies = [ + "aws-config", + "aws-credential-types", + "aws-sdk-sts", + "aws-sigv4", + "aws-types", + "base64 0.22.0", + "chrono", + "futures", + "thiserror", + "url", +] + [[package]] name = "aws-runtime" version = "1.0.1" @@ -1640,7 +1689,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.32.1", "rustc-demangle", ] @@ -1930,7 +1979,7 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-lock", "async-task", "atomic-waker", @@ -2468,15 +2517,6 @@ dependencies = [ "cc", ] -[[package]] -name = "cmsketch" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93710598b87c37ea250ab17a36f9f79dbaf3bd20e55806cf09345103bc26d60e" -dependencies = [ - "paste", -] - [[package]] name = "cmsketch" version = "0.2.0" @@ -2621,23 +2661,21 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "const-random" -version = "0.1.15" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", - "proc-macro-hack", ] [[package]] name = "const-random-macro" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", - "proc-macro-hack", "tiny-keccak", ] @@ -2719,18 +2757,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3775cc6cc00c90d29eebea55feedb2b0168e23f5415bab7859c4004d7323d1" +checksum = "79b27922a6879b5b5361d0a084cb0b1941bf109a98540addcb932da13b68bed4" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637f3184ba5bfa48d425bad1d2e4faf5fcf619f5e0ca107edc6dc02f589d4d74" +checksum = "304c455b28bf56372729acb356afbb55d622f2b0f2f7837aa5e57c138acaac4d" dependencies = [ "bumpalo", "cranelift-bforest", @@ -2749,33 +2787,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b35b8240462341d94d31aab807cad704683988708261aecae3d57db48b7212" +checksum = "1653c56b99591d07f67c5ca7f9f25888948af3f4b97186bff838d687d666f613" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3cd1555aa9df1d6d8375732de41b4cb0d787006948d55b6d004d521e9efeb0" +checksum = "f5b6a9cf6b6eb820ee3f973a0db313c05dc12d370f37b4fe9630286e1672573f" [[package]] name = "cranelift-control" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b31a562a10e98ab148fa146801e20665c5f9eda4fce9b2c5a3836575887d74" +checksum = "d9d06e6bf30075fb6bed9e034ec046475093392eea1aff90eb5c44c4a033d19a" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1e0467700a3f4fccf5feddbaebdf8b0eb82535b06a9600c4bc5df40872e75d" +checksum = "29be04f931b73cdb9694874a295027471817f26f26d2f0ebe5454153176b6e3a" dependencies = [ "serde", "serde_derive", @@ -2783,9 +2821,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb918ee2c23939262efd1b99d76a21212ac7bd35129582133e21a22a6ff0467" +checksum = "a07fd7393041d7faa2f37426f5dc7fc04003b70988810e8c063beefeff1cd8f9" dependencies = [ "cranelift-codegen", "log", @@ -2795,15 +2833,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966e4cfb23cf6d7f1d285d53a912baaffc5f06bcd9c9b0a2d8c66a184fae534b" +checksum = "f341d7938caa6dff8149dac05bb2b53fc680323826b83b4cf175ab9f5139a3c9" [[package]] name = "cranelift-native" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea803aadfc4aabdfae7c3870f1b1f6dd4332f4091859e9758ef5fca6bf8cc87" +checksum = "82af6066e6448d26eeabb7aa26a43f7ff79f8217b06bade4ee6ef230aecc8880" dependencies = [ "cranelift-codegen", "libc", @@ -2812,9 +2850,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.106.1" +version = "0.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d18a3572cd897555bba3621e568029417d8f5cc26aeede2d7cb0bad6afd916" +checksum = "2766fab7284a914a7f17f90ebe865c86453225fb8637ac31f123f5028fee69cd" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -2849,9 +2887,9 @@ checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" [[package]] name = "crc32c" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8f48d60e5b4d2c53d5c2b1d8a58c849a70ae5e5509b08a48d047e3b65714a74" +checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2" dependencies = [ "rustc_version 0.4.0", ] @@ -3094,9 +3132,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", @@ -3321,7 +3359,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "676796427e638d85e9eadf13765705212be60b34f8fc5d3934d95184c63ca1b4" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow 48.0.1", "arrow-array 48.0.1", "arrow-schema 48.0.1", @@ -3368,7 +3406,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e23b3d21a6531259d291bd20ce59282ea794bda1018b0a1e278c13cd52e50c" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", @@ -3408,7 +3446,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e227fe88bf6730cab378d0cd8fc4c6b2ea42bc7e414a8ea9feba7225932735" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow 48.0.1", "arrow-array 48.0.1", "datafusion-common", @@ -3441,7 +3479,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f32b8574add16a32411a9b3fb3844ac1fc09ab4e7be289f86fd56d620e4f2508" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", @@ -3476,7 +3514,7 @@ version = "33.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "796abd77d5bfecd9e5275a99daf0ec45f5b3a793ec431349ce8211a67826fd22" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow 48.0.1", "arrow-array 48.0.1", "arrow-buffer 48.0.1", @@ -4654,6 +4692,27 @@ version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "event-listener" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +dependencies = [ + "event-listener 5.2.0", + "pin-project-lite", +] + [[package]] name = "expect-test" version = "1.5.0" @@ -4949,130 +5008,104 @@ dependencies = [ [[package]] name = "foyer" -version = "0.6.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15240094bab62dfeb59bb974e2fb7bff18727c6dc7bd1f5f6fc82a9e6fda5a38" +checksum = "6e7321845edd6be7f1d505413409d119cddf1fc110197d1d0606fb28cbec6d28" dependencies = [ + "ahash 0.8.11", + "anyhow", "foyer-common", "foyer-intrusive", "foyer-memory", "foyer-storage", - "foyer-workspace-hack", ] [[package]] name = "foyer-common" -version = "0.4.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad95f985cfbd5a8bf9f115d963918e742dfbb75350febff509b09cf194842b0c" +checksum = "deef44aeda48784c3ec1316fe64ed8d9cacbd50b37afcfd1aa211d4eb6360b61" dependencies = [ - "anyhow", + "ahash 0.8.11", "bytes", - "foyer-workspace-hack", + "cfg-if", + "crossbeam", + "hashbrown 0.14.3", "itertools 0.12.1", "madsim-tokio", + "nix 0.28.0", "parking_lot 0.12.1", - "paste", - "tracing", + "rustversion", + "serde", ] [[package]] name = "foyer-intrusive" -version = "0.3.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf22ed0dfa6315714099046504711d5563d191a442afcd87bd4d0bf5010bb9a" +checksum = "7145b59837d77f0090582f5631a6e36f01f09e451ab82685fdba6c9bdac7bd0d" dependencies = [ "bytes", - "cmsketch 0.1.5", "foyer-common", - "foyer-workspace-hack", "itertools 0.12.1", "memoffset", "parking_lot 0.12.1", - "paste", - "tracing", - "twox-hash", ] [[package]] name = "foyer-memory" -version = "0.1.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f59727e86709e4eab603f8a5086f3b2c7d3ddddf7c8b31d8dea2b00364b7fb95" +checksum = "c28baff47d3b1e4a4d416186066ec7bd590b7b50b6332a25e62fab1686904570" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "bitflags 2.5.0", - "cmsketch 0.2.0", - "crossbeam", + "cmsketch", + "foyer-common", "foyer-intrusive", - "foyer-workspace-hack", "futures", "hashbrown 0.14.3", "itertools 0.12.1", "libc", "madsim-tokio", "parking_lot 0.12.1", + "serde", ] [[package]] name = "foyer-storage" -version = "0.5.1" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e0749c569ef5f8d9f877a6a81da24e7d1b6f8ea87ec6c3933e12dac0c7632f" +checksum = "e3ec3638bdc66bc1c83aba27805770d2cafd18ec553e337e2563d69a5676898b" dependencies = [ + "ahash 0.8.11", + "allocator-api2", "anyhow", + "bincode 1.3.3", "bitflags 2.5.0", "bitmaps", "bytes", + "either", "foyer-common", - "foyer-intrusive", - "foyer-workspace-hack", + "foyer-memory", "futures", "itertools 0.12.1", + "lazy_static", "libc", "lz4", "madsim-tokio", "memoffset", - "nix 0.28.0", "parking_lot 0.12.1", "paste", "prometheus", "rand", + "serde", "thiserror", "tracing", "twox-hash", "zstd 0.13.0", ] -[[package]] -name = "foyer-workspace-hack" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5ee5ab890d1893232fd44af33197c7499813816b8880649a15ee9438d5e07" -dependencies = [ - "ahash 0.8.6", - "cc", - "crossbeam-channel", - "crossbeam-utils", - "either", - "futures-channel", - "futures-core", - "futures-executor", - "futures-sink", - "futures-util", - "hashbrown 0.14.3", - "libc", - "parking_lot 0.12.1", - "parking_lot_core 0.9.8", - "proc-macro2", - "quote", - "rand", - "smallvec", - "syn 2.0.57", - "tokio", - "tracing-core", -] - [[package]] name = "fragile" version = "2.0.0" @@ -5199,6 +5232,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "function_name" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1ab577a896d09940b5fe12ec5ae71f9d8211fff62c919c03a3750a9901e98a7" +dependencies = [ + "function_name-proc-macro", +] + +[[package]] +name = "function_name-proc-macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673464e1e314dd67a0fd9544abc99e8eb28d0c7e3b69b033bcff9b2d00b87333" + [[package]] name = "funty" version = "2.0.0" @@ -5480,9 +5528,9 @@ dependencies = [ [[package]] name = "ginepro" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eedbff62a689be48f58f32571dbf3d60c4a73b39740141dfe7ac942536ea27f7" +checksum = "3b00ef897d4082727a53ea1111cd19bfa4ccdc476a5eb9f49087047113a43891" dependencies = [ "anyhow", "async-trait", @@ -5606,7 +5654,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b2184a5c70b994e6d77eb1c140e193e7f5fe6015e9115322fac24f7e33f003c" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-stream", "google-cloud-auth", "google-cloud-gax", @@ -5747,7 +5795,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.7", + "ahash 0.7.8", ] [[package]] @@ -5756,7 +5804,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", ] [[package]] @@ -5765,7 +5813,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "allocator-api2", ] @@ -6289,7 +6337,7 @@ version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73c0fefcb6d409a6587c07515951495d482006f89a21daa0f2f783aa4fd5e027" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "indexmap 2.0.0", "is-terminal", "itoa", @@ -6529,9 +6577,9 @@ dependencies = [ [[package]] name = "jsonbb" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2edfc17ad42a5ece82df036301d5ef0c3dc3d071e28aa8a62e461711c55d19" +checksum = "91cdcbd02ee94c68803dd808bf8406e91491eaf875f09da650f5893dc56be18c" dependencies = [ "bytes", "serde", @@ -6939,32 +6987,23 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "mach2" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" dependencies = [ "libc", ] [[package]] name = "madsim" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7777a8bc4e68878b6e5433ac7b9bc196d9ccdfeef1f7cb3d23193cb997a520c9" +checksum = "c4d58385da6b81328e3e3ccb60426c0da8069a547d9979e2b11aae831089a37c" dependencies = [ - "ahash 0.7.7", - "async-channel", + "ahash 0.8.11", + "async-channel 2.2.1", "async-stream", "async-task", "bincode 1.3.3", @@ -6983,7 +7022,7 @@ dependencies = [ "spin 0.9.8", "tokio", "tokio-util", - "toml 0.7.8", + "toml 0.8.12", "tracing", "tracing-subscriber", ] @@ -7040,11 +7079,11 @@ dependencies = [ [[package]] name = "madsim-rdkafka" -version = "0.3.4+0.34.0" +version = "0.4.2+0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0992d46c19414bda24bad935ea223ad025dcd7e64f38f0acee388efa8ff5319b" +checksum = "6038ceb55ecf0d80eeae01a85dc9db052f0629ca2d82732923c022185096a76c" dependencies = [ - "async-channel", + "async-channel 1.9.0", "async-trait", "futures-channel", "futures-util", @@ -7758,6 +7797,15 @@ name = "object" version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "object" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" dependencies = [ "crc32fast", "hashbrown 0.14.3", @@ -8241,7 +8289,7 @@ version = "48.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bfe55df96e3f02f11bf197ae37d91bb79801631f82f6195dd196ef521df3597" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 48.0.1", "arrow-buffer 48.0.1", "arrow-cast 48.0.1", @@ -8275,7 +8323,7 @@ version = "50.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "547b92ebf0c1177e3892f44c8f79757ee62e678d564a9834189725f2c5b7a750" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "arrow-array 50.0.0", "arrow-buffer 50.0.0", "arrow-cast 50.0.0", @@ -8833,16 +8881,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bfa62906ce8d9badf8d1764501640ae7f0bcea3437a209315830e0f73564d1" -[[package]] -name = "prepare_ci_pubsub" -version = "0.1.0" -dependencies = [ - "anyhow", - "google-cloud-googleapis", - "google-cloud-pubsub", - "madsim-tokio", -] - [[package]] name = "pretty-xmlish" version = "0.1.13" @@ -9308,9 +9346,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" dependencies = [ "cfg-if", "indoc", @@ -9326,9 +9364,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" dependencies = [ "once_cell", "target-lexicon", @@ -9336,9 +9374,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" dependencies = [ "libc", "pyo3-build-config", @@ -9346,9 +9384,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -9358,9 +9396,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.3" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" dependencies = [ "heck 0.4.1", "proc-macro2", @@ -10449,6 +10487,7 @@ dependencies = [ "await-tree", "aws-config", "aws-credential-types", + "aws-msk-iam-sasl-signer", "aws-sdk-kinesis", "aws-sdk-s3", "aws-smithy-http", @@ -10459,6 +10498,7 @@ dependencies = [ "base64 0.22.0", "byteorder", "bytes", + "cfg-or-panic", "chrono", "clickhouse", "criterion", @@ -10562,6 +10602,7 @@ dependencies = [ "chrono", "clap", "comfy-table", + "foyer", "futures", "hex", "inquire", @@ -10655,7 +10696,9 @@ version = "1.9.0-alpha" dependencies = [ "anyhow", "arrow-array 50.0.0", + "arrow-flight", "arrow-schema 50.0.0", + "arrow-udf-flight", "arrow-udf-js", "arrow-udf-js-deno", "arrow-udf-python", @@ -10675,6 +10718,7 @@ dependencies = [ "futures", "futures-async-stream", "futures-util", + "ginepro", "itertools 0.12.1", "linkme", "madsim-tokio", @@ -10684,15 +10728,16 @@ dependencies = [ "openssl", "parse-display", "paste", + "prometheus", "risingwave_common", "risingwave_common_estimate_size", "risingwave_expr_macro", "risingwave_pb", - "risingwave_udf", "smallvec", "static_assertions", "thiserror", "thiserror-ext", + "tonic 0.10.2", "tracing", "workspace-hack", "zstd 0.13.0", @@ -10758,6 +10803,7 @@ dependencies = [ "anyhow", "arc-swap", "arrow-schema 50.0.0", + "arrow-udf-flight", "arrow-udf-wasm", "assert_matches", "async-recursion", @@ -10817,7 +10863,6 @@ dependencies = [ "risingwave_rpc_client", "risingwave_sqlparser", "risingwave_storage", - "risingwave_udf", "risingwave_variables", "rw_futures_util", "serde", @@ -10857,6 +10902,8 @@ dependencies = [ "risingwave_common", "risingwave_common_estimate_size", "risingwave_pb", + "serde", + "serde_bytes", "tracing", "workspace-hack", ] @@ -10940,6 +10987,7 @@ dependencies = [ "cfg-or-panic", "chrono", "expect-test", + "foyer", "fs-err", "futures", "itertools 0.12.1", @@ -10998,6 +11046,7 @@ dependencies = [ "enum-as-inner 0.6.0", "expect-test", "fail", + "function_name", "futures", "hex", "hyper 0.14.27", @@ -11439,12 +11488,13 @@ dependencies = [ name = "risingwave_storage" version = "1.9.0-alpha" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "anyhow", "arc-swap", "async-trait", "auto_enums", "await-tree", + "bincode 1.3.3", "bytes", "criterion", "crossbeam", @@ -11488,6 +11538,8 @@ dependencies = [ "risingwave_rpc_client", "risingwave_test_runner", "scopeguard", + "serde", + "serde_bytes", "sled", "spin 0.9.8", "sync-point", @@ -11580,28 +11632,6 @@ dependencies = [ "workspace-hack", ] -[[package]] -name = "risingwave_udf" -version = "0.1.0" -dependencies = [ - "arrow-array 50.0.0", - "arrow-flight", - "arrow-schema 50.0.0", - "arrow-select 50.0.0", - "cfg-or-panic", - "futures", - "futures-util", - "ginepro", - "madsim-tokio", - "madsim-tonic", - "prometheus", - "risingwave_common", - "static_assertions", - "thiserror", - "thiserror-ext", - "tracing", -] - [[package]] name = "risingwave_variables" version = "1.9.0-alpha" @@ -11733,9 +11763,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.3.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" +checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -11744,9 +11774,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.3.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" +checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" dependencies = [ "proc-macro2", "quote", @@ -11758,9 +11788,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.3.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" +checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" dependencies = [ "mime_guess", "sha2", @@ -12135,7 +12165,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "cfg-if", "hashbrown 0.13.2", ] @@ -13178,7 +13208,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd4cef4251aabbae751a3710927945901ee1d97ee96d757f6880ebb9a79bfd53" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "atoi", "bigdecimal 0.3.1", "byteorder", @@ -13188,7 +13218,7 @@ dependencies = [ "crossbeam-queue", "dotenvy", "either", - "event-listener", + "event-listener 2.5.3", "futures-channel", "futures-core", "futures-intrusive", @@ -13551,7 +13581,7 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630c761c74ac8021490b78578cc2223aa4a568241e26505c27bf0e4fd4ad8ec2" dependencies = [ - "ahash 0.8.6", + "ahash 0.8.11", "anyhow", "dashmap", "once_cell", @@ -15300,9 +15330,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi-common" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b53dfacdeacca15ee2a48a4aa0ec6a6d0da737676e465770c0585f79c04e638" +checksum = "63255d85e10627b07325d7cf4e5fe5a40fa4ff183569a0a67931be26d50ede07" dependencies = [ "anyhow", "bitflags 2.5.0", @@ -15398,9 +15428,18 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "wasm-encoder" -version = "0.201.0" +version = "0.202.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.206.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a" +checksum = "d759312e1137f199096d80a70be685899cd7d3d09c572836bb2e9b69b4dc3b1e" dependencies = [ "leb128", ] @@ -15433,9 +15472,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" +checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" dependencies = [ "bitflags 2.5.0", "indexmap 2.0.0", @@ -15444,9 +15483,9 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a67e66da702706ba08729a78e3c0079085f6bfcb1a62e4799e97bbf728c2c265" +checksum = "ab1cc9508685eef9502e787f4d4123745f5651a1e29aec047645d3cac1e2da7a" dependencies = [ "anyhow", "wasmparser", @@ -15454,9 +15493,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516be5b58a8f75d39b01378516dcb0ff7b9bc39c7f1f10eec5b338d4916cf988" +checksum = "5a5990663c28d81015ddbb02a068ac1bf396a4ea296eba7125b2dfc7c00cb52e" dependencies = [ "addr2line", "anyhow", @@ -15471,7 +15510,7 @@ dependencies = [ "ittapi", "libc", "log", - "object", + "object 0.33.0", "once_cell", "paste", "rayon", @@ -15481,7 +15520,7 @@ dependencies = [ "serde_derive", "serde_json", "target-lexicon", - "wasm-encoder", + "wasm-encoder 0.202.0", "wasmparser", "wasmtime-cache", "wasmtime-component-macro", @@ -15500,18 +15539,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d22d88a92d69385f18143c946884bf6aaa9ec206ce54c85a2d320c1362b009" +checksum = "625ee94c72004f3ea0228989c9506596e469517d7d0ed66f7300d1067bdf1ca9" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068728a840223b56c964507550da671372e7e5c2f3a7856012b57482e3e979a7" +checksum = "98534bf28de232299e83eab33984a7a6c40c69534d6bd0ea216150b63d41a83a" dependencies = [ "anyhow", "base64 0.21.7", @@ -15529,9 +15568,9 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631244bac89c57ebe7283209d86fe175ad5929328e75f61bf9141895cafbf52d" +checksum = "64f84414a25ee3a624c8b77550f3fe7b5d8145bd3405ca58886ee6900abb6dc2" dependencies = [ "anyhow", "proc-macro2", @@ -15544,15 +15583,15 @@ dependencies = [ [[package]] name = "wasmtime-component-util" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ad496ba0558f7602da5e9d4c201f35f7aefcca70f973ec916f3f0d0787ef74" +checksum = "78580bdb4e04c7da3bf98088559ca1d29382668536e4d5c7f2f966d79c390307" [[package]] name = "wasmtime-cranelift" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ab5ee4b17e627001b18069ee89ef906edbbd3f84955515f6aad5ab6d82299" +checksum = "b60df0ee08c6a536c765f69e9e8205273435b66d02dd401e938769a2622a6c1a" dependencies = [ "anyhow", "cfg-if", @@ -15564,36 +15603,19 @@ dependencies = [ "cranelift-wasm", "gimli", "log", - "object", + "object 0.33.0", "target-lexicon", "thiserror", "wasmparser", - "wasmtime-cranelift-shared", "wasmtime-environ", "wasmtime-versioned-export-macros", ] -[[package]] -name = "wasmtime-cranelift-shared" -version = "19.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc4db94596be14cd1f85844ce85470bf68acf235143098b9d9bf72b49e47b917" -dependencies = [ - "anyhow", - "cranelift-codegen", - "cranelift-control", - "cranelift-native", - "gimli", - "object", - "target-lexicon", - "wasmtime-environ", -] - [[package]] name = "wasmtime-environ" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420b13858ef27dfd116f1fdb0513e9593a307a632ade2ea58334b639a3d8d24e" +checksum = "64ffc1613db69ee47c96738861534f9a405e422a5aa00224fbf5d410b03fb445" dependencies = [ "anyhow", "bincode 1.3.3", @@ -15602,13 +15624,13 @@ dependencies = [ "gimli", "indexmap 2.0.0", "log", - "object", + "object 0.33.0", "rustc-demangle", "serde", "serde_derive", "target-lexicon", "thiserror", - "wasm-encoder", + "wasm-encoder 0.202.0", "wasmparser", "wasmprinter", "wasmtime-component-util", @@ -15617,9 +15639,9 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d37ff0e11a023019e34fe839c74a1c00880b989f4446176b6cc6da3b58e3ef2" +checksum = "f043514a23792761c5765f8ba61a4aa7d67f260c0c37494caabceb41d8ae81de" dependencies = [ "anyhow", "cc", @@ -15632,11 +15654,11 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b849f19ad1d4a8133ff05b82c438144f17fb49b08e5f7995f8c1e25cf35f390" +checksum = "9c0ca2ad8f5d2b37f507ef1c935687a690e84e9f325f5a2af9639440b43c1f0e" dependencies = [ - "object", + "object 0.33.0", "once_cell", "rustix 0.38.31", "wasmtime-versioned-export-macros", @@ -15644,9 +15666,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c48eb4223d6556ffbf3decb146d0da124f1fd043f41c98b705252cb6a5c186" +checksum = "7a9f93a3289057b26dc75eb84d6e60d7694f7d169c7c09597495de6e016a13ff" dependencies = [ "cfg-if", "libc", @@ -15655,9 +15677,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fefac2cb5f5a6f365234a3584bf40bd2e45e7f6cd90a689d9b2afbb9881978f" +checksum = "c6332a2b0af4224c3ea57c857ad39acd2780ccc2b0c99ba1baa01864d90d7c94" dependencies = [ "anyhow", "cc", @@ -15666,34 +15688,34 @@ dependencies = [ "indexmap 2.0.0", "libc", "log", - "mach", + "mach2", "memfd", "memoffset", "paste", "psm", "rustix 0.38.31", "sptr", - "wasm-encoder", + "wasm-encoder 0.202.0", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-fiber", "wasmtime-jit-debug", + "wasmtime-slab", "wasmtime-versioned-export-macros", - "wasmtime-wmemcheck", "windows-sys 0.52.0", ] [[package]] name = "wasmtime-slab" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d7b97b92df126fdbe994a53d2215828ec5ed5087535e6d4703b1fbd299f0e3" +checksum = "8b3655075824a374c536a2b2cc9283bb765fcdf3d58b58587862c48571ad81ef" [[package]] name = "wasmtime-types" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509c88abb830819b259c49e2d4e4f22b555db066ba08ded0b76b071a2aa53ddf" +checksum = "b98cf64a242b0b9257604181ca28b28a5fcaa4c9ea1d396f76d1d2d1c5b40eef" dependencies = [ "cranelift-entity", "serde", @@ -15704,9 +15726,9 @@ dependencies = [ [[package]] name = "wasmtime-versioned-export-macros" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d81c092a61ca1667013e2eb08fed7c6c53e496dbbaa32d5685dc5152b0a772" +checksum = "8561d9e2920db2a175213d557d71c2ac7695831ab472bbfafb9060cd1034684f" dependencies = [ "proc-macro2", "quote", @@ -15715,26 +15737,26 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0958907880e37a2d3974f5b3574c23bf70aaf1fc6c1f716625bb50dac776f1a" +checksum = "a06b573d14ac846a0fb8c541d8fca6a64acf9a1d176176982472274ab1d2fa5d" dependencies = [ "anyhow", "cranelift-codegen", "gimli", - "object", + "object 0.33.0", "target-lexicon", "wasmparser", - "wasmtime-cranelift-shared", + "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", ] [[package]] name = "wasmtime-wit-bindgen" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a593ddefd2f80617df6bea084b2e422d8969e924bc209642a794d57518f59587" +checksum = "595bc7bb3b0ff4aa00fab718c323ea552c3034d77abc821a35112552f2ea487a" dependencies = [ "anyhow", "heck 0.4.1", @@ -15742,12 +15764,6 @@ dependencies = [ "wit-parser", ] -[[package]] -name = "wasmtime-wmemcheck" -version = "19.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77212b6874bbc86d220bb1d28632d0c11c6afe996c3e1ddcf746b1a6b4919b9" - [[package]] name = "wast" version = "35.0.2" @@ -15759,24 +15775,24 @@ dependencies = [ [[package]] name = "wast" -version = "201.0.0" +version = "206.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef6e1ef34d7da3e2b374fd2b1a9c0227aff6cad596e1b24df9b58d0f6222faa" +checksum = "68586953ee4960b1f5d84ebf26df3b628b17e6173bc088e0acfbce431469795a" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.206.0", ] [[package]] name = "wat" -version = "1.201.0" +version = "1.206.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453d5b37a45b98dee4f4cb68015fc73634d7883bbef1c65e6e9c78d454cf3f32" +checksum = "da4c6f2606276c6e991aebf441b2fc92c517807393f039992a3e0ad873efe4ad" dependencies = [ - "wast 201.0.0", + "wast 206.0.0", ] [[package]] @@ -15864,9 +15880,9 @@ checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" [[package]] name = "wiggle" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f093d8afdb09efaf2ed1037468bd4614308a762d215b6cafd60a7712993a8ffa" +checksum = "1b6552dda951239e219c329e5a768393664e8d120c5e0818487ac2633f173b1f" dependencies = [ "anyhow", "async-trait", @@ -15879,9 +15895,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c7bccd5172ce8d853242f723e42c84b8c131b24fb07a1570f9045d99258616" +checksum = "da64cb31e0bfe8b1d2d13956ef9fd5c77545756a1a6ef0e6cfd44e8f1f207aed" dependencies = [ "anyhow", "heck 0.4.1", @@ -15894,9 +15910,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "19.0.1" +version = "20.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69d087dee85991096fc0c6eaf4dcf4e17cd16a0594c33b8ab9e2d345234ef75" +checksum = "900b2416ef2ff2903ded6cf55d4a941fed601bf56a8c4874856d7a77c1891994" dependencies = [ "proc-macro2", "quote", @@ -15937,9 +15953,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.17.1" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72a6a7034793b874b85e428fd6d7b3ccccb98c326e33af3aa40cdf50d0c33da" +checksum = "fb23450977f9d4a23c02439cf6899340b2d68887b19465c5682740d9cc37d52e" dependencies = [ "anyhow", "cranelift-codegen", @@ -15948,6 +15964,7 @@ dependencies = [ "smallvec", "target-lexicon", "wasmparser", + "wasmtime-cranelift", "wasmtime-environ", ] @@ -16236,9 +16253,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.201.0" +version = "0.202.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" +checksum = "744237b488352f4f27bca05a10acb79474415951c450e52ebd0da784c1df2bcc" dependencies = [ "anyhow", "id-arena", diff --git a/Cargo.toml b/Cargo.toml index ce1a66c94bdaa..c906f622258bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "scripts/source/prepare_ci_pubsub", "src/batch", "src/bench", "src/cmd", @@ -20,7 +19,6 @@ members = [ "src/expr/core", "src/expr/impl", "src/expr/macro", - "src/expr/udf", "src/frontend", "src/frontend/macro", "src/frontend/planner_test", @@ -78,7 +76,7 @@ license = "Apache-2.0" repository = "https://github.com/risingwavelabs/risingwave" [workspace.dependencies] -foyer = "0.6" +foyer = { version = "0.8.6", features = ["nightly"] } auto_enums = { version = "0.8", features = ["futures03", "tokio1"] } await-tree = "0.2.1" aws-config = { version = "1", default-features = false, features = [ @@ -118,7 +116,7 @@ axum = "=0.7.4" # TODO: 0.7.5+ does not work with current toolchain etcd-client = { package = "madsim-etcd-client", version = "0.4" } futures-async-stream = "0.2.9" hytra = "0.1" -rdkafka = { package = "madsim-rdkafka", version = "0.3.4", features = [ +rdkafka = { package = "madsim-rdkafka", version = "0.4.1", features = [ "cmake-build", ] } hashbrown = { version = "0.14", features = ["ahash", "inline-more", "nightly"] } @@ -139,10 +137,11 @@ arrow-flight = "50" arrow-select = "50" arrow-ord = "50" arrow-row = "50" -arrow-udf-js = "0.1" -arrow-udf-js-deno = { git = "https://github.com/risingwavelabs/arrow-udf.git", rev = "23fe0dd" } -arrow-udf-wasm = { version = "0.2.1", features = ["build"] } -arrow-udf-python = { git = "https://github.com/risingwavelabs/arrow-udf.git", rev = "6c32f71" } +arrow-udf-js = "0.2" +arrow-udf-js-deno = { git = "https://github.com/risingwavelabs/arrow-udf.git", rev = "fa36365" } +arrow-udf-wasm = { version = "0.2.2", features = ["build"] } +arrow-udf-python = "0.1" +arrow-udf-flight = "0.1" arrow-array-deltalake = { package = "arrow-array", version = "48.0.1" } arrow-buffer-deltalake = { package = "arrow-buffer", version = "48.0.1" } arrow-cast-deltalake = { package = "arrow-cast", version = "48.0.1" } @@ -155,6 +154,7 @@ deltalake = { git = "https://github.com/risingwavelabs/delta-rs", rev = "5c2dccd "gcs", ] } itertools = "0.12.0" +jsonbb = "0.1.4" lru = { git = "https://github.com/risingwavelabs/lru-rs.git", rev = "2682b85" } parquet = "50" thiserror-ext = "0.1.2" @@ -251,8 +251,11 @@ await_holding_lock = "warn" ptr_arg = "allow" # a little pedantic get_first = "allow" -# https://github.com/rust-lang/rust-clippy/issues/12016 -blocks_in_conditions = "allow" +new_without_default = "allow" +# TODO: remove later https://github.com/rust-lang/rust-clippy/issues/12537 +# duplicated_attributes = "allow" +# TODO: remove later https://github.com/rust-lang/rust-clippy/issues/12436 +mixed_attributes_style = "allow" [workspace.lints.rustdoc] private_intra_doc_links = "allow" diff --git a/README.md b/README.md index e59af1a0395b4..47893c7bea1ba 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,8 @@ RisingWave is a Postgres-compatible streaming database engineered to provide the simplest and most cost-efficient approach for processing, analyzing, and managing real-time event streaming data. -![RisingWave](https://github.com/risingwavelabs/risingwave-docs/blob/main/docs/images/new_archi_grey.png) +![RisingWave](https://github.com/risingwavelabs/risingwave/assets/41638002/10c44404-f78b-43ce-bbd9-3646690acc59) + ## Try it out in 60 seconds diff --git a/ci/Dockerfile b/ci/Dockerfile index d91853232f626..616af35fd118e 100644 --- a/ci/Dockerfile +++ b/ci/Dockerfile @@ -76,7 +76,7 @@ RUN cargo binstall -y --no-symlinks cargo-llvm-cov cargo-nextest cargo-hakari ca && rm -rf "/root/.cargo/registry/index" \ && rm -rf "/root/.cargo/registry/cache" \ && rm -rf "/root/.cargo/git/db" -RUN cargo install cargo-dylint@2.6.0 dylint-link@2.6.0 +RUN cargo install cargo-dylint@3.1.0 dylint-link@3.1.0 RUN cargo uninstall cargo-binstall cargo-cache # install risedev diff --git a/ci/build-ci-image.sh b/ci/build-ci-image.sh index f9afdd4845d34..778c1a9f315d0 100755 --- a/ci/build-ci-image.sh +++ b/ci/build-ci-image.sh @@ -10,7 +10,7 @@ cat ../rust-toolchain # shellcheck disable=SC2155 # REMEMBER TO ALSO UPDATE ci/docker-compose.yml -export BUILD_ENV_VERSION=v20240424_x +export BUILD_ENV_VERSION=v20240514-1 export BUILD_TAG="public.ecr.aws/w1p7b4n3/rw-build-env:${BUILD_ENV_VERSION}" diff --git a/ci/docker-compose.yml b/ci/docker-compose.yml index 09f2888feaed4..34d629e91cf77 100644 --- a/ci/docker-compose.yml +++ b/ci/docker-compose.yml @@ -71,7 +71,7 @@ services: retries: 5 source-test-env: - image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240424_x + image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240514-1 depends_on: - mysql - db @@ -84,7 +84,7 @@ services: - ..:/risingwave sink-test-env: - image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240424_x + image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240514-1 depends_on: - mysql - db @@ -103,12 +103,12 @@ services: rw-build-env: - image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240424_x + image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240514-1 volumes: - ..:/risingwave ci-flamegraph-env: - image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240424_x + image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240514-1 # NOTE(kwannoel): This is used in order to permit # syscalls for `nperf` (perf_event_open), # so it can do CPU profiling. @@ -119,7 +119,7 @@ services: - ..:/risingwave regress-test-env: - image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240424_x + image: public.ecr.aws/w1p7b4n3/rw-build-env:v20240514-1 depends_on: db: condition: service_healthy diff --git a/ci/rust-toolchain b/ci/rust-toolchain index b1f2df70a8d0f..92ef4a5ff27be 100644 --- a/ci/rust-toolchain +++ b/ci/rust-toolchain @@ -4,4 +4,4 @@ # 3. (optional) **follow the instructions in lints/README.md** to update the toolchain and dependencies for lints [toolchain] -channel = "nightly-2023-12-26" +channel = "nightly-2024-03-12" diff --git a/ci/scripts/build-other.sh b/ci/scripts/build-other.sh index 2311e5164fe74..65c50462f97a0 100755 --- a/ci/scripts/build-other.sh +++ b/ci/scripts/build-other.sh @@ -16,9 +16,13 @@ cd java mvn -B package -Dmaven.test.skip=true mvn -B install -Dmaven.test.skip=true --pl java-binding-integration-test --am mvn dependency:copy-dependencies --no-transfer-progress --pl java-binding-integration-test -mvn -B test --pl udf cd .. +echo "--- Build Java UDF" +cd e2e_test/udf/java +mvn -B package +cd ../../.. + echo "--- Build rust binary for java binding integration test" cargo build -p risingwave_java_binding --bin data-chunk-payload-generator --bin data-chunk-payload-convert-generator @@ -30,9 +34,9 @@ tar --zstd -cf java-binding-integration-test.tar.zst bin java/java-binding-integ echo "--- Upload Java artifacts" cp java/connector-node/assembly/target/risingwave-connector-1.0.0.tar.gz ./risingwave-connector.tar.gz -cp java/udf-example/target/risingwave-udf-example.jar ./risingwave-udf-example.jar +cp e2e_test/udf/java/target/risingwave-udf-example.jar ./udf.jar cp e2e_test/udf/wasm/target/wasm32-wasi/release/udf.wasm udf.wasm buildkite-agent artifact upload ./risingwave-connector.tar.gz -buildkite-agent artifact upload ./risingwave-udf-example.jar buildkite-agent artifact upload ./java-binding-integration-test.tar.zst +buildkite-agent artifact upload ./udf.jar buildkite-agent artifact upload ./udf.wasm diff --git a/ci/scripts/build-simulation.sh b/ci/scripts/build-simulation.sh index c2af5b0f3b7bc..7d8f09c5ca923 100755 --- a/ci/scripts/build-simulation.sh +++ b/ci/scripts/build-simulation.sh @@ -9,7 +9,7 @@ echo "--- Generate RiseDev CI config" cp ci/risedev-components.ci.env risedev-components.user.env echo "--- Build deterministic simulation e2e test runner" -risedev sslt-build-all --profile ci-sim +risedev sslt-build-all --profile ci-sim --timings echo "--- Show sccache stats" sccache --show-stats @@ -25,6 +25,7 @@ sccache --zero-stats echo "--- Upload artifacts" mv target/sim/ci-sim/risingwave_simulation ./risingwave_simulation tar --zstd -cvf risingwave_simulation.tar.zst risingwave_simulation +buildkite-agent artifact upload target/sim/cargo-timings/cargo-timing.html artifacts=(risingwave_simulation.tar.zst simulation-it-test.tar.zst) echo -n "${artifacts[*]}" | parallel -d ' ' "buildkite-agent artifact upload ./{}" diff --git a/ci/scripts/build.sh b/ci/scripts/build.sh index f50c5a794ec8d..026352908cf2f 100755 --- a/ci/scripts/build.sh +++ b/ci/scripts/build.sh @@ -56,7 +56,8 @@ cargo build \ "${RISINGWAVE_FEATURE_FLAGS[@]}" \ --features embedded-deno-udf \ --features embedded-python-udf \ - --profile "$profile" + --profile "$profile" \ + --timings artifacts=(risingwave sqlsmith compaction-test risingwave_regress_test risingwave_e2e_extended_mode_test risedev-dev delete-range-test) @@ -66,6 +67,7 @@ ldd target/"$profile"/risingwave echo "--- Upload artifacts" echo -n "${artifacts[*]}" | parallel -d ' ' "mv target/$profile/{} ./{}-$profile && compress-and-upload-artifact ./{}-$profile" +buildkite-agent artifact upload target/cargo-timings/cargo-timing.html # This magically makes it faster to exit the docker rm -rf target diff --git a/ci/scripts/common.sh b/ci/scripts/common.sh index d99066cb3d5e2..f1177ecf8320c 100755 --- a/ci/scripts/common.sh +++ b/ci/scripts/common.sh @@ -12,7 +12,7 @@ export CARGO_INCREMENTAL=0 export CARGO_MAKE_PRINT_TIME_SUMMARY=true export MINIO_DOWNLOAD_BIN=https://rw-ci-deps-dist.s3.amazonaws.com/minio export MCLI_DOWNLOAD_BIN=https://rw-ci-deps-dist.s3.amazonaws.com/mc -export GCLOUD_DOWNLOAD_TGZ=https://rw-ci-deps-dist.s3.amazonaws.com/google-cloud-cli-406.0.0-linux-x86_64.tar.gz +export GCLOUD_DOWNLOAD_TGZ=https://rw-ci-deps-dist.s3.amazonaws.com/google-cloud-cli-475.0.0-linux-x86_64.tar.gz export NEXTEST_HIDE_PROGRESS_BAR=true unset LANG if [ -n "${BUILDKITE_COMMIT:-}" ]; then diff --git a/ci/scripts/connector-node-integration-test.sh b/ci/scripts/connector-node-integration-test.sh index c90430d80ba3e..8853243b66805 100755 --- a/ci/scripts/connector-node-integration-test.sh +++ b/ci/scripts/connector-node-integration-test.sh @@ -90,15 +90,15 @@ export PYTHONPATH=proto echo "--- running streamchunk data format integration tests" cd "${RISINGWAVE_ROOT}"/java/connector-node/python-client -if python3 integration_tests.py --stream_chunk_format_test --input_binary_file="./data/stream_chunk_data" --data_format_use_json=False; then +if python3 integration_tests.py --stream_chunk_format_test --input_binary_file="./data/stream_chunk_data"; then echo "StreamChunk data format test passed" else echo "StreamChunk data format test failed" exit 1 fi -sink_input_feature=("--input_binary_file=./data/sink_input --data_format_use_json=False") -upsert_sink_input_feature=("--input_binary_file=./data/upsert_sink_input --data_format_use_json=False") +sink_input_feature=("--input_binary_file=./data/sink_input") +upsert_sink_input_feature=("--input_binary_file=./data/upsert_sink_input") type=("StreamChunk format") ${MC_PATH} mb minio/bucket diff --git a/ci/scripts/e2e-source-test.sh b/ci/scripts/e2e-source-test.sh index e91438402a613..76e0900c32fef 100755 --- a/ci/scripts/e2e-source-test.sh +++ b/ci/scripts/e2e-source-test.sh @@ -149,9 +149,8 @@ risedev ci-kill echo "--- e2e, ci-kafka-plus-pubsub, kafka and pubsub source" RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=info" \ -risedev ci-start ci-pubsub-kafka +risedev ci-start ci-kafka ./scripts/source/prepare_ci_kafka.sh -cargo run --bin prepare_ci_pubsub risedev slt './e2e_test/source/basic/*.slt' risedev slt './e2e_test/source/basic/old_row_format_syntax/*.slt' risedev slt './e2e_test/source/basic/alter/kafka.slt' diff --git a/ci/scripts/run-e2e-test.sh b/ci/scripts/run-e2e-test.sh index 5ce0b55f27e9e..b6bf0673e6ae4 100755 --- a/ci/scripts/run-e2e-test.sh +++ b/ci/scripts/run-e2e-test.sh @@ -70,7 +70,7 @@ download-and-decompress-artifact e2e_test_generated ./ download-and-decompress-artifact risingwave_e2e_extended_mode_test-"$profile" target/debug/ mkdir -p e2e_test/udf/wasm/target/wasm32-wasi/release/ buildkite-agent artifact download udf.wasm e2e_test/udf/wasm/target/wasm32-wasi/release/ -buildkite-agent artifact download risingwave-udf-example.jar ./ +buildkite-agent artifact download udf.jar ./ mv target/debug/risingwave_e2e_extended_mode_test-"$profile" target/debug/risingwave_e2e_extended_mode_test chmod +x ./target/debug/risingwave_e2e_extended_mode_test @@ -80,6 +80,7 @@ RUST_LOG="info,risingwave_stream=info,risingwave_batch=info,risingwave_storage=i cluster_start # Please make sure the regression is expected before increasing the timeout. sqllogictest -p 4566 -d dev './e2e_test/streaming/**/*.slt' --junit "streaming-${profile}" +sqllogictest -p 4566 -d dev './e2e_test/backfill/rate_limit/*.slt' echo "--- Kill cluster" cluster_stop @@ -105,6 +106,7 @@ echo "--- e2e, $mode, Apache Superset" sqllogictest -p 4566 -d dev './e2e_test/superset/*.slt' --junit "batch-${profile}" echo "--- e2e, $mode, external python udf" +python3 -m pip install --break-system-packages arrow-udf==0.2.1 python3 e2e_test/udf/test.py & sleep 1 sqllogictest -p 4566 -d dev './e2e_test/udf/external_udf.slt' @@ -117,7 +119,7 @@ sqllogictest -p 4566 -d dev './e2e_test/udf/always_retry_python.slt' # sqllogictest -p 4566 -d dev './e2e_test/udf/retry_python.slt' echo "--- e2e, $mode, external java udf" -java -jar risingwave-udf-example.jar & +java -jar udf.jar & sleep 1 sqllogictest -p 4566 -d dev './e2e_test/udf/external_udf.slt' pkill java diff --git a/ci/scripts/run-unit-test.sh b/ci/scripts/run-unit-test.sh index d9a723a34fa19..394cdb1a78261 100755 --- a/ci/scripts/run-unit-test.sh +++ b/ci/scripts/run-unit-test.sh @@ -5,11 +5,6 @@ set -euo pipefail REPO_ROOT=${PWD} -echo "+++ Run python UDF SDK unit tests" -cd "${REPO_ROOT}"/src/expr/udf/python -python3 -m pytest -cd "${REPO_ROOT}" - echo "+++ Run unit tests" # use tee to disable progress bar NEXTEST_PROFILE=ci cargo nextest run --features failpoints,sync_point --workspace --exclude risingwave_simulation diff --git a/dashboard/components/Layout.tsx b/dashboard/components/Layout.tsx index 7be9f727bddec..6daa7e821ce3c 100644 --- a/dashboard/components/Layout.tsx +++ b/dashboard/components/Layout.tsx @@ -140,6 +140,7 @@ function Layout({ children }: { children: React.ReactNode }) { Internal Tables Sinks Views + Subscriptions
Streaming diff --git a/dashboard/components/Relations.tsx b/dashboard/components/Relations.tsx index 8f536897fb808..49161feea908b 100644 --- a/dashboard/components/Relations.tsx +++ b/dashboard/components/Relations.tsx @@ -183,12 +183,16 @@ export function Relations( {extraColumns.map((c) => ( {c.content(r)} ))} - - {r.columns - .filter((col) => ("isHidden" in col ? !col.isHidden : true)) - .map((col) => extractColumnInfo(col)) - .join(", ")} - + {r.columns && r.columns.length > 0 && ( + + {r.columns + .filter((col) => + "isHidden" in col ? !col.isHidden : true + ) + .map((col) => extractColumnInfo(col)) + .join(", ")} + + )} ))} diff --git a/dashboard/lib/api/api.ts b/dashboard/lib/api/api.ts index 6c5d885542454..af0116e31f3bb 100644 --- a/dashboard/lib/api/api.ts +++ b/dashboard/lib/api/api.ts @@ -26,9 +26,7 @@ export const PREDEFINED_API_ENDPOINTS = [ ] export const DEFAULT_API_ENDPOINT: string = - process.env.NODE_ENV === "production" - ? PROD_API_ENDPOINT - : MOCK_API_ENDPOINT; // EXTERNAL_META_NODE_API_ENDPOINT to debug with RisingWave servers + process.env.NODE_ENV === "production" ? PROD_API_ENDPOINT : MOCK_API_ENDPOINT // EXTERNAL_META_NODE_API_ENDPOINT to debug with RisingWave servers export const API_ENDPOINT_KEY = "risingwave.dashboard.api.endpoint" diff --git a/dashboard/lib/api/streaming.ts b/dashboard/lib/api/streaming.ts index 1a8e97081caa4..948cd567d3f2b 100644 --- a/dashboard/lib/api/streaming.ts +++ b/dashboard/lib/api/streaming.ts @@ -22,6 +22,7 @@ import { Schema, Sink, Source, + Subscription, Table, View, } from "../../proto/gen/catalog" @@ -47,9 +48,9 @@ export interface Relation { owner: number schemaId: number databaseId: number - columns: (ColumnCatalog | Field)[] // For display + columns?: (ColumnCatalog | Field)[] ownerName?: string schemaName?: string databaseName?: string @@ -66,6 +67,8 @@ export function relationType(x: Relation) { return "SINK" } else if ((x as Source).info !== undefined) { return "SOURCE" + } else if ((x as Subscription).dependentTableId !== undefined) { + return "SUBSCRIPTION" } else { return "UNKNOWN" } @@ -98,7 +101,8 @@ export async function getRelations() { await getTables(), await getIndexes(), await getSinks(), - await getSources() + await getSources(), + await getSubscriptions() ) relations = sortBy(relations, (x) => x.id) return relations @@ -150,6 +154,14 @@ export async function getViews() { return views } +export async function getSubscriptions() { + let subscriptions: Subscription[] = (await api.get("/subscriptions")).map( + Subscription.fromJSON + ) + subscriptions = sortBy(subscriptions, (x) => x.id) + return subscriptions +} + export async function getUsers() { let users: UserInfo[] = (await api.get("/users")).map(UserInfo.fromJSON) users = sortBy(users, (x) => x.id) diff --git a/dashboard/mock-server.js b/dashboard/mock-server.js index 2db52df788e22..50c55e12686b8 100644 --- a/dashboard/mock-server.js +++ b/dashboard/mock-server.js @@ -45,6 +45,10 @@ app.get("/indexes", (req, res, next) => { res.json(require("./mock/indexes.json")) }) +app.get("/indexes", (req, res, next) => { + res.json(require("./mock/indexes.json")) +}) + app.get("/internal_tables", (req, res, next) => { res.json(require("./mock/internal_tables.json")) }) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 1c462e1675207..5bf9ae127252e 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -6,6 +6,7 @@ "": { "hasInstallScript": true, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", @@ -26,7 +27,7 @@ "fabric": "^5.2.1", "framer-motion": "^6.5.1", "lodash": "^4.17.21", - "next": "^14.1.0", + "next": "^14.1.1", "nuqs": "^1.14.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -2092,9 +2093,9 @@ } }, "node_modules/@next/env": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", - "integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz", + "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.1.0", @@ -2161,9 +2162,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", - "integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz", + "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==", "cpu": [ "arm64" ], @@ -2176,9 +2177,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", - "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz", + "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==", "cpu": [ "x64" ], @@ -2191,9 +2192,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", - "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz", + "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==", "cpu": [ "arm64" ], @@ -2206,9 +2207,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", - "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz", + "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==", "cpu": [ "arm64" ], @@ -2221,9 +2222,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", - "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz", + "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==", "cpu": [ "x64" ], @@ -2236,9 +2237,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", - "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz", + "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==", "cpu": [ "x64" ], @@ -2251,9 +2252,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", - "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz", + "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==", "cpu": [ "arm64" ], @@ -2266,9 +2267,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", - "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz", + "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==", "cpu": [ "ia32" ], @@ -2281,9 +2282,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", - "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz", + "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==", "cpu": [ "x64" ], @@ -3350,6 +3351,14 @@ "resolved": "https://registry.npmjs.org/@zag-js/focus-visible/-/focus-visible-0.1.0.tgz", "integrity": "sha512-PeaBcTmdZWcFf7n1aM+oiOdZc+sy14qi0emPIeUuGMTjbP0xLGrZu43kdpHnWSXy7/r4Ubp/vlg50MCV8+9Isg==" }, + "node_modules/16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "dependencies": { + "numeric": "^1.2.6" + } + }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -8538,11 +8547,11 @@ } }, "node_modules/next": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", - "integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz", + "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==", "dependencies": { - "@next/env": "14.1.0", + "@next/env": "14.1.1", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -8557,15 +8566,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.1.0", - "@next/swc-darwin-x64": "14.1.0", - "@next/swc-linux-arm64-gnu": "14.1.0", - "@next/swc-linux-arm64-musl": "14.1.0", - "@next/swc-linux-x64-gnu": "14.1.0", - "@next/swc-linux-x64-musl": "14.1.0", - "@next/swc-win32-arm64-msvc": "14.1.0", - "@next/swc-win32-ia32-msvc": "14.1.0", - "@next/swc-win32-x64-msvc": "14.1.0" + "@next/swc-darwin-arm64": "14.1.1", + "@next/swc-darwin-x64": "14.1.1", + "@next/swc-linux-arm64-gnu": "14.1.1", + "@next/swc-linux-arm64-musl": "14.1.1", + "@next/swc-linux-x64-gnu": "14.1.1", + "@next/swc-linux-x64-musl": "14.1.1", + "@next/swc-win32-arm64-msvc": "14.1.1", + "@next/swc-win32-ia32-msvc": "14.1.1", + "@next/swc-win32-x64-msvc": "14.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -8680,6 +8689,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "node_modules/nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", @@ -11586,6 +11600,14 @@ } }, "dependencies": { + "16": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/16/-/16-0.0.2.tgz", + "integrity": "sha512-AhG4lpdn+/it+U5Xl1bm5SbaHYTH5NfU/vXZkP7E7CHjtVtITuFVZKa3AZP3gN38RDJHYYtEqWmqzCutlXaR7w==", + "requires": { + "numeric": "^1.2.6" + } + }, "@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -13123,9 +13145,9 @@ } }, "@next/env": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", - "integrity": "sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==" + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz", + "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==" }, "@next/eslint-plugin-next": { "version": "14.1.0", @@ -13176,57 +13198,57 @@ } }, "@next/swc-darwin-arm64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz", - "integrity": "sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz", + "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==", "optional": true }, "@next/swc-darwin-x64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", - "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz", + "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==", "optional": true }, "@next/swc-linux-arm64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", - "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz", + "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==", "optional": true }, "@next/swc-linux-arm64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", - "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz", + "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==", "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", - "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz", + "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==", "optional": true }, "@next/swc-linux-x64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", - "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz", + "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==", "optional": true }, "@next/swc-win32-arm64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", - "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz", + "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==", "optional": true }, "@next/swc-win32-ia32-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", - "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz", + "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==", "optional": true }, "@next/swc-win32-x64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", - "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz", + "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==", "optional": true }, "@nodelib/fs.scandir": { @@ -17939,20 +17961,20 @@ "dev": true }, "next": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.0.tgz", - "integrity": "sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==", - "requires": { - "@next/env": "14.1.0", - "@next/swc-darwin-arm64": "14.1.0", - "@next/swc-darwin-x64": "14.1.0", - "@next/swc-linux-arm64-gnu": "14.1.0", - "@next/swc-linux-arm64-musl": "14.1.0", - "@next/swc-linux-x64-gnu": "14.1.0", - "@next/swc-linux-x64-musl": "14.1.0", - "@next/swc-win32-arm64-msvc": "14.1.0", - "@next/swc-win32-ia32-msvc": "14.1.0", - "@next/swc-win32-x64-msvc": "14.1.0", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz", + "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==", + "requires": { + "@next/env": "14.1.1", + "@next/swc-darwin-arm64": "14.1.1", + "@next/swc-darwin-x64": "14.1.1", + "@next/swc-linux-arm64-gnu": "14.1.1", + "@next/swc-linux-arm64-musl": "14.1.1", + "@next/swc-linux-x64-gnu": "14.1.1", + "@next/swc-linux-x64-musl": "14.1.1", + "@next/swc-win32-arm64-msvc": "14.1.1", + "@next/swc-win32-ia32-msvc": "14.1.1", + "@next/swc-win32-x64-msvc": "14.1.1", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -18034,6 +18056,11 @@ "set-blocking": "^2.0.0" } }, + "numeric": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/numeric/-/numeric-1.2.6.tgz", + "integrity": "sha512-avBiDAP8siMa7AfJgYyuxw1oyII4z2sswS23+O+ZfV28KrtNzy0wxUFwi4f3RyM4eeeXNs1CThxR7pb5QQcMiw==" + }, "nuqs": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.14.1.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 1e84bcb8abb2d..a3716f7802ccf 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -13,6 +13,7 @@ "clean": "rm -rf .next/ && rm -rf out/" }, "dependencies": { + "16": "^0.0.2", "@chakra-ui/react": "^2.3.1", "@emotion/react": "^11.10.4", "@emotion/styled": "^11.10.4", @@ -33,7 +34,7 @@ "fabric": "^5.2.1", "framer-motion": "^6.5.1", "lodash": "^4.17.21", - "next": "^14.1.0", + "next": "^14.1.1", "nuqs": "^1.14.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/dashboard/pages/subscriptions.tsx b/dashboard/pages/subscriptions.tsx new file mode 100644 index 0000000000000..b2daa38c3f95c --- /dev/null +++ b/dashboard/pages/subscriptions.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2024 RisingWave Labs + * + * Licensed 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. + * + */ + +import { Column, Relations } from "../components/Relations" +import { getSubscriptions } from "../lib/api/streaming" +import { Subscription as RwSubscription } from "../proto/gen/catalog" + +export default function Subscriptions() { + const subscriptionRetentionSeconds: Column = { + name: "Retention Seconds", + width: 3, + content: (r) => r.retentionSeconds ?? "unknown", + } + + const subscriptionDependentTableId: Column = { + name: "Dependent Table Id", + width: 3, + content: (r) => r.dependentTableId ?? "unknown", + } + return Relations("Subscriptions", getSubscriptions, [ + subscriptionRetentionSeconds, + subscriptionDependentTableId, + ]) +} diff --git a/docker/Dockerfile b/docker/Dockerfile index 3c09a02bdd4c3..de5276eac9cf8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,15 +1,9 @@ -FROM ubuntu:22.04 AS base +FROM ubuntu:24.04 AS base ENV LANG en_US.utf8 RUN apt-get update \ - && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-11-jdk software-properties-common - -# Install Python 3.12 -RUN add-apt-repository ppa:deadsnakes/ppa -y && \ - apt-get update -yy && \ - DEBIAN_FRONTEND=noninteractive apt-get install python3.12 python3.12-dev -yy -ENV PYO3_PYTHON=python3.12 + && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-11-jdk software-properties-common python3.12 python3.12-dev FROM base AS rust-base diff --git a/docker/Dockerfile.hdfs b/docker/Dockerfile.hdfs index 53a6da30fe6e0..5f6a9c4af1ff4 100644 --- a/docker/Dockerfile.hdfs +++ b/docker/Dockerfile.hdfs @@ -1,16 +1,9 @@ -FROM ubuntu:22.04 AS base +FROM ubuntu:24.04 AS base ENV LANG en_US.utf8 RUN apt-get update \ - && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-11-jdk software-properties-common - -# Install Python 3.12 -RUN add-apt-repository ppa:deadsnakes/ppa -y && \ - apt-get update -yy && \ - DEBIAN_FRONTEND=noninteractive apt-get install python3.12 python3.12-dev -yy -ENV PYO3_PYTHON=python3.12 - + && apt-get -y install ca-certificates build-essential libsasl2-dev openjdk-11-jdk software-properties-common python3.12 python3.12-dev FROM base AS dashboard-builder @@ -117,7 +110,7 @@ RUN cd /risingwave/java && mvn -B package -Dmaven.test.skip=true -Dno-build-rust mkdir -p /risingwave/bin/connector-node && \ tar -zxvf /risingwave/java/connector-node/assembly/target/risingwave-connector-1.0.0.tar.gz -C /risingwave/bin/connector-node -FROM ubuntu:22.04 as image-base +FROM ubuntu:24.04 as image-base RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates openjdk-11-jdk wget libsasl2-dev && rm -rf /var/lib/{apt,dpkg,cache,log}/ FROM image-base as risingwave diff --git a/docker/aws/Dockerfile b/docker/aws/Dockerfile index b1ada55db4816..1de5f1c7cce08 100644 --- a/docker/aws/Dockerfile +++ b/docker/aws/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM ubuntu:24.04 RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates && rm -rf /var/lib/{apt,dpkg,cache,log}/ RUN mkdir -p /risingwave/bin diff --git a/docker/docker-compose-distributed-etcd.yml b/docker/docker-compose-distributed-etcd.yml new file mode 100644 index 0000000000000..d0297a132b8fc --- /dev/null +++ b/docker/docker-compose-distributed-etcd.yml @@ -0,0 +1,379 @@ +--- +version: "3" +x-image: &image + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} +services: + compactor-0: + <<: *image + command: + - compactor-node + - "--listen-addr" + - "0.0.0.0:6660" + - "--advertise-addr" + - "compactor-0:6660" + - "--prometheus-listener-addr" + - "0.0.0.0:1260" + - "--meta-address" + - "http://meta-node-0:5690" + - "--config-path" + - /risingwave.toml + expose: + - "6660" + - "1260" + ports: [] + depends_on: + - meta-node-0 + # - minio-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: compactor-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/6660; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + compute-node-0: + <<: *image + command: + - compute-node + - "--listen-addr" + - "0.0.0.0:5688" + - "--advertise-addr" + - "compute-node-0:5688" + - "--prometheus-listener-addr" + - "0.0.0.0:1222" + - "--meta-address" + - "http://meta-node-0:5690" + - "--config-path" + - /risingwave.toml + expose: + - "5688" + - "1222" + ports: [] + depends_on: + - meta-node-0 + # - minio-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: compute-node-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5688; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 26G + reservations: + memory: 26G + etcd-0: + image: "quay.io/coreos/etcd:v3.5.10" + command: + - /usr/local/bin/etcd + - "--listen-client-urls" + - "http://0.0.0.0:2388" + - "--advertise-client-urls" + - "http://etcd-0:2388" + - "--listen-peer-urls" + - "http://0.0.0.0:2389" + - "--initial-advertise-peer-urls" + - "http://etcd-0:2389" + - "--listen-metrics-urls" + - "http://0.0.0.0:2379" + - "--name" + - risedev-meta + - "--max-txn-ops" + - "999999" + - "--max-request-bytes" + - "10485760" + - "--auto-compaction-mode" + - periodic + - "--auto-compaction-retention" + - 1m + - "--snapshot-count" + - "10000" + - "--data-dir" + - /etcd-data + expose: + - "2388" + ports: + - "2388:2388" + - "2389:2389" + depends_on: [] + volumes: + - "etcd-0:/etcd-data" + environment: {} + container_name: etcd-0 + healthcheck: + test: + - CMD + - etcdctl + - --endpoints=http://localhost:2388 + - endpoint + - health + interval: 1s + timeout: 5s + retries: 5 + restart: always + frontend-node-0: + <<: *image + command: + - frontend-node + - "--listen-addr" + - "0.0.0.0:4566" + - "--meta-addr" + - "http://meta-node-0:5690" + - "--advertise-addr" + - "frontend-node-0:4566" + - "--config-path" + - /risingwave.toml + - "--prometheus-listener-addr" + - "0.0.0.0:2222" + expose: + - "4566" + ports: + - "4566:4566" + depends_on: + - meta-node-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: frontend-node-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/4566; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + grafana-0: + image: "grafana/grafana-oss:latest" + command: [] + expose: + - "3001" + ports: + - "3001:3001" + depends_on: [] + volumes: + - "grafana-0:/var/lib/grafana" + - "./grafana.ini:/etc/grafana/grafana.ini" + - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" + - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" + - "./dashboards:/dashboards" + environment: {} + container_name: grafana-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/3001; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + meta-node-0: + <<: *image + command: + - meta-node + - "--listen-addr" + - "0.0.0.0:5690" + - "--advertise-addr" + - "meta-node-0:5690" + - "--dashboard-host" + - "0.0.0.0:5691" + - "--prometheus-host" + - "0.0.0.0:1250" + - "--prometheus-endpoint" + - "http://prometheus-0:9500" + - "--backend" + - etcd + - "--etcd-endpoints" + - "etcd-0:2388" + - "--state-store" + - "hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001" + - "--data-directory" + - "hummock_001" + - "--config-path" + - /risingwave.toml + expose: + - "5690" + - "1250" + - "5691" + ports: + - "5690:5690" + - "5691:5691" + depends_on: + - "etcd-0" + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: meta-node-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5690; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + deploy: + resources: + limits: + memory: 2G + reservations: + memory: 1G + minio-0: + image: "quay.io/minio/minio:latest" + command: + - server + - "--address" + - "0.0.0.0:9301" + - "--console-address" + - "0.0.0.0:9400" + - /data + expose: + - "9301" + - "9400" + ports: + - "9301:9301" + - "9400:9400" + depends_on: [] + volumes: + - "minio-0:/data" + entrypoint: " + + /bin/sh -c ' + + set -e + + mkdir -p \"/data/hummock001\" + + /usr/bin/docker-entrypoint.sh \"$$0\" \"$$@\" + + '" + environment: + MINIO_CI_CD: "1" + MINIO_PROMETHEUS_AUTH_TYPE: public + MINIO_PROMETHEUS_URL: "http://prometheus-0:9500" + MINIO_ROOT_PASSWORD: hummockadmin + MINIO_ROOT_USER: hummockadmin + MINIO_DOMAIN: "minio-0" + container_name: minio-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/9301; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + prometheus-0: + image: "prom/prometheus:latest" + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.console.libraries=/usr/share/prometheus/console_libraries" + - "--web.console.templates=/usr/share/prometheus/consoles" + - "--web.listen-address=0.0.0.0:9500" + - "--storage.tsdb.retention.time=30d" + expose: + - "9500" + ports: + - "9500:9500" + depends_on: [] + volumes: + - "prometheus-0:/prometheus" + - "./prometheus.yaml:/etc/prometheus/prometheus.yml" + environment: {} + container_name: prometheus-0 + healthcheck: + test: + - CMD-SHELL + - sh -c 'printf "GET /-/healthy HTTP/1.0\n\n" | nc localhost 9500; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + message_queue: + image: "docker.vectorized.io/vectorized/redpanda:latest" + command: + - redpanda + - start + - "--smp" + - "1" + - "--reserve-memory" + - 0M + - "--memory" + - 4G + - "--overprovisioned" + - "--node-id" + - "0" + - "--check=false" + - "--kafka-addr" + - "PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092" + - "--advertise-kafka-addr" + - "PLAINTEXT://message_queue:29092,OUTSIDE://localhost:9092" + expose: + - "29092" + - "9092" + - "9644" + ports: + - "29092:29092" + - "9092:9092" + - "9644:9644" + - "8081:8081" + depends_on: [] + volumes: + - "message_queue:/var/lib/redpanda/data" + environment: {} + container_name: message_queue + healthcheck: + test: curl -f localhost:9644/v1/status/ready + interval: 1s + timeout: 5s + retries: 5 + restart: always +volumes: + etcd-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false diff --git a/docker/docker-compose-distributed.yml b/docker/docker-compose-distributed.yml index 55cb1cbcffe3c..6a71b3488c41c 100644 --- a/docker/docker-compose-distributed.yml +++ b/docker/docker-compose-distributed.yml @@ -87,52 +87,22 @@ services: memory: 26G reservations: memory: 26G - etcd-0: - image: "quay.io/coreos/etcd:v3.5.10" - command: - - /usr/local/bin/etcd - - "--listen-client-urls" - - "http://0.0.0.0:2388" - - "--advertise-client-urls" - - "http://etcd-0:2388" - - "--listen-peer-urls" - - "http://0.0.0.0:2389" - - "--initial-advertise-peer-urls" - - "http://etcd-0:2389" - - "--listen-metrics-urls" - - "http://0.0.0.0:2379" - - "--name" - - risedev-meta - - "--max-txn-ops" - - "999999" - - "--max-request-bytes" - - "10485760" - - "--auto-compaction-mode" - - periodic - - "--auto-compaction-retention" - - 1m - - "--snapshot-count" - - "10000" - - "--data-dir" - - /etcd-data + postgres-0: + image: "postgres:15-alpine" + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_USER=postgres + - POSTGRES_DB=metadata + - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C expose: - - "2388" + - "5432" ports: - - "2388:2388" - - "2389:2389" - depends_on: [] + - "8432:5432" volumes: - - "etcd-0:/etcd-data" - environment: {} - container_name: etcd-0 + - "postgres-0:/var/lib/postgresql/data" healthcheck: - test: - - CMD - - etcdctl - - --endpoints=http://localhost:2388 - - endpoint - - health - interval: 1s + test: [ "CMD-SHELL", "pg_isready -U postgres" ] + interval: 2s timeout: 5s retries: 5 restart: always @@ -216,9 +186,9 @@ services: - "--prometheus-endpoint" - "http://prometheus-0:9500" - "--backend" - - etcd - - "--etcd-endpoints" - - "etcd-0:2388" + - sql + - "--sql-endpoint" + - "postgres://postgres:@postgres-0:5432/metadata" - "--state-store" - "hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001" - "--data-directory" @@ -233,7 +203,8 @@ services: - "5690:5690" - "5691:5691" depends_on: - - "etcd-0" + - "postgres-0" + - "minio-0" volumes: - "./risingwave.toml:/risingwave.toml" environment: @@ -367,7 +338,7 @@ services: retries: 5 restart: always volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-etcd.yml b/docker/docker-compose-etcd.yml new file mode 100644 index 0000000000000..05d8d16ffdf98 --- /dev/null +++ b/docker/docker-compose-etcd.yml @@ -0,0 +1,278 @@ +--- +version: "3" +x-image: &image + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} +services: + risingwave-standalone: + <<: *image + command: "standalone --meta-opts=\" \ + --listen-addr 0.0.0.0:5690 \ + --advertise-addr 0.0.0.0:5690 \ + --dashboard-host 0.0.0.0:5691 \ + --prometheus-host 0.0.0.0:1250 \ + --prometheus-endpoint http://prometheus-0:9500 \ + --backend etcd \ + --etcd-endpoints etcd-0:2388 \ + --state-store hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001 \ + --data-directory hummock_001 \ + --config-path /risingwave.toml\" \ + --compute-opts=\" \ + --config-path /risingwave.toml \ + --listen-addr 0.0.0.0:5688 \ + --prometheus-listener-addr 0.0.0.0:1250 \ + --advertise-addr 0.0.0.0:5688 \ + --async-stack-trace verbose \ + #--parallelism 4 \ + #--total-memory-bytes 8589934592 \ + --role both \ + --meta-address http://0.0.0.0:5690\" \ + --frontend-opts=\" \ + --config-path /risingwave.toml \ + --listen-addr 0.0.0.0:4566 \ + --advertise-addr 0.0.0.0:4566 \ + --prometheus-listener-addr 0.0.0.0:1250 \ + --health-check-listener-addr 0.0.0.0:6786 \ + --meta-addr http://0.0.0.0:5690\" \ + --compactor-opts=\" \ + --listen-addr 0.0.0.0:6660 \ + --prometheus-listener-addr 0.0.0.0:1250 \ + --advertise-addr 0.0.0.0:6660 \ + --meta-address http://0.0.0.0:5690\"" + expose: + - "6660" + - "4566" + - "5688" + - "5690" + - "1250" + - "5691" + ports: + - "4566:4566" + - "5690:5690" + - "5691:5691" + - "1250:1250" + depends_on: + - etcd-0 + - minio-0 + volumes: + - "./risingwave.toml:/risingwave.toml" + environment: + RUST_BACKTRACE: "1" + # If ENABLE_TELEMETRY is not set, telemetry will start by default + ENABLE_TELEMETRY: ${ENABLE_TELEMETRY:-true} + container_name: risingwave-standalone + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/6660; exit $$?;' + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5688; exit $$?;' + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/4566; exit $$?;' + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/5690; exit $$?;' + interval: 1s + timeout: 5s + restart: always + deploy: + resources: + limits: + memory: 28G + reservations: + memory: 28G + + etcd-0: + image: "quay.io/coreos/etcd:v3.5.10" + command: + - /usr/local/bin/etcd + - "--listen-client-urls" + - "http://0.0.0.0:2388" + - "--advertise-client-urls" + - "http://etcd-0:2388" + - "--listen-peer-urls" + - "http://0.0.0.0:2389" + - "--initial-advertise-peer-urls" + - "http://etcd-0:2389" + - "--listen-metrics-urls" + - "http://0.0.0.0:2379" + - "--name" + - risedev-meta + - "--max-txn-ops" + - "999999" + - "--max-request-bytes" + - "10485760" + - "--auto-compaction-mode" + - periodic + - "--auto-compaction-retention" + - 1m + - "--snapshot-count" + - "10000" + - "--data-dir" + - /etcd-data + expose: + - "2388" + ports: + - "2388:2388" + - "2389:2389" + depends_on: [] + volumes: + - "etcd-0:/etcd-data" + environment: {} + container_name: etcd-0 + healthcheck: + test: + - CMD + - etcdctl + - --endpoints=http://localhost:2388 + - endpoint + - health + interval: 1s + timeout: 5s + retries: 5 + restart: always + + grafana-0: + image: "grafana/grafana-oss:latest" + command: [] + expose: + - "3001" + ports: + - "3001:3001" + depends_on: [] + volumes: + - "grafana-0:/var/lib/grafana" + - "./grafana.ini:/etc/grafana/grafana.ini" + - "./grafana-risedev-datasource.yml:/etc/grafana/provisioning/datasources/grafana-risedev-datasource.yml" + - "./grafana-risedev-dashboard.yml:/etc/grafana/provisioning/dashboards/grafana-risedev-dashboard.yml" + - "./dashboards:/dashboards" + environment: {} + container_name: grafana-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/3001; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + + minio-0: + image: "quay.io/minio/minio:latest" + command: + - server + - "--address" + - "0.0.0.0:9301" + - "--console-address" + - "0.0.0.0:9400" + - /data + expose: + - "9301" + - "9400" + ports: + - "9301:9301" + - "9400:9400" + depends_on: [] + volumes: + - "minio-0:/data" + entrypoint: " + + /bin/sh -c ' + + set -e + + mkdir -p \"/data/hummock001\" + + /usr/bin/docker-entrypoint.sh \"$$0\" \"$$@\" + + '" + environment: + MINIO_CI_CD: "1" + MINIO_PROMETHEUS_AUTH_TYPE: public + MINIO_PROMETHEUS_URL: "http://prometheus-0:9500" + MINIO_ROOT_PASSWORD: hummockadmin + MINIO_ROOT_USER: hummockadmin + MINIO_DOMAIN: "minio-0" + container_name: minio-0 + healthcheck: + test: + - CMD-SHELL + - bash -c 'printf \"GET / HTTP/1.1\n\n\" > /dev/tcp/127.0.0.1/9301; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + + prometheus-0: + image: "prom/prometheus:latest" + command: + - "--config.file=/etc/prometheus/prometheus.yml" + - "--storage.tsdb.path=/prometheus" + - "--web.console.libraries=/usr/share/prometheus/console_libraries" + - "--web.console.templates=/usr/share/prometheus/consoles" + - "--web.listen-address=0.0.0.0:9500" + - "--storage.tsdb.retention.time=30d" + expose: + - "9500" + ports: + - "9500:9500" + depends_on: [] + volumes: + - "prometheus-0:/prometheus" + - "./prometheus.yaml:/etc/prometheus/prometheus.yml" + environment: {} + container_name: prometheus-0 + healthcheck: + test: + - CMD-SHELL + - sh -c 'printf "GET /-/healthy HTTP/1.0\n\n" | nc localhost 9500; exit $$?;' + interval: 1s + timeout: 5s + retries: 5 + restart: always + + message_queue: + image: "docker.vectorized.io/vectorized/redpanda:latest" + command: + - redpanda + - start + - "--smp" + - "1" + - "--reserve-memory" + - 0M + - "--memory" + - 4G + - "--overprovisioned" + - "--node-id" + - "0" + - "--check=false" + - "--kafka-addr" + - "PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092" + - "--advertise-kafka-addr" + - "PLAINTEXT://message_queue:29092,OUTSIDE://localhost:9092" + expose: + - "29092" + - "9092" + - "9644" + ports: + - "29092:29092" + - "9092:9092" + - "9644:9644" + - "8081:8081" + depends_on: [] + volumes: + - "message_queue:/var/lib/redpanda/data" + environment: {} + container_name: message_queue + healthcheck: + test: curl -f localhost:9644/v1/status/ready + interval: 1s + timeout: 5s + retries: 5 + restart: always +volumes: + etcd-0: + external: false + grafana-0: + external: false + minio-0: + external: false + prometheus-0: + external: false + message_queue: + external: false diff --git a/docker/docker-compose-with-azblob.yml b/docker/docker-compose-with-azblob.yml index e0b44c5768011..a1035180d6a68 100644 --- a/docker/docker-compose-with-azblob.yml +++ b/docker/docker-compose-with-azblob.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+azblob:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +51,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-gcs.yml b/docker/docker-compose-with-gcs.yml index 847172c2d09c1..64807f252e536 100644 --- a/docker/docker-compose-with-gcs.yml +++ b/docker/docker-compose-with-gcs.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+gcs:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +51,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-local-fs.yml b/docker/docker-compose-with-local-fs.yml index b45e624c619b3..c44995e063dea 100644 --- a/docker/docker-compose-with-local-fs.yml +++ b/docker/docker-compose-with-local-fs.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:nightly-20231211} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+fs:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -50,7 +50,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 volumes: - "./risingwave.toml:/risingwave.toml" environment: @@ -74,10 +74,10 @@ services: memory: reservations: memory: - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -87,7 +87,7 @@ services: file: docker-compose.yml service: prometheus-0 volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-obs.yml b/docker/docker-compose-with-obs.yml index 5d0df0ca4f72d..634de89172b41 100644 --- a/docker/docker-compose-with-obs.yml +++ b/docker/docker-compose-with-obs.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+obs:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +51,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-oss.yml b/docker/docker-compose-with-oss.yml index 7296a7074d5a6..53466e51711ed 100644 --- a/docker/docker-compose-with-oss.yml +++ b/docker/docker-compose-with-oss.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+oss:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +51,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: multiple_object_storage.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose-with-s3.yml b/docker/docker-compose-with-s3.yml index 815489f82493e..34e1b02ddd38f 100644 --- a/docker/docker-compose-with-s3.yml +++ b/docker/docker-compose-with-s3.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+s3:// \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +51,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 env_file: aws.env volumes: - "./risingwave.toml:/risingwave.toml" @@ -76,10 +76,10 @@ services: memory: 28G reservations: memory: 28G - etcd-0: + postgres-0: extends: file: docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: docker-compose.yml @@ -93,7 +93,7 @@ services: file: docker-compose.yml service: message_queue volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6259a5757b14f..a94324439a42c 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,7 +1,7 @@ --- version: "3" x-image: &image - image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.1} + image: ${RW_IMAGE:-risingwavelabs/risingwave:v1.8.2} services: risingwave-standalone: <<: *image @@ -11,8 +11,8 @@ services: --dashboard-host 0.0.0.0:5691 \ --prometheus-host 0.0.0.0:1250 \ --prometheus-endpoint http://prometheus-0:9500 \ - --backend etcd \ - --etcd-endpoints etcd-0:2388 \ + --backend sql \ + --sql-endpoint postgres://postgres:@postgres-0:5432/metadata \ --state-store hummock+minio://hummockadmin:hummockadmin@minio-0:9301/hummock001 \ --data-directory hummock_001 \ --config-path /risingwave.toml\" \ @@ -51,7 +51,7 @@ services: - "5691:5691" - "1250:1250" depends_on: - - etcd-0 + - postgres-0 - minio-0 volumes: - "./risingwave.toml:/risingwave.toml" @@ -77,52 +77,22 @@ services: reservations: memory: 28G - etcd-0: - image: "quay.io/coreos/etcd:v3.5.10" - command: - - /usr/local/bin/etcd - - "--listen-client-urls" - - "http://0.0.0.0:2388" - - "--advertise-client-urls" - - "http://etcd-0:2388" - - "--listen-peer-urls" - - "http://0.0.0.0:2389" - - "--initial-advertise-peer-urls" - - "http://etcd-0:2389" - - "--listen-metrics-urls" - - "http://0.0.0.0:2379" - - "--name" - - risedev-meta - - "--max-txn-ops" - - "999999" - - "--max-request-bytes" - - "10485760" - - "--auto-compaction-mode" - - periodic - - "--auto-compaction-retention" - - 1m - - "--snapshot-count" - - "10000" - - "--data-dir" - - /etcd-data + postgres-0: + image: "postgres:15-alpine" + environment: + - POSTGRES_HOST_AUTH_METHOD=trust + - POSTGRES_USER=postgres + - POSTGRES_DB=metadata + - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=C --lc-ctype=C expose: - - "2388" + - "5432" ports: - - "2388:2388" - - "2389:2389" - depends_on: [] + - "8432:5432" volumes: - - "etcd-0:/etcd-data" - environment: {} - container_name: etcd-0 + - "postgres-0:/var/lib/postgresql/data" healthcheck: - test: - - CMD - - etcdctl - - --endpoints=http://localhost:2388 - - endpoint - - health - interval: 1s + test: [ "CMD-SHELL", "pg_isready -U postgres" ] + interval: 2s timeout: 5s retries: 5 restart: always @@ -266,7 +236,7 @@ services: retries: 5 restart: always volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/e2e_test/backfill/rate_limit/slow-udf.slt b/e2e_test/backfill/rate_limit/slow-udf.slt new file mode 100644 index 0000000000000..a2b1a6fc63130 --- /dev/null +++ b/e2e_test/backfill/rate_limit/slow-udf.slt @@ -0,0 +1,49 @@ +statement ok +create table t(v1 int); + +statement ok +insert into t select 2 from generate_series(1, 1000000); + +statement ok +set streaming_rate_limit=1; + +statement ok +set background_ddl=true; + +statement ok +CREATE FUNCTION delay(secs int) RETURNS int LANGUAGE python AS $$ +def delay(n): + import time + time.sleep(n) + return n +$$; + +statement ok +create sink m1 as select delay(v1) from t with (connector='blackhole'); + +statement ok +set background_ddl = false; + +statement ok +set streaming_rate_limit=default; + +statement ok +flush; + +statement ok +flush; + +statement ok +flush; + +statement ok +flush; + +statement ok +drop sink m1; + +statement ok +drop function delay; + +statement ok +drop table t; \ No newline at end of file diff --git a/e2e_test/batch/catalog/has_privilege.slt.part b/e2e_test/batch/catalog/has_privilege.slt.part new file mode 100644 index 0000000000000..a742db0c51d6f --- /dev/null +++ b/e2e_test/batch/catalog/has_privilege.slt.part @@ -0,0 +1,264 @@ +statement ok +CREATE USER test_user; + +statement ok +CREATE SCHEMA test_schema; + +statement ok +CREATE TABLE foo (id INT, name VARCHAR); + +statement ok +CREATE VIEW foo_view AS SELECT * FROM foo; + +statement ok +CREATE INDEX foo_index ON foo(id); + +statement ok +CREATE MATERIALIZED VIEW foo_mv AS SELECT * FROM foo; + +statement ok +CREATE SOURCE foo_source (a int, b int) with ( + connector = 'datagen', + datagen.rows.per.second = '1', + datagen.split.num = '1' +); + +statement ok +CREATE TABLE bar (id INT); + +statement ok +GRANT ALL PRIVILEGES ON foo TO test_user GRANTED BY root; + +statement ok +GRANT INSERT ON bar TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT INSERT ON foo_view TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT SELECT ON ALL TABLES IN SCHEMA public TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT SELECT ON ALL MATERIALIZED VIEWS IN SCHEMA public TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT SELECT ON ALL SOURCES IN SCHEMA public TO test_user WITH GRANT OPTION GRANTED BY root; + +statement ok +GRANT CREATE ON SCHEMA test_schema TO test_user; + +query error table not found: bar_err +GRANT INSERT ON bar_err TO test_user WITH GRANT OPTION GRANTED BY root; + +query error Invalid parameter user: User test_user_err not found +SELECT has_table_privilege('test_user_err', 'foo', 'SELECT'); + +query error Invalid parameter name: class not found: foo_err +SELECT has_table_privilege('test_user', 'foo_err', 'SELECT'); + +query error Invalid parameter privilege: unrecognized privilege type: "SELE CT" +SELECT has_table_privilege('test_user', 'foo', 'SELE CT'); + +query error Invalid parameter privilege: unrecognized privilege type: "SELECT INSERT" +SELECT has_table_privilege('test_user', 'foo', 'SELECT INSERT'); + +query error Invalid parameter privilege +SELECT has_table_privilege('test_user', 'foo', 'SELECT, INSERT WITH GRANT OPTION'); + +query error Invalid parameter user: User test_user_err not found +SELECT has_schema_privilege('test_user_err', 'test_schema', 'CREATE'); + +query error Invalid parameter schema: schema not found: test_schema_err +SELECT has_schema_privilege('test_user', 'test_schema_err', 'CREATE'); + +query error Invalid parameter privilege: unrecognized privilege type: "INSERT" +SELECT has_schema_privilege('test_user', 'test_schema', 'INSERT'); + +query error Invalid parameter privilege: unrecognized privilege type: "DELETE" +SELECT has_any_column_privilege('test_user', 'foo_mv'::regclass, 'DELETE'); + +query I +SELECT has_table_privilege('test_user', 'foo', 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'INSERT WITH GRANT OPTION'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo', 'INSERT, SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'DELETE, INSERT, SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo', 'DELETE WITH GRANT OPTION, INSERT, SELECT WITH GRANT OPTION'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_view'::regclass, 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_view'::regclass, 'UPDATE'); +---- +f + +query I +SELECT has_any_column_privilege('test_user', 'foo_view'::regclass, 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_mv', 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_mv'::regclass, 'SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_any_column_privilege('test_user', 'foo_mv'::regclass, 'SELECT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_mv', 'INSERT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_source'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_source', 'INSERT'); +---- +f + +# Indexes are granted by `GRANT SELECT ON ALL MATERIALIZED VIEWS` +query I +SELECT has_table_privilege('test_user', 'foo_index'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'foo_index', 'INSERT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'bar', 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('bar', 'INSERT'); +---- +t + +query I +SELECT has_table_privilege('bar'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('bar'::regclass, 'SELECT'); +---- +t + +query I +SELECT has_table_privilege('test_user', 'bar', 'UPDATE'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'bar'::regclass, 'INSERT WITH GRANT OPTION'); +---- +t + +query I +SELECT has_schema_privilege('public', 'USAGE'); +---- +t + +query I +SELECT has_schema_privilege('test_user', 'test_schema', 'USAGE'); +---- +f + +query I +SELECT has_schema_privilege('test_user', 'test_schema', 'CREATE'); +---- +t + +statement ok +REVOKE SELECT ON ALL TABLES IN SCHEMA public FROM test_user GRANTED BY root; + +query I +SELECT has_table_privilege('test_user', 'bar'::regclass, 'SELECT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'SELECT'); +---- +f + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'INSERT'); +---- +t + +statement ok +REVOKE INSERT ON foo_view FROM test_user GRANTED BY root; + +query I +SELECT has_table_privilege('test_user', 'foo_view', 'INSERT'); +---- +f + +statement ok +DROP SOURCE foo_source; + +statement ok +DROP MATERIALIZED VIEW foo_mv; + +statement ok +DROP INDEX foo_index; + +statement ok +DROP VIEW foo_view; + +statement ok +DROP TABLE foo; + +statement ok +DROP TABLE bar; + +statement ok +DROP SCHEMA test_schema; + +statement ok +DROP USER test_user; diff --git a/e2e_test/error_ui/simple/main.slt b/e2e_test/error_ui/simple/main.slt index 8ef82e1f0d1c7..6bcbbde608cf8 100644 --- a/e2e_test/error_ui/simple/main.slt +++ b/e2e_test/error_ui/simple/main.slt @@ -13,8 +13,10 @@ create function int_42() returns int as int_42 using link '555.0.0.1:8815'; ---- db error: ERROR: Failed to run the query -Caused by: - Flight service error: invalid address: 555.0.0.1:8815, err: failed to parse address: http://555.0.0.1:8815: invalid IPv4 address +Caused by these errors (recent errors listed first): + 1: Expr error + 2: UDF error + 3: Flight service error: invalid address: 555.0.0.1:8815, err: failed to parse address: http://555.0.0.1:8815: invalid IPv4 address statement error diff --git a/e2e_test/sink/remote/jdbc.check.pg.slt b/e2e_test/sink/remote/jdbc.check.pg.slt index d532610c391e5..1ec8c827d939b 100644 --- a/e2e_test/sink/remote/jdbc.check.pg.slt +++ b/e2e_test/sink/remote/jdbc.check.pg.slt @@ -46,7 +46,7 @@ select * from t1_uuid; 221 74605c5a-a7bb-4b3b-8742-2a12e9709dea hello world -query T +query TIT select * from sk_t1_uuid ---- -21189447-8736-44bd-b254-26b5dec91da9 +21189447-8736-44bd-b254-26b5dec91da9 2 bb diff --git a/e2e_test/sink/remote/jdbc.load.slt b/e2e_test/sink/remote/jdbc.load.slt index 97bdabaa47515..9a4ede4e032ed 100644 --- a/e2e_test/sink/remote/jdbc.load.slt +++ b/e2e_test/sink/remote/jdbc.load.slt @@ -166,22 +166,22 @@ INSERT INTO t1_uuid values (221, '74605c5a-a7bb-4b3b-8742-2a12e9709dea', 'hello statement ok -CREATE TABLE t1_test_uuid_delete (id varchar, primary key(id)); +CREATE TABLE t1_test_uuid_delete (id varchar, v1 int, v2 varchar, primary key(id, v2)); statement ok -INSERT INTO t1_test_uuid_delete VALUES ('fb48ecc1-917f-4f4b-ab6d-d8e37809caf8'), ('21189447-8736-44bd-b254-26b5dec91da9'); +INSERT INTO t1_test_uuid_delete VALUES ('fb48ecc1-917f-4f4b-ab6d-d8e37809caf8', 1, 'aa'), ('21189447-8736-44bd-b254-26b5dec91da9', 2, 'bb'); statement ok CREATE SINK sk_t1_uuid FROM t1_test_uuid_delete WITH ( connector='jdbc', jdbc.url='jdbc:postgresql://db:5432/test?user=test&password=connector', table.name='sk_t1_uuid', - primary_key='id', + primary_key='id, v2', type='upsert' ); statement ok -DELETE FROM t1_test_uuid_delete WHERE ID='fb48ecc1-917f-4f4b-ab6d-d8e37809caf8'; +DELETE FROM t1_test_uuid_delete WHERE id='fb48ecc1-917f-4f4b-ab6d-d8e37809caf8' AND v2='aa'; statement ok diff --git a/e2e_test/sink/remote/pg_create_table.sql b/e2e_test/sink/remote/pg_create_table.sql index dab753ee05d6d..ee272ef747a7a 100644 --- a/e2e_test/sink/remote/pg_create_table.sql +++ b/e2e_test/sink/remote/pg_create_table.sql @@ -84,4 +84,4 @@ CREATE TABLE biz.t2 ( "aBc" INTEGER PRIMARY KEY ); -CREATE TABLE sk_t1_uuid (id uuid, primary key(id)); +CREATE TABLE sk_t1_uuid (id uuid, v1 int, v2 varchar, primary key(id, v2)); diff --git a/e2e_test/source/cdc/cdc.check_new_rows.slt b/e2e_test/source/cdc/cdc.check_new_rows.slt index a9daaa5f458fb..77c8b6b5448ca 100644 --- a/e2e_test/source/cdc/cdc.check_new_rows.slt +++ b/e2e_test/source/cdc/cdc.check_new_rows.slt @@ -125,30 +125,58 @@ query II select id, my_int from list_with_null_shared order by id; ---- 1 {1,2,NULL} -2 {NULL,-1,-2} +2 {NULL,3,4} +3 {NULL,-3,-4} +4 {-4,-5,-6} -# will fix in https://github.com/risingwavelabs/risingwave/pull/16416 +# my_num: varchar[] query II select id, my_num from list_with_null_shared order by id; ---- -1 {1.1,POSITIVE_INFINITY,NULL} +1 NULL +2 {2.2,0,NULL} +3 NULL +4 {NULL,-99999999999999999.9999} + +# my_num1: numeric[] +query II +select id, my_num_1 from list_with_null_shared order by id; +---- +1 NULL +2 {2.2,0,NULL} +3 NULL +4 {NULL,-99999999999999999.9999} + +# my_num2: rw_int256[] +query II +select id, my_num_2 from list_with_null_shared order by id; +---- +1 NULL 2 NULL +3 NULL +4 NULL # Due to the bug in Debezium, if a enum list contains `NULL`, the list will be converted to `NULL` query II select id, my_mood from list_with_null_shared order by id; ---- 1 NULL -2 NULL +2 {happy,ok,sad} +3 NULL +4 NULL query II select id, my_uuid from list_with_null_shared order by id; ---- 1 {bb488f9b-330d-4012-b849-12adeb49e57e,bb488f9b-330d-4012-b849-12adeb49e57f,NULL} -2 {NULL,471acecf-a4b4-4ed3-a211-7fb2291f159f,9bc35adf-fb11-4130-944c-e7eadb96b829} +2 {2de296df-eda7-4202-a81f-1036100ef4f6,2977afbc-0b12-459c-a36f-f623fc9e9840} +3 {NULL,471acecf-a4b4-4ed3-a211-7fb2291f159f,9bc35adf-fb11-4130-944c-e7eadb96b829} +4 {b2e4636d-fa03-4ad4-bf16-029a79dca3e2} query II select id, my_bytea from list_with_null_shared order by id; ---- 1 {"\\x00","\\x01",NULL} -2 {NULL,"\\x99","\\xaa"} +2 {"\\x00","\\x01","\\x02"} +3 {NULL,"\\x99","\\xaa"} +4 {"\\x88","\\x99","\\xaa"} diff --git a/e2e_test/source/cdc/cdc.share_stream.slt b/e2e_test/source/cdc/cdc.share_stream.slt index 8eb48a8c81dc4..480c707fb6f42 100644 --- a/e2e_test/source/cdc/cdc.share_stream.slt +++ b/e2e_test/source/cdc/cdc.share_stream.slt @@ -283,6 +283,13 @@ CREATE TABLE numeric_to_varchar_shared ( PRIMARY KEY (id) ) FROM pg_source TABLE 'public.numeric_table'; +statement ok +CREATE TABLE numeric_to_numeric_shared ( + id int, + num numeric, + PRIMARY KEY (id) +) FROM pg_source TABLE 'public.numeric_table'; + statement ok CREATE TABLE numeric_list_to_rw_int256_list_shared ( id int, @@ -297,6 +304,13 @@ CREATE TABLE numeric_list_to_varchar_list_shared ( PRIMARY KEY (id) ) FROM pg_source TABLE 'public.numeric_list'; +statement ok +CREATE TABLE numeric_list_to_numeric_list_shared ( + id int, + num numeric[], + PRIMARY KEY (id) +) FROM pg_source TABLE 'public.numeric_list'; + statement ok CREATE TABLE enum_to_varchar_shared ( id int, @@ -310,6 +324,8 @@ CREATE TABLE list_with_null_shared ( id int, my_int int[], my_num varchar[], + my_num_1 numeric[], + my_num_2 rw_int256[], my_mood varchar[], my_uuid varchar[], my_bytea bytea[], @@ -327,6 +343,8 @@ insert into numeric_table values(105, 115792089237316195423570985008687907853269 insert into numeric_table values(106, 'NaN'::numeric); insert into numeric_table values(107, 'Infinity'::numeric); INSERT INTO enum_table VALUES (2, 'ok'); +insert into numeric_list values(3, '{3.14, 6, 57896044618658097711785492504343953926634992332820282019728792003956564819967, 57896044618658097711785492504343953926634992332820282019728792003956564819968, 115792089237316195423570985008687907853269984665640564039457584007913129639936.555555}'); +INSERT INTO numeric_list values(4, '{nan, infinity, 524596}'); " sleep 3s @@ -366,6 +384,23 @@ select * from numeric_to_rw_int256_shared order by id; 106 NULL 107 NULL +query II +select * from numeric_to_numeric_shared order by id; +---- +1 3.14 +2 NULL +3 NULL +4 NULL +5 NULL +6 NaN +7 Infinity +102 NULL +103 NULL +104 NULL +105 NULL +106 NaN +107 Infinity + system ok psql -c " DELETE FROM numeric_table WHERE id IN (102, 103, 104, 105, 106, 107); @@ -375,13 +410,25 @@ query II select * from numeric_list_to_varchar_list_shared order by id; ---- 1 {3.14,6,57896044618658097711785492504343953926634992332820282019728792003956564819967,57896044618658097711785492504343953926634992332820282019728792003956564819968,115792089237316195423570985008687907853269984665640564039457584007913129639936.555555} -2 {NAN,POSITIVE_INFINITY,NEGATIVE_INFINITY} +2 NULL +3 {3.14,6,57896044618658097711785492504343953926634992332820282019728792003956564819967,57896044618658097711785492504343953926634992332820282019728792003956564819968,115792089237316195423570985008687907853269984665640564039457584007913129639936.555555} +4 NULL query II select * from numeric_list_to_rw_int256_list_shared order by id; ---- -1 {NULL,6,57896044618658097711785492504343953926634992332820282019728792003956564819967,NULL,NULL} -2 {NULL,NULL,NULL} +1 NULL +2 NULL +3 NULL +4 NULL + +query II +select * from numeric_list_to_numeric_list_shared order by id; +---- +1 NULL +2 NULL +3 NULL +4 NULL query II select * from enum_to_varchar_shared order by id; diff --git a/e2e_test/source/cdc/postgres_cdc.sql b/e2e_test/source/cdc/postgres_cdc.sql index 2a34a52051096..6a60644ad6980 100644 --- a/e2e_test/source/cdc/postgres_cdc.sql +++ b/e2e_test/source/cdc/postgres_cdc.sql @@ -97,5 +97,6 @@ CREATE TABLE enum_table ( ); INSERT INTO enum_table VALUES (1, 'happy'); -CREATE TABLE list_with_null(id int primary key, my_int int[], my_num numeric[], my_mood mood[], my_uuid uuid[], my_bytea bytea[]); -INSERT INTO list_with_null VALUES (1, '{1,2,NULL}', '{1.1,inf,NULL}', '{happy,ok,NULL}', '{bb488f9b-330d-4012-b849-12adeb49e57e,bb488f9b-330d-4012-b849-12adeb49e57f, NULL}', '{\\x00,\\x01,NULL}'); +CREATE TABLE list_with_null(id int primary key, my_int int[], my_num numeric[], my_num_1 numeric[], my_num_2 numeric[], my_mood mood[], my_uuid uuid[], my_bytea bytea[]); +INSERT INTO list_with_null VALUES (1, '{1,2,NULL}', '{1.1,inf,NULL}', '{1.1,inf,NULL}', '{1.1,inf,NULL}', '{happy,ok,NULL}', '{bb488f9b-330d-4012-b849-12adeb49e57e,bb488f9b-330d-4012-b849-12adeb49e57f, NULL}', '{\\x00,\\x01,NULL}'); +INSERT INTO list_with_null VALUES (2, '{NULL,3,4}', '{2.2,0,NULL}' , '{2.2,0,NULL}', '{2.2,0,NULL}', '{happy,ok,sad}', '{2de296df-eda7-4202-a81f-1036100ef4f6,2977afbc-0b12-459c-a36f-f623fc9e9840}', '{\\x00,\\x01,\\x02}'); diff --git a/e2e_test/source/cdc/postgres_cdc_insert.sql b/e2e_test/source/cdc/postgres_cdc_insert.sql index 6b4fde2e71244..a02a35a020965 100644 --- a/e2e_test/source/cdc/postgres_cdc_insert.sql +++ b/e2e_test/source/cdc/postgres_cdc_insert.sql @@ -23,4 +23,5 @@ insert into numeric_table values(107, 'Infinity'::numeric); INSERT INTO enum_table VALUES (3, 'sad'); --- to avoid escaping issues of psql -c "", we insert this row here and check the result in check_new_rows.slt -INSERT INTO list_with_null VALUES (2, '{NULL,-1,-2}', '{NULL,nan,-inf}', '{NULL,sad,ok}', '{NULL,471acecf-a4b4-4ed3-a211-7fb2291f159f,9bc35adf-fb11-4130-944c-e7eadb96b829}', '{NULL,\\x99,\\xAA}'); +INSERT INTO list_with_null VALUES (3, '{NULL,-3,-4}', '{NULL,nan,-inf}', '{NULL,nan,-inf}', '{NULL,nan,-inf}', '{NULL,sad,ok}', '{NULL,471acecf-a4b4-4ed3-a211-7fb2291f159f,9bc35adf-fb11-4130-944c-e7eadb96b829}', '{NULL,\\x99,\\xAA}'); +INSERT INTO list_with_null VALUES (4, '{-4,-5,-6}', '{NULL,-99999999999999999.9999}', '{NULL,-99999999999999999.9999}', '{NULL,-99999999999999999.9999}', '{NULL,sad,ok}', '{b2e4636d-fa03-4ad4-bf16-029a79dca3e2}', '{\\x88,\\x99,\\xAA}'); diff --git a/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt b/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt index da3d82083755f..10854d97b6440 100644 --- a/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt +++ b/e2e_test/source_inline/cdc/mysql/mysql_create_drop.slt @@ -5,6 +5,11 @@ control substitution on statement ok ALTER SYSTEM SET max_concurrent_creating_streaming_jobs TO 1; +system ok +mysql -e " + SET GLOBAL time_zone = '+01:00'; +" + system ok mysql -e " DROP DATABASE IF EXISTS testdb1; CREATE DATABASE testdb1; @@ -159,53 +164,53 @@ sleep 5s query IT select * from tt1; ---- -1 2023-10-23 10:00:00+00:00 +1 2023-10-23 09:00:00+00:00 query IT select * from tt2; ---- -2 2023-10-23 11:00:00+00:00 +2 2023-10-23 10:00:00+00:00 query IT select * from tt3; ---- -3 2023-10-23 12:00:00+00:00 +3 2023-10-23 11:00:00+00:00 query IT select * from tt4; ---- -4 2023-10-23 13:00:00+00:00 +4 2023-10-23 12:00:00+00:00 query IT select * from tt5; ---- -5 2023-10-23 14:00:00+00:00 +5 2023-10-23 13:00:00+00:00 query IT select * from tt1_shared; ---- -1 2023-10-23 10:00:00+00:00 +1 2023-10-23 09:00:00+00:00 query IT select * from tt2_shared; ---- -2 2023-10-23 11:00:00+00:00 +2 2023-10-23 10:00:00+00:00 query IT select * from tt3_shared; ---- -3 2023-10-23 12:00:00+00:00 +3 2023-10-23 11:00:00+00:00 query IT select * from tt4_shared; ---- -4 2023-10-23 13:00:00+00:00 +4 2023-10-23 12:00:00+00:00 query IT select * from tt5_shared; ---- -5 2023-10-23 14:00:00+00:00 +5 2023-10-23 13:00:00+00:00 statement ok drop table tt1; @@ -224,3 +229,8 @@ drop table tt5; statement ok drop source s cascade; + +system ok +mysql -e " + SET GLOBAL time_zone = '+00:00'; +" diff --git a/e2e_test/source_inline/kafka/shared_source.slt b/e2e_test/source_inline/kafka/shared_source.slt index 57ab0b95de9b5..f180e6e0d8351 100644 --- a/e2e_test/source_inline/kafka/shared_source.slt +++ b/e2e_test/source_inline/kafka/shared_source.slt @@ -28,7 +28,7 @@ select count(*) from rw_internal_tables where name like '%s0%'; sleep 1s -# Ingestion does not start (state table is empty), even after sleep +# SourceExecutor's ingestion does not start (state table is empty), even after sleep system ok internal_table.mjs --name s0 --type source ---- @@ -41,28 +41,21 @@ create materialized view mv_1 as select * from s0; # Wait enough time to ensure SourceExecutor consumes all Kafka data. sleep 2s -# Ingestion started +# SourceExecutor's ingestion started, but it only starts from latest. system ok internal_table.mjs --name s0 --type source ---- -0,"{""split_info"": {""partition"": 0, ""start_offset"": 0, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" -1,"{""split_info"": {""partition"": 1, ""start_offset"": 0, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" -2,"{""split_info"": {""partition"": 2, ""start_offset"": 0, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" -3,"{""split_info"": {""partition"": 3, ""start_offset"": 0, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" - +(empty) -# The result is non-deterministic: -# If the upstream row comes before the backfill row, it will be ignored, and the result state is Backfilling. -# If the upstream row comes after the backfill row, the result state is Finished. -# Uncomment below and run manually to see the result. -# system ok -# internal_table.mjs --name mv_1 --type sourcebackfill -# ---- -# 0,"{""Backfilling"": ""0""}" -# 1,"{""Backfilling"": ""0""}" -# 2,"{""Backfilling"": ""0""}" -# 3,"{""Backfilling"": ""0""}" +# offset 0 must be backfilled, not from upstream. +system ok +internal_table.mjs --name mv_1 --type sourcebackfill +---- +0,"{""Backfilling"": ""0""}" +1,"{""Backfilling"": ""0""}" +2,"{""Backfilling"": ""0""}" +3,"{""Backfilling"": ""0""}" # This does not affect the behavior for CREATE MATERIALIZED VIEW below. It also uses the shared source, and creates SourceBackfillExecutor. @@ -108,6 +101,16 @@ EOF sleep 2s +# SourceExecutor's finally got new data now. +system ok +internal_table.mjs --name s0 --type source +---- +0,"{""split_info"": {""partition"": 0, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +1,"{""split_info"": {""partition"": 1, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +2,"{""split_info"": {""partition"": 2, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" +3,"{""split_info"": {""partition"": 3, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" + + query IT rowsort select v1, v2 from s0; ---- @@ -143,7 +146,9 @@ internal_table.mjs --name s0 --type source 3,"{""split_info"": {""partition"": 3, ""start_offset"": 1, ""stop_offset"": null, ""topic"": ""shared_source""}, ""split_type"": ""kafka""}" -# Same as above, the result is still non-deterministic: Some partitions may be: "{""Backfilling"": ""1""}" +# The result is non-deterministic: +# If the upstream row comes before the backfill row, it will be ignored, and the result state is "{""Backfilling"": ""1""}". +# If the upstream row comes after the backfill row, the result state is Finished. # Uncomment below and run manually to see the result. # system ok diff --git a/scripts/source/prepare_ci_pubsub/src/main.rs b/e2e_test/source_inline/pubsub/prepare-data.rs old mode 100644 new mode 100755 similarity index 60% rename from scripts/source/prepare_ci_pubsub/src/main.rs rename to e2e_test/source_inline/pubsub/prepare-data.rs index 5357e2d4b6065..81bd1ba73e41e --- a/scripts/source/prepare_ci_pubsub/src/main.rs +++ b/e2e_test/source_inline/pubsub/prepare-data.rs @@ -1,6 +1,19 @@ -use std::fs::File; -use std::io::prelude::*; -use std::io::BufReader; +#!/usr/bin/env -S cargo -Zscript +```cargo +[dependencies] +anyhow = "1" +google-cloud-googleapis = { version = "0.12", features = ["pubsub"] } +google-cloud-pubsub = "0.24" +tokio = { version = "0.2", package = "madsim-tokio", features = [ + "rt", + "rt-multi-thread", + "sync", + "macros", + "time", + "signal", + "fs", +] } +``` use google_cloud_googleapis::pubsub::v1::PubsubMessage; use google_cloud_pubsub::client::Client; @@ -38,21 +51,11 @@ async fn main() -> anyhow::Result<()> { .await?; } - let path = std::env::current_exe()? - .parent() - .and_then(|p| p.parent()) - .and_then(|p| p.parent()) - .unwrap() - .join("scripts/source/test_data/pubsub_1_test_topic.1"); - - let file = File::open(path)?; - let file = BufReader::new(file); - let publisher = topic.new_publisher(Default::default()); - for line in file.lines().map_while(Result::ok) { + for line in DATA.lines() { let a = publisher .publish(PubsubMessage { - data: line.clone().into_bytes(), + data: line.to_string().into_bytes(), ..Default::default() }) .await; @@ -62,3 +65,25 @@ async fn main() -> anyhow::Result<()> { Ok(()) } + +const DATA: &str = r#"{"v1":1,"v2":"name0"} +{"v1":2,"v2":"name0"} +{"v1":6,"v2":"name3"} +{"v1":0,"v2":"name5"} +{"v1":5,"v2":"name8"} +{"v1":6,"v2":"name4"} +{"v1":8,"v2":"name9"} +{"v1":9,"v2":"name2"} +{"v1":4,"v2":"name6"} +{"v1":5,"v2":"name3"} +{"v1":8,"v2":"name8"} +{"v1":9,"v2":"name2"} +{"v1":2,"v2":"name3"} +{"v1":4,"v2":"name7"} +{"v1":7,"v2":"name0"} +{"v1":0,"v2":"name9"} +{"v1":3,"v2":"name2"} +{"v1":7,"v2":"name5"} +{"v1":1,"v2":"name7"} +{"v1":3,"v2":"name9"} +"#; diff --git a/e2e_test/source/basic/pubsub.slt b/e2e_test/source_inline/pubsub/pubsub.slt similarity index 65% rename from e2e_test/source/basic/pubsub.slt rename to e2e_test/source_inline/pubsub/pubsub.slt index b245d9b2aea89..cfb5f551cf36a 100644 --- a/e2e_test/source/basic/pubsub.slt +++ b/e2e_test/source_inline/pubsub/pubsub.slt @@ -1,15 +1,20 @@ +control substitution on + +system ok +e2e_test/source_inline/pubsub/prepare-data.rs + # fail with invalid emulator_host -statement error +statement error failed to lookup address information CREATE TABLE s1 (v1 int, v2 varchar) WITH ( + connector = 'google_pubsub', pubsub.subscription = 'test-subscription-1', pubsub.emulator_host = 'invalid_host:5981' ) FORMAT PLAIN ENCODE JSON; statement ok CREATE TABLE s1 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, pubsub.subscription = 'test-subscription-1', - pubsub.emulator_host = 'localhost:5980' ) FORMAT PLAIN ENCODE JSON; statement ok @@ -18,29 +23,25 @@ SELECT * FROM s1; statement ok DROP TABLE s1; -# fail with invalid subscription -statement error +statement error subscription test-subscription-not-exist does not exist CREATE TABLE s2 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', - pubsub.subscription = 'test-subscription-not-2', - pubsub.emulator_host = 'localhost:5980' + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, + pubsub.subscription = 'test-subscription-not-exist', ) FORMAT PLAIN ENCODE JSON; statement ok CREATE TABLE s2 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, pubsub.subscription = 'test-subscription-2', - pubsub.emulator_host = 'localhost:5980' ) FORMAT PLAIN ENCODE JSON; # fail if both start_offset and start_snapshot are provided -statement error +statement error specify at most one of start_offset or start_snapshot CREATE TABLE s3 (v1 int, v2 varchar) WITH ( - connector = 'google_pubsub', + ${RISEDEV_PUBSUB_WITH_OPTIONS_COMMON}, pubsub.subscription = 'test-subscription-3', - pubsub.emulator_host = 'localhost:5980', - pubsub.start_offset = "121212", - pubsub.start_snapshot = "snapshot-that-doesnt-exist" + pubsub.start_offset.nanos = '121212', + pubsub.start_snapshot = 'snapshot-that-doesnt-exist' ) FORMAT PLAIN ENCODE JSON; # wait for source diff --git a/e2e_test/udf/external_udf.slt b/e2e_test/udf/external_udf.slt index 096a605709d67..7a38506f81563 100644 --- a/e2e_test/udf/external_udf.slt +++ b/e2e_test/udf/external_udf.slt @@ -1,7 +1,7 @@ # Before running this test: # python3 e2e_test/udf/test.py # or: -# cd java/udf-example && mvn package && java -jar target/risingwave-udf-example.jar +# cd e2e_test/udf/java && mvn package && java -jar target/risingwave-udf-example.jar # Create a function. statement ok diff --git a/java/udf-example/README.md b/e2e_test/udf/java/README.md similarity index 100% rename from java/udf-example/README.md rename to e2e_test/udf/java/README.md diff --git a/java/udf-example/pom.xml b/e2e_test/udf/java/pom.xml similarity index 86% rename from java/udf-example/pom.xml rename to e2e_test/udf/java/pom.xml index 8bf51cd108128..7ecd7c54dca17 100644 --- a/java/udf-example/pom.xml +++ b/e2e_test/udf/java/pom.xml @@ -5,17 +5,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - - com.risingwave - risingwave-java-root - 0.1.0-SNAPSHOT - ../pom.xml - - com.risingwave risingwave-udf-example - 0.1.1-SNAPSHOT + 0.1.0-SNAPSHOT udf-example https://docs.risingwave.com/docs/current/udf-java @@ -31,7 +23,7 @@ com.risingwave risingwave-udf - 0.1.3-SNAPSHOT + 0.2.0 com.google.code.gson diff --git a/java/udf-example/src/main/java/com/example/UdfExample.java b/e2e_test/udf/java/src/main/java/com/example/UdfExample.java similarity index 99% rename from java/udf-example/src/main/java/com/example/UdfExample.java rename to e2e_test/udf/java/src/main/java/com/example/UdfExample.java index 883dc5035514c..1702e244bf1ff 100644 --- a/java/udf-example/src/main/java/com/example/UdfExample.java +++ b/e2e_test/udf/java/src/main/java/com/example/UdfExample.java @@ -33,7 +33,7 @@ public class UdfExample { public static void main(String[] args) throws IOException { - try (var server = new UdfServer("0.0.0.0", 8815)) { + try (var server = new UdfServer("localhost", 8815)) { server.addFunction("int_42", new Int42()); server.addFunction("float_to_decimal", new FloatToDecimal()); server.addFunction("sleep", new Sleep()); diff --git a/e2e_test/udf/requirements.txt b/e2e_test/udf/requirements.txt index 8642e2b1ec254..36688db1ed1ee 100644 --- a/e2e_test/udf/requirements.txt +++ b/e2e_test/udf/requirements.txt @@ -1,2 +1,3 @@ flask -waitress \ No newline at end of file +waitress +arrow_udf==0.2.1 \ No newline at end of file diff --git a/e2e_test/udf/test.py b/e2e_test/udf/test.py index 6195476a80004..4443a81a6e74d 100644 --- a/e2e_test/udf/test.py +++ b/e2e_test/udf/test.py @@ -19,9 +19,7 @@ from typing import Iterator, List, Optional, Tuple, Any from decimal import Decimal -sys.path.append("src/expr/udf/python") # noqa - -from risingwave.udf import udf, udtf, UdfServer +from arrow_udf import udf, udtf, UdfServer @udf(input_types=[], result_type="INT") @@ -47,13 +45,21 @@ def gcd3(x: int, y: int, z: int) -> int: return gcd(gcd(x, y), z) -@udf(input_types=["BYTEA"], result_type="STRUCT") +@udf( + input_types=["BYTEA"], + result_type="STRUCT", +) def extract_tcp_info(tcp_packet: bytes): src_addr, dst_addr = struct.unpack("!4s4s", tcp_packet[12:20]) src_port, dst_port = struct.unpack("!HH", tcp_packet[20:24]) src_addr = socket.inet_ntoa(src_addr) dst_addr = socket.inet_ntoa(dst_addr) - return src_addr, dst_addr, src_port, dst_port + return { + "src_addr": src_addr, + "dst_addr": dst_addr, + "src_port": src_port, + "dst_port": dst_port, + } @udtf(input_types="INT", result_types="INT") @@ -84,7 +90,7 @@ def hex_to_dec(hex: Optional[str]) -> Optional[Decimal]: return dec -@udf(input_types=["FLOAT8"], result_type="DECIMAL") +@udf(input_types=["FLOAT64"], result_type="DECIMAL") def float_to_decimal(f: float) -> Decimal: return Decimal(f) @@ -120,21 +126,49 @@ def jsonb_array_identity(list: List[Any]) -> List[Any]: return list -@udf(input_types="STRUCT", result_type="STRUCT") +@udf( + input_types="STRUCT", + result_type="STRUCT", +) def jsonb_array_struct_identity(v: Tuple[List[Any], int]) -> Tuple[List[Any], int]: return v -ALL_TYPES = "BOOLEAN,SMALLINT,INT,BIGINT,FLOAT4,FLOAT8,DECIMAL,DATE,TIME,TIMESTAMP,INTERVAL,VARCHAR,BYTEA,JSONB".split( - "," -) + [ - "STRUCT" -] - - @udf( - input_types=ALL_TYPES, - result_type=f"struct<{','.join(ALL_TYPES)}>", + input_types=[ + "boolean", + "int16", + "int32", + "int64", + "float32", + "float64", + "decimal", + "date32", + "time64", + "timestamp", + "interval", + "string", + "binary", + "json", + "struct", + ], + result_type="""struct< + boolean: boolean, + int16: int16, + int32: int32, + int64: int64, + float32: float32, + float64: float64, + decimal: decimal, + date32: date32, + time64: time64, + timestamp: timestamp, + interval: interval, + string: string, + binary: binary, + json: json, + struct: struct, + >""", ) def return_all( bool, @@ -153,28 +187,60 @@ def return_all( jsonb, struct, ): - return ( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, - struct, - ) + return { + "boolean": bool, + "int16": i16, + "int32": i32, + "int64": i64, + "float32": f32, + "float64": f64, + "decimal": decimal, + "date32": date, + "time64": time, + "timestamp": timestamp, + "interval": interval, + "string": varchar, + "binary": bytea, + "json": jsonb, + "struct": struct, + } @udf( - input_types=[t + "[]" for t in ALL_TYPES], - result_type=f"struct<{','.join(t + '[]' for t in ALL_TYPES)}>", + input_types=[ + "boolean[]", + "int16[]", + "int32[]", + "int64[]", + "float32[]", + "float64[]", + "decimal[]", + "date32[]", + "time64[]", + "timestamp[]", + "interval[]", + "string[]", + "binary[]", + "json[]", + "struct[]", + ], + result_type="""struct< + boolean: boolean[], + int16: int16[], + int32: int32[], + int64: int64[], + float32: float32[], + float64: float64[], + decimal: decimal[], + date32: date32[], + time64: time64[], + timestamp: timestamp[], + interval: interval[], + string: string[], + binary: binary[], + json: json[], + struct: struct[], + >""", ) def return_all_arrays( bool, @@ -193,23 +259,23 @@ def return_all_arrays( jsonb, struct, ): - return ( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, - struct, - ) + return { + "boolean": bool, + "int16": i16, + "int32": i32, + "int64": i64, + "float32": f32, + "float64": f64, + "decimal": decimal, + "date32": date, + "time64": time, + "timestamp": timestamp, + "interval": interval, + "string": varchar, + "binary": bytea, + "json": jsonb, + "struct": struct, + } if __name__ == "__main__": diff --git a/e2e_test/udf/wasm/Cargo.toml b/e2e_test/udf/wasm/Cargo.toml index 250bd8132ca53..54c7da45b1af8 100644 --- a/e2e_test/udf/wasm/Cargo.toml +++ b/e2e_test/udf/wasm/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -arrow-udf = "0.2" +arrow-udf = "0.3" genawaiter = "0.99" rust_decimal = "1" serde_json = "1" diff --git a/integration_tests/ad-click/docker-compose.yml b/integration_tests/ad-click/docker-compose.yml index f1a2bbc8419cb..62d5c3fb76517 100644 --- a/integration_tests/ad-click/docker-compose.yml +++ b/integration_tests/ad-click/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/ad-ctr/docker-compose.yml b/integration_tests/ad-ctr/docker-compose.yml index fb297a8fec8ab..0298f014db11a 100644 --- a/integration_tests/ad-ctr/docker-compose.yml +++ b/integration_tests/ad-ctr/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/big-query-sink/docker-compose.yml b/integration_tests/big-query-sink/docker-compose.yml index ca16ca8f90b3b..6c93903df8bba 100644 --- a/integration_tests/big-query-sink/docker-compose.yml +++ b/integration_tests/big-query-sink/docker-compose.yml @@ -7,10 +7,10 @@ services: service: risingwave-standalone volumes: - "../../gcp-rwctest.json:/gcp-rwctest.json" - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -32,7 +32,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml b/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml index 425e086c56ba0..0fa224ddab9d0 100644 --- a/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml +++ b/integration_tests/cassandra-and-scylladb-sink/docker-compose.yml @@ -22,10 +22,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -45,7 +45,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/cdn-metrics/docker-compose.yml b/integration_tests/cdn-metrics/docker-compose.yml index 69eb63a448eb2..87adef35f8cf4 100644 --- a/integration_tests/cdn-metrics/docker-compose.yml +++ b/integration_tests/cdn-metrics/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/citus-cdc/docker-compose.yml b/integration_tests/citus-cdc/docker-compose.yml index d77e0e80dbfa9..6ce8341047ee4 100644 --- a/integration_tests/citus-cdc/docker-compose.yml +++ b/integration_tests/citus-cdc/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -90,7 +90,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/clickhouse-sink/docker-compose.yml b/integration_tests/clickhouse-sink/docker-compose.yml index 76b0f7fe607f5..1cf61ff8dfa30 100644 --- a/integration_tests/clickhouse-sink/docker-compose.yml +++ b/integration_tests/clickhouse-sink/docker-compose.yml @@ -17,10 +17,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -36,7 +36,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/clickstream/docker-compose.yml b/integration_tests/clickstream/docker-compose.yml index faf66b770af7d..857c93f0d7577 100644 --- a/integration_tests/clickstream/docker-compose.yml +++ b/integration_tests/clickstream/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/client-library/csharp/csharp.csproj b/integration_tests/client-library/csharp/csharp.csproj index 2d7ae2b7e2f48..f57b4028aabe9 100644 --- a/integration_tests/client-library/csharp/csharp.csproj +++ b/integration_tests/client-library/csharp/csharp.csproj @@ -12,7 +12,7 @@ - + diff --git a/integration_tests/client-library/docker-compose.yml b/integration_tests/client-library/docker-compose.yml index d2e4663fdc378..c6868eaa42140 100644 --- a/integration_tests/client-library/docker-compose.yml +++ b/integration_tests/client-library/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -63,7 +63,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/cockroach-sink/docker-compose.yml b/integration_tests/cockroach-sink/docker-compose.yml index a205dca9e19cf..b6b0c8d9e6c5f 100644 --- a/integration_tests/cockroach-sink/docker-compose.yml +++ b/integration_tests/cockroach-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -38,7 +38,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/debezium-mongo/docker-compose.yaml b/integration_tests/debezium-mongo/docker-compose.yaml index 4ae90ea22eb94..886e4622ad6ac 100644 --- a/integration_tests/debezium-mongo/docker-compose.yaml +++ b/integration_tests/debezium-mongo/docker-compose.yaml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml diff --git a/integration_tests/debezium-mysql/docker-compose.yml b/integration_tests/debezium-mysql/docker-compose.yml index 77ff7689b78f8..3462e5e3d09d1 100644 --- a/integration_tests/debezium-mysql/docker-compose.yml +++ b/integration_tests/debezium-mysql/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -78,7 +78,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/debezium-postgres/docker-compose.yml b/integration_tests/debezium-postgres/docker-compose.yml index ef9094e432f6f..c81c33fb3e455 100644 --- a/integration_tests/debezium-postgres/docker-compose.yml +++ b/integration_tests/debezium-postgres/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -88,7 +88,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/debezium-sqlserver/docker-compose.yml b/integration_tests/debezium-sqlserver/docker-compose.yml index 848d2dc5393ab..e88cb36e548b7 100644 --- a/integration_tests/debezium-sqlserver/docker-compose.yml +++ b/integration_tests/debezium-sqlserver/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -76,7 +76,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/deltalake-sink/docker-compose.yml b/integration_tests/deltalake-sink/docker-compose.yml index a486e2336b502..70b1e3c22e325 100644 --- a/integration_tests/deltalake-sink/docker-compose.yml +++ b/integration_tests/deltalake-sink/docker-compose.yml @@ -13,10 +13,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -32,7 +32,7 @@ services: volumes: compute-node-0: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/doris-sink/docker-compose.yml b/integration_tests/doris-sink/docker-compose.yml index fc7cfd751e989..e1a7f1ef5e90e 100644 --- a/integration_tests/doris-sink/docker-compose.yml +++ b/integration_tests/doris-sink/docker-compose.yml @@ -35,10 +35,10 @@ services: networks: mynetwork: ipv4_address: 172.21.0.4 - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: mynetwork: ipv4_address: 172.21.0.5 @@ -86,7 +86,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/elasticsearch-sink/docker-compose.yml b/integration_tests/elasticsearch-sink/docker-compose.yml index 195e8d0070eb8..c885b7136a606 100644 --- a/integration_tests/elasticsearch-sink/docker-compose.yml +++ b/integration_tests/elasticsearch-sink/docker-compose.yml @@ -29,10 +29,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -48,7 +48,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/feature-store/docker-compose.yml b/integration_tests/feature-store/docker-compose.yml index b1767f8d04864..71633cce20a19 100644 --- a/integration_tests/feature-store/docker-compose.yml +++ b/integration_tests/feature-store/docker-compose.yml @@ -73,10 +73,10 @@ services: [kafka,meta-node-0,frontend-node-0] volumes: - ".log:/opt/feature-store/.log" - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose-distributed.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose-distributed.yml @@ -98,7 +98,7 @@ services: file: ../../docker/docker-compose-distributed.yml service: prometheus-0 volumes: - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/http-sink/docker-compose.yml b/integration_tests/http-sink/docker-compose.yml index 8fba5ff352dc0..12546c4f5dd28 100644 --- a/integration_tests/http-sink/docker-compose.yml +++ b/integration_tests/http-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -24,7 +24,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/iceberg-cdc/docker-compose.yml b/integration_tests/iceberg-cdc/docker-compose.yml index e483003907746..703571c2f10a5 100644 --- a/integration_tests/iceberg-cdc/docker-compose.yml +++ b/integration_tests/iceberg-cdc/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -127,7 +127,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/iceberg-sink/docker-compose.yml b/integration_tests/iceberg-sink/docker-compose.yml index c7182b873d85b..91cec5dd24430 100644 --- a/integration_tests/iceberg-sink/docker-compose.yml +++ b/integration_tests/iceberg-sink/docker-compose.yml @@ -53,10 +53,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -161,7 +161,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml b/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml index 3314083c1077b..449d6043e6e95 100644 --- a/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/hive/docker-compose.yml @@ -75,10 +75,10 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: @@ -106,7 +106,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml b/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml index 3f2bb75479563..714fcdf6e0b95 100644 --- a/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/jdbc/docker-compose.yml @@ -76,17 +76,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml b/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml index 025db74c23cae..ee3e1da9b62da 100644 --- a/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/rest/docker-compose.yml @@ -80,17 +80,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml b/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml index 8d6d6f72d53c5..4e0ec11eff500 100644 --- a/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml +++ b/integration_tests/iceberg-sink2/docker/storage/docker-compose.yml @@ -56,17 +56,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/hive/docker-compose.yml b/integration_tests/iceberg-source/docker/hive/docker-compose.yml index 3314083c1077b..449d6043e6e95 100644 --- a/integration_tests/iceberg-source/docker/hive/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/hive/docker-compose.yml @@ -75,10 +75,10 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: @@ -106,7 +106,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml b/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml index 3f2bb75479563..714fcdf6e0b95 100644 --- a/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/jdbc/docker-compose.yml @@ -76,17 +76,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/rest/docker-compose.yml b/integration_tests/iceberg-source/docker/rest/docker-compose.yml index 025db74c23cae..ee3e1da9b62da 100644 --- a/integration_tests/iceberg-source/docker/rest/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/rest/docker-compose.yml @@ -80,17 +80,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/iceberg-source/docker/storage/docker-compose.yml b/integration_tests/iceberg-source/docker/storage/docker-compose.yml index 8d6d6f72d53c5..4e0ec11eff500 100644 --- a/integration_tests/iceberg-source/docker/storage/docker-compose.yml +++ b/integration_tests/iceberg-source/docker/storage/docker-compose.yml @@ -56,17 +56,17 @@ services: networks: iceberg_net: - etcd-0: + postgres-0: extends: file: ../../../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 networks: iceberg_net: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false minio-0: external: false diff --git a/integration_tests/kafka-cdc-sink/docker-compose.yml b/integration_tests/kafka-cdc-sink/docker-compose.yml index c8c31d7762cf6..81f892354b8a0 100644 --- a/integration_tests/kafka-cdc-sink/docker-compose.yml +++ b/integration_tests/kafka-cdc-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -133,7 +133,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/kafka-cdc/docker-compose.yml b/integration_tests/kafka-cdc/docker-compose.yml index d62fa2acd9df3..f42c4399178d0 100644 --- a/integration_tests/kafka-cdc/docker-compose.yml +++ b/integration_tests/kafka-cdc/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -38,7 +38,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/kinesis-s3-source/docker-compose.yml b/integration_tests/kinesis-s3-source/docker-compose.yml index 9108537309bd7..dc91e2095cbde 100644 --- a/integration_tests/kinesis-s3-source/docker-compose.yml +++ b/integration_tests/kinesis-s3-source/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -50,7 +50,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/livestream/docker-compose.yml b/integration_tests/livestream/docker-compose.yml index 1f3e0736a4def..8dffce371562a 100644 --- a/integration_tests/livestream/docker-compose.yml +++ b/integration_tests/livestream/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mindsdb/docker-compose.yml b/integration_tests/mindsdb/docker-compose.yml index c6eeec5c75ee6..40fe4e6192fa3 100644 --- a/integration_tests/mindsdb/docker-compose.yml +++ b/integration_tests/mindsdb/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -52,7 +52,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mongodb-cdc/docker-compose.yaml b/integration_tests/mongodb-cdc/docker-compose.yaml index 60d477945b38b..eaf519b440569 100644 --- a/integration_tests/mongodb-cdc/docker-compose.yaml +++ b/integration_tests/mongodb-cdc/docker-compose.yaml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mqtt/docker-compose.yml b/integration_tests/mqtt/docker-compose.yml index 9db7e7c04f8fc..04f73404be6aa 100644 --- a/integration_tests/mqtt/docker-compose.yml +++ b/integration_tests/mqtt/docker-compose.yml @@ -13,10 +13,10 @@ services: - echo "running command"; printf 'allow_anonymous true\nlistener 1883 0.0.0.0' > /mosquitto/config/mosquitto.conf; echo "starting service..."; cat /mosquitto/config/mosquitto.conf;/docker-entrypoint.sh;/usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf ports: - 1883:1883 - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -36,7 +36,7 @@ services: volumes: compute-node-0: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mysql-cdc/docker-compose.yml b/integration_tests/mysql-cdc/docker-compose.yml index 5cd3003669d7b..c0bba2ccc008b 100644 --- a/integration_tests/mysql-cdc/docker-compose.yml +++ b/integration_tests/mysql-cdc/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -70,7 +70,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/mysql-sink/docker-compose.yml b/integration_tests/mysql-sink/docker-compose.yml index 97d3d78ce4cb0..3e1fc5544276f 100644 --- a/integration_tests/mysql-sink/docker-compose.yml +++ b/integration_tests/mysql-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -41,7 +41,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/nats/docker-compose.yml b/integration_tests/nats/docker-compose.yml index 2010ec2d8b4ed..891c865744747 100644 --- a/integration_tests/nats/docker-compose.yml +++ b/integration_tests/nats/docker-compose.yml @@ -10,10 +10,10 @@ services: ports: - "4222:4222" command: -js - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -50,7 +50,7 @@ services: volumes: compute-node-0: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/pinot-sink/docker-compose.yml b/integration_tests/pinot-sink/docker-compose.yml index 222c1d7e39735..fc4ad250880ce 100644 --- a/integration_tests/pinot-sink/docker-compose.yml +++ b/integration_tests/pinot-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -84,7 +84,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/postgres-cdc/docker-compose.yml b/integration_tests/postgres-cdc/docker-compose.yml index 1898d20a8c9ae..7650da0779178 100644 --- a/integration_tests/postgres-cdc/docker-compose.yml +++ b/integration_tests/postgres-cdc/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -30,7 +30,7 @@ services: - POSTGRES_PASSWORD=123456 - POSTGRES_DB=mydb ports: - - 8432:5432 + - 5432:5432 healthcheck: test: [ "CMD-SHELL", "pg_isready --username=myuser --dbname=mydb" ] interval: 5s @@ -78,7 +78,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/postgres-sink/docker-compose.yml b/integration_tests/postgres-sink/docker-compose.yml index e443965c2e5be..4d8638fdc3c07 100644 --- a/integration_tests/postgres-sink/docker-compose.yml +++ b/integration_tests/postgres-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -44,7 +44,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/presto-trino/docker-compose.yml b/integration_tests/presto-trino/docker-compose.yml index b12785139ebad..a56135a4ae597 100644 --- a/integration_tests/presto-trino/docker-compose.yml +++ b/integration_tests/presto-trino/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -48,7 +48,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/prometheus/docker-compose.yml b/integration_tests/prometheus/docker-compose.yml index 0beb75839d4cd..de3249df9253a 100644 --- a/integration_tests/prometheus/docker-compose.yml +++ b/integration_tests/prometheus/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -67,7 +67,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/prometheus/prometheus.yaml b/integration_tests/prometheus/prometheus.yaml index c70ddd434064e..7d955a835b32d 100644 --- a/integration_tests/prometheus/prometheus.yaml +++ b/integration_tests/prometheus/prometheus.yaml @@ -27,7 +27,7 @@ scrape_configs: - job_name: etcd static_configs: - - targets: ["etcd-0:2379"] + - targets: ["postgres-0:2379"] - job_name: redpanda static_configs: diff --git a/integration_tests/redis-sink/docker-compose.yml b/integration_tests/redis-sink/docker-compose.yml index 0fd33048a29bf..dce27ae99895c 100644 --- a/integration_tests/redis-sink/docker-compose.yml +++ b/integration_tests/redis-sink/docker-compose.yml @@ -16,10 +16,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -39,7 +39,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/schema-registry/docker-compose.yml b/integration_tests/schema-registry/docker-compose.yml index 3020a96790fbf..80d4b90e4f7d2 100644 --- a/integration_tests/schema-registry/docker-compose.yml +++ b/integration_tests/schema-registry/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -62,7 +62,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/starrocks-sink/docker-compose.yml b/integration_tests/starrocks-sink/docker-compose.yml index 81ef7c277dad0..70918713643d6 100644 --- a/integration_tests/starrocks-sink/docker-compose.yml +++ b/integration_tests/starrocks-sink/docker-compose.yml @@ -37,10 +37,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -62,7 +62,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/superset/docker-compose.yml b/integration_tests/superset/docker-compose.yml index 271e79755aab3..746a80fb9a064 100644 --- a/integration_tests/superset/docker-compose.yml +++ b/integration_tests/superset/docker-compose.yml @@ -15,10 +15,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -112,7 +112,7 @@ volumes: external: false risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/tidb-cdc-sink/docker-compose.yml b/integration_tests/tidb-cdc-sink/docker-compose.yml index 9177936d47ab8..f1b4cb0ebdd7b 100644 --- a/integration_tests/tidb-cdc-sink/docker-compose.yml +++ b/integration_tests/tidb-cdc-sink/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -204,7 +204,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/twitter-pulsar/docker-compose.yml b/integration_tests/twitter-pulsar/docker-compose.yml index 8061425d48d38..d684be6b876a8 100644 --- a/integration_tests/twitter-pulsar/docker-compose.yml +++ b/integration_tests/twitter-pulsar/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -42,7 +42,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/twitter/docker-compose.yml b/integration_tests/twitter/docker-compose.yml index e5637b7f2664f..37b2723cb8e50 100644 --- a/integration_tests/twitter/docker-compose.yml +++ b/integration_tests/twitter/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -37,7 +37,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/upsert-avro/docker-compose.yml b/integration_tests/upsert-avro/docker-compose.yml index 695c8f8fcb043..291528f6fb319 100644 --- a/integration_tests/upsert-avro/docker-compose.yml +++ b/integration_tests/upsert-avro/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose.yml @@ -43,7 +43,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/integration_tests/vector/docker-compose.yml b/integration_tests/vector/docker-compose.yml index 2179cd66542c4..4c2e6100b714a 100644 --- a/integration_tests/vector/docker-compose.yml +++ b/integration_tests/vector/docker-compose.yml @@ -5,10 +5,10 @@ services: extends: file: ../../docker/docker-compose.yml service: risingwave-standalone - etcd-0: + postgres-0: extends: file: ../../docker/docker-compose-distributed.yml - service: etcd-0 + service: postgres-0 grafana-0: extends: file: ../../docker/docker-compose-distributed.yml @@ -34,7 +34,7 @@ services: volumes: risingwave-standalone: external: false - etcd-0: + postgres-0: external: false grafana-0: external: false diff --git a/java/connector-node/python-client/.gitignore b/java/connector-node/python-client/.gitignore index 600d2d33badf4..536c32383d754 100644 --- a/java/connector-node/python-client/.gitignore +++ b/java/connector-node/python-client/.gitignore @@ -1 +1,2 @@ -.vscode \ No newline at end of file +.vscode +sink-client-venv/ diff --git a/java/connector-node/python-client/integration_tests.py b/java/connector-node/python-client/integration_tests.py index b16b5eaf34ad4..909859afc218a 100644 --- a/java/connector-node/python-client/integration_tests.py +++ b/java/connector-node/python-client/integration_tests.py @@ -117,7 +117,7 @@ def load_stream_chunk_payload(input_file): return payloads -def test_sink(prop, format, payload_input, table_schema, is_coordinated=False): +def test_sink(prop, payload_input, table_schema, is_coordinated=False): sink_param = connector_service_pb2.SinkParam( sink_id=0, properties=prop, @@ -128,7 +128,6 @@ def test_sink(prop, format, payload_input, table_schema, is_coordinated=False): request_list = [ connector_service_pb2.SinkWriterStreamRequest( start=connector_service_pb2.SinkWriterStreamRequest.StartSink( - format=format, sink_param=sink_param, ) ) @@ -291,9 +290,6 @@ def test_stream_chunk_data_format(param): parser.add_argument( "--deltalake_sink", action="store_true", help="run deltalake sink test" ) - parser.add_argument( - "--input_file", default="./data/sink_input.json", help="input data to run tests" - ) parser.add_argument( "--input_binary_file", default="./data/sink_input", @@ -302,29 +298,18 @@ def test_stream_chunk_data_format(param): parser.add_argument( "--es_sink", action="store_true", help="run elasticsearch sink test" ) - parser.add_argument( - "--data_format_use_json", default=True, help="choose json or streamchunk" - ) args = parser.parse_args() - use_json = args.data_format_use_json == True or args.data_format_use_json == "True" - if use_json: - payload = load_json_payload(args.input_file) - format = connector_service_pb2.SinkPayloadFormat.JSON - else: - payload = load_stream_chunk_payload(args.input_binary_file) - format = connector_service_pb2.SinkPayloadFormat.STREAM_CHUNK + payload = load_stream_chunk_payload(args.input_binary_file) # stream chunk format if args.stream_chunk_format_test: param = { - "format": format, "payload_input": payload, "table_schema": make_mock_schema_stream_chunk(), } test_stream_chunk_data_format(param) param = { - "format": format, "payload_input": payload, "table_schema": make_mock_schema(), } @@ -337,7 +322,5 @@ def test_stream_chunk_data_format(param): test_deltalake_sink(param) if args.es_sink: test_elasticsearch_sink(param) - - # json format if args.upsert_iceberg_sink: test_upsert_iceberg_sink(param) diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JsonDeserializer.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JsonDeserializer.java deleted file mode 100644 index c941b09efe95c..0000000000000 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/JsonDeserializer.java +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.connector; - -import static io.grpc.Status.INVALID_ARGUMENT; - -import com.google.gson.Gson; -import com.risingwave.connector.api.TableSchema; -import com.risingwave.connector.api.sink.*; -import com.risingwave.proto.ConnectorServiceProto; -import com.risingwave.proto.ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch.JsonPayload; -import com.risingwave.proto.Data; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.util.Base64; -import java.util.Map; -import java.util.stream.Collectors; - -public class JsonDeserializer implements Deserializer { - private final TableSchema tableSchema; - - public JsonDeserializer(TableSchema tableSchema) { - this.tableSchema = tableSchema; - } - - // Encoding here should be consistent with `datum_to_json_object()` in - // src/connector/src/sink/mod.rs - @Override - public CloseableIterable deserialize( - ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch writeBatch) { - if (!writeBatch.hasJsonPayload()) { - throw INVALID_ARGUMENT - .withDescription("expected JsonPayload, got " + writeBatch.getPayloadCase()) - .asRuntimeException(); - } - JsonPayload jsonPayload = writeBatch.getJsonPayload(); - return new TrivialCloseIterable<>( - jsonPayload.getRowOpsList().stream() - .map( - rowOp -> { - Map columnValues = - new Gson().fromJson(rowOp.getLine(), Map.class); - Object[] values = new Object[columnValues.size()]; - for (String columnName : tableSchema.getColumnNames()) { - if (!columnValues.containsKey(columnName)) { - throw INVALID_ARGUMENT - .withDescription( - "column " - + columnName - + " not found in json") - .asRuntimeException(); - } - Data.DataType.TypeName typeName = - tableSchema.getColumnType(columnName); - values[tableSchema.getColumnIndex(columnName)] = - validateJsonDataTypes( - typeName, columnValues.get(columnName)); - } - return (SinkRow) new ArraySinkRow(rowOp.getOpType(), values); - }) - .collect(Collectors.toList())); - } - - private static Long castLong(Object value) { - if (value instanceof Integer) { - return ((Integer) value).longValue(); - } else if (value instanceof Double) { - double d = (Double) value; - if (d % 1.0 != 0.0) { - - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "unable to cast into long from non-integer double value: " + d) - .asRuntimeException(); - } - return ((Double) value).longValue(); - } else if (value instanceof Long) { - return (Long) value; - } else if (value instanceof Short) { - return ((Short) value).longValue(); - } else if (value instanceof Float) { - double f = (Float) value; - if (f % 1.0 != 0.0) { - - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "unable to cast into long from non-integer float value: " + f) - .asRuntimeException(); - } - return ((Float) value).longValue(); - } else { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into long from " + value.getClass()) - .asRuntimeException(); - } - } - - private static Double castDouble(Object value) { - if (value instanceof Double) { - return (Double) value; - } else if (value instanceof Float) { - return ((Float) value).doubleValue(); - } else { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into double from " + value.getClass()) - .asRuntimeException(); - } - } - - private static BigDecimal castDecimal(Object value) { - if (value instanceof String) { - // FIXME(eric): See `datum_to_json_object()` in src/connector/src/sink/mod.rs - return new BigDecimal((String) value); - } else if (value instanceof BigDecimal) { - return (BigDecimal) value; - } else { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into double from " + value.getClass()) - .asRuntimeException(); - } - } - - private static LocalTime castTime(Object value) { - try { - Long milli = castLong(value); - return LocalTime.ofNanoOfDay(milli * 1_000_000L); - } catch (RuntimeException e) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into time from " + value.getClass()) - .asRuntimeException(); - } - } - - private static LocalDate castDate(Object value) { - try { - Long days = castLong(value); - return LocalDate.ofEpochDay(days); - } catch (RuntimeException e) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unable to cast into date from " + value.getClass()) - .asRuntimeException(); - } - } - - private static Object validateJsonDataTypes(Data.DataType.TypeName typeName, Object value) { - // value might be null - if (value == null) { - return null; - } - switch (typeName) { - case INT16: - return castLong(value).shortValue(); - case INT32: - return castLong(value).intValue(); - case INT64: - return castLong(value); - case VARCHAR: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected string, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case DOUBLE: - return castDouble(value); - case FLOAT: - return castDouble(value).floatValue(); - case DECIMAL: - return castDecimal(value); - case BOOLEAN: - if (!(value instanceof Boolean)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected boolean, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case TIMESTAMP: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "Expected timestamp in string, got " + value.getClass()) - .asRuntimeException(); - } - return LocalDateTime.parse((String) value); - case TIMESTAMPTZ: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription( - "Expected timestamptz in string, got " + value.getClass()) - .asRuntimeException(); - } - return OffsetDateTime.parse((String) value); - case TIME: - return castTime(value); - case DATE: - return castDate(value); - case INTERVAL: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected interval, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case JSONB: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected jsonb, got " + value.getClass()) - .asRuntimeException(); - } - return value; - case BYTEA: - if (!(value instanceof String)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected bytea, got " + value.getClass()) - .asRuntimeException(); - } - return Base64.getDecoder().decode((String) value); - case LIST: - if (!(value instanceof java.util.ArrayList)) { - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("Expected list, got " + value.getClass()) - .asRuntimeException(); - } - return ((java.util.ArrayList) value).toArray(); - default: - throw io.grpc.Status.INVALID_ARGUMENT - .withDescription("unsupported type " + typeName) - .asRuntimeException(); - } - } -} diff --git a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java index cd61da38d6cb5..53dfe326fbd9d 100644 --- a/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java +++ b/java/connector-node/risingwave-connector-service/src/main/java/com/risingwave/connector/SinkWriterStreamObserver.java @@ -206,19 +206,7 @@ private void bindSink(ConnectorServiceProto.SinkWriterStreamRequest.StartSink st String connectorName = getConnectorName(sinkParam); SinkFactory sinkFactory = SinkUtils.getSinkFactory(connectorName); sink = sinkFactory.createWriter(tableSchema, sinkParam.getPropertiesMap()); - switch (startSink.getFormat()) { - case FORMAT_UNSPECIFIED: - case UNRECOGNIZED: - throw INVALID_ARGUMENT - .withDescription("should specify payload format in request") - .asRuntimeException(); - case JSON: - deserializer = new JsonDeserializer(tableSchema); - break; - case STREAM_CHUNK: - deserializer = new StreamChunkDeserializer(tableSchema); - break; - } + deserializer = new StreamChunkDeserializer(tableSchema); this.connectorName = connectorName.toUpperCase(); ConnectorNodeMetrics.incActiveSinkConnections(connectorName, "node1"); } diff --git a/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties b/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties index f77dd3c1ea4f6..0c62a51986b1c 100644 --- a/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties +++ b/java/connector-node/risingwave-connector-service/src/main/resources/mysql.properties @@ -25,5 +25,6 @@ name=${hostname}:${port}:${database.name}.${table.name:-RW_CDC_Sharing} # In sharing cdc mode, transaction metadata will be enabled in frontend provide.transaction.metadata=${transactional:-false} ## Pass-through driver properties -# set connector timezone to UTC(+00:00) +# force connection session timezone to UTC(+00:00) driver.connectionTimeZone=+00:00 +driver.forceConnectionTimeZoneToSession=true diff --git a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/DeserializerTest.java b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/DeserializerTest.java deleted file mode 100644 index 9284a2ef8fd20..0000000000000 --- a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/DeserializerTest.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.connector.sink; - -import com.risingwave.connector.JsonDeserializer; -import com.risingwave.connector.TestUtils; -import com.risingwave.connector.api.sink.SinkRow; -import com.risingwave.proto.ConnectorServiceProto; -import com.risingwave.proto.ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch.JsonPayload; -import com.risingwave.proto.Data; -import junit.framework.TestCase; - -public class DeserializerTest extends TestCase { - public void testJsonDeserializer() { - JsonDeserializer deserializer = new JsonDeserializer(TestUtils.getMockTableSchema()); - JsonPayload jsonPayload = - JsonPayload.newBuilder() - .addRowOps( - JsonPayload.RowOp.newBuilder() - .setOpType(Data.Op.INSERT) - .setLine("{\"id\": 1, \"name\": \"John\"}") - .build()) - .build(); - ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch writeBatch = - ConnectorServiceProto.SinkWriterStreamRequest.WriteBatch.newBuilder() - .setJsonPayload(jsonPayload) - .build(); - SinkRow outcome = deserializer.deserialize(writeBatch).iterator().next(); - assertEquals(outcome.get(0), 1); - assertEquals(outcome.get(1), "John"); - } -} diff --git a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java index f0dcc4c1c4930..885fc7eb927a3 100644 --- a/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java +++ b/java/connector-node/risingwave-connector-test/src/test/java/com/risingwave/connector/sink/SinkStreamObserverTest.java @@ -14,10 +14,10 @@ package com.risingwave.connector.sink; +import com.google.protobuf.ByteString; import com.risingwave.connector.SinkWriterStreamObserver; import com.risingwave.connector.TestUtils; import com.risingwave.proto.ConnectorServiceProto; -import com.risingwave.proto.Data.Op; import io.grpc.stub.StreamObserver; import java.util.Map; import org.junit.Assert; @@ -94,7 +94,6 @@ public void testOnNext_syncValidation() { .setStart( ConnectorServiceProto.SinkWriterStreamRequest.StartSink.newBuilder() .setSinkParam(fileSinkParam) - .setFormat(ConnectorServiceProto.SinkPayloadFormat.JSON) .build()) .build(); ConnectorServiceProto.SinkWriterStreamRequest firstSync = @@ -138,7 +137,6 @@ public void testOnNext_startEpochValidation() { .setStart( ConnectorServiceProto.SinkWriterStreamRequest.StartSink.newBuilder() .setSinkParam(fileSinkParam) - .setFormat(ConnectorServiceProto.SinkPayloadFormat.JSON) .build()) .build(); ConnectorServiceProto.SinkWriterStreamRequest firstSync = @@ -156,6 +154,8 @@ public void testOnNext_startEpochValidation() { sinkWriterStreamObserver.onNext(firstSync); } + // WARN! This test is skipped in CI pipeline see + // `.github/workflows/connector-node-integration.yml` @Test public void testOnNext_writeValidation() { SinkWriterStreamObserver sinkWriterStreamObserver; @@ -164,10 +164,16 @@ public void testOnNext_writeValidation() { ConnectorServiceProto.SinkWriterStreamRequest.newBuilder() .setStart( ConnectorServiceProto.SinkWriterStreamRequest.StartSink.newBuilder() - .setFormat(ConnectorServiceProto.SinkPayloadFormat.JSON) .setSinkParam(fileSinkParam)) .build(); + // Encoded StreamChunk: 1 'test' + byte[] data1 = + new byte[] { + 8, 1, 18, 1, 1, 26, 20, 8, 2, 18, 6, 8, 1, 18, 2, 1, 1, 26, 8, 8, 1, 18, 4, 0, + 0, 0, 1, 26, 42, 8, 6, 18, 6, 8, 1, 18, 2, 1, 1, 26, 20, 8, 1, 18, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 26, 8, 8, 1, 18, 4, 116, 101, 115, 116 + }; ConnectorServiceProto.SinkWriterStreamRequest firstWrite = ConnectorServiceProto.SinkWriterStreamRequest.newBuilder() .setWriteBatch( @@ -175,19 +181,11 @@ public void testOnNext_writeValidation() { .newBuilder() .setEpoch(0) .setBatchId(1) - .setJsonPayload( + .setStreamChunkPayload( ConnectorServiceProto.SinkWriterStreamRequest - .WriteBatch.JsonPayload.newBuilder() - .addRowOps( - ConnectorServiceProto - .SinkWriterStreamRequest - .WriteBatch.JsonPayload - .RowOp.newBuilder() - .setOpType(Op.INSERT) - .setLine( - "{\"id\": 1, \"name\": \"test\"}") - .build())) - .build()) + .WriteBatch.StreamChunkPayload.newBuilder() + .setBinaryData(ByteString.copyFrom(data1)) + .build())) .build(); ConnectorServiceProto.SinkWriterStreamRequest firstSync = @@ -199,6 +197,13 @@ public void testOnNext_writeValidation() { .build()) .build(); + // Encoded StreamChunk: 2 'test' + byte[] data2 = + new byte[] { + 8, 1, 18, 1, 1, 26, 20, 8, 2, 18, 6, 8, 1, 18, 2, 1, 1, 26, 8, 8, 1, 18, 4, 0, + 0, 0, 2, 26, 42, 8, 6, 18, 6, 8, 1, 18, 2, 1, 1, 26, 20, 8, 1, 18, 16, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 26, 8, 8, 1, 18, 4, 116, 101, 115, 116 + }; ConnectorServiceProto.SinkWriterStreamRequest secondWrite = ConnectorServiceProto.SinkWriterStreamRequest.newBuilder() .setWriteBatch( @@ -206,19 +211,11 @@ public void testOnNext_writeValidation() { .newBuilder() .setEpoch(1) .setBatchId(2) - .setJsonPayload( + .setStreamChunkPayload( ConnectorServiceProto.SinkWriterStreamRequest - .WriteBatch.JsonPayload.newBuilder() - .addRowOps( - ConnectorServiceProto - .SinkWriterStreamRequest - .WriteBatch.JsonPayload - .RowOp.newBuilder() - .setOpType(Op.INSERT) - .setLine( - "{\"id\": 2, \"name\": \"test\"}") - .build())) - .build()) + .WriteBatch.StreamChunkPayload.newBuilder() + .setBinaryData(ByteString.copyFrom(data2)) + .build())) .build(); ConnectorServiceProto.SinkWriterStreamRequest secondWriteWrongEpoch = @@ -228,19 +225,11 @@ public void testOnNext_writeValidation() { .newBuilder() .setEpoch(2) .setBatchId(3) - .setJsonPayload( + .setStreamChunkPayload( ConnectorServiceProto.SinkWriterStreamRequest - .WriteBatch.JsonPayload.newBuilder() - .addRowOps( - ConnectorServiceProto - .SinkWriterStreamRequest - .WriteBatch.JsonPayload - .RowOp.newBuilder() - .setOpType(Op.INSERT) - .setLine( - "{\"id\": 2, \"name\": \"test\"}") - .build())) - .build()) + .WriteBatch.StreamChunkPayload.newBuilder() + .setBinaryData(ByteString.copyFrom(data2)) + .build())) .build(); boolean exceptionThrown = false; @@ -251,7 +240,10 @@ public void testOnNext_writeValidation() { sinkWriterStreamObserver.onNext(firstWrite); } catch (RuntimeException e) { exceptionThrown = true; - Assert.assertTrue(e.getMessage().toLowerCase().contains("batch id")); + if (!e.getMessage().toLowerCase().contains("batch id")) { + e.printStackTrace(); + Assert.fail("Expected `batch id`, but got " + e.getMessage()); + } } if (!exceptionThrown) { Assert.fail("Expected exception not thrown: `invalid batch id`"); @@ -267,7 +259,10 @@ public void testOnNext_writeValidation() { sinkWriterStreamObserver.onNext(secondWriteWrongEpoch); } catch (RuntimeException e) { exceptionThrown = true; - Assert.assertTrue(e.getMessage().toLowerCase().contains("invalid epoch")); + if (!e.getMessage().toLowerCase().contains("invalid epoch")) { + e.printStackTrace(); + Assert.fail("Expected `invalid epoch`, but got " + e.getMessage()); + } } if (!exceptionThrown) { Assert.fail( diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java index 4672f628c1769..d854e561878f4 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/JDBCSink.java @@ -53,9 +53,8 @@ public JDBCSink(JDBCSinkConfig config, TableSchema tableSchema) { this.config = config; try { conn = JdbcUtils.getConnection(config.getJdbcUrl()); - // Retrieve primary keys and column type mappings from the database - this.pkColumnNames = - getPkColumnNames(conn, config.getTableName(), config.getSchemaName()); + // Table schema has been validated before, so we get the PK from it directly + this.pkColumnNames = tableSchema.getPrimaryKeys(); // column name -> java.sql.Types Map columnTypeMapping = getColumnTypeMapping(conn, config.getTableName(), config.getSchemaName()); @@ -72,9 +71,10 @@ public JDBCSink(JDBCSinkConfig config, TableSchema tableSchema) { .collect(Collectors.toList()); LOG.info( - "schema = {}, table = {}, columnSqlTypes = {}, pkIndices = {}", + "schema = {}, table = {}, tableSchema = {}, columnSqlTypes = {}, pkIndices = {}", config.getSchemaName(), config.getTableName(), + tableSchema, columnSqlTypes, pkIndices); @@ -125,28 +125,6 @@ private static Map getColumnTypeMapping( return columnTypeMap; } - private static List getPkColumnNames( - Connection conn, String tableName, String schemaName) { - List pkColumnNames = new ArrayList<>(); - try { - var pks = conn.getMetaData().getPrimaryKeys(null, schemaName, tableName); - while (pks.next()) { - pkColumnNames.add(pks.getString(JDBC_COLUMN_NAME_KEY)); - } - } catch (SQLException e) { - throw Status.INTERNAL - .withDescription( - String.format(ERROR_REPORT_TEMPLATE, e.getSQLState(), e.getMessage())) - .asRuntimeException(); - } - LOG.info( - "schema = {}, table = {}: detected pk column = {}", - schemaName, - tableName, - pkColumnNames); - return pkColumnNames; - } - @Override public boolean write(Iterable rows) { final int maxRetryCount = 4; @@ -311,7 +289,7 @@ public void prepareDelete(SinkRow row) { .asRuntimeException(); } try { - jdbcDialect.bindDeleteStatement(deleteStatement, row); + jdbcDialect.bindDeleteStatement(deleteStatement, tableSchema, row); deleteStatement.addBatch(); } catch (SQLException e) { throw Status.INTERNAL @@ -362,7 +340,7 @@ private void executeStatement(PreparedStatement stmt) throws SQLException { if (stmt == null) { return; } - LOG.debug("Executing statement: {}", stmt); + LOG.info("Executing statement: {}", stmt); stmt.executeBatch(); stmt.clearParameters(); } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java index 3a091ff33f895..308f9927457a2 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/JdbcDialect.java @@ -112,5 +112,6 @@ void bindInsertIntoStatement( throws SQLException; /** Bind the values of primary key fields to the {@code DELETE} statement. */ - void bindDeleteStatement(PreparedStatement stmt, SinkRow row) throws SQLException; + void bindDeleteStatement(PreparedStatement stmt, TableSchema tableSchema, SinkRow row) + throws SQLException; } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java index b79a2f9f35094..2c4ea73448a60 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/MySqlDialect.java @@ -26,18 +26,17 @@ public class MySqlDialect implements JdbcDialect { - private final int[] columnSqlTypes; private final int[] pkIndices; private final int[] pkColumnSqlTypes; public MySqlDialect(List columnSqlTypes, List pkIndices) { - this.columnSqlTypes = columnSqlTypes.stream().mapToInt(i -> i).toArray(); + var columnSqlTypesArr = columnSqlTypes.stream().mapToInt(i -> i).toArray(); this.pkIndices = pkIndices.stream().mapToInt(i -> i).toArray(); // derive sql types for pk columns var pkColumnSqlTypes = new int[pkIndices.size()]; for (int i = 0; i < pkIndices.size(); i++) { - pkColumnSqlTypes[i] = this.columnSqlTypes[this.pkIndices[i]]; + pkColumnSqlTypes[i] = columnSqlTypesArr[this.pkIndices[i]]; } this.pkColumnSqlTypes = pkColumnSqlTypes; } @@ -118,12 +117,13 @@ public void bindInsertIntoStatement( } @Override - public void bindDeleteStatement(PreparedStatement stmt, SinkRow row) throws SQLException { + public void bindDeleteStatement(PreparedStatement stmt, TableSchema tableSchema, SinkRow row) + throws SQLException { // set the values of primary key fields int placeholderIdx = 1; - for (int idx : pkIndices) { - Object pkField = row.get(idx); - stmt.setObject(placeholderIdx++, pkField, pkColumnSqlTypes[idx]); + for (int i = 0; i < pkIndices.length; ++i) { + Object pkField = row.get(pkIndices[i]); + stmt.setObject(placeholderIdx++, pkField, pkColumnSqlTypes[i]); } } } diff --git a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java index 022a54f8c356e..6264d9c5eac18 100644 --- a/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java +++ b/java/connector-node/risingwave-sink-jdbc/src/main/java/com/risingwave/connector/jdbc/PostgresDialect.java @@ -31,18 +31,10 @@ public class PostgresDialect implements JdbcDialect { private final int[] columnSqlTypes; private final int[] pkIndices; - private final int[] pkColumnSqlTypes; public PostgresDialect(List columnSqlTypes, List pkIndices) { this.columnSqlTypes = columnSqlTypes.stream().mapToInt(i -> i).toArray(); this.pkIndices = pkIndices.stream().mapToInt(i -> i).toArray(); - - // derive sql types for pk columns - var pkColumnSqlTypes = new int[pkIndices.size()]; - for (int i = 0; i < pkIndices.size(); i++) { - pkColumnSqlTypes[i] = this.columnSqlTypes[this.pkIndices[i]]; - } - this.pkColumnSqlTypes = pkColumnSqlTypes; } private static final HashMap RW_TYPE_TO_JDBC_TYPE_NAME; @@ -166,12 +158,13 @@ public void bindInsertIntoStatement( } @Override - public void bindDeleteStatement(PreparedStatement stmt, SinkRow row) throws SQLException { + public void bindDeleteStatement(PreparedStatement stmt, TableSchema tableSchema, SinkRow row) + throws SQLException { // set the values of primary key fields int placeholderIdx = 1; - for (int idx : pkIndices) { - Object pkField = row.get(idx); - stmt.setObject(placeholderIdx++, pkField, pkColumnSqlTypes[idx]); + for (int pkIdx : pkIndices) { + Object pkField = row.get(pkIdx); + stmt.setObject(placeholderIdx++, pkField, columnSqlTypes[pkIdx]); } } } diff --git a/java/dev.md b/java/dev.md index ac20c30fe69fa..148fde173baad 100644 --- a/java/dev.md +++ b/java/dev.md @@ -56,9 +56,3 @@ Config with the following. It may work. "java.format.settings.profile": "Android" } ``` - -## Deploy UDF Library to Maven - -```sh -mvn clean deploy --pl udf --am -``` \ No newline at end of file diff --git a/java/pom.xml b/java/pom.xml index 922c62ead69e5..f1ee457ef3b84 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -37,8 +37,6 @@ proto - udf - udf-example java-binding common-utils java-binding-integration-test @@ -572,4 +570,4 @@ https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - + \ No newline at end of file diff --git a/java/udf/CHANGELOG.md b/java/udf/CHANGELOG.md deleted file mode 100644 index fb1f055783225..0000000000000 --- a/java/udf/CHANGELOG.md +++ /dev/null @@ -1,39 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.3] - 2023-12-06 - -### Fixed - -- Fix decimal type output. - -## [0.1.2] - 2023-12-04 - -### Fixed - -- Fix index-out-of-bound error when string or string list is large. -- Fix memory leak. - -## [0.1.1] - 2023-12-03 - -### Added - -- Support struct in struct and struct[] in struct. - -### Changed - -- Bump Arrow version to 14. - -### Fixed - -- Fix unconstrained decimal type. - -## [0.1.0] - 2023-09-01 - -- Initial release. \ No newline at end of file diff --git a/java/udf/README.md b/java/udf/README.md deleted file mode 100644 index 200b897b8b890..0000000000000 --- a/java/udf/README.md +++ /dev/null @@ -1,274 +0,0 @@ -# RisingWave Java UDF SDK - -This library provides a Java SDK for creating user-defined functions (UDF) in RisingWave. - -## Introduction - -RisingWave supports user-defined functions implemented as external functions. -With the RisingWave Java UDF SDK, users can define custom UDFs using Java and start a Java process as a UDF server. -RisingWave can then remotely access the UDF server to execute the defined functions. - -## Installation - -To install the RisingWave Java UDF SDK: - -```sh -git clone https://github.com/risingwavelabs/risingwave.git -cd risingwave/java/udf -mvn install -``` - -Or you can add the following dependency to your `pom.xml` file: - -```xml - - - com.risingwave - risingwave-udf - 0.1.0 - - -``` - - -## Creating a New Project - -> NOTE: You can also start from the [udf-example](../udf-example) project without creating the project from scratch. - -To create a new project using the RisingWave Java UDF SDK, follow these steps: - -```sh -mvn archetype:generate -DgroupId=com.example -DartifactId=udf-example -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false -``` - -Configure your `pom.xml` file as follows: - -```xml - - - 4.0.0 - com.example - udf-example - 1.0-SNAPSHOT - - - - com.risingwave - risingwave-udf - 0.1.0 - - - -``` - -The `--add-opens` flag must be added when running unit tests through Maven: - -```xml - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0 - - --add-opens=java.base/java.nio=ALL-UNNAMED - - - - -``` - -## Scalar Functions - -A user-defined scalar function maps zero, one, or multiple scalar values to a new scalar value. - -In order to define a scalar function, one has to create a new class that implements the `ScalarFunction` -interface in `com.risingwave.functions` and implement exactly one evaluation method named `eval(...)`. -This method must be declared public and non-static. - -Any [data type](#data-types) listed in the data types section can be used as a parameter or return type of an evaluation method. - -Here's an example of a scalar function that calculates the greatest common divisor (GCD) of two integers: - -```java -import com.risingwave.functions.ScalarFunction; - -public class Gcd implements ScalarFunction { - public int eval(int a, int b) { - while (b != 0) { - int temp = b; - b = a % b; - a = temp; - } - return a; - } -} -``` - -> **NOTE:** Differences with Flink -> 1. The `ScalarFunction` is an interface instead of an abstract class. -> 2. Multiple overloaded `eval` methods are not supported. -> 3. Variable arguments such as `eval(Integer...)` are not supported. - -## Table Functions - -A user-defined table function maps zero, one, or multiple scalar values to one or multiple -rows (structured types). - -In order to define a table function, one has to create a new class that implements the `TableFunction` -interface in `com.risingwave.functions` and implement exactly one evaluation method named `eval(...)`. -This method must be declared public and non-static. - -The return type must be an `Iterator` of any [data type](#data-types) listed in the data types section. -Similar to scalar functions, input and output data types are automatically extracted using reflection. -This includes the generic argument T of the return value for determining an output data type. - -Here's an example of a table function that generates a series of integers: - -```java -import com.risingwave.functions.TableFunction; - -public class Series implements TableFunction { - public Iterator eval(int n) { - return java.util.stream.IntStream.range(0, n).iterator(); - } -} -``` - -> **NOTE:** Differences with Flink -> 1. The `TableFunction` is an interface instead of an abstract class. It has no generic arguments. -> 2. Instead of calling `collect` to emit a row, the `eval` method returns an `Iterator` of the output rows. -> 3. Multiple overloaded `eval` methods are not supported. -> 4. Variable arguments such as `eval(Integer...)` are not supported. -> 5. In SQL, table functions can be used in the `FROM` clause directly. `JOIN LATERAL TABLE` is not supported. - -## UDF Server - -To create a UDF server and register functions: - -```java -import com.risingwave.functions.UdfServer; - -public class App { - public static void main(String[] args) { - try (var server = new UdfServer("0.0.0.0", 8815)) { - // register functions - server.addFunction("gcd", new Gcd()); - server.addFunction("series", new Series()); - // start the server - server.start(); - server.awaitTermination(); - } catch (Exception e) { - e.printStackTrace(); - } - } -} -``` - -To run the UDF server, execute the following command: - -```sh -_JAVA_OPTIONS="--add-opens=java.base/java.nio=ALL-UNNAMED" mvn exec:java -Dexec.mainClass="com.example.App" -``` - -## Creating Functions in RisingWave - -```sql -create function gcd(int, int) returns int -as gcd using link 'http://localhost:8815'; - -create function series(int) returns table (x int) -as series using link 'http://localhost:8815'; -``` - -For more detailed information and examples, please refer to the official RisingWave [documentation](https://www.risingwave.dev/docs/current/user-defined-functions/#4-declare-your-functions-in-risingwave). - -## Using Functions in RisingWave - -Once the user-defined functions are created in RisingWave, you can use them in SQL queries just like any built-in functions. Here are a few examples: - -```sql -select gcd(25, 15); - -select * from series(10); -``` - -## Data Types - -The RisingWave Java UDF SDK supports the following data types: - -| SQL Type | Java Type | Notes | -| ---------------- | --------------------------------------- | ------------------ | -| BOOLEAN | boolean, Boolean | | -| SMALLINT | short, Short | | -| INT | int, Integer | | -| BIGINT | long, Long | | -| REAL | float, Float | | -| DOUBLE PRECISION | double, Double | | -| DECIMAL | BigDecimal | | -| DATE | java.time.LocalDate | | -| TIME | java.time.LocalTime | | -| TIMESTAMP | java.time.LocalDateTime | | -| INTERVAL | com.risingwave.functions.PeriodDuration | | -| VARCHAR | String | | -| BYTEA | byte[] | | -| JSONB | String | Use `@DataTypeHint("JSONB") String` as the type. See [example](#jsonb). | -| T[] | T'[] | `T` can be any of the above SQL types. `T'` should be the corresponding Java type.| -| STRUCT<> | user-defined class | Define a data class as the type. See [example](#struct-type). | -| ...others | | Not supported yet. | - -### JSONB - -```java -import com.google.gson.Gson; - -// Returns the i-th element of a JSON array. -public class JsonbAccess implements ScalarFunction { - static Gson gson = new Gson(); - - public @DataTypeHint("JSONB") String eval(@DataTypeHint("JSONB") String json, int index) { - if (json == null) - return null; - var array = gson.fromJson(json, Object[].class); - if (index >= array.length || index < 0) - return null; - var obj = array[index]; - return gson.toJson(obj); - } -} -``` - -```sql -create function jsonb_access(jsonb, int) returns jsonb -as jsonb_access using link 'http://localhost:8815'; -``` - -### Struct Type - -```java -// Split a socket address into host and port. -public static class IpPort implements ScalarFunction { - public static class SocketAddr { - public String host; - public short port; - } - - public SocketAddr eval(String addr) { - var socketAddr = new SocketAddr(); - var parts = addr.split(":"); - socketAddr.host = parts[0]; - socketAddr.port = Short.parseShort(parts[1]); - return socketAddr; - } -} -``` - -```sql -create function ip_port(varchar) returns struct -as ip_port using link 'http://localhost:8815'; -``` - -## Full Example - -You can checkout [udf-example](../udf-example) and use it as a template to create your own UDFs. diff --git a/java/udf/pom.xml b/java/udf/pom.xml deleted file mode 100644 index f747603ca8429..0000000000000 --- a/java/udf/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - 4.0.0 - - com.risingwave - risingwave-udf - jar - 0.1.3-SNAPSHOT - - - risingwave-java-root - com.risingwave - 0.1.0-SNAPSHOT - ../pom.xml - - - RisingWave Java UDF SDK - https://docs.risingwave.com/docs/current/udf-java - - - - org.junit.jupiter - junit-jupiter-engine - 5.9.1 - test - - - org.apache.arrow - arrow-vector - 14.0.0 - - - org.apache.arrow - flight-core - 14.0.0 - - - org.slf4j - slf4j-api - 2.0.7 - - - org.slf4j - slf4j-simple - 2.0.7 - - - - - - kr.motd.maven - os-maven-plugin - 1.7.0 - - - - \ No newline at end of file diff --git a/java/udf/src/main/java/com/risingwave/functions/DataTypeHint.java b/java/udf/src/main/java/com/risingwave/functions/DataTypeHint.java deleted file mode 100644 index 7baf0fe4c6115..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/DataTypeHint.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) -public @interface DataTypeHint { - String value(); -} diff --git a/java/udf/src/main/java/com/risingwave/functions/PeriodDuration.java b/java/udf/src/main/java/com/risingwave/functions/PeriodDuration.java deleted file mode 100644 index 6d704100f6f35..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/PeriodDuration.java +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.time.Duration; -import java.time.Period; - -/** Combination of Period and Duration. */ -public class PeriodDuration extends org.apache.arrow.vector.PeriodDuration { - public PeriodDuration(Period period, Duration duration) { - super(period, duration); - } - - PeriodDuration(org.apache.arrow.vector.PeriodDuration base) { - super(base.getPeriod(), base.getDuration()); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/ScalarFunction.java b/java/udf/src/main/java/com/risingwave/functions/ScalarFunction.java deleted file mode 100644 index 5f3fcaf287330..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/ScalarFunction.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -/** - * Base interface for a user-defined scalar function. A user-defined scalar function maps zero, one, - * or multiple scalar values to a new scalar value. - * - *

The behavior of a {@link ScalarFunction} can be defined by implementing a custom evaluation - * method. An evaluation method must be declared publicly, not static, and named eval. - * Multiple overloaded methods named eval are not supported yet. - * - *

By default, input and output data types are automatically extracted using reflection. - * - *

The following examples show how to specify a scalar function: - * - *

{@code
- * // a function that accepts two INT arguments and computes a sum
- * class SumFunction implements ScalarFunction {
- *     public Integer eval(Integer a, Integer b) {
- *         return a + b;
- *     }
- * }
- *
- * // a function that returns a struct type
- * class StructFunction implements ScalarFunction {
- *     public static class KeyValue {
- *         public String key;
- *         public int value;
- *     }
- *
- *     public KeyValue eval(int a) {
- *         KeyValue kv = new KeyValue();
- *         kv.key = a.toString();
- *         kv.value = a;
- *         return kv;
- *     }
- * }
- * }
- */ -public interface ScalarFunction extends UserDefinedFunction {} diff --git a/java/udf/src/main/java/com/risingwave/functions/ScalarFunctionBatch.java b/java/udf/src/main/java/com/risingwave/functions/ScalarFunctionBatch.java deleted file mode 100644 index 5d837d3b370f9..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/ScalarFunctionBatch.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.lang.invoke.MethodHandle; -import java.util.Collections; -import java.util.Iterator; -import java.util.function.Function; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; - -/** Batch-processing wrapper over a user-defined scalar function. */ -class ScalarFunctionBatch extends UserDefinedFunctionBatch { - ScalarFunction function; - MethodHandle methodHandle; - Function[] processInputs; - - ScalarFunctionBatch(ScalarFunction function) { - this.function = function; - var method = Reflection.getEvalMethod(function); - this.methodHandle = Reflection.getMethodHandle(method); - this.inputSchema = TypeUtils.methodToInputSchema(method); - this.outputSchema = TypeUtils.methodToOutputSchema(method); - this.processInputs = TypeUtils.methodToProcessInputs(method); - } - - @Override - Iterator evalBatch(VectorSchemaRoot batch, BufferAllocator allocator) { - var row = new Object[batch.getSchema().getFields().size() + 1]; - row[0] = this.function; - var outputValues = new Object[batch.getRowCount()]; - for (int i = 0; i < batch.getRowCount(); i++) { - for (int j = 0; j < row.length - 1; j++) { - var val = batch.getVector(j).getObject(i); - row[j + 1] = this.processInputs[j].apply(val); - } - try { - outputValues[i] = this.methodHandle.invokeWithArguments(row); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - var outputVector = - TypeUtils.createVector( - this.outputSchema.getFields().get(0), allocator, outputValues); - var outputBatch = VectorSchemaRoot.of(outputVector); - return Collections.singleton(outputBatch).iterator(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/TableFunction.java b/java/udf/src/main/java/com/risingwave/functions/TableFunction.java deleted file mode 100644 index ec5b9d214553f..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/TableFunction.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -/** - * Base interface for a user-defined table function. A user-defined table function maps zero, one, - * or multiple scalar values to zero, one, or multiple rows (or structured types). If an output - * record consists of only one field, the structured record can be omitted, and a scalar value can - * be emitted that will be implicitly wrapped into a row by the runtime. - * - *

The behavior of a {@link TableFunction} can be defined by implementing a custom evaluation - * method. An evaluation method must be declared publicly, not static, and named eval. - * The return type must be an Iterator. Multiple overloaded methods named eval are not - * supported yet. - * - *

By default, input and output data types are automatically extracted using reflection. - * - *

The following examples show how to specify a table function: - * - *

{@code
- * // a function that accepts an INT arguments and emits the range from 0 to the
- * // given number.
- * class Series implements TableFunction {
- *     public Iterator eval(int n) {
- *         return java.util.stream.IntStream.range(0, n).iterator();
- *     }
- * }
- *
- * // a function that accepts an String arguments and emits the words of the
- * // given string.
- * class Split implements TableFunction {
- *     public static class Row {
- *         public String word;
- *         public int length;
- *     }
- *
- *     public Iterator eval(String str) {
- *         return Stream.of(str.split(" ")).map(s -> {
- *             Row row = new Row();
- *             row.word = s;
- *             row.length = s.length();
- *             return row;
- *         }).iterator();
- *     }
- * }
- * }
- */ -public interface TableFunction extends UserDefinedFunction {} diff --git a/java/udf/src/main/java/com/risingwave/functions/TableFunctionBatch.java b/java/udf/src/main/java/com/risingwave/functions/TableFunctionBatch.java deleted file mode 100644 index a0e0608e60210..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/TableFunctionBatch.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.lang.invoke.MethodHandle; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.function.Function; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; - -/** Batch-processing wrapper over a user-defined table function. */ -class TableFunctionBatch extends UserDefinedFunctionBatch { - TableFunction function; - MethodHandle methodHandle; - Function[] processInputs; - int chunkSize = 1024; - - TableFunctionBatch(TableFunction function) { - this.function = function; - var method = Reflection.getEvalMethod(function); - this.methodHandle = Reflection.getMethodHandle(method); - this.inputSchema = TypeUtils.methodToInputSchema(method); - this.outputSchema = TypeUtils.tableFunctionToOutputSchema(method); - this.processInputs = TypeUtils.methodToProcessInputs(method); - } - - @Override - Iterator evalBatch(VectorSchemaRoot batch, BufferAllocator allocator) { - var outputs = new ArrayList(); - var row = new Object[batch.getSchema().getFields().size() + 1]; - row[0] = this.function; - var indexes = new ArrayList(); - var values = new ArrayList(); - Runnable buildChunk = - () -> { - var fields = this.outputSchema.getFields(); - var indexVector = - TypeUtils.createVector(fields.get(0), allocator, indexes.toArray()); - var valueVector = - TypeUtils.createVector(fields.get(1), allocator, values.toArray()); - indexes.clear(); - values.clear(); - var outputBatch = VectorSchemaRoot.of(indexVector, valueVector); - outputs.add(outputBatch); - }; - for (int i = 0; i < batch.getRowCount(); i++) { - // prepare input row - for (int j = 0; j < row.length - 1; j++) { - var val = batch.getVector(j).getObject(i); - row[j + 1] = this.processInputs[j].apply(val); - } - // call function - Iterator iterator; - try { - iterator = (Iterator) this.methodHandle.invokeWithArguments(row); - } catch (Throwable e) { - throw new RuntimeException(e); - } - // push values - while (iterator.hasNext()) { - indexes.add(i); - values.add(iterator.next()); - // check if we need to flush - if (indexes.size() >= this.chunkSize) { - buildChunk.run(); - } - } - } - if (indexes.size() > 0) { - buildChunk.run(); - } - return outputs.iterator(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/TypeUtils.java b/java/udf/src/main/java/com/risingwave/functions/TypeUtils.java deleted file mode 100644 index 06c2f79858c40..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/TypeUtils.java +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.*; -import org.apache.arrow.vector.complex.ListVector; -import org.apache.arrow.vector.complex.StructVector; -import org.apache.arrow.vector.types.*; -import org.apache.arrow.vector.types.pojo.*; - -class TypeUtils { - /** Convert a string to an Arrow type. */ - static Field stringToField(String typeStr, String name) { - typeStr = typeStr.toUpperCase(); - if (typeStr.equals("BOOLEAN") || typeStr.equals("BOOL")) { - return Field.nullable(name, new ArrowType.Bool()); - } else if (typeStr.equals("SMALLINT") || typeStr.equals("INT2")) { - return Field.nullable(name, new ArrowType.Int(16, true)); - } else if (typeStr.equals("INT") || typeStr.equals("INTEGER") || typeStr.equals("INT4")) { - return Field.nullable(name, new ArrowType.Int(32, true)); - } else if (typeStr.equals("BIGINT") || typeStr.equals("INT8")) { - return Field.nullable(name, new ArrowType.Int(64, true)); - } else if (typeStr.equals("FLOAT4") || typeStr.equals("REAL")) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE)); - } else if (typeStr.equals("FLOAT8") || typeStr.equals("DOUBLE PRECISION")) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE)); - } else if (typeStr.equals("DECIMAL") || typeStr.equals("NUMERIC")) { - return Field.nullable(name, new ArrowType.LargeBinary()); - } else if (typeStr.equals("DATE")) { - return Field.nullable(name, new ArrowType.Date(DateUnit.DAY)); - } else if (typeStr.equals("TIME") || typeStr.equals("TIME WITHOUT TIME ZONE")) { - return Field.nullable(name, new ArrowType.Time(TimeUnit.MICROSECOND, 64)); - } else if (typeStr.equals("TIMESTAMP") || typeStr.equals("TIMESTAMP WITHOUT TIME ZONE")) { - return Field.nullable(name, new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); - } else if (typeStr.startsWith("INTERVAL")) { - return Field.nullable(name, new ArrowType.Interval(IntervalUnit.MONTH_DAY_NANO)); - } else if (typeStr.equals("VARCHAR")) { - return Field.nullable(name, new ArrowType.Utf8()); - } else if (typeStr.equals("JSONB")) { - return Field.nullable(name, new ArrowType.LargeUtf8()); - } else if (typeStr.equals("BYTEA")) { - return Field.nullable(name, new ArrowType.Binary()); - } else if (typeStr.endsWith("[]")) { - Field innerField = stringToField(typeStr.substring(0, typeStr.length() - 2), ""); - return new Field( - name, FieldType.nullable(new ArrowType.List()), Arrays.asList(innerField)); - } else if (typeStr.startsWith("STRUCT")) { - // extract "STRUCT" - var typeList = typeStr.substring(7, typeStr.length() - 1); - var fields = - Arrays.stream(typeList.split(",")) - .map(s -> stringToField(s.trim(), "")) - .collect(Collectors.toList()); - return new Field(name, FieldType.nullable(new ArrowType.Struct()), fields); - } else { - throw new IllegalArgumentException("Unsupported type: " + typeStr); - } - } - - /** - * Convert a Java class to an Arrow type. - * - * @param param The Java class. - * @param hint An optional DataTypeHint annotation. - * @param name The name of the field. - * @return The Arrow type. - */ - static Field classToField(Class param, DataTypeHint hint, String name) { - if (hint != null) { - return stringToField(hint.value(), name); - } else if (param == Boolean.class || param == boolean.class) { - return Field.nullable(name, new ArrowType.Bool()); - } else if (param == Short.class || param == short.class) { - return Field.nullable(name, new ArrowType.Int(16, true)); - } else if (param == Integer.class || param == int.class) { - return Field.nullable(name, new ArrowType.Int(32, true)); - } else if (param == Long.class || param == long.class) { - return Field.nullable(name, new ArrowType.Int(64, true)); - } else if (param == Float.class || param == float.class) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE)); - } else if (param == Double.class || param == double.class) { - return Field.nullable(name, new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE)); - } else if (param == BigDecimal.class) { - return Field.nullable(name, new ArrowType.LargeBinary()); - } else if (param == LocalDate.class) { - return Field.nullable(name, new ArrowType.Date(DateUnit.DAY)); - } else if (param == LocalTime.class) { - return Field.nullable(name, new ArrowType.Time(TimeUnit.MICROSECOND, 64)); - } else if (param == LocalDateTime.class) { - return Field.nullable(name, new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); - } else if (param == PeriodDuration.class) { - return Field.nullable(name, new ArrowType.Interval(IntervalUnit.MONTH_DAY_NANO)); - } else if (param == String.class) { - return Field.nullable(name, new ArrowType.Utf8()); - } else if (param == byte[].class) { - return Field.nullable(name, new ArrowType.Binary()); - } else if (param.isArray()) { - var innerField = classToField(param.getComponentType(), null, ""); - return new Field( - name, FieldType.nullable(new ArrowType.List()), Arrays.asList(innerField)); - } else { - // struct type - var fields = new ArrayList(); - for (var field : param.getDeclaredFields()) { - var subhint = field.getAnnotation(DataTypeHint.class); - fields.add(classToField(field.getType(), subhint, field.getName())); - } - return new Field(name, FieldType.nullable(new ArrowType.Struct()), fields); - // TODO: more types - // throw new IllegalArgumentException("Unsupported type: " + param); - } - } - - /** Get the input schema from a Java method. */ - static Schema methodToInputSchema(Method method) { - var fields = new ArrayList(); - for (var param : method.getParameters()) { - var hint = param.getAnnotation(DataTypeHint.class); - fields.add(classToField(param.getType(), hint, param.getName())); - } - return new Schema(fields); - } - - /** Get the output schema of a scalar function from a Java method. */ - static Schema methodToOutputSchema(Method method) { - var type = method.getReturnType(); - var hint = method.getAnnotation(DataTypeHint.class); - return new Schema(Arrays.asList(classToField(type, hint, ""))); - } - - /** Get the output schema of a table function from a Java class. */ - static Schema tableFunctionToOutputSchema(Method method) { - var hint = method.getAnnotation(DataTypeHint.class); - var type = method.getReturnType(); - if (!Iterator.class.isAssignableFrom(type)) { - throw new IllegalArgumentException("Table function must return Iterator"); - } - var typeArguments = - ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments(); - type = (Class) typeArguments[0]; - var rowIndex = Field.nullable("row_index", new ArrowType.Int(32, true)); - return new Schema(Arrays.asList(rowIndex, classToField(type, hint, ""))); - } - - /** Return functions to process input values from a Java method. */ - static Function[] methodToProcessInputs(Method method) { - var schema = methodToInputSchema(method); - var params = method.getParameters(); - @SuppressWarnings("unchecked") - Function[] funcs = new Function[schema.getFields().size()]; - for (int i = 0; i < schema.getFields().size(); i++) { - funcs[i] = processFunc(schema.getFields().get(i), params[i].getType()); - } - return funcs; - } - - /** Create an Arrow vector from an array of values. */ - static FieldVector createVector(Field field, BufferAllocator allocator, Object[] values) { - var vector = field.createVector(allocator); - fillVector(vector, values); - return vector; - } - - /** Fill an Arrow vector with an array of values. */ - static void fillVector(FieldVector fieldVector, Object[] values) { - if (fieldVector instanceof BitVector) { - var vector = (BitVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (boolean) values[i] ? 1 : 0); - } - } - } else if (fieldVector instanceof SmallIntVector) { - var vector = (SmallIntVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (short) values[i]); - } - } - } else if (fieldVector instanceof IntVector) { - var vector = (IntVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (int) values[i]); - } - } - } else if (fieldVector instanceof BigIntVector) { - var vector = (BigIntVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (long) values[i]); - } - } - } else if (fieldVector instanceof Float4Vector) { - var vector = (Float4Vector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (float) values[i]); - } - } - } else if (fieldVector instanceof Float8Vector) { - var vector = (Float8Vector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (double) values[i]); - } - } - } else if (fieldVector instanceof LargeVarBinaryVector) { - var vector = (LargeVarBinaryVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - // use `toPlainString` to avoid scientific notation - vector.set(i, ((BigDecimal) values[i]).toPlainString().getBytes()); - } - } - } else if (fieldVector instanceof DateDayVector) { - var vector = (DateDayVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (int) ((LocalDate) values[i]).toEpochDay()); - } - } - } else if (fieldVector instanceof TimeMicroVector) { - var vector = (TimeMicroVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, ((LocalTime) values[i]).toNanoOfDay() / 1000); - } - } - } else if (fieldVector instanceof TimeStampMicroVector) { - var vector = (TimeStampMicroVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, timestampToMicros((LocalDateTime) values[i])); - } - } - } else if (fieldVector instanceof IntervalMonthDayNanoVector) { - var vector = (IntervalMonthDayNanoVector) fieldVector; - vector.allocateNew(values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - var pd = (PeriodDuration) values[i]; - var months = (int) pd.getPeriod().toTotalMonths(); - var days = pd.getPeriod().getDays(); - var nanos = pd.getDuration().toNanos(); - vector.set(i, months, days, nanos); - } - } - } else if (fieldVector instanceof VarCharVector) { - var vector = (VarCharVector) fieldVector; - int totalBytes = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - totalBytes += ((String) values[i]).length(); - } - } - vector.allocateNew(totalBytes, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, ((String) values[i]).getBytes()); - } - } - } else if (fieldVector instanceof LargeVarCharVector) { - var vector = (LargeVarCharVector) fieldVector; - int totalBytes = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - totalBytes += ((String) values[i]).length(); - } - } - vector.allocateNew(totalBytes, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, ((String) values[i]).getBytes()); - } - } - } else if (fieldVector instanceof VarBinaryVector) { - var vector = (VarBinaryVector) fieldVector; - int totalBytes = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - totalBytes += ((byte[]) values[i]).length; - } - } - vector.allocateNew(totalBytes, values.length); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.set(i, (byte[]) values[i]); - } - } - } else if (fieldVector instanceof ListVector) { - var vector = (ListVector) fieldVector; - vector.allocateNew(); - // flatten the `values` - var flattenLength = 0; - for (int i = 0; i < values.length; i++) { - if (values[i] == null) { - continue; - } - var len = Array.getLength(values[i]); - vector.startNewValue(i); - vector.endValue(i, len); - flattenLength += len; - } - var flattenValues = new Object[flattenLength]; - var ii = 0; - for (var list : values) { - if (list == null) { - continue; - } - var length = Array.getLength(list); - for (int i = 0; i < length; i++) { - flattenValues[ii++] = Array.get(list, i); - } - } - // fill the inner vector - fillVector(vector.getDataVector(), flattenValues); - } else if (fieldVector instanceof StructVector) { - var vector = (StructVector) fieldVector; - vector.allocateNew(); - var lookup = MethodHandles.lookup(); - // get class of the first non-null value - Class valueClass = null; - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - valueClass = values[i].getClass(); - break; - } - } - for (var field : vector.getField().getChildren()) { - // extract field from values - var subvalues = new Object[values.length]; - if (valueClass != null) { - try { - var javaField = valueClass.getDeclaredField(field.getName()); - var varHandle = lookup.unreflectVarHandle(javaField); - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - subvalues[i] = varHandle.get(values[i]); - } - } - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - var subvector = vector.getChild(field.getName()); - fillVector(subvector, subvalues); - } - for (int i = 0; i < values.length; i++) { - if (values[i] != null) { - vector.setIndexDefined(i); - } - } - } else { - throw new IllegalArgumentException("Unsupported type: " + fieldVector.getClass()); - } - fieldVector.setValueCount(values.length); - } - - static long timestampToMicros(LocalDateTime timestamp) { - var date = timestamp.toLocalDate().toEpochDay(); - var time = timestamp.toLocalTime().toNanoOfDay(); - return date * 24 * 3600 * 1000 * 1000 + time / 1000; - } - - /** Return a function that converts the object get from input array to the correct type. */ - static Function processFunc(Field field, Class targetClass) { - var inner = processFunc0(field, targetClass); - return obj -> obj == null ? null : inner.apply(obj); - } - - static Function processFunc0(Field field, Class targetClass) { - if (field.getType() instanceof ArrowType.Utf8 && targetClass == String.class) { - // object is org.apache.arrow.vector.util.Text - return obj -> obj.toString(); - } else if (field.getType() instanceof ArrowType.LargeUtf8 && targetClass == String.class) { - // object is org.apache.arrow.vector.util.Text - return obj -> obj.toString(); - } else if (field.getType() instanceof ArrowType.LargeBinary - && targetClass == BigDecimal.class) { - // object is byte[] - return obj -> new BigDecimal(new String((byte[]) obj)); - } else if (field.getType() instanceof ArrowType.Date && targetClass == LocalDate.class) { - // object is Integer - return obj -> LocalDate.ofEpochDay((int) obj); - } else if (field.getType() instanceof ArrowType.Time && targetClass == LocalTime.class) { - // object is Long - return obj -> LocalTime.ofNanoOfDay((long) obj * 1000); - } else if (field.getType() instanceof ArrowType.Interval - && targetClass == PeriodDuration.class) { - // object is arrow PeriodDuration - return obj -> new PeriodDuration((org.apache.arrow.vector.PeriodDuration) obj); - } else if (field.getType() instanceof ArrowType.List) { - // object is List - var subfield = field.getChildren().get(0); - var subfunc = processFunc(subfield, targetClass.getComponentType()); - if (subfield.getType() instanceof ArrowType.Bool) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Boolean[]::new); - } else if (subfield.getType().equals(new ArrowType.Int(16, true))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Short[]::new); - } else if (subfield.getType().equals(new ArrowType.Int(32, true))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Integer[]::new); - } else if (subfield.getType().equals(new ArrowType.Int(64, true))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Long[]::new); - } else if (subfield.getType() - .equals(new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Float[]::new); - } else if (subfield.getType() - .equals(new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE))) { - return obj -> ((List) obj).stream().map(subfunc).toArray(Double[]::new); - } else if (subfield.getType() instanceof ArrowType.LargeBinary) { - return obj -> ((List) obj).stream().map(subfunc).toArray(BigDecimal[]::new); - } else if (subfield.getType() instanceof ArrowType.Date) { - return obj -> ((List) obj).stream().map(subfunc).toArray(LocalDate[]::new); - } else if (subfield.getType() instanceof ArrowType.Time) { - return obj -> ((List) obj).stream().map(subfunc).toArray(LocalTime[]::new); - } else if (subfield.getType() instanceof ArrowType.Timestamp) { - return obj -> ((List) obj).stream().map(subfunc).toArray(LocalDateTime[]::new); - } else if (subfield.getType() instanceof ArrowType.Interval) { - return obj -> ((List) obj).stream().map(subfunc).toArray(PeriodDuration[]::new); - } else if (subfield.getType() instanceof ArrowType.Utf8) { - return obj -> ((List) obj).stream().map(subfunc).toArray(String[]::new); - } else if (subfield.getType() instanceof ArrowType.LargeUtf8) { - return obj -> ((List) obj).stream().map(subfunc).toArray(String[]::new); - } else if (subfield.getType() instanceof ArrowType.Binary) { - return obj -> ((List) obj).stream().map(subfunc).toArray(byte[][]::new); - } else if (subfield.getType() instanceof ArrowType.Struct) { - return obj -> { - var list = (List) obj; - Object array = Array.newInstance(targetClass.getComponentType(), list.size()); - for (int i = 0; i < list.size(); i++) { - Array.set(array, i, subfunc.apply(list.get(i))); - } - return array; - }; - } - throw new IllegalArgumentException("Unsupported type: " + subfield.getType()); - } else if (field.getType() instanceof ArrowType.Struct) { - // object is org.apache.arrow.vector.util.JsonStringHashMap - var subfields = field.getChildren(); - @SuppressWarnings("unchecked") - Function[] subfunc = new Function[subfields.size()]; - for (int i = 0; i < subfields.size(); i++) { - subfunc[i] = processFunc(subfields.get(i), targetClass.getFields()[i].getType()); - } - return obj -> { - var map = (AbstractMap) obj; - try { - var row = targetClass.getDeclaredConstructor().newInstance(); - for (int i = 0; i < subfields.size(); i++) { - var field0 = targetClass.getFields()[i]; - var val = subfunc[i].apply(map.get(field0.getName())); - field0.set(row, val); - } - return row; - } catch (InstantiationException - | IllegalAccessException - | InvocationTargetException - | NoSuchMethodException e) { - throw new RuntimeException(e); - } - }; - } - return Function.identity(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/UdfProducer.java b/java/udf/src/main/java/com/risingwave/functions/UdfProducer.java deleted file mode 100644 index 692d898acaf8a..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UdfProducer.java +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import org.apache.arrow.flight.*; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorLoader; -import org.apache.arrow.vector.VectorSchemaRoot; -import org.apache.arrow.vector.VectorUnloader; -import org.apache.arrow.vector.types.pojo.Field; -import org.apache.arrow.vector.types.pojo.Schema; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class UdfProducer extends NoOpFlightProducer { - - private BufferAllocator allocator; - private HashMap functions = new HashMap<>(); - private static final Logger logger = LoggerFactory.getLogger(UdfServer.class); - - UdfProducer(BufferAllocator allocator) { - this.allocator = allocator; - } - - void addFunction(String name, UserDefinedFunction function) throws IllegalArgumentException { - UserDefinedFunctionBatch udf; - if (function instanceof ScalarFunction) { - udf = new ScalarFunctionBatch((ScalarFunction) function); - } else if (function instanceof TableFunction) { - udf = new TableFunctionBatch((TableFunction) function); - } else { - throw new IllegalArgumentException( - "Unknown function type: " + function.getClass().getName()); - } - if (functions.containsKey(name)) { - throw new IllegalArgumentException("Function already exists: " + name); - } - functions.put(name, udf); - } - - @Override - public FlightInfo getFlightInfo(CallContext context, FlightDescriptor descriptor) { - try { - var functionName = descriptor.getPath().get(0); - var udf = functions.get(functionName); - if (udf == null) { - throw new IllegalArgumentException("Unknown function: " + functionName); - } - var fields = new ArrayList(); - fields.addAll(udf.getInputSchema().getFields()); - fields.addAll(udf.getOutputSchema().getFields()); - var fullSchema = new Schema(fields); - var inputLen = udf.getInputSchema().getFields().size(); - - return new FlightInfo(fullSchema, descriptor, Collections.emptyList(), 0, inputLen); - } catch (Exception e) { - logger.error("Error occurred during getFlightInfo", e); - throw e; - } - } - - @Override - public void doExchange(CallContext context, FlightStream reader, ServerStreamListener writer) { - try (var allocator = this.allocator.newChildAllocator("exchange", 0, Long.MAX_VALUE)) { - var functionName = reader.getDescriptor().getPath().get(0); - logger.debug("call function: " + functionName); - - var udf = this.functions.get(functionName); - try (var root = VectorSchemaRoot.create(udf.getOutputSchema(), allocator)) { - var loader = new VectorLoader(root); - writer.start(root); - while (reader.next()) { - try (var input = reader.getRoot()) { - var outputBatches = udf.evalBatch(input, allocator); - while (outputBatches.hasNext()) { - try (var outputRoot = outputBatches.next()) { - var unloader = new VectorUnloader(outputRoot); - try (var outputBatch = unloader.getRecordBatch()) { - loader.load(outputBatch); - } - } - writer.putNext(); - } - } - } - writer.completed(); - } - } catch (Exception e) { - logger.error("Error occurred during UDF execution", e); - writer.error(e); - } - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/UdfServer.java b/java/udf/src/main/java/com/risingwave/functions/UdfServer.java deleted file mode 100644 index 66f2a8d3bb0dd..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UdfServer.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.io.IOException; -import org.apache.arrow.flight.*; -import org.apache.arrow.memory.RootAllocator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** A server that exposes user-defined functions over Apache Arrow Flight. */ -public class UdfServer implements AutoCloseable { - - private FlightServer server; - private UdfProducer producer; - private static final Logger logger = LoggerFactory.getLogger(UdfServer.class); - - public UdfServer(String host, int port) { - var location = Location.forGrpcInsecure(host, port); - var allocator = new RootAllocator(); - this.producer = new UdfProducer(allocator); - this.server = FlightServer.builder(allocator, location, this.producer).build(); - } - - /** - * Add a user-defined function to the server. - * - * @param name the name of the function - * @param udf the function to add - * @throws IllegalArgumentException if a function with the same name already exists - */ - public void addFunction(String name, UserDefinedFunction udf) throws IllegalArgumentException { - logger.info("added function: " + name); - this.producer.addFunction(name, udf); - } - - /** - * Start the server. - * - * @throws IOException if the server fails to start - */ - public void start() throws IOException { - this.server.start(); - logger.info("listening on " + this.server.getLocation().toSocketAddress()); - } - - /** - * Get the port the server is listening on. - * - * @return the port number - */ - public int getPort() { - return this.server.getPort(); - } - - /** - * Wait for the server to terminate. - * - * @throws InterruptedException if the thread is interrupted while waiting - */ - public void awaitTermination() throws InterruptedException { - this.server.awaitTermination(); - } - - /** Close the server. */ - public void close() throws InterruptedException { - this.server.close(); - } -} diff --git a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunction.java b/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunction.java deleted file mode 100644 index 3db6f1714cd83..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunction.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -/** - * Base interface for all user-defined functions. - * - * @see ScalarFunction - * @see TableFunction - */ -public interface UserDefinedFunction {} diff --git a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunctionBatch.java b/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunctionBatch.java deleted file mode 100644 index e2c513a7954ad..0000000000000 --- a/java/udf/src/main/java/com/risingwave/functions/UserDefinedFunctionBatch.java +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Iterator; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; -import org.apache.arrow.vector.types.pojo.Schema; - -/** Base class for a batch-processing user-defined function. */ -abstract class UserDefinedFunctionBatch { - protected Schema inputSchema; - protected Schema outputSchema; - - /** Get the input schema of the function. */ - Schema getInputSchema() { - return inputSchema; - } - - /** Get the output schema of the function. */ - Schema getOutputSchema() { - return outputSchema; - } - - /** - * Evaluate the function by processing a batch of input data. - * - * @param batch the input data batch to process - * @param allocator the allocator to use for allocating output data - * @return an iterator over the output data batches - */ - abstract Iterator evalBatch( - VectorSchemaRoot batch, BufferAllocator allocator); -} - -/** Utility class for reflection. */ -class Reflection { - /** Get the method named eval. */ - static Method getEvalMethod(UserDefinedFunction obj) { - var methods = new ArrayList(); - for (Method method : obj.getClass().getDeclaredMethods()) { - if (method.getName().equals("eval")) { - methods.add(method); - } - } - if (methods.size() != 1) { - throw new IllegalArgumentException( - "Exactly one eval method must be defined for class " - + obj.getClass().getName()); - } - var method = methods.get(0); - if (Modifier.isStatic(method.getModifiers())) { - throw new IllegalArgumentException( - "The eval method should not be static for class " + obj.getClass().getName()); - } - return method; - } - - /** Get the method handle of the given method. */ - static MethodHandle getMethodHandle(Method method) { - var lookup = MethodHandles.lookup(); - try { - return lookup.unreflect(method); - } catch (IllegalAccessException e) { - throw new IllegalArgumentException( - "The eval method must be public for class " - + method.getDeclaringClass().getName()); - } - } -} diff --git a/java/udf/src/test/java/com/risingwave/functions/TestUdfServer.java b/java/udf/src/test/java/com/risingwave/functions/TestUdfServer.java deleted file mode 100644 index 5722efa1dd702..0000000000000 --- a/java/udf/src/test/java/com/risingwave/functions/TestUdfServer.java +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.IOException; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Iterator; -import java.util.stream.IntStream; -import org.apache.arrow.memory.BufferAllocator; -import org.apache.arrow.memory.RootAllocator; -import org.apache.arrow.vector.*; -import org.apache.arrow.vector.complex.StructVector; -import org.apache.arrow.vector.types.Types.MinorType; -import org.apache.arrow.vector.types.pojo.ArrowType; -import org.apache.arrow.vector.types.pojo.FieldType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -/** Unit test for UDF server. */ -public class TestUdfServer { - private static UdfClient client; - private static UdfServer server; - private static BufferAllocator allocator = new RootAllocator(); - - @BeforeAll - public static void setup() throws IOException { - server = new UdfServer("localhost", 0); - server.addFunction("gcd", new Gcd()); - server.addFunction("return_all", new ReturnAll()); - server.addFunction("series", new Series()); - server.start(); - - client = new UdfClient("localhost", server.getPort()); - } - - @AfterAll - public static void teardown() throws InterruptedException { - client.close(); - server.close(); - } - - public static class Gcd implements ScalarFunction { - public int eval(int a, int b) { - while (b != 0) { - int temp = b; - b = a % b; - a = temp; - } - return a; - } - } - - @Test - public void gcd() throws Exception { - var c0 = new IntVector("", allocator); - c0.allocateNew(1); - c0.set(0, 15); - c0.setValueCount(1); - - var c1 = new IntVector("", allocator); - c1.allocateNew(1); - c1.set(0, 12); - c1.setValueCount(1); - - var input = VectorSchemaRoot.of(c0, c1); - - try (var stream = client.call("gcd", input)) { - var output = stream.getRoot(); - assertTrue(stream.next()); - assertEquals("3", output.contentToTSVString().trim()); - } - } - - public static class ReturnAll implements ScalarFunction { - public static class Row { - public Boolean bool; - public Short i16; - public Integer i32; - public Long i64; - public Float f32; - public Double f64; - public BigDecimal decimal; - public LocalDate date; - public LocalTime time; - public LocalDateTime timestamp; - public PeriodDuration interval; - public String str; - public byte[] bytes; - public @DataTypeHint("JSONB") String jsonb; - public Struct struct; - } - - public static class Struct { - public Integer f1; - public Integer f2; - - public String toString() { - return String.format("(%d, %d)", f1, f2); - } - } - - public Row eval( - Boolean bool, - Short i16, - Integer i32, - Long i64, - Float f32, - Double f64, - BigDecimal decimal, - LocalDate date, - LocalTime time, - LocalDateTime timestamp, - PeriodDuration interval, - String str, - byte[] bytes, - @DataTypeHint("JSONB") String jsonb, - Struct struct) { - var row = new Row(); - row.bool = bool; - row.i16 = i16; - row.i32 = i32; - row.i64 = i64; - row.f32 = f32; - row.f64 = f64; - row.decimal = decimal; - row.date = date; - row.time = time; - row.timestamp = timestamp; - row.interval = interval; - row.str = str; - row.bytes = bytes; - row.jsonb = jsonb; - row.struct = struct; - return row; - } - } - - @Test - public void all_types() throws Exception { - var c0 = new BitVector("", allocator); - c0.allocateNew(2); - c0.set(0, 1); - c0.setValueCount(2); - - var c1 = new SmallIntVector("", allocator); - c1.allocateNew(2); - c1.set(0, 1); - c1.setValueCount(2); - - var c2 = new IntVector("", allocator); - c2.allocateNew(2); - c2.set(0, 1); - c2.setValueCount(2); - - var c3 = new BigIntVector("", allocator); - c3.allocateNew(2); - c3.set(0, 1); - c3.setValueCount(2); - - var c4 = new Float4Vector("", allocator); - c4.allocateNew(2); - c4.set(0, 1); - c4.setValueCount(2); - - var c5 = new Float8Vector("", allocator); - c5.allocateNew(2); - c5.set(0, 1); - c5.setValueCount(2); - - var c6 = new LargeVarBinaryVector("", allocator); - c6.allocateNew(2); - c6.set(0, "1.234".getBytes()); - c6.setValueCount(2); - - var c7 = new DateDayVector("", allocator); - c7.allocateNew(2); - c7.set(0, (int) LocalDate.of(2023, 1, 1).toEpochDay()); - c7.setValueCount(2); - - var c8 = new TimeMicroVector("", allocator); - c8.allocateNew(2); - c8.set(0, LocalTime.of(1, 2, 3).toNanoOfDay() / 1000); - c8.setValueCount(2); - - var c9 = new TimeStampMicroVector("", allocator); - c9.allocateNew(2); - var ts = LocalDateTime.of(2023, 1, 1, 1, 2, 3); - c9.set( - 0, - ts.toLocalDate().toEpochDay() * 24 * 3600 * 1000000 - + ts.toLocalTime().toNanoOfDay() / 1000); - c9.setValueCount(2); - - var c10 = - new IntervalMonthDayNanoVector( - "", - FieldType.nullable(MinorType.INTERVALMONTHDAYNANO.getType()), - allocator); - c10.allocateNew(2); - c10.set(0, 1000, 2000, 3000); - c10.setValueCount(2); - - var c11 = new VarCharVector("", allocator); - c11.allocateNew(2); - c11.set(0, "string".getBytes()); - c11.setValueCount(2); - - var c12 = new VarBinaryVector("", allocator); - c12.allocateNew(2); - c12.set(0, "bytes".getBytes()); - c12.setValueCount(2); - - var c13 = new LargeVarCharVector("", allocator); - c13.allocateNew(2); - c13.set(0, "{ key: 1 }".getBytes()); - c13.setValueCount(2); - - var c14 = - new StructVector( - "", allocator, FieldType.nullable(ArrowType.Struct.INSTANCE), null); - c14.allocateNew(); - var f1 = c14.addOrGet("f1", FieldType.nullable(MinorType.INT.getType()), IntVector.class); - var f2 = c14.addOrGet("f2", FieldType.nullable(MinorType.INT.getType()), IntVector.class); - f1.allocateNew(2); - f2.allocateNew(2); - f1.set(0, 1); - f2.set(0, 2); - c14.setIndexDefined(0); - c14.setValueCount(2); - - var input = - VectorSchemaRoot.of( - c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14); - - try (var stream = client.call("return_all", input)) { - var output = stream.getRoot(); - assertTrue(stream.next()); - assertEquals( - "{\"bool\":true,\"i16\":1,\"i32\":1,\"i64\":1,\"f32\":1.0,\"f64\":1.0,\"decimal\":\"MS4yMzQ=\",\"date\":19358,\"time\":3723000000,\"timestamp\":[2023,1,1,1,2,3],\"interval\":{\"period\":\"P1000M2000D\",\"duration\":0.000003000},\"str\":\"string\",\"bytes\":\"Ynl0ZXM=\",\"jsonb\":\"{ key: 1 }\",\"struct\":{\"f1\":1,\"f2\":2}}\n{}", - output.contentToTSVString().trim()); - } - } - - public static class Series implements TableFunction { - public Iterator eval(int n) { - return IntStream.range(0, n).iterator(); - } - } - - @Test - public void series() throws Exception { - var c0 = new IntVector("", allocator); - c0.allocateNew(3); - c0.set(0, 0); - c0.set(1, 1); - c0.set(2, 2); - c0.setValueCount(3); - - var input = VectorSchemaRoot.of(c0); - - try (var stream = client.call("series", input)) { - var output = stream.getRoot(); - assertTrue(stream.next()); - assertEquals("row_index\t\n1\t0\n2\t0\n2\t1\n", output.contentToTSVString()); - } - } -} diff --git a/java/udf/src/test/java/com/risingwave/functions/UdfClient.java b/java/udf/src/test/java/com/risingwave/functions/UdfClient.java deleted file mode 100644 index 12728bf64fbec..0000000000000 --- a/java/udf/src/test/java/com/risingwave/functions/UdfClient.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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 com.risingwave.functions; - -import org.apache.arrow.flight.*; -import org.apache.arrow.memory.RootAllocator; -import org.apache.arrow.vector.VectorSchemaRoot; - -public class UdfClient implements AutoCloseable { - - private FlightClient client; - - public UdfClient(String host, int port) { - var allocator = new RootAllocator(); - var location = Location.forGrpcInsecure(host, port); - this.client = FlightClient.builder(allocator, location).build(); - } - - public void close() throws InterruptedException { - this.client.close(); - } - - public FlightInfo getFlightInfo(String functionName) { - var descriptor = FlightDescriptor.command(functionName.getBytes()); - return client.getInfo(descriptor); - } - - public FlightStream call(String functionName, VectorSchemaRoot root) { - var descriptor = FlightDescriptor.path(functionName); - var readerWriter = client.doExchange(descriptor); - var writer = readerWriter.getWriter(); - var reader = readerWriter.getReader(); - - writer.start(root); - writer.putNext(); - writer.completed(); - return reader; - } -} diff --git a/lints/Cargo.lock b/lints/Cargo.lock index b8fd465fecd7b..3ffb99c6c425e 100644 --- a/lints/Cargo.lock +++ b/lints/Cargo.lock @@ -20,6 +20,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -40,9 +89,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "block-buffer" @@ -113,8 +162,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clippy_config" -version = "0.1.77" -source = "git+https://github.com/rust-lang/rust-clippy?rev=6fd0258e45105161b7e759a22e7350958e5cb0b1#6fd0258e45105161b7e759a22e7350958e5cb0b1" +version = "0.1.79" +source = "git+https://github.com/rust-lang/rust-clippy?rev=9d6f41691ed9dbfaec2a2df2661c42451f2fe0d3#9d6f41691ed9dbfaec2a2df2661c42451f2fe0d3" dependencies = [ "rustc-semver", "serde", @@ -123,15 +172,21 @@ dependencies = [ [[package]] name = "clippy_utils" -version = "0.1.77" -source = "git+https://github.com/rust-lang/rust-clippy?rev=6fd0258e45105161b7e759a22e7350958e5cb0b1#6fd0258e45105161b7e759a22e7350958e5cb0b1" +version = "0.1.79" +source = "git+https://github.com/rust-lang/rust-clippy?rev=9d6f41691ed9dbfaec2a2df2661c42451f2fe0d3#9d6f41691ed9dbfaec2a2df2661c42451f2fe0d3" dependencies = [ "arrayvec", "clippy_config", - "itertools 0.11.0", + "itertools", "rustc-semver", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "compiletest_rs" version = "0.10.2" @@ -233,9 +288,9 @@ dependencies = [ [[package]] name = "dylint" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fdb7b800ab13925402f0048ed0911068db2e5ba6168dd93962269d4f39541d" +checksum = "4c6720f18fdd779ad137ab6bc448c11042b1b32eea625ea618c8b953660bba56" dependencies = [ "ansi_term", "anyhow", @@ -254,12 +309,13 @@ dependencies = [ [[package]] name = "dylint_internal" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5154dada2bee2a69f75f54eae57479f56f93ca1db80725a1d82cdb5fe231ef73" +checksum = "395dade88bc1a3103ef91b442498943d0072df869736a7c0107a5753c05d006f" dependencies = [ "ansi_term", "anyhow", + "bitflags 2.5.0", "cargo_metadata", "git2", "home", @@ -267,15 +323,18 @@ dependencies = [ "is-terminal", "log", "once_cell", + "regex", "rust-embed", - "sedregex", + "serde", + "thiserror", + "toml 0.8.8", ] [[package]] name = "dylint_linting" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d203baeb8770847314632f652e0e62dd7fec6a21102a116472eec0d6931f5dd9" +checksum = "3d070f934310ccf8f04a940affcce0cd196c1068b6d19c5ae6d975f926968b66" dependencies = [ "cargo_metadata", "dylint_internal", @@ -288,9 +347,9 @@ dependencies = [ [[package]] name = "dylint_testing" -version = "2.6.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1208b1f2c40fc2f3c3fa0d5631efbc7a95721d619410dc2da5b0496810d6a941" +checksum = "7fc8a64c781bde7f1e445dc1205754994012e15a2a6e659ab7166b380c1c22ab" dependencies = [ "anyhow", "cargo_metadata", @@ -310,17 +369,27 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" -version = "0.10.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ + "anstream", + "anstyle", + "env_filter", "humantime", - "is-terminal", "log", - "regex", - "termcolor", ] [[package]] @@ -402,7 +471,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", "libgit2-sys", "log", @@ -489,13 +558,10 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.11.0" +name = "is_terminal_polyfill" +version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itertools" @@ -529,9 +595,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libgit2-sys" @@ -553,7 +619,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "libc", "redox_syscall 0.4.1", ] @@ -592,7 +658,7 @@ dependencies = [ "clippy_utils", "dylint_linting", "dylint_testing", - "itertools 0.12.0", + "itertools", "thiserror-ext", "tracing", ] @@ -605,9 +671,9 @@ checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" @@ -766,9 +832,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rust-embed" -version = "8.2.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" +checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -777,9 +843,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.2.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" +checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" dependencies = [ "proc-macro2", "quote", @@ -790,9 +856,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.2.0" +version = "8.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" +checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" dependencies = [ "globset", "sha2", @@ -819,11 +885,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -851,15 +917,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "sedregex" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19411e23596093f03bbd11dc45603b6329bb4bfec77b9fd13e2b9fc9b02efe3e" -dependencies = [ - "regex", -] - [[package]] name = "semver" version = "1.0.20" @@ -933,13 +990,12 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", "windows-sys 0.52.0", ] @@ -955,15 +1011,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" -dependencies = [ - "winapi-util", -] - [[package]] name = "tester" version = "0.9.1" @@ -1168,6 +1215,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1182,9 +1235,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", diff --git a/lints/Cargo.toml b/lints/Cargo.toml index 14bd09faca184..2581c1d3f0483 100644 --- a/lints/Cargo.toml +++ b/lints/Cargo.toml @@ -14,12 +14,12 @@ path = "ui/format_error.rs" # See `README.md` before bumping the version. # Remember to update the version in `ci/Dockerfile` as well. [dependencies] -clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "6fd0258e45105161b7e759a22e7350958e5cb0b1" } -dylint_linting = "2.6.0" +clippy_utils = { git = "https://github.com/rust-lang/rust-clippy", rev = "9d6f41691ed9dbfaec2a2df2661c42451f2fe0d3" } +dylint_linting = "3.1.0" itertools = "0.12" [dev-dependencies] -dylint_testing = "2.6.0" +dylint_testing = "3.1.0" # UI test dependencies anyhow = "1" diff --git a/lints/rust-toolchain b/lints/rust-toolchain index ea1f0e928e5c3..e79f69d40f86e 100644 --- a/lints/rust-toolchain +++ b/lints/rust-toolchain @@ -1,5 +1,5 @@ # See `README.md` before bumping the version. [toolchain] -channel = "nightly-2024-01-11" +channel = "nightly-2024-03-21" components = ["llvm-tools-preview", "rustc-dev"] diff --git a/proto/connector_service.proto b/proto/connector_service.proto index 4deb0d6fb6096..da2c2b88087ea 100644 --- a/proto/connector_service.proto +++ b/proto/connector_service.proto @@ -4,7 +4,6 @@ package connector_service; import "catalog.proto"; import "common.proto"; -import "data.proto"; import "plan_common.proto"; option java_outer_classname = "ConnectorServiceProto"; @@ -30,34 +29,21 @@ message SinkParam { string sink_name = 8; } -enum SinkPayloadFormat { - FORMAT_UNSPECIFIED = 0; - JSON = 1; - STREAM_CHUNK = 2; -} - message SinkWriterStreamRequest { message StartSink { SinkParam sink_param = 1; - SinkPayloadFormat format = 2; + // Deprecated: SinkPayloadFormat format = 2; + reserved "format"; + reserved 2; TableSchema payload_schema = 3; } message WriteBatch { - message JsonPayload { - message RowOp { - data.Op op_type = 1; - string line = 2; - } - repeated RowOp row_ops = 1; - } - message StreamChunkPayload { bytes binary_data = 1; } oneof payload { - JsonPayload json_payload = 1; StreamChunkPayload stream_chunk_payload = 2; // This is a reference pointer to a StreamChunk. The StreamChunk is owned // by the JniSinkWriterStreamRequest, which should handle the release of StreamChunk. @@ -65,6 +51,10 @@ message SinkWriterStreamRequest { int64 stream_chunk_ref_pointer = 5; } + // Deprecated in oneof payload: JsonPayload json_payload = 1; + reserved "json_payload"; + reserved 1; + uint64 batch_id = 3; uint64 epoch = 4; } diff --git a/proto/expr.proto b/proto/expr.proto index c8a9887c1663e..1ba1f052eb9c4 100644 --- a/proto/expr.proto +++ b/proto/expr.proto @@ -299,6 +299,9 @@ message ExprNode { PG_INDEXES_SIZE = 2404; PG_RELATION_SIZE = 2405; PG_GET_SERIAL_SEQUENCE = 2406; + HAS_TABLE_PRIVILEGE = 2407; + HAS_ANY_COLUMN_PRIVILEGE = 2408; + HAS_SCHEMA_PRIVILEGE = 2409; // EXTERNAL ICEBERG_TRANSFORM = 2201; diff --git a/proto/hummock.proto b/proto/hummock.proto index faf9ee4f375e0..8d68ec168ef21 100644 --- a/proto/hummock.proto +++ b/proto/hummock.proto @@ -889,7 +889,7 @@ message WriteLimits { message BranchedObject { uint64 object_id = 1; - uint64 sst_id = 2; + repeated uint64 sst_id = 2; // Compaction group id the SST belongs to. uint64 compaction_group_id = 3; } diff --git a/proto/user.proto b/proto/user.proto index 383e78cb57b28..6218fea4fd98d 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -66,7 +66,7 @@ message GrantPrivilege { uint32 all_tables_schema_id = 11; uint32 all_sources_schema_id = 12; - uint32 all_dml_tables_schema_id = 13; + uint32 all_dml_relations_schema_id = 13; uint32 subscription_id = 14; } repeated ActionWithGrantOption action_with_opts = 7; diff --git a/risedev.yml b/risedev.yml index c9b56c19c394b..646ebb551bf6f 100644 --- a/risedev.yml +++ b/risedev.yml @@ -105,6 +105,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -119,6 +120,7 @@ profile: - use: etcd - use: meta-node user-managed: true + meta-backend: etcd - use: compute-node user-managed: true - use: frontend @@ -136,6 +138,7 @@ profile: - use: etcd - use: meta-node user-managed: true + meta-backend: etcd - use: compute-node user-managed: true - use: frontend @@ -149,6 +152,7 @@ profile: - use: etcd - use: meta-node user-managed: true + meta-backend: etcd - use: compute-node user-managed: true - use: frontend @@ -253,6 +257,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -286,6 +291,7 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: etcd - use: meta-node port: 15690 dashboard-port: 15691 @@ -333,6 +339,7 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: etcd - use: meta-node port: 15690 dashboard-port: 15691 @@ -353,6 +360,7 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: sqlite - use: compactor - use: compute-node - use: frontend @@ -366,6 +374,40 @@ profile: port: 5690 dashboard-port: 5691 exporter-port: 1250 + meta-backend: sqlite + - use: compactor + - use: compute-node + - use: frontend + + meta-1cn-1fe-pg-backend: + steps: + - use: minio + - use: postgres + port: 8432 + user: postgres + database: metadata + - use: meta-node + port: 5690 + dashboard-port: 5691 + exporter-port: 1250 + meta-backend: postgres + - use: compactor + - use: compute-node + - use: frontend + + meta-1cn-1fe-pg-backend-with-recovery: + config-path: src/config/ci-recovery.toml + steps: + - use: minio + - use: postgres + port: 8432 + user: postgres + database: metadata + - use: meta-node + port: 5690 + dashboard-port: 5691 + exporter-port: 1250 + meta-backend: postgres - use: compactor - use: compute-node - use: frontend @@ -392,6 +434,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node parallelism: 8 - use: frontend @@ -550,6 +593,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -562,6 +606,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -584,6 +629,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -610,6 +656,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -634,6 +681,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -658,6 +706,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -717,6 +766,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: opendal engine: fs bucket: "/tmp/rw_ci" @@ -750,6 +800,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -794,19 +845,18 @@ profile: health-check-port: 6788 - use: compactor - ci-pubsub-kafka: + ci-kafka: config-path: src/config/ci.toml steps: - use: minio - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend - use: compactor - - use: pubsub - persist-data: true - use: kafka user-managed: true address: message_queue @@ -819,6 +869,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -843,6 +894,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -856,6 +908,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true total-memory-bytes: 17179869184 @@ -868,6 +921,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -880,6 +934,7 @@ profile: - use: etcd unsafe-no-fsync: true - use: meta-node + meta-backend: etcd - use: compute-node port: 5687 exporter-port: 1222 @@ -901,6 +956,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node enable-tiered-cache: true - use: frontend @@ -914,6 +970,7 @@ profile: - use: etcd - use: minio - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -930,6 +987,7 @@ profile: - use: sqlite - use: minio - use: meta-node + meta-backend: sqlite - use: compute-node - use: frontend - use: compactor @@ -980,6 +1038,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -991,6 +1050,7 @@ profile: bucket: renjie-iceberg-bench - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -1003,6 +1063,7 @@ profile: - use: minio - use: etcd - use: meta-node + meta-backend: etcd - use: compute-node - use: frontend - use: compactor @@ -1163,12 +1224,18 @@ template: # If `user-managed` is true, this service will be started by user with the above config user-managed: false + # meta backend type, requires extra config for provided backend + meta-backend: "memory" + # Etcd backend config provide-etcd-backend: "etcd*" # Sqlite backend config provide-sqlite-backend: "sqlite*" + # Postgres backend config + provide-postgres-backend: "postgres*" + # Prometheus nodes used by dashboard service provide-prometheus: "prometheus*" @@ -1438,7 +1505,36 @@ template: database: "risedev" # The docker image. Can be overridden to use a different version. - image: "mysql:8" + image: "mysql:8.0" + + # If set to true, data will be persisted at data/{id}. + persist-data: true + + # If `user-managed` is true, user is responsible for starting the service + # to serve at the above address and port in any way they see fit. + user-managed: false + + # PostgreSQL service backed by docker. + postgres: + # Id to be picked-up by services + id: postgres-${port} + + # address of pg + address: "127.0.0.1" + + # listen port of pg + port: 8432 + + # Note: + # - This will be used to initialize the PostgreSQL instance if it's fresh. + # - In user-managed mode, these configs are not validated by risedev. + # They are passed as-is to risedev-env default user for PostgreSQL operations. + user: postgres + password: "" + database: "postgres" + + # The docker image. Can be overridden to use a different version. + image: "postgres:15-alpine" # If set to true, data will be persisted at data/{id}. persist-data: true diff --git a/scripts/source/prepare_ci_pubsub/Cargo.toml b/scripts/source/prepare_ci_pubsub/Cargo.toml deleted file mode 100644 index ff3fd2b311468..0000000000000 --- a/scripts/source/prepare_ci_pubsub/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "prepare_ci_pubsub" -version = "0.1.0" -edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[package.metadata.cargo-machete] -ignored = ["workspace-hack"] - -[package.metadata.cargo-udeps.ignore] -normal = ["workspace-hack"] - -[dependencies] -anyhow = "1" -google-cloud-googleapis = { version = "0.12", features = ["pubsub"] } -google-cloud-pubsub = "0.24" -tokio = { version = "0.2", package = "madsim-tokio", features = [ - "rt", - "rt-multi-thread", - "sync", - "macros", - "time", - "signal", - "fs", -] } - -[lints] -workspace = true diff --git a/scripts/source/test_data/pubsub_1_test_topic.1 b/scripts/source/test_data/pubsub_1_test_topic.1 deleted file mode 100644 index fdc299d97d826..0000000000000 --- a/scripts/source/test_data/pubsub_1_test_topic.1 +++ /dev/null @@ -1,20 +0,0 @@ -{"v1":1,"v2":"name0"} -{"v1":2,"v2":"name0"} -{"v1":6,"v2":"name3"} -{"v1":0,"v2":"name5"} -{"v1":5,"v2":"name8"} -{"v1":6,"v2":"name4"} -{"v1":8,"v2":"name9"} -{"v1":9,"v2":"name2"} -{"v1":4,"v2":"name6"} -{"v1":5,"v2":"name3"} -{"v1":8,"v2":"name8"} -{"v1":9,"v2":"name2"} -{"v1":2,"v2":"name3"} -{"v1":4,"v2":"name7"} -{"v1":7,"v2":"name0"} -{"v1":0,"v2":"name9"} -{"v1":3,"v2":"name2"} -{"v1":7,"v2":"name5"} -{"v1":1,"v2":"name7"} -{"v1":3,"v2":"name9"} diff --git a/src/batch/src/executor/delete.rs b/src/batch/src/executor/delete.rs index 00b7ffb1ca519..b280197c6a603 100644 --- a/src/batch/src/executor/delete.rs +++ b/src/batch/src/executor/delete.rs @@ -195,8 +195,6 @@ mod tests { use std::sync::Arc; use futures::StreamExt; - use itertools::Itertools; - use risingwave_common::array::Array; use risingwave_common::catalog::{ schema_test_utils, ColumnDesc, ColumnId, INITIAL_TABLE_VERSION_ID, }; diff --git a/src/batch/src/executor/generic_exchange.rs b/src/batch/src/executor/generic_exchange.rs index 24ff47958dd3f..f4992ed845a0d 100644 --- a/src/batch/src/executor/generic_exchange.rs +++ b/src/batch/src/executor/generic_exchange.rs @@ -277,9 +277,8 @@ impl GenericExchangeExec #[cfg(test)] mod tests { - use futures::StreamExt; use rand::Rng; - use risingwave_common::array::{Array, DataChunk, I32Array}; + use risingwave_common::array::{Array, I32Array}; use risingwave_common::types::DataType; use super::*; diff --git a/src/batch/src/executor/group_top_n.rs b/src/batch/src/executor/group_top_n.rs index 3f11bd4430c0f..b1f4791358131 100644 --- a/src/batch/src/executor/group_top_n.rs +++ b/src/batch/src/executor/group_top_n.rs @@ -15,7 +15,6 @@ use std::marker::PhantomData; use std::mem::swap; use std::sync::Arc; -use std::vec::Vec; use futures_async_stream::try_stream; use hashbrown::HashMap; @@ -240,11 +239,9 @@ impl GroupTopNExecutor { #[cfg(test)] mod tests { use futures::stream::StreamExt; - use risingwave_common::array::DataChunk; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::catalog::Field; use risingwave_common::metrics::LabelGuardedIntGauge; use risingwave_common::test_prelude::DataChunkTestExt; - use risingwave_common::types::DataType; use risingwave_common::util::sort_util::OrderType; use super::*; diff --git a/src/batch/src/executor/hash_agg.rs b/src/batch/src/executor/hash_agg.rs index bc487553c9b5f..c93296b0cfa98 100644 --- a/src/batch/src/executor/hash_agg.rs +++ b/src/batch/src/executor/hash_agg.rs @@ -310,7 +310,6 @@ mod tests { use std::sync::Arc; use futures_async_stream::for_await; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::metrics::LabelGuardedIntGauge; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_pb::data::data_type::TypeName; diff --git a/src/batch/src/executor/hop_window.rs b/src/batch/src/executor/hop_window.rs index d287efc4667d7..ea03d714a7978 100644 --- a/src/batch/src/executor/hop_window.rs +++ b/src/batch/src/executor/hop_window.rs @@ -209,10 +209,8 @@ impl HopWindowExecutor { #[cfg(test)] mod tests { use futures::stream::StreamExt; - use risingwave_common::array::{DataChunk, DataChunkTestExt}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::DataChunkTestExt; use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::DataType; use risingwave_expr::expr::test_utils::make_hop_window_expression; use super::*; diff --git a/src/batch/src/executor/insert.rs b/src/batch/src/executor/insert.rs index aa4063be84bb5..51b247cb884be 100644 --- a/src/batch/src/executor/insert.rs +++ b/src/batch/src/executor/insert.rs @@ -264,18 +264,16 @@ impl BoxedExecutorBuilder for InsertExecutor { #[cfg(test)] mod tests { use std::ops::Bound; - use std::sync::Arc; use assert_matches::assert_matches; - use foyer::memory::CacheContext; + use foyer::CacheContext; use futures::StreamExt; - use itertools::Itertools; use risingwave_common::array::{Array, ArrayImpl, I32Array, StructArray}; use risingwave_common::catalog::{ schema_test_utils, ColumnDesc, ColumnId, INITIAL_TABLE_VERSION_ID, }; use risingwave_common::transaction::transaction_message::TxnMsg; - use risingwave_common::types::{DataType, StructType}; + use risingwave_common::types::StructType; use risingwave_dml::dml_manager::DmlManager; use risingwave_storage::hummock::CachePolicy; use risingwave_storage::memory::MemoryStateStore; diff --git a/src/batch/src/executor/join/hash_join.rs b/src/batch/src/executor/join/hash_join.rs index 7a3b51c450cda..bcba929d60c37 100644 --- a/src/batch/src/executor/join/hash_join.rs +++ b/src/batch/src/executor/join/hash_join.rs @@ -584,7 +584,7 @@ impl HashJoinExecutor { } shutdown_rx.check()?; if !ANTI_JOIN { - if hash_map.get(probe_key).is_some() { + if hash_map.contains_key(probe_key) { if let Some(spilled) = Self::append_one_probe_row( &mut chunk_builder, &probe_chunk, diff --git a/src/batch/src/executor/limit.rs b/src/batch/src/executor/limit.rs index eb57a78e1ab12..8c02ed082f2b2 100644 --- a/src/batch/src/executor/limit.rs +++ b/src/batch/src/executor/limit.rs @@ -150,8 +150,8 @@ mod tests { use std::vec; use futures_async_stream::for_await; - use risingwave_common::array::{Array, ArrayRef, BoolArray, DataChunk, PrimitiveArray}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::{Array, ArrayRef, BoolArray, PrimitiveArray}; + use risingwave_common::catalog::Field; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqDebug; diff --git a/src/batch/src/executor/merge_sort_exchange.rs b/src/batch/src/executor/merge_sort_exchange.rs index 7ebbc6379f4fd..fe8af611f0635 100644 --- a/src/batch/src/executor/merge_sort_exchange.rs +++ b/src/batch/src/executor/merge_sort_exchange.rs @@ -281,10 +281,8 @@ impl BoxedExecutorBuilder for MergeSortExchangeExecutorBuilder { #[cfg(test)] mod tests { - use std::sync::Arc; - use futures::StreamExt; - use risingwave_common::array::{Array, DataChunk}; + use risingwave_common::array::Array; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_common::util::sort_util::OrderType; diff --git a/src/batch/src/executor/order_by.rs b/src/batch/src/executor/order_by.rs index b8be2211c5157..fd07b4fab845e 100644 --- a/src/batch/src/executor/order_by.rs +++ b/src/batch/src/executor/order_by.rs @@ -145,8 +145,7 @@ impl SortExecutor { mod tests { use futures::StreamExt; use risingwave_common::array::*; - use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::test_prelude::DataChunkTestExt; + use risingwave_common::catalog::Field; use risingwave_common::types::{ DataType, Date, Interval, Scalar, StructType, Time, Timestamp, F32, }; diff --git a/src/batch/src/executor/project.rs b/src/batch/src/executor/project.rs index fc5cddaec0787..7fbc5540b975b 100644 --- a/src/batch/src/executor/project.rs +++ b/src/batch/src/executor/project.rs @@ -108,16 +108,14 @@ impl BoxedExecutorBuilder for ProjectExecutor { #[cfg(test)] mod tests { - use futures::stream::StreamExt; use risingwave_common::array::{Array, I32Array}; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::test_prelude::*; use risingwave_common::types::DataType; use risingwave_expr::expr::{InputRefExpression, LiteralExpression}; use super::*; use crate::executor::test_utils::MockExecutor; - use crate::executor::{Executor, ValuesExecutor}; + use crate::executor::ValuesExecutor; use crate::*; const CHUNK_SIZE: usize = 1024; diff --git a/src/batch/src/executor/project_set.rs b/src/batch/src/executor/project_set.rs index b7291609be586..2d50c1039743c 100644 --- a/src/batch/src/executor/project_set.rs +++ b/src/batch/src/executor/project_set.rs @@ -174,15 +174,13 @@ impl BoxedExecutorBuilder for ProjectSetExecutor { mod tests { use futures::stream::StreamExt; use futures_async_stream::for_await; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::test_prelude::*; - use risingwave_common::types::DataType; use risingwave_expr::expr::{ExpressionBoxExt, InputRefExpression, LiteralExpression}; use risingwave_expr::table_function::repeat; use super::*; use crate::executor::test_utils::MockExecutor; - use crate::executor::{Executor, ValuesExecutor}; + use crate::executor::ValuesExecutor; use crate::*; const CHUNK_SIZE: usize = 1024; diff --git a/src/batch/src/executor/sort_agg.rs b/src/batch/src/executor/sort_agg.rs index 2f7cea6059367..bcda8773efa40 100644 --- a/src/batch/src/executor/sort_agg.rs +++ b/src/batch/src/executor/sort_agg.rs @@ -360,14 +360,12 @@ mod tests { use futures::StreamExt; use futures_async_stream::for_await; use risingwave_common::array::{Array as _, I64Array}; - use risingwave_common::catalog::{Field, Schema}; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_expr::expr::build_from_pretty; use super::*; use crate::executor::test_utils::MockExecutor; - use crate::task::ShutdownToken; #[tokio::test] async fn execute_count_star_int32() -> Result<()> { diff --git a/src/batch/src/executor/top_n.rs b/src/batch/src/executor/top_n.rs index c02b84ec5b7ca..4811330430682 100644 --- a/src/batch/src/executor/top_n.rs +++ b/src/batch/src/executor/top_n.rs @@ -14,7 +14,6 @@ use std::cmp::Ordering; use std::sync::Arc; -use std::vec::Vec; use futures_async_stream::try_stream; use risingwave_common::array::DataChunk; @@ -296,8 +295,8 @@ impl TopNExecutor { mod tests { use futures::stream::StreamExt; use itertools::Itertools; - use risingwave_common::array::{Array, DataChunk}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::Array; + use risingwave_common::catalog::Field; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::DataType; use risingwave_common::util::sort_util::OrderType; diff --git a/src/batch/src/executor/update.rs b/src/batch/src/executor/update.rs index 9e9c2fa8f543d..a753aef840f52 100644 --- a/src/batch/src/executor/update.rs +++ b/src/batch/src/executor/update.rs @@ -259,7 +259,6 @@ mod tests { use std::sync::Arc; use futures::StreamExt; - use risingwave_common::array::Array; use risingwave_common::catalog::{ schema_test_utils, ColumnDesc, ColumnId, INITIAL_TABLE_VERSION_ID, }; diff --git a/src/batch/src/lib.rs b/src/batch/src/lib.rs index d937c64826550..b8e6df1ac9538 100644 --- a/src/batch/src/lib.rs +++ b/src/batch/src/lib.rs @@ -25,13 +25,11 @@ #![feature(is_sorted)] #![recursion_limit = "256"] #![feature(let_chains)] -#![feature(bound_map)] #![feature(int_roundings)] #![feature(allocator_api)] #![feature(impl_trait_in_assoc_type)] #![feature(assert_matches)] #![feature(lazy_cell)] -#![feature(array_methods)] #![feature(error_generic_member_access)] #![feature(map_try_insert)] diff --git a/src/batch/src/rpc/service/task_service.rs b/src/batch/src/rpc/service/task_service.rs index 816b62a02fc4d..11ae64e7b3918 100644 --- a/src/batch/src/rpc/service/task_service.rs +++ b/src/batch/src/rpc/service/task_service.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::Into; use std::sync::Arc; use risingwave_common::util::tracing::TracingContext; diff --git a/src/batch/src/task/consistent_hash_shuffle_channel.rs b/src/batch/src/task/consistent_hash_shuffle_channel.rs index 3e42186fcf587..1181654f41249 100644 --- a/src/batch/src/task/consistent_hash_shuffle_channel.rs +++ b/src/batch/src/task/consistent_hash_shuffle_channel.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::fmt::{Debug, Formatter}; -use std::option::Option; use std::sync::Arc; use anyhow::anyhow; diff --git a/src/batch/src/task/hash_shuffle_channel.rs b/src/batch/src/task/hash_shuffle_channel.rs index 3965161030a32..626324e6f3456 100644 --- a/src/batch/src/task/hash_shuffle_channel.rs +++ b/src/batch/src/task/hash_shuffle_channel.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::fmt::{Debug, Formatter}; -use std::option::Option; use std::sync::Arc; use anyhow::anyhow; diff --git a/src/bench/sink_bench/main.rs b/src/bench/sink_bench/main.rs index 033cb47ba5a24..cfb506fa2efab 100644 --- a/src/bench/sink_bench/main.rs +++ b/src/bench/sink_bench/main.rs @@ -49,7 +49,6 @@ use risingwave_connector::source::datagen::{ use risingwave_connector::source::{ Column, DataType, SourceContext, SourceEnumeratorContext, SplitEnumerator, SplitReader, }; -use risingwave_pb::connector_service::SinkPayloadFormat; use risingwave_stream::executor::test_utils::prelude::ColumnDesc; use risingwave_stream::executor::{Barrier, Message, MessageStreamItem, StreamExecutorError}; use serde::{Deserialize, Deserializer}; @@ -472,9 +471,8 @@ async fn main() { sink_from_name: "not_need_set".to_string(), }; let sink = build_sink(sink_param).unwrap(); - let mut sink_writer_param = SinkWriterParam::for_test(); + let sink_writer_param = SinkWriterParam::for_test(); println!("Start Sink Bench!, Wait {:?}s", BENCH_TIME); - sink_writer_param.connector_params.sink_payload_format = SinkPayloadFormat::StreamChunk; tokio::spawn(async move { dispatch_sink!(sink, sink, { consume_log_stream(sink, mock_range_log_reader, sink_writer_param).boxed() diff --git a/src/cmd_all/src/single_node.rs b/src/cmd_all/src/single_node.rs index 63099c1ff640c..2340841a13c31 100644 --- a/src/cmd_all/src/single_node.rs +++ b/src/cmd_all/src/single_node.rs @@ -186,7 +186,7 @@ pub fn map_single_node_opts_to_standalone_opts(opts: SingleNodeOpts) -> ParsedSt std::fs::create_dir_all(&meta_store_dir).unwrap(); let meta_store_endpoint = format!("sqlite://{}/single_node.db?mode=rwc", &meta_store_dir); - meta_opts.sql_endpoint = Some(meta_store_endpoint); + meta_opts.sql_endpoint = Some(meta_store_endpoint.into()); } } diff --git a/src/cmd_all/src/standalone.rs b/src/cmd_all/src/standalone.rs index 62030a3c84aff..819aba03d865e 100644 --- a/src/cmd_all/src/standalone.rs +++ b/src/cmd_all/src/standalone.rs @@ -356,7 +356,6 @@ mod test { http://127.0.0.1:5690/, ], ), - connector_rpc_sink_payload_format: None, config_path: "src/config/test.toml", total_memory_bytes: 34359738368, reserved_memory_bytes: None, diff --git a/src/common/Cargo.toml b/src/common/Cargo.toml index a7072f7c79c51..1ac0752575541 100644 --- a/src/common/Cargo.toml +++ b/src/common/Cargo.toml @@ -54,7 +54,7 @@ humantime = "2.1" hytra = { workspace = true } itertools = { workspace = true } itoa = "1.0" -jsonbb = "0.1.2" +jsonbb = { workspace = true } lru = { workspace = true } memcomparable = { version = "0.2", features = ["decimal"] } num-integer = "0.1" diff --git a/src/common/estimate_size/Cargo.toml b/src/common/estimate_size/Cargo.toml index 04bcf369cc588..24cf19b3809e9 100644 --- a/src/common/estimate_size/Cargo.toml +++ b/src/common/estimate_size/Cargo.toml @@ -19,7 +19,7 @@ bytes = "1" educe = "0.5" ethnum = { version = "1", features = ["serde"] } fixedbitset = "0.5" -jsonbb = "0.1.2" +jsonbb = { workspace = true } lru = { workspace = true } risingwave_common_proc_macro = { workspace = true } rust_decimal = "1" diff --git a/src/common/estimate_size/src/collections/btreemap.rs b/src/common/estimate_size/src/collections/btreemap.rs index cf4398eb191df..84ecd687d8df1 100644 --- a/src/common/estimate_size/src/collections/btreemap.rs +++ b/src/common/estimate_size/src/collections/btreemap.rs @@ -92,7 +92,11 @@ where // [ left, [mid], right ] let mut mid_right = self.inner.split_off(start); - let mid_right_split_key = mid_right.lower_bound(Bound::Excluded(end)).key().cloned(); + let mid_right_split_key = mid_right + .lower_bound(Bound::Excluded(end)) + .peek_next() + .map(|(k, _)| k) + .cloned(); let right = if let Some(ref mid_right_split_key) = mid_right_split_key { mid_right.split_off(mid_right_split_key) } else { diff --git a/src/common/estimate_size/src/collections/lru.rs b/src/common/estimate_size/src/collections/lru.rs deleted file mode 100644 index 4add8df754673..0000000000000 --- a/src/common/estimate_size/src/collections/lru.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -use std::alloc::{Allocator, Global}; -use std::borrow::Borrow; -use std::hash::{BuildHasher, Hash}; - -use lru::{DefaultHasher, LruCache}; - -use super::{AtomicMutGuard, MutGuard}; -use crate::{EstimateSize, KvSize}; - -/// The managed cache is a lru cache that bounds the memory usage by epoch. -/// Should be used with `MemoryManager`. -pub struct EstimatedLruCache { - inner: LruCache, - kv_heap_size: KvSize, -} - -impl - EstimatedLruCache -{ - pub fn with_hasher_in(hasher: S, alloc: A) -> Self { - Self { - inner: LruCache::unbounded_with_hasher_in(hasher, alloc), - kv_heap_size: KvSize::new(), - } - } - - /// Evict epochs lower than the watermark - pub fn evict_by_epoch(&mut self, epoch: u64) { - while let Some((key, value, _)) = self.inner.pop_lru_by_epoch(epoch) { - self.kv_heap_size.sub(&key, &value); - } - } - - pub fn update_epoch(&mut self, epoch: u64) { - self.inner.update_epoch(epoch); - } - - pub fn current_epoch(&mut self) -> u64 { - self.inner.current_epoch() - } - - /// An iterator visiting all values in most-recently used order. The iterator element type is - /// &V. - pub fn values(&self) -> impl Iterator { - self.inner.iter().map(|(_k, v)| v) - } - - pub fn get_mut(&mut self, k: &K) -> Option> { - let v = self.inner.get_mut(k); - v.map(|inner| MutGuard::new(inner, &mut self.kv_heap_size)) - } - - pub fn get(&mut self, k: &Q) -> Option<&V> - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.inner.get(k) - } - - pub fn iter_mut( - &mut self, - ) -> impl ExactSizeIterator)> + '_ { - let kv_heap_size = &self.kv_heap_size; - self.inner.iter_mut().map(move |(k, v)| { - let guard = AtomicMutGuard::new(v, kv_heap_size); - (k, guard) - }) - } - - pub fn peek_mut(&mut self, k: &K) -> Option> { - let v = self.inner.peek_mut(k); - v.map(|inner| MutGuard::new(inner, &mut self.kv_heap_size)) - } - - pub fn push(&mut self, k: K, v: V) -> Option<(K, V)> { - self.kv_heap_size.add(&k, &v); - - let old_kv = self.inner.push(k, v); - - if let Some((old_key, old_val)) = &old_kv { - self.kv_heap_size.sub(old_key, old_val); - } - old_kv - } - - pub fn contains(&self, k: &Q) -> bool - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.inner.contains(k) - } - - pub fn len(&self) -> usize { - self.inner.len() - } - - pub fn is_empty(&self) -> bool { - self.inner.len() == 0 - } - - pub fn clear(&mut self) { - self.inner.clear(); - } -} - -impl EstimatedLruCache { - pub fn unbounded() -> Self { - Self { - inner: LruCache::unbounded(), - kv_heap_size: KvSize::new(), - } - } -} - -impl EstimatedLruCache { - pub fn unbounded_with_hasher(hasher: S) -> Self { - Self { - inner: LruCache::unbounded_with_hasher(hasher), - kv_heap_size: KvSize::new(), - } - } -} - -impl - EstimatedLruCache -{ - pub fn unbounded_with_hasher_in(hasher: S, allocator: A) -> Self { - Self { - inner: LruCache::unbounded_with_hasher_in(hasher, allocator), - kv_heap_size: KvSize::new(), - } - } -} - -impl - EstimateSize for EstimatedLruCache -{ - fn estimated_heap_size(&self) -> usize { - // TODO: Add lru cache internal size - // https://github.com/risingwavelabs/risingwave/issues/9713 - self.kv_heap_size.size() - } -} diff --git a/src/common/estimate_size/src/collections/mod.rs b/src/common/estimate_size/src/collections/mod.rs index f7cdff490a88d..5bffd5133eddc 100644 --- a/src/common/estimate_size/src/collections/mod.rs +++ b/src/common/estimate_size/src/collections/mod.rs @@ -16,7 +16,6 @@ use std::ops::{Deref, DerefMut}; use super::{EstimateSize, KvSize}; -pub mod lru; pub mod vecdeque; pub use vecdeque::EstimatedVecDeque; pub mod hashmap; diff --git a/src/common/heap_profiling/src/jeprof.rs b/src/common/heap_profiling/src/jeprof.rs index b0ae0f1658c9e..6c17fed7045a5 100644 --- a/src/common/heap_profiling/src/jeprof.rs +++ b/src/common/heap_profiling/src/jeprof.rs @@ -14,7 +14,6 @@ use std::path::Path; use std::process::Command; -use std::result::Result; use std::{env, fs}; /// Error type for running `jeprof`. diff --git a/src/common/metrics/src/guarded_metrics.rs b/src/common/metrics/src/guarded_metrics.rs index 9c0919d7a420b..888dc76f73944 100644 --- a/src/common/metrics/src/guarded_metrics.rs +++ b/src/common/metrics/src/guarded_metrics.rs @@ -21,31 +21,15 @@ use std::sync::Arc; use itertools::Itertools; use parking_lot::Mutex; use prometheus::core::{ - Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, GenericCounter, GenericCounterVec, - GenericGauge, GenericGaugeVec, GenericLocalCounter, MetricVec, MetricVecBuilder, + Atomic, AtomicF64, AtomicI64, AtomicU64, Collector, Desc, GenericCounter, GenericLocalCounter, + MetricVec, MetricVecBuilder, }; use prometheus::local::{LocalHistogram, LocalIntCounter}; use prometheus::proto::MetricFamily; -use prometheus::{Gauge, Histogram, HistogramVec, IntCounter, IntGauge}; +use prometheus::{Gauge, Histogram, IntCounter, IntGauge}; use thiserror_ext::AsReport; use tracing::warn; -pub fn __extract_counter_builder( - vec: GenericCounterVec

, -) -> MetricVec> { - vec -} - -pub fn __extract_gauge_builder( - vec: GenericGaugeVec

, -) -> MetricVec> { - vec -} - -pub fn __extract_histogram_builder(vec: HistogramVec) -> MetricVec { - vec -} - #[macro_export] macro_rules! register_guarded_histogram_vec_with_registry { ($NAME:expr, $HELP:expr, $LABELS_NAMES:expr, $REGISTRY:expr $(,)?) => {{ @@ -105,9 +89,35 @@ macro_rules! register_guarded_int_counter_vec_with_registry { }}; } -pub type VecBuilderOfCounter = impl MetricVecBuilder>; -pub type VecBuilderOfGauge = impl MetricVecBuilder>; -pub type VecBuilderOfHistogram = impl MetricVecBuilder; +// put TAITs in a separate module to avoid "non-defining opaque type use in defining scope" +mod tait { + use prometheus::core::{ + Atomic, GenericCounter, GenericCounterVec, GenericGauge, GenericGaugeVec, MetricVec, + MetricVecBuilder, + }; + use prometheus::{Histogram, HistogramVec}; + + pub type VecBuilderOfCounter = impl MetricVecBuilder>; + pub type VecBuilderOfGauge = impl MetricVecBuilder>; + pub type VecBuilderOfHistogram = impl MetricVecBuilder; + + pub fn __extract_counter_builder( + vec: GenericCounterVec

, + ) -> MetricVec> { + vec + } + + pub fn __extract_gauge_builder( + vec: GenericGaugeVec

, + ) -> MetricVec> { + vec + } + + pub fn __extract_histogram_builder(vec: HistogramVec) -> MetricVec { + vec + } +} +pub use tait::*; pub type LabelGuardedHistogramVec = LabelGuardedMetricVec; pub type LabelGuardedIntCounterVec = diff --git a/src/common/metrics/src/lib.rs b/src/common/metrics/src/lib.rs index a2e4156b525d1..574e684c6703b 100644 --- a/src/common/metrics/src/lib.rs +++ b/src/common/metrics/src/lib.rs @@ -15,7 +15,6 @@ #![feature(lazy_cell)] #![feature(type_alias_impl_trait)] #![feature(impl_trait_in_assoc_type)] -#![feature(array_methods)] use std::ops::Deref; use std::sync::LazyLock; diff --git a/src/common/src/acl/mod.rs b/src/common/src/acl/mod.rs index 91f69fea51310..b1c41bc8ec6ad 100644 --- a/src/common/src/acl/mod.rs +++ b/src/common/src/acl/mod.rs @@ -14,7 +14,6 @@ //! `Acl` defines all grantable privileges. -use std::convert::Into; use std::fmt::Formatter; use std::sync::LazyLock; @@ -100,11 +99,11 @@ pub static ALL_AVAILABLE_DATABASE_MODES: LazyLock = LazyLock::new(|| make_bitflags!(AclMode::{Create | Connect}).into()); pub static ALL_AVAILABLE_SCHEMA_MODES: LazyLock = LazyLock::new(|| make_bitflags!(AclMode::{Create | Usage}).into()); +// Including TABLES and VIEWS pub static ALL_AVAILABLE_TABLE_MODES: LazyLock = LazyLock::new(|| make_bitflags!(AclMode::{Select | Insert | Update | Delete}).into()); pub static ALL_AVAILABLE_SOURCE_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); pub static ALL_AVAILABLE_MVIEW_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); -pub static ALL_AVAILABLE_VIEW_MODES: LazyLock = LazyLock::new(AclModeSet::readonly); pub static ALL_AVAILABLE_SINK_MODES: LazyLock = LazyLock::new(AclModeSet::empty); pub static ALL_AVAILABLE_SUBSCRIPTION_MODES: LazyLock = LazyLock::new(AclModeSet::empty); diff --git a/src/common/src/array/arrow/arrow_impl.rs b/src/common/src/array/arrow/arrow_impl.rs index 514d3b299769c..773efdd088e3d 100644 --- a/src/common/src/array/arrow/arrow_impl.rs +++ b/src/common/src/array/arrow/arrow_impl.rs @@ -36,8 +36,11 @@ //! mod arrow_impl; //! ``` +// Is this a bug? Why do we have these lints? +#![allow(unused_imports)] +#![allow(dead_code)] + use std::fmt::Write; -use std::sync::Arc; use arrow_buffer::OffsetBuffer; use chrono::{NaiveDateTime, NaiveTime}; @@ -47,7 +50,6 @@ use itertools::Itertools; use super::{arrow_array, arrow_buffer, arrow_cast, arrow_schema}; // Other import should always use the absolute path. use crate::array::*; -use crate::buffer::Bitmap; use crate::types::*; use crate::util::iter_util::ZipEqFast; diff --git a/src/common/src/array/arrow/arrow_udf.rs b/src/common/src/array/arrow/arrow_udf.rs index e2f9e39ad385a..83383044f506d 100644 --- a/src/common/src/array/arrow/arrow_udf.rs +++ b/src/common/src/array/arrow/arrow_udf.rs @@ -29,82 +29,110 @@ use crate::array::{ArrayError, ArrayImpl, DataType, DecimalArray, JsonbArray}; #[path = "./arrow_impl.rs"] mod arrow_impl; -/// Arrow conversion for the current version of UDF. This is in use but will be deprecated soon. -/// -/// In the current version of UDF protocol, decimal and jsonb types are mapped to Arrow `LargeBinary` and `LargeUtf8` types. -pub struct UdfArrowConvert; +/// Arrow conversion for UDF. +#[derive(Default, Debug)] +pub struct UdfArrowConvert { + /// Whether the UDF talks in legacy mode. + /// + /// If true, decimal and jsonb types are mapped to Arrow `LargeBinary` and `LargeUtf8` types. + /// Otherwise, they are mapped to Arrow extension types. + /// See . + pub legacy: bool, +} impl ToArrow for UdfArrowConvert { - // Decimal values are stored as ASCII text representation in a large binary array. fn decimal_to_arrow( &self, _data_type: &arrow_schema::DataType, array: &DecimalArray, ) -> Result { - Ok(Arc::new(arrow_array::LargeBinaryArray::from(array))) + if self.legacy { + // Decimal values are stored as ASCII text representation in a large binary array. + Ok(Arc::new(arrow_array::LargeBinaryArray::from(array))) + } else { + Ok(Arc::new(arrow_array::StringArray::from(array))) + } } - // JSON values are stored as text representation in a large string array. fn jsonb_to_arrow(&self, array: &JsonbArray) -> Result { - Ok(Arc::new(arrow_array::LargeStringArray::from(array))) + if self.legacy { + // JSON values are stored as text representation in a large string array. + Ok(Arc::new(arrow_array::LargeStringArray::from(array))) + } else { + Ok(Arc::new(arrow_array::StringArray::from(array))) + } } fn jsonb_type_to_arrow(&self, name: &str) -> arrow_schema::Field { - arrow_schema::Field::new(name, arrow_schema::DataType::LargeUtf8, true) + if self.legacy { + arrow_schema::Field::new(name, arrow_schema::DataType::LargeUtf8, true) + } else { + arrow_schema::Field::new(name, arrow_schema::DataType::Utf8, true) + .with_metadata([("ARROW:extension:name".into(), "arrowudf.json".into())].into()) + } } fn decimal_type_to_arrow(&self, name: &str) -> arrow_schema::Field { - arrow_schema::Field::new(name, arrow_schema::DataType::LargeBinary, true) + if self.legacy { + arrow_schema::Field::new(name, arrow_schema::DataType::LargeBinary, true) + } else { + arrow_schema::Field::new(name, arrow_schema::DataType::Utf8, true) + .with_metadata([("ARROW:extension:name".into(), "arrowudf.decimal".into())].into()) + } } } impl FromArrow for UdfArrowConvert { fn from_large_utf8(&self) -> Result { - Ok(DataType::Jsonb) + if self.legacy { + Ok(DataType::Jsonb) + } else { + Ok(DataType::Varchar) + } } fn from_large_binary(&self) -> Result { - Ok(DataType::Decimal) + if self.legacy { + Ok(DataType::Decimal) + } else { + Ok(DataType::Bytea) + } } fn from_large_utf8_array( &self, array: &arrow_array::LargeStringArray, ) -> Result { - Ok(ArrayImpl::Jsonb(array.try_into()?)) + if self.legacy { + Ok(ArrayImpl::Jsonb(array.try_into()?)) + } else { + Ok(ArrayImpl::Utf8(array.into())) + } } fn from_large_binary_array( &self, array: &arrow_array::LargeBinaryArray, ) -> Result { - Ok(ArrayImpl::Decimal(array.try_into()?)) + if self.legacy { + Ok(ArrayImpl::Decimal(array.try_into()?)) + } else { + Ok(ArrayImpl::Bytea(array.into())) + } } } -/// Arrow conversion for the next version of UDF. This is unused for now. -/// -/// In the next version of UDF protocol, decimal and jsonb types will be mapped to Arrow extension types. -/// See . -pub struct NewUdfArrowConvert; - -impl ToArrow for NewUdfArrowConvert {} -impl FromArrow for NewUdfArrowConvert {} - #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; use crate::array::*; - use crate::buffer::Bitmap; #[test] fn struct_array() { // Empty array - risingwave to arrow conversion. let test_arr = StructArray::new(StructType::empty(), vec![], Bitmap::ones(0)); assert_eq!( - UdfArrowConvert + UdfArrowConvert::default() .struct_to_arrow( &arrow_schema::DataType::Struct(arrow_schema::Fields::empty()), &test_arr @@ -117,7 +145,7 @@ mod tests { // Empty array - arrow to risingwave conversion. let test_arr_2 = arrow_array::StructArray::from(vec![]); assert_eq!( - UdfArrowConvert + UdfArrowConvert::default() .from_struct_array(&test_arr_2) .unwrap() .len(), @@ -146,7 +174,7 @@ mod tests { ), ]) .unwrap(); - let actual_risingwave_struct_array = UdfArrowConvert + let actual_risingwave_struct_array = UdfArrowConvert::default() .from_struct_array(&test_arrow_struct_array) .unwrap() .into_struct(); @@ -168,8 +196,10 @@ mod tests { fn list() { let array = ListArray::from_iter([None, Some(vec![0, -127, 127, 50]), Some(vec![0; 0])]); let data_type = arrow_schema::DataType::new_list(arrow_schema::DataType::Int32, true); - let arrow = UdfArrowConvert.list_to_arrow(&data_type, &array).unwrap(); - let rw_array = UdfArrowConvert + let arrow = UdfArrowConvert::default() + .list_to_arrow(&data_type, &array) + .unwrap(); + let rw_array = UdfArrowConvert::default() .from_list_array(arrow.as_any().downcast_ref().unwrap()) .unwrap(); assert_eq!(rw_array.as_list(), &array); diff --git a/src/common/src/array/data_chunk.rs b/src/common/src/array/data_chunk.rs index cdb012a3185cb..7fb908ecc138f 100644 --- a/src/common/src/array/data_chunk.rs +++ b/src/common/src/array/data_chunk.rs @@ -13,10 +13,10 @@ // limitations under the License. use std::borrow::Cow; +use std::fmt; use std::fmt::Display; use std::hash::BuildHasher; use std::sync::Arc; -use std::{fmt, usize}; use bytes::Bytes; use either::Either; @@ -249,6 +249,7 @@ impl DataChunk { Self::new(columns, Bitmap::ones(cardinality)) } + /// Scatter a compacted chunk to a new chunk with the given visibility. pub fn uncompact(self, vis: Bitmap) -> Self { let mut uncompact_builders: Vec<_> = self .columns diff --git a/src/common/src/array/primitive_array.rs b/src/common/src/array/primitive_array.rs index 9840ba68135c5..29c7bfb49289a 100644 --- a/src/common/src/array/primitive_array.rs +++ b/src/common/src/array/primitive_array.rs @@ -315,7 +315,6 @@ impl ArrayBuilder for PrimitiveArrayBuilder { #[cfg(test)] mod tests { use super::*; - use crate::types::{F32, F64}; fn helper_test_builder(data: Vec>) -> PrimitiveArray { let mut builder = PrimitiveArrayBuilder::::new(data.len()); diff --git a/src/common/src/array/proto_reader.rs b/src/common/src/array/proto_reader.rs index b0b133d1b2df0..073ad0b3de7ba 100644 --- a/src/common/src/array/proto_reader.rs +++ b/src/common/src/array/proto_reader.rs @@ -17,16 +17,10 @@ use std::io::{Cursor, Read}; use anyhow::Context; use byteorder::{BigEndian, ReadBytesExt}; use paste::paste; -use risingwave_pb::data::{PbArray, PbArrayType}; +use risingwave_pb::data::PbArrayType; use super::*; use crate::array::value_reader::{PrimitiveValueReader, VarSizedValueReader}; -use crate::array::{ - Array, ArrayBuilder, ArrayImpl, ArrayResult, BoolArray, DateArrayBuilder, IntervalArrayBuilder, - PrimitiveArrayBuilder, PrimitiveArrayItemType, TimeArrayBuilder, TimestampArrayBuilder, -}; -use crate::buffer::Bitmap; -use crate::types::{Date, Interval, Time, Timestamp}; impl ArrayImpl { pub fn from_protobuf(array: &PbArray, cardinality: usize) -> ArrayResult { @@ -252,12 +246,6 @@ fn read_string_array>( #[cfg(test)] mod tests { use super::*; - use crate::array::{ - Array, ArrayBuilder, BoolArray, BoolArrayBuilder, DateArray, DateArrayBuilder, - DecimalArray, DecimalArrayBuilder, I32Array, I32ArrayBuilder, TimeArray, TimeArrayBuilder, - TimestampArray, TimestampArrayBuilder, Utf8Array, Utf8ArrayBuilder, - }; - use crate::types::{Date, Decimal, Time, Timestamp}; // Convert a column to protobuf, then convert it back to column, and ensures the two are // identical. diff --git a/src/common/src/cache.rs b/src/common/src/cache.rs index 99a373d6a94a8..bb4af1d615eb9 100644 --- a/src/common/src/cache.rs +++ b/src/common/src/cache.rs @@ -1019,14 +1019,12 @@ impl Clone for CacheableEntry { #[cfg(test)] mod tests { use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; + use std::hash::Hasher; use std::pin::Pin; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::Relaxed; - use std::sync::Arc; use std::task::{Context, Poll}; - use futures::FutureExt; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; use tokio::sync::oneshot::error::TryRecvError; diff --git a/src/common/src/catalog/mod.rs b/src/common/src/catalog/mod.rs index 1909ca1635f7c..cef5b021b9dbd 100644 --- a/src/common/src/catalog/mod.rs +++ b/src/common/src/catalog/mod.rs @@ -33,6 +33,7 @@ use risingwave_pb::catalog::{ }; use risingwave_pb::plan_common::ColumnDescVersion; pub use schema::{test_utils as schema_test_utils, Field, FieldDisplay, Schema}; +use serde::{Deserialize, Serialize}; use crate::array::DataChunk; pub use crate::constants::hummock; @@ -228,7 +229,20 @@ impl From for u32 { } } -#[derive(Clone, Copy, Debug, Display, Default, Hash, PartialOrd, PartialEq, Eq, Ord)] +#[derive( + Clone, + Copy, + Debug, + Display, + Default, + Hash, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, +)] #[display("{table_id}")] pub struct TableId { pub table_id: u32, diff --git a/src/common/src/config.rs b/src/common/src/config.rs index a5cb55445c070..41a4e1415b301 100644 --- a/src/common/src/config.rs +++ b/src/common/src/config.rs @@ -24,7 +24,7 @@ use std::num::NonZeroUsize; use anyhow::Context; use clap::ValueEnum; use educe::Educe; -use foyer::memory::{LfuConfig, LruConfig, S3FifoConfig}; +use foyer::{LfuConfig, LruConfig, S3FifoConfig}; use risingwave_common_proc_macro::ConfigDoc; pub use risingwave_common_proc_macro::OverrideConfig; use risingwave_pb::meta::SystemParams; @@ -583,6 +583,8 @@ pub enum CacheEvictionConfig { }, S3Fifo { small_queue_capacity_ratio_in_percent: Option, + ghost_queue_capacity_ratio_in_percent: Option, + small_to_main_freq_threshold: Option, }, } @@ -1347,6 +1349,14 @@ pub mod default { 10 } + pub fn ghost_queue_capacity_ratio_in_percent() -> usize { + 1000 + } + + pub fn small_to_main_freq_threshold() -> u8 { + 1 + } + pub fn meta_cache_capacity_mb() -> usize { 128 } @@ -1889,6 +1899,16 @@ impl EvictionConfig { } } +impl From for foyer::EvictionConfig { + fn from(value: EvictionConfig) -> Self { + match value { + EvictionConfig::Lru(lru) => foyer::EvictionConfig::Lru(lru), + EvictionConfig::Lfu(lfu) => foyer::EvictionConfig::Lfu(lfu), + EvictionConfig::S3Fifo(s3fifo) => foyer::EvictionConfig::S3Fifo(s3fifo), + } + } +} + pub struct StorageMemoryConfig { pub block_cache_capacity_mb: usize, pub block_cache_shard_num: usize, @@ -1975,11 +1995,19 @@ pub fn extract_storage_memory_config(s: &RwConfig) -> StorageMemoryConfig { }), CacheEvictionConfig::S3Fifo { small_queue_capacity_ratio_in_percent, + ghost_queue_capacity_ratio_in_percent, + small_to_main_freq_threshold, } => EvictionConfig::S3Fifo(S3FifoConfig { small_queue_capacity_ratio: small_queue_capacity_ratio_in_percent .unwrap_or(default::storage::small_queue_capacity_ratio_in_percent()) as f64 / 100.0, + ghost_queue_capacity_ratio: ghost_queue_capacity_ratio_in_percent + .unwrap_or(default::storage::ghost_queue_capacity_ratio_in_percent()) + as f64 + / 100.0, + small_to_main_freq_threshold: small_to_main_freq_threshold + .unwrap_or(default::storage::small_to_main_freq_threshold()), }), } }; @@ -2053,8 +2081,6 @@ pub struct CompactionConfig { #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use super::*; /// This test ensures that `config/example.toml` is up-to-date with the default values specified diff --git a/src/common/src/field_generator/varchar.rs b/src/common/src/field_generator/varchar.rs index c4af1499620ca..9f68bcb287c46 100644 --- a/src/common/src/field_generator/varchar.rs +++ b/src/common/src/field_generator/varchar.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::string::ToString; - use rand::distributions::Alphanumeric; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; diff --git a/src/common/src/hash/consistent_hash/mapping.rs b/src/common/src/hash/consistent_hash/mapping.rs index c542ab2050cf1..07e62b7eac275 100644 --- a/src/common/src/hash/consistent_hash/mapping.rs +++ b/src/common/src/hash/consistent_hash/mapping.rs @@ -335,7 +335,6 @@ mod tests { use rand::Rng; use super::*; - use crate::util::iter_util::ZipEqDebug; struct Test; impl VnodeMappingItem for Test { diff --git a/src/common/src/hash/consistent_hash/vnode.rs b/src/common/src/hash/consistent_hash/vnode.rs index 9bc49a9372ac0..fc6dac0978adf 100644 --- a/src/common/src/hash/consistent_hash/vnode.rs +++ b/src/common/src/hash/consistent_hash/vnode.rs @@ -169,7 +169,6 @@ mod tests { use super::*; use crate::array::DataChunkTestExt; use crate::row::OwnedRow; - use crate::types::ScalarImpl; use crate::util::row_id::RowIdGenerator; #[test] diff --git a/src/common/src/hash/key.rs b/src/common/src/hash/key.rs index c136710edb23f..4911e041370a9 100644 --- a/src/common/src/hash/key.rs +++ b/src/common/src/hash/key.rs @@ -21,7 +21,6 @@ //! are encoded from both `t.b` and `t.c`. If `t.b="abc"` and `t.c=1`, the hashkey may be //! encoded in certain format of `("abc", 1)`. -use std::convert::TryInto; use std::default::Default; use std::fmt::Debug; use std::hash::{BuildHasher, Hasher}; @@ -643,11 +642,9 @@ mod tests { DateArray, DecimalArray, F32Array, F64Array, I16Array, I32Array, I32ArrayBuilder, I64Array, TimeArray, TimestampArray, Utf8Array, }; - use crate::hash::{ - HashKey, Key128, Key16, Key256, Key32, Key64, KeySerialized, PrecomputedBuildHasher, - }; + use crate::hash::{HashKey, Key128, Key16, Key256, Key32, Key64, KeySerialized}; use crate::test_utils::rand_array::seed_rand_array_ref; - use crate::types::{DataType, Datum}; + use crate::types::Datum; #[derive(Hash, PartialEq, Eq)] struct Row(Vec); diff --git a/src/common/src/lib.rs b/src/common/src/lib.rs index ea3cd4a4f7102..c30b12ba4cf2d 100644 --- a/src/common/src/lib.rs +++ b/src/common/src/lib.rs @@ -34,16 +34,14 @@ #![feature(inline_const_pat)] #![allow(incomplete_features)] #![feature(iterator_try_collect)] -#![feature(round_ties_even)] #![feature(iter_order_by)] #![feature(exclusive_range_pattern)] #![feature(binary_heap_into_iter_sorted)] #![feature(impl_trait_in_assoc_type)] #![feature(map_entry_replace)] #![feature(negative_impls)] -#![feature(bound_map)] -#![feature(array_methods)] #![feature(register_tool)] +#![feature(btree_cursors)] #![register_tool(rw)] #[cfg_attr(not(test), allow(unused_extern_crates))] diff --git a/src/common/src/row/owned_row.rs b/src/common/src/row/owned_row.rs index 8ea3803538d84..15a01d192a64c 100644 --- a/src/common/src/row/owned_row.rs +++ b/src/common/src/row/owned_row.rs @@ -182,7 +182,7 @@ mod tests { use super::*; use crate::row::RowExt; - use crate::types::{DataType as Ty, Interval, ScalarImpl}; + use crate::types::DataType as Ty; use crate::util::hash_util::Crc32FastBuilder; #[test] diff --git a/src/common/src/system_param/mod.rs b/src/common/src/system_param/mod.rs index 06d4cce2e4e6b..f71324cb4e55d 100644 --- a/src/common/src/system_param/mod.rs +++ b/src/common/src/system_param/mod.rs @@ -77,10 +77,10 @@ macro_rules! for_all_params { { barrier_interval_ms, u32, Some(1000_u32), true, "The interval of periodic barrier.", }, { checkpoint_frequency, u64, Some(1_u64), true, "There will be a checkpoint for every n barriers.", }, { sstable_size_mb, u32, Some(256_u32), false, "Target size of the Sstable.", }, - { parallel_compact_size_mb, u32, Some(512_u32), false, "", }, + { parallel_compact_size_mb, u32, Some(512_u32), false, "The size of parallel task for one compact/flush job.", }, { block_size_kb, u32, Some(64_u32), false, "Size of each block in bytes in SST.", }, { bloom_false_positive, f64, Some(0.001_f64), false, "False positive probability of bloom filter.", }, - { state_store, String, None, false, "", }, + { state_store, String, None, false, "URL for the state store", }, { data_directory, String, None, false, "Remote directory for storing data and metadata objects.", }, { backup_storage_url, String, None, true, "Remote storage url for storing snapshots.", }, { backup_storage_directory, String, None, true, "Remote directory for storing snapshots.", }, diff --git a/src/common/src/types/decimal.rs b/src/common/src/types/decimal.rs index b6031a1665540..b38dbb4822e68 100644 --- a/src/common/src/types/decimal.rs +++ b/src/common/src/types/decimal.rs @@ -13,14 +13,15 @@ // limitations under the License. use std::fmt::Debug; -use std::io::{Read, Write}; +use std::io::{Cursor, Read, Write}; use std::ops::{Add, Div, Mul, Neg, Rem, Sub}; +use byteorder::{BigEndian, ReadBytesExt}; use bytes::{BufMut, Bytes, BytesMut}; use num_traits::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedSub, Num, One, Zero, }; -use postgres_types::{accepts, to_sql_checked, IsNull, ToSql, Type}; +use postgres_types::{accepts, to_sql_checked, FromSql, IsNull, ToSql, Type}; use risingwave_common_estimate_size::ZeroHeapSize; use rust_decimal::prelude::FromStr; use rust_decimal::{Decimal as RustDecimal, Error, MathematicalOps as _, RoundingStrategy}; @@ -145,6 +146,28 @@ impl ToSql for Decimal { } } +impl<'a> FromSql<'a> for Decimal { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + let mut rdr = Cursor::new(raw); + let _n_digits = rdr.read_u16::()?; + let _weight = rdr.read_i16::()?; + let sign = rdr.read_u16::()?; + match sign { + 0xC000 => Ok(Self::NaN), + 0xD000 => Ok(Self::PositiveInf), + 0xF000 => Ok(Self::NegativeInf), + _ => RustDecimal::from_sql(ty, raw).map(Self::Normalized), + } + } + + fn accepts(ty: &Type) -> bool { + matches!(*ty, Type::NUMERIC) + } +} + macro_rules! impl_convert_int { ($T:ty) => { impl core::convert::From<$T> for Decimal { diff --git a/src/common/src/types/interval.rs b/src/common/src/types/interval.rs index 4436181059d78..495561561e937 100644 --- a/src/common/src/types/interval.rs +++ b/src/common/src/types/interval.rs @@ -22,15 +22,12 @@ use std::sync::LazyLock; use byteorder::{BigEndian, NetworkEndian, ReadBytesExt, WriteBytesExt}; use bytes::BytesMut; -use chrono::Timelike; use num_traits::{CheckedAdd, CheckedNeg, CheckedSub, Zero}; -use postgres_types::{to_sql_checked, FromSql}; +use postgres_types::to_sql_checked; use regex::Regex; -use risingwave_common_estimate_size::ZeroHeapSize; use risingwave_pb::data::PbInterval; use rust_decimal::prelude::Decimal; -use super::to_binary::ToBinary; use super::*; /// Every interval can be represented by a `Interval`. diff --git a/src/common/src/types/mod.rs b/src/common/src/types/mod.rs index 9364f438cfacf..bb41e75c8a4f8 100644 --- a/src/common/src/types/mod.rs +++ b/src/common/src/types/mod.rs @@ -17,7 +17,6 @@ // NOTE: When adding or modifying data types, remember to update the type matrix in // src/expr/macro/src/types.rs -use std::convert::TryFrom; use std::fmt::Debug; use std::hash::Hash; use std::str::FromStr; diff --git a/src/common/src/types/num256.rs b/src/common/src/types/num256.rs index 43c97fd8b155a..2acbcf636a037 100644 --- a/src/common/src/types/num256.rs +++ b/src/common/src/types/num256.rs @@ -335,7 +335,6 @@ impl EstimateSize for Int256 { #[cfg(test)] mod tests { use super::*; - use crate::types::F64; macro_rules! check_op { ($t:ty, $lhs:expr, $rhs:expr, [$($op:tt),+]) => { diff --git a/src/common/src/types/ordered.rs b/src/common/src/types/ordered.rs index 77a0588e06672..bb2b0c9f316c1 100644 --- a/src/common/src/types/ordered.rs +++ b/src/common/src/types/ordered.rs @@ -14,7 +14,7 @@ //! `ScalarImpl` and `Datum` wrappers that implement `PartialOrd` and `Ord` with default order type. -use std::cmp::{Ord, Ordering}; +use std::cmp::Ordering; use std::ops::Deref; use risingwave_common_estimate_size::EstimateSize; diff --git a/src/common/src/types/scalar_impl.rs b/src/common/src/types/scalar_impl.rs index db2732ca67c48..d222473fa6bbb 100644 --- a/src/common/src/types/scalar_impl.rs +++ b/src/common/src/types/scalar_impl.rs @@ -15,8 +15,6 @@ use std::hash::Hasher; use super::*; -use crate::array::list_array::{ListRef, ListValue}; -use crate::array::struct_array::{StructRef, StructValue}; use crate::{dispatch_scalar_ref_variants, dispatch_scalar_variants, for_all_native_types}; /// `ScalarPartialOrd` allows comparison between `Scalar` and `ScalarRef`. diff --git a/src/common/src/util/memcmp_encoding.rs b/src/common/src/util/memcmp_encoding.rs index 5709d42d226f8..7f262d0ff8d2c 100644 --- a/src/common/src/util/memcmp_encoding.rs +++ b/src/common/src/util/memcmp_encoding.rs @@ -333,15 +333,12 @@ pub fn decode_row( mod tests { use std::ops::Neg; - use itertools::Itertools; use rand::thread_rng; use super::*; - use crate::array::{DataChunk, ListValue, StructValue}; - use crate::row::{OwnedRow, RowExt}; - use crate::types::{DataType, FloatExt, ScalarImpl, F32}; - use crate::util::iter_util::ZipEqFast; - use crate::util::sort_util::{ColumnOrder, OrderType}; + use crate::array::{ListValue, StructValue}; + use crate::row::RowExt; + use crate::types::FloatExt; #[test] fn test_memcomparable() { diff --git a/src/common/src/util/panic.rs b/src/common/src/util/panic.rs index 43db76df4f768..517a040e03b09 100644 --- a/src/common/src/util/panic.rs +++ b/src/common/src/util/panic.rs @@ -71,8 +71,8 @@ pub fn is_catching_unwind() -> bool { } #[cfg(all(test, not(madsim)))] +#[expect(clippy::disallowed_methods)] mod tests { - #![allow(clippy::disallowed_methods)] use rusty_fork::rusty_fork_test; diff --git a/src/common/src/util/sort_util.rs b/src/common/src/util/sort_util.rs index 015222136d382..ff4bde652496d 100644 --- a/src/common/src/util/sort_util.rs +++ b/src/common/src/util/sort_util.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::cmp::{Ord, Ordering}; +use std::cmp::Ordering; use std::fmt; use std::sync::Arc; @@ -574,13 +574,11 @@ pub fn cmp_rows(lhs: impl Row, rhs: impl Row, order_types: &[OrderType]) -> Orde #[cfg(test)] mod tests { - use std::cmp::Ordering; - use itertools::Itertools; use super::*; - use crate::array::{DataChunk, ListValue, StructValue}; - use crate::row::{OwnedRow, Row}; + use crate::array::{ListValue, StructValue}; + use crate::row::OwnedRow; use crate::types::{DataType, Datum, ScalarImpl}; #[test] diff --git a/src/common/src/util/value_encoding/column_aware_row_encoding.rs b/src/common/src/util/value_encoding/column_aware_row_encoding.rs index 28fcb65c4d8db..1bfbc0c926410 100644 --- a/src/common/src/util/value_encoding/column_aware_row_encoding.rs +++ b/src/common/src/util/value_encoding/column_aware_row_encoding.rs @@ -26,7 +26,6 @@ use bitflags::bitflags; use super::*; use crate::catalog::ColumnId; -use crate::row::Row; // deprecated design of have a Width to represent number of datum // may be considered should `ColumnId` representation be optimized diff --git a/src/common/src/util/value_encoding/mod.rs b/src/common/src/util/value_encoding/mod.rs index 57e21c3782aa0..aa80c15617048 100644 --- a/src/common/src/util/value_encoding/mod.rs +++ b/src/common/src/util/value_encoding/mod.rs @@ -20,7 +20,7 @@ use either::{for_both, Either}; use enum_as_inner::EnumAsInner; use risingwave_pb::data::PbDatum; -use crate::array::{ArrayImpl, ListRef, ListValue, StructRef, StructValue}; +use crate::array::ArrayImpl; use crate::row::{Row, RowDeserializer as BasicDeserializer}; use crate::types::*; diff --git a/src/compute/src/lib.rs b/src/compute/src/lib.rs index 344e0c781eb5e..87f052470ea7d 100644 --- a/src/compute/src/lib.rs +++ b/src/compute/src/lib.rs @@ -76,10 +76,6 @@ pub struct ComputeNodeOpts { #[clap(long, env = "RW_META_ADDR", default_value = "http://127.0.0.1:5690")] pub meta_address: MetaAddressStrategy, - /// Payload format of connector sink rpc - #[clap(long, env = "RW_CONNECTOR_RPC_SINK_PAYLOAD_FORMAT")] - pub connector_rpc_sink_payload_format: Option, - /// The path of `risingwave.toml` configuration file. /// /// If empty, default configuration values will be used. @@ -91,7 +87,7 @@ pub struct ComputeNodeOpts { pub total_memory_bytes: usize, /// Reserved memory for the compute node in bytes. - /// If not set, a portion (default to 30%) for the total_memory_bytes will be used as the reserved memory. + /// If not set, a portion (default to 30%) for the `total_memory_bytes` will be used as the reserved memory. /// /// The total memory compute and storage can use is `total_memory_bytes` - `reserved_memory_bytes`. #[clap(long, env = "RW_RESERVED_MEMORY_BYTES")] diff --git a/src/compute/src/memory/config.rs b/src/compute/src/memory/config.rs index dffc950429a91..29afbf6feba82 100644 --- a/src/compute/src/memory/config.rs +++ b/src/compute/src/memory/config.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use foyer::memory::{LfuConfig, LruConfig, S3FifoConfig}; +use foyer::{LfuConfig, LruConfig, S3FifoConfig}; use risingwave_common::config::{ CacheEvictionConfig, EvictionConfig, StorageConfig, StorageMemoryConfig, MAX_BLOCK_CACHE_SHARD_BITS, MAX_META_CACHE_SHARD_BITS, MIN_BUFFER_SIZE_PER_SHARD, @@ -211,12 +211,22 @@ pub fn storage_memory_config( }), CacheEvictionConfig::S3Fifo { small_queue_capacity_ratio_in_percent, + ghost_queue_capacity_ratio_in_percent, + small_to_main_freq_threshold, } => EvictionConfig::S3Fifo(S3FifoConfig { small_queue_capacity_ratio: small_queue_capacity_ratio_in_percent.unwrap_or( risingwave_common::config::default::storage::small_queue_capacity_ratio_in_percent( ), ) as f64 / 100.0, + ghost_queue_capacity_ratio: ghost_queue_capacity_ratio_in_percent.unwrap_or( + risingwave_common::config::default::storage::ghost_queue_capacity_ratio_in_percent( + ), + ) as f64 + / 100.0, + small_to_main_freq_threshold: small_to_main_freq_threshold.unwrap_or( + risingwave_common::config::default::storage::small_to_main_freq_threshold(), + ), }), }; diff --git a/src/compute/src/rpc/service/config_service.rs b/src/compute/src/rpc/service/config_service.rs index 97793f71722c9..e827ef176413e 100644 --- a/src/compute/src/rpc/service/config_service.rs +++ b/src/compute/src/rpc/service/config_service.rs @@ -18,7 +18,6 @@ use risingwave_common::error::tonic::ToTonicStatus; use risingwave_pb::compute::config_service_server::ConfigService; use risingwave_pb::compute::{ShowConfigRequest, ShowConfigResponse}; use risingwave_stream::task::LocalStreamManager; -use serde_json; use tonic::{Code, Request, Response, Status}; pub struct ConfigServiceImpl { diff --git a/src/compute/src/server.rs b/src/compute/src/server.rs index 07dee5dfa4c8c..cb459c1368a8d 100644 --- a/src/compute/src/server.rs +++ b/src/compute/src/server.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::net::SocketAddr; -use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::time::Duration; @@ -42,7 +41,6 @@ use risingwave_connector::source::monitor::GLOBAL_SOURCE_METRICS; use risingwave_dml::dml_manager::DmlManager; use risingwave_pb::common::WorkerType; use risingwave_pb::compute::config_service_server::ConfigServiceServer; -use risingwave_pb::connector_service::SinkPayloadFormat; use risingwave_pb::health::health_server::HealthServer; use risingwave_pb::meta::add_worker_node_request::Property; use risingwave_pb::monitor_service::monitor_service_server::MonitorServiceServer; @@ -217,12 +215,6 @@ pub async fn compute_node_serve( )); let compaction_executor = Arc::new(CompactionExecutor::new(Some(1))); - let max_task_parallelism = Arc::new(AtomicU32::new( - (compaction_executor.worker_num() as f32 - * storage_opts.compactor_max_task_multiplier) - .ceil() as u32, - )); - let compactor_context = CompactorContext { storage_opts, sstable_store: storage.sstable_store(), @@ -235,8 +227,6 @@ pub async fn compute_node_serve( await_tree_reg: await_tree_config .clone() .map(new_compaction_await_tree_reg_ref), - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism, }; let (handle, shutdown_sender) = start_compactor( @@ -334,28 +324,9 @@ pub async fn compute_node_serve( config.server.metrics_level, ); - info!( - "connector param: payload_format={:?}", - opts.connector_rpc_sink_payload_format - ); - - let connector_params = risingwave_connector::ConnectorParams { - sink_payload_format: match opts.connector_rpc_sink_payload_format.as_deref() { - None | Some("stream_chunk") => SinkPayloadFormat::StreamChunk, - Some("json") => SinkPayloadFormat::Json, - _ => { - unreachable!( - "invalid sink payload format: {:?}. Should be either json or stream_chunk", - opts.connector_rpc_sink_payload_format - ) - } - }, - }; - // Initialize the streaming environment. let stream_env = StreamEnvironment::new( advertise_addr.clone(), - connector_params, stream_config, worker_id, state_store, diff --git a/src/compute/src/telemetry.rs b/src/compute/src/telemetry.rs index 143ec51c5bc39..c35a6063ae267 100644 --- a/src/compute/src/telemetry.rs +++ b/src/compute/src/telemetry.rs @@ -33,6 +33,7 @@ impl ComputeTelemetryCreator { #[async_trait::async_trait] impl TelemetryReportCreator for ComputeTelemetryCreator { + #[allow(refining_impl_trait)] async fn create_report( &self, tracking_id: String, diff --git a/src/config/docs.md b/src/config/docs.md index aa5cab015c7ee..d8f551f96fdf4 100644 --- a/src/config/docs.md +++ b/src/config/docs.md @@ -155,7 +155,7 @@ This page is automatically generated by `./risedev generate-example-config` | data_directory | Remote directory for storing data and metadata objects. | | | enable_tracing | Whether to enable distributed tracing. | false | | max_concurrent_creating_streaming_jobs | Max number of concurrent creating streaming jobs. | 1 | -| parallel_compact_size_mb | | 512 | +| parallel_compact_size_mb | The size of parallel task for one compact/flush job. | 512 | | pause_on_next_bootstrap | Whether to pause all data sources on next bootstrap. | false | | sstable_size_mb | Target size of the Sstable. | 256 | -| state_store | | | +| state_store | URL for the state store | | diff --git a/src/connector/Cargo.toml b/src/connector/Cargo.toml index 8e0a16c95f8bb..c8953e1c46d84 100644 --- a/src/connector/Cargo.toml +++ b/src/connector/Cargo.toml @@ -33,6 +33,7 @@ auto_impl = "1" await-tree = { workspace = true } aws-config = { workspace = true } aws-credential-types = { workspace = true } +aws-msk-iam-sasl-signer = "1.0.0" aws-sdk-kinesis = { workspace = true } aws-sdk-s3 = { workspace = true } aws-smithy-http = { workspace = true } @@ -43,6 +44,7 @@ aws-types = { workspace = true } base64 = "0.22" byteorder = "1" bytes = { version = "1", features = ["serde"] } +cfg-or-panic = "0.2" chrono = { version = "0.4", default-features = false, features = [ "clock", "std", diff --git a/src/connector/src/connector_common/common.rs b/src/connector/src/connector_common/common.rs index 302b68dd664a1..e5aa7529e3e97 100644 --- a/src/connector/src/connector_common/common.rs +++ b/src/connector/src/connector_common/common.rs @@ -44,6 +44,8 @@ use crate::source::nats::source::NatsOffset; pub const PRIVATE_LINK_BROKER_REWRITE_MAP_KEY: &str = "broker.rewrite.endpoints"; pub const PRIVATE_LINK_TARGETS_KEY: &str = "privatelink.targets"; +const AWS_MSK_IAM_AUTH: &str = "AWS_MSK_IAM"; + #[derive(Debug, Clone, Deserialize)] pub struct AwsPrivateLinkItem { pub az_id: Option, @@ -59,16 +61,28 @@ use aws_types::SdkConfig; /// A flatten config map for aws auth. #[derive(Deserialize, Debug, Clone, WithOptions)] pub struct AwsAuthProps { + #[serde(rename = "aws.region", alias = "region")] pub region: Option, - #[serde(alias = "endpoint_url")] + + #[serde( + rename = "aws.endpoint_url", + alias = "endpoint_url", + alias = "endpoint" + )] pub endpoint: Option, + #[serde(rename = "aws.credentials.access_key_id", alias = "access_key")] pub access_key: Option, + #[serde(rename = "aws.credentials.secret_access_key", alias = "secret_key")] pub secret_key: Option, + #[serde(rename = "aws.credentials.session_token", alias = "session_token")] pub session_token: Option, + /// IAM role + #[serde(rename = "aws.credentials.role.arn", alias = "arn")] pub arn: Option, - /// This field was added for kinesis. Not sure if it's useful for other connectors. - /// Please ignore it in the documentation for now. + /// external ID in IAM role trust policy + #[serde(rename = "aws.credentials.role.external_id", alias = "external_id")] pub external_id: Option, + #[serde(rename = "aws.profile", alias = "profile")] pub profile: Option, } @@ -181,7 +195,7 @@ pub struct KafkaCommon { #[serde(rename = "properties.ssl.key.password")] ssl_key_password: Option, - /// SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM and GSSAPI. + /// SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM, GSSAPI, and AWS_MSK_IAM. #[serde(rename = "properties.sasl.mechanism")] sasl_mechanism: Option, @@ -286,6 +300,13 @@ impl RdKafkaPropertiesCommon { impl KafkaCommon { pub(crate) fn set_security_properties(&self, config: &mut ClientConfig) { + // AWS_MSK_IAM + if self.is_aws_msk_iam() { + config.set("security.protocol", "SASL_SSL"); + config.set("sasl.mechanism", "OAUTHBEARER"); + return; + } + // Security protocol if let Some(security_protocol) = self.security_protocol.as_ref() { config.set("security.protocol", security_protocol); @@ -356,6 +377,16 @@ impl KafkaCommon { // Currently, we only support unsecured OAUTH. config.set("enable.sasl.oauthbearer.unsecure.jwt", "true"); } + + pub(crate) fn is_aws_msk_iam(&self) -> bool { + if let Some(sasl_mechanism) = self.sasl_mechanism.as_ref() + && sasl_mechanism == AWS_MSK_IAM_AUTH + { + true + } else { + false + } + } } #[derive(Clone, Debug, Deserialize, WithOptions)] diff --git a/src/connector/src/lib.rs b/src/connector/src/lib.rs index 70de9f8561a76..c866a68b298d6 100644 --- a/src/connector/src/lib.rs +++ b/src/connector/src/lib.rs @@ -41,7 +41,6 @@ use std::time::Duration; use duration_str::parse_std; -use risingwave_pb::connector_service::SinkPayloadFormat; use serde::de; pub mod aws_utils; @@ -64,19 +63,6 @@ pub use with_options::WithPropertiesExt; #[cfg(test)] mod with_options_test; -#[derive(Clone, Debug, Default)] -pub struct ConnectorParams { - pub sink_payload_format: SinkPayloadFormat, -} - -impl ConnectorParams { - pub fn new(sink_payload_format: SinkPayloadFormat) -> Self { - Self { - sink_payload_format, - } - } -} - pub(crate) fn deserialize_u32_from_string<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, diff --git a/src/connector/src/parser/avro/parser.rs b/src/connector/src/parser/avro/parser.rs index 65601d9c3a3e6..c1950313b5e5c 100644 --- a/src/connector/src/parser/avro/parser.rs +++ b/src/connector/src/parser/avro/parser.rs @@ -182,8 +182,8 @@ mod test { use std::path::PathBuf; use apache_avro::schema::RecordSchema; - use apache_avro::types::{Record, Value}; - use apache_avro::{Codec, Days, Duration, Millis, Months, Reader, Schema, Writer}; + use apache_avro::types::Record; + use apache_avro::{Codec, Days, Duration, Millis, Months, Writer}; use itertools::Itertools; use risingwave_common::array::Op; use risingwave_common::catalog::ColumnId; @@ -195,12 +195,9 @@ mod test { use super::*; use crate::connector_common::AwsAuthProps; - use crate::error::ConnectorResult; use crate::parser::plain_parser::PlainParser; use crate::parser::unified::avro::unix_epoch_days; - use crate::parser::{ - AccessBuilderImpl, EncodingType, SourceStreamChunkBuilder, SpecificParserConfig, - }; + use crate::parser::{AccessBuilderImpl, SourceStreamChunkBuilder, SpecificParserConfig}; use crate::source::{SourceColumnDesc, SourceContext}; fn test_data_path(file_name: &str) -> String { diff --git a/src/connector/src/parser/canal/simd_json_parser.rs b/src/connector/src/parser/canal/simd_json_parser.rs index 9bc236392eb48..ac8a63a6457e7 100644 --- a/src/connector/src/parser/canal/simd_json_parser.rs +++ b/src/connector/src/parser/canal/simd_json_parser.rs @@ -141,7 +141,6 @@ mod tests { use super::*; use crate::parser::SourceStreamChunkBuilder; - use crate::source::SourceColumnDesc; #[tokio::test] async fn test_data_types() { diff --git a/src/connector/src/parser/debezium/avro_parser.rs b/src/connector/src/parser/debezium/avro_parser.rs index 29d9139d221cf..3b11a1926f107 100644 --- a/src/connector/src/parser/debezium/avro_parser.rs +++ b/src/connector/src/parser/debezium/avro_parser.rs @@ -139,7 +139,6 @@ mod tests { use std::io::Read; use std::path::PathBuf; - use apache_avro::Schema; use itertools::Itertools; use maplit::{convert_args, hashmap}; use risingwave_common::array::Op; @@ -151,9 +150,7 @@ mod tests { use risingwave_pb::plan_common::{PbEncodeType, PbFormatType}; use super::*; - use crate::parser::{ - DebeziumAvroParserConfig, DebeziumParser, SourceStreamChunkBuilder, SpecificParserConfig, - }; + use crate::parser::{DebeziumParser, SourceStreamChunkBuilder, SpecificParserConfig}; use crate::source::{SourceColumnDesc, SourceContext}; const DEBEZIUM_AVRO_DATA: &[u8] = b"\x00\x00\x00\x00\x06\x00\x02\xd2\x0f\x0a\x53\x61\x6c\x6c\x79\x0c\x54\x68\x6f\x6d\x61\x73\x2a\x73\x61\x6c\x6c\x79\x2e\x74\x68\x6f\x6d\x61\x73\x40\x61\x63\x6d\x65\x2e\x63\x6f\x6d\x16\x32\x2e\x31\x2e\x32\x2e\x46\x69\x6e\x61\x6c\x0a\x6d\x79\x73\x71\x6c\x12\x64\x62\x73\x65\x72\x76\x65\x72\x31\xc0\xb4\xe8\xb7\xc9\x61\x00\x30\x66\x69\x72\x73\x74\x5f\x69\x6e\x5f\x64\x61\x74\x61\x5f\x63\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x12\x69\x6e\x76\x65\x6e\x74\x6f\x72\x79\x00\x02\x12\x63\x75\x73\x74\x6f\x6d\x65\x72\x73\x00\x00\x20\x6d\x79\x73\x71\x6c\x2d\x62\x69\x6e\x2e\x30\x30\x30\x30\x30\x33\x8c\x06\x00\x00\x00\x02\x72\x02\x92\xc3\xe8\xb7\xc9\x61\x00"; diff --git a/src/connector/src/parser/debezium/simd_json_parser.rs b/src/connector/src/parser/debezium/simd_json_parser.rs index 63cb939d81238..dff681687bc47 100644 --- a/src/connector/src/parser/debezium/simd_json_parser.rs +++ b/src/connector/src/parser/debezium/simd_json_parser.rs @@ -99,8 +99,6 @@ impl AccessBuilder for DebeziumMongoJsonAccessBuilder { #[cfg(test)] mod tests { - use std::convert::TryInto; - use chrono::{NaiveDate, NaiveTime}; use risingwave_common::array::{Op, StructValue}; use risingwave_common::catalog::ColumnId; diff --git a/src/connector/src/parser/mod.rs b/src/connector/src/parser/mod.rs index afdd07716634e..08e8bd3dd46ea 100644 --- a/src/connector/src/parser/mod.rs +++ b/src/connector/src/parser/mod.rs @@ -43,7 +43,7 @@ use self::avro::AvroAccessBuilder; use self::bytes_parser::BytesAccessBuilder; pub use self::mysql::mysql_row_to_owned_row; use self::plain_parser::PlainParser; -pub use self::postgres::{postgres_row_to_owned_row, EnumString}; +pub use self::postgres::postgres_row_to_owned_row; use self::simd_json_parser::DebeziumJsonAccessBuilder; pub use self::unified::json::TimestamptzHandling; use self::unified::AccessImpl; @@ -75,7 +75,9 @@ mod maxwell; mod mysql; pub mod plain_parser; mod postgres; + mod protobuf; +pub mod scalar_adapter; mod unified; mod upsert_parser; mod util; diff --git a/src/connector/src/parser/postgres.rs b/src/connector/src/parser/postgres.rs index 61d7e0b588875..f01484ad18fb2 100644 --- a/src/connector/src/parser/postgres.rs +++ b/src/connector/src/parser/postgres.rs @@ -12,23 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::str::FromStr; use std::sync::LazyLock; -use bytes::BytesMut; use chrono::{NaiveDate, Utc}; -use pg_bigdecimal::PgNumeric; use risingwave_common::catalog::Schema; use risingwave_common::log::LogSuppresser; use risingwave_common::row::OwnedRow; use risingwave_common::types::{ - DataType, Date, Decimal, Int256, Interval, JsonbVal, ListValue, ScalarImpl, Time, Timestamp, + DataType, Date, Decimal, Interval, JsonbVal, ListValue, ScalarImpl, Time, Timestamp, Timestamptz, }; -use rust_decimal::Decimal as RustDecimal; use thiserror_ext::AsReport; -use tokio_postgres::types::{to_sql_checked, FromSql, IsNull, Kind, ToSql, Type}; +use tokio_postgres::types::{Kind, Type}; +use crate::parser::scalar_adapter::ScalarAdapter; use crate::parser::util::log_error; static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); @@ -93,284 +90,225 @@ pub fn postgres_row_to_owned_row(row: tokio_postgres::Row, schema: &Schema) -> O for i in 0..schema.fields.len() { let rw_field = &schema.fields[i]; let name = rw_field.name.as_str(); - let datum = { - match &rw_field.data_type { - DataType::Boolean => { - handle_data_type!(row, i, name, bool) - } - DataType::Int16 => { - handle_data_type!(row, i, name, i16) - } - DataType::Int32 => { - handle_data_type!(row, i, name, i32) - } - DataType::Int64 => { - handle_data_type!(row, i, name, i64) - } - DataType::Float32 => { - handle_data_type!(row, i, name, f32) - } - DataType::Float64 => { - handle_data_type!(row, i, name, f64) - } - DataType::Decimal => { - handle_data_type!(row, i, name, RustDecimal, Decimal) + let datum = postgres_cell_to_scalar_impl(&row, &rw_field.data_type, i, name); + datums.push(datum); + } + OwnedRow::new(datums) +} + +fn postgres_cell_to_scalar_impl( + row: &tokio_postgres::Row, + data_type: &DataType, + i: usize, + name: &str, +) -> Option { + // We observe several incompatibility issue in Debezium's Postgres connector. We summarize them here: + // Issue #1. The null of enum list is not supported in Debezium. An enum list contains `NULL` will fallback to `NULL`. + // Issue #2. In our parser, when there's inf, -inf, nan or invalid item in a list, the whole list will fallback null. + match data_type { + DataType::Boolean => { + handle_data_type!(row, i, name, bool) + } + DataType::Int16 => { + handle_data_type!(row, i, name, i16) + } + DataType::Int32 => { + handle_data_type!(row, i, name, i32) + } + DataType::Int64 => { + handle_data_type!(row, i, name, i64) + } + DataType::Float32 => { + handle_data_type!(row, i, name, f32) + } + DataType::Float64 => { + handle_data_type!(row, i, name, f64) + } + DataType::Decimal => { + handle_data_type!(row, i, name, Decimal) + } + DataType::Int256 => { + // Currently in order to handle the decimal beyond RustDecimal, + // we use the PgNumeric type to convert the decimal to a string. + // Then we convert the string to Int256. + // Note: It's only used to map the numeric type in upstream Postgres to RisingWave's rw_int256. + let res = row.try_get::<_, Option>>(i); + match res { + Ok(val) => val.and_then(|v| v.into_scalar(DataType::Int256)), + Err(err) => { + log_error!(name, err, "parse numeric column as pg_numeric failed"); + None } - DataType::Int256 => { - // Currently in order to handle the decimal beyond RustDecimal, - // we use the PgNumeric type to convert the decimal to a string. - // Then we convert the string to Int256. - // Note: It's only used to map the numeric type in upstream Postgres to RisingWave's rw_int256. - let res = row.try_get::<_, Option>(i); - match res { - Ok(val) => pg_numeric_to_rw_int256(val, name), - Err(err) => { - log_error!(name, err, "parse numeric column as pg_numeric failed"); - None - } + } + } + DataType::Varchar => { + if let Kind::Enum(_) = row.columns()[i].type_().kind() { + // enum type needs to be handled separately + let res = row.try_get::<_, Option>>(i); + match res { + Ok(val) => val.and_then(|v| v.into_scalar(DataType::Varchar)), + Err(err) => { + log_error!(name, err, "parse enum column failed"); + None } } - DataType::Varchar => { - if let Kind::Enum(_) = row.columns()[i].type_().kind() { - // enum type needs to be handled separately - let res = row.try_get::<_, Option>(i); + } else { + match *row.columns()[i].type_() { + // Since we don't support UUID natively, adapt it to a VARCHAR column + Type::UUID => { + let res = row.try_get::<_, Option>>(i); match res { - Ok(val) => val.map(|v| ScalarImpl::from(v.0)), + Ok(val) => val.and_then(|v| v.into_scalar(DataType::Varchar)), Err(err) => { - log_error!(name, err, "parse enum column failed"); + log_error!(name, err, "parse uuid column failed"); None } } - } else { - match *row.columns()[i].type_() { - // Since we don't support UUID natively, adapt it to a VARCHAR column - Type::UUID => { - let res = row.try_get::<_, Option>(i); - match res { - Ok(val) => val.map(|v| ScalarImpl::from(v.to_string())), - Err(err) => { - log_error!(name, err, "parse uuid column failed"); - None - } - } - } - // we support converting NUMERIC to VARCHAR implicitly - Type::NUMERIC => { - // Currently in order to handle the decimal beyond RustDecimal, - // we use the PgNumeric type to convert the decimal to a string. - // Note: It's only used to map the numeric type in upstream Postgres to RisingWave's varchar. - let res = row.try_get::<_, Option>(i); - match res { - Ok(val) => pg_numeric_to_varchar(val), - Err(err) => { - log_error!( - name, - err, - "parse numeric column as pg_numeric failed" - ); - None - } - } - } - _ => { - handle_data_type!(row, i, name, String) + } + // we support converting NUMERIC to VARCHAR implicitly + Type::NUMERIC => { + // Currently in order to handle the decimal beyond RustDecimal, + // we use the PgNumeric type to convert the decimal to a string. + // Note: It's only used to map the numeric type in upstream Postgres to RisingWave's varchar. + let res = row.try_get::<_, Option>>(i); + match res { + Ok(val) => val.and_then(|v| v.into_scalar(DataType::Varchar)), + Err(err) => { + log_error!(name, err, "parse numeric column as pg_numeric failed"); + None } } } + _ => { + handle_data_type!(row, i, name, String) + } } - DataType::Date => { - handle_data_type!(row, i, name, NaiveDate, Date) - } - DataType::Time => { - handle_data_type!(row, i, name, chrono::NaiveTime, Time) - } - DataType::Timestamp => { - handle_data_type!(row, i, name, chrono::NaiveDateTime, Timestamp) - } - DataType::Timestamptz => { - handle_data_type!(row, i, name, chrono::DateTime, Timestamptz) + } + } + DataType::Date => { + handle_data_type!(row, i, name, NaiveDate, Date) + } + DataType::Time => { + handle_data_type!(row, i, name, chrono::NaiveTime, Time) + } + DataType::Timestamp => { + handle_data_type!(row, i, name, chrono::NaiveDateTime, Timestamp) + } + DataType::Timestamptz => { + handle_data_type!(row, i, name, chrono::DateTime, Timestamptz) + } + DataType::Bytea => { + let res = row.try_get::<_, Option>>(i); + match res { + Ok(val) => val.map(|v| ScalarImpl::from(v.into_boxed_slice())), + Err(err) => { + log_error!(name, err, "parse column failed"); + None } - DataType::Bytea => { - let res = row.try_get::<_, Option>>(i); - match res { - Ok(val) => val.map(|v| ScalarImpl::from(v.into_boxed_slice())), - Err(err) => { - log_error!(name, err, "parse column failed"); - None + } + } + DataType::Jsonb => { + handle_data_type!(row, i, name, serde_json::Value, JsonbVal) + } + DataType::Interval => { + handle_data_type!(row, i, name, Interval) + } + DataType::List(dtype) => { + let mut builder = dtype.create_array_builder(0); + // enum list needs to be handled separately + if let Kind::Array(item_type) = row.columns()[i].type_().kind() + && let Kind::Enum(_) = item_type.kind() + { + // Issue #1, we use ScalarAdapter instead of Option + let res = row.try_get::<_, Option>>>(i); + match res { + Ok(val) => { + if let Some(vec) = val { + for val in vec { + builder.append(val.into_scalar(DataType::Varchar)) + } } + Some(ScalarImpl::from(ListValue::new(builder.finish()))) + } + Err(err) => { + log_error!(name, err, "parse enum column failed"); + None } } - DataType::Jsonb => { - handle_data_type!(row, i, name, serde_json::Value, JsonbVal) - } - DataType::Interval => { - handle_data_type!(row, i, name, Interval) - } - DataType::List(dtype) => { - let mut builder = dtype.create_array_builder(0); - // enum list needs to be handled separately - if let Kind::Array(item_type) = row.columns()[i].type_().kind() - && let Kind::Enum(_) = item_type.kind() - { - // FIXME(Kexiang): The null of enum list is not supported in Debezium. - // As `NULL` in enum list is not supported in Debezium, we use `EnumString` - // instead of `Option` to handle enum to keep the behaviors aligned. - // An enum list contains `NULL` will be converted to `NULL`. - let res = row.try_get::<_, Option>>(i); + } else { + match **dtype { + DataType::Boolean => { + handle_list_data_type!(row, i, name, bool, builder); + } + DataType::Int16 => { + handle_list_data_type!(row, i, name, i16, builder); + } + DataType::Int32 => { + handle_list_data_type!(row, i, name, i32, builder); + } + DataType::Int64 => { + handle_list_data_type!(row, i, name, i64, builder); + } + DataType::Float32 => { + handle_list_data_type!(row, i, name, f32, builder); + } + DataType::Float64 => { + handle_list_data_type!(row, i, name, f64, builder); + } + DataType::Decimal => { + let res = row.try_get::<_, Option>>>>(i); match res { Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(Some(ScalarImpl::from(val.0))); - }); + if let Some(vec) = val { + builder = ScalarAdapter::build_scalar_in_list( + vec, + DataType::Decimal, + builder, + )?; } - Some(ScalarImpl::from(ListValue::new(builder.finish()))) } Err(err) => { - log_error!(name, err, "parse enum column failed"); - None - } - } - } else { - match **dtype { - DataType::Boolean => { - handle_list_data_type!(row, i, name, bool, builder); - } - DataType::Int16 => { - handle_list_data_type!(row, i, name, i16, builder); - } - DataType::Int32 => { - handle_list_data_type!(row, i, name, i32, builder); - } - DataType::Int64 => { - handle_list_data_type!(row, i, name, i64, builder); - } - DataType::Float32 => { - handle_list_data_type!(row, i, name, f32, builder); - } - DataType::Float64 => { - handle_list_data_type!(row, i, name, f64, builder); - } - DataType::Decimal => { - handle_list_data_type!(row, i, name, RustDecimal, builder, Decimal); - } - DataType::Date => { - handle_list_data_type!(row, i, name, NaiveDate, builder, Date); - } - DataType::Varchar => { - match *row.columns()[i].type_() { - // Since we don't support UUID natively, adapt it to a VARCHAR column - Type::UUID_ARRAY => { - let res = - row.try_get::<_, Option>>>(i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(val.map(|v| { - ScalarImpl::from(v.to_string()) - })) - }); - } - } - Err(err) => { - log_error!(name, err, "parse uuid column failed"); - } - }; - } - Type::NUMERIC_ARRAY => { - let res = - row.try_get::<_, Option>>>(i); - match res { - Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(pg_numeric_to_varchar(val)) - }); - } - } - Err(err) => { - log_error!( - name, - err, - "parse numeric list column as pg_numeric list failed" - ); - } - }; - } - _ => { - handle_list_data_type!(row, i, name, String, builder); - } - } - } - DataType::Time => { - handle_list_data_type!( - row, - i, - name, - chrono::NaiveTime, - builder, - Time - ); - } - DataType::Timestamp => { - handle_list_data_type!( - row, - i, - name, - chrono::NaiveDateTime, - builder, - Timestamp - ); - } - DataType::Timestamptz => { - handle_list_data_type!( - row, - i, - name, - chrono::DateTime, - builder, - Timestamptz - ); - } - DataType::Interval => { - handle_list_data_type!(row, i, name, Interval, builder); + log_error!(name, err, "parse uuid column failed"); } - DataType::Jsonb => { - handle_list_data_type!( - row, - i, - name, - serde_json::Value, - builder, - JsonbVal - ); - } - DataType::Bytea => { - let res = row.try_get::<_, Option>>>>(i); + }; + } + DataType::Date => { + handle_list_data_type!(row, i, name, NaiveDate, builder, Date); + } + DataType::Varchar => { + match *row.columns()[i].type_() { + // Since we don't support UUID natively, adapt it to a VARCHAR column + Type::UUID_ARRAY => { + let res = + row.try_get::<_, Option>>>>(i); match res { Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(val.map(|v| { - ScalarImpl::from(v.into_boxed_slice()) - })) - }) + if let Some(vec) = val { + for val in vec { + builder.append( + val.and_then(|v| { + v.into_scalar(DataType::Varchar) + }), + ) + } } } Err(err) => { - log_error!(name, err, "parse column failed"); + log_error!(name, err, "parse uuid column failed"); } - } + }; } - DataType::Int256 => { - let res = row.try_get::<_, Option>>>(i); + Type::NUMERIC_ARRAY => { + let res = + row.try_get::<_, Option>>>>(i); match res { Ok(val) => { - if let Some(v) = val { - v.into_iter().for_each(|val| { - builder.append(pg_numeric_to_rw_int256(val, name)) - }); + if let Some(vec) = val { + builder = ScalarAdapter::build_scalar_in_list( + vec, + DataType::Varchar, + builder, + )?; } } Err(err) => { @@ -382,104 +320,92 @@ pub fn postgres_row_to_owned_row(row: tokio_postgres::Row, schema: &Schema) -> O } }; } - DataType::Struct(_) | DataType::List(_) | DataType::Serial => { - tracing::warn!( - "unsupported List data type {:?}, set the List to empty", - **dtype + _ => { + handle_list_data_type!(row, i, name, String, builder); + } + } + } + DataType::Time => { + handle_list_data_type!(row, i, name, chrono::NaiveTime, builder, Time); + } + DataType::Timestamp => { + handle_list_data_type!( + row, + i, + name, + chrono::NaiveDateTime, + builder, + Timestamp + ); + } + DataType::Timestamptz => { + handle_list_data_type!( + row, + i, + name, + chrono::DateTime, + builder, + Timestamptz + ); + } + DataType::Interval => { + handle_list_data_type!(row, i, name, Interval, builder); + } + DataType::Jsonb => { + handle_list_data_type!(row, i, name, serde_json::Value, builder, JsonbVal); + } + DataType::Bytea => { + let res = row.try_get::<_, Option>>>>(i); + match res { + Ok(val) => { + if let Some(v) = val { + v.into_iter().for_each(|val| { + builder.append( + val.map(|v| ScalarImpl::from(v.into_boxed_slice())), + ) + }) + } + } + Err(err) => { + log_error!(name, err, "parse column failed"); + } + } + } + DataType::Int256 => { + let res = row.try_get::<_, Option>>>>(i); + match res { + Ok(val) => { + if let Some(vec) = val { + builder = ScalarAdapter::build_scalar_in_list( + vec, + DataType::Int256, + builder, + )?; + } + } + Err(err) => { + log_error!( + name, + err, + "parse numeric list column as pg_numeric list failed" ); } }; - Some(ScalarImpl::from(ListValue::new(builder.finish()))) } - } - DataType::Struct(_) | DataType::Serial => { - // Interval, Struct, List are not supported - tracing::warn!(rw_field.name, ?rw_field.data_type, "unsupported data type, set to null"); - None - } + DataType::Struct(_) | DataType::List(_) | DataType::Serial => { + tracing::warn!( + "unsupported List data type {:?}, set the List to empty", + **dtype + ); + } + }; + Some(ScalarImpl::from(ListValue::new(builder.finish()))) } - }; - datums.push(datum); - } - OwnedRow::new(datums) -} - -fn pg_numeric_to_rw_int256(val: Option, name: &str) -> Option { - let string = pg_numeric_to_string(val)?; - match Int256::from_str(string.as_str()) { - Ok(num) => Some(ScalarImpl::from(num)), - Err(err) => { - log_error!(name, err, "parse numeric string as rw_int256 failed"); - None } - } -} - -fn pg_numeric_to_varchar(val: Option) -> Option { - pg_numeric_to_string(val).map(ScalarImpl::from) -} - -fn pg_numeric_to_string(val: Option) -> Option { - if let Some(pg_numeric) = val { - // TODO(kexiang): NEGATIVE_INFINITY -> -Infinity, POSITIVE_INFINITY -> Infinity, NAN -> NaN - // The current implementation is to ensure consistency with the behavior of cdc event parsor. - match pg_numeric { - PgNumeric::NegativeInf => Some(String::from("NEGATIVE_INFINITY")), - PgNumeric::Normalized(big_decimal) => Some(big_decimal.to_string()), - PgNumeric::PositiveInf => Some(String::from("POSITIVE_INFINITY")), - PgNumeric::NaN => Some(String::from("NAN")), - } - } else { - // NULL - None - } -} - -#[derive(Clone, Debug)] -pub struct EnumString(pub String); - -impl<'a> FromSql<'a> for EnumString { - fn from_sql( - _ty: &Type, - raw: &'a [u8], - ) -> Result> { - Ok(EnumString(String::from_utf8_lossy(raw).into_owned())) - } - - fn accepts(ty: &Type) -> bool { - matches!(ty.kind(), Kind::Enum(_)) - } -} - -impl ToSql for EnumString { - to_sql_checked!(); - - fn accepts(ty: &Type) -> bool { - matches!(ty.kind(), Kind::Enum(_)) - } - - fn to_sql( - &self, - ty: &Type, - out: &mut BytesMut, - ) -> Result> - where - Self: Sized, - { - match ty.kind() { - Kind::Enum(e) => { - if e.contains(&self.0) { - out.extend_from_slice(self.0.as_bytes()); - Ok(IsNull::No) - } else { - Err(format!( - "EnumString value {} is not in the enum type {:?}", - self.0, e - ) - .into()) - } - } - _ => Err("EnumString can only be used with ENUM types".into()), + DataType::Struct(_) | DataType::Serial => { + // Interval and Struct are not supported + tracing::warn!(name, ?data_type, "unsupported data type, set to null"); + None } } } @@ -488,7 +414,7 @@ impl ToSql for EnumString { mod tests { use tokio_postgres::NoTls; - use crate::parser::postgres::EnumString; + use crate::parser::scalar_adapter::EnumString; const DB: &str = "postgres"; const USER: &str = "kexiang"; diff --git a/src/connector/src/parser/protobuf/parser.rs b/src/connector/src/parser/protobuf/parser.rs index e9ae317ca5ebc..7dcb502b1f674 100644 --- a/src/connector/src/parser/protobuf/parser.rs +++ b/src/connector/src/parser/protobuf/parser.rs @@ -583,7 +583,7 @@ mod test { use std::path::PathBuf; use prost::Message; - use risingwave_common::types::{DataType, ListValue, StructType}; + use risingwave_common::types::StructType; use risingwave_pb::catalog::StreamSourceInfo; use risingwave_pb::data::data_type::PbTypeName; use risingwave_pb::plan_common::{PbEncodeType, PbFormatType}; diff --git a/src/connector/src/parser/scalar_adapter.rs b/src/connector/src/parser/scalar_adapter.rs new file mode 100644 index 0000000000000..6a6546a8d7600 --- /dev/null +++ b/src/connector/src/parser/scalar_adapter.rs @@ -0,0 +1,272 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use std::str::FromStr; + +use anyhow::anyhow; +use bytes::BytesMut; +use pg_bigdecimal::PgNumeric; +use risingwave_common::types::{DataType, Decimal, Int256, ScalarImpl, ScalarRefImpl}; +use thiserror_ext::AsReport; +use tokio_postgres::types::{to_sql_checked, FromSql, IsNull, Kind, ToSql, Type}; + +use crate::error::ConnectorResult; + +#[derive(Clone, Debug)] +pub struct EnumString(pub String); + +impl<'a> FromSql<'a> for EnumString { + fn from_sql( + _ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(EnumString(String::from_utf8_lossy(raw).into_owned())) + } + + fn accepts(ty: &Type) -> bool { + matches!(ty.kind(), Kind::Enum(_)) + } +} + +impl ToSql for EnumString { + to_sql_checked!(); + + fn to_sql( + &self, + ty: &Type, + out: &mut BytesMut, + ) -> Result> + where + Self: Sized, + { + match ty.kind() { + Kind::Enum(e) => { + if e.contains(&self.0) { + out.extend_from_slice(self.0.as_bytes()); + Ok(IsNull::No) + } else { + Err(format!( + "EnumString value {} is not in the enum type {:?}", + self.0, e + ) + .into()) + } + } + _ => Err("EnumString can only be used with ENUM types".into()), + } + } + + fn accepts(ty: &Type) -> bool { + matches!(ty.kind(), Kind::Enum(_)) + } +} + +/// Adapter for `ScalarImpl` to Postgres data type, +/// which can be used to encode/decode to/from Postgres value. +#[derive(Debug)] +pub(crate) enum ScalarAdapter<'a> { + Builtin(ScalarRefImpl<'a>), + Uuid(uuid::Uuid), + Numeric(PgNumeric), + Enum(EnumString), +} + +impl ToSql for ScalarAdapter<'_> { + to_sql_checked!(); + + fn to_sql( + &self, + ty: &Type, + out: &mut bytes::BytesMut, + ) -> Result> { + match self { + ScalarAdapter::Builtin(v) => v.to_sql(ty, out), + ScalarAdapter::Uuid(v) => v.to_sql(ty, out), + ScalarAdapter::Numeric(v) => v.to_sql(ty, out), + ScalarAdapter::Enum(v) => v.to_sql(ty, out), + } + } + + fn accepts(_ty: &Type) -> bool { + true + } +} + +/// convert from Postgres uuid, numeric and enum to `ScalarAdapter` +impl<'a> FromSql<'a> for ScalarAdapter<'_> { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + match ty.kind() { + Kind::Simple => match *ty { + Type::UUID => { + let uuid = uuid::Uuid::from_sql(ty, raw)?; + Ok(ScalarAdapter::Uuid(uuid)) + } + Type::NUMERIC => { + let numeric = PgNumeric::from_sql(ty, raw)?; + Ok(ScalarAdapter::Numeric(numeric)) + } + _ => Err(anyhow!("failed to convert type {:?} to ScalarAdapter", ty).into()), + }, + Kind::Enum(_) => Ok(ScalarAdapter::Enum(EnumString::from_sql(ty, raw)?)), + _ => Err(anyhow!("failed to convert type {:?} to ScalarAdapter", ty).into()), + } + } + + fn accepts(ty: &Type) -> bool { + matches!(ty, &Type::UUID | &Type::NUMERIC) || ::accepts(ty) + } +} + +impl ScalarAdapter<'_> { + pub fn name(&self) -> &'static str { + match self { + ScalarAdapter::Builtin(_) => "Builtin", + ScalarAdapter::Uuid(_) => "Uuid", + ScalarAdapter::Numeric(_) => "Numeric", + ScalarAdapter::Enum(_) => "Enum", + } + } + + /// convert `ScalarRefImpl` to `ScalarAdapter` so that we can correctly encode to postgres value + pub(crate) fn from_scalar<'a>( + scalar: ScalarRefImpl<'a>, + ty: &Type, + ) -> ConnectorResult> { + Ok(match (scalar, ty, ty.kind()) { + (ScalarRefImpl::Utf8(s), &Type::UUID, _) => ScalarAdapter::Uuid(s.parse()?), + (ScalarRefImpl::Utf8(s), &Type::NUMERIC, _) => { + ScalarAdapter::Numeric(string_to_pg_numeric(s)?) + } + (ScalarRefImpl::Int256(s), &Type::NUMERIC, _) => { + ScalarAdapter::Numeric(string_to_pg_numeric(&s.to_string())?) + } + (ScalarRefImpl::Utf8(s), _, Kind::Enum(_)) => { + ScalarAdapter::Enum(EnumString(s.to_owned())) + } + _ => ScalarAdapter::Builtin(scalar), + }) + } + + pub fn into_scalar(self, ty: DataType) -> Option { + match (&self, &ty) { + (ScalarAdapter::Builtin(scalar), _) => Some(scalar.into_scalar_impl()), + (ScalarAdapter::Uuid(uuid), &DataType::Varchar) => { + Some(ScalarImpl::from(uuid.to_string())) + } + (ScalarAdapter::Numeric(numeric), &DataType::Varchar) => { + Some(ScalarImpl::from(pg_numeric_to_string(numeric))) + } + (ScalarAdapter::Numeric(numeric), &DataType::Int256) => { + pg_numeric_to_rw_int256(numeric) + } + (ScalarAdapter::Enum(EnumString(s)), &DataType::Varchar) => Some(ScalarImpl::from(s)), + _ => { + tracing::error!( + adapter = self.name(), + rw_type = ty.pg_name(), + "failed to convert from ScalarAdapter: invalid conversion" + ); + None + } + } + } + + pub fn build_scalar_in_list( + vec: Vec>>, + ty: DataType, + mut builder: risingwave_common::array::ArrayBuilderImpl, + ) -> Option { + for val in vec { + let scalar = match (val, &ty) { + (Some(ScalarAdapter::Numeric(numeric)), &DataType::Varchar) => { + if pg_numeric_is_special(&numeric) { + return None; + } else { + Some(ScalarImpl::from(pg_numeric_to_string(&numeric))) + } + } + (Some(ScalarAdapter::Numeric(numeric)), &DataType::Int256) => match numeric { + PgNumeric::Normalized(big_decimal) => { + match Int256::from_str(big_decimal.to_string().as_str()) { + Ok(num) => Some(ScalarImpl::from(num)), + Err(err) => { + tracing::error!(error = %err.as_report(), "parse pg-numeric as rw_int256 failed"); + return None; + } + } + } + _ => return None, + }, + (Some(ScalarAdapter::Numeric(numeric)), &DataType::Decimal) => match numeric { + PgNumeric::Normalized(big_decimal) => { + match Decimal::from_str(big_decimal.to_string().as_str()) { + Ok(num) => Some(ScalarImpl::from(num)), + Err(err) => { + tracing::error!(error = %err.as_report(), "parse pg-numeric as rw-numeric failed (likely out-of-range"); + return None; + } + } + } + _ => return None, + }, + (Some(_), _) => unreachable!( + "into_scalar_in_list should only be called with ScalarAdapter::Numeric types" + ), + (None, _) => None, + }; + builder.append(scalar); + } + Some(builder) + } +} + +fn pg_numeric_is_special(val: &PgNumeric) -> bool { + matches!( + val, + PgNumeric::NegativeInf | PgNumeric::PositiveInf | PgNumeric::NaN + ) +} + +fn pg_numeric_to_rw_int256(val: &PgNumeric) -> Option { + match Int256::from_str(pg_numeric_to_string(val).as_str()) { + Ok(num) => Some(ScalarImpl::from(num)), + Err(err) => { + tracing::error!(error = %err.as_report(), "failed to convert PgNumeric to Int256"); + None + } + } +} + +fn pg_numeric_to_string(val: &PgNumeric) -> String { + // TODO(kexiang): NEGATIVE_INFINITY -> -Infinity, POSITIVE_INFINITY -> Infinity, NAN -> NaN + // The current implementation is to ensure consistency with the behavior of cdc event parsor. + match val { + PgNumeric::NegativeInf => String::from("NEGATIVE_INFINITY"), + PgNumeric::Normalized(big_decimal) => big_decimal.to_string(), + PgNumeric::PositiveInf => String::from("POSITIVE_INFINITY"), + PgNumeric::NaN => String::from("NAN"), + } +} + +fn string_to_pg_numeric(s: &str) -> super::ConnectorResult { + Ok(match s { + "NEGATIVE_INFINITY" => PgNumeric::NegativeInf, + "POSITIVE_INFINITY" => PgNumeric::PositiveInf, + "NAN" => PgNumeric::NaN, + _ => PgNumeric::Normalized(s.parse().unwrap()), + }) +} diff --git a/src/connector/src/parser/unified/avro.rs b/src/connector/src/parser/unified/avro.rs index 4e7d134f42554..257ca2926c342 100644 --- a/src/connector/src/parser/unified/avro.rs +++ b/src/connector/src/parser/unified/avro.rs @@ -431,7 +431,7 @@ pub(crate) fn unix_epoch_days() -> i32 { #[cfg(test)] mod tests { use apache_avro::Decimal as AvroDecimal; - use risingwave_common::types::{Decimal, Timestamptz}; + use risingwave_common::types::Decimal; use super::*; diff --git a/src/connector/src/parser/unified/json.rs b/src/connector/src/parser/unified/json.rs index d9deb73d582d9..11c569832268e 100644 --- a/src/connector/src/parser/unified/json.rs +++ b/src/connector/src/parser/unified/json.rs @@ -385,10 +385,16 @@ impl JsonParseOptions { .map_err(|_| create_error())? .into() } - - (Some(DataType::Decimal), ValueType::String) => ScalarImpl::Decimal( - Decimal::from_str(value.as_str().unwrap()).map_err(|_err| create_error())?, - ), + (Some(DataType::Decimal), ValueType::String) => { + let str = value.as_str().unwrap(); + // the following values are special string generated by Debezium and should be handled separately + match str { + "NAN" => ScalarImpl::Decimal(Decimal::NaN), + "POSITIVE_INFINITY" => ScalarImpl::Decimal(Decimal::PositiveInf), + "NEGATIVE_INFINITY" => ScalarImpl::Decimal(Decimal::NegativeInf), + _ => ScalarImpl::Decimal(Decimal::from_str(str).map_err(|_err| create_error())?), + } + } (Some(DataType::Decimal), ValueType::Object) => { // ref https://github.com/risingwavelabs/risingwave/issues/10628 // handle debezium json (variable scale): {"scale": int, "value": bytes} diff --git a/src/connector/src/sink/encoder/avro.rs b/src/connector/src/sink/encoder/avro.rs index 924beb281eda7..20d8a0997a7ff 100644 --- a/src/connector/src/sink/encoder/avro.rs +++ b/src/connector/src/sink/encoder/avro.rs @@ -526,7 +526,7 @@ mod tests { test_ok( &DataType::Int64, - Some(ScalarImpl::Int64(std::i64::MAX)), + Some(ScalarImpl::Int64(i64::MAX)), r#""long""#, Value::Long(i64::MAX), ); diff --git a/src/connector/src/sink/encoder/json.rs b/src/connector/src/sink/encoder/json.rs index 6eb7ca5cd1b50..2ee8bfcc0d27c 100644 --- a/src/connector/src/sink/encoder/json.rs +++ b/src/connector/src/sink/encoder/json.rs @@ -436,8 +436,8 @@ fn type_as_json_schema(rw_type: &DataType) -> Map { mod tests { use risingwave_common::types::{ - DataType, Date, Decimal, Interval, Scalar, ScalarImpl, StructRef, StructType, StructValue, - Time, Timestamp, + Date, Decimal, Interval, Scalar, ScalarImpl, StructRef, StructType, StructValue, Time, + Timestamp, }; use super::*; @@ -485,7 +485,7 @@ mod tests { data_type: DataType::Int64, ..mock_field.clone() }, - Some(ScalarImpl::Int64(std::i64::MAX).as_scalar_ref_impl()), + Some(ScalarImpl::Int64(i64::MAX).as_scalar_ref_impl()), DateHandlingMode::FromCe, TimestampHandlingMode::String, TimestamptzHandlingMode::UtcString, @@ -495,7 +495,7 @@ mod tests { .unwrap(); assert_eq!( serde_json::to_string(&int64_value).unwrap(), - std::i64::MAX.to_string() + i64::MAX.to_string() ); // https://github.com/debezium/debezium/blob/main/debezium-core/src/main/java/io/debezium/time/ZonedTimestamp.java diff --git a/src/connector/src/sink/kafka.rs b/src/connector/src/sink/kafka.rs index f15173a4aabf6..8313ea20dafa7 100644 --- a/src/connector/src/sink/kafka.rs +++ b/src/connector/src/sink/kafka.rs @@ -35,7 +35,9 @@ use with_options::WithOptions; use super::catalog::{SinkFormat, SinkFormatDesc}; use super::{Sink, SinkError, SinkParam}; -use crate::connector_common::{KafkaCommon, KafkaPrivateLinkCommon, RdKafkaPropertiesCommon}; +use crate::connector_common::{ + AwsAuthProps, KafkaCommon, KafkaPrivateLinkCommon, RdKafkaPropertiesCommon, +}; use crate::sink::catalog::desc::SinkDesc; use crate::sink::formatter::SinkFormatterImpl; use crate::sink::log_store::DeliveryFutureManagerAddFuture; @@ -43,7 +45,9 @@ use crate::sink::writer::{ AsyncTruncateLogSinkerOf, AsyncTruncateSinkWriter, AsyncTruncateSinkWriterExt, FormattedSink, }; use crate::sink::{DummySinkCommitCoordinator, Result, SinkWriterParam}; -use crate::source::kafka::{KafkaProperties, KafkaSplitEnumerator, PrivateLinkProducerContext}; +use crate::source::kafka::{ + KafkaContextCommon, KafkaProperties, KafkaSplitEnumerator, RwProducerContext, +}; use crate::source::{SourceEnumeratorContext, SplitEnumerator}; use crate::{ deserialize_duration_from_string, deserialize_u32_from_string, dispatch_sink_formatter_impl, @@ -244,6 +248,9 @@ pub struct KafkaConfig { #[serde(flatten)] pub privatelink_common: KafkaPrivateLinkCommon, + + #[serde(flatten)] + pub aws_auth_props: AwsAuthProps, } impl KafkaConfig { @@ -274,6 +281,7 @@ impl From for KafkaProperties { rdkafka_properties_common: val.rdkafka_properties_common, rdkafka_properties_consumer: Default::default(), privatelink_common: val.privatelink_common, + aws_auth_props: val.aws_auth_props, unknown_fields: Default::default(), } } @@ -393,7 +401,7 @@ const KAFKA_WRITER_MAX_QUEUE_SIZE_RATIO: f32 = 1.2; const KAFKA_WRITER_MAX_QUEUE_SIZE: usize = 100000; struct KafkaPayloadWriter<'a> { - inner: &'a FutureProducer, + inner: &'a FutureProducer, add_future: DeliveryFutureManagerAddFuture<'a, KafkaSinkDeliveryFuture>, config: &'a KafkaConfig, } @@ -402,13 +410,13 @@ pub type KafkaSinkDeliveryFuture = impl TryFuture + pub struct KafkaSinkWriter { formatter: SinkFormatterImpl, - inner: FutureProducer, + inner: FutureProducer, config: KafkaConfig, } impl KafkaSinkWriter { async fn new(config: KafkaConfig, formatter: SinkFormatterImpl) -> Result { - let inner: FutureProducer = { + let inner: FutureProducer = { let mut c = ClientConfig::new(); // KafkaConfig configuration @@ -419,13 +427,16 @@ impl KafkaSinkWriter { c.set("bootstrap.servers", &config.common.brokers); // Create the producer context, will be used to create the producer - let producer_ctx = PrivateLinkProducerContext::new( - config.privatelink_common.broker_rewrite_map.clone(), - // fixme: enable kafka native metrics for sink + let broker_rewrite_map = config.privatelink_common.broker_rewrite_map.clone(); + let ctx_common = KafkaContextCommon::new( + broker_rewrite_map, None, None, - )?; - + config.aws_auth_props.clone(), + config.common.is_aws_msk_iam(), + ) + .await?; + let producer_ctx = RwProducerContext::new(ctx_common); // Generate the producer c.create_with_context(producer_ctx).await? }; diff --git a/src/connector/src/sink/mod.rs b/src/connector/src/sink/mod.rs index d281a97ef6c26..2ef4bb953b67e 100644 --- a/src/connector/src/sink/mod.rs +++ b/src/connector/src/sink/mod.rs @@ -71,7 +71,6 @@ use crate::sink::catalog::desc::SinkDesc; use crate::sink::catalog::{SinkCatalog, SinkId}; use crate::sink::log_store::{LogReader, LogStoreReadItem, LogStoreResult, TruncateOffset}; use crate::sink::writer::SinkWriter; -use crate::ConnectorParams; const BOUNDED_CHANNEL_SIZE: usize = 16; #[macro_export] @@ -288,7 +287,6 @@ impl SinkMetrics { #[derive(Clone)] pub struct SinkWriterParam { - pub connector_params: ConnectorParams, pub executor_id: u64, pub vnode_bitmap: Option, pub meta_client: Option, @@ -326,7 +324,6 @@ impl SinkMetaClient { impl SinkWriterParam { pub fn for_test() -> Self { SinkWriterParam { - connector_params: Default::default(), executor_id: Default::default(), vnode_bitmap: Default::default(), meta_client: Default::default(), diff --git a/src/connector/src/sink/redis.rs b/src/connector/src/sink/redis.rs index 9da1d8b4d3f03..6baeccdd32ed8 100644 --- a/src/connector/src/sink/redis.rs +++ b/src/connector/src/sink/redis.rs @@ -378,8 +378,8 @@ mod test { use std::collections::BTreeMap; use rdkafka::message::FromBytes; - use risingwave_common::array::{Array, I32Array, Op, StreamChunk, Utf8Array}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::{Array, I32Array, Op, Utf8Array}; + use risingwave_common::catalog::Field; use risingwave_common::types::DataType; use risingwave_common::util::iter_util::ZipEqDebug; diff --git a/src/connector/src/sink/remote.rs b/src/connector/src/sink/remote.rs index 3ab7e90a69367..63fc60ecc4510 100644 --- a/src/connector/src/sink/remote.rs +++ b/src/connector/src/sink/remote.rs @@ -42,8 +42,8 @@ use risingwave_pb::connector_service::sink_writer_stream_request::{ use risingwave_pb::connector_service::{ sink_coordinator_stream_request, sink_coordinator_stream_response, sink_writer_stream_response, PbSinkParam, SinkCoordinatorStreamRequest, SinkCoordinatorStreamResponse, SinkMetadata, - SinkPayloadFormat, SinkWriterStreamRequest, SinkWriterStreamResponse, TableSchema, - ValidateSinkRequest, ValidateSinkResponse, + SinkWriterStreamRequest, SinkWriterStreamResponse, TableSchema, ValidateSinkRequest, + ValidateSinkResponse, }; use risingwave_rpc_client::error::RpcError; use risingwave_rpc_client::{ @@ -68,7 +68,6 @@ use crate::sink::{ DummySinkCommitCoordinator, LogSinker, Result, Sink, SinkCommitCoordinator, SinkError, SinkLogReader, SinkMetrics, SinkParam, SinkWriterParam, }; -use crate::ConnectorParams; macro_rules! def_remote_sink { () => { @@ -82,7 +81,6 @@ macro_rules! def_remote_sink { { HttpJava, HttpJavaSink, "http" } } }; - () => {}; ({ $variant_name:ident, $sink_type_name:ident, $sink_name:expr }) => { #[derive(Debug)] pub struct $variant_name; @@ -168,7 +166,7 @@ impl Sink for RemoteSink { async fn validate_remote_sink(param: &SinkParam, sink_name: &str) -> ConnectorResult<()> { if sink_name == ElasticSearchSink::SINK_NAME && param.downstream_pk.len() > 1 - && param.properties.get(ES_OPTION_DELIMITER).is_none() + && !param.properties.contains_key(ES_OPTION_DELIMITER) { bail!("Es sink only support single pk or pk with delimiter option"); } @@ -283,7 +281,7 @@ impl RemoteLogSinker { request_sender, response_stream, } = EmbeddedConnectorClient::new()? - .start_sink_writer_stream(payload_schema, sink_proto, SinkPayloadFormat::StreamChunk) + .start_sink_writer_stream(payload_schema, sink_proto) .await?; let sink_metrics = writer_param.sink_metrics; @@ -547,12 +545,8 @@ impl Sink for CoordinatedRemoteSink { "sink needs coordination should not have singleton input" )) })?, - CoordinatedRemoteSinkWriter::new( - self.param.clone(), - writer_param.connector_params, - writer_param.sink_metrics.clone(), - ) - .await?, + CoordinatedRemoteSinkWriter::new(self.param.clone(), writer_param.sink_metrics.clone()) + .await?, ) .await? .into_log_sinker(writer_param.sink_metrics)) @@ -572,18 +566,10 @@ pub struct CoordinatedRemoteSinkWriter { } impl CoordinatedRemoteSinkWriter { - pub async fn new( - param: SinkParam, - connector_params: ConnectorParams, - sink_metrics: SinkMetrics, - ) -> Result { + pub async fn new(param: SinkParam, sink_metrics: SinkMetrics) -> Result { let sink_proto = param.to_proto(); let stream_handle = EmbeddedConnectorClient::new()? - .start_sink_writer_stream( - sink_proto.table_schema.clone(), - sink_proto, - connector_params.sink_payload_format, - ) + .start_sink_writer_stream(sink_proto.table_schema.clone(), sink_proto) .await?; Ok(Self { @@ -717,13 +703,11 @@ impl EmbeddedConnectorClient { &self, payload_schema: Option, sink_proto: PbSinkParam, - sink_payload_format: SinkPayloadFormat, ) -> Result> { let (handle, first_rsp) = SinkWriterStreamHandle::initialize( SinkWriterStreamRequest { request: Some(SinkRequest::Start(StartSink { sink_param: Some(sink_proto), - format: sink_payload_format as i32, payload_schema, })), }, diff --git a/src/connector/src/sink/snowflake_connector.rs b/src/connector/src/sink/snowflake_connector.rs index eee4af2258b09..ee1cb90b70a52 100644 --- a/src/connector/src/sink/snowflake_connector.rs +++ b/src/connector/src/sink/snowflake_connector.rs @@ -19,7 +19,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::{anyhow, Context}; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use reqwest::{header, Client, RequestBuilder, StatusCode}; -use risingwave_common::config::ObjectStoreConfig; use risingwave_object_store::object::*; use serde::{Deserialize, Serialize}; diff --git a/src/connector/src/source/cdc/external/mock_external_table.rs b/src/connector/src/source/cdc/external/mock_external_table.rs index fcfb1505225ea..7242f5614d409 100644 --- a/src/connector/src/source/cdc/external/mock_external_table.rs +++ b/src/connector/src/source/cdc/external/mock_external_table.rs @@ -86,7 +86,7 @@ impl MockExternalTableReader { ]), ]; - let snapshots = vec![snap0]; + let snapshots = [snap0]; if snap_idx >= snapshots.len() { return Ok(()); } diff --git a/src/connector/src/source/cdc/external/postgres.rs b/src/connector/src/source/cdc/external/postgres.rs index 049fedd1485af..a4e3117b49519 100644 --- a/src/connector/src/source/cdc/external/postgres.rs +++ b/src/connector/src/source/cdc/external/postgres.rs @@ -32,6 +32,7 @@ use tokio_postgres::{NoTls, Statement}; use crate::error::{ConnectorError, ConnectorResult}; use crate::parser::postgres_row_to_owned_row; +use crate::parser::scalar_adapter::ScalarAdapter; #[cfg(not(madsim))] use crate::source::cdc::external::maybe_tls_connector::MaybeMakeTlsConnector; use crate::source::cdc::external::{ @@ -240,12 +241,12 @@ impl PostgresExternalTableReader { let stream = match start_pk_row { Some(ref pk_row) => { - let params: Vec>> = pk_row + let params: Vec>> = pk_row .iter() .zip_eq_fast(self.prepared_scan_stmt.params()) .map(|(datum, ty)| { datum - .map(|scalar| scalar_adapter::to_extra(scalar, ty)) + .map(|scalar| ScalarAdapter::from_scalar(scalar, ty)) .transpose() }) .try_collect()?; @@ -259,7 +260,7 @@ impl PostgresExternalTableReader { Self::get_normalized_table_name(&table_name), order_key, ); - let params: Vec>> = vec![]; + let params: Vec>> = vec![]; client.query_raw(&sql, ¶ms).await? } }; @@ -297,72 +298,6 @@ impl PostgresExternalTableReader { } } -mod scalar_adapter { - use pg_bigdecimal::PgNumeric; - use risingwave_common::types::ScalarRefImpl; - use tokio_postgres::types::{to_sql_checked, IsNull, Kind, ToSql, Type}; - - use crate::parser::EnumString; - - #[derive(Debug)] - pub(super) enum ScalarAdapter<'a> { - Builtin(ScalarRefImpl<'a>), - Uuid(uuid::Uuid), - Numeric(PgNumeric), - Enum(EnumString), - } - - impl ToSql for ScalarAdapter<'_> { - to_sql_checked!(); - - fn to_sql( - &self, - ty: &Type, - out: &mut bytes::BytesMut, - ) -> Result> { - match self { - ScalarAdapter::Builtin(v) => v.to_sql(ty, out), - ScalarAdapter::Uuid(v) => v.to_sql(ty, out), - ScalarAdapter::Numeric(v) => v.to_sql(ty, out), - ScalarAdapter::Enum(v) => v.to_sql(ty, out), - } - } - - fn accepts(_ty: &Type) -> bool { - true - } - } - - /// convert `ScalarRefImpl` to `ScalarAdapter` so that we can correctly encode to postgres value - pub(super) fn to_extra<'a>( - scalar: ScalarRefImpl<'a>, - ty: &Type, - ) -> super::ConnectorResult> { - Ok(match (scalar, ty, ty.kind()) { - (ScalarRefImpl::Utf8(s), &Type::UUID, _) => ScalarAdapter::Uuid(s.parse()?), - (ScalarRefImpl::Utf8(s), &Type::NUMERIC, _) => { - ScalarAdapter::Numeric(string_to_pg_numeric(s)?) - } - (ScalarRefImpl::Int256(s), &Type::NUMERIC, _) => { - ScalarAdapter::Numeric(string_to_pg_numeric(&s.to_string())?) - } - (ScalarRefImpl::Utf8(s), _, Kind::Enum(_)) => { - ScalarAdapter::Enum(EnumString(s.to_owned())) - } - _ => ScalarAdapter::Builtin(scalar), - }) - } - - fn string_to_pg_numeric(s: &str) -> super::ConnectorResult { - Ok(match s { - "NEGATIVE_INFINITY" => PgNumeric::NegativeInf, - "POSITIVE_INFINITY" => PgNumeric::PositiveInf, - "NAN" => PgNumeric::NaN, - _ => PgNumeric::Normalized(s.parse().unwrap()), - }) - } -} - #[cfg(test)] mod tests { use futures::pin_mut; diff --git a/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs b/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs index 96646ade0e1df..f65eacd9a9696 100644 --- a/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs +++ b/src/connector/src/source/filesystem/opendal_source/opendal_enumerator.rs @@ -56,10 +56,7 @@ impl SplitEnumerator for OpendalEnumerator { impl OpendalEnumerator { pub async fn list(&self) -> ConnectorResult { - let prefix = match &self.prefix { - Some(prefix) => prefix, - None => "", - }; + let prefix = self.prefix.as_deref().unwrap_or(""); let object_lister = self .op diff --git a/src/connector/src/source/kafka/client_context.rs b/src/connector/src/source/kafka/client_context.rs new file mode 100644 index 0000000000000..6a01da356ff51 --- /dev/null +++ b/src/connector/src/source/kafka/client_context.rs @@ -0,0 +1,227 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use std::collections::HashMap; +use std::sync::Arc; +use std::thread; + +use anyhow::anyhow; +use aws_config::Region; +use aws_sdk_s3::config::SharedCredentialsProvider; +use rdkafka::client::{BrokerAddr, OAuthToken}; +use rdkafka::consumer::ConsumerContext; +use rdkafka::message::DeliveryResult; +use rdkafka::producer::ProducerContext; +use rdkafka::{ClientContext, Statistics}; + +use super::private_link::{BrokerAddrRewriter, PrivateLinkContextRole}; +use super::stats::RdKafkaStats; +use crate::connector_common::AwsAuthProps; +use crate::error::ConnectorResult; + +struct IamAuthEnv { + credentials_provider: SharedCredentialsProvider, + region: Region, + // XXX(runji): madsim does not support `Handle` for now + #[cfg(not(madsim))] + rt: tokio::runtime::Handle, +} + +pub struct KafkaContextCommon { + // For VPC PrivateLink support + addr_rewriter: BrokerAddrRewriter, + + // identifier is required when reporting metrics as a label, usually it is compose by connector + // format (source or sink) and corresponding id (source_id or sink_id) + // identifier and metrics should be set at the same time + identifier: Option, + metrics: Option>, + + /// Credential and region for AWS MSK + auth: Option, +} + +impl KafkaContextCommon { + pub async fn new( + broker_rewrite_map: Option>, + identifier: Option, + metrics: Option>, + auth: AwsAuthProps, + is_aws_msk_iam: bool, + ) -> ConnectorResult { + let addr_rewriter = + BrokerAddrRewriter::new(PrivateLinkContextRole::Consumer, broker_rewrite_map)?; + let auth = if is_aws_msk_iam { + let config = auth.build_config().await?; + let credentials_provider = config + .credentials_provider() + .ok_or_else(|| anyhow!("missing aws credentials_provider"))?; + let region = config + .region() + .ok_or_else(|| anyhow!("missing aws region"))? + .clone(); + Some(IamAuthEnv { + credentials_provider, + region, + #[cfg(not(madsim))] + rt: tokio::runtime::Handle::current(), + }) + } else { + None + }; + Ok(Self { + addr_rewriter, + identifier, + metrics, + auth, + }) + } +} + +impl KafkaContextCommon { + fn stats(&self, statistics: Statistics) { + if let Some(metrics) = &self.metrics + && let Some(id) = &self.identifier + { + metrics.report(id.as_str(), &statistics); + } + } + + fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + self.addr_rewriter.rewrite_broker_addr(addr) + } + + // XXX(runji): oauth is ignored in simulation + #[cfg_or_panic::cfg_or_panic(not(madsim))] + fn generate_oauth_token( + &self, + _oauthbearer_config: Option<&str>, + ) -> Result> { + use aws_msk_iam_sasl_signer::generate_auth_token_from_credentials_provider; + use tokio::time::{timeout, Duration}; + + if let Some(IamAuthEnv { + credentials_provider, + region, + rt, + }) = &self.auth + { + let region = region.clone(); + let credentials_provider = credentials_provider.clone(); + let rt = rt.clone(); + let (token, expiration_time_ms) = { + let handle = thread::spawn(move || { + rt.block_on(async { + timeout( + Duration::from_secs(10), + generate_auth_token_from_credentials_provider( + region, + credentials_provider, + ), + ) + .await + }) + }); + handle.join().unwrap()?? + }; + Ok(OAuthToken { + token, + principal_name: "".to_string(), + lifetime_ms: expiration_time_ms, + }) + } else { + Err("must provide AWS IAM credential".into()) + } + } + + fn enable_refresh_oauth_token(&self) -> bool { + self.auth.is_some() + } +} + +pub type BoxConsumerContext = Box; + +/// Kafka consumer context used for private link, IAM auth, and metrics +pub struct RwConsumerContext { + common: KafkaContextCommon, +} + +impl RwConsumerContext { + pub fn new(common: KafkaContextCommon) -> Self { + Self { common } + } +} + +impl ClientContext for RwConsumerContext { + /// this func serves as a callback when `poll` is completed. + fn stats(&self, statistics: Statistics) { + self.common.stats(statistics); + } + + fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + self.common.rewrite_broker_addr(addr) + } + + fn generate_oauth_token( + &self, + oauthbearer_config: Option<&str>, + ) -> Result> { + self.common.generate_oauth_token(oauthbearer_config) + } + + fn enable_refresh_oauth_token(&self) -> bool { + self.common.enable_refresh_oauth_token() + } +} + +// required by the trait bound of BaseConsumer +impl ConsumerContext for RwConsumerContext {} + +/// Kafka producer context used for private link, IAM auth, and metrics +pub struct RwProducerContext { + common: KafkaContextCommon, +} + +impl RwProducerContext { + pub fn new(common: KafkaContextCommon) -> Self { + Self { common } + } +} + +impl ClientContext for RwProducerContext { + fn stats(&self, statistics: Statistics) { + self.common.stats(statistics); + } + + fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + self.common.rewrite_broker_addr(addr) + } + + fn generate_oauth_token( + &self, + oauthbearer_config: Option<&str>, + ) -> Result> { + self.common.generate_oauth_token(oauthbearer_config) + } + + fn enable_refresh_oauth_token(&self) -> bool { + self.common.enable_refresh_oauth_token() + } +} + +impl ProducerContext for RwProducerContext { + type DeliveryOpaque = (); + + fn delivery(&self, _: &DeliveryResult<'_>, _: Self::DeliveryOpaque) {} +} diff --git a/src/connector/src/source/kafka/enumerator/client.rs b/src/connector/src/source/kafka/enumerator/client.rs index 697fe421a5fb2..d4dddad67bfdf 100644 --- a/src/connector/src/source/kafka/enumerator/client.rs +++ b/src/connector/src/source/kafka/enumerator/client.rs @@ -25,7 +25,9 @@ use risingwave_common::bail; use crate::error::ConnectorResult; use crate::source::base::SplitEnumerator; use crate::source::kafka::split::KafkaSplit; -use crate::source::kafka::{KafkaProperties, PrivateLinkConsumerContext, KAFKA_ISOLATION_LEVEL}; +use crate::source::kafka::{ + KafkaContextCommon, KafkaProperties, RwConsumerContext, KAFKA_ISOLATION_LEVEL, +}; use crate::source::SourceEnumeratorContextRef; #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -40,7 +42,7 @@ pub struct KafkaSplitEnumerator { context: SourceEnumeratorContextRef, broker_address: String, topic: String, - client: BaseConsumer, + client: BaseConsumer, start_offset: KafkaEnumeratorOffset, // maybe used in the future for batch processing @@ -90,10 +92,30 @@ impl SplitEnumerator for KafkaSplitEnumerator { } // don't need kafka metrics from enumerator - let client_ctx = PrivateLinkConsumerContext::new(broker_rewrite_map, None, None)?; - let client: BaseConsumer = + let ctx_common = KafkaContextCommon::new( + broker_rewrite_map, + None, + None, + properties.aws_auth_props, + common_props.is_aws_msk_iam(), + ) + .await?; + let client_ctx = RwConsumerContext::new(ctx_common); + let client: BaseConsumer = config.create_with_context(client_ctx).await?; + // Note that before any SASL/OAUTHBEARER broker connection can succeed the application must call + // rd_kafka_oauthbearer_set_token() once – either directly or, more typically, by invoking either + // rd_kafka_poll(), rd_kafka_consumer_poll(), rd_kafka_queue_poll(), etc, in order to cause retrieval + // of an initial token to occur. + // https://docs.confluent.io/platform/current/clients/librdkafka/html/rdkafka_8h.html#a988395722598f63396d7a1bedb22adaf + if common_props.is_aws_msk_iam() { + #[cfg(not(madsim))] + client.poll(Duration::from_secs(10)); // note: this is a blocking call + #[cfg(madsim)] + client.poll(Duration::from_secs(10)).await; + } + Ok(Self { context, broker_address, @@ -129,6 +151,7 @@ impl SplitEnumerator for KafkaSplitEnumerator { partition, start_offset: start_offsets.remove(&partition).unwrap(), stop_offset: stop_offsets.remove(&partition).unwrap(), + hack_seek_to_latest: false, }) .collect(); @@ -232,6 +255,7 @@ impl KafkaSplitEnumerator { partition: *partition, start_offset: Some(start_offset), stop_offset: Some(stop_offset), + hack_seek_to_latest:false } }) .collect::>()) diff --git a/src/connector/src/source/kafka/mod.rs b/src/connector/src/source/kafka/mod.rs index 91d4ccce5ca88..4c560d30ecce6 100644 --- a/src/connector/src/source/kafka/mod.rs +++ b/src/connector/src/source/kafka/mod.rs @@ -17,16 +17,17 @@ use std::collections::HashMap; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; -use crate::connector_common::KafkaPrivateLinkCommon; +use crate::connector_common::{AwsAuthProps, KafkaPrivateLinkCommon}; +mod client_context; pub mod enumerator; pub mod private_link; pub mod source; pub mod split; pub mod stats; +pub use client_context::*; pub use enumerator::*; -pub use private_link::*; pub use source::*; pub use split::*; use with_options::WithOptions; @@ -137,6 +138,9 @@ pub struct KafkaProperties { #[serde(flatten)] pub privatelink_common: KafkaPrivateLinkCommon, + #[serde(flatten)] + pub aws_auth_props: AwsAuthProps, + #[serde(flatten)] pub unknown_fields: HashMap, } @@ -191,8 +195,6 @@ impl RdKafkaPropertiesConsumer { #[cfg(test)] mod test { - use std::collections::HashMap; - use maplit::hashmap; use super::*; diff --git a/src/connector/src/source/kafka/private_link.rs b/src/connector/src/source/kafka/private_link.rs index 6187078ae24fb..348749ba3f113 100644 --- a/src/connector/src/source/kafka/private_link.rs +++ b/src/connector/src/source/kafka/private_link.rs @@ -14,14 +14,10 @@ use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; -use std::sync::Arc; use anyhow::{anyhow, Context}; use itertools::Itertools; use rdkafka::client::BrokerAddr; -use rdkafka::consumer::ConsumerContext; -use rdkafka::producer::{DeliveryResult, ProducerContext}; -use rdkafka::{ClientContext, Statistics}; use risingwave_common::bail; use risingwave_common::util::addr::HostAddr; use risingwave_common::util::iter_util::ZipEqFast; @@ -31,14 +27,13 @@ use crate::connector_common::{ AwsPrivateLinkItem, PRIVATE_LINK_BROKER_REWRITE_MAP_KEY, PRIVATE_LINK_TARGETS_KEY, }; use crate::error::ConnectorResult; -use crate::source::kafka::stats::RdKafkaStats; use crate::source::kafka::{KAFKA_PROPS_BROKER_KEY, KAFKA_PROPS_BROKER_KEY_ALIAS}; pub const PRIVATELINK_ENDPOINT_KEY: &str = "privatelink.endpoint"; pub const CONNECTION_NAME_KEY: &str = "connection.name"; #[derive(Debug)] -enum PrivateLinkContextRole { +pub(super) enum PrivateLinkContextRole { Consumer, Producer, } @@ -52,13 +47,13 @@ impl std::fmt::Display for PrivateLinkContextRole { } } -struct BrokerAddrRewriter { +pub(super) struct BrokerAddrRewriter { role: PrivateLinkContextRole, rewrite_map: BTreeMap, } impl BrokerAddrRewriter { - fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { + pub(super) fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { let rewrote_addr = match self.rewrite_map.get(&addr) { None => addr, Some(new_addr) => new_addr.clone(), @@ -95,94 +90,6 @@ impl BrokerAddrRewriter { } } -pub struct PrivateLinkConsumerContext { - inner: BrokerAddrRewriter, - - // identifier is required when reporting metrics as a label, usually it is compose by connector - // format (source or sink) and corresponding id (source_id or sink_id) - // identifier and metrics should be set at the same time - identifier: Option, - metrics: Option>, -} - -impl PrivateLinkConsumerContext { - pub fn new( - broker_rewrite_map: Option>, - identifier: Option, - metrics: Option>, - ) -> ConnectorResult { - let inner = BrokerAddrRewriter::new(PrivateLinkContextRole::Consumer, broker_rewrite_map)?; - Ok(Self { - inner, - identifier, - metrics, - }) - } -} - -impl ClientContext for PrivateLinkConsumerContext { - /// this func serves as a callback when `poll` is completed. - fn stats(&self, statistics: Statistics) { - if let Some(metrics) = &self.metrics - && let Some(id) = &self.identifier - { - metrics.report(id.as_str(), &statistics); - } - } - - fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { - self.inner.rewrite_broker_addr(addr) - } -} - -// required by the trait bound of BaseConsumer -impl ConsumerContext for PrivateLinkConsumerContext {} - -pub struct PrivateLinkProducerContext { - inner: BrokerAddrRewriter, - - // identifier is required when reporting metrics as a label, usually it is compose by connector - // format (source or sink) and corresponding id (source_id or sink_id) - // identifier and metrics should be set at the same time - identifier: Option, - metrics: Option>, -} - -impl PrivateLinkProducerContext { - pub fn new( - broker_rewrite_map: Option>, - identifier: Option, - metrics: Option>, - ) -> ConnectorResult { - let inner = BrokerAddrRewriter::new(PrivateLinkContextRole::Producer, broker_rewrite_map)?; - Ok(Self { - inner, - identifier, - metrics, - }) - } -} - -impl ClientContext for PrivateLinkProducerContext { - fn stats(&self, statistics: Statistics) { - if let Some(metrics) = &self.metrics - && let Some(id) = &self.identifier - { - metrics.report(id.as_str(), &statistics); - } - } - - fn rewrite_broker_addr(&self, addr: BrokerAddr) -> BrokerAddr { - self.inner.rewrite_broker_addr(addr) - } -} - -impl ProducerContext for PrivateLinkProducerContext { - type DeliveryOpaque = (); - - fn delivery(&self, _: &DeliveryResult<'_>, _: Self::DeliveryOpaque) {} -} - #[inline(always)] fn kafka_props_broker_key(with_properties: &BTreeMap) -> &str { if with_properties.contains_key(KAFKA_PROPS_BROKER_KEY) { diff --git a/src/connector/src/source/kafka/source/reader.rs b/src/connector/src/source/kafka/source/reader.rs index 73c24dd5f810d..e3c59611d1acc 100644 --- a/src/connector/src/source/kafka/source/reader.rs +++ b/src/connector/src/source/kafka/source/reader.rs @@ -31,7 +31,7 @@ use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; use crate::source::base::SourceMessage; use crate::source::kafka::{ - KafkaProperties, KafkaSplit, PrivateLinkConsumerContext, KAFKA_ISOLATION_LEVEL, + KafkaContextCommon, KafkaProperties, KafkaSplit, RwConsumerContext, KAFKA_ISOLATION_LEVEL, }; use crate::source::{ into_chunk_stream, BoxChunkSourceStream, Column, CommonSplitReader, SourceContextRef, SplitId, @@ -39,7 +39,7 @@ use crate::source::{ }; pub struct KafkaSplitReader { - consumer: StreamConsumer, + consumer: StreamConsumer, offsets: HashMap, Option)>, bytes_per_second: usize, max_num_messages: usize, @@ -78,7 +78,7 @@ impl SplitReader for KafkaSplitReader { format!("rw-consumer-{}", source_ctx.fragment_id), ); - let client_ctx = PrivateLinkConsumerContext::new( + let ctx_common = KafkaContextCommon::new( broker_rewrite_map, Some(format!( "fragment-{}-source-{}-actor-{}", @@ -87,8 +87,13 @@ impl SplitReader for KafkaSplitReader { // thread consumer will keep polling in the background, we don't need to call `poll` // explicitly Some(source_ctx.metrics.rdkafka_native_metric.clone()), - )?; - let consumer: StreamConsumer = config + properties.aws_auth_props, + properties.common.is_aws_msk_iam(), + ) + .await?; + + let client_ctx = RwConsumerContext::new(ctx_common); + let consumer: StreamConsumer = config .set_log_level(RDKafkaLogLevel::Info) .create_with_context(client_ctx) .await @@ -101,7 +106,9 @@ impl SplitReader for KafkaSplitReader { for split in splits { offsets.insert(split.id(), (split.start_offset, split.stop_offset)); - if let Some(offset) = split.start_offset { + if split.hack_seek_to_latest { + tpl.add_partition_offset(split.topic.as_str(), split.partition, Offset::End)?; + } else if let Some(offset) = split.start_offset { tpl.add_partition_offset( split.topic.as_str(), split.partition, diff --git a/src/connector/src/source/kafka/split.rs b/src/connector/src/source/kafka/split.rs index a649a729c0397..7ad0d52619b4a 100644 --- a/src/connector/src/source/kafka/split.rs +++ b/src/connector/src/source/kafka/split.rs @@ -24,6 +24,12 @@ pub struct KafkaSplit { pub(crate) partition: i32, pub(crate) start_offset: Option, pub(crate) stop_offset: Option, + #[serde(skip)] + /// Used by shared source to hackily seek to the latest offset without fetching start offset first. + /// XXX: But why do we fetch low watermark for latest start offset..? + /// + /// When this is `true`, `start_offset` will be ignored. + pub(crate) hack_seek_to_latest: bool, } impl SplitMetaData for KafkaSplit { @@ -58,10 +64,16 @@ impl KafkaSplit { partition, start_offset, stop_offset, + hack_seek_to_latest: false, } } pub fn get_topic_and_partition(&self) -> (String, i32) { (self.topic.clone(), self.partition) } + + /// This should only be used for a fresh split, not persisted in state table yet. + pub fn seek_to_latest_offset(&mut self) { + self.hack_seek_to_latest = true; + } } diff --git a/src/connector/src/source/kinesis/enumerator/client.rs b/src/connector/src/source/kinesis/enumerator/client.rs index b8966f99ae1ac..840def08f6855 100644 --- a/src/connector/src/source/kinesis/enumerator/client.rs +++ b/src/connector/src/source/kinesis/enumerator/client.rs @@ -19,7 +19,7 @@ use aws_sdk_kinesis::Client as kinesis_client; use risingwave_common::bail; use crate::error::ConnectorResult as Result; -use crate::source::kinesis::split::{KinesisOffset, KinesisSplit}; +use crate::source::kinesis::split::KinesisOffset; use crate::source::kinesis::*; use crate::source::{SourceEnumeratorContextRef, SplitEnumerator}; diff --git a/src/connector/src/source/kinesis/mod.rs b/src/connector/src/source/kinesis/mod.rs index 019cefb500643..de393a5121961 100644 --- a/src/connector/src/source/kinesis/mod.rs +++ b/src/connector/src/source/kinesis/mod.rs @@ -65,8 +65,6 @@ impl crate::source::UnknownFields for KinesisProperties { #[cfg(test)] mod test { - use std::collections::HashMap; - use maplit::hashmap; use super::*; diff --git a/src/connector/src/source/kinesis/source/reader.rs b/src/connector/src/source/kinesis/source/reader.rs index c9026428d1df0..4504324c5fb96 100644 --- a/src/connector/src/source/kinesis/source/reader.rs +++ b/src/connector/src/source/kinesis/source/reader.rs @@ -24,7 +24,6 @@ use aws_sdk_kinesis::Client as KinesisClient; use futures_async_stream::try_stream; use risingwave_common::bail; use thiserror_ext::AsReport; -use tokio_retry; use crate::error::ConnectorResult as Result; use crate::parser::ParserConfig; @@ -306,7 +305,6 @@ mod tests { use super::*; use crate::connector_common::KinesisCommon; - use crate::source::kinesis::split::KinesisSplit; use crate::source::SourceContext; #[tokio::test] diff --git a/src/connector/src/source/nats/source/message.rs b/src/connector/src/source/nats/source/message.rs index 3944cd6bd4c71..55c91457d4bfc 100644 --- a/src/connector/src/source/nats/source/message.rs +++ b/src/connector/src/source/nats/source/message.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use async_nats; use async_nats::jetstream::Message; use crate::source::base::SourceMessage; diff --git a/src/connector/src/source/nexmark/source/reader.rs b/src/connector/src/source/nexmark/source/reader.rs index 6441baa154ae4..d1f10f4a00fc6 100644 --- a/src/connector/src/source/nexmark/source/reader.rs +++ b/src/connector/src/source/nexmark/source/reader.rs @@ -210,7 +210,7 @@ impl NexmarkSplitReader { #[cfg(test)] mod tests { use super::*; - use crate::source::nexmark::{NexmarkProperties, NexmarkSplitEnumerator}; + use crate::source::nexmark::NexmarkSplitEnumerator; use crate::source::{SourceContext, SourceEnumeratorContext, SplitEnumerator}; #[tokio::test] diff --git a/src/connector/src/source/reader/desc.rs b/src/connector/src/source/reader/desc.rs index 808ef1232c50c..cbd63a2a4906a 100644 --- a/src/connector/src/source/reader/desc.rs +++ b/src/connector/src/source/reader/desc.rs @@ -30,7 +30,6 @@ use crate::parser::additional_columns::add_partition_offset_cols; use crate::parser::{EncodingProperties, ProtocolProperties, SpecificParserConfig}; use crate::source::monitor::SourceMetrics; use crate::source::{SourceColumnDesc, SourceColumnType, UPSTREAM_SOURCE_KEY}; -use crate::ConnectorParams; pub const DEFAULT_CONNECTOR_MESSAGE_BUFFER_SIZE: usize = 16; @@ -59,7 +58,6 @@ pub struct SourceDescBuilder { row_id_index: Option, with_properties: HashMap, source_info: PbStreamSourceInfo, - connector_params: ConnectorParams, connector_message_buffer_size: usize, pk_indices: Vec, } @@ -72,7 +70,6 @@ impl SourceDescBuilder { row_id_index: Option, with_properties: HashMap, source_info: PbStreamSourceInfo, - connector_params: ConnectorParams, connector_message_buffer_size: usize, pk_indices: Vec, ) -> Self { @@ -82,7 +79,6 @@ impl SourceDescBuilder { row_id_index, with_properties, source_info, - connector_params, connector_message_buffer_size, pk_indices, } @@ -223,7 +219,6 @@ pub mod test_utils { row_id_index, with_properties, source_info, - connector_params: Default::default(), connector_message_buffer_size: DEFAULT_CONNECTOR_MESSAGE_BUFFER_SIZE, pk_indices, } diff --git a/src/connector/src/with_options_test.rs b/src/connector/src/with_options_test.rs index 155932ce90452..160964d7920c3 100644 --- a/src/connector/src/with_options_test.rs +++ b/src/connector/src/with_options_test.rs @@ -191,15 +191,15 @@ struct FieldInfo { #[serde(skip_serializing_if = "Option::is_none")] default: Option, - #[serde(skip_serializing_if = "Option::is_none")] - alias: Option, + #[serde(skip_serializing_if = "Vec::is_empty")] + alias: Vec, } #[derive(Default)] struct SerdeProperties { default_func: Option, rename: Option, - alias: Option, + alias: Vec, } #[derive(Debug, Serialize, Default)] @@ -270,7 +270,7 @@ fn extract_serde_properties(field: &Field) -> SerdeProperties { } } else if path.is_ident("alias") { if let Lit::Str(lit_str) = lit { - serde_props.alias = Some(lit_str.value()); + serde_props.alias.push(lit_str.value()); } } else if path.is_ident("default") { if let Lit::Str(lit_str) = lit { diff --git a/src/connector/with_options_sink.yaml b/src/connector/with_options_sink.yaml index aa6d83a143b3a..3dbe5d394a8e8 100644 --- a/src/connector/with_options_sink.yaml +++ b/src/connector/with_options_sink.yaml @@ -21,32 +21,49 @@ BigQueryConfig: field_type: usize required: false default: '1024' - - name: region + - name: aws.region field_type: String required: false - - name: endpoint + alias: + - region + - name: aws.endpoint_url field_type: String required: false - alias: endpoint_url - - name: access_key + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id field_type: String required: false - - name: secret_key + alias: + - access_key + - name: aws.credentials.secret_access_key field_type: String required: false - - name: session_token + alias: + - secret_key + - name: aws.credentials.session_token field_type: String required: false - - name: arn + alias: + - session_token + - name: aws.credentials.role.arn field_type: String + comments: IAM role required: false - - name: external_id + alias: + - arn + - name: aws.credentials.role.external_id field_type: String - comments: This field was added for kinesis. Not sure if it's useful for other connectors. Please ignore it in the documentation for now. + comments: external ID in IAM role trust policy required: false - - name: profile + alias: + - external_id + - name: aws.profile field_type: String required: false + alias: + - profile - name: r#type field_type: String required: true @@ -196,11 +213,13 @@ KafkaConfig: - name: properties.bootstrap.server field_type: String required: true - alias: kafka.brokers + alias: + - kafka.brokers - name: topic field_type: String required: true - alias: kafka.topic + alias: + - kafka.topic - name: properties.sync.call.timeout field_type: Duration required: false @@ -230,7 +249,7 @@ KafkaConfig: required: false - name: properties.sasl.mechanism field_type: String - comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM and GSSAPI. + comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM, GSSAPI, and AWS_MSK_IAM. required: false - name: properties.sasl.username field_type: String @@ -351,40 +370,91 @@ KafkaConfig: field_type: HashMap comments: This is generated from `private_link_targets` and `private_link_endpoint` in frontend, instead of given by users. required: false + - name: aws.region + field_type: String + required: false + alias: + - region + - name: aws.endpoint_url + field_type: String + required: false + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id + field_type: String + required: false + alias: + - access_key + - name: aws.credentials.secret_access_key + field_type: String + required: false + alias: + - secret_key + - name: aws.credentials.session_token + field_type: String + required: false + alias: + - session_token + - name: aws.credentials.role.arn + field_type: String + comments: IAM role + required: false + alias: + - arn + - name: aws.credentials.role.external_id + field_type: String + comments: external ID in IAM role trust policy + required: false + alias: + - external_id + - name: aws.profile + field_type: String + required: false + alias: + - profile KinesisSinkConfig: fields: - name: stream field_type: String required: true - alias: kinesis.stream.name + alias: + - kinesis.stream.name - name: aws.region field_type: String required: true - alias: kinesis.stream.region + alias: + - kinesis.stream.region - name: endpoint field_type: String required: false - alias: kinesis.endpoint + alias: + - kinesis.endpoint - name: aws.credentials.access_key_id field_type: String required: false - alias: kinesis.credentials.access + alias: + - kinesis.credentials.access - name: aws.credentials.secret_access_key field_type: String required: false - alias: kinesis.credentials.secret + alias: + - kinesis.credentials.secret - name: aws.credentials.session_token field_type: String required: false - alias: kinesis.credentials.session_token + alias: + - kinesis.credentials.session_token - name: aws.credentials.role.arn field_type: String required: false - alias: kinesis.assumerole.arn + alias: + - kinesis.assumerole.arn - name: aws.credentials.role.external_id field_type: String required: false - alias: kinesis.assumerole.external_id + alias: + - kinesis.assumerole.external_id MqttConfig: fields: - name: url @@ -497,11 +567,13 @@ PulsarConfig: - name: topic field_type: String required: true - alias: pulsar.topic + alias: + - pulsar.topic - name: service.url field_type: String required: true - alias: pulsar.service.url + alias: + - pulsar.service.url - name: auth.token field_type: String required: false @@ -517,32 +589,49 @@ PulsarConfig: - name: oauth.scope field_type: String required: false - - name: region + - name: aws.region field_type: String required: false - - name: endpoint + alias: + - region + - name: aws.endpoint_url field_type: String required: false - alias: endpoint_url - - name: access_key + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id field_type: String required: false - - name: secret_key + alias: + - access_key + - name: aws.credentials.secret_access_key field_type: String required: false - - name: session_token + alias: + - secret_key + - name: aws.credentials.session_token field_type: String required: false - - name: arn + alias: + - session_token + - name: aws.credentials.role.arn field_type: String + comments: IAM role required: false - - name: external_id + alias: + - arn + - name: aws.credentials.role.external_id field_type: String - comments: This field was added for kinesis. Not sure if it's useful for other connectors. Please ignore it in the documentation for now. + comments: external ID in IAM role trust policy required: false - - name: profile + alias: + - external_id + - name: aws.profile field_type: String required: false + alias: + - profile - name: properties.batch.size field_type: u32 required: false @@ -616,12 +705,14 @@ StarrocksConfig: field_type: String comments: The port to the MySQL server of `StarRocks` FE. required: true - alias: starrocks.query_port + alias: + - starrocks.query_port - name: starrocks.httpport field_type: String comments: The port to the HTTP server of `StarRocks` FE. required: true - alias: starrocks.http_port + alias: + - starrocks.http_port - name: starrocks.user field_type: String comments: The user name used to access the `StarRocks` database. diff --git a/src/connector/with_options_source.yaml b/src/connector/with_options_source.yaml index 2ea0e5f3488ee..68ed02624199e 100644 --- a/src/connector/with_options_source.yaml +++ b/src/connector/with_options_source.yaml @@ -80,20 +80,25 @@ KafkaProperties: field_type: String comments: This parameter is not intended to be exposed to users. This parameter specifies only for one parallelism. The parallelism of kafka source is equal to the parallelism passed into compute nodes. So users need to calculate how many bytes will be consumed in total across all the parallelism by themselves. required: false - alias: kafka.bytes.per.second + alias: + - kafka.bytes.per.second - name: max.num.messages field_type: String comments: This parameter is not intended to be exposed to users. This parameter specifies only for one parallelism. The parallelism of kafka source is equal to the parallelism passed into compute nodes. So users need to calculate how many messages will be consumed in total across all the parallelism by themselves. required: false - alias: kafka.max.num.messages + alias: + - kafka.max.num.messages - name: scan.startup.mode field_type: String required: false - alias: kafka.scan.startup.mode + alias: + - kafka.scan.startup.mode - name: scan.startup.timestamp.millis field_type: String required: false - alias: scan.startup.timestamp_millis + alias: + - kafka.time.offset + - scan.startup.timestamp_millis - name: upsert field_type: String comments: 'This parameter is used to tell `KafkaSplitReader` to produce `UpsertMessage`s, which combine both key and value fields of the Kafka message. TODO: Currently, `Option` can not be parsed here.' @@ -101,11 +106,13 @@ KafkaProperties: - name: properties.bootstrap.server field_type: String required: true - alias: kafka.brokers + alias: + - kafka.brokers - name: topic field_type: String required: true - alias: kafka.topic + alias: + - kafka.topic - name: properties.sync.call.timeout field_type: Duration required: false @@ -135,7 +142,7 @@ KafkaProperties: required: false - name: properties.sasl.mechanism field_type: String - comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM and GSSAPI. + comments: SASL mechanism if SASL is enabled. Currently support PLAIN, SCRAM, GSSAPI, and AWS_MSK_IAM. required: false - name: properties.sasl.username field_type: String @@ -214,47 +221,99 @@ KafkaProperties: field_type: HashMap comments: This is generated from `private_link_targets` and `private_link_endpoint` in frontend, instead of given by users. required: false + - name: aws.region + field_type: String + required: false + alias: + - region + - name: aws.endpoint_url + field_type: String + required: false + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id + field_type: String + required: false + alias: + - access_key + - name: aws.credentials.secret_access_key + field_type: String + required: false + alias: + - secret_key + - name: aws.credentials.session_token + field_type: String + required: false + alias: + - session_token + - name: aws.credentials.role.arn + field_type: String + comments: IAM role + required: false + alias: + - arn + - name: aws.credentials.role.external_id + field_type: String + comments: external ID in IAM role trust policy + required: false + alias: + - external_id + - name: aws.profile + field_type: String + required: false + alias: + - profile KinesisProperties: fields: - name: scan.startup.mode field_type: String required: false - alias: kinesis.scan.startup.mode + alias: + - kinesis.scan.startup.mode - name: scan.startup.timestamp.millis field_type: i64 required: false - name: stream field_type: String required: true - alias: kinesis.stream.name + alias: + - kinesis.stream.name - name: aws.region field_type: String required: true - alias: kinesis.stream.region + alias: + - kinesis.stream.region - name: endpoint field_type: String required: false - alias: kinesis.endpoint + alias: + - kinesis.endpoint - name: aws.credentials.access_key_id field_type: String required: false - alias: kinesis.credentials.access + alias: + - kinesis.credentials.access - name: aws.credentials.secret_access_key field_type: String required: false - alias: kinesis.credentials.secret + alias: + - kinesis.credentials.secret - name: aws.credentials.session_token field_type: String required: false - alias: kinesis.credentials.session_token + alias: + - kinesis.credentials.session_token - name: aws.credentials.role.arn field_type: String required: false - alias: kinesis.assumerole.arn + alias: + - kinesis.assumerole.arn - name: aws.credentials.role.external_id field_type: String required: false - alias: kinesis.assumerole.external_id + alias: + - kinesis.assumerole.external_id MqttProperties: fields: - name: url @@ -350,7 +409,8 @@ NatsProperties: - name: scan.startup.timestamp.millis field_type: String required: false - alias: scan.startup.timestamp_millis + alias: + - scan.startup.timestamp_millis - name: stream field_type: String required: true @@ -592,19 +652,24 @@ PulsarProperties: - name: scan.startup.mode field_type: String required: false - alias: pulsar.scan.startup.mode + alias: + - pulsar.scan.startup.mode - name: scan.startup.timestamp.millis field_type: String required: false - alias: scan.startup.timestamp_millis + alias: + - pulsar.time.offset + - scan.startup.timestamp_millis - name: topic field_type: String required: true - alias: pulsar.topic + alias: + - pulsar.topic - name: service.url field_type: String required: true - alias: pulsar.service.url + alias: + - pulsar.service.url - name: auth.token field_type: String required: false @@ -620,32 +685,49 @@ PulsarProperties: - name: oauth.scope field_type: String required: false - - name: region + - name: aws.region field_type: String required: false - - name: endpoint + alias: + - region + - name: aws.endpoint_url field_type: String required: false - alias: endpoint_url - - name: access_key + alias: + - endpoint_url + - endpoint + - name: aws.credentials.access_key_id field_type: String required: false - - name: secret_key + alias: + - access_key + - name: aws.credentials.secret_access_key field_type: String required: false - - name: session_token + alias: + - secret_key + - name: aws.credentials.session_token field_type: String required: false - - name: arn + alias: + - session_token + - name: aws.credentials.role.arn field_type: String + comments: IAM role required: false - - name: external_id + alias: + - arn + - name: aws.credentials.role.external_id field_type: String - comments: This field was added for kinesis. Not sure if it's useful for other connectors. Please ignore it in the documentation for now. + comments: external ID in IAM role trust policy required: false - - name: profile + alias: + - external_id + - name: aws.profile field_type: String required: false + alias: + - profile - name: iceberg.enabled field_type: bool required: false diff --git a/src/ctl/Cargo.toml b/src/ctl/Cargo.toml index 109d412ba1e89..0b9f7bb3f1e4c 100644 --- a/src/ctl/Cargo.toml +++ b/src/ctl/Cargo.toml @@ -20,6 +20,7 @@ chrono = "0.4" clap = { workspace = true } comfy-table = "7" etcd-client = { workspace = true } +foyer ={ workspace = true } futures = { version = "0.3", default-features = false, features = ["alloc"] } hex = "0.4" inquire = "0.7.0" diff --git a/src/ctl/src/cmd_impl/compute.rs b/src/ctl/src/cmd_impl/compute.rs index 0150c00058773..ff519efcfffa7 100644 --- a/src/ctl/src/cmd_impl/compute.rs +++ b/src/ctl/src/cmd_impl/compute.rs @@ -11,12 +11,10 @@ // 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. -use std::convert::TryFrom; use risingwave_common::config::{BatchConfig, StreamingConfig}; use risingwave_common::util::addr::HostAddr; use risingwave_rpc_client::ComputeClient; -use serde_json; pub async fn show_config(host: &str) -> anyhow::Result<()> { let listen_addr = HostAddr::try_from(host)?; diff --git a/src/ctl/src/cmd_impl/meta/reschedule.rs b/src/ctl/src/cmd_impl/meta/reschedule.rs index 64d3e229c9112..8d0a45be842fb 100644 --- a/src/ctl/src/cmd_impl/meta/reschedule.rs +++ b/src/ctl/src/cmd_impl/meta/reschedule.rs @@ -24,7 +24,6 @@ use risingwave_pb::meta::get_reschedule_plan_request::PbPolicy; use risingwave_pb::meta::table_fragments::ActorStatus; use risingwave_pb::meta::{GetClusterInfoResponse, GetReschedulePlanResponse, Reschedule}; use serde::{Deserialize, Serialize}; -use serde_yaml; use thiserror_ext::AsReport; use crate::CtlContext; diff --git a/src/ctl/src/cmd_impl/scale/resize.rs b/src/ctl/src/cmd_impl/scale/resize.rs index 6ef0b3c15d7e1..6b990c7865519 100644 --- a/src/ctl/src/cmd_impl/scale/resize.rs +++ b/src/ctl/src/cmd_impl/scale/resize.rs @@ -24,7 +24,6 @@ use risingwave_pb::meta::get_reschedule_plan_request::{ use risingwave_pb::meta::update_worker_node_schedulability_request::Schedulability; use risingwave_pb::meta::{GetClusterInfoResponse, GetReschedulePlanResponse}; use risingwave_stream::task::FragmentId; -use serde_yaml; use thiserror_ext::AsReport; use crate::cmd_impl::meta::ReschedulePayload; diff --git a/src/ctl/src/common/hummock_service.rs b/src/ctl/src/common/hummock_service.rs index 3f7ebdbb51bec..c71e7facc2870 100644 --- a/src/ctl/src/common/hummock_service.rs +++ b/src/ctl/src/common/hummock_service.rs @@ -17,11 +17,12 @@ use std::sync::Arc; use std::time::Duration; use anyhow::{anyhow, bail, Result}; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use foyer::HybridCacheBuilder; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_object_store::object::build_remote_object_store; use risingwave_rpc_client::MetaClient; use risingwave_storage::hummock::hummock_meta_client::MonitoredHummockMetaClient; -use risingwave_storage::hummock::{FileCache, HummockStorage, SstableStore, SstableStoreConfig}; +use risingwave_storage::hummock::{HummockStorage, SstableStore, SstableStoreConfig}; use risingwave_storage::monitor::{ global_hummock_state_store_metrics, CompactorMetrics, HummockMetrics, HummockStateStoreMetrics, MonitoredStateStore, MonitoredStorageMetrics, ObjectStoreMetrics, @@ -167,23 +168,30 @@ impl HummockServiceOpts { let opts = self.get_storage_opts(); + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(opts.meta_cache_capacity_mb * (1 << 20)) + .with_shards(opts.meta_cache_shard_num) + .storage() + .build() + .await?; + let block_cache_v2 = HybridCacheBuilder::new() + .memory(opts.block_cache_capacity_mb * (1 << 20)) + .with_shards(opts.block_cache_shard_num) + .storage() + .build() + .await?; + Ok(Arc::new(SstableStore::new(SstableStoreConfig { store: Arc::new(object_store), path: opts.data_directory, - block_cache_capacity: opts.block_cache_capacity_mb * (1 << 20), - meta_cache_capacity: opts.meta_cache_capacity_mb * (1 << 20), - block_cache_shard_num: opts.block_cache_shard_num, - meta_cache_shard_num: opts.meta_cache_shard_num, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_eviction: EvictionConfig::for_test(), prefetch_buffer_capacity: opts.block_cache_capacity_mb * (1 << 20), max_prefetch_block_number: opts.max_prefetch_block_number, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: Arc::new(global_hummock_state_store_metrics( MetricLevel::Disabled, )), + meta_cache_v2, + block_cache_v2, }))) } } diff --git a/src/dml/src/dml_manager.rs b/src/dml/src/dml_manager.rs index f6611b6c94c21..d3502f1f9d2de 100644 --- a/src/dml/src/dml_manager.rs +++ b/src/dml/src/dml_manager.rs @@ -170,7 +170,6 @@ mod tests { use risingwave_common::array::StreamChunk; use risingwave_common::catalog::INITIAL_TABLE_VERSION_ID; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::transaction::transaction_id::TxnId; use risingwave_common::types::DataType; use super::*; diff --git a/src/dml/src/table.rs b/src/dml/src/table.rs index 4981ebce2e8a3..a5d0ecf8c23a4 100644 --- a/src/dml/src/table.rs +++ b/src/dml/src/table.rs @@ -283,14 +283,11 @@ impl TableStreamReader { #[cfg(test)] mod tests { - use std::sync::Arc; - use assert_matches::assert_matches; use futures::StreamExt; use itertools::Itertools; - use risingwave_common::array::{Array, I64Array, Op, StreamChunk}; + use risingwave_common::array::{Array, I64Array, Op}; use risingwave_common::catalog::ColumnId; - use risingwave_common::transaction::transaction_id::TxnId; use risingwave_common::types::DataType; use super::*; diff --git a/src/error/src/macros.rs b/src/error/src/macros.rs index b3079aa281ce9..16a11d2dba4d6 100644 --- a/src/error/src/macros.rs +++ b/src/error/src/macros.rs @@ -149,9 +149,6 @@ pub use must_match; #[cfg(test)] mod ensure_tests { - use std::convert::Into; - use std::result::Result::Err; - use anyhow::anyhow; use thiserror::Error; diff --git a/src/expr/core/Cargo.toml b/src/expr/core/Cargo.toml index 3f5ca590026db..c811d81b34658 100644 --- a/src/expr/core/Cargo.toml +++ b/src/expr/core/Cargo.toml @@ -22,7 +22,9 @@ embedded-python-udf = ["arrow-udf-python"] [dependencies] anyhow = "1" arrow-array = { workspace = true } +arrow-flight = "50" arrow-schema = { workspace = true } +arrow-udf-flight = { workspace = true } arrow-udf-js = { workspace = true } arrow-udf-js-deno = { workspace = true, optional = true } arrow-udf-python = { workspace = true, optional = true } @@ -44,6 +46,7 @@ enum-as-inner = "0.6" futures = "0.3" futures-async-stream = { workspace = true } futures-util = "0.3" +ginepro = "0.7" itertools = { workspace = true } linkme = { version = "0.3", features = ["used_linker"] } md5 = "0.7" @@ -52,11 +55,11 @@ num-traits = "0.2" openssl = { version = "0.10", features = ["vendored"] } parse-display = "0.9" paste = "1" +prometheus = "0.13" risingwave_common = { workspace = true } risingwave_common_estimate_size = { workspace = true } risingwave_expr_macro = { path = "../macro" } risingwave_pb = { workspace = true } -risingwave_udf = { workspace = true } smallvec = "1" static_assertions = "1" thiserror = "1" @@ -65,6 +68,7 @@ tokio = { version = "0.2", package = "madsim-tokio", features = [ "rt-multi-thread", "macros", ] } +tonic = "0.10" tracing = "0.1" zstd = { version = "0.13", default-features = false } diff --git a/src/expr/core/src/error.rs b/src/expr/core/src/error.rs index 6688824093d2d..08562b3a973b7 100644 --- a/src/expr/core/src/error.rs +++ b/src/expr/core/src/error.rs @@ -99,7 +99,7 @@ pub enum ExprError { Udf( #[from] #[backtrace] - risingwave_udf::Error, + Box, ), #[error("not a constant")] @@ -119,6 +119,10 @@ pub enum ExprError { #[error("error in cryptography: {0}")] Cryptography(Box), + + /// Function error message returned by UDF. + #[error("{0}")] + Custom(String), } #[derive(Debug)] @@ -152,6 +156,12 @@ impl From for ExprError { } } +impl From for ExprError { + fn from(err: arrow_udf_flight::Error) -> Self { + Self::Udf(Box::new(err)) + } +} + /// A collection of multiple errors. #[derive(Error, Debug)] pub struct MultiExprError(Box<[ExprError]>); @@ -178,6 +188,12 @@ impl From> for MultiExprError { } } +impl FromIterator for MultiExprError { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + impl IntoIterator for MultiExprError { type IntoIter = std::vec::IntoIter; type Item = ExprError; diff --git a/src/expr/core/src/expr/and_or.rs b/src/expr/core/src/expr/and_or.rs index fac820a61b5b3..f265a8a208d29 100644 --- a/src/expr/core/src/expr/and_or.rs +++ b/src/expr/core/src/expr/and_or.rs @@ -142,9 +142,6 @@ fn or(l: Option, r: Option) -> Option { #[cfg(test)] mod tests { - use risingwave_common::array::DataChunk; - use risingwave_common::test_prelude::DataChunkTestExt; - use super::*; use crate::expr::build_from_pretty; diff --git a/src/expr/core/src/expr/expr_literal.rs b/src/expr/core/src/expr/expr_literal.rs index ff6fdb79197a2..e85e6f03af6d7 100644 --- a/src/expr/core/src/expr/expr_literal.rs +++ b/src/expr/core/src/expr/expr_literal.rs @@ -96,7 +96,6 @@ mod tests { use risingwave_pb::data::{PbDataType, PbDatum}; use risingwave_pb::expr::expr_node::RexNode::{self, Constant}; use risingwave_pb::expr::expr_node::Type; - use risingwave_pb::expr::ExprNode; use super::*; diff --git a/src/expr/core/src/expr/expr_some_all.rs b/src/expr/core/src/expr/expr_some_all.rs index 9250aa6d877cf..3f7bc7abbc9f5 100644 --- a/src/expr/core/src/expr/expr_some_all.rs +++ b/src/expr/core/src/expr/expr_some_all.rs @@ -272,7 +272,6 @@ impl Build for SomeAllExpression { #[cfg(test)] mod tests { - use risingwave_common::array::DataChunk; use risingwave_common::row::Row; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::ToOwnedDatum; diff --git a/src/expr/core/src/expr/expr_udf.rs b/src/expr/core/src/expr/expr_udf.rs index b9103b62649e5..54d3006dc3033 100644 --- a/src/expr/core/src/expr/expr_udf.rs +++ b/src/expr/core/src/expr/expr_udf.rs @@ -18,7 +18,9 @@ use std::sync::{Arc, LazyLock, Weak}; use std::time::Duration; use anyhow::{Context, Error}; +use arrow_array::RecordBatch; use arrow_schema::{Fields, Schema, SchemaRef}; +use arrow_udf_flight::Client as FlightClient; use arrow_udf_js::{CallMode as JsCallMode, Runtime as JsRuntime}; #[cfg(feature = "embedded-deno-udf")] use arrow_udf_js_deno::{CallMode as DenoCallMode, Runtime as DenoRuntime}; @@ -27,19 +29,25 @@ use arrow_udf_python::{CallMode as PythonCallMode, Runtime as PythonRuntime}; use arrow_udf_wasm::Runtime as WasmRuntime; use await_tree::InstrumentAwait; use cfg_or_panic::cfg_or_panic; +use ginepro::{LoadBalancedChannel, ResolutionStrategy}; use moka::sync::Cache; +use prometheus::{ + exponential_buckets, register_histogram_vec_with_registry, + register_int_counter_vec_with_registry, HistogramVec, IntCounter, IntCounterVec, Registry, +}; use risingwave_common::array::arrow::{FromArrow, ToArrow, UdfArrowConvert}; -use risingwave_common::array::{ArrayRef, DataChunk}; +use risingwave_common::array::{Array, ArrayRef, DataChunk}; +use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY; use risingwave_common::row::OwnedRow; use risingwave_common::types::{DataType, Datum}; +use risingwave_common::util::addr::HostAddr; use risingwave_expr::expr_context::FRAGMENT_ID; use risingwave_pb::expr::ExprNode; -use risingwave_udf::metrics::GLOBAL_METRICS; -use risingwave_udf::ArrowFlightUdfClient; +use thiserror_ext::AsReport; use super::{BoxedExpression, Build}; use crate::expr::Expression; -use crate::{bail, Result}; +use crate::{bail, ExprError, Result}; #[derive(Debug)] pub struct UserDefinedFunction { @@ -49,6 +57,8 @@ pub struct UserDefinedFunction { arg_schema: SchemaRef, imp: UdfImpl, identifier: String, + link: Option, + arrow_convert: UdfArrowConvert, span: await_tree::Span, /// Number of remaining successful calls until retry is enabled. /// This parameter is designed to prevent continuous retry on every call, which would increase delay. @@ -68,7 +78,7 @@ const INITIAL_RETRY_COUNT: u8 = 16; #[derive(Debug)] pub enum UdfImpl { - External(Arc), + External(Arc), Wasm(Arc), JavaScript(JsRuntime), #[cfg(feature = "embedded-python-udf")] @@ -115,7 +125,9 @@ impl Expression for UserDefinedFunction { impl UserDefinedFunction { async fn eval_inner(&self, input: &DataChunk) -> Result { // this will drop invisible rows - let arrow_input = UdfArrowConvert.to_record_batch(self.arg_schema.clone(), input)?; + let arrow_input = self + .arrow_convert + .to_record_batch(self.arg_schema.clone(), input)?; // metrics let metrics = &*GLOBAL_METRICS; @@ -123,10 +135,6 @@ impl UserDefinedFunction { let fragment_id = FRAGMENT_ID::try_with(ToOwned::to_owned) .unwrap_or(0) .to_string(); - let addr = match &self.imp { - UdfImpl::External(client) => client.get_addr(), - _ => "", - }; let language = match &self.imp { UdfImpl::Wasm(_) => "wasm", UdfImpl::JavaScript(_) => "javascript(quickjs)", @@ -136,7 +144,12 @@ impl UserDefinedFunction { UdfImpl::Deno(_) => "javascript(deno)", UdfImpl::External(_) => "external", }; - let labels: &[&str; 4] = &[addr, language, &self.identifier, fragment_id.as_str()]; + let labels: &[&str; 4] = &[ + self.link.as_deref().unwrap_or(""), + language, + &self.identifier, + fragment_id.as_str(), + ]; metrics .udf_input_chunk_rows .with_label_values(labels) @@ -164,28 +177,27 @@ impl UserDefinedFunction { UdfImpl::External(client) => { let disable_retry_count = self.disable_retry_count.load(Ordering::Relaxed); let result = if self.always_retry_on_network_error { - client - .call_with_always_retry_on_network_error( - &self.identifier, - arrow_input, - &fragment_id, - ) - .instrument_await(self.span.clone()) - .await + call_with_always_retry_on_network_error( + client, + &self.identifier, + &arrow_input, + &metrics.udf_retry_count.with_label_values(labels), + ) + .instrument_await(self.span.clone()) + .await } else { let result = if disable_retry_count != 0 { client - .call(&self.identifier, arrow_input) + .call(&self.identifier, &arrow_input) .instrument_await(self.span.clone()) .await } else { - client - .call_with_retry(&self.identifier, arrow_input) + call_with_retry(client, &self.identifier, &arrow_input) .instrument_await(self.span.clone()) .await }; let disable_retry_count = self.disable_retry_count.load(Ordering::Relaxed); - let connection_error = matches!(&result, Err(e) if e.is_connection_error()); + let connection_error = matches!(&result, Err(e) if is_connection_error(e)); if connection_error && disable_retry_count != INITIAL_RETRY_COUNT { // reset count on connection error self.disable_retry_count @@ -223,7 +235,7 @@ impl UserDefinedFunction { ); } - let output = UdfArrowConvert.from_record_batch(&arrow_output)?; + let output = self.arrow_convert.from_record_batch(&arrow_output)?; let output = output.uncompact(input.visibility().clone()); let Some(array) = output.columns().first() else { @@ -237,10 +249,72 @@ impl UserDefinedFunction { ); } + // handle optional error column + if let Some(errors) = output.columns().get(1) { + if errors.data_type() != DataType::Varchar { + bail!( + "UDF returned errors column with invalid type: {:?}", + errors.data_type() + ); + } + let errors = errors + .as_utf8() + .iter() + .filter_map(|msg| msg.map(|s| ExprError::Custom(s.into()))) + .collect(); + return Err(crate::ExprError::Multiple(array.clone(), errors)); + } + Ok(array.clone()) } } +/// Call a function, retry up to 5 times / 3s if connection is broken. +async fn call_with_retry( + client: &FlightClient, + id: &str, + input: &RecordBatch, +) -> Result { + let mut backoff = Duration::from_millis(100); + for i in 0..5 { + match client.call(id, input).await { + Err(err) if is_connection_error(&err) && i != 4 => { + tracing::error!(error = %err.as_report(), "UDF connection error. retry..."); + } + ret => return ret, + } + tokio::time::sleep(backoff).await; + backoff *= 2; + } + unreachable!() +} + +/// Always retry on connection error +async fn call_with_always_retry_on_network_error( + client: &FlightClient, + id: &str, + input: &RecordBatch, + retry_count: &IntCounter, +) -> Result { + let mut backoff = Duration::from_millis(100); + loop { + match client.call(id, input).await { + Err(err) if is_tonic_error(&err) => { + tracing::error!(error = %err.as_report(), "UDF tonic error. retry..."); + } + ret => { + if ret.is_err() { + tracing::error!(error = %ret.as_ref().unwrap_err().as_report(), "UDF error. exiting..."); + } + return ret; + } + } + retry_count.inc(); + tokio::time::sleep(backoff).await; + backoff *= 2; + } +} + impl Build for UserDefinedFunction { fn build( prost: &ExprNode, @@ -248,11 +322,7 @@ impl Build for UserDefinedFunction { ) -> Result { let return_type = DataType::from(prost.get_return_type().unwrap()); let udf = prost.get_rex_node().unwrap().as_udf().unwrap(); - - let arrow_return_type = UdfArrowConvert - .to_arrow_field("", &return_type)? - .data_type() - .clone(); + let mut arrow_convert = UdfArrowConvert::default(); #[cfg(not(feature = "embedded-deno-udf"))] let runtime = "quickjs"; @@ -271,6 +341,11 @@ impl Build for UserDefinedFunction { let wasm_binary = zstd::stream::decode_all(compressed_wasm_binary.as_slice()) .context("failed to decompress wasm binary")?; let runtime = get_or_create_wasm_runtime(&wasm_binary)?; + // backward compatibility + // see for details + if runtime.abi_version().0 <= 2 { + arrow_convert = UdfArrowConvert { legacy: true }; + } UdfImpl::Wasm(runtime) } "javascript" if runtime != "deno" => { @@ -283,7 +358,7 @@ impl Build for UserDefinedFunction { ); rt.add_function( identifier, - arrow_return_type, + arrow_convert.to_arrow_field("", &return_type)?, JsCallMode::CalledOnNullInput, &body, )?; @@ -324,7 +399,7 @@ impl Build for UserDefinedFunction { futures::executor::block_on(rt.add_function( identifier, - arrow_return_type, + arrow_convert.to_arrow_field("", &return_type)?, DenoCallMode::CalledOnNullInput, &body, ))?; @@ -337,7 +412,7 @@ impl Build for UserDefinedFunction { let body = udf.get_body()?; rt.add_function( identifier, - arrow_return_type, + arrow_convert.to_arrow_field("", &return_type)?, PythonCallMode::CalledOnNullInput, body, )?; @@ -346,7 +421,13 @@ impl Build for UserDefinedFunction { #[cfg(not(madsim))] _ => { let link = udf.get_link()?; - UdfImpl::External(get_or_create_flight_client(link)?) + let client = get_or_create_flight_client(link)?; + // backward compatibility + // see for details + if client.protocol_version() == 1 { + arrow_convert = UdfArrowConvert { legacy: true }; + } + UdfImpl::External(client) } #[cfg(madsim)] l => panic!("UDF language {l:?} is not supported on madsim"), @@ -355,7 +436,7 @@ impl Build for UserDefinedFunction { let arg_schema = Arc::new(Schema::new( udf.arg_types .iter() - .map(|t| UdfArrowConvert.to_arrow_field("", &DataType::from(t))) + .map(|t| arrow_convert.to_arrow_field("", &DataType::from(t))) .try_collect::()?, )); @@ -366,6 +447,8 @@ impl Build for UserDefinedFunction { arg_schema, imp, identifier: identifier.clone(), + link: udf.link.clone(), + arrow_convert, span: format!("udf_call({})", identifier).into(), disable_retry_count: AtomicU8::new(0), always_retry_on_network_error: udf.always_retry_on_network_error, @@ -373,12 +456,12 @@ impl Build for UserDefinedFunction { } } -#[cfg(not(madsim))] +#[cfg_or_panic(not(madsim))] /// Get or create a client for the given UDF service. /// /// There is a global cache for clients, so that we can reuse the same client for the same service. -pub(crate) fn get_or_create_flight_client(link: &str) -> Result> { - static CLIENTS: LazyLock>>> = +pub fn get_or_create_flight_client(link: &str) -> Result> { + static CLIENTS: LazyLock>>> = LazyLock::new(Default::default); let mut clients = CLIENTS.lock().unwrap(); if let Some(client) = clients.get(link).and_then(|c| c.upgrade()) { @@ -386,12 +469,58 @@ pub(crate) fn get_or_create_flight_client(link: &str) -> Result + }) + })?); + clients.insert(link.to_owned(), Arc::downgrade(&client)); Ok(client) } } +/// Connect to a UDF service and return a tonic `Channel`. +async fn connect_tonic(mut addr: &str) -> Result { + // Interval between two successive probes of the UDF DNS. + const DNS_PROBE_INTERVAL_SECS: u64 = 5; + // Timeout duration for performing an eager DNS resolution. + const EAGER_DNS_RESOLVE_TIMEOUT_SECS: u64 = 5; + const REQUEST_TIMEOUT_SECS: u64 = 5; + const CONNECT_TIMEOUT_SECS: u64 = 5; + + if let Some(s) = addr.strip_prefix("http://") { + addr = s; + } + if let Some(s) = addr.strip_prefix("https://") { + addr = s; + } + let host_addr = addr.parse::().map_err(|e| { + arrow_udf_flight::Error::Service(format!( + "invalid address: {}, err: {}", + addr, + e.as_report() + )) + })?; + let channel = LoadBalancedChannel::builder((host_addr.host.clone(), host_addr.port)) + .dns_probe_interval(std::time::Duration::from_secs(DNS_PROBE_INTERVAL_SECS)) + .timeout(Duration::from_secs(REQUEST_TIMEOUT_SECS)) + .connect_timeout(Duration::from_secs(CONNECT_TIMEOUT_SECS)) + .resolution_strategy(ResolutionStrategy::Eager { + timeout: tokio::time::Duration::from_secs(EAGER_DNS_RESOLVE_TIMEOUT_SECS), + }) + .channel() + .await + .map_err(|e| { + arrow_udf_flight::Error::Service(format!( + "failed to create LoadBalancedChannel, address: {}, err: {}", + host_addr, + e.as_report() + )) + })?; + Ok(channel.into()) +} + /// Get or create a wasm runtime. /// /// Runtimes returned by this function are cached inside for at least 60 seconds. @@ -413,3 +542,109 @@ pub fn get_or_create_wasm_runtime(binary: &[u8]) -> Result> { RUNTIMES.insert(md5, runtime.clone()); Ok(runtime) } + +/// Returns true if the arrow flight error is caused by a connection error. +fn is_connection_error(err: &arrow_udf_flight::Error) -> bool { + match err { + // Connection refused + arrow_udf_flight::Error::Tonic(status) if status.code() == tonic::Code::Unavailable => true, + _ => false, + } +} + +fn is_tonic_error(err: &arrow_udf_flight::Error) -> bool { + matches!( + err, + arrow_udf_flight::Error::Tonic(_) + | arrow_udf_flight::Error::Flight(arrow_flight::error::FlightError::Tonic(_)) + ) +} + +/// Monitor metrics for UDF. +#[derive(Debug, Clone)] +struct Metrics { + /// Number of successful UDF calls. + udf_success_count: IntCounterVec, + /// Number of failed UDF calls. + udf_failure_count: IntCounterVec, + /// Total number of retried UDF calls. + udf_retry_count: IntCounterVec, + /// Input chunk rows of UDF calls. + udf_input_chunk_rows: HistogramVec, + /// The latency of UDF calls in seconds. + udf_latency: HistogramVec, + /// Total number of input rows of UDF calls. + udf_input_rows: IntCounterVec, + /// Total number of input bytes of UDF calls. + udf_input_bytes: IntCounterVec, +} + +/// Global UDF metrics. +static GLOBAL_METRICS: LazyLock = LazyLock::new(|| Metrics::new(&GLOBAL_METRICS_REGISTRY)); + +impl Metrics { + fn new(registry: &Registry) -> Self { + let labels = &["link", "language", "name", "fragment_id"]; + let udf_success_count = register_int_counter_vec_with_registry!( + "udf_success_count", + "Total number of successful UDF calls", + labels, + registry + ) + .unwrap(); + let udf_failure_count = register_int_counter_vec_with_registry!( + "udf_failure_count", + "Total number of failed UDF calls", + labels, + registry + ) + .unwrap(); + let udf_retry_count = register_int_counter_vec_with_registry!( + "udf_retry_count", + "Total number of retried UDF calls", + labels, + registry + ) + .unwrap(); + let udf_input_chunk_rows = register_histogram_vec_with_registry!( + "udf_input_chunk_rows", + "Input chunk rows of UDF calls", + labels, + exponential_buckets(1.0, 2.0, 10).unwrap(), // 1 to 1024 + registry + ) + .unwrap(); + let udf_latency = register_histogram_vec_with_registry!( + "udf_latency", + "The latency(s) of UDF calls", + labels, + exponential_buckets(0.000001, 2.0, 30).unwrap(), // 1us to 1000s + registry + ) + .unwrap(); + let udf_input_rows = register_int_counter_vec_with_registry!( + "udf_input_rows", + "Total number of input rows of UDF calls", + labels, + registry + ) + .unwrap(); + let udf_input_bytes = register_int_counter_vec_with_registry!( + "udf_input_bytes", + "Total number of input bytes of UDF calls", + labels, + registry + ) + .unwrap(); + + Metrics { + udf_success_count, + udf_failure_count, + udf_retry_count, + udf_input_chunk_rows, + udf_latency, + udf_input_rows, + udf_input_bytes, + } + } +} diff --git a/src/expr/core/src/expr/mod.rs b/src/expr/core/src/expr/mod.rs index 6dbb3906f5618..9188ced21d111 100644 --- a/src/expr/core/src/expr/mod.rs +++ b/src/expr/core/src/expr/mod.rs @@ -51,7 +51,7 @@ use risingwave_common::types::{DataType, Datum}; pub use self::build::*; pub use self::expr_input_ref::InputRefExpression; pub use self::expr_literal::LiteralExpression; -pub use self::expr_udf::get_or_create_wasm_runtime; +pub use self::expr_udf::{get_or_create_flight_client, get_or_create_wasm_runtime}; pub use self::value::{ValueImpl, ValueRef}; pub use self::wrapper::*; pub use super::{ExprError, Result}; diff --git a/src/expr/core/src/table_function/user_defined.rs b/src/expr/core/src/table_function/user_defined.rs index 4362ff27b57b9..79b14a126f10d 100644 --- a/src/expr/core/src/table_function/user_defined.rs +++ b/src/expr/core/src/table_function/user_defined.rs @@ -23,9 +23,8 @@ use arrow_udf_js_deno::{CallMode as DenoCallMode, Runtime as DenoRuntime}; #[cfg(feature = "embedded-python-udf")] use arrow_udf_python::{CallMode as PythonCallMode, Runtime as PythonRuntime}; use cfg_or_panic::cfg_or_panic; -use futures_util::stream; use risingwave_common::array::arrow::{FromArrow, ToArrow, UdfArrowConvert}; -use risingwave_common::array::{DataChunk, I32Array}; +use risingwave_common::array::I32Array; use risingwave_common::bail; use super::*; @@ -38,6 +37,7 @@ pub struct UserDefinedTableFunction { return_type: DataType, client: UdfImpl, identifier: String, + arrow_convert: UdfArrowConvert, #[allow(dead_code)] chunk_size: usize, } @@ -61,10 +61,7 @@ impl UdfImpl { match self { UdfImpl::External(client) => { #[for_await] - for res in client - .call_stream(identifier, stream::once(async { input })) - .await? - { + for res in client.call_table_function(identifier, &input).await? { yield res?; } } @@ -110,8 +107,9 @@ impl UserDefinedTableFunction { // compact the input chunk and record the row mapping let visible_rows = direct_input.visibility().iter_ones().collect::>(); // this will drop invisible rows - let arrow_input = - UdfArrowConvert.to_record_batch(self.arg_schema.clone(), &direct_input)?; + let arrow_input = self + .arrow_convert + .to_record_batch(self.arg_schema.clone(), &direct_input)?; // call UDTF #[for_await] @@ -119,7 +117,7 @@ impl UserDefinedTableFunction { .client .call_table_function(&self.identifier, arrow_input) { - let output = UdfArrowConvert.from_record_batch(&res?)?; + let output = self.arrow_convert.from_record_batch(&res?)?; self.check_output(&output)?; // we send the compacted input to UDF, so we need to map the row indices back to the @@ -179,21 +177,9 @@ pub fn new_user_defined(prost: &PbTableFunction, chunk_size: usize) -> Result()?, - )); - let identifier = udtf.get_identifier()?; let return_type = DataType::from(prost.get_return_type()?); - let arrow_return_type = UdfArrowConvert - .to_arrow_field("", &return_type)? - .data_type() - .clone(); - #[cfg(not(feature = "embedded-deno-udf"))] let runtime = "quickjs"; @@ -203,12 +189,18 @@ pub fn new_user_defined(prost: &PbTableFunction, chunk_size: usize) -> Result "quickjs", }; + let mut arrow_convert = UdfArrowConvert::default(); + let client = match udtf.language.as_str() { "wasm" | "rust" => { let compressed_wasm_binary = udtf.get_compressed_binary()?; let wasm_binary = zstd::stream::decode_all(compressed_wasm_binary.as_slice()) .context("failed to decompress wasm binary")?; let runtime = crate::expr::expr_udf::get_or_create_wasm_runtime(&wasm_binary)?; + // backward compatibility + if runtime.abi_version().0 <= 2 { + arrow_convert = UdfArrowConvert { legacy: true }; + } UdfImpl::Wasm(runtime) } "javascript" if runtime != "deno" => { @@ -221,7 +213,7 @@ pub fn new_user_defined(prost: &PbTableFunction, chunk_size: usize) -> Result Result Result Result { let link = udtf.get_link()?; - UdfImpl::External(crate::expr::expr_udf::get_or_create_flight_client(link)?) + let client = crate::expr::expr_udf::get_or_create_flight_client(link)?; + // backward compatibility + // see for details + if client.protocol_version() == 1 { + arrow_convert = UdfArrowConvert { legacy: true }; + } + UdfImpl::External(client) } }; + let arg_schema = Arc::new(Schema::new( + udtf.arg_types + .iter() + .map(|t| arrow_convert.to_arrow_field("", &DataType::from(t))) + .try_collect::()?, + )); + Ok(UserDefinedTableFunction { children: prost.args.iter().map(expr_build_from_prost).try_collect()?, return_type, arg_schema, client, identifier: identifier.clone(), + arrow_convert, chunk_size, } .boxed()) diff --git a/src/expr/impl/Cargo.toml b/src/expr/impl/Cargo.toml index 437fcad7cbac3..c20c46436e6fc 100644 --- a/src/expr/impl/Cargo.toml +++ b/src/expr/impl/Cargo.toml @@ -32,7 +32,7 @@ futures-util = "0.3" hex = "0.4" icelake = { workspace = true } itertools = { workspace = true } -jsonbb = "0.1.2" +jsonbb = { workspace = true } linkme = { version = "0.3", features = ["used_linker"] } md5 = "0.7" num-traits = "0.2" diff --git a/src/expr/impl/benches/expr.rs b/src/expr/impl/benches/expr.rs index 8468ae86e241b..e04bc9faee35e 100644 --- a/src/expr/impl/benches/expr.rs +++ b/src/expr/impl/benches/expr.rs @@ -29,7 +29,6 @@ use risingwave_common::types::*; use risingwave_expr::aggregate::{build_append_only, AggArgs, AggCall, AggKind}; use risingwave_expr::expr::*; use risingwave_expr::sig::FUNCTION_REGISTRY; -use risingwave_expr::ExprError; use risingwave_pb::expr::expr_node::PbType; use thiserror_ext::AsReport; diff --git a/src/expr/impl/src/aggregate/general.rs b/src/expr/impl/src/aggregate/general.rs index 2ee55f0a16d52..8c006377642bf 100644 --- a/src/expr/impl/src/aggregate/general.rs +++ b/src/expr/impl/src/aggregate/general.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::From; - use num_traits::{CheckedAdd, CheckedSub}; use risingwave_expr::{aggregate, ExprError, Result}; diff --git a/src/expr/impl/src/lib.rs b/src/expr/impl/src/lib.rs index a47754cb4e821..5bc4a758b42a1 100644 --- a/src/expr/impl/src/lib.rs +++ b/src/expr/impl/src/lib.rs @@ -27,7 +27,6 @@ #![feature(iterator_try_collect)] #![feature(exclusive_range_pattern)] #![feature(lazy_cell)] -#![feature(round_ties_even)] #![feature(coroutines)] #![feature(test)] #![feature(iter_array_chunks)] diff --git a/src/expr/impl/src/scalar/arithmetic_op.rs b/src/expr/impl/src/scalar/arithmetic_op.rs index ad5fe8497d8bb..e48b0b5e94b59 100644 --- a/src/expr/impl/src/scalar/arithmetic_op.rs +++ b/src/expr/impl/src/scalar/arithmetic_op.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::TryInto; use std::fmt::Debug; use chrono::{Duration, NaiveDateTime}; @@ -443,9 +442,7 @@ mod tests { use num_traits::Float; use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::{ - Date, Decimal, Int256, Int256Ref, Interval, Scalar, Timestamp, F32, F64, - }; + use risingwave_common::types::{Int256, Int256Ref, Scalar, F32}; use super::*; diff --git a/src/expr/impl/src/scalar/bitwise_op.rs b/src/expr/impl/src/scalar/bitwise_op.rs index 38aa4d194ea60..0d7f14bb6965a 100644 --- a/src/expr/impl/src/scalar/bitwise_op.rs +++ b/src/expr/impl/src/scalar/bitwise_op.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. use std::any::type_name; -use std::convert::TryInto; use std::fmt::Debug; use std::ops::{BitAnd, BitOr, BitXor, Not}; diff --git a/src/expr/impl/src/scalar/case.rs b/src/expr/impl/src/scalar/case.rs index 99ec8cb880ddc..f7fb9d89ef41b 100644 --- a/src/expr/impl/src/scalar/case.rs +++ b/src/expr/impl/src/scalar/case.rs @@ -285,7 +285,6 @@ fn build_case_expr( #[cfg(test)] mod tests { - use risingwave_common::row::Row; use risingwave_common::test_prelude::DataChunkTestExt; use risingwave_common::types::ToOwnedDatum; use risingwave_common::util::iter_util::ZipEqDebug; diff --git a/src/expr/impl/src/scalar/cast.rs b/src/expr/impl/src/scalar/cast.rs index cb990bdcec6a7..48218d7200809 100644 --- a/src/expr/impl/src/scalar/cast.rs +++ b/src/expr/impl/src/scalar/cast.rs @@ -244,11 +244,9 @@ fn struct_cast(input: StructRef<'_>, ctx: &Context) -> Result { #[cfg(test)] mod tests { use chrono::NaiveDateTime; - use itertools::Itertools; use risingwave_common::array::*; use risingwave_common::types::*; use risingwave_expr::expr::build_from_pretty; - use risingwave_pb::expr::expr_node::PbType; use super::*; diff --git a/src/expr/impl/src/scalar/cmp.rs b/src/expr/impl/src/scalar/cmp.rs index 69dfcb5f57ef3..02c1662f18025 100644 --- a/src/expr/impl/src/scalar/cmp.rs +++ b/src/expr/impl/src/scalar/cmp.rs @@ -519,7 +519,7 @@ mod tests { use risingwave_common::array::*; use risingwave_common::row::OwnedRow; use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::{Date, Interval, Scalar}; + use risingwave_common::types::{Date, Interval}; use risingwave_pb::expr::expr_node::Type; use crate::scalar::arithmetic_op::{date_interval_add, date_interval_sub}; diff --git a/src/expr/impl/src/scalar/proctime.rs b/src/expr/impl/src/scalar/proctime.rs index 2e96e602633cb..2c3c2c651c3df 100644 --- a/src/expr/impl/src/scalar/proctime.rs +++ b/src/expr/impl/src/scalar/proctime.rs @@ -25,7 +25,6 @@ fn proctime() -> Result { #[cfg(test)] mod tests { - use risingwave_common::types::Timestamptz; use risingwave_common::util::epoch::{Epoch, EpochPair}; use super::*; diff --git a/src/expr/impl/src/scalar/round.rs b/src/expr/impl/src/scalar/round.rs index a43dd425e13c3..90c9512c6c9b2 100644 --- a/src/expr/impl/src/scalar/round.rs +++ b/src/expr/impl/src/scalar/round.rs @@ -73,9 +73,6 @@ pub fn round_decimal(input: Decimal) -> Decimal { mod tests { use std::str::FromStr; - use risingwave_common::types::{Decimal, F64}; - - use super::ceil_f64; use crate::scalar::round::*; fn do_test(input: &str, digits: i32, expected_output: Option<&str>) { diff --git a/src/expr/impl/src/scalar/trigonometric.rs b/src/expr/impl/src/scalar/trigonometric.rs index 705ee8f3eeba2..20e2b3523716b 100644 --- a/src/expr/impl/src/scalar/trigonometric.rs +++ b/src/expr/impl/src/scalar/trigonometric.rs @@ -362,7 +362,7 @@ pub fn radians_f64(input: F64) -> F64 { mod tests { use std::f64::consts::PI; - use risingwave_common::types::{FloatExt, F64}; + use risingwave_common::types::FloatExt; use crate::scalar::trigonometric::*; diff --git a/src/expr/udf/Cargo.toml b/src/expr/udf/Cargo.toml deleted file mode 100644 index b17ad7acadfc1..0000000000000 --- a/src/expr/udf/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "risingwave_udf" -version = "0.1.0" -edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[package.metadata.cargo-machete] -ignored = ["workspace-hack"] - -[package.metadata.cargo-udeps.ignore] -normal = ["workspace-hack"] - -[dependencies] -arrow-array = { workspace = true } -arrow-flight = { workspace = true } -arrow-schema = { workspace = true } -arrow-select = { workspace = true } -cfg-or-panic = "0.2" -futures = "0.3" -futures-util = "0.3.28" -ginepro = "0.7.0" -prometheus = "0.13" -risingwave_common = { workspace = true } -static_assertions = "1" -thiserror = "1" -thiserror-ext = { workspace = true } -tokio = { version = "0.2", package = "madsim-tokio", features = [ - "rt", - "macros", -] } -tonic = { workspace = true } -tracing = "0.1" - -[lints] -workspace = true diff --git a/src/expr/udf/README-js.md b/src/expr/udf/README-js.md deleted file mode 100644 index 902bce4ef52ee..0000000000000 --- a/src/expr/udf/README-js.md +++ /dev/null @@ -1,83 +0,0 @@ -# Use UDFs in JavaScript - -This article provides a step-by-step guide for defining JavaScript functions in RisingWave. - -JavaScript code is inlined in `CREATE FUNCTION` statement and then run on the embedded QuickJS virtual machine in RisingWave. It does not support access to external networks and is limited to computational tasks only. -Compared to other languages, JavaScript UDFs offer the easiest way to define UDFs in RisingWave. - -## Define your functions - -You can use the `CREATE FUNCTION` statement to create JavaScript UDFs. The syntax is as follows: - -```sql -CREATE FUNCTION function_name ( arg_name arg_type [, ...] ) - [ RETURNS return_type | RETURNS TABLE ( column_name column_type [, ...] ) ] - LANGUAGE javascript - AS [ $$ function_body $$ | 'function_body' ]; -``` - -The argument names you define can be used in the function body. For example: - -```sql -CREATE FUNCTION gcd(a int, b int) RETURNS int LANGUAGE javascript AS $$ - if(a == null || b == null) { - return null; - } - while (b != 0) { - let t = b; - b = a % b; - a = t; - } - return a; -$$; -``` - -The correspondence between SQL types and JavaScript types can be found in the [appendix table](#appendix-type-mapping). You need to ensure that the type of the return value is either `null` or consistent with the type in the `RETURNS` clause. - -If the function you define returns a table, you need to use the `yield` statement to return the data of each row. For example: - -```sql -CREATE FUNCTION series(n int) RETURNS TABLE (x int) LANGUAGE javascript AS $$ - for(let i = 0; i < n; i++) { - yield i; - } -$$; -``` - -## Use your functions - -Once the UDFs are created in RisingWave, you can use them in SQL queries just like any built-in functions. For example: - -```sql -SELECT gcd(25, 15); -SELECT * from series(5); -``` - -## Appendix: Type Mapping - -The following table shows the type mapping between SQL and JavaScript: - -| SQL Type | JS Type | Note | -| --------------------- | ------------- | --------------------- | -| boolean | boolean | | -| smallint | number | | -| int | number | | -| bigint | number | | -| real | number | | -| double precision | number | | -| decimal | BigDecimal | | -| date | | not supported yet | -| time | | not supported yet | -| timestamp | | not supported yet | -| timestamptz | | not supported yet | -| interval | | not supported yet | -| varchar | string | | -| bytea | Uint8Array | | -| jsonb | null, boolean, number, string, array or object | `JSON.parse(string)` | -| smallint[] | Int16Array | | -| int[] | Int32Array | | -| bigint[] | BigInt64Array | | -| real[] | Float32Array | | -| double precision[] | Float64Array | | -| others[] | array | | -| struct<..> | object | | diff --git a/src/expr/udf/README.md b/src/expr/udf/README.md deleted file mode 100644 index d9428cc547249..0000000000000 --- a/src/expr/udf/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# Use UDFs in Rust - -This article provides a step-by-step guide for defining Rust functions in RisingWave. - -Rust functions are compiled into WebAssembly modules and then run on the embedded WebAssembly virtual machine in RisingWave. Compared to Python and Java, Rust UDFs offer **higher performance** (near native) and are **managed by the RisingWave kernel**, eliminating the need for additional maintenance. However, since they run embedded in the kernel, for security reasons, Rust UDFs currently **do not support access to external networks and are limited to computational tasks only**, with restricted CPU and memory resources. Therefore, we recommend using Rust UDFs for **computationally intensive tasks**, such as packet parsing and format conversion. - -## Prerequisites - -- Ensure that you have [Rust toolchain](https://rustup.rs) (stable channel) installed on your computer. -- Ensure that the Rust standard library for `wasm32-wasi` target is installed: - ```shell - rustup target add wasm32-wasi - ``` - -## 1. Create a project - -Create a Rust project named `udf`: - -```shell -cargo new --lib udf -cd udf -``` - -Add the following lines to `Cargo.toml`: - -```toml -[lib] -crate-type = ["cdylib"] - -[dependencies] -arrow-udf = "0.1" -``` - -## 2. Define your functions - -In `src/lib.rs`, define your functions using the `function` macro: - -```rust -use arrow_udf::function; - -// define a scalar function -#[function("gcd(int, int) -> int")] -fn gcd(mut x: i32, mut y: i32) -> i32 { - while y != 0 { - (x, y) = (y, x % y); - } - x -} - -// define a table function -#[function("series(int) -> setof int")] -fn series(n: i32) -> impl Iterator { - 0..n -} -``` - -You can find more usages in the [documentation](https://docs.rs/arrow_udf/0.1.0/arrow_udf/attr.function.html) and more examples in the [tests](https://github.com/risingwavelabs/arrow-udf/blob/main/arrow-udf/tests/tests.rs). - -Currently we only support a limited set of data types. `timestamptz` and complex array types are not supported yet. - -## 3. Build the project - -Build your functions into a WebAssembly module: - -```shell -cargo build --release --target wasm32-wasi -``` - -You can find the generated WASM module at `target/wasm32-wasi/release/udf.wasm`. - -Optional: It is recommended to strip the binary to reduce its size: - -```shell -# Install wasm-tools -cargo install wasm-tools - -# Strip the binary -wasm-tools strip ./target/wasm32-wasi/release/udf.wasm > udf.wasm -``` - -## 4. Declare your functions in RisingWave - -In RisingWave, use the `CREATE FUNCTION` command to declare the functions you defined. - -There are two ways to load the WASM module: - -1. The WASM binary can be embedded in the SQL statement using the base64 encoding. -You can use the following shell script to encode the binary and generate the SQL statement: - ```shell - encoded=$(base64 -i udf.wasm) - sql="CREATE FUNCTION gcd(int, int) RETURNS int LANGUAGE wasm USING BASE64 '$encoded';" - echo "$sql" > create_function.sql - ``` - When created successfully, the WASM binary will be automatically uploaded to the object store. - -2. The WASM binary can be loaded from the object store. - ```sql - CREATE FUNCTION gcd(int, int) RETURNS int - LANGUAGE wasm USING LINK 's3://bucket/path/to/udf.wasm'; - - CREATE FUNCTION series(int) RETURNS TABLE (x int) - LANGUAGE wasm USING LINK 's3://bucket/path/to/udf.wasm'; - ``` - - Or if you run RisingWave locally, you can use the local file system: - ```sql - CREATE FUNCTION gcd(int, int) RETURNS int - LANGUAGE wasm USING LINK 'fs://path/to/udf.wasm'; - ``` - -## 5. Use your functions in RisingWave - -Once the UDFs are created in RisingWave, you can use them in SQL queries just like any built-in functions. For example: - -```sql -SELECT gcd(25, 15); -SELECT series(5); -``` diff --git a/src/expr/udf/examples/client.rs b/src/expr/udf/examples/client.rs deleted file mode 100644 index 92f93ae13614e..0000000000000 --- a/src/expr/udf/examples/client.rs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -use std::sync::Arc; - -use arrow_array::{Int32Array, RecordBatch}; -use arrow_schema::{DataType, Field, Schema}; -use risingwave_udf::ArrowFlightUdfClient; - -#[tokio::main] -async fn main() { - let addr = "http://localhost:8815"; - let client = ArrowFlightUdfClient::connect(addr).await.unwrap(); - - // build `RecordBatch` to send (equivalent to our `DataChunk`) - let array1 = Arc::new(Int32Array::from_iter(vec![1, 6, 10])); - let array2 = Arc::new(Int32Array::from_iter(vec![3, 4, 15])); - let array3 = Arc::new(Int32Array::from_iter(vec![6, 8, 3])); - let input2_schema = Schema::new(vec![ - Field::new("a", DataType::Int32, true), - Field::new("b", DataType::Int32, true), - ]); - let input3_schema = Schema::new(vec![ - Field::new("a", DataType::Int32, true), - Field::new("b", DataType::Int32, true), - Field::new("c", DataType::Int32, true), - ]); - let output_schema = Schema::new(vec![Field::new("x", DataType::Int32, true)]); - - // check function - client - .check("gcd", &input2_schema, &output_schema) - .await - .unwrap(); - client - .check("gcd3", &input3_schema, &output_schema) - .await - .unwrap(); - - let input2 = RecordBatch::try_new( - Arc::new(input2_schema), - vec![array1.clone(), array2.clone()], - ) - .unwrap(); - - let output = client - .call("gcd", input2) - .await - .expect("failed to call function"); - - println!("{:?}", output); - - let input3 = RecordBatch::try_new( - Arc::new(input3_schema), - vec![array1.clone(), array2.clone(), array3.clone()], - ) - .unwrap(); - - let output = client - .call("gcd3", input3) - .await - .expect("failed to call function"); - - println!("{:?}", output); -} diff --git a/src/expr/udf/python/.gitignore b/src/expr/udf/python/.gitignore deleted file mode 100644 index 75b18b1dc1919..0000000000000 --- a/src/expr/udf/python/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/dist -/risingwave.egg-info diff --git a/src/expr/udf/python/CHANGELOG.md b/src/expr/udf/python/CHANGELOG.md deleted file mode 100644 index a20411e69d83e..0000000000000 --- a/src/expr/udf/python/CHANGELOG.md +++ /dev/null @@ -1,37 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.1.1] - 2023-12-06 - -### Fixed - -- Fix decimal type output. - -## [0.1.0] - 2023-12-01 - -### Fixed - -- Fix unconstrained decimal type. - -## [0.0.12] - 2023-11-28 - -### Changed - -- Change the default struct field name to `f{i}`. - -### Fixed - -- Fix parsing nested struct type. - - -## [0.0.11] - 2023-11-06 - -### Fixed - -- Hook SIGTERM to stop the UDF server gracefully. diff --git a/src/expr/udf/python/README.md b/src/expr/udf/python/README.md deleted file mode 100644 index d1655be05350b..0000000000000 --- a/src/expr/udf/python/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# RisingWave Python UDF SDK - -This library provides a Python SDK for creating user-defined functions (UDF) in [RisingWave](https://www.risingwave.com/). - -For a detailed guide on how to use Python UDF in RisingWave, please refer to [this doc](https://docs.risingwave.com/docs/current/udf-python/). - -## Introduction - -RisingWave supports user-defined functions implemented as external functions. -With the RisingWave Python UDF SDK, users can define custom UDFs using Python and start a Python process as a UDF server. -RisingWave can then remotely access the UDF server to execute the defined functions. - -## Installation - -```sh -pip install risingwave -``` - -## Usage - -Define functions in a Python file: - -```python -# udf.py -from risingwave.udf import udf, udtf, UdfServer -import struct -import socket - -# Define a scalar function -@udf(input_types=['INT', 'INT'], result_type='INT') -def gcd(x, y): - while y != 0: - (x, y) = (y, x % y) - return x - -# Define a scalar function that returns multiple values (within a struct) -@udf(input_types=['BYTEA'], result_type='STRUCT') -def extract_tcp_info(tcp_packet: bytes): - src_addr, dst_addr = struct.unpack('!4s4s', tcp_packet[12:20]) - src_port, dst_port = struct.unpack('!HH', tcp_packet[20:24]) - src_addr = socket.inet_ntoa(src_addr) - dst_addr = socket.inet_ntoa(dst_addr) - return src_addr, dst_addr, src_port, dst_port - -# Define a table function -@udtf(input_types='INT', result_types='INT') -def series(n): - for i in range(n): - yield i - -# Start a UDF server -if __name__ == '__main__': - server = UdfServer(location="0.0.0.0:8815") - server.add_function(gcd) - server.add_function(series) - server.serve() -``` - -Start the UDF server: - -```sh -python3 udf.py -``` - -To create functions in RisingWave, use the following syntax: - -```sql -create function ( [, ...] ) - [ returns | returns table ( [, ...] ) ] - as using link ''; -``` - -- The `as` parameter specifies the function name defined in the UDF server. -- The `link` parameter specifies the address of the UDF server. - -For example: - -```sql -create function gcd(int, int) returns int -as gcd using link 'http://localhost:8815'; - -create function series(int) returns table (x int) -as series using link 'http://localhost:8815'; - -select gcd(25, 15); - -select * from series(10); -``` - -## Data Types - -The RisingWave Python UDF SDK supports the following data types: - -| SQL Type | Python Type | Notes | -| ---------------- | ----------------------------- | ------------------ | -| BOOLEAN | bool | | -| SMALLINT | int | | -| INT | int | | -| BIGINT | int | | -| REAL | float | | -| DOUBLE PRECISION | float | | -| DECIMAL | decimal.Decimal | | -| DATE | datetime.date | | -| TIME | datetime.time | | -| TIMESTAMP | datetime.datetime | | -| INTERVAL | MonthDayNano / (int, int, int) | Fields can be obtained by `months()`, `days()` and `nanoseconds()` from `MonthDayNano` | -| VARCHAR | str | | -| BYTEA | bytes | | -| JSONB | any | | -| T[] | list[T] | | -| STRUCT<> | tuple | | -| ...others | | Not supported yet. | diff --git a/src/expr/udf/python/publish.md b/src/expr/udf/python/publish.md deleted file mode 100644 index 0bc22d713906f..0000000000000 --- a/src/expr/udf/python/publish.md +++ /dev/null @@ -1,19 +0,0 @@ -# How to publish this library - -Install the build tool: - -```sh -pip3 install build -``` - -Build the library: - -```sh -python3 -m build -``` - -Upload the library to PyPI: - -```sh -twine upload dist/* -``` diff --git a/src/expr/udf/python/pyproject.toml b/src/expr/udf/python/pyproject.toml deleted file mode 100644 index b535355168363..0000000000000 --- a/src/expr/udf/python/pyproject.toml +++ /dev/null @@ -1,20 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "risingwave" -version = "0.1.1" -authors = [{ name = "RisingWave Labs" }] -description = "RisingWave Python API" -readme = "README.md" -license = { text = "Apache Software License" } -classifiers = [ - "Programming Language :: Python", - "License :: OSI Approved :: Apache Software License", -] -requires-python = ">=3.8" -dependencies = ["pyarrow"] - -[project.optional-dependencies] -test = ["pytest"] diff --git a/src/expr/udf/python/risingwave/__init__.py b/src/expr/udf/python/risingwave/__init__.py deleted file mode 100644 index 3d60f2f96d025..0000000000000 --- a/src/expr/udf/python/risingwave/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# Licensed 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. diff --git a/src/expr/udf/python/risingwave/test_udf.py b/src/expr/udf/python/risingwave/test_udf.py deleted file mode 100644 index e3c2029d3d1f9..0000000000000 --- a/src/expr/udf/python/risingwave/test_udf.py +++ /dev/null @@ -1,240 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# Licensed 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. - -from multiprocessing import Process -import pytest -from risingwave.udf import udf, UdfServer, _to_data_type -import pyarrow as pa -import pyarrow.flight as flight -import time -import datetime -from typing import Any - - -def flight_server(): - server = UdfServer(location="localhost:8815") - server.add_function(add) - server.add_function(wait) - server.add_function(wait_concurrent) - server.add_function(return_all) - return server - - -def flight_client(): - client = flight.FlightClient(("localhost", 8815)) - return client - - -# Define a scalar function -@udf(input_types=["INT", "INT"], result_type="INT") -def add(x, y): - return x + y - - -@udf(input_types=["INT"], result_type="INT") -def wait(x): - time.sleep(0.01) - return 0 - - -@udf(input_types=["INT"], result_type="INT", io_threads=32) -def wait_concurrent(x): - time.sleep(0.01) - return 0 - - -@udf( - input_types=[ - "BOOLEAN", - "SMALLINT", - "INT", - "BIGINT", - "FLOAT4", - "FLOAT8", - "DECIMAL", - "DATE", - "TIME", - "TIMESTAMP", - "INTERVAL", - "VARCHAR", - "BYTEA", - "JSONB", - ], - result_type="""struct< - BOOLEAN, - SMALLINT, - INT, - BIGINT, - FLOAT4, - FLOAT8, - DECIMAL, - DATE, - TIME, - TIMESTAMP, - INTERVAL, - VARCHAR, - BYTEA, - JSONB - >""", -) -def return_all( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, -): - return ( - bool, - i16, - i32, - i64, - f32, - f64, - decimal, - date, - time, - timestamp, - interval, - varchar, - bytea, - jsonb, - ) - - -def test_simple(): - LEN = 64 - data = pa.Table.from_arrays( - [pa.array(range(0, LEN)), pa.array(range(0, LEN))], names=["x", "y"] - ) - - batches = data.to_batches(max_chunksize=512) - - with flight_client() as client, flight_server() as server: - flight_info = flight.FlightDescriptor.for_path(b"add") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=data.schema) - for batch in batches: - writer.write_batch(batch) - writer.done_writing() - - chunk = reader.read_chunk() - assert len(chunk.data) == LEN - assert chunk.data.column("output").equals( - pa.array(range(0, LEN * 2, 2), type=pa.int32()) - ) - - -def test_io_concurrency(): - LEN = 64 - data = pa.Table.from_arrays([pa.array(range(0, LEN))], names=["x"]) - batches = data.to_batches(max_chunksize=512) - - with flight_client() as client, flight_server() as server: - # Single-threaded function takes a long time - flight_info = flight.FlightDescriptor.for_path(b"wait") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=data.schema) - for batch in batches: - writer.write_batch(batch) - writer.done_writing() - start_time = time.time() - - total_len = 0 - for chunk in reader: - total_len += len(chunk.data) - - assert total_len == LEN - - elapsed_time = time.time() - start_time # ~0.64s - assert elapsed_time > 0.5 - - # Multi-threaded I/O bound function will take a much shorter time - flight_info = flight.FlightDescriptor.for_path(b"wait_concurrent") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=data.schema) - for batch in batches: - writer.write_batch(batch) - writer.done_writing() - start_time = time.time() - - total_len = 0 - for chunk in reader: - total_len += len(chunk.data) - - assert total_len == LEN - - elapsed_time = time.time() - start_time - assert elapsed_time < 0.25 - - -def test_all_types(): - arrays = [ - pa.array([True], type=pa.bool_()), - pa.array([1], type=pa.int16()), - pa.array([1], type=pa.int32()), - pa.array([1], type=pa.int64()), - pa.array([1], type=pa.float32()), - pa.array([1], type=pa.float64()), - pa.array(["12345678901234567890.1234567890"], type=pa.large_binary()), - pa.array([datetime.date(2023, 6, 1)], type=pa.date32()), - pa.array([datetime.time(1, 2, 3, 456789)], type=pa.time64("us")), - pa.array( - [datetime.datetime(2023, 6, 1, 1, 2, 3, 456789)], - type=pa.timestamp("us"), - ), - pa.array([(1, 2, 3)], type=pa.month_day_nano_interval()), - pa.array(["string"], type=pa.string()), - pa.array(["bytes"], type=pa.binary()), - pa.array(['{ "key": 1 }'], type=pa.large_string()), - ] - batch = pa.RecordBatch.from_arrays(arrays, names=["" for _ in arrays]) - - with flight_client() as client, flight_server() as server: - flight_info = flight.FlightDescriptor.for_path(b"return_all") - writer, reader = client.do_exchange(descriptor=flight_info) - with writer: - writer.begin(schema=batch.schema) - writer.write_batch(batch) - writer.done_writing() - - chunk = reader.read_chunk() - assert [v.as_py() for _, v in chunk.data.column(0)[0].items()] == [ - True, - 1, - 1, - 1, - 1.0, - 1.0, - b"12345678901234567890.1234567890", - datetime.date(2023, 6, 1), - datetime.time(1, 2, 3, 456789), - datetime.datetime(2023, 6, 1, 1, 2, 3, 456789), - (1, 2, 3), - "string", - b"bytes", - '{"key": 1}', - ] diff --git a/src/expr/udf/python/risingwave/udf.py b/src/expr/udf/python/risingwave/udf.py deleted file mode 100644 index aad53e25e0c98..0000000000000 --- a/src/expr/udf/python/risingwave/udf.py +++ /dev/null @@ -1,552 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# Licensed 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. - -from typing import * -import pyarrow as pa -import pyarrow.flight -import pyarrow.parquet -import inspect -import traceback -import json -from concurrent.futures import ThreadPoolExecutor -import concurrent -from decimal import Decimal -import signal - - -class UserDefinedFunction: - """ - Base interface for user-defined function. - """ - - _name: str - _input_schema: pa.Schema - _result_schema: pa.Schema - _io_threads: Optional[int] - _executor: Optional[ThreadPoolExecutor] - - def eval_batch(self, batch: pa.RecordBatch) -> Iterator[pa.RecordBatch]: - """ - Apply the function on a batch of inputs. - """ - return iter([]) - - -class ScalarFunction(UserDefinedFunction): - """ - Base interface for user-defined scalar function. A user-defined scalar functions maps zero, one, - or multiple scalar values to a new scalar value. - """ - - def __init__(self, *args, **kwargs): - self._io_threads = kwargs.pop("io_threads") - self._executor = ( - ThreadPoolExecutor(max_workers=self._io_threads) - if self._io_threads is not None - else None - ) - super().__init__(*args, **kwargs) - - def eval(self, *args) -> Any: - """ - Method which defines the logic of the scalar function. - """ - pass - - def eval_batch(self, batch: pa.RecordBatch) -> Iterator[pa.RecordBatch]: - # parse value from json string for jsonb columns - inputs = [[v.as_py() for v in array] for array in batch] - inputs = [ - _process_func(pa.list_(type), False)(array) - for array, type in zip(inputs, self._input_schema.types) - ] - if self._executor is not None: - # evaluate the function for each row - tasks = [ - self._executor.submit(self._func, *[col[i] for col in inputs]) - for i in range(batch.num_rows) - ] - column = [ - future.result() for future in concurrent.futures.as_completed(tasks) - ] - else: - # evaluate the function for each row - column = [ - self.eval(*[col[i] for col in inputs]) for i in range(batch.num_rows) - ] - - column = _process_func(pa.list_(self._result_schema.types[0]), True)(column) - - array = pa.array(column, type=self._result_schema.types[0]) - yield pa.RecordBatch.from_arrays([array], schema=self._result_schema) - - -def _process_func(type: pa.DataType, output: bool) -> Callable: - """Return a function to process input or output value.""" - if pa.types.is_list(type): - func = _process_func(type.value_type, output) - return lambda array: [(func(v) if v is not None else None) for v in array] - - if pa.types.is_struct(type): - funcs = [_process_func(field.type, output) for field in type] - if output: - return lambda tup: tuple( - (func(v) if v is not None else None) for v, func in zip(tup, funcs) - ) - else: - # the input value of struct type is a dict - # we convert it into tuple here - return lambda map: tuple( - (func(v) if v is not None else None) - for v, func in zip(map.values(), funcs) - ) - - if type.equals(JSONB): - if output: - return lambda v: json.dumps(v) - else: - return lambda v: json.loads(v) - - if type.equals(UNCONSTRAINED_DECIMAL): - if output: - - def decimal_to_str(v): - if not isinstance(v, Decimal): - raise ValueError(f"Expected Decimal, got {v}") - # use `f` format to avoid scientific notation, e.g. `1e10` - return format(v, "f").encode("utf-8") - - return decimal_to_str - else: - return lambda v: Decimal(v.decode("utf-8")) - - return lambda v: v - - -class TableFunction(UserDefinedFunction): - """ - Base interface for user-defined table function. A user-defined table functions maps zero, one, - or multiple scalar values to a new table value. - """ - - BATCH_SIZE = 1024 - - def eval(self, *args) -> Iterator: - """ - Method which defines the logic of the table function. - """ - yield - - def eval_batch(self, batch: pa.RecordBatch) -> Iterator[pa.RecordBatch]: - class RecordBatchBuilder: - """A utility class for constructing Arrow RecordBatch by row.""" - - schema: pa.Schema - columns: List[List] - - def __init__(self, schema: pa.Schema): - self.schema = schema - self.columns = [[] for _ in self.schema.types] - - def len(self) -> int: - """Returns the number of rows in the RecordBatch being built.""" - return len(self.columns[0]) - - def append(self, index: int, value: Any): - """Appends a new row to the RecordBatch being built.""" - self.columns[0].append(index) - self.columns[1].append(value) - - def build(self) -> pa.RecordBatch: - """Builds the RecordBatch from the accumulated data and clears the state.""" - # Convert the columns to arrow arrays - arrays = [ - pa.array(col, type) - for col, type in zip(self.columns, self.schema.types) - ] - # Reset columns - self.columns = [[] for _ in self.schema.types] - return pa.RecordBatch.from_arrays(arrays, schema=self.schema) - - builder = RecordBatchBuilder(self._result_schema) - - # Iterate through rows in the input RecordBatch - for row_index in range(batch.num_rows): - row = tuple(column[row_index].as_py() for column in batch) - for result in self.eval(*row): - builder.append(row_index, result) - if builder.len() == self.BATCH_SIZE: - yield builder.build() - if builder.len() != 0: - yield builder.build() - - -class UserDefinedScalarFunctionWrapper(ScalarFunction): - """ - Base Wrapper for Python user-defined scalar function. - """ - - _func: Callable - - def __init__(self, func, input_types, result_type, name=None, io_threads=None): - self._func = func - self._input_schema = pa.schema( - zip( - inspect.getfullargspec(func)[0], - [_to_data_type(t) for t in _to_list(input_types)], - ) - ) - self._result_schema = pa.schema([("output", _to_data_type(result_type))]) - self._name = name or ( - func.__name__ if hasattr(func, "__name__") else func.__class__.__name__ - ) - super().__init__(io_threads=io_threads) - - def __call__(self, *args): - return self._func(*args) - - def eval(self, *args): - return self._func(*args) - - -class UserDefinedTableFunctionWrapper(TableFunction): - """ - Base Wrapper for Python user-defined table function. - """ - - _func: Callable - - def __init__(self, func, input_types, result_types, name=None): - self._func = func - self._name = name or ( - func.__name__ if hasattr(func, "__name__") else func.__class__.__name__ - ) - self._input_schema = pa.schema( - zip( - inspect.getfullargspec(func)[0], - [_to_data_type(t) for t in _to_list(input_types)], - ) - ) - self._result_schema = pa.schema( - [ - ("row_index", pa.int32()), - ( - self._name, - pa.struct([("", _to_data_type(t)) for t in result_types]) - if isinstance(result_types, list) - else _to_data_type(result_types), - ), - ] - ) - - def __call__(self, *args): - return self._func(*args) - - def eval(self, *args): - return self._func(*args) - - -def _to_list(x): - if isinstance(x, list): - return x - else: - return [x] - - -def udf( - input_types: Union[List[Union[str, pa.DataType]], Union[str, pa.DataType]], - result_type: Union[str, pa.DataType], - name: Optional[str] = None, - io_threads: Optional[int] = None, -) -> Callable: - """ - Annotation for creating a user-defined scalar function. - - Parameters: - - input_types: A list of strings or Arrow data types that specifies the input data types. - - result_type: A string or an Arrow data type that specifies the return value type. - - name: An optional string specifying the function name. If not provided, the original name will be used. - - io_threads: Number of I/O threads used per data chunk for I/O bound functions. - - Example: - ``` - @udf(input_types=['INT', 'INT'], result_type='INT') - def gcd(x, y): - while y != 0: - (x, y) = (y, x % y) - return x - ``` - - I/O bound Example: - ``` - @udf(input_types=['INT'], result_type='INT', io_threads=64) - def external_api(x): - response = requests.get(my_endpoint + '?param=' + x) - return response["data"] - ``` - """ - - if io_threads is not None and io_threads > 1: - return lambda f: UserDefinedScalarFunctionWrapper( - f, input_types, result_type, name, io_threads=io_threads - ) - else: - return lambda f: UserDefinedScalarFunctionWrapper( - f, input_types, result_type, name - ) - - -def udtf( - input_types: Union[List[Union[str, pa.DataType]], Union[str, pa.DataType]], - result_types: Union[List[Union[str, pa.DataType]], Union[str, pa.DataType]], - name: Optional[str] = None, -) -> Callable: - """ - Annotation for creating a user-defined table function. - - Parameters: - - input_types: A list of strings or Arrow data types that specifies the input data types. - - result_types A list of strings or Arrow data types that specifies the return value types. - - name: An optional string specifying the function name. If not provided, the original name will be used. - - Example: - ``` - @udtf(input_types='INT', result_types='INT') - def series(n): - for i in range(n): - yield i - ``` - """ - - return lambda f: UserDefinedTableFunctionWrapper(f, input_types, result_types, name) - - -class UdfServer(pa.flight.FlightServerBase): - """ - A server that provides user-defined functions to clients. - - Example: - ``` - server = UdfServer(location="0.0.0.0:8815") - server.add_function(my_udf) - server.serve() - ``` - """ - - # UDF server based on Apache Arrow Flight protocol. - # Reference: https://arrow.apache.org/cookbook/py/flight.html#simple-parquet-storage-service-with-arrow-flight - - _location: str - _functions: Dict[str, UserDefinedFunction] - - def __init__(self, location="0.0.0.0:8815", **kwargs): - super(UdfServer, self).__init__("grpc://" + location, **kwargs) - self._location = location - self._functions = {} - - def get_flight_info(self, context, descriptor): - """Return the result schema of a function.""" - udf = self._functions[descriptor.path[0].decode("utf-8")] - # return the concatenation of input and output schema - full_schema = pa.schema(list(udf._input_schema) + list(udf._result_schema)) - # we use `total_records` to indicate the number of input arguments - return pa.flight.FlightInfo( - schema=full_schema, - descriptor=descriptor, - endpoints=[], - total_records=len(udf._input_schema), - total_bytes=0, - ) - - def add_function(self, udf: UserDefinedFunction): - """Add a function to the server.""" - name = udf._name - if name in self._functions: - raise ValueError("Function already exists: " + name) - - input_types = ",".join( - [_data_type_to_string(t) for t in udf._input_schema.types] - ) - if isinstance(udf, TableFunction): - output_type = udf._result_schema.types[-1] - if isinstance(output_type, pa.StructType): - output_type = ",".join( - f"field_{i} {_data_type_to_string(field.type)}" - for i, field in enumerate(output_type) - ) - output_type = f"TABLE({output_type})" - else: - output_type = _data_type_to_string(output_type) - output_type = f"TABLE(output {output_type})" - else: - output_type = _data_type_to_string(udf._result_schema.types[-1]) - - sql = f"CREATE FUNCTION {name}({input_types}) RETURNS {output_type} AS '{name}' USING LINK 'http://{self._location}';" - print(f"added function: {name}, corresponding SQL:\n{sql}\n") - self._functions[name] = udf - - def do_exchange(self, context, descriptor, reader, writer): - """Call a function from the client.""" - udf = self._functions[descriptor.path[0].decode("utf-8")] - writer.begin(udf._result_schema) - try: - for batch in reader: - # print(pa.Table.from_batches([batch.data])) - for output_batch in udf.eval_batch(batch.data): - writer.write_batch(output_batch) - except Exception as e: - print(traceback.print_exc()) - raise e - - def serve(self): - """ - Block until the server shuts down. - - This method only returns if shutdown() is called or a signal (SIGINT, SIGTERM) received. - """ - print( - "Note: You can use arbitrary function names and struct field names in CREATE FUNCTION statements." - f"\n\nlistening on {self._location}" - ) - signal.signal(signal.SIGTERM, lambda s, f: self.shutdown()) - super(UdfServer, self).serve() - - -def _to_data_type(t: Union[str, pa.DataType]) -> pa.DataType: - """ - Convert a SQL data type string or `pyarrow.DataType` to `pyarrow.DataType`. - """ - if isinstance(t, str): - return _string_to_data_type(t) - else: - return t - - -# we use `large_binary` to represent unconstrained decimal type -UNCONSTRAINED_DECIMAL = pa.large_binary() -JSONB = pa.large_string() - - -def _string_to_data_type(type_str: str): - """ - Convert a SQL data type string to `pyarrow.DataType`. - """ - type_str = type_str.upper() - if type_str.endswith("[]"): - return pa.list_(_string_to_data_type(type_str[:-2])) - elif type_str in ("BOOLEAN", "BOOL"): - return pa.bool_() - elif type_str in ("SMALLINT", "INT2"): - return pa.int16() - elif type_str in ("INT", "INTEGER", "INT4"): - return pa.int32() - elif type_str in ("BIGINT", "INT8"): - return pa.int64() - elif type_str in ("FLOAT4", "REAL"): - return pa.float32() - elif type_str in ("FLOAT8", "DOUBLE PRECISION"): - return pa.float64() - elif type_str.startswith("DECIMAL") or type_str.startswith("NUMERIC"): - if type_str == "DECIMAL" or type_str == "NUMERIC": - return UNCONSTRAINED_DECIMAL - rest = type_str[8:-1] # remove "DECIMAL(" and ")" - if "," in rest: - precision, scale = rest.split(",") - return pa.decimal128(int(precision), int(scale)) - else: - return pa.decimal128(int(rest), 0) - elif type_str in ("DATE"): - return pa.date32() - elif type_str in ("TIME", "TIME WITHOUT TIME ZONE"): - return pa.time64("us") - elif type_str in ("TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"): - return pa.timestamp("us") - elif type_str.startswith("INTERVAL"): - return pa.month_day_nano_interval() - elif type_str in ("VARCHAR"): - return pa.string() - elif type_str in ("JSONB"): - return JSONB - elif type_str in ("BYTEA"): - return pa.binary() - elif type_str.startswith("STRUCT"): - # extract 'STRUCT, ...>' - type_list = type_str[7:-1] # strip "STRUCT<>" - fields = [] - elements = [] - start = 0 - depth = 0 - for i, c in enumerate(type_list): - if c == "<": - depth += 1 - elif c == ">": - depth -= 1 - elif c == "," and depth == 0: - type_str = type_list[start:i].strip() - fields.append(pa.field("", _string_to_data_type(type_str))) - start = i + 1 - type_str = type_list[start:].strip() - fields.append(pa.field("", _string_to_data_type(type_str))) - return pa.struct(fields) - - raise ValueError(f"Unsupported type: {type_str}") - - -def _data_type_to_string(t: pa.DataType) -> str: - """ - Convert a `pyarrow.DataType` to a SQL data type string. - """ - if isinstance(t, pa.ListType): - return _data_type_to_string(t.value_type) + "[]" - elif t.equals(pa.bool_()): - return "BOOLEAN" - elif t.equals(pa.int16()): - return "SMALLINT" - elif t.equals(pa.int32()): - return "INT" - elif t.equals(pa.int64()): - return "BIGINT" - elif t.equals(pa.float32()): - return "FLOAT4" - elif t.equals(pa.float64()): - return "FLOAT8" - elif t.equals(UNCONSTRAINED_DECIMAL): - return "DECIMAL" - elif pa.types.is_decimal(t): - return f"DECIMAL({t.precision},{t.scale})" - elif t.equals(pa.date32()): - return "DATE" - elif t.equals(pa.time64("us")): - return "TIME" - elif t.equals(pa.timestamp("us")): - return "TIMESTAMP" - elif t.equals(pa.month_day_nano_interval()): - return "INTERVAL" - elif t.equals(pa.string()): - return "VARCHAR" - elif t.equals(JSONB): - return "JSONB" - elif t.equals(pa.binary()): - return "BYTEA" - elif isinstance(t, pa.StructType): - return ( - "STRUCT<" - + ",".join( - f"f{i+1} {_data_type_to_string(field.type)}" - for i, field in enumerate(t) - ) - + ">" - ) - else: - raise ValueError(f"Unsupported type: {t}") diff --git a/src/expr/udf/python/risingwave/udf/health_check.py b/src/expr/udf/python/risingwave/udf/health_check.py deleted file mode 100644 index ad2d38681a6cc..0000000000000 --- a/src/expr/udf/python/risingwave/udf/health_check.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2024 RisingWave Labs -# -# Licensed 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. - -from pyarrow.flight import FlightClient -import sys - - -def check_udf_service_available(addr: str) -> bool: - """Check if the UDF service is available at the given address.""" - try: - client = FlightClient(f"grpc://{addr}") - client.wait_for_available() - return True - except Exception as e: - print(f"Error connecting to RisingWave UDF service: {str(e)}") - return False - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print("usage: python3 health_check.py ") - sys.exit(1) - - server_address = sys.argv[1] - if check_udf_service_available(server_address): - print("OK") - else: - print("unavailable") - exit(-1) diff --git a/src/expr/udf/src/error.rs b/src/expr/udf/src/error.rs deleted file mode 100644 index fc6733052b137..0000000000000 --- a/src/expr/udf/src/error.rs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -use arrow_flight::error::FlightError; -use thiserror::Error; -use thiserror_ext::{Box, Construct}; - -/// A specialized `Result` type for UDF operations. -pub type Result = std::result::Result; - -/// The error type for UDF operations. -#[derive(Error, Debug, Box, Construct)] -#[thiserror_ext(newtype(name = Error))] -pub enum ErrorInner { - #[error("failed to send requests to UDF service: {0}")] - Tonic(#[from] tonic::Status), - - #[error("failed to call UDF: {0}")] - Flight(#[from] FlightError), - - #[error("type mismatch: {0}")] - TypeMismatch(String), - - #[error("arrow error: {0}")] - Arrow(#[from] arrow_schema::ArrowError), - - #[error("UDF unsupported: {0}")] - // TODO(error-handling): should prefer use error types than strings. - Unsupported(String), - - #[error("UDF service returned no data")] - NoReturned, - - #[error("Flight service error: {0}")] - ServiceError(String), -} - -impl Error { - /// Returns true if the error is caused by a connection error. - pub fn is_connection_error(&self) -> bool { - match self.inner() { - // Connection refused - ErrorInner::Tonic(status) if status.code() == tonic::Code::Unavailable => true, - _ => false, - } - } - - pub fn is_tonic_error(&self) -> bool { - matches!( - self.inner(), - ErrorInner::Tonic(_) | ErrorInner::Flight(FlightError::Tonic(_)) - ) - } -} - -static_assertions::const_assert_eq!(std::mem::size_of::(), 8); diff --git a/src/expr/udf/src/external.rs b/src/expr/udf/src/external.rs deleted file mode 100644 index 7560638b03985..0000000000000 --- a/src/expr/udf/src/external.rs +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -use std::str::FromStr; -use std::time::Duration; - -use arrow_array::RecordBatch; -use arrow_flight::decode::FlightRecordBatchStream; -use arrow_flight::encode::FlightDataEncoderBuilder; -use arrow_flight::error::FlightError; -use arrow_flight::flight_service_client::FlightServiceClient; -use arrow_flight::{FlightData, FlightDescriptor}; -use arrow_schema::Schema; -use cfg_or_panic::cfg_or_panic; -use futures_util::{stream, FutureExt, Stream, StreamExt, TryStreamExt}; -use ginepro::{LoadBalancedChannel, ResolutionStrategy}; -use risingwave_common::util::addr::HostAddr; -use thiserror_ext::AsReport; -use tokio::time::Duration as TokioDuration; -use tonic::transport::Channel; - -use crate::metrics::GLOBAL_METRICS; -use crate::{Error, Result}; - -// Interval between two successive probes of the UDF DNS. -const DNS_PROBE_INTERVAL_SECS: u64 = 5; -// Timeout duration for performing an eager DNS resolution. -const EAGER_DNS_RESOLVE_TIMEOUT_SECS: u64 = 5; -const REQUEST_TIMEOUT_SECS: u64 = 5; -const CONNECT_TIMEOUT_SECS: u64 = 5; - -/// Client for external function service based on Arrow Flight. -#[derive(Debug)] -pub struct ArrowFlightUdfClient { - client: FlightServiceClient, - addr: String, -} - -// TODO: support UDF in simulation -#[cfg_or_panic(not(madsim))] -impl ArrowFlightUdfClient { - /// Connect to a UDF service. - pub async fn connect(addr: &str) -> Result { - Self::connect_inner( - addr, - ResolutionStrategy::Eager { - timeout: TokioDuration::from_secs(EAGER_DNS_RESOLVE_TIMEOUT_SECS), - }, - ) - .await - } - - /// Connect to a UDF service lazily (i.e. only when the first request is sent). - pub fn connect_lazy(addr: &str) -> Result { - Self::connect_inner(addr, ResolutionStrategy::Lazy) - .now_or_never() - .unwrap() - } - - async fn connect_inner( - mut addr: &str, - resolution_strategy: ResolutionStrategy, - ) -> Result { - if addr.starts_with("http://") { - addr = addr.strip_prefix("http://").unwrap(); - } - if addr.starts_with("https://") { - addr = addr.strip_prefix("https://").unwrap(); - } - let host_addr = HostAddr::from_str(addr).map_err(|e| { - Error::service_error(format!("invalid address: {}, err: {}", addr, e.as_report())) - })?; - let channel = LoadBalancedChannel::builder((host_addr.host.clone(), host_addr.port)) - .dns_probe_interval(std::time::Duration::from_secs(DNS_PROBE_INTERVAL_SECS)) - .timeout(Duration::from_secs(REQUEST_TIMEOUT_SECS)) - .connect_timeout(Duration::from_secs(CONNECT_TIMEOUT_SECS)) - .resolution_strategy(resolution_strategy) - .channel() - .await - .map_err(|e| { - Error::service_error(format!( - "failed to create LoadBalancedChannel, address: {}, err: {}", - host_addr, - e.as_report() - )) - })?; - let client = FlightServiceClient::new(channel.into()); - Ok(Self { - client, - addr: addr.into(), - }) - } - - /// Check if the function is available and the schema is match. - pub async fn check(&self, id: &str, args: &Schema, returns: &Schema) -> Result<()> { - let descriptor = FlightDescriptor::new_path(vec![id.into()]); - - let response = self.client.clone().get_flight_info(descriptor).await?; - - // check schema - let info = response.into_inner(); - let input_num = info.total_records as usize; - let full_schema = Schema::try_from(info).map_err(|e| { - FlightError::DecodeError(format!("Error decoding schema: {}", e.as_report())) - })?; - if input_num > full_schema.fields.len() { - return Err(Error::service_error(format!( - "function {:?} schema info not consistency: input_num: {}, total_fields: {}", - id, - input_num, - full_schema.fields.len() - ))); - } - - let (input_fields, return_fields) = full_schema.fields.split_at(input_num); - let actual_input_types: Vec<_> = input_fields.iter().map(|f| f.data_type()).collect(); - let actual_result_types: Vec<_> = return_fields.iter().map(|f| f.data_type()).collect(); - let expect_input_types: Vec<_> = args.fields.iter().map(|f| f.data_type()).collect(); - let expect_result_types: Vec<_> = returns.fields.iter().map(|f| f.data_type()).collect(); - if !data_types_match(&expect_input_types, &actual_input_types) { - return Err(Error::type_mismatch(format!( - "function: {:?}, expect arguments: {:?}, actual: {:?}", - id, expect_input_types, actual_input_types - ))); - } - if !data_types_match(&expect_result_types, &actual_result_types) { - return Err(Error::type_mismatch(format!( - "function: {:?}, expect return: {:?}, actual: {:?}", - id, expect_result_types, actual_result_types - ))); - } - Ok(()) - } - - /// Call a function. - pub async fn call(&self, id: &str, input: RecordBatch) -> Result { - self.call_internal(id, input).await - } - - async fn call_internal(&self, id: &str, input: RecordBatch) -> Result { - let mut output_stream = self - .call_stream_internal(id, stream::once(async { input })) - .await?; - let mut batches = vec![]; - while let Some(batch) = output_stream.next().await { - batches.push(batch?); - } - Ok(arrow_select::concat::concat_batches( - output_stream.schema().ok_or_else(Error::no_returned)?, - batches.iter(), - )?) - } - - /// Call a function, retry up to 5 times / 3s if connection is broken. - pub async fn call_with_retry(&self, id: &str, input: RecordBatch) -> Result { - let mut backoff = Duration::from_millis(100); - for i in 0..5 { - match self.call(id, input.clone()).await { - Err(err) if err.is_connection_error() && i != 4 => { - tracing::error!(error = %err.as_report(), "UDF connection error. retry..."); - } - ret => return ret, - } - tokio::time::sleep(backoff).await; - backoff *= 2; - } - unreachable!() - } - - /// Always retry on connection error - pub async fn call_with_always_retry_on_network_error( - &self, - id: &str, - input: RecordBatch, - fragment_id: &str, - ) -> Result { - let mut backoff = Duration::from_millis(100); - let metrics = &*GLOBAL_METRICS; - let labels: &[&str; 4] = &[&self.addr, "external", id, fragment_id]; - loop { - match self.call(id, input.clone()).await { - Err(err) if err.is_tonic_error() => { - tracing::error!(error = %err.as_report(), "UDF tonic error. retry..."); - } - ret => { - if ret.is_err() { - tracing::error!(error = %ret.as_ref().unwrap_err().as_report(), "UDF error. exiting..."); - } - return ret; - } - } - metrics.udf_retry_count.with_label_values(labels).inc(); - tokio::time::sleep(backoff).await; - backoff *= 2; - } - } - - /// Call a function with streaming input and output. - #[panic_return = "Result>"] - pub async fn call_stream( - &self, - id: &str, - inputs: impl Stream + Send + 'static, - ) -> Result> + Send + 'static> { - Ok(self - .call_stream_internal(id, inputs) - .await? - .map_err(|e| e.into())) - } - - async fn call_stream_internal( - &self, - id: &str, - inputs: impl Stream + Send + 'static, - ) -> Result { - let descriptor = FlightDescriptor::new_path(vec![id.into()]); - let flight_data_stream = - FlightDataEncoderBuilder::new() - .build(inputs.map(Ok)) - .map(move |res| FlightData { - // TODO: fill descriptor only for the first message - flight_descriptor: Some(descriptor.clone()), - ..res.unwrap() - }); - - // call `do_exchange` on Flight server - let response = self.client.clone().do_exchange(flight_data_stream).await?; - - // decode response - let stream = response.into_inner(); - Ok(FlightRecordBatchStream::new_from_flight_data( - // convert tonic::Status to FlightError - stream.map_err(|e| e.into()), - )) - } - - pub fn get_addr(&self) -> &str { - &self.addr - } -} - -/// Check if two list of data types match, ignoring field names. -fn data_types_match(a: &[&arrow_schema::DataType], b: &[&arrow_schema::DataType]) -> bool { - if a.len() != b.len() { - return false; - } - #[allow(clippy::disallowed_methods)] - a.iter().zip(b.iter()).all(|(a, b)| a.equals_datatype(b)) -} diff --git a/src/expr/udf/src/lib.rs b/src/expr/udf/src/lib.rs deleted file mode 100644 index ddd8cf1bdeab9..0000000000000 --- a/src/expr/udf/src/lib.rs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -#![feature(error_generic_member_access)] -#![feature(lazy_cell)] - -mod error; -mod external; -pub mod metrics; - -pub use error::{Error, Result}; -pub use external::ArrowFlightUdfClient; -pub use metrics::GLOBAL_METRICS; diff --git a/src/expr/udf/src/metrics.rs b/src/expr/udf/src/metrics.rs deleted file mode 100644 index 50ef1b068307d..0000000000000 --- a/src/expr/udf/src/metrics.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -use std::sync::LazyLock; - -use prometheus::{ - exponential_buckets, register_histogram_vec_with_registry, - register_int_counter_vec_with_registry, HistogramVec, IntCounterVec, Registry, -}; -use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY; - -/// Monitor metrics for UDF. -#[derive(Debug, Clone)] -pub struct Metrics { - /// Number of successful UDF calls. - pub udf_success_count: IntCounterVec, - /// Number of failed UDF calls. - pub udf_failure_count: IntCounterVec, - /// Total number of retried UDF calls. - pub udf_retry_count: IntCounterVec, - /// Input chunk rows of UDF calls. - pub udf_input_chunk_rows: HistogramVec, - /// The latency of UDF calls in seconds. - pub udf_latency: HistogramVec, - /// Total number of input rows of UDF calls. - pub udf_input_rows: IntCounterVec, - /// Total number of input bytes of UDF calls. - pub udf_input_bytes: IntCounterVec, -} - -/// Global UDF metrics. -pub static GLOBAL_METRICS: LazyLock = - LazyLock::new(|| Metrics::new(&GLOBAL_METRICS_REGISTRY)); - -impl Metrics { - fn new(registry: &Registry) -> Self { - let labels = &["link", "language", "name", "fragment_id"]; - let udf_success_count = register_int_counter_vec_with_registry!( - "udf_success_count", - "Total number of successful UDF calls", - labels, - registry - ) - .unwrap(); - let udf_failure_count = register_int_counter_vec_with_registry!( - "udf_failure_count", - "Total number of failed UDF calls", - labels, - registry - ) - .unwrap(); - let udf_retry_count = register_int_counter_vec_with_registry!( - "udf_retry_count", - "Total number of retried UDF calls", - labels, - registry - ) - .unwrap(); - let udf_input_chunk_rows = register_histogram_vec_with_registry!( - "udf_input_chunk_rows", - "Input chunk rows of UDF calls", - labels, - exponential_buckets(1.0, 2.0, 10).unwrap(), // 1 to 1024 - registry - ) - .unwrap(); - let udf_latency = register_histogram_vec_with_registry!( - "udf_latency", - "The latency(s) of UDF calls", - labels, - exponential_buckets(0.000001, 2.0, 30).unwrap(), // 1us to 1000s - registry - ) - .unwrap(); - let udf_input_rows = register_int_counter_vec_with_registry!( - "udf_input_rows", - "Total number of input rows of UDF calls", - labels, - registry - ) - .unwrap(); - let udf_input_bytes = register_int_counter_vec_with_registry!( - "udf_input_bytes", - "Total number of input bytes of UDF calls", - labels, - registry - ) - .unwrap(); - - Metrics { - udf_success_count, - udf_failure_count, - udf_retry_count, - udf_input_chunk_rows, - udf_latency, - udf_input_rows, - udf_input_bytes, - } - } -} diff --git a/src/frontend/Cargo.toml b/src/frontend/Cargo.toml index 3cba7afe82660..3decb8535ca67 100644 --- a/src/frontend/Cargo.toml +++ b/src/frontend/Cargo.toml @@ -18,6 +18,7 @@ normal = ["workspace-hack"] anyhow = "1" arc-swap = "1" arrow-schema = { workspace = true } +arrow-udf-flight = { workspace = true } arrow-udf-wasm = { workspace = true } async-recursion = "1.1.0" async-trait = "0.1" @@ -40,7 +41,7 @@ futures-async-stream = { workspace = true } iana-time-zone = "0.1" icelake = { workspace = true } itertools = { workspace = true } -jsonbb = "0.1.2" +jsonbb = { workspace = true } linkme = { version = "0.3", features = ["used_linker"] } maplit = "1" md5 = "0.7.0" @@ -72,7 +73,6 @@ risingwave_pb = { workspace = true } risingwave_rpc_client = { workspace = true } risingwave_sqlparser = { workspace = true } risingwave_storage = { workspace = true } -risingwave_udf = { workspace = true } risingwave_variables = { workspace = true } rw_futures_util = { workspace = true } serde = { version = "1", features = ["derive"] } diff --git a/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml b/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml index 411d129d415ff..bdd6f11f0b7be 100644 --- a/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml +++ b/src/frontend/planner_test/tests/testdata/input/recursive_cte.yaml @@ -1,12 +1,16 @@ - name: basic sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT * FROM t1; expected_outputs: - - planner_error + - logical_plan - name: output column follows lhs sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a FROM t1; expected_outputs: - - planner_error + - logical_plan +- name: with normal column + sql: WITH RECURSIVE t(a) AS (VALUES(1) UNION ALL SELECT a + 1 FROM t WHERE a < 100) SELECT * FROM t; + expected_outputs: + - logical_plan - name: name a is leaked outside sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a; expected_outputs: - - binder_error + - binder_error \ No newline at end of file diff --git a/src/frontend/planner_test/tests/testdata/output/agg.yaml b/src/frontend/planner_test/tests/testdata/output/agg.yaml index e618a58500783..9a07df7558d96 100644 --- a/src/frontend/planner_test/tests/testdata/output/agg.yaml +++ b/src/frontend/planner_test/tests/testdata/output/agg.yaml @@ -1278,7 +1278,7 @@ logical_plan: |- LogicalProject { exprs: [Case((count(t.v1) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - ((sum(t.v1)::Decimal * sum(t.v1)::Decimal) / count(t.v1)::Decimal)) / (count(t.v1) - 1:Int64)::Decimal))) as $expr2, Sqrt(((sum($expr1)::Decimal - ((sum(t.v1)::Decimal * sum(t.v1)::Decimal) / count(t.v1)::Decimal)) / count(t.v1)::Decimal)) as $expr3] } └─LogicalAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } - └─LogicalProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1] } + └─LogicalProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1] } └─LogicalScan { table: t, columns: [t.v1, t._row_id] } batch_plan: |- BatchProject { exprs: [Case((sum0(count(t.v1)) <= 1:Int64), null:Decimal, Sqrt(((sum(sum($expr1))::Decimal - (($expr2 * $expr2) / $expr3)) / (sum0(count(t.v1)) - 1:Int64)::Decimal))) as $expr4, Sqrt(((sum(sum($expr1))::Decimal - (($expr2 * $expr2) / $expr3)) / $expr3)) as $expr5] } @@ -1286,14 +1286,14 @@ └─BatchSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v1)), sum0(count(t.v1))] } └─BatchExchange { order: [], dist: Single } └─BatchSimpleAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } - └─BatchProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1] } + └─BatchProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1] } └─BatchScan { table: t, columns: [t.v1], distribution: SomeShard } batch_local_plan: |- BatchProject { exprs: [Case((count(t.v1) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - (($expr2 * $expr2) / $expr3)) / (count(t.v1) - 1:Int64)::Decimal))) as $expr4, Sqrt(((sum($expr1)::Decimal - (($expr2 * $expr2) / $expr3)) / $expr3)) as $expr5] } └─BatchProject { exprs: [sum($expr1), sum(t.v1), count(t.v1), sum(t.v1)::Decimal as $expr2, count(t.v1)::Decimal as $expr3] } └─BatchSimpleAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } └─BatchExchange { order: [], dist: Single } - └─BatchProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1] } + └─BatchProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1] } └─BatchScan { table: t, columns: [t.v1], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [stddev_samp, stddev_pop], stream_key: [], pk_columns: [], pk_conflict: NoCheck } @@ -1302,7 +1302,7 @@ └─StreamSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v1)), sum0(count(t.v1)), count] } └─StreamExchange { dist: Single } └─StreamStatelessSimpleAgg { aggs: [sum($expr1), sum(t.v1), count(t.v1)] } - └─StreamProject { exprs: [t.v1, (t.v1 * t.v1) as $expr1, t._row_id] } + └─StreamProject { exprs: [(t.v1 * t.v1) as $expr1, t.v1, t._row_id] } └─StreamTableScan { table: t, columns: [t.v1, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - name: stddev_samp with other columns sql: | @@ -1310,7 +1310,7 @@ logical_plan: |- LogicalProject { exprs: [count('':Varchar), Case((count(1:Int32) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - ((sum(1:Int32)::Decimal * sum(1:Int32)::Decimal) / count(1:Int32)::Decimal)) / (count(1:Int32) - 1:Int64)::Decimal))) as $expr2] } └─LogicalAgg { aggs: [count('':Varchar), sum($expr1), sum(1:Int32), count(1:Int32)] } - └─LogicalProject { exprs: ['':Varchar, 1:Int32, (1:Int32 * 1:Int32) as $expr1] } + └─LogicalProject { exprs: ['':Varchar, (1:Int32 * 1:Int32) as $expr1, 1:Int32] } └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } - name: stddev_samp with group sql: | @@ -1319,7 +1319,7 @@ logical_plan: |- LogicalProject { exprs: [Case((count(t.v) <= 1:Int64), null:Decimal, Sqrt(((sum($expr1)::Decimal - ((sum(t.v)::Decimal * sum(t.v)::Decimal) / count(t.v)::Decimal)) / (count(t.v) - 1:Int64)::Decimal))) as $expr2] } └─LogicalAgg { group_key: [t.w], aggs: [sum($expr1), sum(t.v), count(t.v)] } - └─LogicalProject { exprs: [t.w, t.v, (t.v * t.v) as $expr1] } + └─LogicalProject { exprs: [t.w, (t.v * t.v) as $expr1, t.v] } └─LogicalScan { table: t, columns: [t.v, t.w, t._row_id] } - name: force two phase aggregation should succeed with UpstreamHashShard and SomeShard (batch only). sql: | diff --git a/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml b/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml index ceb706446f986..0e5d72b3499a3 100644 --- a/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/cse_expr.yaml @@ -67,7 +67,7 @@ └─BatchSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v)), sum0(count(t.v))] } └─BatchExchange { order: [], dist: Single } └─BatchSimpleAgg { aggs: [sum($expr1), sum(t.v), count(t.v)] } - └─BatchProject { exprs: [t.v, (t.v * t.v) as $expr1] } + └─BatchProject { exprs: [(t.v * t.v) as $expr1, t.v] } └─BatchScan { table: t, columns: [t.v], distribution: SomeShard } stream_plan: |- StreamMaterialize { columns: [stddev_pop, stddev_samp, var_pop, var_samp], stream_key: [], pk_columns: [], pk_conflict: NoCheck } @@ -78,7 +78,7 @@ └─StreamSimpleAgg { aggs: [sum(sum($expr1)), sum(sum(t.v)), sum0(count(t.v)), count] } └─StreamExchange { dist: Single } └─StreamStatelessSimpleAgg { aggs: [sum($expr1), sum(t.v), count(t.v)] } - └─StreamProject { exprs: [t.v, (t.v * t.v) as $expr1, t._row_id] } + └─StreamProject { exprs: [(t.v * t.v) as $expr1, t.v, t._row_id] } └─StreamTableScan { table: t, columns: [t.v, t._row_id], stream_scan_type: ArrangementBackfill, stream_key: [t._row_id], pk: [_row_id], dist: UpstreamHashShard(t._row_id) } - name: Common sub expression shouldn't extract partial expression of `some`/`all`. See 11766 sql: | diff --git a/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml b/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml index f819990200576..89aea24f2bd80 100644 --- a/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml +++ b/src/frontend/planner_test/tests/testdata/output/dynamic_filter.yaml @@ -40,7 +40,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: With Simple Agg upstream materialized view on inner side before: - create_tables @@ -97,7 +97,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: Ensure error on output columns from inner before: - create_tables @@ -111,7 +111,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: Use Inner Join for equi condition before: - create_tables diff --git a/src/frontend/planner_test/tests/testdata/output/expr.yaml b/src/frontend/planner_test/tests/testdata/output/expr.yaml index 7f9f63049cd70..6875160cffa79 100644 --- a/src/frontend/planner_test/tests/testdata/output/expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/expr.yaml @@ -648,7 +648,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: without variadic keyword sql: | create table t(a varchar, b varchar); diff --git a/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml b/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml index fbdccf685ca2e..a1c014161988e 100644 --- a/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml +++ b/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml @@ -164,7 +164,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - id: aggregate with over clause, without ORDER BY and frame definition sql: | create table t(x int, y int, z int, w int); diff --git a/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml b/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml index 18e0e18726d38..93872d5848bd6 100644 --- a/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml +++ b/src/frontend/planner_test/tests/testdata/output/recursive_cte.yaml @@ -1,14 +1,33 @@ # This file is automatically generated. See `src/frontend/planner_test/README.md` for more information. - name: basic sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT * FROM t1; - planner_error: |- - Feature is not yet implemented: recursive CTE is not supported - Tracking issue: https://github.com/risingwavelabs/risingwave/issues/15135 + logical_plan: |- + LogicalProject { exprs: [$expr1] } + └─LogicalRecursiveUnion { id: 4 } + ├─LogicalProject { exprs: [1:Int32] } + │ └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } + └─LogicalProject { exprs: [(1:Int32 + 1:Int32) as $expr1] } + └─LogicalFilter { predicate: (1:Int32 < 10:Int32) } + └─LogicalCteRef { share_id: 0 } - name: output column follows lhs sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a FROM t1; - planner_error: |- - Feature is not yet implemented: recursive CTE is not supported - Tracking issue: https://github.com/risingwavelabs/risingwave/issues/15135 + logical_plan: |- + LogicalProject { exprs: [$expr1] } + └─LogicalRecursiveUnion { id: 4 } + ├─LogicalProject { exprs: [1:Int32] } + │ └─LogicalValues { rows: [[]], schema: Schema { fields: [] } } + └─LogicalProject { exprs: [(1:Int32 + 1:Int32) as $expr1] } + └─LogicalFilter { predicate: (1:Int32 < 10:Int32) } + └─LogicalCteRef { share_id: 0 } +- name: with normal column + sql: WITH RECURSIVE t(a) AS (VALUES(1) UNION ALL SELECT a + 1 FROM t WHERE a < 100) SELECT * FROM t; + logical_plan: |- + LogicalProject { exprs: [$expr1] } + └─LogicalRecursiveUnion { id: 3 } + ├─LogicalValues { rows: [[1:Int32]], schema: Schema { fields: [*VALUES*_0.column_0:Int32] } } + └─LogicalProject { exprs: [(*VALUES*_0.column_0 + 1:Int32) as $expr1] } + └─LogicalFilter { predicate: (*VALUES*_0.column_0 < 100:Int32) } + └─LogicalCteRef { share_id: 0 } - name: name a is leaked outside sql: WITH RECURSIVE t1 AS (SELECT 1 AS a UNION ALL SELECT a + 1 FROM t1 WHERE a < 10) SELECT a; binder_error: | diff --git a/src/frontend/planner_test/tests/testdata/output/subquery.yaml b/src/frontend/planner_test/tests/testdata/output/subquery.yaml index e17f2f3cf699a..b981f8f968ed8 100644 --- a/src/frontend/planner_test/tests/testdata/output/subquery.yaml +++ b/src/frontend/planner_test/tests/testdata/output/subquery.yaml @@ -439,7 +439,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - sql: | SELECT 1 a, (SELECT regexp_matches('barbeque barbeque', '(bar)(beque)', 'g')) b optimized_logical_plan_for_batch: |- @@ -658,7 +658,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: test over window subquery 3 sql: | CREATE TABLE integers(i INTEGER); @@ -715,7 +715,7 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ - name: test over window subquery 5 sql: | CREATE TABLE integers(i INTEGER, correlated_col int); diff --git a/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml b/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml index 4b9c4122a4222..37a68dba7f362 100644 --- a/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml +++ b/src/frontend/planner_test/tests/testdata/output/with_ordinality.yaml @@ -213,4 +213,4 @@ stream_error: |- Not supported: streaming nested-loop join HINT: The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. Consider rewriting the query to use dynamic filter as a substitute if possible. - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/ diff --git a/src/frontend/src/binder/bind_context.rs b/src/frontend/src/binder/bind_context.rs index 7a52de7decec4..02dbc6a28bc10 100644 --- a/src/frontend/src/binder/bind_context.rs +++ b/src/frontend/src/binder/bind_context.rs @@ -81,12 +81,12 @@ pub struct LateralBindContext { /// WITH RECURSIVE t(n) AS ( /// # -------------^ => Init /// VALUES (1) -/// # ----------^ => BaseResolved (after binding the base term) /// UNION ALL -/// SELECT n+1 FROM t WHERE n < 100 -/// # ------------------^ => Bound (we know exactly what the entire cte looks like) +/// SELECT n + 1 FROM t WHERE n < 100 +/// # --------------------^ => BaseResolved (after binding the base term, this relation will be bound to `Relation::BackCteRef`) /// ) /// SELECT sum(n) FROM t; +/// # -----------------^ => Bound (we know exactly what the entire `RecursiveUnion` looks like, and this relation will be bound to `Relation::Share`) /// ``` #[derive(Default, Debug, Clone)] pub enum BindingCteState { @@ -94,7 +94,7 @@ pub enum BindingCteState { #[default] Init, /// We know the schema form after the base term resolved. - BaseResolved { schema: Schema }, + BaseResolved { base: BoundSetExpr }, /// We get the whole bound result of the (recursive) CTE. Bound { query: Either, diff --git a/src/frontend/src/binder/expr/function.rs b/src/frontend/src/binder/expr/function.rs index 2134e08fe8c66..393e27081572c 100644 --- a/src/frontend/src/binder/expr/function.rs +++ b/src/frontend/src/binder/expr/function.rs @@ -1303,6 +1303,51 @@ impl Binder { ("pg_get_partkeydef", raw_literal(ExprImpl::literal_null(DataType::Varchar))), ("pg_encoding_to_char", raw_literal(ExprImpl::literal_varchar("UTF8".into()))), ("has_database_privilege", raw_literal(ExprImpl::literal_bool(true))), + ("has_table_privilege", raw(|binder, mut inputs|{ + if inputs.len() == 2 { + inputs.insert(0, ExprImpl::literal_varchar(binder.auth_context.user_name.clone())); + } + if inputs.len() == 3 { + if inputs[1].return_type() == DataType::Varchar { + inputs[1].cast_to_regclass_mut()?; + } + Ok(FunctionCall::new(ExprType::HasTablePrivilege, inputs)?.into()) + } else { + Err(ErrorCode::ExprError( + "Too many/few arguments for pg_catalog.has_table_privilege()".into(), + ) + .into()) + } + })), + ("has_any_column_privilege", raw(|binder, mut inputs|{ + if inputs.len() == 2 { + inputs.insert(0, ExprImpl::literal_varchar(binder.auth_context.user_name.clone())); + } + if inputs.len() == 3 { + if inputs[1].return_type() == DataType::Varchar { + inputs[1].cast_to_regclass_mut()?; + } + Ok(FunctionCall::new(ExprType::HasAnyColumnPrivilege, inputs)?.into()) + } else { + Err(ErrorCode::ExprError( + "Too many/few arguments for pg_catalog.has_any_column_privilege()".into(), + ) + .into()) + } + })), + ("has_schema_privilege", raw(|binder, mut inputs|{ + if inputs.len() == 2 { + inputs.insert(0, ExprImpl::literal_varchar(binder.auth_context.user_name.clone())); + } + if inputs.len() == 3 { + Ok(FunctionCall::new(ExprType::HasSchemaPrivilege, inputs)?.into()) + } else { + Err(ErrorCode::ExprError( + "Too many/few arguments for pg_catalog.has_schema_privilege()".into(), + ) + .into()) + } + })), ("pg_stat_get_numscans", raw_literal(ExprImpl::literal_bigint(0))), ("pg_backend_pid", raw(|binder, _inputs| { // FIXME: the session id is not global unique in multi-frontend env. diff --git a/src/frontend/src/binder/expr/value.rs b/src/frontend/src/binder/expr/value.rs index 0b6eace27083f..a0758a15d4440 100644 --- a/src/frontend/src/binder/expr/value.rs +++ b/src/frontend/src/binder/expr/value.rs @@ -221,13 +221,12 @@ impl Binder { #[cfg(test)] mod tests { use risingwave_common::types::test_utils::IntervalTestExt; - use risingwave_common::types::DataType; use risingwave_expr::expr::build_from_prost; use risingwave_sqlparser::ast::Value::Number; use super::*; use crate::binder::test_utils::mock_binder; - use crate::expr::{Expr, ExprImpl, ExprType, FunctionCall}; + use crate::expr::Expr; #[tokio::test] async fn test_bind_value() { diff --git a/src/frontend/src/binder/insert.rs b/src/frontend/src/binder/insert.rs index 1cc719d066420..e0b5ce422e75a 100644 --- a/src/frontend/src/binder/insert.rs +++ b/src/frontend/src/binder/insert.rs @@ -175,14 +175,14 @@ impl Binder { // is given and it is NOT equivalent to assignment cast over potential implicit cast inside. // For example, the following is valid: // - // ``` + // ```sql // create table t (v1 time); // insert into t values (timestamp '2020-01-01 01:02:03'), (time '03:04:05'); // ``` // // But the followings are not: // - // ``` + // ```sql // values (timestamp '2020-01-01 01:02:03'), (time '03:04:05'); // insert into t values (timestamp '2020-01-01 01:02:03'), (time '03:04:05') limit 1; // ``` diff --git a/src/frontend/src/binder/mod.rs b/src/frontend/src/binder/mod.rs index 3648f53d50271..8d72aa7e3d286 100644 --- a/src/frontend/src/binder/mod.rs +++ b/src/frontend/src/binder/mod.rs @@ -49,8 +49,9 @@ pub use insert::BoundInsert; use pgwire::pg_server::{Session, SessionId}; pub use query::BoundQuery; pub use relation::{ - BoundBaseTable, BoundJoin, BoundShare, BoundSource, BoundSystemTable, BoundWatermark, - BoundWindowTableFunction, Relation, ResolveQualifiedNameError, WindowTableFunctionKind, + BoundBackCteRef, BoundBaseTable, BoundJoin, BoundShare, BoundSource, BoundSystemTable, + BoundWatermark, BoundWindowTableFunction, Relation, ResolveQualifiedNameError, + WindowTableFunctionKind, }; pub use select::{BoundDistinct, BoundSelect}; pub use set_expr::*; @@ -649,6 +650,41 @@ mod tests { BackCteRef( BoundBackCteRef { share_id: 0, + base: Select( + BoundSelect { + distinct: All, + select_items: [ + Literal( + Literal { + data: Some( + Int32( + 1, + ), + ), + data_type: Some( + Int32, + ), + }, + ), + ], + aliases: [ + Some( + "a", + ), + ], + from: None, + where_clause: None, + group_by: GroupKey( + [], + ), + having: None, + schema: Schema { + fields: [ + a:Int32, + ], + }, + }, + ), }, ), ), diff --git a/src/frontend/src/binder/query.rs b/src/frontend/src/binder/query.rs index c92c6c78e6bc4..2aa1d066953c7 100644 --- a/src/frontend/src/binder/query.rs +++ b/src/frontend/src/binder/query.rs @@ -284,6 +284,7 @@ impl Binder { fn bind_with(&mut self, with: With) -> Result<()> { for cte_table in with.cte_tables { + // note that the new `share_id` for the rcte is generated here let share_id = self.next_share_id(); let Cte { alias, query, .. } = cte_table; let table_name = alias.name.real_value(); @@ -423,9 +424,7 @@ impl Binder { // reference: let mut base = self.bind_set_expr(left)?; - entry.borrow_mut().state = BindingCteState::BaseResolved { - schema: base.schema().clone(), - }; + entry.borrow_mut().state = BindingCteState::BaseResolved { base: base.clone() }; // Reset context for right side, but keep `cte_to_relation`. let new_context = std::mem::take(&mut self.context); diff --git a/src/frontend/src/binder/relation/cte_ref.rs b/src/frontend/src/binder/relation/cte_ref.rs index 86c40cc096dd1..87e87fb1823ae 100644 --- a/src/frontend/src/binder/relation/cte_ref.rs +++ b/src/frontend/src/binder/relation/cte_ref.rs @@ -13,14 +13,14 @@ // limitations under the License. use crate::binder::statement::RewriteExprsRecursive; -use crate::binder::ShareId; +use crate::binder::{BoundSetExpr, ShareId}; /// A CTE reference, currently only used in the back reference of recursive CTE. /// For the non-recursive one, see [`BoundShare`](super::BoundShare). #[derive(Debug, Clone)] pub struct BoundBackCteRef { - #[expect(dead_code)] pub(crate) share_id: ShareId, + pub(crate) base: BoundSetExpr, } impl RewriteExprsRecursive for BoundBackCteRef { diff --git a/src/frontend/src/binder/relation/join.rs b/src/frontend/src/binder/relation/join.rs index c2f820aff7744..d13b683be08b0 100644 --- a/src/frontend/src/binder/relation/join.rs +++ b/src/frontend/src/binder/relation/join.rs @@ -164,10 +164,10 @@ impl Binder { JoinConstraint::Using(cols) => { // sanity check for col in &cols { - if old_context.indices_of.get(&col.real_value()).is_none() { + if !old_context.indices_of.contains_key(&col.real_value()) { return Err(ErrorCode::ItemNotFound(format!("column \"{}\" specified in USING clause does not exist in left table", col.real_value())).into()); } - if self.context.indices_of.get(&col.real_value()).is_none() { + if !self.context.indices_of.contains_key(&col.real_value()) { return Err(ErrorCode::ItemNotFound(format!("column \"{}\" specified in USING clause does not exist in right table", col.real_value())).into()); } } diff --git a/src/frontend/src/binder/relation/mod.rs b/src/frontend/src/binder/relation/mod.rs index b7c288551191c..4de9c3cfaa9aa 100644 --- a/src/frontend/src/binder/relation/mod.rs +++ b/src/frontend/src/binder/relation/mod.rs @@ -26,7 +26,6 @@ use risingwave_sqlparser::ast::{ use thiserror::Error; use thiserror_ext::AsReport; -use self::cte_ref::BoundBackCteRef; use super::bind_context::ColumnBinding; use super::statement::RewriteExprsRecursive; use crate::binder::bind_context::{BindingCte, BindingCteState}; @@ -43,6 +42,7 @@ mod table_or_source; mod watermark; mod window_table_function; +pub use cte_ref::BoundBackCteRef; pub use join::BoundJoin; pub use share::BoundShare; pub use subquery::BoundSubquery; @@ -374,13 +374,13 @@ impl Binder { BindingCteState::Init => { Err(ErrorCode::BindError("Base term of recursive CTE not found, consider writing it to left side of the `UNION ALL` operator".to_string()).into()) } - BindingCteState::BaseResolved { schema } => { + BindingCteState::BaseResolved { base } => { self.bind_table_to_context( - schema.fields.iter().map(|f| (false, f.clone())), + base.schema().fields.iter().map(|f| (false, f.clone())), table_name.clone(), Some(original_alias), )?; - Ok(Relation::BackCteRef(Box::new(BoundBackCteRef { share_id }))) + Ok(Relation::BackCteRef(Box::new(BoundBackCteRef { share_id, base }))) } BindingCteState::Bound { query } => { let schema = match &query { diff --git a/src/frontend/src/binder/relation/table_function.rs b/src/frontend/src/binder/relation/table_function.rs index 9189e176e0d55..efb4de3c55510 100644 --- a/src/frontend/src/binder/relation/table_function.rs +++ b/src/frontend/src/binder/relation/table_function.rs @@ -143,7 +143,7 @@ impl Binder { // Note: named return value should take precedence over table alias. // But we don't support it yet. // e.g., - // ``` + // ```sql // > create function foo(ret out int) language sql as 'select 1'; // > select t.ret from foo() as t; // ``` diff --git a/src/frontend/src/binder/values.rs b/src/frontend/src/binder/values.rs index 93f9e3f4f8a18..9e190c3cf4dc5 100644 --- a/src/frontend/src/binder/values.rs +++ b/src/frontend/src/binder/values.rs @@ -154,7 +154,7 @@ impl Binder { #[cfg(test)] mod tests { - use risingwave_common::util::iter_util::{zip_eq_fast, ZipEqFast}; + use risingwave_common::util::iter_util::zip_eq_fast; use risingwave_sqlparser::ast::{Expr, Value}; use super::*; diff --git a/src/frontend/src/catalog/database_catalog.rs b/src/frontend/src/catalog/database_catalog.rs index ec040e309eba8..c562e5fc77a86 100644 --- a/src/frontend/src/catalog/database_catalog.rs +++ b/src/frontend/src/catalog/database_catalog.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use itertools::Itertools; use risingwave_common::catalog::PG_CATALOG_SCHEMA_NAME; use risingwave_pb::catalog::{PbDatabase, PbSchema}; +use risingwave_pb::user::grant_privilege::Object; use super::OwnedByUserCatalog; use crate::catalog::schema_catalog::SchemaCatalog; @@ -99,6 +100,16 @@ impl DatabaseCatalog { .find(|schema| schema.get_table_by_id(table_id).is_some()) } + pub fn get_grant_object_by_oid(&self, oid: u32) -> Option { + for schema in self.schema_by_name.values() { + let object = schema.get_grant_object_by_oid(oid); + if object.is_some() { + return object; + } + } + None + } + pub fn update_schema(&mut self, prost: &PbSchema) { let id = prost.id; let name = prost.name.clone(); diff --git a/src/frontend/src/catalog/schema_catalog.rs b/src/frontend/src/catalog/schema_catalog.rs index 56de1d743da41..20a99ad820af8 100644 --- a/src/frontend/src/catalog/schema_catalog.rs +++ b/src/frontend/src/catalog/schema_catalog.rs @@ -24,6 +24,7 @@ pub use risingwave_expr::sig::*; use risingwave_pb::catalog::{ PbConnection, PbFunction, PbIndex, PbSchema, PbSink, PbSource, PbSubscription, PbTable, PbView, }; +use risingwave_pb::user::grant_privilege::Object; use super::subscription_catalog::SubscriptionCatalog; use super::{OwnedByUserCatalog, SubscriptionId}; @@ -703,6 +704,23 @@ impl SchemaCatalog { .map(|s| s.to_owned()) } + pub fn get_grant_object_by_oid(&self, oid: u32) -> Option { + #[allow(clippy::manual_map)] + if self.get_table_by_id(&TableId::new(oid)).is_some() + || self.get_index_by_id(&IndexId::new(oid)).is_some() + { + Some(Object::TableId(oid)) + } else if self.get_source_by_id(&oid).is_some() { + Some(Object::SourceId(oid)) + } else if self.get_sink_by_id(&oid).is_some() { + Some(Object::SinkId(oid)) + } else if self.get_view_by_id(&oid).is_some() { + Some(Object::ViewId(oid)) + } else { + None + } + } + pub fn id(&self) -> SchemaId { self.id } diff --git a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs index 443fa255f4398..43f62aaee2105 100644 --- a/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs +++ b/src/frontend/src/catalog/system_catalog/rw_catalog/rw_hummock_branched_objects.rs @@ -19,11 +19,11 @@ use crate::catalog::system_catalog::SysCatalogReaderImpl; use crate::error::Result; #[derive(Fields)] -#[primary_key(object_id, sst_id)] // TODO: is this correct? +#[primary_key(object_id, compaction_group_id)] struct RwHummockBranchedObject { object_id: i64, - sst_id: i64, compaction_group_id: i64, + sst_id: Vec, } #[system_catalog(table, "rw_catalog.rw_hummock_branched_objects")] @@ -33,7 +33,7 @@ async fn read(reader: &SysCatalogReaderImpl) -> Result>, diff --git a/src/frontend/src/catalog/table_catalog.rs b/src/frontend/src/catalog/table_catalog.rs index 9b02402571d0f..d66e01bac1dc1 100644 --- a/src/frontend/src/catalog/table_catalog.rs +++ b/src/frontend/src/catalog/table_catalog.rs @@ -595,19 +595,15 @@ impl OwnedByUserCatalog for TableCatalog { #[cfg(test)] mod tests { - use risingwave_common::catalog::{ - row_id_column_desc, ColumnCatalog, ColumnDesc, ColumnId, TableId, - }; + use risingwave_common::catalog::{row_id_column_desc, ColumnDesc, ColumnId}; use risingwave_common::test_prelude::*; use risingwave_common::types::*; use risingwave_common::util::sort_util::OrderType; - use risingwave_pb::catalog::{PbStreamJobStatus, PbTable}; use risingwave_pb::plan_common::{ AdditionalColumn, ColumnDescVersion, PbColumnCatalog, PbColumnDesc, }; use super::*; - use crate::catalog::table_catalog::{TableCatalog, TableType}; #[test] fn test_into_table_catalog() { diff --git a/src/frontend/src/expr/agg_call.rs b/src/frontend/src/expr/agg_call.rs index 0f9493a694952..353f4416af1c0 100644 --- a/src/frontend/src/expr/agg_call.rs +++ b/src/frontend/src/expr/agg_call.rs @@ -21,13 +21,13 @@ use crate::utils::Condition; #[derive(Clone, Eq, PartialEq, Hash)] pub struct AggCall { - agg_kind: AggKind, - return_type: DataType, - args: Vec, - distinct: bool, - order_by: OrderBy, - filter: Condition, - direct_args: Vec, + pub agg_kind: AggKind, + pub return_type: DataType, + pub args: Vec, + pub distinct: bool, + pub order_by: OrderBy, + pub filter: Condition, + pub direct_args: Vec, } impl std::fmt::Debug for AggCall { diff --git a/src/frontend/src/expr/function_impl/has_privilege.rs b/src/frontend/src/expr/function_impl/has_privilege.rs new file mode 100644 index 0000000000000..735a9dd1b6d92 --- /dev/null +++ b/src/frontend/src/expr/function_impl/has_privilege.rs @@ -0,0 +1,189 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use std::collections::HashSet; + +use risingwave_expr::{capture_context, function, ExprError, Result}; +use risingwave_pb::user::grant_privilege::{Action, Object}; +use thiserror_ext::AsReport; + +use super::context::{CATALOG_READER, DB_NAME, USER_INFO_READER}; +use crate::catalog::CatalogReader; +use crate::user::user_service::UserInfoReader; + +#[inline(always)] +pub fn user_not_found_err(inner_err: &str) -> ExprError { + ExprError::InvalidParam { + name: "user", + reason: inner_err.into(), + } +} + +#[function("has_table_privilege(int4, int4, varchar) -> boolean")] +fn has_table_privilege(user_id: i32, table_oid: i32, privileges: &str) -> Result { + // does user have privilege for table + let user_name = get_user_name_by_id_captured(user_id)?; + has_table_privilege_1(user_name.as_str(), table_oid, privileges) +} + +#[function("has_table_privilege(varchar, int4, varchar) -> boolean")] +fn has_table_privilege_1(user_name: &str, table_oid: i32, privileges: &str) -> Result { + let allowed_actions = HashSet::new(); + let actions = parse_privilege(privileges, &allowed_actions)?; + // currently, we haven't support grant for view. + has_privilege_impl_captured( + user_name, + &get_grant_object_by_oid_captured(table_oid)?, + &actions, + ) +} + +#[function("has_any_column_privilege(int4, int4, varchar) -> boolean")] +fn has_any_column_privilege(user_id: i32, table_oid: i32, privileges: &str) -> Result { + // does user have privilege for any column of table + let user_name = get_user_name_by_id_captured(user_id)?; + has_any_column_privilege_1(user_name.as_str(), table_oid, privileges) +} + +#[function("has_any_column_privilege(varchar, int4, varchar) -> boolean")] +fn has_any_column_privilege_1(user_name: &str, table_oid: i32, privileges: &str) -> Result { + let allowed_actions = HashSet::from_iter([Action::Select, Action::Insert, Action::Update]); + let actions = parse_privilege(privileges, &allowed_actions)?; + has_privilege_impl_captured( + user_name, + &get_grant_object_by_oid_captured(table_oid)?, + &actions, + ) +} + +#[function("has_schema_privilege(varchar, int4, varchar) -> boolean")] +fn has_schema_privilege(user_name: &str, schema_oid: i32, privileges: &str) -> Result { + // does user have privilege for schema + let allowed_actions = HashSet::from_iter([Action::Create, Action::Usage]); + let actions = parse_privilege(privileges, &allowed_actions)?; + has_privilege_impl_captured(user_name, &Object::SchemaId(schema_oid as u32), &actions) +} + +#[function("has_schema_privilege(int4, varchar, varchar) -> boolean")] +fn has_schema_privilege_1(user_id: i32, schema_name: &str, privileges: &str) -> Result { + let user_name = get_user_name_by_id_captured(user_id)?; + let schema_oid = get_schema_id_by_name_captured(schema_name)?; + has_schema_privilege(user_name.as_str(), schema_oid, privileges) +} + +#[function("has_schema_privilege(int4, int4, varchar) -> boolean")] +fn has_schema_privilege_2(user_id: i32, schema_oid: i32, privileges: &str) -> Result { + let user_name = get_user_name_by_id_captured(user_id)?; + has_schema_privilege(user_name.as_str(), schema_oid, privileges) +} + +#[function("has_schema_privilege(varchar, varchar, varchar) -> boolean")] +fn has_schema_privilege_3(user_name: &str, schema_name: &str, privileges: &str) -> Result { + let schema_oid = get_schema_id_by_name_captured(schema_name)?; + has_schema_privilege(user_name, schema_oid, privileges) +} + +#[capture_context(USER_INFO_READER)] +fn has_privilege_impl( + user_info_reader: &UserInfoReader, + user_name: &str, + object: &Object, + actions: &Vec<(Action, bool)>, +) -> Result { + let user_info = &user_info_reader.read_guard(); + let user_catalog = user_info + .get_user_by_name(user_name) + .ok_or(user_not_found_err( + format!("User {} not found", user_name).as_str(), + ))?; + Ok(user_catalog.check_privilege_with_grant_option(object, actions)) +} + +#[capture_context(USER_INFO_READER)] +fn get_user_name_by_id(user_info_reader: &UserInfoReader, user_id: i32) -> Result { + let user_info = &user_info_reader.read_guard(); + user_info + .get_user_name_by_id(user_id as u32) + .ok_or(user_not_found_err( + format!("User {} not found", user_id).as_str(), + )) +} + +#[capture_context(CATALOG_READER, DB_NAME)] +fn get_grant_object_by_oid( + catalog_reader: &CatalogReader, + db_name: &str, + oid: i32, +) -> Result { + catalog_reader + .read_guard() + .get_database_by_name(db_name) + .map_err(|e| ExprError::InvalidParam { + name: "oid", + reason: e.to_report_string().into(), + })? + .get_grant_object_by_oid(oid as u32) + .ok_or(ExprError::InvalidParam { + name: "oid", + reason: format!("Table {} not found", oid).as_str().into(), + }) +} + +#[capture_context(CATALOG_READER, DB_NAME)] +fn get_schema_id_by_name( + catalog_reader: &CatalogReader, + db_name: &str, + schema_name: &str, +) -> Result { + let reader = &catalog_reader.read_guard(); + Ok(reader + .get_schema_by_name(db_name, schema_name) + .map_err(|e| ExprError::InvalidParam { + name: "schema", + reason: e.to_report_string().into(), + })? + .id() as i32) +} + +fn parse_privilege( + privilege_string: &str, + allowed_actions: &HashSet, +) -> Result> { + let mut privileges = Vec::new(); + for part in privilege_string.split(',').map(str::trim) { + let (privilege_type, grant_option) = match part.rsplit_once(" WITH GRANT OPTION") { + Some((p, _)) => (p, true), + None => (part, false), + }; + match Action::from_str_name(privilege_type.to_uppercase().as_str()) { + Some(Action::Unspecified) | None => { + return Err(ExprError::InvalidParam { + name: "privilege", + reason: format!("unrecognized privilege type: \"{}\"", part).into(), + }) + } + Some(action) => { + if allowed_actions.is_empty() || allowed_actions.contains(&action) { + privileges.push((action, grant_option)) + } else { + return Err(ExprError::InvalidParam { + name: "privilege", + reason: format!("unrecognized privilege type: \"{}\"", part).into(), + }); + } + } + } + } + Ok(privileges) +} diff --git a/src/frontend/src/expr/function_impl/mod.rs b/src/frontend/src/expr/function_impl/mod.rs index ad0b3b7a853d8..3a70ebf5db474 100644 --- a/src/frontend/src/expr/function_impl/mod.rs +++ b/src/frontend/src/expr/function_impl/mod.rs @@ -15,6 +15,7 @@ mod cast_regclass; mod col_description; pub mod context; +mod has_privilege; mod pg_get_indexdef; mod pg_get_userbyid; mod pg_get_viewdef; diff --git a/src/frontend/src/expr/pure.rs b/src/frontend/src/expr/pure.rs index bc18959e5be55..d80ef31b04fde 100644 --- a/src/frontend/src/expr/pure.rs +++ b/src/frontend/src/expr/pure.rs @@ -267,6 +267,9 @@ impl ExprVisitor for ImpureAnalyzer { | Type::PgIndexesSize | Type::PgRelationSize | Type::PgGetSerialSequence + | Type::HasTablePrivilege + | Type::HasAnyColumnPrivilege + | Type::HasSchemaPrivilege | Type::MakeTimestamptz => self.impure = true, } } diff --git a/src/frontend/src/handler/create_function.rs b/src/frontend/src/handler/create_function.rs index a23632fbd62c9..c6cafdea37fa7 100644 --- a/src/frontend/src/handler/create_function.rs +++ b/src/frontend/src/handler/create_function.rs @@ -12,19 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use anyhow::{anyhow, Context}; +use anyhow::Context; use arrow_schema::Fields; use bytes::Bytes; -use itertools::Itertools; -use pgwire::pg_response::StatementType; use risingwave_common::array::arrow::{ToArrow, UdfArrowConvert}; use risingwave_common::catalog::FunctionId; use risingwave_common::types::DataType; -use risingwave_expr::expr::get_or_create_wasm_runtime; +use risingwave_expr::expr::{get_or_create_flight_client, get_or_create_wasm_runtime}; use risingwave_pb::catalog::function::{Kind, ScalarFunction, TableFunction}; use risingwave_pb::catalog::Function; -use risingwave_sqlparser::ast::{CreateFunctionBody, ObjectName, OperateFunctionArg}; -use risingwave_udf::ArrowFlightUdfClient; use super::*; use crate::catalog::CatalogError; @@ -167,13 +163,12 @@ pub async fn handle_create_function( // check UDF server { - let client = ArrowFlightUdfClient::connect(&l) - .await - .map_err(|e| anyhow!(e))?; - /// A helper function to create a unnamed field from data type. - fn to_field(data_type: &DataType) -> Result { - Ok(UdfArrowConvert.to_arrow_field("", data_type)?) - } + let client = get_or_create_flight_client(&l)?; + let convert = UdfArrowConvert { + legacy: client.protocol_version() == 1, + }; + // A helper function to create a unnamed field from data type. + let to_field = |data_type| convert.to_arrow_field("", data_type); let args = arrow_schema::Schema::new( arg_types .iter() @@ -183,15 +178,29 @@ pub async fn handle_create_function( let returns = arrow_schema::Schema::new(match kind { Kind::Scalar(_) => vec![to_field(&return_type)?], Kind::Table(_) => vec![ - arrow_schema::Field::new("row_index", arrow_schema::DataType::Int32, true), + arrow_schema::Field::new("row", arrow_schema::DataType::Int32, true), to_field(&return_type)?, ], _ => unreachable!(), }); - client - .check(&identifier, &args, &returns) + let function = client + .get(&identifier) .await .context("failed to check UDF signature")?; + if !data_types_match(&function.args, &args) { + return Err(ErrorCode::InvalidParameterValue(format!( + "argument type mismatch, expect: {:?}, actual: {:?}", + args, function.args, + )) + .into()); + } + if !data_types_match(&function.returns, &returns) { + return Err(ErrorCode::InvalidParameterValue(format!( + "return type mismatch, expect: {:?}, actual: {:?}", + returns, function.returns, + )) + .into()); + } } link = Some(l); } @@ -276,6 +285,7 @@ pub async fn handle_create_function( let wasm_binary = tokio::task::spawn_blocking(move || { let mut opts = arrow_udf_wasm::build::BuildOpts::default(); + opts.arrow_udf_version = Some("0.3".to_string()); opts.script = script; // use a fixed tempdir to reuse the build cache opts.tempdir = Some(std::env::temp_dir().join("risingwave-rust-udf")); @@ -309,6 +319,13 @@ pub async fn handle_create_function( } }; let runtime = get_or_create_wasm_runtime(&wasm_binary)?; + if runtime.abi_version().0 <= 2 { + return Err(ErrorCode::InvalidParameterValue( + "legacy arrow-udf is no longer supported. please update arrow-udf to 0.3+" + .to_string(), + ) + .into()); + } let identifier_v1 = wasm_identifier_v1( &function_name, &arg_types, @@ -457,13 +474,13 @@ fn wasm_identifier_v1( fn datatype_name(ty: &DataType) -> String { match ty { DataType::Boolean => "boolean".to_string(), - DataType::Int16 => "int2".to_string(), - DataType::Int32 => "int4".to_string(), - DataType::Int64 => "int8".to_string(), - DataType::Float32 => "float4".to_string(), - DataType::Float64 => "float8".to_string(), - DataType::Date => "date".to_string(), - DataType::Time => "time".to_string(), + DataType::Int16 => "int16".to_string(), + DataType::Int32 => "int32".to_string(), + DataType::Int64 => "int64".to_string(), + DataType::Float32 => "float32".to_string(), + DataType::Float64 => "float64".to_string(), + DataType::Date => "date32".to_string(), + DataType::Time => "time64".to_string(), DataType::Timestamp => "timestamp".to_string(), DataType::Timestamptz => "timestamptz".to_string(), DataType::Interval => "interval".to_string(), @@ -471,8 +488,8 @@ fn datatype_name(ty: &DataType) -> String { DataType::Jsonb => "json".to_string(), DataType::Serial => "serial".to_string(), DataType::Int256 => "int256".to_string(), - DataType::Bytea => "bytea".to_string(), - DataType::Varchar => "varchar".to_string(), + DataType::Bytea => "binary".to_string(), + DataType::Varchar => "string".to_string(), DataType::List(inner) => format!("{}[]", datatype_name(inner)), DataType::Struct(s) => format!( "struct<{}>", @@ -482,3 +499,15 @@ fn datatype_name(ty: &DataType) -> String { ), } } + +/// Check if two list of data types match, ignoring field names. +fn data_types_match(a: &arrow_schema::Schema, b: &arrow_schema::Schema) -> bool { + if a.fields().len() != b.fields().len() { + return false; + } + #[allow(clippy::disallowed_methods)] + a.fields() + .iter() + .zip(b.fields()) + .all(|(a, b)| a.data_type().equals_datatype(b.data_type())) +} diff --git a/src/frontend/src/handler/create_sink.rs b/src/frontend/src/handler/create_sink.rs index c4c1b2ad0aace..b81a40f6d5759 100644 --- a/src/frontend/src/handler/create_sink.rs +++ b/src/frontend/src/handler/create_sink.rs @@ -556,7 +556,7 @@ fn check_cycle_for_sink( path.push(table.name.clone()); self.visit_table(table.as_ref(), target_table_id, path)?; path.pop(); - } else if self.source_index.get(&table_id.table_id).is_some() { + } else if self.source_index.contains_key(&table_id.table_id) { continue; } else { bail!("streaming job not found: {:?}", table_id); @@ -718,7 +718,7 @@ fn derive_default_column_project_for_sink( // If users specified the columns to be inserted e.g. `CREATE SINK s INTO t(a, b)`, the expressions of `Project` will be generated accordingly. // The missing columns will be filled with default value (`null` if not explicitly defined). - // Otherwhise, e.g. `CREATE SINK s INTO t`, the columns will be matched by their order in `select` query and the target table. + // Otherwise, e.g. `CREATE SINK s INTO t`, the columns will be matched by their order in `select` query and the target table. #[allow(clippy::collapsible_else_if)] if user_specified_columns { if let Some(idx) = sink_visible_col_idxes_by_name.get(table_column.name()) { diff --git a/src/frontend/src/handler/create_source.rs b/src/frontend/src/handler/create_source.rs index 84d22631a454e..49f3126f53b96 100644 --- a/src/frontend/src/handler/create_source.rs +++ b/src/frontend/src/handler/create_source.rs @@ -54,15 +54,13 @@ use risingwave_connector::source::{ POSIX_FS_CONNECTOR, PULSAR_CONNECTOR, S3_CONNECTOR, }; use risingwave_connector::WithPropertiesExt; -use risingwave_pb::catalog::{ - PbSchemaRegistryNameStrategy, PbSource, StreamSourceInfo, WatermarkDesc, -}; +use risingwave_pb::catalog::{PbSchemaRegistryNameStrategy, StreamSourceInfo, WatermarkDesc}; use risingwave_pb::plan_common::additional_column::ColumnType as AdditionalColumnType; use risingwave_pb::plan_common::{EncodeType, FormatType}; use risingwave_pb::stream_plan::stream_fragment_graph::Parallelism; use risingwave_sqlparser::ast::{ get_delimiter, AstString, ColumnDef, ConnectorSchema, CreateSourceStatement, Encode, Format, - ProtobufSchema, SourceWatermark, + ObjectName, ProtobufSchema, SourceWatermark, TableConstraint, }; use risingwave_sqlparser::parser::IncludeOption; use thiserror_ext::AsReport; @@ -70,6 +68,7 @@ use thiserror_ext::AsReport; use super::RwPgResponse; use crate::binder::Binder; use crate::catalog::source_catalog::SourceCatalog; +use crate::catalog::{DatabaseId, SchemaId}; use crate::error::ErrorCode::{self, Deprecated, InvalidInputSyntax, NotSupported, ProtocolError}; use crate::error::{Result, RwError}; use crate::expr::Expr; @@ -504,7 +503,6 @@ pub(crate) async fn bind_columns_from_source( fn bind_columns_from_source_for_cdc( session: &SessionImpl, source_schema: &ConnectorSchema, - _with_properties: &HashMap, ) -> Result<(Option>, StreamSourceInfo)> { let format_encode_options = WithOptions::try_from(source_schema.row_options())?.into_inner(); let mut format_encode_options_to_consume = format_encode_options.clone(); @@ -1320,63 +1318,56 @@ pub fn bind_connector_props( } Ok(with_properties) } - -pub async fn handle_create_source( +#[allow(clippy::too_many_arguments)] +pub async fn bind_create_source( handler_args: HandlerArgs, - stmt: CreateSourceStatement, -) -> Result { - let session = handler_args.session.clone(); - - if let Either::Right(resp) = session.check_relation_name_duplicated( - stmt.source_name.clone(), - StatementType::CREATE_SOURCE, - stmt.if_not_exists, - )? { - return Ok(resp); - } - - let db_name = session.database(); - let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, stmt.source_name)?; + full_name: ObjectName, + source_schema: ConnectorSchema, + with_properties: HashMap, + sql_columns_defs: &[ColumnDef], + constraints: Vec, + wildcard_idx: Option, + source_watermarks: Vec, + columns_from_resolve_source: Option>, + source_info: StreamSourceInfo, + include_column_options: IncludeOption, + col_id_gen: &mut ColumnIdGenerator, + // `true` for "create source", `false` for "create table with connector" + is_create_source: bool, +) -> Result<(SourceCatalog, DatabaseId, SchemaId)> { + let session = &handler_args.session; + let db_name: &str = session.database(); + let (schema_name, source_name) = Binder::resolve_schema_qualified_name(db_name, full_name)?; let (database_id, schema_id) = session.get_database_and_schema_id_for_create(schema_name.clone())?; - if handler_args.with_options.is_empty() { - return Err(RwError::from(InvalidInputSyntax( - "missing WITH clause".to_string(), - ))); + if !is_create_source && with_properties.is_iceberg_connector() { + return Err( + ErrorCode::BindError("can't create table with iceberg connector".to_string()).into(), + ); } - let source_schema = stmt.source_schema.into_v2_with_warning(); - - let with_properties = bind_connector_props(&handler_args, &source_schema, true)?; - ensure_table_constraints_supported(&stmt.constraints)?; - let sql_pk_names = bind_sql_pk_names(&stmt.columns, &stmt.constraints)?; + ensure_table_constraints_supported(&constraints)?; + let sql_pk_names = bind_sql_pk_names(sql_columns_defs, &constraints)?; - let create_cdc_source_job = with_properties.is_shareable_cdc_connector(); - let is_shared = create_cdc_source_job - || (with_properties.is_kafka_connector() && session.config().rw_enable_shared_source()); - - let (columns_from_resolve_source, mut source_info) = if create_cdc_source_job { - bind_columns_from_source_for_cdc(&session, &source_schema, &with_properties)? - } else { - bind_columns_from_source(&session, &source_schema, &with_properties).await? - }; - if is_shared { - // Note: this field should be called is_shared. Check field doc for more details. - source_info.cdc_source_job = true; - source_info.is_distributed = !create_cdc_source_job; - } - let columns_from_sql = bind_sql_columns(&stmt.columns)?; + let columns_from_sql = bind_sql_columns(sql_columns_defs)?; let mut columns = bind_all_columns( &source_schema, columns_from_resolve_source, columns_from_sql, - &stmt.columns, - stmt.wildcard_idx, + sql_columns_defs, + wildcard_idx, )?; + // add additional columns before bind pk, because `format upsert` requires the key column - handle_addition_columns(&with_properties, stmt.include_column_options, &mut columns)?; + handle_addition_columns(&with_properties, include_column_options, &mut columns)?; + // compatible with the behavior that add a hidden column `_rw_kafka_timestamp` to each message from Kafka source + if is_create_source { + // must behind `handle_addition_columns` + check_and_add_timestamp_column(&with_properties, &mut columns); + } + let pk_names = bind_source_pk( &source_schema, &source_info, @@ -1386,70 +1377,139 @@ pub async fn handle_create_source( ) .await?; - // must behind `handle_addition_columns` - check_and_add_timestamp_column(&with_properties, &mut columns); - - let mut col_id_gen = ColumnIdGenerator::new_initial(); - for c in &mut columns { - c.column_desc.column_id = col_id_gen.generate(c.name()) - } - - if !pk_names.is_empty() { + if is_create_source && !pk_names.is_empty() { return Err(ErrorCode::InvalidInputSyntax( "Source does not support PRIMARY KEY constraint, please use \"CREATE TABLE\" instead" .to_owned(), ) .into()); } - let (mut columns, pk_column_ids, row_id_index) = - bind_pk_on_relation(columns, pk_names, with_properties.connector_need_pk())?; + for c in &mut columns { + c.column_desc.column_id = col_id_gen.generate(c.name()) + } debug_assert!(is_column_ids_dedup(&columns)); + let must_need_pk = if is_create_source { + with_properties.connector_need_pk() + } else { + // For those connectors that do not need generate a `row_id`` column in the source schema such as iceberg. + // But in such case, we can not create mv or table on the source because there is not a pk. + assert!(with_properties.connector_need_pk()); + + true + }; + + let (mut columns, pk_col_ids, row_id_index) = + bind_pk_on_relation(columns, pk_names, must_need_pk)?; + let watermark_descs = - bind_source_watermark(&session, name.clone(), stmt.source_watermarks, &columns)?; + bind_source_watermark(session, source_name.clone(), source_watermarks, &columns)?; // TODO(yuhao): allow multiple watermark on source. assert!(watermark_descs.len() <= 1); bind_sql_column_constraints( - &session, - name.clone(), + session, + source_name.clone(), &mut columns, - stmt.columns, - &pk_column_ids, + // TODO(st1page): pass the ref + sql_columns_defs.to_vec(), + &pk_col_ids, )?; - check_source_schema(&with_properties, row_id_index, &columns).await?; - let pk_column_ids = pk_column_ids.into_iter().map(Into::into).collect(); - - let mut with_options = WithOptions::new(with_properties); - // resolve privatelink connection for Kafka source + // resolve privatelink connection for Kafka + let mut with_properties = WithOptions::new(with_properties); let connection_id = - resolve_privatelink_in_with_option(&mut with_options, &schema_name, &session)?; - let definition = handler_args.normalized_sql.clone(); + resolve_privatelink_in_with_option(&mut with_properties, &schema_name, session)?; + + let definition: String = handler_args.normalized_sql.clone(); - let source = PbSource { + let associated_table_id = if is_create_source { + None + } else { + Some(TableId::placeholder()) + }; + let source = SourceCatalog { id: TableId::placeholder().table_id, - schema_id, - database_id, - name, - row_id_index: row_id_index.map(|idx| idx as u32), - columns: columns.iter().map(|c| c.to_protobuf()).collect_vec(), - pk_column_ids, - with_properties: with_options.into_inner().into_iter().collect(), - info: Some(source_info), + name: source_name, + columns, + pk_col_ids, + append_only: row_id_index.is_some(), owner: session.user_id(), + info: source_info, + row_id_index, + with_properties: with_properties.into_inner().into_iter().collect(), watermark_descs, + associated_table_id, definition, connection_id, - initialized_at_epoch: None, created_at_epoch: None, - optional_associated_table_id: None, + initialized_at_epoch: None, version: INITIAL_SOURCE_VERSION_ID, - initialized_at_cluster_version: None, created_at_cluster_version: None, + initialized_at_cluster_version: None, }; + Ok((source, database_id, schema_id)) +} + +pub async fn handle_create_source( + handler_args: HandlerArgs, + stmt: CreateSourceStatement, +) -> Result { + let session = handler_args.session.clone(); + + if let Either::Right(resp) = session.check_relation_name_duplicated( + stmt.source_name.clone(), + StatementType::CREATE_SOURCE, + stmt.if_not_exists, + )? { + return Ok(resp); + } + + if handler_args.with_options.is_empty() { + return Err(RwError::from(InvalidInputSyntax( + "missing WITH clause".to_string(), + ))); + } + + let source_schema = stmt.source_schema.into_v2_with_warning(); + let with_properties = bind_connector_props(&handler_args, &source_schema, true)?; + + let create_cdc_source_job = with_properties.is_shareable_cdc_connector(); + let is_shared = create_cdc_source_job + || (with_properties.is_kafka_connector() && session.config().rw_enable_shared_source()); + + let (columns_from_resolve_source, mut source_info) = if create_cdc_source_job { + bind_columns_from_source_for_cdc(&session, &source_schema)? + } else { + bind_columns_from_source(&session, &source_schema, &with_properties).await? + }; + if is_shared { + // Note: this field should be called is_shared. Check field doc for more details. + source_info.cdc_source_job = true; + source_info.is_distributed = !create_cdc_source_job; + } + let mut col_id_gen = ColumnIdGenerator::new_initial(); + + let (source_catalog, database_id, schema_id) = bind_create_source( + handler_args.clone(), + stmt.source_name, + source_schema, + with_properties, + &stmt.columns, + stmt.constraints, + stmt.wildcard_idx, + stmt.source_watermarks, + columns_from_resolve_source, + source_info, + stmt.include_column_options, + &mut col_id_gen, + true, + ) + .await?; + + let source = source_catalog.to_prost(schema_id, database_id); let catalog_writer = session.catalog_writer()?; @@ -1457,7 +1517,7 @@ pub async fn handle_create_source( let graph = { let context = OptimizerContext::from_handler_args(handler_args); let source_node = LogicalSource::with_catalog( - Rc::new(SourceCatalog::from(&source)), + Rc::new(source_catalog), SourceNodeKind::CreateSharedSource, context.into(), None, @@ -1645,7 +1705,7 @@ pub mod tests { ); // Options are not merged into props. - assert!(source.with_properties.get("schema.location").is_none()); + assert!(!source.with_properties.contains_key("schema.location")); } #[tokio::test] diff --git a/src/frontend/src/handler/create_sql_function.rs b/src/frontend/src/handler/create_sql_function.rs index ae7b2730e25d5..a9ac2f394c68f 100644 --- a/src/frontend/src/handler/create_sql_function.rs +++ b/src/frontend/src/handler/create_sql_function.rs @@ -15,15 +15,10 @@ use std::collections::HashMap; use fancy_regex::Regex; -use itertools::Itertools; -use pgwire::pg_response::StatementType; use risingwave_common::catalog::FunctionId; use risingwave_common::types::DataType; use risingwave_pb::catalog::function::{Kind, ScalarFunction, TableFunction}; use risingwave_pb::catalog::Function; -use risingwave_sqlparser::ast::{ - CreateFunctionBody, FunctionDefinition, ObjectName, OperateFunctionArg, -}; use risingwave_sqlparser::parser::{Parser, ParserError}; use super::*; diff --git a/src/frontend/src/handler/create_table.rs b/src/frontend/src/handler/create_table.rs index 76608c40d068a..41415d5987676 100644 --- a/src/frontend/src/handler/create_table.rs +++ b/src/frontend/src/handler/create_table.rs @@ -24,16 +24,15 @@ use pgwire::pg_response::{PgResponse, StatementType}; use risingwave_common::bail_not_implemented; use risingwave_common::catalog::{ CdcTableDesc, ColumnCatalog, ColumnDesc, TableId, TableVersionId, DEFAULT_SCHEMA_NAME, - INITIAL_SOURCE_VERSION_ID, INITIAL_TABLE_VERSION_ID, USER_COLUMN_ID_OFFSET, + INITIAL_TABLE_VERSION_ID, USER_COLUMN_ID_OFFSET, }; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_common::util::value_encoding::DatumToProtoExt; +use risingwave_connector::source; use risingwave_connector::source::cdc::external::{ DATABASE_NAME_KEY, SCHEMA_NAME_KEY, TABLE_NAME_KEY, }; -use risingwave_connector::{source, WithPropertiesExt}; -use risingwave_pb::catalog::source::OptionalAssociatedTableId; -use risingwave_pb::catalog::{PbSource, PbTable, StreamSourceInfo, Table, WatermarkDesc}; +use risingwave_pb::catalog::{PbSource, PbTable, Table, WatermarkDesc}; use risingwave_pb::ddl_service::TableJobType; use risingwave_pb::plan_common::column_desc::GeneratedOrDefaultColumn; use risingwave_pb::plan_common::{ @@ -52,12 +51,12 @@ use crate::binder::{bind_data_type, bind_struct_field, Clause}; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::source_catalog::SourceCatalog; use crate::catalog::table_catalog::TableVersion; -use crate::catalog::{check_valid_column_name, ColumnId}; +use crate::catalog::{check_valid_column_name, ColumnId, DatabaseId, SchemaId}; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{Expr, ExprImpl, ExprRewriter, InlineNowProcTime}; use crate::handler::create_source::{ - bind_all_columns, bind_columns_from_source, bind_connector_props, bind_source_pk, - bind_source_watermark, check_source_schema, handle_addition_columns, UPSTREAM_SOURCE_KEY, + bind_columns_from_source, bind_connector_props, bind_create_source, bind_source_watermark, + UPSTREAM_SOURCE_KEY, }; use crate::handler::HandlerArgs; use crate::optimizer::plan_node::generic::{CdcScanOptions, SourceNodeKind}; @@ -66,7 +65,6 @@ use crate::optimizer::property::{Order, RequiredDist}; use crate::optimizer::{OptimizerContext, OptimizerContextRef, PlanRef, PlanRoot}; use crate::session::SessionImpl; use crate::stream_fragmenter::build_graph; -use crate::utils::resolve_privatelink_in_with_option; use crate::{Binder, TableCatalog, WithOptions}; /// Column ID generator for a new table or a new version of an existing table to alter. @@ -482,82 +480,42 @@ pub(crate) async fn gen_create_table_plan_with_source( let session = &handler_args.session; let with_properties = bind_connector_props(&handler_args, &source_schema, false)?; - ensure_table_constraints_supported(&constraints)?; - - let sql_pk_names = bind_sql_pk_names(&column_defs, &constraints)?; - let (columns_from_resolve_source, source_info) = bind_columns_from_source(session, &source_schema, &with_properties).await?; - let columns_from_sql = bind_sql_columns(&column_defs)?; - let mut columns = bind_all_columns( - &source_schema, - columns_from_resolve_source, - columns_from_sql, + let (source_catalog, database_id, schema_id) = bind_create_source( + handler_args.clone(), + table_name, + source_schema, + with_properties, &column_defs, + constraints, wildcard_idx, - )?; - - // add additional columns before bind pk, because `format upsert` requires the key column - handle_addition_columns(&with_properties, include_column_options, &mut columns)?; - let pk_names = bind_source_pk( - &source_schema, - &source_info, - &mut columns, - sql_pk_names, - &with_properties, + source_watermarks, + columns_from_resolve_source, + source_info, + include_column_options, + &mut col_id_gen, + false, ) .await?; - for c in &mut columns { - c.column_desc.column_id = col_id_gen.generate(c.name()) - } - - if with_properties.is_iceberg_connector() { - return Err( - ErrorCode::BindError("can't create table with iceberg connector".to_string()).into(), - ); - } - let (mut columns, pk_column_ids, row_id_index) = bind_pk_on_relation(columns, pk_names, true)?; - - let watermark_descs = bind_source_watermark( - session, - table_name.real_value(), - source_watermarks, - &columns, - )?; - // TODO(yuhao): allow multiple watermark on source. - assert!(watermark_descs.len() <= 1); - - let definition = handler_args.normalized_sql.clone(); - - bind_sql_column_constraints( - session, - table_name.real_value(), - &mut columns, - column_defs, - &pk_column_ids, - )?; - - check_source_schema(&with_properties, row_id_index, &columns).await?; + let pb_source = source_catalog.to_prost(schema_id, database_id); let context = OptimizerContext::new(handler_args, explain_options); - gen_table_plan_inner( + let (plan, table) = gen_table_plan_with_source( context.into(), - table_name, - columns, - with_properties, - pk_column_ids, - row_id_index, - Some(source_info), - definition, - watermark_descs, + source_catalog, append_only, on_conflict, with_version_column, Some(col_id_gen.into_version()), - ) + database_id, + schema_id, + )?; + + Ok((plan, Some(pb_source), table)) } /// `gen_create_table_plan` generates the plan for creating a table without an external stream @@ -572,14 +530,14 @@ pub(crate) fn gen_create_table_plan( append_only: bool, on_conflict: Option, with_version_column: Option, -) -> Result<(PlanRef, Option, PbTable)> { +) -> Result<(PlanRef, PbTable)> { let definition = context.normalized_sql().to_owned(); let mut columns = bind_sql_columns(&column_defs)?; for c in &mut columns { c.column_desc.column_id = col_id_gen.generate(c.name()) } let with_properties = context.with_options().inner().clone().into_iter().collect(); - gen_create_table_plan_without_bind( + gen_create_table_plan_without_source( context, table_name, columns, @@ -596,7 +554,7 @@ pub(crate) fn gen_create_table_plan( } #[allow(clippy::too_many_arguments)] -pub(crate) fn gen_create_table_plan_without_bind( +pub(crate) fn gen_create_table_plan_without_source( context: OptimizerContext, table_name: ObjectName, columns: Vec, @@ -609,12 +567,12 @@ pub(crate) fn gen_create_table_plan_without_bind( on_conflict: Option, with_version_column: Option, version: Option, -) -> Result<(PlanRef, Option, PbTable)> { +) -> Result<(PlanRef, PbTable)> { ensure_table_constraints_supported(&constraints)?; let pk_names = bind_sql_pk_names(&column_defs, &constraints)?; let (mut columns, pk_column_ids, row_id_index) = bind_pk_on_relation(columns, pk_names, true)?; - let watermark_descs = bind_source_watermark( + let watermark_descs: Vec = bind_source_watermark( context.session_ctx(), table_name.real_value(), source_watermarks, @@ -628,33 +586,71 @@ pub(crate) fn gen_create_table_plan_without_bind( column_defs, &pk_column_ids, )?; + let session = context.session_ctx().clone(); + + let db_name = session.database(); + let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, table_name)?; + let (database_id, schema_id) = + session.get_database_and_schema_id_for_create(schema_name.clone())?; gen_table_plan_inner( context.into(), - table_name, + name, columns, with_properties, pk_column_ids, row_id_index, - None, definition, watermark_descs, append_only, on_conflict, with_version_column, version, + None, + database_id, + schema_id, + ) +} + +fn gen_table_plan_with_source( + context: OptimizerContextRef, + source_catalog: SourceCatalog, + append_only: bool, + on_conflict: Option, + with_version_column: Option, + version: Option, /* TODO: this should always be `Some` if we support `ALTER + * TABLE` for `CREATE TABLE AS`. */ + database_id: DatabaseId, + schema_id: SchemaId, +) -> Result<(PlanRef, PbTable)> { + let cloned_source_catalog = source_catalog.clone(); + gen_table_plan_inner( + context, + source_catalog.name, + source_catalog.columns, + source_catalog.with_properties.clone().into_iter().collect(), + source_catalog.pk_col_ids, + source_catalog.row_id_index, + source_catalog.definition, + source_catalog.watermark_descs, + append_only, + on_conflict, + with_version_column, + version, + Some(cloned_source_catalog), + database_id, + schema_id, ) } #[allow(clippy::too_many_arguments)] fn gen_table_plan_inner( context: OptimizerContextRef, - table_name: ObjectName, + table_name: String, columns: Vec, with_properties: HashMap, pk_column_ids: Vec, row_id_index: Option, - source_info: Option, definition: String, watermark_descs: Vec, append_only: bool, @@ -662,51 +658,16 @@ fn gen_table_plan_inner( with_version_column: Option, version: Option, /* TODO: this should always be `Some` if we support `ALTER * TABLE` for `CREATE TABLE AS`. */ -) -> Result<(PlanRef, Option, PbTable)> { + source_catalog: Option, + database_id: DatabaseId, + schema_id: SchemaId, +) -> Result<(PlanRef, PbTable)> { let session = context.session_ctx().clone(); - let db_name = session.database(); - let (schema_name, name) = Binder::resolve_schema_qualified_name(db_name, table_name)?; - let (database_id, schema_id) = - session.get_database_and_schema_id_for_create(schema_name.clone())?; - - // resolve privatelink connection for Table backed by Kafka source - let mut with_properties = WithOptions::new(with_properties); - let connection_id = - resolve_privatelink_in_with_option(&mut with_properties, &schema_name, &session)?; + let with_properties = WithOptions::new(with_properties); let retention_seconds = with_properties.retention_seconds(); - - let is_external_source = source_info.is_some(); - - let source = source_info.map(|source_info| PbSource { - id: TableId::placeholder().table_id, - schema_id, - database_id, - name: name.clone(), - row_id_index: row_id_index.map(|i| i as _), - columns: columns - .iter() - .map(|column| column.to_protobuf()) - .collect_vec(), - pk_column_ids: pk_column_ids.iter().map(Into::into).collect_vec(), - with_properties: with_properties.into_inner().into_iter().collect(), - info: Some(source_info), - owner: session.user_id(), - watermark_descs: watermark_descs.clone(), - definition: "".to_string(), - connection_id, - initialized_at_epoch: None, - created_at_epoch: None, - optional_associated_table_id: Some(OptionalAssociatedTableId::AssociatedTableId( - TableId::placeholder().table_id, - )), - version: INITIAL_SOURCE_VERSION_ID, - initialized_at_cluster_version: None, - created_at_cluster_version: None, - }); - - let source_catalog = source.as_ref().map(|source| Rc::new((source).into())); + let is_external_source = source_catalog.is_some(); let source_node: PlanRef = LogicalSource::new( - source_catalog.clone(), + source_catalog.map(|source| Rc::new(source.clone())), columns.clone(), row_id_index, SourceNodeKind::CreateTable, @@ -749,7 +710,7 @@ fn gen_table_plan_inner( let materialize = plan_root.gen_table_plan( context, - name, + table_name, columns, definition, pk_column_ids, @@ -766,7 +727,7 @@ fn gen_table_plan_inner( let mut table = materialize.table().to_prost(schema_id, database_id); table.owner = session.user_id(); - Ok((materialize.into(), source, table)) + Ok((materialize.into(), table)) } #[allow(clippy::too_many_arguments)] @@ -976,20 +937,19 @@ pub(super) async fn handle_create_table_plan( ), (None, None) => { let context = OptimizerContext::new(handler_args, explain_options); - ( - gen_create_table_plan( - context, - table_name.clone(), - column_defs, - constraints, - col_id_gen, - source_watermarks, - append_only, - on_conflict, - with_version_column, - )?, - TableJobType::General, - ) + let (plan, table) = gen_create_table_plan( + context, + table_name.clone(), + column_defs, + constraints, + col_id_gen, + source_watermarks, + append_only, + on_conflict, + with_version_column, + )?; + + ((plan, None, table), TableJobType::General) } (None, Some(cdc_table)) => { @@ -1186,7 +1146,7 @@ pub async fn generate_stream_graph_for_table( } None => { let context = OptimizerContext::from_handler_args(handler_args); - gen_create_table_plan( + let (plan, table) = gen_create_table_plan( context, table_name, columns, @@ -1196,7 +1156,8 @@ pub async fn generate_stream_graph_for_table( append_only, on_conflict, with_version_column, - )? + )?; + (plan, None, table) } }; @@ -1231,15 +1192,10 @@ pub async fn generate_stream_graph_for_table( #[cfg(test)] mod tests { - use std::collections::HashMap; - - use risingwave_common::catalog::{ - Field, DEFAULT_DATABASE_NAME, DEFAULT_SCHEMA_NAME, ROWID_PREFIX, - }; + use risingwave_common::catalog::{Field, DEFAULT_DATABASE_NAME, ROWID_PREFIX}; use risingwave_common::types::DataType; use super::*; - use crate::catalog::root_catalog::SchemaPath; use crate::test_utils::{create_proto_file, LocalFrontend, PROTO_FILE_DATA}; #[test] @@ -1456,6 +1412,6 @@ mod tests { ); // Options are not merged into props. - assert!(source.with_properties.get("schema.location").is_none()); + assert!(!source.with_properties.contains_key("schema.location")); } } diff --git a/src/frontend/src/handler/create_table_as.rs b/src/frontend/src/handler/create_table_as.rs index a828cb9708b48..bdd6e9d078e90 100644 --- a/src/frontend/src/handler/create_table_as.rs +++ b/src/frontend/src/handler/create_table_as.rs @@ -22,7 +22,7 @@ use risingwave_sqlparser::ast::{ColumnDef, ObjectName, OnConflict, Query, Statem use super::{HandlerArgs, RwPgResponse}; use crate::binder::BoundStatement; use crate::error::{ErrorCode, Result}; -use crate::handler::create_table::{gen_create_table_plan_without_bind, ColumnIdGenerator}; +use crate::handler::create_table::{gen_create_table_plan_without_source, ColumnIdGenerator}; use crate::handler::query::handle_query; use crate::{build_graph, Binder, OptimizerContext}; pub async fn handle_create_as( @@ -96,7 +96,7 @@ pub async fn handle_create_as( .clone() .into_iter() .collect(); - let (plan, source, table) = gen_create_table_plan_without_bind( + let (plan, table) = gen_create_table_plan_without_source( context, table_name.clone(), columns, @@ -118,7 +118,7 @@ pub async fn handle_create_as( .map(|parallelism| Parallelism { parallelism: parallelism.get(), }); - (graph, source, table) + (graph, None, table) }; tracing::trace!( diff --git a/src/frontend/src/handler/drop_function.rs b/src/frontend/src/handler/drop_function.rs index 61eaa8d0c0ad0..bd6ad11b7d5c5 100644 --- a/src/frontend/src/handler/drop_function.rs +++ b/src/frontend/src/handler/drop_function.rs @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use pgwire::pg_response::StatementType; -use risingwave_sqlparser::ast::{FunctionDesc, ReferentialAction}; - use super::*; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::CatalogError; diff --git a/src/frontend/src/handler/handle_privilege.rs b/src/frontend/src/handler/handle_privilege.rs index 44e5b8fdac33c..e8a37b0ab407b 100644 --- a/src/frontend/src/handler/handle_privilege.rs +++ b/src/frontend/src/handler/handle_privilege.rs @@ -21,6 +21,7 @@ use super::RwPgResponse; use crate::binder::Binder; use crate::catalog::root_catalog::SchemaPath; use crate::catalog::table_catalog::TableType; +use crate::catalog::CatalogError; use crate::error::{ErrorCode, Result}; use crate::handler::HandlerArgs; use crate::session::SessionImpl; @@ -93,17 +94,31 @@ fn make_prost_privilege( Binder::resolve_schema_qualified_name(db_name, name)?; let schema_path = SchemaPath::new(schema_name.as_deref(), &search_path, user_name); - let (table, _) = reader.get_table_by_name(db_name, schema_path, &table_name)?; - match table.table_type() { - TableType::Table => {} - _ => { - return Err(ErrorCode::InvalidInputSyntax(format!( - "{table_name} is not a table", - )) - .into()); + match reader.get_table_by_name(db_name, schema_path, &table_name) { + Ok((table, _)) => { + match table.table_type() { + TableType::Table => { + grant_objs.push(PbObject::TableId(table.id().table_id)); + continue; + } + _ => { + return Err(ErrorCode::InvalidInputSyntax(format!( + "{table_name} is not a table", + )) + .into()); + } + }; + } + Err(CatalogError::NotFound("table", _)) => { + let (view, _) = reader + .get_view_by_name(db_name, schema_path, &table_name) + .map_err(|_| CatalogError::NotFound("table", table_name))?; + grant_objs.push(PbObject::ViewId(view.id)); + } + Err(e) => { + return Err(e.into()); } } - grant_objs.push(PbObject::TableId(table.id().table_id)); } } GrantObjects::Sources(sources) => { @@ -138,7 +153,7 @@ fn make_prost_privilege( for schema in schemas { let schema_name = Binder::resolve_schema_name(schema)?; let schema = reader.get_schema_by_name(session.database(), &schema_name)?; - grant_objs.push(PbObject::AllDmlTablesSchemaId(schema.id())); + grant_objs.push(PbObject::AllDmlRelationsSchemaId(schema.id())); } } o => { diff --git a/src/frontend/src/handler/show.rs b/src/frontend/src/handler/show.rs index 9f7b357201d6f..6a1261b871f77 100644 --- a/src/frontend/src/handler/show.rs +++ b/src/frontend/src/handler/show.rs @@ -29,7 +29,6 @@ use risingwave_pb::catalog::connection; use risingwave_sqlparser::ast::{ display_comma_separated, Ident, ObjectName, ShowCreateType, ShowObject, ShowStatementFilter, }; -use serde_json; use super::{fields_to_descriptors, RwPgResponse, RwPgResponseBuilderExt}; use crate::binder::{Binder, Relation}; diff --git a/src/frontend/src/handler/util.rs b/src/frontend/src/handler/util.rs index 33392049fce3a..7fd4f0b92822b 100644 --- a/src/frontend/src/handler/util.rs +++ b/src/frontend/src/handler/util.rs @@ -231,10 +231,8 @@ pub fn convert_logstore_u64_to_unix_millis(logstore_u64: u64) -> u64 { #[cfg(test)] mod tests { - use bytes::BytesMut; use postgres_types::{ToSql, Type}; use risingwave_common::array::*; - use risingwave_common::types::Timestamptz; use super::*; diff --git a/src/frontend/src/lib.rs b/src/frontend/src/lib.rs index 9435b2952f2d2..2c0eac6d0ad88 100644 --- a/src/frontend/src/lib.rs +++ b/src/frontend/src/lib.rs @@ -32,7 +32,6 @@ #![feature(impl_trait_in_assoc_type)] #![feature(result_flattening)] #![feature(error_generic_member_access)] -#![feature(round_ties_even)] #![feature(iterator_try_collect)] #![feature(used_with_arg)] #![feature(entry_insert)] diff --git a/src/frontend/src/optimizer/logical_optimization.rs b/src/frontend/src/optimizer/logical_optimization.rs index 14fa66be68d5f..1c73245a0afcf 100644 --- a/src/frontend/src/optimizer/logical_optimization.rs +++ b/src/frontend/src/optimizer/logical_optimization.rs @@ -732,8 +732,8 @@ impl LogicalOptimizer { // Do a final column pruning and predicate pushing down to clean up the plan. plan = Self::column_pruning(plan, explain_trace, &ctx); if last_total_rule_applied_before_predicate_pushdown != ctx.total_rule_applied() { - #[allow(unused_assignments)] - last_total_rule_applied_before_predicate_pushdown = ctx.total_rule_applied(); + (#[allow(unused_assignments)] + last_total_rule_applied_before_predicate_pushdown) = ctx.total_rule_applied(); plan = Self::predicate_pushdown(plan, explain_trace, &ctx); } diff --git a/src/frontend/src/optimizer/mod.rs b/src/frontend/src/optimizer/mod.rs index 67b265e02d3a2..44ec95054d9a1 100644 --- a/src/frontend/src/optimizer/mod.rs +++ b/src/frontend/src/optimizer/mod.rs @@ -1099,11 +1099,7 @@ fn require_additional_exchange_on_root_in_local_mode(plan: PlanRef) -> bool { #[cfg(test)] mod tests { - use risingwave_common::catalog::Field; - use risingwave_common::types::DataType; - use super::*; - use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; #[tokio::test] diff --git a/src/frontend/src/optimizer/optimizer_context.rs b/src/frontend/src/optimizer/optimizer_context.rs index eb6e410378749..35c5a3ee9129b 100644 --- a/src/frontend/src/optimizer/optimizer_context.rs +++ b/src/frontend/src/optimizer/optimizer_context.rs @@ -12,20 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::convert::Into; use core::fmt::Formatter; use std::cell::{RefCell, RefMut}; +use std::collections::HashMap; use std::marker::PhantomData; use std::rc::Rc; use std::sync::Arc; use risingwave_sqlparser::ast::{ExplainOptions, ExplainType}; +use crate::binder::ShareId; use crate::expr::{CorrelatedId, SessionTimezone}; use crate::handler::HandlerArgs; use crate::optimizer::plan_node::PlanNodeId; use crate::session::SessionImpl; use crate::utils::{OverwriteOptions, WithOptions}; +use crate::PlanRef; const RESERVED_ID_NUM: u16 = 10000; @@ -58,6 +60,9 @@ pub struct OptimizerContext { /// Store the configs can be overwritten in with clause /// if not specified, use the value from session variable. overwrite_options: OverwriteOptions, + /// Store the mapping between `share_id` and the corresponding + /// `PlanRef`, used by rcte's planning. (e.g., in `LogicalCteRef`) + rcte_cache: RefCell>, _phantom: PhantomUnsend, } @@ -91,6 +96,7 @@ impl OptimizerContext { next_expr_display_id: RefCell::new(RESERVED_ID_NUM.into()), total_rule_applied: RefCell::new(0), overwrite_options, + rcte_cache: RefCell::new(HashMap::new()), _phantom: Default::default(), } } @@ -113,6 +119,7 @@ impl OptimizerContext { next_expr_display_id: RefCell::new(0), total_rule_applied: RefCell::new(0), overwrite_options: OverwriteOptions::default(), + rcte_cache: RefCell::new(HashMap::new()), _phantom: Default::default(), } .into() @@ -230,6 +237,14 @@ impl OptimizerContext { pub fn get_session_timezone(&self) -> String { self.session_timezone.borrow().timezone() } + + pub fn get_rcte_cache_plan(&self, id: &ShareId) -> Option { + self.rcte_cache.borrow().get(id).cloned() + } + + pub fn insert_rcte_cache_plan(&self, id: ShareId, plan: PlanRef) { + self.rcte_cache.borrow_mut().insert(id, plan); + } } impl std::fmt::Debug for OptimizerContext { diff --git a/src/frontend/src/optimizer/plan_expr_visitor/strong.rs b/src/frontend/src/optimizer/plan_expr_visitor/strong.rs index e30cdb0b6e314..7045bb226efd7 100644 --- a/src/frontend/src/optimizer/plan_expr_visitor/strong.rs +++ b/src/frontend/src/optimizer/plan_expr_visitor/strong.rs @@ -301,6 +301,9 @@ impl Strong { | ExprType::PgRelationSize | ExprType::PgGetSerialSequence | ExprType::IcebergTransform + | ExprType::HasTablePrivilege + | ExprType::HasAnyColumnPrivilege + | ExprType::HasSchemaPrivilege | ExprType::InetAton | ExprType::InetNtoa => false, ExprType::Unspecified => unreachable!(), diff --git a/src/frontend/src/optimizer/plan_node/batch_delete.rs b/src/frontend/src/optimizer/plan_node/batch_delete.rs index 62499a495d1de..8980f58a9e2c4 100644 --- a/src/frontend/src/optimizer/plan_node/batch_delete.rs +++ b/src/frontend/src/optimizer/plan_node/batch_delete.rs @@ -22,7 +22,6 @@ use super::{ }; use crate::error::Result; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::PhysicalPlanRef; use crate::optimizer::plan_node::{utils, ToLocalBatch}; use crate::optimizer::plan_visitor::DistributedDmlVisitor; use crate::optimizer::property::{Distribution, Order, RequiredDist}; diff --git a/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs b/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs index af5f4c4accbb6..341d1281009eb 100644 --- a/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs +++ b/src/frontend/src/optimizer/plan_node/batch_hash_agg.rs @@ -17,7 +17,7 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::HashAggNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::utils::impl_distill_by_unit; use super::{ ExprRewritable, PlanBase, PlanNodeType, PlanRef, PlanTreeNodeUnary, ToBatchPb, diff --git a/src/frontend/src/optimizer/plan_node/batch_hash_join.rs b/src/frontend/src/optimizer/plan_node/batch_hash_join.rs index 8b92f722490d5..399817336e4ca 100644 --- a/src/frontend/src/optimizer/plan_node/batch_hash_join.rs +++ b/src/frontend/src/optimizer/plan_node/batch_hash_join.rs @@ -18,10 +18,9 @@ use risingwave_pb::batch_plan::HashJoinNode; use risingwave_pb::plan_common::JoinType; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef}; use super::utils::{childless_record, Distill}; use super::{ - EqJoinPredicate, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, + generic, EqJoinPredicate, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, ToDistributedBatch, }; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_insert.rs b/src/frontend/src/optimizer/plan_node/batch_insert.rs index 1dbe387bb708f..8a3e511efc83c 100644 --- a/src/frontend/src/optimizer/plan_node/batch_insert.rs +++ b/src/frontend/src/optimizer/plan_node/batch_insert.rs @@ -18,7 +18,6 @@ use risingwave_pb::batch_plan::InsertNode; use risingwave_pb::plan_common::{DefaultColumns, IndexAndExpr}; use super::batch::prelude::*; -use super::generic::GenericPlanRef; use super::utils::{childless_record, Distill}; use super::{generic, ExprRewritable, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_limit.rs b/src/frontend/src/optimizer/plan_node/batch_limit.rs index 1dfe9215be1ea..ef717d179130e 100644 --- a/src/frontend/src/optimizer/plan_node/batch_limit.rs +++ b/src/frontend/src/optimizer/plan_node/batch_limit.rs @@ -16,7 +16,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::LimitNode; use super::batch::prelude::*; -use super::generic::PhysicalPlanRef; use super::utils::impl_distill_by_unit; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, diff --git a/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs b/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs index a20b4ad316cd7..a181c590e0e65 100644 --- a/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs +++ b/src/frontend/src/optimizer/plan_node/batch_lookup_join.rs @@ -18,9 +18,8 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::{DistributedLookupJoinNode, LocalLookupJoinNode}; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef}; use super::utils::{childless_record, Distill}; -use super::ExprRewritable; +use super::{generic, ExprRewritable}; use crate::error::Result; use crate::expr::{Expr, ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs b/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs index 94b8b3e6e0483..e35d6976672c4 100644 --- a/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs +++ b/src/frontend/src/optimizer/plan_node/batch_max_one_row.rs @@ -17,7 +17,7 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::MaxOneRowNode; use super::batch::prelude::*; -use super::generic::{DistillUnit, PhysicalPlanRef}; +use super::generic::DistillUnit; use super::utils::Distill; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, diff --git a/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs b/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs index 401166b1a298a..5d07aad86017b 100644 --- a/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs +++ b/src/frontend/src/optimizer/plan_node/batch_nested_loop_join.rs @@ -17,9 +17,10 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::NestedLoopJoinNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef}; use super::utils::{childless_record, Distill}; -use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, ToDistributedBatch}; +use super::{ + generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, ToBatchPb, ToDistributedBatch, +}; use crate::error::Result; use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/batch_over_window.rs b/src/frontend/src/optimizer/plan_node/batch_over_window.rs index 1712e87bbabbf..b9355fb117098 100644 --- a/src/frontend/src/optimizer/plan_node/batch_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/batch_over_window.rs @@ -17,7 +17,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::SortOverWindowNode; use super::batch::prelude::*; -use super::batch::BatchPlanRef; use super::generic::PlanWindowFunction; use super::utils::impl_distill_by_unit; use super::{ diff --git a/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs b/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs index a112e36c3a98f..ff20df7a4d177 100644 --- a/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs +++ b/src/frontend/src/optimizer/plan_node/batch_simple_agg.rs @@ -16,7 +16,7 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::SortAggNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::utils::impl_distill_by_unit; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_sort.rs b/src/frontend/src/optimizer/plan_node/batch_sort.rs index 413103ee98c89..9136e3f7c685a 100644 --- a/src/frontend/src/optimizer/plan_node/batch_sort.rs +++ b/src/frontend/src/optimizer/plan_node/batch_sort.rs @@ -17,7 +17,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::SortNode; use super::batch::prelude::*; -use super::batch::BatchPlanRef; use super::utils::{childless_record, Distill}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs b/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs index 7393c10edc8f0..f8ea17dcd837e 100644 --- a/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs +++ b/src/frontend/src/optimizer/plan_node/batch_sort_agg.rs @@ -17,7 +17,7 @@ use risingwave_pb::batch_plan::SortAggNode; use risingwave_pb::expr::ExprNode; use super::batch::prelude::*; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::utils::impl_distill_by_unit; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch}; use crate::error::Result; diff --git a/src/frontend/src/optimizer/plan_node/batch_topn.rs b/src/frontend/src/optimizer/plan_node/batch_topn.rs index d8f96668f88a7..643ef0fcc01fc 100644 --- a/src/frontend/src/optimizer/plan_node/batch_topn.rs +++ b/src/frontend/src/optimizer/plan_node/batch_topn.rs @@ -22,7 +22,6 @@ use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, }; use crate::error::Result; -use crate::optimizer::plan_node::batch::BatchPlanRef; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; use crate::optimizer::plan_node::{BatchLimit, ToLocalBatch}; use crate::optimizer::property::{Order, RequiredDist}; diff --git a/src/frontend/src/optimizer/plan_node/batch_update.rs b/src/frontend/src/optimizer/plan_node/batch_update.rs index 34d1eb3db3d74..d0351e6fdec2e 100644 --- a/src/frontend/src/optimizer/plan_node/batch_update.rs +++ b/src/frontend/src/optimizer/plan_node/batch_update.rs @@ -18,7 +18,6 @@ use risingwave_pb::batch_plan::plan_node::NodeBody; use risingwave_pb::batch_plan::UpdateNode; use super::batch::prelude::*; -use super::generic::GenericPlanRef; use super::utils::impl_distill_by_unit; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, ToBatchPb, ToDistributedBatch, diff --git a/src/frontend/src/optimizer/plan_node/convert.rs b/src/frontend/src/optimizer/plan_node/convert.rs index a39ed7d3b048d..db961b3b1e20a 100644 --- a/src/frontend/src/optimizer/plan_node/convert.rs +++ b/src/frontend/src/optimizer/plan_node/convert.rs @@ -19,8 +19,7 @@ use risingwave_common::catalog::FieldDisplay; use risingwave_pb::stream_plan::StreamScanType; use super::*; -use crate::optimizer::property::{Order, RequiredDist}; -use crate::utils::ColIndexMapping; +use crate::optimizer::property::RequiredDist; use crate::{for_batch_plan_nodes, for_logical_plan_nodes, for_stream_plan_nodes}; /// `ToStream` converts a logical plan node to streaming physical node diff --git a/src/frontend/src/optimizer/plan_node/expr_rewritable.rs b/src/frontend/src/optimizer/plan_node/expr_rewritable.rs index 33deebde1cdf9..4a30a599742ed 100644 --- a/src/frontend/src/optimizer/plan_node/expr_rewritable.rs +++ b/src/frontend/src/optimizer/plan_node/expr_rewritable.rs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::ops::Deref; - use super::*; -use crate::expr::ExprRewriter; /// Rewrites expressions in a `PlanRef`. Due to `Share` operator, /// the `ExprRewriter` needs to be idempotent i.e., applying it more than once diff --git a/src/frontend/src/optimizer/plan_node/expr_visitable.rs b/src/frontend/src/optimizer/plan_node/expr_visitable.rs index e4454fa92a7e5..e5527dfa17785 100644 --- a/src/frontend/src/optimizer/plan_node/expr_visitable.rs +++ b/src/frontend/src/optimizer/plan_node/expr_visitable.rs @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::ops::Deref; - use super::*; -use crate::expr::ExprVisitor; /// Vistis expressions in a `PlanRef`. /// To visit recursively, call `visit_exprs_recursive` on [`VisitExprsRecursive`]. diff --git a/src/frontend/src/optimizer/plan_node/generic/cte_ref.rs b/src/frontend/src/optimizer/plan_node/generic/cte_ref.rs new file mode 100644 index 0000000000000..6414b8dfdc0b2 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/cte_ref.rs @@ -0,0 +1,100 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use std::hash::Hash; + +use itertools::Itertools; +use pretty_xmlish::{Pretty, StrAssocArr}; +use risingwave_common::catalog::Schema; + +use super::{impl_distill_unit_from_fields, GenericPlanNode, GenericPlanRef}; +use crate::binder::ShareId; +use crate::optimizer::property::FunctionalDependencySet; +use crate::{optimizer, OptimizerContextRef}; + +#[derive(Clone, Debug)] +pub struct CteRef { + share_id: ShareId, + base: PlanRef, +} + +impl PartialEq for CteRef { + fn eq(&self, other: &Self) -> bool { + self.share_id == other.share_id + } +} + +impl Eq for CteRef {} + +impl Hash for CteRef { + fn hash(&self, state: &mut H) { + self.share_id.hash(state); + } +} + +impl CteRef { + pub fn new(share_id: ShareId, base: PlanRef) -> Self { + Self { share_id, base } + } +} + +impl CteRef { + pub fn get_cte_ref(&self) -> Option { + self.ctx().get_rcte_cache_plan(&self.share_id) + } +} + +impl GenericPlanNode for CteRef { + fn schema(&self) -> Schema { + if let Some(plan_ref) = self.get_cte_ref() { + plan_ref.schema().clone() + } else { + self.base.schema().clone() + } + } + + fn stream_key(&self) -> Option> { + if let Some(plan_ref) = self.get_cte_ref() { + plan_ref + .stream_key() + .map(|s| s.iter().map(|i| i.to_owned()).collect_vec()) + } else { + self.base + .stream_key() + .map(|s| s.iter().map(|i| i.to_owned()).collect_vec()) + } + } + + fn ctx(&self) -> OptimizerContextRef { + // it does not matter where the context is coming from, + // since we are only getting a reference. + self.base.ctx() + } + + fn functional_dependency(&self) -> FunctionalDependencySet { + if let Some(plan_ref) = self.get_cte_ref() { + plan_ref.functional_dependency().clone() + } else { + self.base.functional_dependency().clone() + } + } +} + +impl CteRef { + pub fn fields_pretty<'a>(&self) -> StrAssocArr<'a> { + vec![("share_id", Pretty::debug(&self.share_id))] + } +} + +impl_distill_unit_from_fields! {CteRef, GenericPlanRef} diff --git a/src/frontend/src/optimizer/plan_node/generic/mod.rs b/src/frontend/src/optimizer/plan_node/generic/mod.rs index 0fcd255cdb03b..00db5730e8038 100644 --- a/src/frontend/src/optimizer/plan_node/generic/mod.rs +++ b/src/frontend/src/optimizer/plan_node/generic/mod.rs @@ -74,6 +74,10 @@ mod limit; pub use limit::*; mod max_one_row; pub use max_one_row::*; +mod cte_ref; +pub use cte_ref::*; +mod recursive_union; +pub use recursive_union::*; pub trait DistillUnit { fn distill_with_name<'a>(&self, name: impl Into>) -> XmlNode<'a>; diff --git a/src/frontend/src/optimizer/plan_node/generic/project.rs b/src/frontend/src/optimizer/plan_node/generic/project.rs index 10329c9f8ca91..68c652f0e006f 100644 --- a/src/frontend/src/optimizer/plan_node/generic/project.rs +++ b/src/frontend/src/optimizer/plan_node/generic/project.rs @@ -86,7 +86,7 @@ impl GenericPlanNode for Project { Some(input_idx) => { let mut field = input_schema.fields()[input_idx].clone(); if let Some(name) = self.field_names.get(&i) { - field.name = name.clone(); + field.name.clone_from(name); } (field.name, field.sub_fields, field.type_name) } diff --git a/src/frontend/src/optimizer/plan_node/generic/recursive_union.rs b/src/frontend/src/optimizer/plan_node/generic/recursive_union.rs new file mode 100644 index 0000000000000..023718f1a9682 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/generic/recursive_union.rs @@ -0,0 +1,64 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use pretty_xmlish::StrAssocArr; +use risingwave_common::catalog::Schema; + +use super::{impl_distill_unit_from_fields, GenericPlanNode, GenericPlanRef}; +use crate::binder::ShareId; +use crate::optimizer::property::FunctionalDependencySet; +use crate::OptimizerContextRef; + +/// `RecursiveUnion` returns the union of the rows of its inputs. +/// note: if `all` is false, it needs to eliminate duplicates. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecursiveUnion { + pub id: ShareId, + pub base: PlanRef, + pub recursive: PlanRef, +} + +impl GenericPlanNode for RecursiveUnion { + fn functional_dependency(&self) -> FunctionalDependencySet { + self.recursive.functional_dependency().clone() + } + + fn schema(&self) -> Schema { + self.recursive.schema().clone() + } + + fn stream_key(&self) -> Option> { + let fields_len = self.base.schema().len(); + let base = self.base.stream_key(); + if let Some(base) = base { + let mut base = base.to_vec(); + base.push(fields_len); + Some(base) + } else { + None + } + } + + fn ctx(&self) -> OptimizerContextRef { + self.recursive.ctx() + } +} + +impl RecursiveUnion { + pub fn fields_pretty<'a>(&self) -> StrAssocArr<'a> { + vec![] + } +} + +impl_distill_unit_from_fields!(RecursiveUnion, GenericPlanRef); diff --git a/src/frontend/src/optimizer/plan_node/logical_agg.rs b/src/frontend/src/optimizer/plan_node/logical_agg.rs index cad073386a42e..ba0c1f9608279 100644 --- a/src/frontend/src/optimizer/plan_node/logical_agg.rs +++ b/src/frontend/src/optimizer/plan_node/logical_agg.rs @@ -18,7 +18,6 @@ use risingwave_common::types::{DataType, Datum, ScalarImpl}; use risingwave_common::util::sort_util::ColumnOrder; use risingwave_common::{bail_not_implemented, not_implemented}; use risingwave_expr::aggregate::{agg_kinds, AggKind}; -use risingwave_expr::sig::FUNCTION_REGISTRY; use super::generic::{self, Agg, GenericPlanRef, PlanAggCall, ProjectBuilder}; use super::utils::impl_distill_by_unit; @@ -27,7 +26,7 @@ use super::{ PlanTreeNodeUnary, PredicatePushdown, StreamHashAgg, StreamProject, StreamSimpleAgg, StreamStatelessSimpleAgg, ToBatch, ToStream, }; -use crate::error::{ErrorCode, Result}; +use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{ AggCall, Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, Literal, OrderBy, WindowFunction, @@ -262,7 +261,7 @@ pub struct LogicalAggBuilder { /// the agg calls agg_calls: Vec, /// the error during the expression rewriting - error: Option, + error: Option, /// If `is_in_filter_clause` is true, it means that /// we are processing filter clause. /// This field is needed because input refs in these clauses @@ -354,7 +353,7 @@ impl LogicalAggBuilder { fn rewrite_with_error(&mut self, expr: ExprImpl) -> Result { let rewritten_expr = self.rewrite_expr(expr); if let Some(error) = self.error.take() { - return Err(error.into()); + return Err(error); } Ok(rewritten_expr) } @@ -377,51 +376,151 @@ impl LogicalAggBuilder { self.group_key.len() } - /// Push a new planned agg call into the builder. - /// Return an `InputRef` to that agg call. - /// For existing agg calls, return an `InputRef` to the existing one. - fn push_agg_call(&mut self, agg_call: PlanAggCall) -> InputRef { - if let Some((pos, existing)) = self.agg_calls.iter().find_position(|&c| c == &agg_call) { - return InputRef::new( - self.schema_agg_start_offset() + pos, - existing.return_type.clone(), - ); - } - let index = self.schema_agg_start_offset() + self.agg_calls.len(); - let data_type = agg_call.return_type.clone(); - self.agg_calls.push(agg_call); - InputRef::new(index, data_type) - } - - /// When there is an agg call, there are 3 things to do: - /// 1. eval its inputs via project; - /// 2. add a `PlanAggCall` to agg; - /// 3. rewrite it as an `InputRef` to the agg result in select list. - /// - /// Note that the rewriter does not traverse into inputs of agg calls. - fn try_rewrite_agg_call( - &mut self, + /// Rewrite [`AggCall`] if needed, and push it into the builder using `push_agg_call`. + /// This is shared by [`LogicalAggBuilder`] and `LogicalOverWindowBuilder`. + pub(crate) fn general_rewrite_agg_call( agg_call: AggCall, - ) -> std::result::Result { - let return_type = agg_call.return_type(); - let (agg_kind, inputs, mut distinct, mut order_by, filter, direct_args) = - agg_call.decompose(); + mut push_agg_call: impl FnMut(AggCall) -> Result, + ) -> Result { + match agg_call.agg_kind { + // Rewrite avg to cast(sum as avg_return_type) / count. + AggKind::Avg => { + assert_eq!(agg_call.args.len(), 1); + + let sum = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Sum, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?) + .cast_explicit(agg_call.return_type())?; + + let count = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Count, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?); + + Ok(FunctionCall::new(ExprType::Divide, Vec::from([sum, count]))?.into()) + } + // We compute `var_samp` as + // (sum(sq) - sum * sum / count) / (count - 1) + // and `var_pop` as + // (sum(sq) - sum * sum / count) / count + // Since we don't have the square function, we use the plain Multiply for squaring, + // which is in a sense more general than the pow function, especially when calculating + // covariances in the future. Also we don't have the sqrt function for rooting, so we + // use pow(x, 0.5) to simulate + kind @ (AggKind::StddevPop + | AggKind::StddevSamp + | AggKind::VarPop + | AggKind::VarSamp) => { + let arg = agg_call.args().iter().exactly_one().unwrap(); + let squared_arg = ExprImpl::from(FunctionCall::new( + ExprType::Multiply, + vec![arg.clone(), arg.clone()], + )?); + + let sum_of_sq = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Sum, + vec![squared_arg], + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?) + .cast_explicit(agg_call.return_type())?; + + let sum = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Sum, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?) + .cast_explicit(agg_call.return_type())?; + + let count = ExprImpl::from(push_agg_call(AggCall::new( + AggKind::Count, + agg_call.args.clone(), + agg_call.distinct, + agg_call.order_by.clone(), + agg_call.filter.clone(), + agg_call.direct_args.clone(), + )?)?); + + let one = ExprImpl::from(Literal::new( + Datum::from(ScalarImpl::Int64(1)), + DataType::Int64, + )); - if matches!(agg_kind, agg_kinds::must_have_order_by!()) && order_by.sort_exprs.is_empty() { - return Err(ErrorCode::InvalidInputSyntax(format!( - "Aggregation function {} requires ORDER BY clause", - agg_kind - ))); - } + let squared_sum = ExprImpl::from(FunctionCall::new( + ExprType::Multiply, + vec![sum.clone(), sum], + )?); + + let numerator = ExprImpl::from(FunctionCall::new( + ExprType::Subtract, + vec![ + sum_of_sq, + ExprImpl::from(FunctionCall::new( + ExprType::Divide, + vec![squared_sum, count.clone()], + )?), + ], + )?); + + let denominator = match kind { + AggKind::VarPop | AggKind::StddevPop => count.clone(), + AggKind::VarSamp | AggKind::StddevSamp => ExprImpl::from(FunctionCall::new( + ExprType::Subtract, + vec![count.clone(), one.clone()], + )?), + _ => unreachable!(), + }; - // try ignore ORDER BY if it doesn't affect the result - if matches!(agg_kind, agg_kinds::result_unaffected_by_order_by!()) { - order_by = OrderBy::any(); - } - // try ignore DISTINCT if it doesn't affect the result - if matches!(agg_kind, agg_kinds::result_unaffected_by_distinct!()) { - distinct = false; + let mut target = ExprImpl::from(FunctionCall::new( + ExprType::Divide, + vec![numerator, denominator], + )?); + + if matches!(kind, AggKind::StddevPop | AggKind::StddevSamp) { + target = ExprImpl::from(FunctionCall::new(ExprType::Sqrt, vec![target])?); + } + + match kind { + AggKind::VarPop | AggKind::StddevPop => Ok(target), + AggKind::StddevSamp | AggKind::VarSamp => { + let case_cond = ExprImpl::from(FunctionCall::new( + ExprType::LessThanOrEqual, + vec![count, one], + )?); + let null = ExprImpl::from(Literal::new(None, agg_call.return_type())); + + Ok(ExprImpl::from(FunctionCall::new( + ExprType::Case, + vec![case_cond, null, target], + )?)) + } + _ => unreachable!(), + } + } + _ => Ok(push_agg_call(agg_call)?.into()), } + } + + /// Push a new agg call into the builder. + /// Return an `InputRef` to that agg call. + /// For existing agg calls, return an `InputRef` to the existing one. + fn push_agg_call(&mut self, agg_call: AggCall) -> Result { + let return_type = agg_call.return_type(); + let (kind, args, distinct, order_by, filter, direct_args) = agg_call.decompose(); self.is_in_filter_clause = true; // filter expr is not added to `input_proj_builder` as a whole. Special exprs incl @@ -429,27 +528,7 @@ impl LogicalAggBuilder { let filter = filter.rewrite_expr(self); self.is_in_filter_clause = false; - if matches!(agg_kind, AggKind::Grouping) { - if self.grouping_sets.is_empty() { - return Err(ErrorCode::NotSupported( - "GROUPING must be used in a query with grouping sets".into(), - "try to use grouping sets instead".into(), - )); - } - if inputs.len() >= 32 { - return Err(ErrorCode::InvalidInputSyntax( - "GROUPING must have fewer than 32 arguments".into(), - )); - } - if inputs.iter().any(|x| self.try_as_group_expr(x).is_none()) { - return Err(ErrorCode::InvalidInputSyntax( - "arguments to GROUPING must be grouping expressions of the associated query level" - .into(), - )); - } - } - - let inputs: Vec<_> = inputs + let args: Vec<_> = args .iter() .map(|expr| { let index = self.input_proj_builder.add_expr(expr)?; @@ -470,220 +549,90 @@ impl LogicalAggBuilder { not_implemented!("{err} inside aggregation calls order by") })?; - match agg_kind { - // Rewrite avg to cast(sum as avg_return_type) / count. - AggKind::Avg => { - assert_eq!(inputs.len(), 1); - - let left_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Sum, &[inputs[0].return_type()]) - .unwrap(); - let left_ref = self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Sum, - return_type: left_return_type, - inputs: inputs.clone(), - distinct, - order_by: order_by.clone(), - filter: filter.clone(), - direct_args: direct_args.clone(), - }); - let left = ExprImpl::from(left_ref).cast_explicit(return_type).unwrap(); - - let right_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Count, &[inputs[0].return_type()]) - .unwrap(); - let right_ref = self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Count, - return_type: right_return_type, - inputs, - distinct, - order_by, - filter, - direct_args, - }); - - Ok(ExprImpl::from( - FunctionCall::new(ExprType::Divide, vec![left, right_ref.into()]).unwrap(), - )) - } + let plan_agg_call = PlanAggCall { + agg_kind: kind, + return_type: return_type.clone(), + inputs: args, + distinct, + order_by, + filter, + direct_args, + }; - // We compute `var_samp` as - // (sum(sq) - sum * sum / count) / (count - 1) - // and `var_pop` as - // (sum(sq) - sum * sum / count) / count - // Since we don't have the square function, we use the plain Multiply for squaring, - // which is in a sense more general than the pow function, especially when calculating - // covariances in the future. Also we don't have the sqrt function for rooting, so we - // use pow(x, 0.5) to simulate - AggKind::StddevPop | AggKind::StddevSamp | AggKind::VarPop | AggKind::VarSamp => { - let input = inputs.iter().exactly_one().unwrap(); - let pre_proj_input = self.input_proj_builder.get_expr(input.index).unwrap(); - - // first, we compute sum of squared as sum_sq - let squared_input_expr = ExprImpl::from( - FunctionCall::new( - ExprType::Multiply, - vec![pre_proj_input.clone(), pre_proj_input.clone()], - ) - .unwrap(), - ); - - let squared_input_proj_index = self - .input_proj_builder - .add_expr(&squared_input_expr) - .unwrap(); - - let sum_of_squares_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Sum, &[squared_input_expr.return_type()]) - .unwrap(); - - let sum_of_squares_expr = ExprImpl::from(self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Sum, - return_type: sum_of_squares_return_type, - inputs: vec![InputRef::new( - squared_input_proj_index, - squared_input_expr.return_type(), - )], - distinct, - order_by: order_by.clone(), - filter: filter.clone(), - direct_args: direct_args.clone(), - })) - .cast_explicit(return_type.clone()) - .unwrap(); - - // after that, we compute sum - let sum_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Sum, &[input.return_type()]) - .unwrap(); - - let sum_expr = ExprImpl::from(self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Sum, - return_type: sum_return_type, - inputs: inputs.clone(), - distinct, - order_by: order_by.clone(), - filter: filter.clone(), - direct_args: direct_args.clone(), - })) - .cast_explicit(return_type.clone()) - .unwrap(); - - // then, we compute count - let count_return_type = FUNCTION_REGISTRY - .get_return_type(AggKind::Count, &[input.return_type()]) - .unwrap(); - - let count_expr = ExprImpl::from(self.push_agg_call(PlanAggCall { - agg_kind: AggKind::Count, - return_type: count_return_type, - inputs, - distinct, - order_by, - filter, - direct_args, - })); - - // we start with variance - - // sum * sum - let square_of_sum_expr = ExprImpl::from( - FunctionCall::new(ExprType::Multiply, vec![sum_expr.clone(), sum_expr]) - .unwrap(), - ); - - // sum_sq - sum * sum / count - let numerator_expr = ExprImpl::from( - FunctionCall::new( - ExprType::Subtract, - vec![ - sum_of_squares_expr, - ExprImpl::from( - FunctionCall::new( - ExprType::Divide, - vec![square_of_sum_expr, count_expr.clone()], - ) - .unwrap(), - ), - ], - ) - .unwrap(), - ); - - // count or count - 1 - let denominator_expr = match agg_kind { - AggKind::StddevPop | AggKind::VarPop => count_expr.clone(), - AggKind::StddevSamp | AggKind::VarSamp => ExprImpl::from( - FunctionCall::new( - ExprType::Subtract, - vec![ - count_expr.clone(), - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - ) - .unwrap(), - ), - _ => unreachable!(), - }; + if let Some((pos, existing)) = self + .agg_calls + .iter() + .find_position(|&c| c == &plan_agg_call) + { + return Ok(InputRef::new( + self.schema_agg_start_offset() + pos, + existing.return_type.clone(), + )); + } + let index = self.schema_agg_start_offset() + self.agg_calls.len(); + self.agg_calls.push(plan_agg_call); + Ok(InputRef::new(index, return_type)) + } - let mut target_expr = ExprImpl::from( - FunctionCall::new(ExprType::Divide, vec![numerator_expr, denominator_expr]) - .unwrap(), - ); + /// When there is an agg call, there are 3 things to do: + /// 1. Rewrite `avg`, `var_samp`, etc. into a combination of `sum`, `count`, etc.; + /// 2. Add exprs in arguments to input `Project`; + /// 2. Add the agg call to current `Agg`, and return an `InputRef` to it. + /// + /// Note that the rewriter does not traverse into inputs of agg calls. + fn try_rewrite_agg_call(&mut self, mut agg_call: AggCall) -> Result { + if matches!(agg_call.agg_kind, agg_kinds::must_have_order_by!()) + && agg_call.order_by.sort_exprs.is_empty() + { + return Err(ErrorCode::InvalidInputSyntax(format!( + "Aggregation function {} requires ORDER BY clause", + agg_call.agg_kind + )) + .into()); + } - // stddev = sqrt(variance) - if matches!(agg_kind, AggKind::StddevPop | AggKind::StddevSamp) { - target_expr = ExprImpl::from( - FunctionCall::new(ExprType::Sqrt, vec![target_expr]).unwrap(), - ); - } + // try ignore ORDER BY if it doesn't affect the result + if matches!( + agg_call.agg_kind, + agg_kinds::result_unaffected_by_order_by!() + ) { + agg_call.order_by = OrderBy::any(); + } + // try ignore DISTINCT if it doesn't affect the result + if matches!( + agg_call.agg_kind, + agg_kinds::result_unaffected_by_distinct!() + ) { + agg_call.distinct = false; + } - match agg_kind { - AggKind::VarPop | AggKind::StddevPop => Ok(target_expr), - AggKind::StddevSamp | AggKind::VarSamp => { - let less_than_expr = ExprImpl::from( - FunctionCall::new( - ExprType::LessThanOrEqual, - vec![ - count_expr, - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - ) - .unwrap(), - ); - let null_expr = ExprImpl::from(Literal::new(None, return_type)); - - let case_expr = ExprImpl::from( - FunctionCall::new( - ExprType::Case, - vec![less_than_expr, null_expr, target_expr], - ) - .unwrap(), - ); - - Ok(case_expr) - } - _ => unreachable!(), - } + if matches!(agg_call.agg_kind, AggKind::Grouping) { + if self.grouping_sets.is_empty() { + return Err(ErrorCode::NotSupported( + "GROUPING must be used in a query with grouping sets".into(), + "try to use grouping sets instead".into(), + ) + .into()); + } + if agg_call.args.len() >= 32 { + return Err(ErrorCode::InvalidInputSyntax( + "GROUPING must have fewer than 32 arguments".into(), + ) + .into()); + } + if agg_call + .args + .iter() + .any(|x| self.try_as_group_expr(x).is_none()) + { + return Err(ErrorCode::InvalidInputSyntax( + "arguments to GROUPING must be grouping expressions of the associated query level" + .into(), + ).into()); } - _ => Ok(self - .push_agg_call(PlanAggCall { - agg_kind, - return_type, - inputs, - distinct, - order_by, - filter, - direct_args, - }) - .into()), } + + Self::general_rewrite_agg_call(agg_call, |agg_call| self.push_agg_call(agg_call)) } } @@ -754,10 +703,13 @@ impl ExprRewriter for LogicalAggBuilder { ) .into() } else { - self.error = Some(ErrorCode::InvalidInputSyntax( - "column must appear in the GROUP BY clause or be used in an aggregate function" - .into(), - )); + self.error = Some( + ErrorCode::InvalidInputSyntax( + "column must appear in the GROUP BY clause or be used in an aggregate function" + .into(), + ) + .into(), + ); expr } } @@ -1202,12 +1154,9 @@ impl ToStream for LogicalAgg { #[cfg(test)] mod tests { use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; use super::*; - use crate::expr::{ - assert_eq_input_ref, input_ref_to_column_indices, AggCall, ExprType, FunctionCall, OrderBy, - }; + use crate::expr::{assert_eq_input_ref, input_ref_to_column_indices}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; diff --git a/src/frontend/src/optimizer/plan_node/logical_cte_ref.rs b/src/frontend/src/optimizer/plan_node/logical_cte_ref.rs new file mode 100644 index 0000000000000..473ed7d06f67d --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/logical_cte_ref.rs @@ -0,0 +1,98 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use risingwave_common::bail_not_implemented; +use risingwave_common::util::column_index_mapping::ColIndexMapping; + +use super::expr_visitable::ExprVisitable; +use super::utils::impl_distill_by_unit; +use super::{ + generic, ColPrunable, ColumnPruningContext, ExprRewritable, Logical, LogicalProject, PlanBase, + PredicatePushdown, PredicatePushdownContext, RewriteStreamContext, ToBatch, ToStream, + ToStreamContext, +}; +use crate::binder::ShareId; +use crate::error::Result; +use crate::utils::Condition; +use crate::PlanRef; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalCteRef { + pub base: PlanBase, + core: generic::CteRef, +} + +impl LogicalCteRef { + pub fn new(share_id: ShareId, base_plan: PlanRef) -> Self { + let core = generic::CteRef::new(share_id, base_plan); + let base = PlanBase::new_logical_with_core(&core); + Self { base, core } + } + + pub fn create(share_id: ShareId, base_plan: PlanRef) -> PlanRef { + Self::new(share_id, base_plan).into() + } +} + +impl_plan_tree_node_for_leaf! {LogicalCteRef} + +impl_distill_by_unit! {LogicalCteRef, core, "LogicalCteRef"} + +impl ExprRewritable for LogicalCteRef {} + +impl ExprVisitable for LogicalCteRef {} + +impl ColPrunable for LogicalCteRef { + fn prune_col(&self, required_cols: &[usize], _ctx: &mut ColumnPruningContext) -> PlanRef { + LogicalProject::with_out_col_idx(self.clone().into(), required_cols.iter().copied()).into() + } +} + +impl PredicatePushdown for LogicalCteRef { + fn predicate_pushdown( + &self, + _predicate: Condition, + _ctx: &mut PredicatePushdownContext, + ) -> PlanRef { + self.clone().into() + } +} + +impl ToBatch for LogicalCteRef { + fn to_batch(&self) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_batch of LogicalCteRef" + ) + } +} + +impl ToStream for LogicalCteRef { + fn to_stream(&self, _ctx: &mut ToStreamContext) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_stream of LogicalCteRef" + ) + } + + fn logical_rewrite_for_stream( + &self, + _ctx: &mut RewriteStreamContext, + ) -> Result<(PlanRef, ColIndexMapping)> { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for logical_rewrite_for_stream of LogicalCteRef" + ) + } +} diff --git a/src/frontend/src/optimizer/plan_node/logical_filter.rs b/src/frontend/src/optimizer/plan_node/logical_filter.rs index 6bae6cc7efb22..4ea9adf7aacac 100644 --- a/src/frontend/src/optimizer/plan_node/logical_filter.rs +++ b/src/frontend/src/optimizer/plan_node/logical_filter.rs @@ -247,11 +247,11 @@ mod tests { use std::collections::HashSet; use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::{DataType, ScalarImpl}; + use risingwave_common::types::ScalarImpl; use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{assert_eq_input_ref, FunctionCall, InputRef, Literal}; + use crate::expr::{assert_eq_input_ref, Literal}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_hop_window.rs b/src/frontend/src/optimizer/plan_node/logical_hop_window.rs index 37f1dba5c2720..b15424d6d892a 100644 --- a/src/frontend/src/optimizer/plan_node/logical_hop_window.rs +++ b/src/frontend/src/optimizer/plan_node/logical_hop_window.rs @@ -366,7 +366,6 @@ mod test { use risingwave_common::types::DataType; use super::*; - use crate::expr::InputRef; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_join.rs b/src/frontend/src/optimizer/plan_node/logical_join.rs index 571efee542c2b..e9dac0de38b5a 100644 --- a/src/frontend/src/optimizer/plan_node/logical_join.rs +++ b/src/frontend/src/optimizer/plan_node/logical_join.rs @@ -1339,10 +1339,9 @@ impl ToStream for LogicalJoin { } else { Err(RwError::from(ErrorCode::NotSupported( "streaming nested-loop join".to_string(), - // TODO: replace the link with user doc "The non-equal join in the query requires a nested-loop join executor, which could be very expensive to run. \ Consider rewriting the query to use dynamic filter as a substitute if possible.\n\ - See also: https://github.com/risingwavelabs/rfcs/blob/main/rfcs/0033-dynamic-filter.md".to_owned(), + See also: https://docs.risingwave.com/docs/current/sql-pattern-dynamic-filters/".to_owned(), ))) } } @@ -1472,7 +1471,7 @@ mod tests { use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{assert_eq_input_ref, FunctionCall, InputRef, Literal}; + use crate::expr::{assert_eq_input_ref, FunctionCall, Literal}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_multi_join.rs b/src/frontend/src/optimizer/plan_node/logical_multi_join.rs index a47c75d31bd27..a30b78d44e00c 100644 --- a/src/frontend/src/optimizer/plan_node/logical_multi_join.rs +++ b/src/frontend/src/optimizer/plan_node/logical_multi_join.rs @@ -871,7 +871,7 @@ mod test { use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{FunctionCall, InputRef}; + use crate::expr::InputRef; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::optimizer::plan_node::LogicalValues; diff --git a/src/frontend/src/optimizer/plan_node/logical_over_window.rs b/src/frontend/src/optimizer/plan_node/logical_over_window.rs index af1fc50c9057f..b5762a224d180 100644 --- a/src/frontend/src/optimizer/plan_node/logical_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/logical_over_window.rs @@ -14,7 +14,7 @@ use fixedbitset::FixedBitSet; use itertools::Itertools; -use risingwave_common::types::{DataType, Datum, ScalarImpl}; +use risingwave_common::types::DataType; use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; use risingwave_common::{bail_not_implemented, not_implemented}; use risingwave_expr::aggregate::AggKind; @@ -29,9 +29,11 @@ use super::{ }; use crate::error::{ErrorCode, Result, RwError}; use crate::expr::{ - Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, WindowFunction, + AggCall, Expr, ExprImpl, ExprRewriter, ExprType, ExprVisitor, FunctionCall, InputRef, + WindowFunction, }; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; +use crate::optimizer::plan_node::logical_agg::LogicalAggBuilder; use crate::optimizer::plan_node::{ ColumnPruningContext, Literal, PredicatePushdownContext, RewriteStreamContext, ToStreamContext, }; @@ -103,7 +105,7 @@ impl<'a> LogicalOverWindowBuilder<'a> { window_func.frame, ); - if let WindowFuncKind::Aggregate(agg_kind) = kind + let new_expr = if let WindowFuncKind::Aggregate(agg_kind) = kind && matches!( agg_kind, AggKind::Avg @@ -111,148 +113,39 @@ impl<'a> LogicalOverWindowBuilder<'a> { | AggKind::StddevSamp | AggKind::VarPop | AggKind::VarSamp - ) - { - // Refer to `LogicalAggBuilder::try_rewrite_agg_call` - match agg_kind { - AggKind::Avg => { - assert_eq!(args.len(), 1); - let left_ref = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Sum), - partition_by.clone(), - order_by.clone(), - args.clone(), - frame.clone(), - )?)) - .cast_explicit(return_type)?; - let right_ref = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Count), - partition_by, - order_by, - args, - frame, - )?)); - - let new_expr = ExprImpl::from(FunctionCall::new( - ExprType::Divide, - vec![left_ref, right_ref], - )?); - Ok(new_expr) - } - AggKind::StddevPop | AggKind::StddevSamp | AggKind::VarPop | AggKind::VarSamp => { - let input = args.first().unwrap(); - let squared_input_expr = ExprImpl::from(FunctionCall::new( - ExprType::Multiply, - vec![input.clone(), input.clone()], - )?); - - let sum_of_squares_expr = - ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Sum), - partition_by.clone(), - order_by.clone(), - vec![squared_input_expr], - frame.clone(), - )?)) - .cast_explicit(return_type.clone())?; - - let sum_expr = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Sum), + ) { + let agg_call = AggCall::new( + agg_kind, + args, + false, + order_by, + Condition::true_cond(), + vec![], + )?; + LogicalAggBuilder::general_rewrite_agg_call(agg_call, |agg_call| { + Ok(self.push_window_func( + // AggCall -> WindowFunction + WindowFunction::new( + WindowFuncKind::Aggregate(agg_call.agg_kind), partition_by.clone(), - order_by.clone(), - args.clone(), + agg_call.order_by.clone(), + agg_call.args.clone(), frame.clone(), - )?)) - .cast_explicit(return_type.clone())?; - - let count_expr = ExprImpl::from(self.push_window_func(WindowFunction::new( - WindowFuncKind::Aggregate(AggKind::Count), - partition_by, - order_by, - args.clone(), - frame, - )?)); - - let square_of_sum_expr = ExprImpl::from(FunctionCall::new( - ExprType::Multiply, - vec![sum_expr.clone(), sum_expr], - )?); - - let numerator_expr = ExprImpl::from(FunctionCall::new( - ExprType::Subtract, - vec![ - sum_of_squares_expr, - ExprImpl::from(FunctionCall::new( - ExprType::Divide, - vec![square_of_sum_expr, count_expr.clone()], - )?), - ], - )?); - - let denominator_expr = match agg_kind { - AggKind::StddevPop | AggKind::VarPop => count_expr.clone(), - AggKind::StddevSamp | AggKind::VarSamp => { - ExprImpl::from(FunctionCall::new( - ExprType::Subtract, - vec![ - count_expr.clone(), - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - )?) - } - _ => unreachable!(), - }; - - let mut target_expr = ExprImpl::from(FunctionCall::new( - ExprType::Divide, - vec![numerator_expr, denominator_expr], - )?); - - if matches!(agg_kind, AggKind::StddevPop | AggKind::StddevSamp) { - target_expr = ExprImpl::from( - FunctionCall::new(ExprType::Sqrt, vec![target_expr]).unwrap(), - ); - } - - match agg_kind { - AggKind::VarPop | AggKind::StddevPop => Ok(target_expr), - AggKind::StddevSamp | AggKind::VarSamp => { - let less_than_expr = ExprImpl::from(FunctionCall::new( - ExprType::LessThanOrEqual, - vec![ - count_expr, - ExprImpl::from(Literal::new( - Datum::from(ScalarImpl::Int64(1)), - DataType::Int64, - )), - ], - )?); - let null_expr = ExprImpl::from(Literal::new(None, return_type)); - - let case_expr = ExprImpl::from(FunctionCall::new( - ExprType::Case, - vec![less_than_expr, null_expr, target_expr], - )?); - Ok(case_expr) - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } + )?, + )) + })? } else { - let new_expr = ExprImpl::from(self.push_window_func(WindowFunction::new( + ExprImpl::from(self.push_window_func(WindowFunction::new( kind, partition_by, order_by, args, frame, - )?)); - Ok(new_expr) - } + )?)) + }; + + assert_eq!(new_expr.return_type(), return_type); + Ok(new_expr) } } diff --git a/src/frontend/src/optimizer/plan_node/logical_project.rs b/src/frontend/src/optimizer/plan_node/logical_project.rs index a0f26548da2b1..78bd74969b2c1 100644 --- a/src/frontend/src/optimizer/plan_node/logical_project.rs +++ b/src/frontend/src/optimizer/plan_node/logical_project.rs @@ -303,7 +303,7 @@ mod tests { use risingwave_pb::expr::expr_node::Type; use super::*; - use crate::expr::{assert_eq_input_ref, FunctionCall, InputRef, Literal}; + use crate::expr::{assert_eq_input_ref, FunctionCall, Literal}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; diff --git a/src/frontend/src/optimizer/plan_node/logical_project_set.rs b/src/frontend/src/optimizer/plan_node/logical_project_set.rs index 53e7995c1517e..984fdac720b90 100644 --- a/src/frontend/src/optimizer/plan_node/logical_project_set.rs +++ b/src/frontend/src/optimizer/plan_node/logical_project_set.rs @@ -412,10 +412,8 @@ mod test { use std::collections::HashSet; use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; use super::*; - use crate::expr::{ExprImpl, InputRef, TableFunction}; use crate::optimizer::optimizer_context::OptimizerContext; use crate::optimizer::plan_node::LogicalValues; use crate::optimizer::property::FunctionalDependency; diff --git a/src/frontend/src/optimizer/plan_node/logical_recursive_union.rs b/src/frontend/src/optimizer/plan_node/logical_recursive_union.rs new file mode 100644 index 0000000000000..04a3dc297d352 --- /dev/null +++ b/src/frontend/src/optimizer/plan_node/logical_recursive_union.rs @@ -0,0 +1,141 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use itertools::Itertools; +use pretty_xmlish::{Pretty, XmlNode}; +use risingwave_common::bail_not_implemented; +use risingwave_common::util::column_index_mapping::ColIndexMapping; +use smallvec::{smallvec, SmallVec}; + +use super::expr_visitable::ExprVisitable; +use super::generic::GenericPlanRef; +use super::utils::{childless_record, Distill}; +use super::{ + generic, ColPrunable, ColumnPruningContext, ExprRewritable, Logical, PlanBase, PlanTreeNode, + PredicatePushdown, PredicatePushdownContext, RewriteStreamContext, ToBatch, ToStream, + ToStreamContext, +}; +use crate::binder::ShareId; +use crate::error::Result; +use crate::utils::Condition; +use crate::PlanRef; + +/// `LogicalRecursiveUnion` returns the union of the rows of its inputs. +/// note: if `all` is false, it needs to eliminate duplicates. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct LogicalRecursiveUnion { + pub base: PlanBase, + core: generic::RecursiveUnion, +} + +impl LogicalRecursiveUnion { + pub fn new(base_plan: PlanRef, recursive: PlanRef, id: ShareId) -> Self { + let core = generic::RecursiveUnion { + base: base_plan, + recursive, + id, + }; + let base = PlanBase::new_logical_with_core(&core); + LogicalRecursiveUnion { base, core } + } + + pub fn create(base_plan: PlanRef, recursive: PlanRef, id: ShareId) -> PlanRef { + Self::new(base_plan, recursive, id).into() + } + + pub(super) fn pretty_fields(base: impl GenericPlanRef, name: &str) -> XmlNode<'_> { + childless_record(name, vec![("id", Pretty::debug(&base.id().0))]) + } +} + +impl PlanTreeNode for LogicalRecursiveUnion { + fn inputs(&self) -> SmallVec<[PlanRef; 2]> { + smallvec![self.core.base.clone(), self.core.recursive.clone()] + } + + fn clone_with_inputs(&self, inputs: &[PlanRef]) -> PlanRef { + let mut inputs = inputs.iter().cloned(); + Self::create(inputs.next().unwrap(), inputs.next().unwrap(), self.core.id) + } +} + +impl Distill for LogicalRecursiveUnion { + fn distill<'a>(&self) -> XmlNode<'a> { + Self::pretty_fields(&self.base, "LogicalRecursiveUnion") + } +} + +impl ColPrunable for LogicalRecursiveUnion { + fn prune_col(&self, required_cols: &[usize], ctx: &mut ColumnPruningContext) -> PlanRef { + let new_inputs = self + .inputs() + .iter() + .map(|input| input.prune_col(required_cols, ctx)) + .collect_vec(); + let new_plan = self.clone_with_inputs(&new_inputs); + self.ctx() + .insert_rcte_cache_plan(self.core.id, new_plan.clone()); + new_plan + } +} + +impl ExprRewritable for LogicalRecursiveUnion {} + +impl ExprVisitable for LogicalRecursiveUnion {} + +impl PredicatePushdown for LogicalRecursiveUnion { + fn predicate_pushdown( + &self, + predicate: Condition, + ctx: &mut PredicatePushdownContext, + ) -> PlanRef { + let new_inputs = self + .inputs() + .iter() + .map(|input| input.predicate_pushdown(predicate.clone(), ctx)) + .collect_vec(); + let new_plan = self.clone_with_inputs(&new_inputs); + self.ctx() + .insert_rcte_cache_plan(self.core.id, new_plan.clone()); + new_plan + } +} + +impl ToBatch for LogicalRecursiveUnion { + fn to_batch(&self) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_batch of LogicalRecursiveUnion" + ) + } +} + +impl ToStream for LogicalRecursiveUnion { + fn to_stream(&self, _ctx: &mut ToStreamContext) -> Result { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for to_stream of LogicalRecursiveUnion" + ) + } + + fn logical_rewrite_for_stream( + &self, + _ctx: &mut RewriteStreamContext, + ) -> Result<(PlanRef, ColIndexMapping)> { + bail_not_implemented!( + issue = 15135, + "recursive CTE not supported for logical_rewrite_for_stream of LogicalRecursiveUnion" + ) + } +} diff --git a/src/frontend/src/optimizer/plan_node/logical_union.rs b/src/frontend/src/optimizer/plan_node/logical_union.rs index 99de2748dca4e..6238e63fd9f51 100644 --- a/src/frontend/src/optimizer/plan_node/logical_union.rs +++ b/src/frontend/src/optimizer/plan_node/logical_union.rs @@ -327,8 +327,7 @@ impl ToStream for LogicalUnion { #[cfg(test)] mod tests { - use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; + use risingwave_common::catalog::Field; use super::*; use crate::optimizer::optimizer_context::OptimizerContext; diff --git a/src/frontend/src/optimizer/plan_node/logical_values.rs b/src/frontend/src/optimizer/plan_node/logical_values.rs index cdf9af3c93541..7a585f7e0801f 100644 --- a/src/frontend/src/optimizer/plan_node/logical_values.rs +++ b/src/frontend/src/optimizer/plan_node/logical_values.rs @@ -205,11 +205,9 @@ impl ToStream for LogicalValues { #[cfg(test)] mod tests { - use risingwave_common::catalog::Field; - use risingwave_common::types::{DataType, Datum}; + use risingwave_common::types::Datum; use super::*; - use crate::expr::Literal; use crate::optimizer::optimizer_context::OptimizerContext; fn literal(val: i32) -> ExprImpl { diff --git a/src/frontend/src/optimizer/plan_node/mod.rs b/src/frontend/src/optimizer/plan_node/mod.rs index b35b97a724bcb..d7187357e3fad 100644 --- a/src/frontend/src/optimizer/plan_node/mod.rs +++ b/src/frontend/src/optimizer/plan_node/mod.rs @@ -33,7 +33,7 @@ use std::ops::Deref; use std::rc::Rc; use downcast_rs::{impl_downcast, Downcast}; -use dyn_clone::{self, DynClone}; +use dyn_clone::DynClone; use fixedbitset::FixedBitSet; use itertools::Itertools; use paste::paste; @@ -848,6 +848,7 @@ mod batch_values; mod logical_agg; mod logical_apply; mod logical_cdc_scan; +mod logical_cte_ref; mod logical_dedup; mod logical_delete; mod logical_except; @@ -865,6 +866,7 @@ mod logical_now; mod logical_over_window; mod logical_project; mod logical_project_set; +mod logical_recursive_union; mod logical_scan; mod logical_share; mod logical_source; @@ -947,6 +949,7 @@ pub use batch_values::BatchValues; pub use logical_agg::LogicalAgg; pub use logical_apply::LogicalApply; pub use logical_cdc_scan::LogicalCdcScan; +pub use logical_cte_ref::LogicalCteRef; pub use logical_dedup::LogicalDedup; pub use logical_delete::LogicalDelete; pub use logical_except::LogicalExcept; @@ -965,6 +968,7 @@ pub use logical_now::LogicalNow; pub use logical_over_window::LogicalOverWindow; pub use logical_project::LogicalProject; pub use logical_project_set::LogicalProjectSet; +pub use logical_recursive_union::LogicalRecursiveUnion; pub use logical_scan::LogicalScan; pub use logical_share::LogicalShare; pub use logical_source::LogicalSource; @@ -1063,6 +1067,8 @@ macro_rules! for_all_plan_nodes { , { Logical, MaxOneRow } , { Logical, KafkaScan } , { Logical, IcebergScan } + , { Logical, RecursiveUnion } + , { Logical, CteRef } , { Batch, SimpleAgg } , { Batch, HashAgg } , { Batch, SortAgg } @@ -1165,6 +1171,8 @@ macro_rules! for_logical_plan_nodes { , { Logical, MaxOneRow } , { Logical, KafkaScan } , { Logical, IcebergScan } + , { Logical, RecursiveUnion } + , { Logical, CteRef } } }; } diff --git a/src/frontend/src/optimizer/plan_node/plan_base.rs b/src/frontend/src/optimizer/plan_node/plan_base.rs index 3e9783894d40f..12fba475241c5 100644 --- a/src/frontend/src/optimizer/plan_node/plan_base.rs +++ b/src/frontend/src/optimizer/plan_node/plan_base.rs @@ -13,13 +13,10 @@ // limitations under the License. use educe::Educe; -use fixedbitset::FixedBitSet; -use risingwave_common::catalog::Schema; use super::generic::GenericPlanNode; use super::*; -use crate::optimizer::optimizer_context::OptimizerContextRef; -use crate::optimizer::property::{Distribution, FunctionalDependencySet, Order}; +use crate::optimizer::property::Distribution; /// No extra fields for logical plan nodes. #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs b/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs index 64abc6bcd2de8..af56ae3f76582 100644 --- a/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs +++ b/src/frontend/src/optimizer/plan_node/predicate_pushdown.rs @@ -19,7 +19,6 @@ use paste::paste; use super::*; use crate::optimizer::plan_visitor::ShareParentCounter; use crate::optimizer::PlanVisitor; -use crate::utils::Condition; use crate::{for_batch_plan_nodes, for_stream_plan_nodes}; /// The trait for predicate pushdown, only logical plan node will use it, though all plan node impl diff --git a/src/frontend/src/optimizer/plan_node/stream_delta_join.rs b/src/frontend/src/optimizer/plan_node/stream_delta_join.rs index 89b1402cba919..49257676bc004 100644 --- a/src/frontend/src/optimizer/plan_node/stream_delta_join.rs +++ b/src/frontend/src/optimizer/plan_node/stream_delta_join.rs @@ -20,10 +20,9 @@ use risingwave_pb::plan_common::JoinType; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{ArrangementInfo, DeltaIndexJoinNode}; -use super::generic::{self, GenericPlanRef}; use super::stream::prelude::*; use super::utils::{childless_record, Distill}; -use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary}; +use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary}; use crate::expr::{Expr, ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; use crate::optimizer::plan_node::utils::IndicesDisplay; diff --git a/src/frontend/src/optimizer/plan_node/stream_dml.rs b/src/frontend/src/optimizer/plan_node/stream_dml.rs index bdda75d061012..e777041718291 100644 --- a/src/frontend/src/optimizer/plan_node/stream_dml.rs +++ b/src/frontend/src/optimizer/plan_node/stream_dml.rs @@ -18,7 +18,6 @@ use risingwave_common::catalog::{ColumnDesc, INITIAL_TABLE_VERSION_ID}; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, Distill}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs b/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs index 974c6fca5df1a..a90cd4b77c669 100644 --- a/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs +++ b/src/frontend/src/optimizer/plan_node/stream_dynamic_filter.rs @@ -17,9 +17,8 @@ pub use risingwave_pb::expr::expr_node::Type as ExprType; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::DynamicFilterNode; -use super::generic::{DynamicFilter, GenericPlanRef}; +use super::generic::DynamicFilter; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{ childless_record, column_names_pretty, plan_node_name, watermark_pretty, Distill, }; diff --git a/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs b/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs index 7cd9bb533a807..78cb0e3b9d605 100644 --- a/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/stream_eowc_over_window.rs @@ -18,7 +18,7 @@ use fixedbitset::FixedBitSet; use risingwave_common::util::sort_util::OrderType; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use super::generic::{self, GenericPlanRef, PlanWindowFunction}; +use super::generic::{self, PlanWindowFunction}; use super::stream::prelude::*; use super::utils::{impl_distill_by_unit, TableCatalogBuilder}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; diff --git a/src/frontend/src/optimizer/plan_node/stream_group_topn.rs b/src/frontend/src/optimizer/plan_node/stream_group_topn.rs index e48d23d14e04e..8500e24b0fd9f 100644 --- a/src/frontend/src/optimizer/plan_node/stream_group_topn.rs +++ b/src/frontend/src/optimizer/plan_node/stream_group_topn.rs @@ -16,9 +16,8 @@ use fixedbitset::FixedBitSet; use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use super::generic::{DistillUnit, GenericPlanRef, TopNLimit}; +use super::generic::{DistillUnit, TopNLimit}; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{plan_node_name, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanTreeNodeUnary, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs b/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs index d5d7bde249a17..eb69d5c259bb6 100644 --- a/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs +++ b/src/frontend/src/optimizer/plan_node/stream_hash_agg.rs @@ -17,14 +17,13 @@ use itertools::Itertools; use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; -use super::generic::{self, GenericPlanRef, PlanAggCall}; +use super::generic::{self, PlanAggCall}; use super::stream::prelude::*; use super::utils::{childless_record, plan_node_name, watermark_pretty, Distill}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::error::{ErrorCode, Result}; use crate::expr::{ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::stream::StreamPlanRef; use crate::stream_fragmenter::BuildFragmentGraphState; use crate::utils::{ColIndexMapping, ColIndexMappingRewriteExt, IndexSet}; diff --git a/src/frontend/src/optimizer/plan_node/stream_hash_join.rs b/src/frontend/src/optimizer/plan_node/stream_hash_join.rs index d5e5e9838ac66..b803fccef7b07 100644 --- a/src/frontend/src/optimizer/plan_node/stream_hash_join.rs +++ b/src/frontend/src/optimizer/plan_node/stream_hash_join.rs @@ -20,9 +20,8 @@ use risingwave_pb::plan_common::JoinType; use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::{DeltaExpression, HashJoinNode, PbInequalityPair}; -use super::generic::{GenericPlanRef, Join}; +use super::generic::Join; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, plan_node_name, watermark_pretty, Distill}; use super::{ generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary, StreamDeltaJoin, StreamNode, diff --git a/src/frontend/src/optimizer/plan_node/stream_hop_window.rs b/src/frontend/src/optimizer/plan_node/stream_hop_window.rs index 9cb5800cc7220..0cdddf77ed0e5 100644 --- a/src/frontend/src/optimizer/plan_node/stream_hop_window.rs +++ b/src/frontend/src/optimizer/plan_node/stream_hop_window.rs @@ -17,9 +17,7 @@ use risingwave_common::util::column_index_mapping::ColIndexMapping; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::HopWindowNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprVisitor}; diff --git a/src/frontend/src/optimizer/plan_node/stream_materialize.rs b/src/frontend/src/optimizer/plan_node/stream_materialize.rs index 3da52ef4f55a2..53b8983bfd0ed 100644 --- a/src/frontend/src/optimizer/plan_node/stream_materialize.rs +++ b/src/frontend/src/optimizer/plan_node/stream_materialize.rs @@ -27,14 +27,12 @@ use risingwave_pb::stream_plan::stream_node::PbNodeBody; use super::derive::derive_columns; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, Distill}; use super::{reorganize_elements_id, ExprRewritable, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::catalog::table_catalog::{TableCatalog, TableType, TableVersion}; use crate::error::Result; use crate::optimizer::plan_node::derive::derive_pk; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::optimizer::plan_node::{PlanBase, PlanNodeMeta}; use crate::optimizer::property::{Cardinality, Distribution, Order, RequiredDist}; use crate::stream_fragmenter::BuildFragmentGraphState; diff --git a/src/frontend/src/optimizer/plan_node/stream_over_window.rs b/src/frontend/src/optimizer/plan_node/stream_over_window.rs index 7173e3a46e0cd..be6c63bcb50de 100644 --- a/src/frontend/src/optimizer/plan_node/stream_over_window.rs +++ b/src/frontend/src/optimizer/plan_node/stream_over_window.rs @@ -23,7 +23,6 @@ use super::stream::prelude::*; use super::utils::{impl_distill_by_unit, TableCatalogBuilder}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::stream_fragmenter::BuildFragmentGraphState; use crate::TableCatalog; diff --git a/src/frontend/src/optimizer/plan_node/stream_project.rs b/src/frontend/src/optimizer/plan_node/stream_project.rs index 1b97835e9896f..c077af9241aa1 100644 --- a/src/frontend/src/optimizer/plan_node/stream_project.rs +++ b/src/frontend/src/optimizer/plan_node/stream_project.rs @@ -17,9 +17,7 @@ use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::ProjectNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{ diff --git a/src/frontend/src/optimizer/plan_node/stream_share.rs b/src/frontend/src/optimizer/plan_node/stream_share.rs index 395a32bc9be8a..5bf575f622bce 100644 --- a/src/frontend/src/optimizer/plan_node/stream_share.rs +++ b/src/frontend/src/optimizer/plan_node/stream_share.rs @@ -16,7 +16,6 @@ use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::PbStreamNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; use super::utils::Distill; use super::{generic, ExprRewritable, PlanRef, PlanTreeNodeUnary, StreamExchange, StreamNode}; diff --git a/src/frontend/src/optimizer/plan_node/stream_sink.rs b/src/frontend/src/optimizer/plan_node/stream_sink.rs index ff8f12b49db0b..94dbf36591444 100644 --- a/src/frontend/src/optimizer/plan_node/stream_sink.rs +++ b/src/frontend/src/optimizer/plan_node/stream_sink.rs @@ -37,12 +37,11 @@ use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::SinkLogStoreType; use super::derive::{derive_columns, derive_pk}; -use super::generic::{self, GenericPlanRef}; use super::stream::prelude::*; use super::utils::{ childless_record, infer_kv_log_store_table_catalog_inner, Distill, IndicesDisplay, }; -use super::{ExprRewritable, PlanBase, PlanRef, StreamNode, StreamProject}; +use super::{generic, ExprRewritable, PlanBase, PlanRef, StreamNode, StreamProject}; use crate::error::{ErrorCode, Result}; use crate::expr::{ExprImpl, FunctionCall, InputRef}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs b/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs index d5fce4eecdc1c..8ce0997b7fe1f 100644 --- a/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs +++ b/src/frontend/src/optimizer/plan_node/stream_stateless_simple_agg.rs @@ -22,7 +22,6 @@ use super::utils::impl_distill_by_unit; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{ExprRewriter, ExprVisitor}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; -use crate::optimizer::plan_node::generic::PhysicalPlanRef; use crate::optimizer::property::RequiredDist; use crate::stream_fragmenter::BuildFragmentGraphState; diff --git a/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs b/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs index ce8753b9ddbc8..aa090143b925b 100644 --- a/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs +++ b/src/frontend/src/optimizer/plan_node/stream_temporal_join.rs @@ -20,9 +20,7 @@ use risingwave_pb::stream_plan::stream_node::NodeBody; use risingwave_pb::stream_plan::TemporalJoinNode; use risingwave_sqlparser::ast::AsOf; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanBase, PlanRef, PlanTreeNodeBinary}; use crate::expr::{Expr, ExprRewriter, ExprVisitor}; diff --git a/src/frontend/src/optimizer/plan_node/stream_union.rs b/src/frontend/src/optimizer/plan_node/stream_union.rs index 7a317a2677495..1c269ec0c5ad2 100644 --- a/src/frontend/src/optimizer/plan_node/stream_union.rs +++ b/src/frontend/src/optimizer/plan_node/stream_union.rs @@ -19,9 +19,7 @@ use pretty_xmlish::XmlNode; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use risingwave_pb::stream_plan::UnionNode; -use super::generic::GenericPlanRef; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill}; use super::{generic, ExprRewritable, PlanRef}; use crate::optimizer::plan_node::expr_visitable::ExprVisitable; diff --git a/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs b/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs index ed7e367f93715..ffb08776b3fe5 100644 --- a/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs +++ b/src/frontend/src/optimizer/plan_node/stream_watermark_filter.rs @@ -20,7 +20,6 @@ use risingwave_pb::catalog::WatermarkDesc; use risingwave_pb::stream_plan::stream_node::PbNodeBody; use super::stream::prelude::*; -use super::stream::StreamPlanRef; use super::utils::{childless_record, watermark_pretty, Distill, TableCatalogBuilder}; use super::{ExprRewritable, PlanBase, PlanRef, PlanTreeNodeUnary, StreamNode}; use crate::expr::{ExprDisplay, ExprImpl}; diff --git a/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs b/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs index d0a4358b4582a..c41e1c5b36463 100644 --- a/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs +++ b/src/frontend/src/optimizer/plan_visitor/jsonb_stream_key_checker.rs @@ -17,7 +17,7 @@ use risingwave_common::types::DataType; use super::{DefaultBehavior, Merge}; use crate::optimizer::plan_node::generic::GenericPlanRef; -use crate::optimizer::plan_node::{PlanNode, *}; +use crate::optimizer::plan_node::*; use crate::optimizer::plan_visitor::PlanVisitor; #[derive(Debug, Clone, Default)] diff --git a/src/frontend/src/optimizer/property/distribution.rs b/src/frontend/src/optimizer/property/distribution.rs index 4999e1d8630bf..358ad934a9ccf 100644 --- a/src/frontend/src/optimizer/property/distribution.rs +++ b/src/frontend/src/optimizer/property/distribution.rs @@ -62,7 +62,6 @@ use crate::catalog::catalog_service::CatalogReader; use crate::catalog::FragmentId; use crate::error::Result; use crate::optimizer::property::Order; -use crate::optimizer::PlanRef; /// the distribution property provided by a operator. #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/src/frontend/src/optimizer/property/order.rs b/src/frontend/src/optimizer/property/order.rs index 1a657e190fac4..68f2120df567b 100644 --- a/src/frontend/src/optimizer/property/order.rs +++ b/src/frontend/src/optimizer/property/order.rs @@ -22,7 +22,6 @@ use risingwave_pb::common::PbColumnOrder; use super::super::plan_node::*; use crate::error::Result; -use crate::optimizer::PlanRef; // TODO(rc): use this type to replace all `Vec` #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] diff --git a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs index 7823c7f41f7b4..c9cb4edc860cd 100644 --- a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs +++ b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_agg_rule.rs @@ -24,7 +24,6 @@ use crate::expr::{Expr, ExprImpl, ExprRewriter, ExprType, FunctionCall, InputRef use crate::optimizer::plan_expr_visitor::Strong; use crate::optimizer::plan_node::generic::{Agg, GenericPlanNode, GenericPlanRef}; use crate::optimizer::plan_visitor::{PlanCorrelatedIdFinder, PlanVisitor}; -use crate::optimizer::PlanRef; use crate::utils::{Condition, IndexSet}; /// Pull up correlated predicates from the right agg side of Apply to the `on` clause of Join. diff --git a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs index b5df4f1738072..d6b59b9553911 100644 --- a/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs +++ b/src/frontend/src/optimizer/rule/pull_up_correlated_predicate_rule.rs @@ -20,7 +20,6 @@ use super::{BoxedRule, Rule}; use crate::expr::{CorrelatedId, CorrelatedInputRef, Expr, ExprImpl, ExprRewriter, InputRef}; use crate::optimizer::plan_node::generic::GenericPlanRef; use crate::optimizer::plan_visitor::{PlanCorrelatedIdFinder, PlanVisitor}; -use crate::optimizer::PlanRef; use crate::utils::Condition; /// This rule is for pattern: Apply->Project->Filter. diff --git a/src/frontend/src/planner/mod.rs b/src/frontend/src/planner/mod.rs index d2f695faa5ab2..12bf25c0372df 100644 --- a/src/frontend/src/planner/mod.rs +++ b/src/frontend/src/planner/mod.rs @@ -21,6 +21,7 @@ use crate::optimizer::{OptimizerContextRef, PlanRoot}; mod delete; mod insert; mod query; +mod recursive_union; mod relation; mod select; mod set_expr; diff --git a/src/frontend/src/planner/recursive_union.rs b/src/frontend/src/planner/recursive_union.rs new file mode 100644 index 0000000000000..655e50e951a4b --- /dev/null +++ b/src/frontend/src/planner/recursive_union.rs @@ -0,0 +1,33 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use crate::binder::{BoundSetExpr, ShareId}; +use crate::error::Result; +use crate::optimizer::plan_node::LogicalRecursiveUnion; +use crate::{PlanRef, Planner}; + +impl Planner { + pub(super) fn plan_recursive_union( + &mut self, + base: BoundSetExpr, + recursive: BoundSetExpr, + id: ShareId, + ) -> Result { + let base = self.plan_set_expr(base, vec![], &[])?; + let recursive = self.plan_set_expr(recursive, vec![], &[])?; + let plan = LogicalRecursiveUnion::create(base, recursive, id); + self.ctx.insert_rcte_cache_plan(id, plan.clone()); + Ok(plan) + } +} diff --git a/src/frontend/src/planner/relation.rs b/src/frontend/src/planner/relation.rs index 04f6ca8de4c9c..51d475616e210 100644 --- a/src/frontend/src/planner/relation.rs +++ b/src/frontend/src/planner/relation.rs @@ -22,15 +22,15 @@ use risingwave_common::types::{DataType, Interval, ScalarImpl}; use risingwave_sqlparser::ast::AsOf; use crate::binder::{ - BoundBaseTable, BoundJoin, BoundShare, BoundSource, BoundSystemTable, BoundWatermark, - BoundWindowTableFunction, Relation, WindowTableFunctionKind, + BoundBackCteRef, BoundBaseTable, BoundJoin, BoundShare, BoundSource, BoundSystemTable, + BoundWatermark, BoundWindowTableFunction, Relation, WindowTableFunctionKind, }; use crate::error::{ErrorCode, Result}; use crate::expr::{Expr, ExprImpl, ExprType, FunctionCall, InputRef}; use crate::optimizer::plan_node::generic::SourceNodeKind; use crate::optimizer::plan_node::{ - LogicalApply, LogicalHopWindow, LogicalJoin, LogicalProject, LogicalScan, LogicalShare, - LogicalSource, LogicalSysScan, LogicalTableFunction, LogicalValues, PlanRef, + LogicalApply, LogicalCteRef, LogicalHopWindow, LogicalJoin, LogicalProject, LogicalScan, + LogicalShare, LogicalSource, LogicalSysScan, LogicalTableFunction, LogicalValues, PlanRef, }; use crate::optimizer::property::Cardinality; use crate::planner::Planner; @@ -57,9 +57,7 @@ impl Planner { Relation::Watermark(tf) => self.plan_watermark(*tf), // note that rcte (i.e., RecursiveUnion) is included *implicitly* in share. Relation::Share(share) => self.plan_share(*share), - Relation::BackCteRef(..) => { - bail_not_implemented!(issue = 15135, "recursive CTE is not supported") - } + Relation::BackCteRef(cte_ref) => self.plan_cte_ref(*cte_ref), } } @@ -225,20 +223,27 @@ impl Planner { } pub(super) fn plan_share(&mut self, share: BoundShare) -> Result { - let Either::Left(nonrecursive_query) = share.input else { - bail_not_implemented!(issue = 15135, "recursive CTE is not supported"); - }; - let id = share.share_id; - match self.share_cache.get(&id) { - None => { - let result = self - .plan_query(nonrecursive_query)? - .into_unordered_subplan(); - let logical_share = LogicalShare::create(result); - self.share_cache.insert(id, logical_share.clone()); - Ok(logical_share) + match share.input { + Either::Left(nonrecursive_query) => { + let id = share.share_id; + match self.share_cache.get(&id) { + None => { + let result = self + .plan_query(nonrecursive_query)? + .into_unordered_subplan(); + let logical_share = LogicalShare::create(result); + self.share_cache.insert(id, logical_share.clone()); + Ok(logical_share) + } + Some(result) => Ok(result.clone()), + } } - Some(result) => Ok(result.clone()), + // for the recursive union in rcte + Either::Right(recursive_union) => self.plan_recursive_union( + *recursive_union.base, + *recursive_union.recursive, + share.share_id, + ), } } @@ -246,6 +251,12 @@ impl Planner { todo!("plan watermark"); } + pub(super) fn plan_cte_ref(&mut self, cte_ref: BoundBackCteRef) -> Result { + // TODO: this is actually duplicated from `plan_recursive_union`, refactor? + let base = self.plan_set_expr(cte_ref.base, vec![], &[])?; + Ok(LogicalCteRef::create(cte_ref.share_id, base)) + } + fn collect_col_data_types_for_tumble_window(relation: &Relation) -> Result> { let col_data_types = match relation { Relation::Source(s) => s diff --git a/src/frontend/src/scheduler/distributed/query.rs b/src/frontend/src/scheduler/distributed/query.rs index c6e866630067b..b556bc3af6c85 100644 --- a/src/frontend/src/scheduler/distributed/query.rs +++ b/src/frontend/src/scheduler/distributed/query.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::collections::HashMap; -use std::default::Default; use std::fmt::{Debug, Formatter}; use std::mem; use std::sync::Arc; diff --git a/src/frontend/src/telemetry.rs b/src/frontend/src/telemetry.rs index 631914cee7a4f..d50b9999b946f 100644 --- a/src/frontend/src/telemetry.rs +++ b/src/frontend/src/telemetry.rs @@ -33,6 +33,7 @@ impl FrontendTelemetryCreator { #[async_trait::async_trait] impl TelemetryReportCreator for FrontendTelemetryCreator { + #[allow(refining_impl_trait)] async fn create_report( &self, tracking_id: String, @@ -97,7 +98,6 @@ mod tests { assert_eq!(report.base.node_type, TelemetryNodeType::Frontend); } - use risingwave_common::telemetry::pb_compatible::TelemetryToProtobuf; use risingwave_common::telemetry::{post_telemetry_report_pb, TELEMETRY_REPORT_URL}; // It is ok to diff --git a/src/frontend/src/user/user_catalog.rs b/src/frontend/src/user/user_catalog.rs index a33e9423fc6ea..905348d37dfa6 100644 --- a/src/frontend/src/user/user_catalog.rs +++ b/src/frontend/src/user/user_catalog.rs @@ -16,7 +16,7 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use risingwave_common::acl::{AclMode, AclModeSet}; -use risingwave_pb::user::grant_privilege::{Object as GrantObject, Object}; +use risingwave_pb::user::grant_privilege::{Action, Object as GrantObject, Object}; use risingwave_pb::user::{PbAuthInfo, PbGrantPrivilege, PbUserInfo}; use crate::catalog::{DatabaseId, SchemaId}; @@ -168,4 +168,33 @@ impl UserCatalog { self.get_acl(object) .map_or(false, |acl_set| acl_set.has_mode(mode)) } + + pub fn check_privilege_with_grant_option( + &self, + object: &GrantObject, + actions: &Vec<(Action, bool)>, + ) -> bool { + if self.is_super { + return true; + } + let mut action_map: HashMap<_, _> = actions.iter().map(|action| (action, false)).collect(); + + for privilege in &self.grant_privileges { + if privilege.get_object().unwrap() != object { + continue; + } + for awo in &privilege.action_with_opts { + let action = awo.get_action().unwrap(); + let with_grant_option = awo.with_grant_option; + + for (&key, found) in &mut action_map { + let (required_action, required_grant_option) = *key; + if action == required_action && (!required_grant_option | with_grant_option) { + *found = true; + } + } + } + } + action_map.values().all(|&found| found) + } } diff --git a/src/frontend/src/user/user_privilege.rs b/src/frontend/src/user/user_privilege.rs index 2b474bda0554e..d4ad71e64120d 100644 --- a/src/frontend/src/user/user_privilege.rs +++ b/src/frontend/src/user/user_privilege.rs @@ -94,7 +94,7 @@ pub fn available_prost_privilege(object: PbObject, for_dml_table: bool) -> PbGra &acl::ALL_AVAILABLE_MVIEW_MODES } } - PbObject::ViewId(_) => &acl::ALL_AVAILABLE_VIEW_MODES, + PbObject::ViewId(_) => &acl::ALL_AVAILABLE_TABLE_MODES, PbObject::SinkId(_) => &acl::ALL_AVAILABLE_SINK_MODES, PbObject::SubscriptionId(_) => &acl::ALL_AVAILABLE_SUBSCRIPTION_MODES, PbObject::FunctionId(_) => &acl::ALL_AVAILABLE_FUNCTION_MODES, diff --git a/src/frontend/src/utils/condition.rs b/src/frontend/src/utils/condition.rs index 5161877d1d163..d78adcbd07a97 100644 --- a/src/frontend/src/utils/condition.rs +++ b/src/frontend/src/utils/condition.rs @@ -1018,10 +1018,8 @@ mod cast_compare { #[cfg(test)] mod tests { use rand::Rng; - use risingwave_common::types::DataType; use super::*; - use crate::expr::{FunctionCall, InputRef}; #[test] fn test_split() { diff --git a/src/frontend/src/utils/with_options.rs b/src/frontend/src/utils/with_options.rs index dc3e66bdfbac2..b5848d124c9d3 100644 --- a/src/frontend/src/utils/with_options.rs +++ b/src/frontend/src/utils/with_options.rs @@ -13,10 +13,9 @@ // limitations under the License. use std::collections::{BTreeMap, HashMap}; -use std::convert::TryFrom; use std::num::NonZeroU32; -use risingwave_connector::source::kafka::{ +use risingwave_connector::source::kafka::private_link::{ insert_privatelink_broker_rewrite_map, CONNECTION_NAME_KEY, PRIVATELINK_ENDPOINT_KEY, }; use risingwave_connector::WithPropertiesExt; diff --git a/src/jni_core/Cargo.toml b/src/jni_core/Cargo.toml index 1e2e2fb3c8218..4d9c6cab092ab 100644 --- a/src/jni_core/Cargo.toml +++ b/src/jni_core/Cargo.toml @@ -14,6 +14,7 @@ anyhow = "1" bytes = "1" cfg-or-panic = "0.2" chrono = { version = "0.4", default-features = false } +foyer ={ workspace = true } fs-err = "2" futures = { version = "0.3", default-features = false, features = ["alloc"] } itertools = { workspace = true } diff --git a/src/jni_core/src/hummock_iterator.rs b/src/jni_core/src/hummock_iterator.rs index 41ba4b65e22b8..4fc6bce57e6e0 100644 --- a/src/jni_core/src/hummock_iterator.rs +++ b/src/jni_core/src/hummock_iterator.rs @@ -15,9 +15,10 @@ use std::sync::Arc; use bytes::Bytes; -use futures::{Stream, TryStreamExt}; +use foyer::HybridCacheBuilder; +use futures::{Stream, TryFutureExt, TryStreamExt}; use risingwave_common::catalog::ColumnDesc; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_common::hash::VirtualNode; use risingwave_common::row::OwnedRow; use risingwave_common::util::value_encoding::column_aware_row_encoding::ColumnAwareSerde; @@ -28,12 +29,12 @@ use risingwave_object_store::object::build_remote_object_store; use risingwave_object_store::object::object_metrics::ObjectStoreMetrics; use risingwave_pb::java_binding::key_range::Bound; use risingwave_pb::java_binding::{KeyRange, ReadPlan}; -use risingwave_storage::error::StorageResult; +use risingwave_storage::error::{StorageError, StorageResult}; use risingwave_storage::hummock::local_version::pinned_version::PinnedVersion; use risingwave_storage::hummock::store::version::HummockVersionReader; use risingwave_storage::hummock::store::HummockStorageIterator; use risingwave_storage::hummock::{ - get_committed_read_version_tuple, CachePolicy, FileCache, SstableStore, SstableStoreConfig, + get_committed_read_version_tuple, CachePolicy, HummockError, SstableStore, SstableStoreConfig, }; use risingwave_storage::monitor::{global_hummock_state_store_metrics, HummockStateStoreMetrics}; use risingwave_storage::row_serde::value_serde::ValueRowSerdeNew; @@ -77,23 +78,35 @@ impl HummockJavaBindingIterator { ) .await, ); + + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(1 << 10) + .with_shards(2) + .storage() + .build() + .map_err(HummockError::foyer_error) + .map_err(StorageError::from) + .await?; + let block_cache_v2 = HybridCacheBuilder::new() + .memory(1 << 10) + .with_shards(2) + .storage() + .build() + .map_err(HummockError::foyer_error) + .map_err(StorageError::from) + .await?; + let sstable_store = Arc::new(SstableStore::new(SstableStoreConfig { store: object_store, path: read_plan.data_dir, - block_cache_capacity: 1 << 10, - block_cache_shard_num: 2, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_capacity: 1 << 10, - meta_cache_shard_num: 2, - meta_cache_eviction: EvictionConfig::for_test(), prefetch_buffer_capacity: 1 << 10, max_prefetch_block_number: 16, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: Arc::new(global_hummock_state_store_metrics( MetricLevel::Disabled, )), + meta_cache_v2, + block_cache_v2, })); let reader = HummockVersionReader::new( sstable_store, diff --git a/src/jni_core/src/jvm_runtime.rs b/src/jni_core/src/jvm_runtime.rs index 5950cf7685ea7..f848b7d44240d 100644 --- a/src/jni_core/src/jvm_runtime.rs +++ b/src/jni_core/src/jvm_runtime.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::option::Option::Some; use std::ffi::c_void; use std::path::PathBuf; use std::sync::OnceLock; diff --git a/src/meta/Cargo.toml b/src/meta/Cargo.toml index 6e2e2f3a44bad..24dfecd99524e 100644 --- a/src/meta/Cargo.toml +++ b/src/meta/Cargo.toml @@ -31,6 +31,7 @@ either = "1" enum-as-inner = "0.6" etcd-client = { workspace = true } fail = "0.5" +function_name = "0.3.0" futures = { version = "0.3", default-features = false, features = ["alloc"] } hex = "0.4" hyper = "0.14" # required by tonic diff --git a/src/meta/model_v2/src/lib.rs b/src/meta/model_v2/src/lib.rs index b34cd5e73e6ce..87d7e3e3597f6 100644 --- a/src/meta/model_v2/src/lib.rs +++ b/src/meta/model_v2/src/lib.rs @@ -256,7 +256,7 @@ macro_rules! derive_array_from_blob { }; } -pub(crate) use {derive_array_from_blob, derive_from_blob, derive_from_json_struct}; +pub(crate) use {derive_array_from_blob, derive_from_blob}; derive_from_json_struct!(I32Array, Vec); diff --git a/src/meta/model_v2/src/subscription.rs b/src/meta/model_v2/src/subscription.rs index 47ebbc63a2dc1..3d53c4b767804 100644 --- a/src/meta/model_v2/src/subscription.rs +++ b/src/meta/model_v2/src/subscription.rs @@ -28,7 +28,7 @@ pub struct Model { pub retention_seconds: i64, pub definition: String, pub subscription_state: i32, - pub dependent_table_id: u32, + pub dependent_table_id: i32, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -59,7 +59,7 @@ impl From for ActiveModel { retention_seconds: Set(pb_subscription.retention_seconds as _), definition: Set(pb_subscription.definition), subscription_state: Set(pb_subscription.subscription_state), - dependent_table_id: Set(pb_subscription.dependent_table_id), + dependent_table_id: Set(pb_subscription.dependent_table_id as _), } } } diff --git a/src/meta/node/src/lib.rs b/src/meta/node/src/lib.rs index 140cf481e3c40..c7d8e95d7e49d 100644 --- a/src/meta/node/src/lib.rs +++ b/src/meta/node/src/lib.rs @@ -76,7 +76,7 @@ pub struct MetaNodeOpts { /// Endpoint of the SQL service, make it non-option when SQL service is required. #[clap(long, hide = true, env = "RW_SQL_ENDPOINT")] - pub sql_endpoint: Option, + pub sql_endpoint: Option>, /// The HTTP REST-API address of the Prometheus instance associated to this cluster. /// This address is used to serve `PromQL` queries to Prometheus. @@ -221,7 +221,11 @@ pub fn start(opts: MetaNodeOpts) -> Pin + Send>> { }, MetaBackend::Mem => MetaStoreBackend::Mem, MetaBackend::Sql => MetaStoreBackend::Sql { - endpoint: opts.sql_endpoint.expect("sql endpoint is required"), + endpoint: opts + .sql_endpoint + .expect("sql endpoint is required") + .expose_secret() + .to_string(), }, }; diff --git a/src/meta/service/src/session_config.rs b/src/meta/service/src/session_config.rs index a4a7d1b70591a..a3999a3d27426 100644 --- a/src/meta/service/src/session_config.rs +++ b/src/meta/service/src/session_config.rs @@ -19,7 +19,6 @@ use risingwave_pb::meta::{ GetSessionParamsRequest, GetSessionParamsResponse, SetSessionParamRequest, SetSessionParamResponse, }; -use serde_json; use thiserror_ext::AsReport; use tonic::{Request, Response, Status}; diff --git a/src/meta/service/src/user_service.rs b/src/meta/service/src/user_service.rs index d7dd4f5b4de4b..cab1b29228055 100644 --- a/src/meta/service/src/user_service.rs +++ b/src/meta/service/src/user_service.rs @@ -76,7 +76,7 @@ impl UserServiceImpl { } expanded_privileges.push(privilege); } - } else if let Some(Object::AllDmlTablesSchemaId(schema_id)) = &privilege.object { + } else if let Some(Object::AllDmlRelationsSchemaId(schema_id)) = &privilege.object { let tables = match &self.metadata_manager { MetadataManager::V1(mgr) => { mgr.catalog_manager.list_dml_table_ids(*schema_id).await @@ -89,6 +89,16 @@ impl UserServiceImpl { .map(|id| id as _) .collect(), }; + let views = match &self.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_view_ids(*schema_id).await, + MetadataManager::V2(mgr) => mgr + .catalog_controller + .list_view_ids(*schema_id as _) + .await? + .into_iter() + .map(|id| id as _) + .collect(), + }; for table_id in tables { let mut privilege = privilege.clone(); privilege.object = Some(Object::TableId(table_id)); @@ -100,6 +110,17 @@ impl UserServiceImpl { } expanded_privileges.push(privilege); } + for view_id in views { + let mut privilege = privilege.clone(); + privilege.object = Some(Object::ViewId(view_id)); + if let Some(true) = with_grant_option { + privilege + .action_with_opts + .iter_mut() + .for_each(|p| p.with_grant_option = true); + } + expanded_privileges.push(privilege); + } } else if let Some(Object::AllSourcesSchemaId(schema_id)) = &privilege.object { let sources = match &self.metadata_manager { MetadataManager::V1(mgr) => { diff --git a/src/meta/src/barrier/command.rs b/src/meta/src/barrier/command.rs index 51175ca9475f2..865835c49edeb 100644 --- a/src/meta/src/barrier/command.rs +++ b/src/meta/src/barrier/command.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::collections::{HashMap, HashSet}; -use std::default::Default; use std::sync::Arc; use futures::future::try_join_all; @@ -96,7 +95,7 @@ pub struct ReplaceTablePlan { /// We need to reassign splits for it. /// /// Note that there's no `SourceBackfillExecutor` involved for table with connector, so we don't need to worry about - /// backfill_splits. + /// `backfill_splits`. pub init_split_assignment: SplitAssignment, } @@ -389,7 +388,7 @@ pub struct CommandContext { /// Differs from [`TracedEpoch`], this span focuses on the lifetime of the corresponding /// barrier, including the process of waiting for the barrier to be sent, flowing through the /// stream graph on compute nodes, and finishing its `post_collect` stuffs. - pub span: tracing::Span, + pub _span: tracing::Span, } impl CommandContext { @@ -412,7 +411,7 @@ impl CommandContext { command, kind, barrier_manager_context, - span, + _span: span, } } diff --git a/src/meta/src/barrier/info.rs b/src/meta/src/barrier/info.rs index e07cb705b26a1..aa8882d438dce 100644 --- a/src/meta/src/barrier/info.rs +++ b/src/meta/src/barrier/info.rs @@ -51,7 +51,7 @@ pub struct InflightActorInfo { /// `actor_id` => `WorkerId` pub actor_location_map: HashMap, - /// mv_table_id => subscription_id => retention seconds + /// `mv_table_id` => `subscription_id` => retention seconds pub mv_depended_subscriptions: HashMap>, } diff --git a/src/meta/src/barrier/mod.rs b/src/meta/src/barrier/mod.rs index c1620bce7d20b..6d5657a158e48 100644 --- a/src/meta/src/barrier/mod.rs +++ b/src/meta/src/barrier/mod.rs @@ -426,6 +426,7 @@ enum CompletingCommand { // that has finished but not checkpointed. If there is any, we will force checkpoint on the next barrier join_handle: JoinHandle>, }, + #[expect(dead_code)] Err(MetaError), } diff --git a/src/meta/src/controller/catalog.rs b/src/meta/src/controller/catalog.rs index af1853aec366d..27bac7048b967 100644 --- a/src/meta/src/controller/catalog.rs +++ b/src/meta/src/controller/catalog.rs @@ -31,7 +31,7 @@ use risingwave_meta_model_v2::{ ActorUpstreamActors, ColumnCatalogArray, ConnectionId, CreateType, DatabaseId, FragmentId, FunctionId, I32Array, IndexId, JobStatus, ObjectId, PrivateLinkService, Property, SchemaId, SinkId, SourceId, StreamNode, StreamSourceInfo, StreamingParallelism, SubscriptionId, TableId, - UserId, + UserId, ViewId, }; use risingwave_pb::catalog::subscription::SubscriptionState; use risingwave_pb::catalog::table::PbTableType; @@ -581,6 +581,29 @@ impl CatalogController { } })); + let subscription_dependencies: Vec<(SubscriptionId, TableId)> = Subscription::find() + .select_only() + .columns([ + subscription::Column::SubscriptionId, + subscription::Column::DependentTableId, + ]) + .join(JoinType::InnerJoin, subscription::Relation::Object.def()) + .filter( + subscription::Column::SubscriptionState + .eq(Into::::into(SubscriptionState::Created)) + .and(subscription::Column::DependentTableId.is_not_null()), + ) + .into_tuple() + .all(&inner.db) + .await?; + + obj_dependencies.extend(subscription_dependencies.into_iter().map( + |(subscription_id, table_id)| PbObjectDependencies { + object_id: subscription_id as _, + referenced_object_id: table_id as _, + }, + )); + Ok(obj_dependencies) } @@ -2428,6 +2451,19 @@ impl CatalogController { Ok(table_ids) } + pub async fn list_view_ids(&self, schema_id: SchemaId) -> MetaResult> { + let inner = self.inner.read().await; + let view_ids: Vec = View::find() + .select_only() + .column(view::Column::ViewId) + .join(JoinType::InnerJoin, view::Relation::Object.def()) + .filter(object::Column::SchemaId.eq(schema_id)) + .into_tuple() + .all(&inner.db) + .await?; + Ok(view_ids) + } + pub async fn list_tables_by_type(&self, table_type: TableType) -> MetaResult> { let inner = self.inner.read().await; let table_objs = Table::find() @@ -2909,8 +2945,6 @@ impl CatalogControllerInner { async fn list_subscriptions(&self) -> MetaResult> { let subscription_objs = Subscription::find() .find_also_related(Object) - .join(JoinType::LeftJoin, object::Relation::StreamingJob.def()) - .filter(streaming_job::Column::JobStatus.eq(JobStatus::Created)) .all(&self.db) .await?; @@ -2972,7 +3006,6 @@ impl CatalogControllerInner { #[cfg(test)] #[cfg(not(madsim))] mod tests { - use risingwave_meta_model_v2::ViewId; use super::*; diff --git a/src/meta/src/controller/fragment.rs b/src/meta/src/controller/fragment.rs index 552008914d76e..7bf5b02f971af 100644 --- a/src/meta/src/controller/fragment.rs +++ b/src/meta/src/controller/fragment.rs @@ -1346,7 +1346,6 @@ impl CatalogController { #[cfg(test)] mod tests { use std::collections::{BTreeMap, HashMap}; - use std::default::Default; use itertools::Itertools; use risingwave_common::hash::{ParallelUnitId, ParallelUnitMapping}; diff --git a/src/meta/src/controller/mod.rs b/src/meta/src/controller/mod.rs index ff90dd33297d2..7e9f20f7557d6 100644 --- a/src/meta/src/controller/mod.rs +++ b/src/meta/src/controller/mod.rs @@ -235,7 +235,7 @@ impl From> for PbSubscription { ), initialized_at_cluster_version: value.1.initialized_at_cluster_version, created_at_cluster_version: value.1.created_at_cluster_version, - dependent_table_id: value.0.dependent_table_id, + dependent_table_id: value.0.dependent_table_id as _, subscription_state: PbSubscriptionState::Init as _, } } diff --git a/src/meta/src/controller/streaming_job.rs b/src/meta/src/controller/streaming_job.rs index c501c14252ffe..db9c1d03eab4b 100644 --- a/src/meta/src/controller/streaming_job.rs +++ b/src/meta/src/controller/streaming_job.rs @@ -1253,10 +1253,6 @@ impl CatalogController { .exec(&txn) .await?; - // newly created actor - let mut new_actors = vec![]; - let mut new_actor_dispatchers = vec![]; - for ( PbStreamActor { actor_id, @@ -1276,6 +1272,7 @@ impl CatalogController { ) in newly_created_actors { let mut actor_upstreams = BTreeMap::>::new(); + let mut new_actor_dispatchers = vec![]; if let Some(nodes) = &mut nodes { visit_stream_node(nodes, |node| { @@ -1314,7 +1311,7 @@ impl CatalogController { .get(&actor_id) .map(|splits| splits.iter().map(PbConnectorSplit::from).collect_vec()); - new_actors.push(actor::ActiveModel { + Actor::insert(actor::ActiveModel { actor_id: Set(actor_id as _), fragment_id: Set(fragment_id as _), status: Set(ActorStatus::Running), @@ -1324,7 +1321,9 @@ impl CatalogController { upstream_actor_ids: Set(actor_upstreams), vnode_bitmap: Set(vnode_bitmap.as_ref().map(|bitmap| bitmap.into())), expr_context: Set(expr_context.as_ref().unwrap().into()), - }); + }) + .exec(&txn) + .await?; for PbDispatcher { r#type: dispatcher_type, @@ -1348,16 +1347,11 @@ impl CatalogController { downstream_actor_ids: Set(downstream_actor_id.into()), }) } - } - - if !new_actors.is_empty() { - Actor::insert_many(new_actors).exec(&txn).await?; - } - - if !new_actor_dispatchers.is_empty() { - ActorDispatcher::insert_many(new_actor_dispatchers) - .exec(&txn) - .await?; + if !new_actor_dispatchers.is_empty() { + ActorDispatcher::insert_many(new_actor_dispatchers) + .exec(&txn) + .await?; + } } // actor update diff --git a/src/meta/src/controller/utils.rs b/src/meta/src/controller/utils.rs index 42ccc97e6637f..7d8f4769e8264 100644 --- a/src/meta/src/controller/utils.rs +++ b/src/meta/src/controller/utils.rs @@ -754,12 +754,11 @@ where let obj = match object.obj_type { ObjectType::Database => PbObject::DatabaseId(oid), ObjectType::Schema => PbObject::SchemaId(oid), - ObjectType::Table => PbObject::TableId(oid), + ObjectType::Table | ObjectType::Index => PbObject::TableId(oid), ObjectType::Source => PbObject::SourceId(oid), ObjectType::Sink => PbObject::SinkId(oid), ObjectType::View => PbObject::ViewId(oid), ObjectType::Function => PbObject::FunctionId(oid), - ObjectType::Index => unreachable!("index is not supported yet"), ObjectType::Connection => unreachable!("connection is not supported yet"), ObjectType::Subscription => PbObject::SubscriptionId(oid), }; diff --git a/src/meta/src/dashboard/mod.rs b/src/meta/src/dashboard/mod.rs index ec52c3a3ee533..1229554032614 100644 --- a/src/meta/src/dashboard/mod.rs +++ b/src/meta/src/dashboard/mod.rs @@ -55,7 +55,7 @@ pub(super) mod handlers { use itertools::Itertools; use risingwave_common_heap_profiling::COLLAPSED_SUFFIX; use risingwave_pb::catalog::table::TableType; - use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Table, View}; + use risingwave_pb::catalog::{PbDatabase, PbSchema, Sink, Source, Subscription, Table, View}; use risingwave_pb::common::{WorkerNode, WorkerType}; use risingwave_pb::meta::list_object_dependencies_response::PbObjectDependencies; use risingwave_pb::meta::PbTableFragments; @@ -141,6 +141,21 @@ pub(super) mod handlers { list_table_catalogs_inner(&srv.metadata_manager, TableType::Index).await } + pub async fn list_subscription( + Extension(srv): Extension, + ) -> Result>> { + let subscriptions = match &srv.metadata_manager { + MetadataManager::V1(mgr) => mgr.catalog_manager.list_subscriptions().await, + MetadataManager::V2(mgr) => mgr + .catalog_controller + .list_subscriptions() + .await + .map_err(err)?, + }; + + Ok(Json(subscriptions)) + } + pub async fn list_internal_tables( Extension(srv): Extension, ) -> Result>> { @@ -417,6 +432,7 @@ impl DashboardService { .route("/materialized_views", get(list_materialized_views)) .route("/tables", get(list_tables)) .route("/indexes", get(list_indexes)) + .route("/subscriptions", get(list_subscription)) .route("/internal_tables", get(list_internal_tables)) .route("/sources", get(list_sources)) .route("/sinks", get(list_sinks)) diff --git a/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs index 6dac7394dfb46..f98e14203d95b 100644 --- a/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/base_level_compaction_picker.rs @@ -30,7 +30,7 @@ use crate::hummock::compaction::{create_overlap_strategy, CompactionDeveloperCon use crate::hummock::level_handler::LevelHandler; std::thread_local! { - static LOG_COUNTER: RefCell = RefCell::new(0); + static LOG_COUNTER: RefCell = const { RefCell::new(0) }; } pub struct LevelCompactionPicker { @@ -285,14 +285,11 @@ impl LevelCompactionPicker { #[cfg(test)] pub mod tests { - use itertools::Itertools; use super::*; use crate::hummock::compaction::compaction_config::CompactionConfigBuilder; use crate::hummock::compaction::selector::tests::*; - use crate::hummock::compaction::{ - CompactionDeveloperConfig, CompactionMode, TierCompactionPicker, - }; + use crate::hummock::compaction::{CompactionMode, TierCompactionPicker}; fn create_compaction_picker_for_test() -> LevelCompactionPicker { let config = Arc::new( diff --git a/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs index f46a99bc80c0d..82e58b87c5517 100644 --- a/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/manual_compaction_picker.rs @@ -326,10 +326,10 @@ impl CompactionPicker for ManualCompactionPicker { #[cfg(test)] pub mod tests { - use std::collections::{HashMap, HashSet}; + use std::collections::HashMap; use risingwave_pb::hummock::compact_task; - pub use risingwave_pb::hummock::{KeyRange, Level, LevelType}; + pub use risingwave_pb::hummock::KeyRange; use super::*; use crate::hummock::compaction::compaction_config::CompactionConfigBuilder; diff --git a/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs index 994ca4297a502..f082c405a61ff 100644 --- a/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/min_overlap_compaction_picker.rs @@ -533,8 +533,6 @@ impl NonOverlapSubLevelPicker { pub mod tests { use std::collections::BTreeSet; - pub use risingwave_pb::hummock::{Level, LevelType}; - use super::*; use crate::hummock::compaction::overlap_strategy::RangeOverlapStrategy; use crate::hummock::compaction::selector::tests::{ diff --git a/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs b/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs index 5d05fedbe5338..ab9f74c063a0b 100644 --- a/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs +++ b/src/meta/src/hummock/compaction/picker/space_reclaim_compaction_picker.rs @@ -24,7 +24,7 @@ use crate::hummock::level_handler::LevelHandler; // key_range and selects the appropriate files to generate compaction pub struct SpaceReclaimCompactionPicker { // config - pub max_space_reclaim_bytes: u64, + pub _max_space_reclaim_bytes: u64, // for filter pub all_table_ids: HashSet, @@ -40,7 +40,7 @@ pub struct SpaceReclaimPickerState { impl SpaceReclaimCompactionPicker { pub fn new(max_space_reclaim_bytes: u64, all_table_ids: HashSet) -> Self { Self { - max_space_reclaim_bytes, + _max_space_reclaim_bytes: max_space_reclaim_bytes, all_table_ids, } } diff --git a/src/meta/src/hummock/manager/checkpoint.rs b/src/meta/src/hummock/manager/checkpoint.rs index 4d4317189f914..70bbef6bd3db2 100644 --- a/src/meta/src/hummock/manager/checkpoint.rs +++ b/src/meta/src/hummock/manager/checkpoint.rs @@ -31,7 +31,7 @@ use tracing::warn; use crate::hummock::error::Result; use crate::hummock::manager::versioning::Versioning; -use crate::hummock::metrics_utils::trigger_gc_stat; +use crate::hummock::metrics_utils::{trigger_gc_stat, trigger_split_stat}; use crate::hummock::HummockManager; #[derive(Default)] @@ -234,6 +234,7 @@ impl HummockManager { let min_pinned_version_id = context_info.min_pinned_version_id(); trigger_gc_stat(&self.metrics, &versioning.checkpoint, min_pinned_version_id); + trigger_split_stat(&self.metrics, &versioning.current_version); drop(versioning_guard); timer.observe_duration(); self.metrics diff --git a/src/meta/src/hummock/manager/compaction.rs b/src/meta/src/hummock/manager/compaction.rs index 7e4412fac1bae..369799a8d7b04 100644 --- a/src/meta/src/hummock/manager/compaction.rs +++ b/src/meta/src/hummock/manager/compaction.rs @@ -49,7 +49,7 @@ pub struct Compaction { /// `CompactStatus` of each compaction group pub compaction_statuses: BTreeMap, - pub deterministic_mode: bool, + pub _deterministic_mode: bool, } impl HummockManager { diff --git a/src/meta/src/hummock/manager/compaction_group_manager.rs b/src/meta/src/hummock/manager/compaction_group_manager.rs index 58fc4d8e9054e..c98700792a881 100644 --- a/src/meta/src/hummock/manager/compaction_group_manager.rs +++ b/src/meta/src/hummock/manager/compaction_group_manager.rs @@ -19,8 +19,8 @@ use std::sync::Arc; use itertools::Itertools; use risingwave_common::catalog::TableId; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - build_version_delta_after_version, get_compaction_group_ids, get_compaction_group_ssts, - get_member_table_ids, try_get_compaction_group_id_by_table_id, TableGroupInfo, + build_version_delta_after_version, get_compaction_group_ids, get_member_table_ids, + try_get_compaction_group_id_by_table_id, TableGroupInfo, }; use risingwave_hummock_sdk::compaction_group::{StateTableId, StaticCompactionGroupId}; use risingwave_hummock_sdk::CompactionGroupId; @@ -33,7 +33,7 @@ use risingwave_pb::hummock::subscribe_compaction_event_request::ReportTask; use risingwave_pb::hummock::write_limits::WriteLimit; use risingwave_pb::hummock::{ compact_task, CompactionConfig, CompactionGroupInfo, CompatibilityVersion, GroupConstruct, - GroupDelta, GroupDestroy, GroupMetaChange, GroupTableChange, + GroupDelta, GroupDestroy, GroupMetaChange, }; use thiserror_ext::AsReport; use tokio::sync::OnceCell; @@ -42,7 +42,7 @@ use crate::hummock::compaction::compaction_config::{ validate_compaction_config, CompactionConfigBuilder, }; use crate::hummock::error::{Error, Result}; -use crate::hummock::manager::{commit_multi_var, create_trx_wrapper, drop_sst, HummockManager}; +use crate::hummock::manager::{commit_multi_var, create_trx_wrapper, HummockManager}; use crate::hummock::metrics_utils::remove_compaction_group_in_sst_stat; use crate::hummock::model::CompactionGroup; use crate::hummock::sequence::{next_compaction_group_id, next_sstable_object_id}; @@ -298,12 +298,6 @@ impl HummockManager { .push(TableId::new(*table_id)); } - // Remove empty group, GC SSTs and remove metric. - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); let groups_to_remove = modified_groups .into_iter() .filter_map(|(group_id, member_count)| { @@ -315,12 +309,6 @@ impl HummockManager { }) .collect_vec(); for group_id in &groups_to_remove { - // We don't bother to add IntraLevelDelta to remove SSTs from group, because the entire - // group is to be removed. - // However, we need to take care of SST GC for the removed group. - for (object_id, sst_id) in get_compaction_group_ssts(current_version, *group_id) { - drop_sst(&mut branched_ssts, *group_id, object_id, sst_id); - } let group_deltas = &mut new_version_delta .group_deltas .entry(*group_id) @@ -344,7 +332,6 @@ impl HummockManager { remove_compaction_group_in_sst_stat(&self.metrics, *group_id, max_level); } versioning.current_version = current_version; - branched_ssts.commit_memory(); self.notify_last_version_delta(versioning); @@ -427,7 +414,6 @@ impl HummockManager { .move_state_table_to_compaction_group( parent_group_id, table_ids, - None, self.env.opts.partition_vnode_count, ) .await?; @@ -445,7 +431,6 @@ impl HummockManager { &self, parent_group_id: CompactionGroupId, table_ids: &[StateTableId], - target_group_id: Option, partition_vnode_count: u32, ) -> Result<(CompactionGroupId, BTreeMap)> { let mut table_to_partition = BTreeMap::default(); @@ -477,18 +462,6 @@ impl HummockManager { parent_group_id ))); } - if let Some(compaction_group_id) = target_group_id { - if !versioning.check_branched_sst_in_target_group( - &table_ids, - &parent_group_id, - &compaction_group_id, - ) { - return Err(Error::CompactionGroup(format!( - "invalid split attempt for group {}: we shall wait some time for parent group and target group could compact stale sst files", - parent_group_id - ))); - } - } let mut new_version_delta = create_trx_wrapper!( self.meta_store_ref(), BTreeMapEntryTransactionWrapper, @@ -506,44 +479,8 @@ impl HummockManager { ), ) .await?; - let mut new_group = None; - let target_compaction_group_id = match target_group_id { - Some(compaction_group_id) => { - match current_version.levels.get(&compaction_group_id) { - Some(group) => { - for table_id in &table_ids { - if group.member_table_ids.contains(table_id) { - return Err(Error::CompactionGroup(format!( - "table {} already exist in group {}", - *table_id, compaction_group_id, - ))); - } - } - } - None => { - return Err(Error::CompactionGroup(format!( - "target group {} does not exist", - compaction_group_id, - ))); - } - } - let group_deltas = &mut new_version_delta - .group_deltas - .entry(compaction_group_id) - .or_default() - .group_deltas; - group_deltas.push(GroupDelta { - delta_type: Some(DeltaType::GroupTableChange(GroupTableChange { - table_ids: table_ids.to_vec(), - origin_group_id: parent_group_id, - target_group_id: compaction_group_id, - new_sst_start_id, - version: CompatibilityVersion::NoTrivialSplit as i32, - })), - }); - compaction_group_id - } - None => { + let (new_group, target_compaction_group_id) = { + { // All NewCompactionGroup pairs are mapped to one new compaction group. let new_compaction_group_id = next_compaction_group_id(&self.env).await?; // The new config will be persisted later. @@ -570,7 +507,6 @@ impl HummockManager { }, ); - new_group = Some((new_compaction_group_id, config)); new_version_delta.group_deltas.insert( parent_group_id, GroupDeltas { @@ -582,20 +518,15 @@ impl HummockManager { }], }, ); - new_compaction_group_id + ((new_compaction_group_id, config), new_compaction_group_id) } }; let mut current_version = versioning.current_version.clone(); let sst_split_info = current_version.apply_version_delta(&new_version_delta); - // `branched_ssts` only commit in memory, so `TXN` make no difference. - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); - if let Some((new_compaction_group_id, config)) = new_group { + let (new_compaction_group_id, config) = new_group; + { let mut compaction_group_manager = self.compaction_group_manager.write().await; let insert = create_trx_wrapper!( self.meta_store_ref(), @@ -614,27 +545,13 @@ impl HummockManager { for table_id in table_ids { table_to_partition.insert(table_id, partition_vnode_count); } - } else { - commit_multi_var!(self.meta_store_ref(), new_version_delta)?; } versioning.current_version = current_version; // Updates SST split info let mut changed_sst_ids: HashSet = HashSet::default(); - for (object_id, sst_id, parent_old_sst_id, parent_new_sst_id) in sst_split_info { + for (_, _, parent_old_sst_id, _) in sst_split_info { changed_sst_ids.insert(parent_old_sst_id); - match branched_ssts.get_mut(object_id) { - Some(mut entry) => { - entry.insert(parent_group_id, parent_new_sst_id); - entry.insert(target_compaction_group_id, sst_id); - } - None => { - let mut groups = HashMap::from_iter([(target_compaction_group_id, sst_id)]); - groups.insert(parent_group_id, parent_new_sst_id); - branched_ssts.insert(object_id, groups); - } - } } - branched_ssts.commit_memory(); self.notify_last_version_delta(versioning); drop(versioning_guard); let mut canceled_tasks = vec![]; diff --git a/src/meta/src/hummock/manager/mod.rs b/src/meta/src/hummock/manager/mod.rs index 830c5f5c85418..d3d91fa70c59a 100644 --- a/src/meta/src/hummock/manager/mod.rs +++ b/src/meta/src/hummock/manager/mod.rs @@ -37,13 +37,13 @@ use risingwave_common::util::epoch::{Epoch, INVALID_EPOCH}; use risingwave_hummock_sdk::compact::{compact_task_to_string, statistics_compact_task}; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ build_version_delta_after_version, get_compaction_group_ids, - get_table_compaction_group_id_mapping, BranchedSstInfo, HummockLevelsExt, + get_table_compaction_group_id_mapping, HummockLevelsExt, }; use risingwave_hummock_sdk::version::HummockVersionDelta; use risingwave_hummock_sdk::{ version_archive_dir, version_checkpoint_path, CompactionGroupId, ExtendedSstableInfo, - HummockCompactionTaskId, HummockContextId, HummockEpoch, HummockSstableId, - HummockSstableObjectId, HummockVersionId, SstObjectIdRange, INVALID_VERSION_ID, + HummockCompactionTaskId, HummockContextId, HummockEpoch, HummockSstableObjectId, + HummockVersionId, SstObjectIdRange, INVALID_VERSION_ID, }; use risingwave_meta_model_v2::{ compaction_status, compaction_task, hummock_pinned_snapshot, hummock_pinned_version, @@ -58,7 +58,7 @@ use risingwave_pb::hummock::subscribe_compaction_event_request::{ use risingwave_pb::hummock::subscribe_compaction_event_response::Event as ResponseEvent; use risingwave_pb::hummock::{ CompactTask, CompactTaskAssignment, GroupDelta, GroupMetaChange, HummockPinnedSnapshot, - HummockPinnedVersion, HummockSnapshot, HummockVersionStats, IntraLevelDelta, + HummockPinnedVersion, HummockSnapshot, HummockVersionStats, InputLevel, IntraLevelDelta, Level, PbCompactionGroupInfo, SstableInfo, SubscribeCompactionEventRequest, TableOption, TableSchema, }; use risingwave_pb::meta::subscribe_response::{Info, Operation}; @@ -80,8 +80,8 @@ use crate::hummock::error::{Error, Result}; use crate::hummock::metrics_utils::{ build_compact_task_level_type_metrics_label, get_or_create_local_table_stat, trigger_delta_log_stats, trigger_local_table_stat, trigger_lsm_stat, trigger_mv_stat, - trigger_pin_unpin_snapshot_state, trigger_pin_unpin_version_state, trigger_split_stat, - trigger_sst_stat, trigger_version_stat, trigger_write_stop_stats, + trigger_pin_unpin_snapshot_state, trigger_pin_unpin_version_state, trigger_sst_stat, + trigger_version_stat, trigger_write_stop_stats, }; use crate::hummock::sequence::next_compaction_task_id; use crate::hummock::{CompactorManagerRef, TASK_NORMAL}; @@ -563,7 +563,6 @@ impl HummockManager { .into(), ); versioning_guard.current_version = redo_state; - versioning_guard.branched_ssts = versioning_guard.current_version.build_branched_sst_info(); versioning_guard.hummock_version_deltas = hummock_version_deltas; context_info.pinned_versions = match &meta_store { @@ -905,11 +904,6 @@ impl HummockManager { BTreeMapTransactionWrapper, BTreeMapTransaction::new(&mut versioning.hummock_version_deltas) ); - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); let mut unschedule_groups = vec![]; let mut trivial_tasks = vec![]; @@ -922,7 +916,7 @@ impl HummockManager { break; } - if current_version.levels.get(&compaction_group_id).is_none() { + if !current_version.levels.contains_key(&compaction_group_id) { continue; } @@ -1054,8 +1048,9 @@ impl HummockManager { compact_task.set_task_status(TaskStatus::Success); compact_status.report_compact_task(&compact_task); if !is_trivial_reclaim { - compact_task.sorted_output_ssts = - compact_task.input_ssts[0].table_infos.clone(); + compact_task + .sorted_output_ssts + .clone_from(&compact_task.input_ssts[0].table_infos); } self.metrics .compact_frequency @@ -1068,7 +1063,6 @@ impl HummockManager { .inc(); let version_delta = gen_version_delta( &mut hummock_version_deltas, - &mut branched_ssts, ¤t_version, &compact_task, deterministic_mode, @@ -1094,6 +1088,13 @@ impl HummockManager { compact_task.table_watermarks = current_version .safe_epoch_table_watermarks(&compact_task.existing_table_ids); + // do not split sst by vnode partition when target_level > base_level + // The purpose of data alignment is mainly to improve the parallelism of base level compaction and reduce write amplification. + // However, at high level, the size of the sst file is often larger and only contains the data of a single table_id, so there is no need to cut it. + if compact_task.target_level > compact_task.base_level { + compact_task.table_vnode_partition.clear(); + } + if self.env.opts.enable_dropped_column_reclaim { // TODO: get all table schemas for all tables in once call to avoid acquiring lock and await. compact_task.table_schemas = match self.metadata_manager() { @@ -1146,7 +1147,6 @@ impl HummockManager { compact_task_assignment, hummock_version_deltas )?; - branched_ssts.commit_memory(); trigger_version_stat(&self.metrics, ¤t_version); versioning.current_version = current_version; @@ -1344,17 +1344,34 @@ impl HummockManager { fn is_compact_task_expired( compact_task: &CompactTask, - branched_ssts: &BTreeMap, + hummock_version: &HummockVersion, ) -> bool { - for input_level in compact_task.get_input_ssts() { - for table_info in input_level.get_table_infos() { - if let Some(mp) = branched_ssts.get(&table_info.object_id) { - if mp - .get(&compact_task.compaction_group_id) - .map_or(true, |sst_id| *sst_id != table_info.sst_id) - { - return true; + if let Some(group) = hummock_version + .levels + .get(&compact_task.compaction_group_id) + { + for input_level in compact_task.get_input_ssts() { + let input_level: &InputLevel = input_level; + let mut sst_ids: HashSet<_> = input_level + .table_infos + .iter() + .map(|sst| sst.sst_id) + .collect(); + fn filter_ssts(levels: &Level, sst_ids: &mut HashSet) { + for sst in &levels.table_infos { + sst_ids.remove(&sst.sst_id); + } + } + if input_level.level_idx == 0 { + for level in &group.get_level0().sub_levels { + filter_ssts(level, &mut sst_ids); } + } else { + filter_ssts(group.get_level(input_level.level_idx as _), &mut sst_ids); + } + if !sst_ids.is_empty() { + warn!(stale_sst_id = ?sst_ids, ?compact_task, "compact task expired"); + return true; } } } @@ -1422,11 +1439,6 @@ impl HummockManager { BTreeMapTransactionWrapper, BTreeMapTransaction::new(&mut versioning.hummock_version_deltas,) ); - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); let mut version_stats = create_trx_wrapper!( self.meta_store_ref(), @@ -1473,8 +1485,7 @@ impl HummockManager { .collect(); let is_success = if let TaskStatus::Success = compact_task.task_status() { // if member_table_ids changes, the data of sstable may stale. - let is_expired = - Self::is_compact_task_expired(&compact_task, branched_ssts.tree_ref()); + let is_expired = Self::is_compact_task_expired(&compact_task, ¤t_version); if is_expired { compact_task.set_task_status(TaskStatus::InputOutdatedCanceled); false @@ -1501,7 +1512,6 @@ impl HummockManager { success_count += 1; let version_delta = gen_version_delta( &mut hummock_version_deltas, - &mut branched_ssts, ¤t_version, &compact_task, deterministic_mode, @@ -1531,7 +1541,6 @@ impl HummockManager { hummock_version_deltas, version_stats )?; - branched_ssts.commit_memory(); trigger_version_stat(&self.metrics, ¤t_version); trigger_delta_log_stats(&self.metrics, versioning.hummock_version_deltas.len()); @@ -1780,11 +1789,6 @@ impl HummockManager { } } let mut new_sst_id = next_sstable_object_id(&self.env, new_sst_id_number).await?; - let mut branched_ssts = create_trx_wrapper!( - self.meta_store_ref(), - BTreeMapTransactionWrapper, - BTreeMapTransaction::new(&mut versioning.branched_ssts) - ); let original_sstables = std::mem::take(&mut sstables); sstables.reserve_exact(original_sstables.len() - incorrect_ssts.len() + new_sst_id_number); let mut incorrect_ssts = incorrect_ssts.into_iter(); @@ -1803,9 +1807,6 @@ impl HummockManager { branch_groups.insert(group_id, new_sst_id); new_sst_id += 1; } - if !branch_groups.is_empty() { - branched_ssts.insert(sst.get_object_id(), branch_groups); - } } else { sstables.push(original_sstable); } @@ -1891,7 +1892,6 @@ impl HummockManager { table_metrics.inc_write_throughput(stats_value as u64); } commit_multi_var!(self.meta_store_ref(), new_version_delta, version_stats)?; - branched_ssts.commit_memory(); versioning.current_version = new_hummock_version; let snapshot = HummockSnapshot { @@ -1995,20 +1995,16 @@ impl HummockManager { let pinned_snapshots_copy = context_info_guard.pinned_snapshots.clone(); let hummock_version_deltas_copy = versioning_guard.hummock_version_deltas.clone(); let version_stats_copy = versioning_guard.version_stats.clone(); - let branched_ssts = versioning_guard.branched_ssts.clone(); - ( - ( - compact_statuses_copy, - compact_task_assignment_copy, - pinned_versions_copy, - pinned_snapshots_copy, - hummock_version_deltas_copy, - version_stats_copy, - ), - branched_ssts, - ) + (( + compact_statuses_copy, + compact_task_assignment_copy, + pinned_versions_copy, + pinned_snapshots_copy, + hummock_version_deltas_copy, + version_stats_copy, + ),) }; - let (mem_state, branched_ssts) = get_state( + let mem_state = get_state( &mut compaction_guard, &mut versioning_guard, &mut context_info_guard, @@ -2020,12 +2016,11 @@ impl HummockManager { ) .await .expect("Failed to load state from meta store"); - let (loaded_state, load_branched_ssts) = get_state( + let loaded_state = get_state( &mut compaction_guard, &mut versioning_guard, &mut context_info_guard, ); - assert_eq!(branched_ssts, load_branched_ssts); assert_eq!( mem_state, loaded_state, "hummock in-mem state is inconsistent with meta store state", @@ -2051,12 +2046,6 @@ impl HummockManager { .max_committed_epoch } - /// Gets branched sstable infos - /// Should not be called inside [`HummockManager`], because it requests locks internally. - pub async fn get_branched_ssts_info(&self) -> BTreeMap { - self.versioning.read().await.branched_ssts.clone() - } - /// Gets the mapping from table id to compaction group id pub async fn get_table_compaction_group_id_mapping( &self, @@ -2528,12 +2517,7 @@ impl HummockManager { } HummockTimerEvent::Report => { - let ( - current_version, - id_to_config, - branched_sst, - version_stats, - ) = { + let (current_version, id_to_config, version_stats) = { let versioning_guard = hummock_manager.versioning.read().await; @@ -2543,7 +2527,6 @@ impl HummockManager { ( versioning_deref.current_version.clone(), configs, - versioning_deref.branched_ssts.clone(), versioning_deref.version_stats.clone(), ) }; @@ -2571,13 +2554,6 @@ impl HummockManager { compaction_group_config.group_id(), ); - trigger_split_stat( - &hummock_manager.metrics, - compaction_group_config.group_id(), - group_levels.member_table_ids.len(), - &branched_sst, - ); - trigger_lsm_stat( &hummock_manager.metrics, compaction_group_config.compaction_config(), @@ -3183,7 +3159,6 @@ impl HummockManager { .move_state_table_to_compaction_group( parent_group_id, &[*table_id], - None, partition_vnode_count, ) .await; @@ -3325,32 +3300,8 @@ pub enum TableAlignRule { SplitByTable(StateTableId), } -fn drop_sst( - branched_ssts: &mut BTreeMapTransactionWrapper<'_, HummockSstableObjectId, BranchedSstInfo>, - group_id: CompactionGroupId, - object_id: HummockSstableObjectId, - sst_id: HummockSstableId, -) -> bool { - match branched_ssts.get_mut(object_id) { - Some(mut entry) => { - // if group_id not exist, it would not pass the stale check before. - let removed_sst_id = entry.get(&group_id).unwrap(); - assert_eq!(*removed_sst_id, sst_id); - entry.remove(&group_id); - if entry.is_empty() { - branched_ssts.remove(object_id); - true - } else { - false - } - } - None => true, - } -} - -fn gen_version_delta<'a>( - txn: &mut BTreeMapTransactionWrapper<'a, HummockVersionId, HummockVersionDelta>, - branched_ssts: &mut BTreeMapTransactionWrapper<'a, HummockSstableObjectId, BranchedSstInfo>, +fn gen_version_delta( + txn: &mut BTreeMapTransactionWrapper<'_, HummockVersionId, HummockVersionDelta>, old_version: &HummockVersion, compact_task: &CompactTask, deterministic_mode: bool, @@ -3376,19 +3327,7 @@ fn gen_version_delta<'a>( let mut removed_table_ids = level .table_infos .iter() - .map(|sst| { - let object_id = sst.get_object_id(); - let sst_id = sst.get_sst_id(); - if !trivial_move { - drop_sst( - branched_ssts, - compact_task.compaction_group_id, - object_id, - sst_id, - ); - } - sst_id - }) + .map(|sst| sst.get_sst_id()) .collect_vec(); removed_table_ids_map diff --git a/src/meta/src/hummock/manager/tests.rs b/src/meta/src/hummock/manager/tests.rs index d033bccd5867d..160f9518f648e 100644 --- a/src/meta/src/hummock/manager/tests.rs +++ b/src/meta/src/hummock/manager/tests.rs @@ -14,16 +14,14 @@ use std::borrow::Borrow; use std::cmp::Ordering; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::sync::Arc; use itertools::Itertools; use prometheus::Registry; use risingwave_common::util::epoch::{test_epoch, EpochExt, INVALID_EPOCH}; use risingwave_hummock_sdk::compact::compact_task_to_string; -use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - get_compaction_group_ssts, BranchedSstInfo, -}; +use risingwave_hummock_sdk::compaction_group::hummock_version_ext::get_compaction_group_ssts; use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; use risingwave_hummock_sdk::table_stats::{to_prost_table_stats_map, TableStats, TableStatsMap}; use risingwave_hummock_sdk::version::HummockVersion; @@ -1388,41 +1386,6 @@ async fn test_split_compaction_group_on_commit() { .member_table_ids, vec![101] ); - let branched_ssts = hummock_manager - .versioning - .read() - .await - .branched_ssts - .clone(); - assert_eq!(branched_ssts.len(), 1); - assert_eq!(branched_ssts.values().next().unwrap().len(), 2); - assert_ne!( - branched_ssts - .values() - .next() - .unwrap() - .get(&2) - .cloned() - .unwrap(), - branched_ssts - .values() - .next() - .unwrap() - .get(&3) - .cloned() - .unwrap(), - ); -} - -async fn get_branched_ssts( - hummock_manager: &HummockManager, -) -> BTreeMap { - hummock_manager - .versioning - .read() - .await - .branched_ssts - .clone() } #[tokio::test] @@ -1556,21 +1519,6 @@ async fn test_split_compaction_group_on_demand_basic() { .member_table_ids, vec![100, 101] ); - let branched_ssts = get_branched_ssts(&hummock_manager).await; - assert_eq!(branched_ssts.len(), 2); - for object_id in [10, 11] { - assert_eq!(branched_ssts.get(&object_id).unwrap().len(), 2); - assert_ne!( - branched_ssts - .get(&object_id) - .unwrap() - .get(&new_group_id) - .cloned() - .unwrap(), - object_id, - "trivial adjust should also generate a new SST id" - ); - } } #[tokio::test] @@ -1635,20 +1583,6 @@ async fn test_split_compaction_group_on_demand_non_trivial() { .member_table_ids, vec![100] ); - let branched_ssts = get_branched_ssts(&hummock_manager).await; - assert_eq!(branched_ssts.len(), 1); - assert_eq!(branched_ssts.get(&10).unwrap().len(), 2); - let sst_ids = branched_ssts.get(&10).unwrap().get(&2).cloned().unwrap(); - assert_ne!(sst_ids, 10); - assert_ne!( - branched_ssts - .get(&10) - .unwrap() - .get(&new_group_id) - .cloned() - .unwrap(), - sst_ids, - ); } #[tokio::test] @@ -1927,14 +1861,6 @@ async fn test_split_compaction_group_on_demand_bottom_levels() { 2 ); - let branched_ssts = hummock_manager.get_branched_ssts_info().await; - // object-11 and object-12 - assert_eq!(branched_ssts.len(), 2); - let info = branched_ssts.get(&11).unwrap(); - assert_eq!( - info.keys().sorted().cloned().collect_vec(), - vec![2, new_group_id] - ); assert_eq!( current_version.get_compaction_group_levels(2).levels[base_level - 1].table_infos[0] .object_id, @@ -2140,12 +2066,6 @@ async fn test_move_tables_between_compaction_group() { assert_eq!(level.table_infos[0].table_ids, vec![100]); assert_eq!(level.table_infos[1].table_ids, vec![100, 101]); assert_eq!(level.table_infos.len(), 2); - let branched_ssts = hummock_manager.get_branched_ssts_info().await; - // object-id 11 and 12. - assert_eq!(branched_ssts.len(), 2); - let info = branched_ssts.get(&12).unwrap(); - let groups = info.keys().sorted().cloned().collect_vec(); - assert_eq!(groups, vec![2, new_group_id]); let mut selector: Box = Box::::default(); @@ -2169,9 +2089,6 @@ async fn test_move_tables_between_compaction_group() { .await .unwrap(); assert!(ret); - let branched_ssts = hummock_manager.get_branched_ssts_info().await; - // there is still left one sst for object-12 in branched-sst. - assert_eq!(branched_ssts.len(), 2); } #[tokio::test] diff --git a/src/meta/src/hummock/manager/versioning.rs b/src/meta/src/hummock/manager/versioning.rs index 61b3399d3b958..464404b8f25c0 100644 --- a/src/meta/src/hummock/manager/versioning.rs +++ b/src/meta/src/hummock/manager/versioning.rs @@ -20,7 +20,7 @@ use risingwave_common::util::epoch::INVALID_EPOCH; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ build_initial_compaction_group_levels, get_compaction_group_ids, BranchedSstInfo, }; -use risingwave_hummock_sdk::compaction_group::{StateTableId, StaticCompactionGroupId}; +use risingwave_hummock_sdk::compaction_group::StaticCompactionGroupId; use risingwave_hummock_sdk::table_stats::add_prost_table_stats_map; use risingwave_hummock_sdk::version::{HummockVersion, HummockVersionDelta}; use risingwave_hummock_sdk::{ @@ -77,12 +77,6 @@ pub struct Versioning { pub disable_commit_epochs: bool, /// Latest hummock version pub current_version: HummockVersion, - /// SST whose `object_id` != `sst_id` - pub branched_ssts: BTreeMap< - // SST object id - HummockSstableObjectId, - BranchedSstInfo, - >, pub local_metrics: HashMap, // Persistent states below @@ -123,46 +117,6 @@ impl Versioning { .flat_map(|(_, stale_objects)| stale_objects.id.iter().cloned()), ); } - - /// If there is some sst in the target group which is just split but we have not compact it, we - /// can not split or move state-table to those group, because it may cause data overlap. - pub fn check_branched_sst_in_target_group( - &self, - table_ids: &[StateTableId], - source_group_id: &CompactionGroupId, - target_group_id: &CompactionGroupId, - ) -> bool { - for groups in self.branched_ssts.values() { - if groups.contains_key(target_group_id) && groups.contains_key(source_group_id) { - return false; - } - } - let mut found_sstable_repeated = false; - let moving_table_ids: HashSet<&u32> = HashSet::from_iter(table_ids); - if let Some(group) = self.current_version.levels.get(target_group_id) { - let target_member_table_ids: HashSet = - HashSet::from_iter(group.member_table_ids.clone()); - self.current_version.level_iter(*source_group_id, |level| { - for sst in &level.table_infos { - if sst - .table_ids - .iter() - .all(|table_id| !moving_table_ids.contains(table_id)) - { - continue; - } - for table_id in &sst.table_ids { - if target_member_table_ids.contains(table_id) { - found_sstable_repeated = true; - return false; - } - } - } - true - }); - } - !found_sstable_repeated - } } impl HummockManager { @@ -279,7 +233,7 @@ impl HummockManager { pub async fn list_branched_objects(&self) -> BTreeMap { let guard = self.versioning.read().await; - guard.branched_ssts.clone() + guard.current_version.build_branched_sst_info() } pub async fn rebuild_table_stats(&self) -> Result<()> { diff --git a/src/meta/src/hummock/metrics_utils.rs b/src/meta/src/hummock/metrics_utils.rs index be25c5cf452b2..32b4bed9abefb 100644 --- a/src/meta/src/hummock/metrics_utils.rs +++ b/src/meta/src/hummock/metrics_utils.rs @@ -20,14 +20,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; use itertools::{enumerate, Itertools}; use prometheus::core::{AtomicU64, GenericCounter}; use prometheus::IntGauge; -use risingwave_hummock_sdk::compaction_group::hummock_version_ext::{ - object_size_map, BranchedSstInfo, -}; +use risingwave_hummock_sdk::compaction_group::hummock_version_ext::object_size_map; use risingwave_hummock_sdk::table_stats::PbTableStatsMap; use risingwave_hummock_sdk::version::HummockVersion; -use risingwave_hummock_sdk::{ - CompactionGroupId, HummockContextId, HummockEpoch, HummockSstableObjectId, HummockVersionId, -}; +use risingwave_hummock_sdk::{CompactionGroupId, HummockContextId, HummockEpoch, HummockVersionId}; use risingwave_pb::hummock::hummock_version::Levels; use risingwave_pb::hummock::write_limits::WriteLimit; use risingwave_pb::hummock::{ @@ -555,36 +551,31 @@ pub fn trigger_write_stop_stats( } } -pub fn trigger_split_stat( - metrics: &MetaMetrics, - compaction_group_id: CompactionGroupId, - member_table_id_len: usize, - branched_ssts: &BTreeMap< - // SST object id - HummockSstableObjectId, - BranchedSstInfo, - >, -) { - let group_label = compaction_group_id.to_string(); - metrics - .state_table_count - .with_label_values(&[&group_label]) - .set(member_table_id_len as _); +pub fn trigger_split_stat(metrics: &MetaMetrics, version: &HummockVersion) { + let branched_ssts = version.build_branched_sst_info(); - let branched_sst_count: usize = branched_ssts - .values() - .map(|branched_map| { - branched_map - .keys() - .filter(|group_id| **group_id == compaction_group_id) - .count() - }) - .sum(); + for (compaction_group_id, group) in &version.levels { + let group_label = compaction_group_id.to_string(); + metrics + .state_table_count + .with_label_values(&[&group_label]) + .set(group.member_table_ids.len() as _); + + let branched_sst_count: usize = branched_ssts + .values() + .map(|branched_map| { + branched_map + .keys() + .filter(|group_id| *group_id == compaction_group_id) + .count() + }) + .sum(); - metrics - .branched_sst_count - .with_label_values(&[&group_label]) - .set(branched_sst_count as _); + metrics + .branched_sst_count + .with_label_values(&[&group_label]) + .set(branched_sst_count as _); + } } pub fn build_level_metrics_label(compaction_group_id: u64, level_idx: usize) -> String { diff --git a/src/meta/src/manager/catalog/database.rs b/src/meta/src/manager/catalog/database.rs index 1cf466caddde5..5b1b24e82de21 100644 --- a/src/meta/src/manager/catalog/database.rs +++ b/src/meta/src/manager/catalog/database.rs @@ -363,6 +363,14 @@ impl DatabaseManager { .collect_vec() } + pub fn list_view_ids(&self, schema_id: SchemaId) -> Vec { + self.views + .values() + .filter(|view| view.schema_id == schema_id) + .map(|view| view.id) + .collect_vec() + } + pub fn list_sources(&self) -> Vec { self.sources.values().cloned().collect_vec() } diff --git a/src/meta/src/manager/catalog/mod.rs b/src/meta/src/manager/catalog/mod.rs index 4eb2970fea858..040a91a1bc1e5 100644 --- a/src/meta/src/manager/catalog/mod.rs +++ b/src/meta/src/manager/catalog/mod.rs @@ -19,7 +19,6 @@ mod utils; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::iter; -use std::option::Option::Some; use std::sync::Arc; use anyhow::{anyhow, Context}; @@ -3608,6 +3607,10 @@ impl CatalogManager { .list_dml_table_ids(schema_id) } + pub async fn list_view_ids(&self, schema_id: SchemaId) -> Vec { + self.core.lock().await.database.list_view_ids(schema_id) + } + pub async fn list_sources(&self) -> Vec { self.core.lock().await.database.list_sources() } @@ -3698,6 +3701,13 @@ impl CatalogManager { } } + for subscription in core.subscriptions.values() { + dependencies.push(PbObjectDependencies { + object_id: subscription.id, + referenced_object_id: subscription.dependent_table_id, + }); + } + dependencies } @@ -3958,7 +3968,7 @@ impl CatalogManager { id ))); } - if user_core.catalog_create_ref_count.get(&id).is_some() { + if user_core.catalog_create_ref_count.contains_key(&id) { return Err(MetaError::permission_denied(format!( "User {} cannot be dropped because some objects depend on it", user.name diff --git a/src/meta/src/manager/env.rs b/src/meta/src/manager/env.rs index 5006f5864e84f..1d9ef9fc27452 100644 --- a/src/meta/src/manager/env.rs +++ b/src/meta/src/manager/env.rs @@ -275,7 +275,7 @@ pub struct MetaOpts { /// The maximum number of trivial move tasks to be picked in a single loop pub max_trivial_move_task_count_per_loop: usize, - /// The maximum number of times to probe for PullTaskEvent + /// The maximum number of times to probe for `PullTaskEvent` pub max_get_task_probe_times: usize, } diff --git a/src/meta/src/manager/metadata.rs b/src/meta/src/manager/metadata.rs index ffd791fd23b9d..241a47941755b 100644 --- a/src/meta/src/manager/metadata.rs +++ b/src/meta/src/manager/metadata.rs @@ -64,6 +64,7 @@ pub struct MetadataManagerV2 { #[derive(Debug)] pub(crate) enum ActiveStreamingWorkerChange { Add(WorkerNode), + #[expect(dead_code)] Remove(WorkerNode), Update(WorkerNode), } diff --git a/src/meta/src/rpc/election/sql.rs b/src/meta/src/rpc/election/sql.rs index 49dd0474d4975..9ec5bd199cf76 100644 --- a/src/meta/src/rpc/election/sql.rs +++ b/src/meta/src/rpc/election/sql.rs @@ -715,7 +715,7 @@ where break; } else if prev_leader != election_row.id { tracing::info!("leader is {}", election_row.id); - prev_leader = election_row.id.clone(); + prev_leader.clone_from(&election_row.id) } timeout_ticker.reset(); diff --git a/src/meta/src/storage/etcd_meta_store.rs b/src/meta/src/storage/etcd_meta_store.rs index 8c5f93f9dd9ac..1986dab6247d6 100644 --- a/src/meta/src/storage/etcd_meta_store.rs +++ b/src/meta/src/storage/etcd_meta_store.rs @@ -14,7 +14,6 @@ use std::sync::atomic::{self, AtomicI64}; -use anyhow; use async_trait::async_trait; use etcd_client::{Compare, CompareOp, Error as EtcdError, GetOptions, Txn, TxnOp}; use futures::Future; diff --git a/src/meta/src/stream/stream_manager.rs b/src/meta/src/stream/stream_manager.rs index 58c3b9add64c5..f55caa3ea67b0 100644 --- a/src/meta/src/stream/stream_manager.rs +++ b/src/meta/src/stream/stream_manager.rs @@ -778,13 +778,12 @@ impl GlobalStreamManager { #[cfg(test)] mod tests { - use std::collections::{BTreeMap, HashMap, HashSet}; + use std::collections::BTreeMap; use std::net::SocketAddr; - use std::sync::{Arc, Mutex}; + use std::sync::Mutex; use std::time::Duration; use futures::{Stream, TryStreamExt}; - use risingwave_common::catalog::TableId; use risingwave_common::hash::ParallelUnitMapping; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_pb::common::{HostAddress, WorkerType}; @@ -797,10 +796,7 @@ mod tests { StreamService, StreamServiceServer, }; use risingwave_pb::stream_service::streaming_control_stream_response::InitResponse; - use risingwave_pb::stream_service::{ - BroadcastActorInfoTableResponse, BuildActorsResponse, DropActorsRequest, - DropActorsResponse, UpdateActorsResponse, *, - }; + use risingwave_pb::stream_service::*; use tokio::spawn; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::oneshot::Sender; @@ -812,14 +808,14 @@ mod tests { use tonic::{Request, Response, Status, Streaming}; use super::*; - use crate::barrier::{GlobalBarrierManager, StreamRpcManager}; + use crate::barrier::GlobalBarrierManager; use crate::hummock::{CompactorManager, HummockManager}; use crate::manager::sink_coordination::SinkCoordinatorManager; use crate::manager::{ CatalogManager, CatalogManagerRef, ClusterManager, FragmentManager, FragmentManagerRef, - MetaSrvEnv, RelationIdEnum, StreamingClusterInfo, + RelationIdEnum, StreamingClusterInfo, }; - use crate::model::{ActorId, FragmentId}; + use crate::model::FragmentId; use crate::rpc::ddl_controller::DropMode; use crate::rpc::metrics::MetaMetrics; use crate::stream::{ScaleController, SourceManager}; diff --git a/src/object_store/Cargo.toml b/src/object_store/Cargo.toml index e4d573e8f4d2d..fa3f50964dcfa 100644 --- a/src/object_store/Cargo.toml +++ b/src/object_store/Cargo.toml @@ -23,14 +23,14 @@ crc32fast = "1" either = "1" fail = "0.5" futures = { version = "0.3", default-features = false, features = ["alloc"] } -hyper = { version = "0.14", features = ["tcp", "client"] } # required by aws sdk +hyper = { version = "0.14", features = ["tcp", "client"] } # required by aws sdk hyper-rustls = { version = "0.24.2", features = ["webpki-roots"] } hyper-tls = "0.5.0" itertools = { workspace = true } -madsim = "0.2.22" +madsim = "0.2.27" opendal = "0.45.1" prometheus = { version = "0.13", features = ["process"] } -reqwest = "0.11" # required by opendal +reqwest = "0.11" # required by opendal risingwave_common = { workspace = true } rustls = "0.23.5" spin = "0.9" diff --git a/src/object_store/src/object/error.rs b/src/object_store/src/object/error.rs index 38399a59a5a2f..aa79f53c4c065 100644 --- a/src/object_store/src/object/error.rs +++ b/src/object_store/src/object/error.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::io; -use std::marker::{Send, Sync}; use aws_sdk_s3::operation::get_object::GetObjectError; use aws_sdk_s3::operation::head_object::HeadObjectError; diff --git a/src/object_store/src/object/mem.rs b/src/object_store/src/object/mem.rs index 3a1a7ed655e81..b790a2cdecf5e 100644 --- a/src/object_store/src/object/mem.rs +++ b/src/object_store/src/object/mem.rs @@ -307,7 +307,6 @@ impl Stream for InMemObjectIter { #[cfg(test)] mod tests { - use bytes::Bytes; use futures::TryStreamExt; use itertools::enumerate; diff --git a/src/object_store/src/object/mod.rs b/src/object_store/src/object/mod.rs index 2f32cb7c0716d..125ae9b9d33bb 100644 --- a/src/object_store/src/object/mod.rs +++ b/src/object_store/src/object/mod.rs @@ -1023,7 +1023,7 @@ fn get_attempt_timeout_by_type(config: &ObjectStoreConfig, operation_type: Opera } OperationType::Metadata => config.retry.metadata_attempt_timeout_ms, OperationType::Delete => config.retry.delete_attempt_timeout_ms, - OperationType::DeleteObjects => config.retry.delete_attempt_timeout_ms, + OperationType::DeleteObjects => config.retry.delete_objects_attempt_timeout_ms, OperationType::List => config.retry.list_attempt_timeout_ms, } } diff --git a/src/object_store/src/object/opendal_engine/opendal_object_store.rs b/src/object_store/src/object/opendal_engine/opendal_object_store.rs index c073df7c1e102..e58fe6cf44192 100644 --- a/src/object_store/src/object/opendal_engine/opendal_object_store.rs +++ b/src/object_store/src/object/opendal_engine/opendal_object_store.rs @@ -307,8 +307,6 @@ impl StreamingUploader for OpendalStreamingUploader { #[cfg(test)] mod tests { - use bytes::Bytes; - use super::*; async fn list_all(prefix: &str, store: &OpendalObjectStore) -> Vec { diff --git a/src/object_store/src/object/s3.rs b/src/object_store/src/object/s3.rs index 6832b3a8e04da..d7c151e867465 100644 --- a/src/object_store/src/object/s3.rs +++ b/src/object_store/src/object/s3.rs @@ -1006,10 +1006,10 @@ where } => { let sdk_err = inner .as_ref() - .downcast_ref::>>() - .unwrap(); + .downcast_ref::>>(); + let err_should_retry = match sdk_err { - SdkError::DispatchFailure(e) => { + Some(SdkError::DispatchFailure(e)) => { if e.is_timeout() { tracing::warn!(target: "http_timeout_retry", "{e:?} occurs, retry S3 get_object request."); true @@ -1017,7 +1017,8 @@ where false } } - SdkError::ServiceError(e) => { + + Some(SdkError::ServiceError(e)) => { let retry = match e.err().code() { None => { if config.s3.developer.object_store_retry_unknown_service_error @@ -1048,7 +1049,7 @@ where retry } - SdkError::TimeoutError(_err) => true, + Some(SdkError::TimeoutError(_err)) => true, _ => false, }; diff --git a/src/prost/helpers/src/generate.rs b/src/prost/helpers/src/generate.rs index 432f233e74cb9..f674fec5aa2cf 100644 --- a/src/prost/helpers/src/generate.rs +++ b/src/prost/helpers/src/generate.rs @@ -17,8 +17,8 @@ use quote::quote; use syn::ext::IdentExt; use syn::spanned::Spanned; use syn::{ - self, Error, Expr, ExprLit, Field, GenericArgument, Lit, Meta, PathArguments, PathSegment, - Result, Type, + Error, Expr, ExprLit, Field, GenericArgument, Lit, Meta, PathArguments, PathSegment, Result, + Type, }; fn extract_type_from_option(option_segment: &PathSegment) -> Type { diff --git a/src/prost/src/lib.rs b/src/prost/src/lib.rs index 825e721891fed..d4f0359fadab2 100644 --- a/src/prost/src/lib.rs +++ b/src/prost/src/lib.rs @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// for derived code of `Message` #![expect(clippy::all)] #![expect(clippy::doc_markdown)] -#![allow(non_snake_case)] // for derived code of `Message` #![feature(lint_reasons)] use std::str::FromStr; diff --git a/src/risedevtool/gcloud-pubsub.toml b/src/risedevtool/gcloud-pubsub.toml index 313561b287cba..85b5eae3e170a 100644 --- a/src/risedevtool/gcloud-pubsub.toml +++ b/src/risedevtool/gcloud-pubsub.toml @@ -2,14 +2,16 @@ extend = "common.toml" [env] GCLOUD_DOWNLOAD_PATH = "${PREFIX_TMP}/gcloud.tgz" -GCLOUD_DOWNLOAD_TGZ = { value = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-406.0.0-linux-x86_64.tar.gz", condition = { env_not_set = [ "GCLOUD_DOWNLOAD_TGZ" ] } } +GCLOUD_DOWNLOAD_TGZ = { value = "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-475.0.0-linux-x86_64.tar.gz", condition = { env_not_set = [ + "GCLOUD_DOWNLOAD_TGZ", +] } } GCLOUD_SDK_DIR = "google-cloud-sdk" [tasks.download-pubsub] private = true category = "RiseDev - Components" dependencies = ["prepare"] -condition = { env_set = [ "ENABLE_PUBSUB" ] } +condition = { env_set = ["ENABLE_PUBSUB"] } description = "Download and enable Google Pubsub Emulator" script = ''' #!/usr/bin/env bash diff --git a/src/risedevtool/src/bin/risedev-compose.rs b/src/risedevtool/src/bin/risedev-compose.rs index 10b29e836c66d..ec805a840fa71 100644 --- a/src/risedevtool/src/bin/risedev-compose.rs +++ b/src/risedevtool/src/bin/risedev-compose.rs @@ -19,7 +19,7 @@ use std::path::Path; use anyhow::{anyhow, Result}; use clap::Parser; use console::style; -use fs_err::{self, File}; +use fs_err::File; use itertools::Itertools; use risedev::{ compose_deploy, generate_risedev_env, Compose, ComposeConfig, ComposeDeployConfig, ComposeFile, @@ -219,7 +219,7 @@ fn main() -> Result<()> { volumes.insert(c.id.clone(), ComposeVolume::default()); (c.address.clone(), c.compose(&compose_config)?) } - ServiceConfig::Redis(_) | ServiceConfig::MySql(_) => { + ServiceConfig::Redis(_) | ServiceConfig::MySql(_) | ServiceConfig::Postgres(_) => { return Err(anyhow!("not supported")) } }; diff --git a/src/risedevtool/src/bin/risedev-dev.rs b/src/risedevtool/src/bin/risedev-dev.rs index f11bc1b3b5148..4f636cb7e30ba 100644 --- a/src/risedevtool/src/bin/risedev-dev.rs +++ b/src/risedevtool/src/bin/risedev-dev.rs @@ -26,8 +26,9 @@ use risedev::util::{complete_spin, fail_spin}; use risedev::{ generate_risedev_env, preflight_check, CompactorService, ComputeNodeService, ConfigExpander, ConfigureTmuxTask, DummyService, EnsureStopService, ExecuteContext, FrontendService, - GrafanaService, KafkaService, MetaNodeService, MinioService, MySqlService, PrometheusService, - PubsubService, RedisService, ServiceConfig, SqliteConfig, Task, TempoService, RISEDEV_NAME, + GrafanaService, KafkaService, MetaNodeService, MinioService, MySqlService, PostgresService, + PrometheusService, PubsubService, RedisService, ServiceConfig, SqliteConfig, Task, + TempoService, RISEDEV_NAME, }; use tempfile::tempdir; use thiserror_ext::AsReport; @@ -314,6 +315,16 @@ fn task_main( ctx.pb .set_message(format!("mysql {}:{}", c.address, c.port)); } + ServiceConfig::Postgres(c) => { + let mut ctx = + ExecuteContext::new(&mut logger, manager.new_progress(), status_dir.clone()); + PostgresService::new(c.clone()).execute(&mut ctx)?; + let mut task = + risedev::ConfigureTcpNodeTask::new(c.address.clone(), c.port, c.user_managed)?; + task.execute(&mut ctx)?; + ctx.pb + .set_message(format!("postgres {}:{}", c.address, c.port)); + } } let service_id = service.id().to_string(); diff --git a/src/risedevtool/src/config.rs b/src/risedevtool/src/config.rs index 541f269ee18c4..839ebc22486ee 100644 --- a/src/risedevtool/src/config.rs +++ b/src/risedevtool/src/config.rs @@ -174,6 +174,7 @@ impl ConfigExpander { "redis" => ServiceConfig::Redis(serde_yaml::from_str(&out_str)?), "redpanda" => ServiceConfig::RedPanda(serde_yaml::from_str(&out_str)?), "mysql" => ServiceConfig::MySql(serde_yaml::from_str(&out_str)?), + "postgres" => ServiceConfig::Postgres(serde_yaml::from_str(&out_str)?), other => return Err(anyhow!("unsupported use type: {}", other)), }; Ok(result) diff --git a/src/risedevtool/src/risedev_env.rs b/src/risedevtool/src/risedev_env.rs index 24f18895434e9..b33a65f9986aa 100644 --- a/src/risedevtool/src/risedev_env.rs +++ b/src/risedevtool/src/risedev_env.rs @@ -91,6 +91,22 @@ pub fn generate_risedev_env(services: &Vec) -> String { // It's expected to create another dedicated user for the source. writeln!(env, r#"RISEDEV_MYSQL_WITH_OPTIONS_COMMON="connector='mysql-cdc',hostname='{host}',port='{port}'""#,).unwrap(); } + ServiceConfig::Pubsub(c) => { + let address = &c.address; + let port = &c.port; + writeln!(env, r#"RISEDEV_PUBSUB_WITH_OPTIONS_COMMON="connector='google_pubsub',pubsub.emulator_host='{address}:{port}'""#,).unwrap(); + } + ServiceConfig::Postgres(c) => { + let host = &c.address; + let port = &c.port; + let user = &c.user; + let password = &c.password; + // These envs are used by `postgres` cli. + writeln!(env, r#"PGHOST="{host}""#,).unwrap(); + writeln!(env, r#"PGPORT="{port}""#,).unwrap(); + writeln!(env, r#"PGUSER="{user}""#,).unwrap(); + writeln!(env, r#"PGPASSWORD="{password}""#,).unwrap(); + } _ => {} } } diff --git a/src/risedevtool/src/service_config.rs b/src/risedevtool/src/service_config.rs index 3135d7af4c009..4811ef96f5b87 100644 --- a/src/risedevtool/src/service_config.rs +++ b/src/risedevtool/src/service_config.rs @@ -60,8 +60,10 @@ pub struct MetaNodeConfig { pub user_managed: bool, + pub meta_backend: String, pub provide_etcd_backend: Option>, pub provide_sqlite_backend: Option>, + pub provide_postgres_backend: Option>, pub provide_prometheus: Option>, pub provide_compute_node: Option>, @@ -341,6 +343,26 @@ pub struct MySqlConfig { pub persist_data: bool, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +#[serde(deny_unknown_fields)] +pub struct PostgresConfig { + #[serde(rename = "use")] + phantom_use: Option, + pub id: String, + + pub port: u16, + pub address: String, + + pub user: String, + pub password: String, + pub database: String, + + pub image: String, + pub user_managed: bool, + pub persist_data: bool, +} + /// All service configuration #[derive(Clone, Debug, PartialEq)] pub enum ServiceConfig { @@ -361,6 +383,7 @@ pub enum ServiceConfig { Redis(RedisConfig), RedPanda(RedPandaConfig), MySql(MySqlConfig), + Postgres(PostgresConfig), } impl ServiceConfig { @@ -383,6 +406,7 @@ impl ServiceConfig { Self::RedPanda(c) => &c.id, Self::Opendal(c) => &c.id, Self::MySql(c) => &c.id, + ServiceConfig::Postgres(c) => &c.id, } } @@ -405,6 +429,7 @@ impl ServiceConfig { Self::RedPanda(_c) => None, Self::Opendal(_) => None, Self::MySql(c) => Some(c.port), + ServiceConfig::Postgres(c) => Some(c.port), } } @@ -427,6 +452,7 @@ impl ServiceConfig { Self::RedPanda(_c) => false, Self::Opendal(_c) => false, Self::MySql(c) => c.user_managed, + Self::Postgres(c) => c.user_managed, } } } diff --git a/src/risedevtool/src/task.rs b/src/risedevtool/src/task.rs index 24474d14c600f..fbabc6a27d226 100644 --- a/src/risedevtool/src/task.rs +++ b/src/risedevtool/src/task.rs @@ -25,6 +25,7 @@ mod kafka_service; mod meta_node_service; mod minio_service; mod mysql_service; +mod postgres_service; mod prometheus_service; mod pubsub_service; mod redis_service; @@ -62,6 +63,7 @@ pub use self::kafka_service::*; pub use self::meta_node_service::*; pub use self::minio_service::*; pub use self::mysql_service::*; +pub use self::postgres_service::*; pub use self::prometheus_service::*; pub use self::pubsub_service::*; pub use self::redis_service::*; diff --git a/src/risedevtool/src/task/meta_node_service.rs b/src/risedevtool/src/task/meta_node_service.rs index 42d555f48b3d8..a1bbd2c04e6ca 100644 --- a/src/risedevtool/src/task/meta_node_service.rs +++ b/src/risedevtool/src/task/meta_node_service.rs @@ -77,35 +77,58 @@ impl MetaNodeService { let mut is_persistent_meta_store = false; - if let Some(sqlite_config) = &config.provide_sqlite_backend - && !sqlite_config.is_empty() - { - is_persistent_meta_store = true; - let prefix_data = env::var("PREFIX_DATA")?; - let file_path = PathBuf::from(&prefix_data) - .join(&sqlite_config[0].id) - .join(&sqlite_config[0].file); - cmd.arg("--backend") - .arg("sql") - .arg("--sql-endpoint") - .arg(format!("sqlite://{}?mode=rwc", file_path.display())); - } else { - match config.provide_etcd_backend.as_ref().unwrap().as_slice() { - [] => { - cmd.arg("--backend").arg("mem"); - } - etcds => { - is_persistent_meta_store = true; - cmd.arg("--backend") - .arg("etcd") - .arg("--etcd-endpoints") - .arg( - etcds - .iter() - .map(|etcd| format!("{}:{}", etcd.address, etcd.port)) - .join(","), - ); - } + match config.meta_backend.to_ascii_lowercase().as_str() { + "memory" => { + cmd.arg("--backend").arg("mem"); + } + "etcd" => { + let etcd_config = config.provide_etcd_backend.as_ref().unwrap(); + assert!(!etcd_config.is_empty()); + is_persistent_meta_store = true; + + cmd.arg("--backend") + .arg("etcd") + .arg("--etcd-endpoints") + .arg( + etcd_config + .iter() + .map(|etcd| format!("{}:{}", etcd.address, etcd.port)) + .join(","), + ); + } + "sqlite" => { + let sqlite_config = config.provide_sqlite_backend.as_ref().unwrap(); + assert_eq!(sqlite_config.len(), 1); + is_persistent_meta_store = true; + + let prefix_data = env::var("PREFIX_DATA")?; + let file_path = PathBuf::from(&prefix_data) + .join(&sqlite_config[0].id) + .join(&sqlite_config[0].file); + cmd.arg("--backend") + .arg("sql") + .arg("--sql-endpoint") + .arg(format!("sqlite://{}?mode=rwc", file_path.display())); + } + "postgres" => { + let pg_config = config.provide_postgres_backend.as_ref().unwrap(); + assert_eq!(pg_config.len(), 1); + is_persistent_meta_store = true; + + cmd.arg("--backend") + .arg("sql") + .arg("--sql-endpoint") + .arg(format!( + "postgres://{}:{}@{}:{}/{}", + pg_config[0].user, + pg_config[0].password, + pg_config[0].address, + pg_config[0].port, + pg_config[0].database + )); + } + backend => { + return Err(anyhow!("unsupported meta backend {}", backend)); } } diff --git a/src/risedevtool/src/task/postgres_service.rs b/src/risedevtool/src/task/postgres_service.rs new file mode 100644 index 0000000000000..4a6d6571c82cc --- /dev/null +++ b/src/risedevtool/src/task/postgres_service.rs @@ -0,0 +1,55 @@ +// Copyright 2024 RisingWave Labs +// +// Licensed 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. + +use crate::task::docker_service::{DockerService, DockerServiceConfig}; +use crate::PostgresConfig; + +impl DockerServiceConfig for PostgresConfig { + fn id(&self) -> String { + self.id.clone() + } + + fn is_user_managed(&self) -> bool { + self.user_managed + } + + fn image(&self) -> String { + self.image.clone() + } + + fn envs(&self) -> Vec<(String, String)> { + vec![ + ("POSTGRES_HOST_AUTH_METHOD".to_owned(), "trust".to_owned()), + ("POSTGRES_USER".to_owned(), self.user.clone()), + ("POSTGRES_PASSWORD".to_owned(), self.password.clone()), + ("POSTGRES_DB".to_owned(), self.database.clone()), + ( + "POSTGRES_INITDB_ARGS".to_owned(), + "--encoding=UTF-8 --lc-collate=C --lc-ctype=C".to_owned(), + ), + ] + } + + fn ports(&self) -> Vec<(String, String)> { + vec![(format!("{}:{}", self.address, self.port), "5432".to_owned())] + } + + fn data_path(&self) -> Option { + self.persist_data + .then(|| "/var/lib/postgresql/data".to_owned()) + } +} + +/// Docker-backed PostgreSQL service. +pub type PostgresService = DockerService; diff --git a/src/rpc_client/src/connector_client.rs b/src/rpc_client/src/connector_client.rs index f4a87e3c1f5b4..3042394b25877 100644 --- a/src/rpc_client/src/connector_client.rs +++ b/src/rpc_client/src/connector_client.rs @@ -278,7 +278,6 @@ impl ConnectorClient { &self, payload_schema: Option, sink_proto: PbSinkParam, - sink_payload_format: SinkPayloadFormat, ) -> Result { let mut rpc_client = self.rpc_client.clone(); let (handle, first_rsp) = SinkWriterStreamHandle::initialize( @@ -286,7 +285,6 @@ impl ConnectorClient { request: Some(SinkRequest::Start(StartSink { payload_schema, sink_param: Some(sink_proto), - format: sink_payload_format as i32, })), }, |rx| async move { diff --git a/src/sqlparser/src/ast/statement.rs b/src/sqlparser/src/ast/statement.rs index 24950f19aadb9..aef8ec417f605 100644 --- a/src/sqlparser/src/ast/statement.rs +++ b/src/sqlparser/src/ast/statement.rs @@ -402,9 +402,13 @@ impl ParseTo for CreateSourceStatement { .iter() .find(|&opt| opt.name.real_value() == UPSTREAM_SOURCE_KEY); let connector: String = option.map(|opt| opt.value.to_string()).unwrap_or_default(); - // The format of cdc source job is fixed to `FORMAT PLAIN ENCODE JSON` - let cdc_source_job = - connector.contains("-cdc") && columns.is_empty() && constraints.is_empty(); + let cdc_source_job = connector.contains("-cdc"); + if cdc_source_job && (!columns.is_empty() || !constraints.is_empty()) { + return Err(ParserError::ParserError( + "CDC source cannot define columns and constraints".to_string(), + )); + } + // row format for nexmark source must be native // default row format for datagen source is native let source_schema = p.parse_source_schema_with_connector(&connector, cdc_source_job)?; diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index 05217fd53a723..84b1e1d97808d 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -25,12 +25,7 @@ use core::fmt; use itertools::Itertools; use tracing::{debug, instrument}; -use self::ddl::AlterSubscriptionOperation; -use crate::ast::ddl::{ - AlterConnectionOperation, AlterDatabaseOperation, AlterFunctionOperation, AlterIndexOperation, - AlterSchemaOperation, AlterSinkOperation, AlterViewOperation, SourceWatermark, -}; -use crate::ast::{ParseTo, *}; +use crate::ast::*; use crate::keywords::{self, Keyword}; use crate::tokenizer::*; @@ -180,12 +175,6 @@ pub struct Parser { /// Since we cannot distinguish `>>` and double `>`, so use `angle_brackets_num` to store the /// number of `<` to match `>` in sql like `struct>`. angle_brackets_num: i32, - /// It's important that already in named Array or not. so use this field check in or not. - /// Consider 0 is you're not in named Array. if more than 0 is you're in named Array - array_depth: usize, - /// We cannot know current array should be keep named or not, so by using this field store - /// every depth of array that should be keep named or not. - array_named_stack: Vec, } impl Parser { @@ -195,8 +184,6 @@ impl Parser { tokens, index: 0, angle_brackets_num: 0, - array_depth: 0, - array_named_stack: Vec::new(), } } @@ -312,25 +299,6 @@ impl Parser { Ok(Statement::Analyze { table_name }) } - /// Check is enter array expression. - pub fn peek_array_depth(&self) -> usize { - self.array_depth - } - - /// When enter specify ARRAY prefix expression. - pub fn increase_array_depth(&mut self, num: usize) { - self.array_depth += num; - } - - /// When exit specify ARRAY prefix expression. - pub fn decrease_array_depth(&mut self, num: usize) { - self.array_depth -= num; - } - - pub fn is_in_array(&self) -> bool { - self.peek_array_depth() > 0 - } - /// Tries to parse a wildcard expression. If it is not a wildcard, parses an expression. /// /// A wildcard expression either means: @@ -596,10 +564,6 @@ impl Parser { expr: Box::new(self.parse_subexpr(Precedence::UnaryNot)?), }), Keyword::ROW => self.parse_row_expr(), - Keyword::ARRAY if self.peek_token() == Token::LBracket => { - self.expect_token(&Token::LBracket)?; - self.parse_array_expr(true) - } Keyword::ARRAY if self.peek_token() == Token::LParen => { // similar to `exists(subquery)` self.expect_token(&Token::LParen)?; @@ -607,6 +571,7 @@ impl Parser { self.expect_token(&Token::RParen)?; Ok(exists_node) } + Keyword::ARRAY if self.peek_token() == Token::LBracket => self.parse_array_expr(), // `LEFT` and `RIGHT` are reserved as identifier but okay as function Keyword::LEFT | Keyword::RIGHT => { self.parse_function(ObjectName(vec![w.to_ident()?])) @@ -669,8 +634,6 @@ impl Parser { }, }, // End of Token::Word - Token::LBracket if self.is_in_array() => self.parse_array_expr(false), - tok @ Token::Minus | tok @ Token::Plus => { let op = if tok == Token::Plus { UnaryOperator::Plus @@ -1242,46 +1205,50 @@ impl Parser { } /// Parses an array expression `[ex1, ex2, ..]` - /// if `named` is `true`, came from an expression like `ARRAY[ex1, ex2]` - pub fn parse_array_expr(&mut self, named: bool) -> Result { - self.increase_array_depth(1); - if self.array_named_stack.len() < self.peek_array_depth() { - self.array_named_stack.push(named); - } else if let Err(parse_err) = self.check_same_named_array(named) { - Err(parse_err)? - } - - if self.peek_token() == Token::RBracket { - let _ = self.next_token(); // consume ] - self.decrease_array_depth(1); - Ok(Expr::Array(Array { - elem: vec![], - named, - })) - } else { - let exprs = self.parse_comma_separated(Parser::parse_expr)?; - self.expect_token(&Token::RBracket)?; - if self.array_named_stack.len() > self.peek_array_depth() { - self.array_named_stack.pop(); - } - self.decrease_array_depth(1); - Ok(Expr::Array(Array { elem: exprs, named })) - } + pub fn parse_array_expr(&mut self) -> Result { + let mut expected_depth = None; + let exprs = self.parse_array_inner(0, &mut expected_depth)?; + Ok(Expr::Array(Array { + elem: exprs, + // Top-level array is named. + named: true, + })) } - fn check_same_named_array(&mut self, current_named: bool) -> Result<(), ParserError> { - let previous_named = self.array_named_stack.last().unwrap(); - if current_named != *previous_named { - // for '[' - self.prev_token(); - if current_named { - // for keyword 'array' - self.prev_token(); - } - parser_err!(format!("syntax error at or near {}", self.peek_token()))? + fn parse_array_inner( + &mut self, + depth: usize, + expected_depth: &mut Option, + ) -> Result, ParserError> { + self.expect_token(&Token::LBracket)?; + if let Some(expected_depth) = *expected_depth + && depth > expected_depth + { + return self.expected("]", self.peek_token()); + } + let exprs = if self.peek_token() == Token::LBracket { + self.parse_comma_separated(|parser| { + let exprs = parser.parse_array_inner(depth + 1, expected_depth)?; + Ok(Expr::Array(Array { + elem: exprs, + named: false, + })) + })? } else { - Ok(()) - } + if let Some(expected_depth) = *expected_depth { + if depth < expected_depth { + return self.expected("[", self.peek_token()); + } + } else { + *expected_depth = Some(depth); + } + if self.consume_token(&Token::RBracket) { + return Ok(vec![]); + } + self.parse_comma_separated(Self::parse_expr)? + }; + self.expect_token(&Token::RBracket)?; + Ok(exprs) } // This function parses date/time fields for interval qualifiers. diff --git a/src/sqlparser/tests/sqlparser_common.rs b/src/sqlparser/tests/sqlparser_common.rs index ff0e9d2e53c40..ac6a1d310944a 100644 --- a/src/sqlparser/tests/sqlparser_common.rs +++ b/src/sqlparser/tests/sqlparser_common.rs @@ -25,7 +25,6 @@ use risingwave_sqlparser::ast::*; use risingwave_sqlparser::keywords::ALL_KEYWORDS; use risingwave_sqlparser::parser::ParserError; use risingwave_sqlparser::test_utils::*; -use test_utils::{expr_from_projection, join, number, only, table, table_alias}; #[test] fn parse_insert_values() { diff --git a/src/sqlparser/tests/sqlparser_postgres.rs b/src/sqlparser/tests/sqlparser_postgres.rs index d70625c33d770..55e4eefe0e406 100644 --- a/src/sqlparser/tests/sqlparser_postgres.rs +++ b/src/sqlparser/tests/sqlparser_postgres.rs @@ -1119,41 +1119,19 @@ fn parse_array() { ); let sql = "SELECT ARRAY[ARRAY[1, 2], [3, 4]]"; - assert_eq!( - parse_sql_statements(sql), - Err(ParserError::ParserError( - "syntax error at or near [ at line:1, column:28".to_string() - )) - ); + assert!(parse_sql_statements(sql).is_err()); let sql = "SELECT ARRAY[ARRAY[], []]"; - assert_eq!( - parse_sql_statements(sql), - Err(ParserError::ParserError( - "syntax error at or near [ at line:1, column:24".to_string() - )) - ); + assert!(parse_sql_statements(sql).is_err()); let sql = "SELECT ARRAY[[1, 2], ARRAY[3, 4]]"; - assert_eq!( - parse_sql_statements(sql), - Err(ParserError::ParserError( - "syntax error at or near ARRAY at line:1, column:27".to_string() - )) - ); + assert!(parse_sql_statements(sql).is_err()); let sql = "SELECT ARRAY[[], ARRAY[]]"; - assert_eq!( - parse_sql_statements(sql), - Err(ParserError::ParserError( - "syntax error at or near ARRAY at line:1, column:23".to_string() - )) - ); + assert!(parse_sql_statements(sql).is_err()); let sql = "SELECT [[1, 2], [3, 4]]"; - let res = parse_sql_statements(sql); - let err_msg = "Expected an expression:, found: ["; - assert!(format!("{}", res.unwrap_err()).contains(err_msg)); + assert!(parse_sql_statements(sql).is_err()); } #[test] diff --git a/src/sqlparser/tests/test_utils/mod.rs b/src/sqlparser/tests/test_utils/mod.rs index 6df188a90ff29..c72dbc33d0635 100644 --- a/src/sqlparser/tests/test_utils/mod.rs +++ b/src/sqlparser/tests/test_utils/mod.rs @@ -11,6 +11,7 @@ // limitations under the License. // Re-export everything from `src/test_utils.rs`. +#[allow(unused_imports)] pub use risingwave_sqlparser::test_utils::*; // For the test-only macros we take a different approach of keeping them here diff --git a/src/sqlparser/tests/testdata/array.yaml b/src/sqlparser/tests/testdata/array.yaml index aa655652b2b7f..9af94c041fdcb 100644 --- a/src/sqlparser/tests/testdata/array.yaml +++ b/src/sqlparser/tests/testdata/array.yaml @@ -25,3 +25,41 @@ formatted_sql: SELECT (CAST(ARRAY[ARRAY[2, 3]] AS INT[][]))[1][2] - input: SELECT ARRAY[] formatted_sql: SELECT ARRAY[] +- input: SELECT ARRAY[[1,2],[3,4]] + formatted_sql: SELECT ARRAY[[1, 2], [3, 4]] +- input: SELECT ARRAY[ARRAY[1,2],ARRAY[3,4]] + formatted_sql: SELECT ARRAY[ARRAY[1, 2], ARRAY[3, 4]] +- input: SELECT ARRAY[[],[]] + formatted_sql: SELECT ARRAY[[], []] +- input: SELECT ARRAY[ARRAY[],[]] + error_msg: |- + sql parser error: Expected an expression:, found: [ at line:1, column:23 + Near "SELECT ARRAY[ARRAY[],[" +- input: SELECT ARRAY[[],ARRAY[]] + error_msg: |- + sql parser error: Expected [, found: ARRAY at line:1, column:22 + Near "SELECT ARRAY[[]," +- input: SELECT ARRAY[[1,2],3] + error_msg: |- + sql parser error: Expected [, found: 3 at line:1, column:21 + Near "SELECT ARRAY[[1,2]," +- input: SELECT ARRAY[1,[2,3]] + error_msg: |- + sql parser error: Expected an expression:, found: [ at line:1, column:17 + Near "SELECT ARRAY[1,[" +- input: SELECT ARRAY[ARRAY[1,2],[3,4]] + error_msg: |- + sql parser error: Expected an expression:, found: [ at line:1, column:26 + Near "ARRAY[ARRAY[1,2],[" +- input: SELECT ARRAY[[1,2],ARRAY[3,4]] + error_msg: |- + sql parser error: Expected [, found: ARRAY at line:1, column:25 + Near "SELECT ARRAY[[1,2]," +- input: SELECT ARRAY[[1,2],[3] || [4]] + error_msg: |- + sql parser error: Expected ], found: || at line:1, column:25 + Near "[[1,2],[3]" +- input: SELECT [1,2] + error_msg: |- + sql parser error: Expected an expression:, found: [ at line:1, column:9 + Near "SELECT [" diff --git a/src/storage/Cargo.toml b/src/storage/Cargo.toml index 76004ef7b3f75..844f915192e36 100644 --- a/src/storage/Cargo.toml +++ b/src/storage/Cargo.toml @@ -14,7 +14,7 @@ ignored = ["workspace-hack"] normal = ["workspace-hack"] [dependencies] -ahash = "0.8" +ahash = "0.8.7" anyhow = "1" arc-swap = "1" async-trait = "0.1" @@ -52,6 +52,8 @@ risingwave_object_store = { workspace = true } risingwave_pb = { workspace = true } risingwave_rpc_client = { workspace = true } scopeguard = "1" +serde = { version = "1", features = ["derive"] } +serde_bytes = "0.11" sled = "0.34.7" spin = "0.9" sync-point = { path = "../utils/sync-point" } @@ -89,6 +91,7 @@ mach2 = "0.4" workspace-hack = { path = "../workspace-hack" } [dev-dependencies] +bincode = "1" criterion = { workspace = true, features = ["async_futures", "async_tokio"] } expect-test = "1" moka = { version = "0.12", features = ["future"] } diff --git a/src/storage/benches/bench_block_cache.rs b/src/storage/benches/bench_block_cache.rs index 497d532732daf..260bd409bc4f7 100644 --- a/src/storage/benches/bench_block_cache.rs +++ b/src/storage/benches/bench_block_cache.rs @@ -21,14 +21,15 @@ use std::time::{Duration, Instant}; use async_trait::async_trait; use bytes::{BufMut, Bytes, BytesMut}; use criterion::{criterion_group, criterion_main, Criterion}; -use foyer::memory as foyer; use moka::future::Cache; use rand::rngs::SmallRng; use rand::{RngCore, SeedableRng}; use risingwave_common::cache::CachePriority; use risingwave_storage::hummock::{HummockError, HummockResult, LruCache}; +use serde::{Deserialize, Serialize}; use tokio::runtime::{Builder, Runtime}; +#[derive(Debug, Serialize, Deserialize)] pub struct Block { sst: u64, offset: u64, @@ -123,16 +124,13 @@ pub struct FoyerCache { impl FoyerCache { pub fn lru(capacity: usize, fake_io_latency: Duration) -> Self { - let inner = foyer::Cache::lru(foyer::LruCacheConfig { - capacity, - shards: 8, - eviction_config: foyer::LruConfig { - high_priority_pool_ratio: 0.0, - }, - object_pool_capacity: 8 * 1024, - hash_builder: ahash::RandomState::default(), - event_listener: foyer::DefaultCacheEventListener::default(), - }); + let inner = foyer::CacheBuilder::new(capacity) + .with_shards(8) + .with_eviction_config(foyer::LruConfig { + high_priority_pool_ratio: 0.8, + }) + .with_object_pool_capacity(8 * 1024) + .build(); Self { inner, fake_io_latency, @@ -140,19 +138,16 @@ impl FoyerCache { } pub fn lfu(capacity: usize, fake_io_latency: Duration) -> Self { - let inner = foyer::Cache::lfu(foyer::LfuCacheConfig { - capacity, - shards: 8, - eviction_config: foyer::LfuConfig { + let inner = foyer::CacheBuilder::new(capacity) + .with_shards(8) + .with_eviction_config(foyer::LfuConfig { window_capacity_ratio: 0.1, protected_capacity_ratio: 0.8, cmsketch_eps: 0.001, cmsketch_confidence: 0.9, - }, - object_pool_capacity: 8 * 1024, - hash_builder: ahash::RandomState::default(), - event_listener: foyer::DefaultCacheEventListener::default(), - }); + }) + .with_object_pool_capacity(8 * 1024) + .build(); Self { inner, fake_io_latency, @@ -170,7 +165,7 @@ impl CacheBase for FoyerCache { async move { get_fake_block(sst_object_id, block_idx, latency) .await - .map(|block| (Arc::new(block), 1, foyer::CacheContext::Default)) + .map(|block| (Arc::new(block), foyer::CacheContext::Default)) } }) .await?; @@ -178,6 +173,72 @@ impl CacheBase for FoyerCache { } } +pub struct FoyerHybridCache { + inner: foyer::HybridCache<(u64, u64), Arc>, + fake_io_latency: Duration, +} + +impl FoyerHybridCache { + pub async fn lru(capacity: usize, fake_io_latency: Duration) -> Self { + let inner = foyer::HybridCacheBuilder::new() + .memory(capacity) + .with_shards(8) + .with_eviction_config(foyer::LruConfig { + high_priority_pool_ratio: 0.8, + }) + .with_object_pool_capacity(8 * 1024) + .storage() + .build() + .await + .unwrap(); + Self { + inner, + fake_io_latency, + } + } + + pub async fn lfu(capacity: usize, fake_io_latency: Duration) -> Self { + let inner = foyer::HybridCacheBuilder::new() + .memory(capacity) + .with_shards(8) + .with_eviction_config(foyer::LfuConfig { + window_capacity_ratio: 0.1, + protected_capacity_ratio: 0.8, + cmsketch_eps: 0.001, + cmsketch_confidence: 0.9, + }) + .with_object_pool_capacity(8 * 1024) + .storage() + .build() + .await + .unwrap(); + Self { + inner, + fake_io_latency, + } + } +} + +#[async_trait] +impl CacheBase for FoyerHybridCache { + async fn try_get_with(&self, sst_object_id: u64, block_idx: u64) -> HummockResult> { + let entry = self + .inner + .entry((sst_object_id, block_idx), || { + let latency = self.fake_io_latency; + async move { + get_fake_block(sst_object_id, block_idx, latency) + .await + .map(|block| (Arc::new(block), foyer::CacheContext::Default)) + .map_err(anyhow::Error::from) + } + }) + .await + .map_err(HummockError::foyer_error)?; + Ok(entry.value().clone()) + } +} + static IO_COUNT: AtomicUsize = AtomicUsize::new(0); async fn get_fake_block(sst: u64, offset: u64, io_latency: Duration) -> HummockResult { @@ -260,6 +321,20 @@ fn bench_block_cache(c: &mut Criterion) { bench_cache(block_cache, c, 10000); let block_cache = Arc::new(FoyerCache::lfu(2048, Duration::from_millis(0))); bench_cache(block_cache, c, 10000); + let block_cache = Arc::new( + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(FoyerHybridCache::lru(2048, Duration::from_millis(0))), + ); + bench_cache(block_cache, c, 10000); + let block_cache = Arc::new( + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(FoyerHybridCache::lfu(2048, Duration::from_millis(0))), + ); + bench_cache(block_cache, c, 10000); let block_cache = Arc::new(MokaCache::new(2048, Duration::from_millis(1))); bench_cache(block_cache, c, 1000); @@ -269,6 +344,20 @@ fn bench_block_cache(c: &mut Criterion) { bench_cache(block_cache, c, 1000); let block_cache = Arc::new(FoyerCache::lfu(2048, Duration::from_millis(1))); bench_cache(block_cache, c, 1000); + let block_cache = Arc::new( + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(FoyerHybridCache::lru(2048, Duration::from_millis(1))), + ); + bench_cache(block_cache, c, 1000); + let block_cache = Arc::new( + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(FoyerHybridCache::lfu(2048, Duration::from_millis(1))), + ); + bench_cache(block_cache, c, 1000); let block_cache = Arc::new(MokaCache::new(256, Duration::from_millis(10))); bench_cache(block_cache, c, 200); @@ -278,6 +367,20 @@ fn bench_block_cache(c: &mut Criterion) { bench_cache(block_cache, c, 200); let block_cache = Arc::new(FoyerCache::lfu(256, Duration::from_millis(10))); bench_cache(block_cache, c, 200); + let block_cache = Arc::new( + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(FoyerHybridCache::lru(256, Duration::from_millis(10))), + ); + bench_cache(block_cache, c, 200); + let block_cache = Arc::new( + tokio::runtime::Builder::new_current_thread() + .build() + .unwrap() + .block_on(FoyerHybridCache::lfu(256, Duration::from_millis(10))), + ); + bench_cache(block_cache, c, 200); } criterion_group!(benches, bench_block_cache); diff --git a/src/storage/benches/bench_compactor.rs b/src/storage/benches/bench_compactor.rs index 602617b31ea13..085fb722ec604 100644 --- a/src/storage/benches/bench_compactor.rs +++ b/src/storage/benches/bench_compactor.rs @@ -17,9 +17,9 @@ use std::sync::Arc; use criterion::async_executor::FuturesExecutor; use criterion::{criterion_group, criterion_main, Criterion}; -use foyer::memory::CacheContext; +use foyer::{CacheContext, HybridCacheBuilder}; use risingwave_common::catalog::{ColumnDesc, ColumnId, TableId}; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_common::hash::VirtualNode; use risingwave_common::row::OwnedRow; use risingwave_common::types::DataType; @@ -44,35 +44,45 @@ use risingwave_storage::hummock::sstable::SstableIteratorReadOptions; use risingwave_storage::hummock::sstable_store::SstableStoreRef; use risingwave_storage::hummock::value::HummockValue; use risingwave_storage::hummock::{ - CachePolicy, FileCache, SstableBuilder, SstableBuilderOptions, SstableIterator, SstableStore, + CachePolicy, SstableBuilder, SstableBuilderOptions, SstableIterator, SstableStore, SstableStoreConfig, SstableWriterOptions, Xor16FilterBuilder, }; use risingwave_storage::monitor::{ global_hummock_state_store_metrics, CompactorMetrics, StoreLocalStatistic, }; -pub fn mock_sstable_store() -> SstableStoreRef { +pub async fn mock_sstable_store() -> SstableStoreRef { let store = InMemObjectStore::new().monitored( Arc::new(ObjectStoreMetrics::unused()), Arc::new(ObjectStoreConfig::default()), ); let store = Arc::new(ObjectStoreImpl::InMem(store)); let path = "test".to_string(); + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(64 << 20) + .with_shards(2) + .storage() + .build() + .await + .unwrap(); + let block_cache_v2 = HybridCacheBuilder::new() + .memory(128 << 20) + .with_shards(2) + .storage() + .build() + .await + .unwrap(); Arc::new(SstableStore::new(SstableStoreConfig { store, path, - block_cache_capacity: 64 << 20, - meta_cache_capacity: 128 << 20, - meta_cache_shard_num: 2, - block_cache_shard_num: 2, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_eviction: EvictionConfig::for_test(), + prefetch_buffer_capacity: 64 << 20, max_prefetch_block_number: 16, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: Arc::new(global_hummock_state_store_metrics(MetricLevel::Disabled)), + + meta_cache_v2, + block_cache_v2, })) } @@ -213,11 +223,11 @@ async fn scan_all_table(info: &SstableInfo, sstable_store: SstableStoreRef) { fn bench_table_build(c: &mut Criterion) { c.bench_function("bench_table_build", |b| { - let sstable_store = mock_sstable_store(); let runtime = tokio::runtime::Builder::new_current_thread() .enable_time() .build() .unwrap(); + let sstable_store = runtime.block_on(mock_sstable_store()); b.to_async(&runtime).iter(|| async { build_table(sstable_store.clone(), 0, 0..(MAX_KEY_COUNT as u64), 1).await; }); @@ -225,11 +235,11 @@ fn bench_table_build(c: &mut Criterion) { } fn bench_table_scan(c: &mut Criterion) { - let sstable_store = mock_sstable_store(); let runtime = tokio::runtime::Builder::new_current_thread() .enable_time() .build() .unwrap(); + let sstable_store = runtime.block_on(mock_sstable_store()); let info = runtime.block_on(async { build_table(sstable_store.clone(), 0, 0..(MAX_KEY_COUNT as u64), 1).await }); @@ -289,7 +299,7 @@ fn bench_merge_iterator_compactor(c: &mut Criterion) { .enable_time() .build() .unwrap(); - let sstable_store = mock_sstable_store(); + let sstable_store = runtime.block_on(mock_sstable_store()); let test_key_size = 256 * 1024; let info1 = runtime .block_on(async { build_table(sstable_store.clone(), 1, 0..test_key_size / 2, 1).await }); @@ -365,7 +375,7 @@ fn bench_drop_column_compaction_impl(c: &mut Criterion, column_num: usize) { .enable_time() .build() .unwrap(); - let sstable_store = mock_sstable_store(); + let sstable_store = runtime.block_on(mock_sstable_store()); let test_key_size = 256 * 1024; let info1 = runtime.block_on(async { build_table_2( diff --git a/src/storage/benches/bench_fs_operation.rs b/src/storage/benches/bench_fs_operation.rs index 0983883fdaa44..8d0b20987686c 100644 --- a/src/storage/benches/bench_fs_operation.rs +++ b/src/storage/benches/bench_fs_operation.rs @@ -217,6 +217,7 @@ fn gen_std_files(path: &Path) -> impl IntoIterator { .read(true) .write(true) .create(true) + .truncate(true) .open(file_path) .unwrap(); ret diff --git a/src/storage/benches/bench_multi_builder.rs b/src/storage/benches/bench_multi_builder.rs index e8b43abdac7b9..e0c60df8e22eb 100644 --- a/src/storage/benches/bench_multi_builder.rs +++ b/src/storage/benches/bench_multi_builder.rs @@ -20,17 +20,18 @@ use std::sync::Arc; use std::time::Duration; use criterion::{criterion_group, criterion_main, Criterion}; +use foyer::HybridCacheBuilder; use futures::future::try_join_all; use itertools::Itertools; use risingwave_common::catalog::TableId; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_hummock_sdk::key::{FullKey, UserKey}; use risingwave_object_store::object::{ObjectStore, ObjectStoreImpl, S3ObjectStore}; use risingwave_storage::hummock::multi_builder::{CapacitySplitTableBuilder, TableBuilderFactory}; use risingwave_storage::hummock::value::HummockValue; use risingwave_storage::hummock::{ - BatchSstableWriterFactory, CachePolicy, FileCache, HummockResult, MemoryLimiter, - SstableBuilder, SstableBuilderOptions, SstableStore, SstableStoreConfig, SstableWriterFactory, + BatchSstableWriterFactory, CachePolicy, HummockResult, MemoryLimiter, SstableBuilder, + SstableBuilderOptions, SstableStore, SstableStoreConfig, SstableWriterFactory, SstableWriterOptions, StreamingSstableWriterFactory, Xor16FilterBuilder, }; use risingwave_storage::monitor::{global_hummock_state_store_metrics, ObjectStoreMetrics}; @@ -131,6 +132,7 @@ fn bench_builder( .unwrap(); let metrics = Arc::new(ObjectStoreMetrics::unused()); + let default_config = Arc::new(ObjectStoreConfig::default()); let object_store = runtime.block_on(async { S3ObjectStore::new_with_config(bucket.to_string(), metrics.clone(), default_config.clone()) @@ -138,22 +140,35 @@ fn bench_builder( .monitored(metrics, default_config) }); let object_store = Arc::new(ObjectStoreImpl::S3(object_store)); - let sstable_store = Arc::new(SstableStore::new(SstableStoreConfig { - store: object_store, - path: "test".to_string(), - block_cache_capacity: 64 << 20, - meta_cache_capacity: 128 << 20, - meta_cache_shard_num: 2, - block_cache_shard_num: 2, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_eviction: EvictionConfig::for_test(), - prefetch_buffer_capacity: 64 << 20, - max_prefetch_block_number: 16, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), - recent_filter: None, - state_store_metrics: Arc::new(global_hummock_state_store_metrics(MetricLevel::Disabled)), - })); + + let sstable_store = runtime.block_on(async { + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(64 << 20) + .with_shards(2) + .storage() + .build() + .await + .unwrap(); + let block_cache_v2 = HybridCacheBuilder::new() + .memory(128 << 20) + .with_shards(2) + .storage() + .build() + .await + .unwrap(); + Arc::new(SstableStore::new(SstableStoreConfig { + store: object_store, + path: "test".to_string(), + prefetch_buffer_capacity: 64 << 20, + max_prefetch_block_number: 16, + recent_filter: None, + state_store_metrics: Arc::new(global_hummock_state_store_metrics( + MetricLevel::Disabled, + )), + meta_cache_v2, + block_cache_v2, + })) + }); let mut group = c.benchmark_group("bench_multi_builder"); group diff --git a/src/storage/compactor/src/lib.rs b/src/storage/compactor/src/lib.rs index 3ceb9f8954e3b..d9fbe5189f43f 100644 --- a/src/storage/compactor/src/lib.rs +++ b/src/storage/compactor/src/lib.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![feature(lint_reasons)] + mod compactor_observer; mod rpc; pub mod server; diff --git a/src/storage/compactor/src/server.rs b/src/storage/compactor/src/server.rs index c17318a2851f7..e11491fe0360b 100644 --- a/src/storage/compactor/src/server.rs +++ b/src/storage/compactor/src/server.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::net::SocketAddr; -use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::time::Duration; @@ -127,12 +126,17 @@ pub async fn prepare_start_parameters( .await; let object_store = Arc::new(object_store); - let sstable_store = Arc::new(SstableStore::for_compactor( - object_store, - storage_opts.data_directory.to_string(), - 1 << 20, // set 1MB memory to avoid panic. - meta_cache_capacity_bytes, - )); + let sstable_store = Arc::new( + SstableStore::for_compactor( + object_store, + storage_opts.data_directory.to_string(), + 0, + meta_cache_capacity_bytes, + ) + .await + // FIXME(MrCroxx): Handle this error. + .unwrap(), + ); let memory_limiter = Arc::new(MemoryLimiter::new(compactor_memory_limit_bytes)); let storage_memory_config = extract_storage_memory_config(&config); @@ -242,10 +246,6 @@ pub async fn compactor_serve( let compaction_executor = Arc::new(CompactionExecutor::new( opts.compaction_worker_threads_number, )); - let max_task_parallelism = Arc::new(AtomicU32::new( - (compaction_executor.worker_num() as f32 * storage_opts.compactor_max_task_multiplier) - .ceil() as u32, - )); let compactor_context = CompactorContext { storage_opts, @@ -257,8 +257,6 @@ pub async fn compactor_serve( task_progress_manager: Default::default(), await_tree_reg: await_tree_reg.clone(), - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism, }; let mut sub_tasks = vec![ MetaClient::start_heartbeat_loop( @@ -378,10 +376,6 @@ pub async fn shared_compactor_serve( let compaction_executor = Arc::new(CompactionExecutor::new( opts.compaction_worker_threads_number, )); - let max_task_parallelism = Arc::new(AtomicU32::new( - (compaction_executor.worker_num() as f32 * storage_opts.compactor_max_task_multiplier) - .ceil() as u32, - )); let compactor_context = CompactorContext { storage_opts, sstable_store, @@ -391,8 +385,6 @@ pub async fn shared_compactor_serve( memory_limiter, task_progress_manager: Default::default(), await_tree_reg, - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism, }; let join_handle = tokio::spawn(async move { tonic::transport::Server::builder() diff --git a/src/storage/compactor/src/telemetry.rs b/src/storage/compactor/src/telemetry.rs index 2bf7aaaa3f508..b01fdd1e258f3 100644 --- a/src/storage/compactor/src/telemetry.rs +++ b/src/storage/compactor/src/telemetry.rs @@ -33,6 +33,7 @@ impl CompactorTelemetryCreator { #[async_trait::async_trait] impl TelemetryReportCreator for CompactorTelemetryCreator { + #[allow(refining_impl_trait)] async fn create_report( &self, tracking_id: String, diff --git a/src/storage/hummock_sdk/Cargo.toml b/src/storage/hummock_sdk/Cargo.toml index 76f7913127f58..fa080b90b7469 100644 --- a/src/storage/hummock_sdk/Cargo.toml +++ b/src/storage/hummock_sdk/Cargo.toml @@ -23,6 +23,8 @@ prost = { workspace = true } risingwave_common = { workspace = true } risingwave_common_estimate_size = { workspace = true } risingwave_pb = { workspace = true } +serde = { version = "1", features = ["derive"] } +serde_bytes = "0.11" tracing = "0.1" [target.'cfg(not(madsim))'.dependencies] diff --git a/src/storage/hummock_sdk/src/compaction_group/hummock_version_ext.rs b/src/storage/hummock_sdk/src/compaction_group/hummock_version_ext.rs index 585d64f7d4663..c785270086959 100644 --- a/src/storage/hummock_sdk/src/compaction_group/hummock_version_ext.rs +++ b/src/storage/hummock_sdk/src/compaction_group/hummock_version_ext.rs @@ -127,7 +127,7 @@ pub struct SstDeltaInfo { pub delete_sst_object_ids: Vec, } -pub type BranchedSstInfo = HashMap; +pub type BranchedSstInfo = HashMap>; impl HummockVersion { pub fn get_compaction_group_levels(&self, compaction_group_id: CompactionGroupId) -> &Levels { @@ -696,10 +696,10 @@ impl HummockVersion { } let object_id = table_info.get_object_id(); let entry: &mut BranchedSstInfo = ret.entry(object_id).or_default(); - if let Some(exist_sst_id) = entry.get(compaction_group_id) { - panic!("we do not allow more than one sst with the same object id in one grou. object-id: {}, duplicated sst id: {:?} and {}", object_id, exist_sst_id, table_info.sst_id); - } - entry.insert(*compaction_group_id, table_info.sst_id); + entry + .entry(*compaction_group_id) + .or_default() + .push(table_info.sst_id) } } } @@ -1277,13 +1277,7 @@ pub fn validate_version(version: &HummockVersion) -> Vec { let mut prev_table_info: Option<&SstableInfo> = None; for table_info in &level.table_infos { // Ensure table_ids are sorted and unique - if !table_info.table_ids.is_sorted_by(|a, b| { - if a < b { - Some(Ordering::Less) - } else { - Some(Ordering::Greater) - } - }) { + if !table_info.table_ids.is_sorted_by(|a, b| a < b) { res.push(format!( "{} SST {}: table_ids not sorted", level_identifier, table_info.object_id diff --git a/src/storage/hummock_sdk/src/key.rs b/src/storage/hummock_sdk/src/key.rs index 4264dd161d3a1..6a33d1ff1a09b 100644 --- a/src/storage/hummock_sdk/src/key.rs +++ b/src/storage/hummock_sdk/src/key.rs @@ -24,6 +24,7 @@ use bytes::{Buf, BufMut, Bytes, BytesMut}; use risingwave_common::catalog::TableId; use risingwave_common::hash::VirtualNode; use risingwave_common_estimate_size::EstimateSize; +use serde::{Deserialize, Serialize}; use crate::{EpochWithGap, HummockEpoch}; @@ -440,8 +441,14 @@ impl CopyFromSlice for Bytes { /// /// Its name come from the assumption that Hummock is always accessed by a table-like structure /// identified by a [`TableId`]. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct TableKey>(pub T); +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)] +pub struct TableKey>( + #[serde(bound( + serialize = "T: serde::Serialize + serde_bytes::Serialize", + deserialize = "T: serde::Deserialize<'de> + serde_bytes::Deserialize<'de>" + ))] + pub T, +); impl> Debug for TableKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -476,7 +483,11 @@ impl> TableKey { "too short table key: {:?}", self.0.as_ref() ); - let (vnode, inner_key) = self.0.as_ref().split_array_ref::<{ VirtualNode::SIZE }>(); + let (vnode, inner_key) = self + .0 + .as_ref() + .split_first_chunk::<{ VirtualNode::SIZE }>() + .unwrap(); (VirtualNode::from_be_bytes(*vnode), inner_key) } @@ -531,11 +542,15 @@ pub fn gen_key_from_str(vnode: VirtualNode, payload: &str) -> TableKey { /// will group these two values into one struct for convenient filtering. /// /// The encoded format is | `table_id` | `table_key` |. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)] pub struct UserKey> { // When comparing `UserKey`, we first compare `table_id`, then `table_key`. So the order of // declaration matters. pub table_id: TableId, + #[serde(bound( + serialize = "T: serde::Serialize + serde_bytes::Serialize", + deserialize = "T: serde::Deserialize<'de> + serde_bytes::Deserialize<'de>" + ))] pub table_key: TableKey, } @@ -867,10 +882,14 @@ impl + Ord + Eq> PartialOrd for FullKey { } } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct PointRange> { // When comparing `PointRange`, we first compare `left_user_key`, then // `is_exclude_left_key`. Therefore the order of declaration matters. + #[serde(bound( + serialize = "T: serde::Serialize + serde_bytes::Serialize", + deserialize = "T: serde::Deserialize<'de> + serde_bytes::Deserialize<'de>" + ))] pub left_user_key: UserKey, /// `PointRange` represents the left user key itself if `is_exclude_left_key==false` /// while represents the right δ Neighborhood of the left user key if @@ -1096,8 +1115,6 @@ impl + Ord + Eq, const SKIP_DEDUP: bool> FullKeyTracker String { +impl Display for WatermarkDirection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - WatermarkDirection::Ascending => "Ascending".to_string(), - WatermarkDirection::Descending => "Descending".to_string(), + WatermarkDirection::Ascending => write!(f, "Ascending"), + WatermarkDirection::Descending => write!(f, "Descending"), } } } @@ -551,13 +551,8 @@ impl TableWatermarks { // epoch watermark are added from later epoch to earlier epoch. // reverse to ensure that earlier epochs are at the front result_epoch_watermark.reverse(); - assert!( - result_epoch_watermark.is_sorted_by(|(first_epoch, _), (second_epoch, _)| { - let ret = first_epoch.cmp(second_epoch); - assert_ne!(ret, Ordering::Equal); - Some(ret) - }) - ); + assert!(result_epoch_watermark + .is_sorted_by(|(first_epoch, _), (second_epoch, _)| { first_epoch < second_epoch })); *self = TableWatermarks { watermarks: result_epoch_watermark, direction: self.direction, diff --git a/src/storage/hummock_test/benches/bench_hummock_iter.rs b/src/storage/hummock_test/benches/bench_hummock_iter.rs index 059853433a2fc..9f0cef22e4e43 100644 --- a/src/storage/hummock_test/benches/bench_hummock_iter.rs +++ b/src/storage/hummock_test/benches/bench_hummock_iter.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use bytes::Bytes; use criterion::{criterion_group, criterion_main, Criterion}; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::pin_mut; use risingwave_common::util::epoch::test_epoch; use risingwave_hummock_sdk::key::TableKey; @@ -32,7 +32,6 @@ use risingwave_storage::hummock::test_utils::default_opts_for_test; use risingwave_storage::hummock::{CachePolicy, HummockStorage}; use risingwave_storage::storage_value::StorageValue; use risingwave_storage::store::*; -use risingwave_storage::StateStore; fn gen_interleave_shared_buffer_batch_iter( batch_size: usize, @@ -57,7 +56,6 @@ fn gen_interleave_shared_buffer_batch_iter( fn criterion_benchmark(c: &mut Criterion) { let runtime = tokio::runtime::Runtime::new().unwrap(); let batches = gen_interleave_shared_buffer_batch_iter(10000, 100); - let sstable_store = mock_sstable_store(); let hummock_options = Arc::new(default_opts_for_test()); let (env, hummock_manager_ref, _cluster_manager_ref, worker_node) = runtime.block_on(setup_compute_env(8080)); @@ -67,6 +65,7 @@ fn criterion_benchmark(c: &mut Criterion) { )); let global_hummock_storage = runtime.block_on(async { + let sstable_store = mock_sstable_store().await; HummockStorage::for_test( hummock_options, sstable_store, diff --git a/src/storage/hummock_test/src/bin/replay/main.rs b/src/storage/hummock_test/src/bin/replay/main.rs index a08aeff6814bf..89ad5907fae57 100644 --- a/src/storage/hummock_test/src/bin/replay/main.rs +++ b/src/storage/hummock_test/src/bin/replay/main.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![feature(bound_map)] #![feature(coroutines)] #![feature(stmt_expr_attributes)] #![feature(proc_macro_hygiene)] @@ -29,10 +28,10 @@ use std::path::Path; use std::sync::Arc; use clap::Parser; +use foyer::HybridCacheBuilder; use replay_impl::{get_replay_notification_client, GlobalReplayImpl}; use risingwave_common::config::{ - extract_storage_memory_config, load_config, EvictionConfig, NoOverride, ObjectStoreConfig, - StorageConfig, + extract_storage_memory_config, load_config, NoOverride, ObjectStoreConfig, StorageConfig, }; use risingwave_common::system_param::reader::SystemParamsReader; use risingwave_hummock_trace::{ @@ -44,7 +43,7 @@ use risingwave_object_store::object::build_remote_object_store; use risingwave_storage::filter_key_extractor::{ FakeRemoteTableAccessor, RpcFilterKeyExtractorManager, }; -use risingwave_storage::hummock::{FileCache, HummockStorage, SstableStore, SstableStoreConfig}; +use risingwave_storage::hummock::{HummockStorage, SstableStore, SstableStoreConfig}; use risingwave_storage::monitor::{CompactorMetrics, HummockStateStoreMetrics, ObjectStoreMetrics}; use risingwave_storage::opts::StorageOpts; use serde::{Deserialize, Serialize}; @@ -111,21 +110,30 @@ async fn create_replay_hummock(r: Record, args: &Args) -> Result, sstable_store: SstableStoreRef, ) -> CompactorContext { - let compaction_executor = Arc::new(CompactionExecutor::new(Some(1))); - let max_task_parallelism = Arc::new(AtomicU32::new( - (compaction_executor.worker_num() as f32 * storage_opts.compactor_max_task_multiplier) - .ceil() as u32, - )); - CompactorContext { storage_opts, sstable_store, @@ -215,8 +208,6 @@ pub(crate) mod tests { memory_limiter: MemoryLimiter::unlimit(), task_progress_manager: Default::default(), await_tree_reg: None, - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism, } } diff --git a/src/storage/hummock_test/src/failpoint_tests.rs b/src/storage/hummock_test/src/failpoint_tests.rs index d2ab797cb634a..ad96612a2bc32 100644 --- a/src/storage/hummock_test/src/failpoint_tests.rs +++ b/src/storage/hummock_test/src/failpoint_tests.rs @@ -16,7 +16,7 @@ use std::ops::Bound; use std::sync::Arc; use bytes::{BufMut, Bytes}; -use foyer::memory::CacheContext; +use foyer::CacheContext; use risingwave_common::catalog::TableId; use risingwave_common::hash::VirtualNode; use risingwave_hummock_sdk::key::TABLE_PREFIX_LEN; @@ -43,7 +43,7 @@ use crate::test_utils::{gen_key_from_str, TestIngestBatch}; async fn test_failpoints_state_store_read_upload() { let mem_upload_err = "mem_upload_err"; let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let hummock_options = Arc::new(default_opts_for_test()); let (env, hummock_manager_ref, _cluster_manager_ref, worker_node) = setup_compute_env(8080).await; @@ -147,8 +147,8 @@ async fn test_failpoints_state_store_read_upload() { .await .unwrap(); // clear block cache - sstable_store.clear_block_cache(); - sstable_store.clear_meta_cache(); + sstable_store.clear_block_cache().unwrap(); + sstable_store.clear_meta_cache().unwrap(); fail::cfg(mem_read_err, "return").unwrap(); let anchor_prefix_hint = { diff --git a/src/storage/hummock_test/src/hummock_storage_tests.rs b/src/storage/hummock_test/src/hummock_storage_tests.rs index f7711b7fcdf13..599b261a17b8c 100644 --- a/src/storage/hummock_test/src/hummock_storage_tests.rs +++ b/src/storage/hummock_test/src/hummock_storage_tests.rs @@ -16,7 +16,7 @@ use std::ops::Range; use std::sync::Arc; use bytes::{BufMut, Bytes}; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::TryStreamExt; use itertools::Itertools; use risingwave_common::buffer::BitmapBuilder; @@ -37,7 +37,6 @@ use risingwave_storage::hummock::store::version::read_filter_for_version; use risingwave_storage::hummock::{CachePolicy, HummockStorage, LocalHummockStorage}; use risingwave_storage::storage_value::StorageValue; use risingwave_storage::store::*; -use risingwave_storage::StateStore; use crate::local_state_store_test_utils::LocalStateStoreTestExt; use crate::test_utils::{gen_key_from_str, prepare_hummock_test_env, TestIngestBatch}; diff --git a/src/storage/hummock_test/src/lib.rs b/src/storage/hummock_test/src/lib.rs index 890277678b756..a89ead17944f5 100644 --- a/src/storage/hummock_test/src/lib.rs +++ b/src/storage/hummock_test/src/lib.rs @@ -14,9 +14,7 @@ #![feature(proc_macro_hygiene, stmt_expr_attributes)] #![feature(custom_test_frameworks)] #![test_runner(risingwave_test_runner::test_runner::run_failpont_tests)] -#![feature(bound_map)] #![feature(type_alias_impl_trait)] -#![feature(associated_type_bounds)] #[cfg(test)] mod compactor_tests; diff --git a/src/storage/hummock_test/src/snapshot_tests.rs b/src/storage/hummock_test/src/snapshot_tests.rs index 981ed3687bd40..b85457f309f73 100644 --- a/src/storage/hummock_test/src/snapshot_tests.rs +++ b/src/storage/hummock_test/src/snapshot_tests.rs @@ -15,7 +15,7 @@ use std::ops::Bound; use std::sync::Arc; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::CacheContext; use risingwave_common::hash::VirtualNode; use risingwave_common::util::epoch::{test_epoch, EpochExt}; use risingwave_hummock_sdk::key::prefixed_range_with_vnode; diff --git a/src/storage/hummock_test/src/state_store_tests.rs b/src/storage/hummock_test/src/state_store_tests.rs index 104a7e00512ba..4e14e006f009f 100644 --- a/src/storage/hummock_test/src/state_store_tests.rs +++ b/src/storage/hummock_test/src/state_store_tests.rs @@ -18,7 +18,7 @@ use std::sync::Arc; use bytes::Bytes; use expect_test::expect; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::{pin_mut, StreamExt}; use itertools::Itertools; use risingwave_common::buffer::Bitmap; @@ -526,7 +526,7 @@ async fn test_state_store_sync_v2() { /// Fix this when we finished epoch management. #[ignore] async fn test_reload_storage() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let hummock_options = Arc::new(default_opts_for_test()); let (env, hummock_manager_ref, _cluster_manager_ref, worker_node) = setup_compute_env(8080).await; diff --git a/src/storage/hummock_test/src/sync_point_tests.rs b/src/storage/hummock_test/src/sync_point_tests.rs index 655852451d93b..3074bba6dd75f 100644 --- a/src/storage/hummock_test/src/sync_point_tests.rs +++ b/src/storage/hummock_test/src/sync_point_tests.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use std::time::Duration; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::CacheContext; use risingwave_common::catalog::hummock::CompactionFilterFlag; use risingwave_common::catalog::TableId; use risingwave_common::hash::VirtualNode; diff --git a/src/storage/hummock_test/src/test_utils.rs b/src/storage/hummock_test/src/test_utils.rs index 82b01bccaa12f..9a4e674e1f8a9 100644 --- a/src/storage/hummock_test/src/test_utils.rs +++ b/src/storage/hummock_test/src/test_utils.rs @@ -117,6 +117,7 @@ impl TestIngestBatch for S { #[cfg(test)] #[async_trait::async_trait] pub(crate) trait HummockStateStoreTestTrait: StateStore { + #[allow(dead_code)] fn get_pinned_version(&self) -> PinnedVersion; async fn seal_and_sync_epoch(&self, epoch: u64) -> StorageResult { self.seal_epoch(epoch, true); @@ -134,7 +135,7 @@ impl HummockStateStoreTestTrait for HummockStorage { pub async fn with_hummock_storage_v2( table_id: TableId, ) -> (HummockStorage, Arc) { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let hummock_options = Arc::new(default_opts_for_test()); let (env, hummock_manager_ref, _cluster_manager_ref, worker_node) = setup_compute_env(8080).await; @@ -262,7 +263,7 @@ impl HummockTestEnv { } pub async fn prepare_hummock_test_env() -> HummockTestEnv { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let hummock_options = Arc::new(default_opts_for_test()); let (env, hummock_manager_ref, _cluster_manager_ref, worker_node) = setup_compute_env(8080).await; diff --git a/src/storage/hummock_test/src/vacuum_tests.rs b/src/storage/hummock_test/src/vacuum_tests.rs index d35c6fe48c3e7..5c6dbdc556d5a 100644 --- a/src/storage/hummock_test/src/vacuum_tests.rs +++ b/src/storage/hummock_test/src/vacuum_tests.rs @@ -31,7 +31,7 @@ use risingwave_storage::hummock::vacuum::Vacuum; #[tokio::test] async fn test_vacuum() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // Put some SSTs to object store let object_ids = (1..10).collect_vec(); let mut sstables = vec![]; @@ -70,7 +70,7 @@ async fn test_vacuum() { async fn test_full_scan() { let (_env, hummock_manager_ref, _cluster_manager_ref, worker_node) = setup_compute_env(8080).await; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let _mock_hummock_meta_client = Arc::new(MockHummockMetaClient::new( hummock_manager_ref, worker_node.id, diff --git a/src/storage/hummock_trace/src/lib.rs b/src/storage/hummock_trace/src/lib.rs index 0de5680908254..64417832206e0 100644 --- a/src/storage/hummock_trace/src/lib.rs +++ b/src/storage/hummock_trace/src/lib.rs @@ -14,7 +14,6 @@ #![feature(lazy_cell)] #![feature(cursor_remaining)] -#![feature(bound_map)] #![feature(trait_alias)] #![feature(coroutines)] diff --git a/src/storage/hummock_trace/src/opts.rs b/src/storage/hummock_trace/src/opts.rs index a0073d620a5bc..9eadecf4e59b0 100644 --- a/src/storage/hummock_trace/src/opts.rs +++ b/src/storage/hummock_trace/src/opts.rs @@ -13,7 +13,7 @@ // limitations under the License. use bincode::{Decode, Encode}; -use foyer::memory::CacheContext; +use foyer::CacheContext; use risingwave_common::buffer::Bitmap; use risingwave_common::cache::CachePriority; use risingwave_common::catalog::{TableId, TableOption}; @@ -33,7 +33,6 @@ pub struct TracedPrefetchOptions { pub enum TracedCachePolicy { Disable, Fill(TracedCachePriority), - FileFileCache, NotFill, } diff --git a/src/storage/hummock_trace/src/replay/worker.rs b/src/storage/hummock_trace/src/replay/worker.rs index 29e814c3f9ade..2c2cef690b19d 100644 --- a/src/storage/hummock_trace/src/replay/worker.rs +++ b/src/storage/hummock_trace/src/replay/worker.rs @@ -530,13 +530,10 @@ mod tests { use bytes::Bytes; use mockall::predicate; - use tokio::sync::mpsc::unbounded_channel; use super::*; use crate::replay::{MockGlobalReplayInterface, MockLocalReplayInterface}; - use crate::{ - MockReplayIterStream, StorageType, TracedBytes, TracedNewLocalOptions, TracedReadOptions, - }; + use crate::{MockReplayIterStream, TracedBytes, TracedReadOptions}; #[tokio::test] async fn test_handle_record() { diff --git a/src/storage/hummock_trace/src/write.rs b/src/storage/hummock_trace/src/write.rs index 528cdac5f659d..5cf10b39378f2 100644 --- a/src/storage/hummock_trace/src/write.rs +++ b/src/storage/hummock_trace/src/write.rs @@ -103,7 +103,7 @@ impl> Drop for TraceWriterImpl { mod test { use std::io::Cursor; - use bincode::{config, decode_from_slice, encode_to_vec}; + use bincode::{decode_from_slice, encode_to_vec}; use byteorder::{BigEndian, ReadBytesExt}; use bytes::Bytes; diff --git a/src/storage/src/hummock/block_cache.rs b/src/storage/src/hummock/block_cache.rs index 88f01bf25e22c..1e91bc7afc301 100644 --- a/src/storage/src/hummock/block_cache.rs +++ b/src/storage/src/hummock/block_cache.rs @@ -15,24 +15,17 @@ use std::ops::Deref; use std::sync::Arc; -use ahash::RandomState; use await_tree::InstrumentAwait; -use foyer::memory::{ - Cache, CacheContext, CacheEntry, Entry, EntryState, LfuCacheConfig, LruCacheConfig, - S3FifoCacheConfig, -}; -use futures::Future; +use foyer::{EntryState, HybridCacheEntry, HybridEntry}; use risingwave_common::config::EvictionConfig; -use risingwave_hummock_sdk::HummockSstableObjectId; -use super::{Block, BlockCacheEventListener, HummockResult}; +use super::{Block, HummockResult, SstableBlockIndex}; use crate::hummock::HummockError; -type CachedBlockEntry = - CacheEntry<(HummockSstableObjectId, u64), Box, BlockCacheEventListener>; +type HybridCachedBlockEntry = HybridCacheEntry>; enum BlockEntry { - Cache(#[allow(dead_code)] CachedBlockEntry), + HybridCache(#[allow(dead_code)] HybridCachedBlockEntry), Owned(#[allow(dead_code)] Box), RefEntry(#[allow(dead_code)] Arc), } @@ -59,10 +52,10 @@ impl BlockHolder { } } - pub fn from_cached_block(entry: CachedBlockEntry) -> Self { - let ptr = entry.deref().as_ref() as *const _; + pub fn from_hybrid_cache_entry(entry: HybridCachedBlockEntry) -> Self { + let ptr = entry.value().as_ref() as *const _; Self { - _handle: BlockEntry::Cache(entry), + _handle: BlockEntry::HybridCache(entry), block: ptr, } } @@ -84,17 +77,11 @@ pub struct BlockCacheConfig { pub capacity: usize, pub shard_num: usize, pub eviction: EvictionConfig, - pub listener: BlockCacheEventListener, -} - -#[derive(Clone)] -pub struct BlockCache { - inner: Cache<(HummockSstableObjectId, u64), Box, BlockCacheEventListener>, } pub enum BlockResponse { Block(BlockHolder), - Entry(Entry<(HummockSstableObjectId, u64), Box, HummockError, BlockCacheEventListener>), + Entry(HybridEntry>), } impl BlockResponse { @@ -104,121 +91,20 @@ impl BlockResponse { BlockResponse::Entry(entry) => entry, }; match entry.state() { - EntryState::Hit => entry.await.map(BlockHolder::from_cached_block), + EntryState::Hit => entry + .await + .map(BlockHolder::from_hybrid_cache_entry) + .map_err(HummockError::foyer_error), EntryState::Wait => entry .verbose_instrument_await("wait_pending_fetch_block") .await - .map(BlockHolder::from_cached_block), + .map(BlockHolder::from_hybrid_cache_entry) + .map_err(HummockError::foyer_error), EntryState::Miss => entry .verbose_instrument_await("fetch_block") .await - .map(BlockHolder::from_cached_block), + .map(BlockHolder::from_hybrid_cache_entry) + .map_err(HummockError::foyer_error), } } } - -impl BlockCache { - pub fn new(config: BlockCacheConfig) -> Self { - assert!( - config.capacity > 0, - "Block cache capacity must be positive." - ); - - let capacity = config.capacity; - let shards = config.shard_num; - let object_pool_capacity = shards * 1024; - let hash_builder = RandomState::default(); - let event_listener = config.listener; - - let inner = match config.eviction { - EvictionConfig::Lru(eviction_config) => Cache::lru(LruCacheConfig { - capacity, - shards, - eviction_config, - object_pool_capacity, - hash_builder, - event_listener, - }), - EvictionConfig::Lfu(eviction_config) => Cache::lfu(LfuCacheConfig { - capacity, - shards, - eviction_config, - object_pool_capacity, - hash_builder, - event_listener, - }), - EvictionConfig::S3Fifo(eviction_config) => Cache::s3fifo(S3FifoCacheConfig { - capacity, - shards, - eviction_config, - object_pool_capacity, - hash_builder, - event_listener, - }), - }; - - Self { inner } - } - - pub fn get(&self, object_id: HummockSstableObjectId, block_idx: u64) -> Option { - self.inner - .get(&(object_id, block_idx)) - .map(BlockHolder::from_cached_block) - } - - pub fn exists_block(&self, sst_id: HummockSstableObjectId, block_idx: u64) -> bool { - // TODO(MrCroxx): optimize me - self.get(sst_id, block_idx).is_some() - } - - pub fn insert( - &self, - object_id: HummockSstableObjectId, - block_idx: u64, - block: Box, - context: CacheContext, - ) -> BlockHolder { - let charge = block.capacity(); - BlockHolder::from_cached_block(self.inner.insert_with_context( - (object_id, block_idx), - block, - charge, - context, - )) - } - - pub fn get_or_insert_with( - &self, - object_id: HummockSstableObjectId, - block_idx: u64, - context: CacheContext, - mut fetch_block: F, - ) -> BlockResponse - where - F: FnMut() -> Fut, - Fut: Future>> + Send + 'static, - { - let key = (object_id, block_idx); - - let entry = self.inner.entry(key, || { - let f = fetch_block(); - async move { - let block = f.await?; - let len = block.capacity(); - Ok::<_, HummockError>((block, len, context)) - } - }); - - BlockResponse::Entry(entry) - } - - pub fn size(&self) -> usize { - self.inner.usage() - } - - #[cfg(any(test, feature = "test"))] - pub fn clear(&self) { - // This is only a method for test. Therefore it should be safe to call the unsafe method. - self.inner.clear(); - } -} diff --git a/src/storage/src/hummock/compactor/compaction_utils.rs b/src/storage/src/hummock/compactor/compaction_utils.rs index 9594d7295a03e..d0e5fe93c62ee 100644 --- a/src/storage/src/hummock/compactor/compaction_utils.rs +++ b/src/storage/src/hummock/compactor/compaction_utils.rs @@ -26,8 +26,10 @@ use risingwave_hummock_sdk::key_range::KeyRange; use risingwave_hummock_sdk::prost_key_range::KeyRangeExt; use risingwave_hummock_sdk::table_stats::TableStatsMap; use risingwave_hummock_sdk::{can_concat, EpochWithGap, KeyComparator}; +use risingwave_pb::hummock::compact_task::TaskType; use risingwave_pb::hummock::{ - compact_task, CompactTask, KeyRange as KeyRange_vec, LevelType, SstableInfo, TableSchema, + compact_task, BloomFilterType, CompactTask, KeyRange as KeyRange_vec, LevelType, SstableInfo, + TableSchema, }; use tokio::time::Instant; @@ -172,24 +174,19 @@ pub fn build_multi_compaction_filter(compact_task: &CompactTask) -> MultiCompact multi_filter } -const MAX_FILE_COUNT: usize = 32; - fn generate_splits_fast( sstable_infos: &Vec, compaction_size: u64, - context: CompactorContext, -) -> HummockResult> { + context: &CompactorContext, +) -> Vec { let worker_num = context.compaction_executor.worker_num(); let parallel_compact_size = (context.storage_opts.parallel_compact_size_mb as u64) << 20; - let parallelism = (compaction_size + parallel_compact_size - 1) / parallel_compact_size; - - let parallelism = std::cmp::min( + let parallelism = calculate_task_parallelism_impl( worker_num, - std::cmp::min( - parallelism as usize, - context.storage_opts.max_sub_compaction as usize, - ), + parallel_compact_size, + compaction_size, + context.storage_opts.max_sub_compaction, ); let mut indexes = vec![]; for sst in sstable_infos { @@ -212,8 +209,9 @@ fn generate_splits_fast( indexes.sort_by(|a, b| KeyComparator::compare_encoded_full_key(a.as_ref(), b.as_ref())); indexes.dedup(); if indexes.len() <= parallelism { - return Ok(vec![]); + return vec![]; } + let mut splits = vec![]; splits.push(KeyRange_vec::new(vec![], vec![])); let parallel_key_count = indexes.len() / parallelism; @@ -226,18 +224,24 @@ fn generate_splits_fast( } last_split_key_count += 1; } - Ok(splits) + + splits } pub async fn generate_splits( sstable_infos: &Vec, compaction_size: u64, - context: CompactorContext, + context: &CompactorContext, ) -> HummockResult> { + const MAX_FILE_COUNT: usize = 32; let parallel_compact_size = (context.storage_opts.parallel_compact_size_mb as u64) << 20; if compaction_size > parallel_compact_size { if sstable_infos.len() > MAX_FILE_COUNT { - return generate_splits_fast(sstable_infos, compaction_size, context); + return Ok(generate_splits_fast( + sstable_infos, + compaction_size, + context, + )); } let mut indexes = vec![]; // preload the meta and get the smallest key to split sub_compaction @@ -267,18 +271,15 @@ pub async fn generate_splits( let mut splits = vec![]; splits.push(KeyRange_vec::new(vec![], vec![])); - let worker_num = context.compaction_executor.worker_num(); - - let parallelism = std::cmp::min( - worker_num as u64, - std::cmp::min( - indexes.len() as u64, - context.storage_opts.max_sub_compaction as u64, - ), + let parallelism = calculate_task_parallelism_impl( + context.compaction_executor.worker_num(), + parallel_compact_size, + compaction_size, + context.storage_opts.max_sub_compaction, ); + let sub_compaction_data_size = - std::cmp::max(compaction_size / parallelism, parallel_compact_size); - let parallelism = compaction_size / sub_compaction_data_size; + std::cmp::max(compaction_size / parallelism as u64, parallel_compact_size); if parallelism > 1 { let mut last_buffer_size = 0; @@ -489,3 +490,185 @@ async fn check_result< } Ok(true) } + +pub fn optimize_by_copy_block(compact_task: &CompactTask, context: &CompactorContext) -> bool { + let sstable_infos = compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter()) + .filter(|table_info| { + let table_ids = &table_info.table_ids; + table_ids + .iter() + .any(|table_id| compact_task.existing_table_ids.contains(table_id)) + }) + .cloned() + .collect_vec(); + let compaction_size = sstable_infos + .iter() + .map(|table_info| table_info.file_size) + .sum::(); + + let all_ssts_are_blocked_filter = sstable_infos + .iter() + .all(|table_info| table_info.bloom_filter_kind() == BloomFilterType::Blocked); + + let delete_key_count = sstable_infos + .iter() + .map(|table_info| table_info.stale_key_count + table_info.range_tombstone_count) + .sum::(); + let total_key_count = sstable_infos + .iter() + .map(|table_info| table_info.total_key_count) + .sum::(); + + let has_tombstone = compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter()) + .any(|sst| sst.range_tombstone_count > 0); + let has_ttl = compact_task + .table_options + .iter() + .any(|(_, table_option)| table_option.retention_seconds.is_some_and(|ttl| ttl > 0)); + + let compact_table_ids: HashSet = HashSet::from_iter( + compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter()) + .flat_map(|sst| sst.table_ids.clone()), + ); + let single_table = compact_table_ids.len() == 1; + + context.storage_opts.enable_fast_compaction + && all_ssts_are_blocked_filter + && !has_tombstone + && !has_ttl + && single_table + && compact_task.target_level > 0 + && compact_task.input_ssts.len() == 2 + && compaction_size < context.storage_opts.compactor_fast_max_compact_task_size + && delete_key_count * 100 + < context.storage_opts.compactor_fast_max_compact_delete_ratio as u64 * total_key_count + && compact_task.task_type() == TaskType::Dynamic +} + +pub async fn generate_splits_for_task( + compact_task: &mut CompactTask, + context: &CompactorContext, + optimize_by_copy_block: bool, +) -> HummockResult<()> { + let sstable_infos = compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter()) + .filter(|table_info| { + let table_ids = &table_info.table_ids; + table_ids + .iter() + .any(|table_id| compact_task.existing_table_ids.contains(table_id)) + }) + .cloned() + .collect_vec(); + let compaction_size = sstable_infos + .iter() + .map(|table_info| table_info.file_size) + .sum::(); + + if !optimize_by_copy_block { + let splits = generate_splits(&sstable_infos, compaction_size, context).await?; + if !splits.is_empty() { + compact_task.splits = splits; + } + return Ok(()); + } + + Ok(()) +} + +pub fn metrics_report_for_task(compact_task: &CompactTask, context: &CompactorContext) { + let group_label = compact_task.compaction_group_id.to_string(); + let cur_level_label = compact_task.input_ssts[0].level_idx.to_string(); + let select_table_infos = compact_task + .input_ssts + .iter() + .filter(|level| level.level_idx != compact_task.target_level) + .flat_map(|level| level.table_infos.iter()) + .collect_vec(); + let target_table_infos = compact_task + .input_ssts + .iter() + .filter(|level| level.level_idx == compact_task.target_level) + .flat_map(|level| level.table_infos.iter()) + .collect_vec(); + let select_size = select_table_infos + .iter() + .map(|table| table.file_size) + .sum::(); + context + .compactor_metrics + .compact_read_current_level + .with_label_values(&[&group_label, &cur_level_label]) + .inc_by(select_size); + context + .compactor_metrics + .compact_read_sstn_current_level + .with_label_values(&[&group_label, &cur_level_label]) + .inc_by(select_table_infos.len() as u64); + + let target_level_read_bytes = target_table_infos.iter().map(|t| t.file_size).sum::(); + let next_level_label = compact_task.target_level.to_string(); + context + .compactor_metrics + .compact_read_next_level + .with_label_values(&[&group_label, next_level_label.as_str()]) + .inc_by(target_level_read_bytes); + context + .compactor_metrics + .compact_read_sstn_next_level + .with_label_values(&[&group_label, next_level_label.as_str()]) + .inc_by(target_table_infos.len() as u64); +} + +pub fn calculate_task_parallelism(compact_task: &CompactTask, context: &CompactorContext) -> usize { + let optimize_by_copy_block = optimize_by_copy_block(compact_task, context); + + if optimize_by_copy_block { + return 1; + } + + let sstable_infos = compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter()) + .filter(|table_info| { + let table_ids = &table_info.table_ids; + table_ids + .iter() + .any(|table_id| compact_task.existing_table_ids.contains(table_id)) + }) + .cloned() + .collect_vec(); + let compaction_size = sstable_infos + .iter() + .map(|table_info| table_info.file_size) + .sum::(); + let parallel_compact_size = (context.storage_opts.parallel_compact_size_mb as u64) << 20; + calculate_task_parallelism_impl( + context.compaction_executor.worker_num(), + parallel_compact_size, + compaction_size, + context.storage_opts.max_sub_compaction, + ) +} + +pub fn calculate_task_parallelism_impl( + worker_num: usize, + parallel_compact_size: u64, + compaction_size: u64, + max_sub_compaction: u32, +) -> usize { + let parallelism = (compaction_size + parallel_compact_size - 1) / parallel_compact_size; + worker_num.min(parallelism.min(max_sub_compaction as u64) as usize) +} diff --git a/src/storage/src/hummock/compactor/compactor_runner.rs b/src/storage/src/hummock/compactor/compactor_runner.rs index f1a28a5e2b93a..05b3d7dd182d9 100644 --- a/src/storage/src/hummock/compactor/compactor_runner.rs +++ b/src/storage/src/hummock/compactor/compactor_runner.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::collections::{BinaryHeap, HashMap, HashSet}; -use std::sync::atomic::Ordering; use std::sync::Arc; use await_tree::InstrumentAwait; @@ -30,8 +29,8 @@ use risingwave_hummock_sdk::table_stats::{add_table_stats_map, TableStats, Table use risingwave_hummock_sdk::{ can_concat, compact_task_output_to_string, HummockSstableObjectId, KeyComparator, }; -use risingwave_pb::hummock::compact_task::{TaskStatus, TaskType}; -use risingwave_pb::hummock::{BloomFilterType, CompactTask, LevelType, SstableInfo}; +use risingwave_pb::hummock::compact_task::TaskStatus; +use risingwave_pb::hummock::{CompactTask, LevelType, SstableInfo}; use thiserror_ext::AsReport; use tokio::sync::oneshot::Receiver; @@ -40,7 +39,8 @@ use super::task_progress::TaskProgress; use super::{CompactionStatistics, TaskConfig}; use crate::filter_key_extractor::{FilterKeyExtractorImpl, FilterKeyExtractorManager}; use crate::hummock::compactor::compaction_utils::{ - build_multi_compaction_filter, estimate_task_output_capacity, generate_splits, + build_multi_compaction_filter, estimate_task_output_capacity, generate_splits_for_task, + metrics_report_for_task, optimize_by_copy_block, }; use crate::hummock::compactor::iterator::ConcatSstableIterator; use crate::hummock::compactor::task_progress::TaskProgressGuard; @@ -276,7 +276,9 @@ pub fn partition_overlapping_sstable_infos( &prev_group.max_right_bound, &sst.key_range.as_ref().unwrap().left, ) { - prev_group.max_right_bound = sst.key_range.as_ref().unwrap().right.clone(); + prev_group + .max_right_bound + .clone_from(&sst.key_range.as_ref().unwrap().right); prev_group.ssts.push(sst); continue; } @@ -304,46 +306,7 @@ pub async fn compact( ) { let context = compactor_context.clone(); let group_label = compact_task.compaction_group_id.to_string(); - let cur_level_label = compact_task.input_ssts[0].level_idx.to_string(); - let select_table_infos = compact_task - .input_ssts - .iter() - .filter(|level| level.level_idx != compact_task.target_level) - .flat_map(|level| level.table_infos.iter()) - .collect_vec(); - let target_table_infos = compact_task - .input_ssts - .iter() - .filter(|level| level.level_idx == compact_task.target_level) - .flat_map(|level| level.table_infos.iter()) - .collect_vec(); - let select_size = select_table_infos - .iter() - .map(|table| table.file_size) - .sum::(); - context - .compactor_metrics - .compact_read_current_level - .with_label_values(&[&group_label, &cur_level_label]) - .inc_by(select_size); - context - .compactor_metrics - .compact_read_sstn_current_level - .with_label_values(&[&group_label, &cur_level_label]) - .inc_by(select_table_infos.len() as u64); - - let target_level_read_bytes = target_table_infos.iter().map(|t| t.file_size).sum::(); - let next_level_label = compact_task.target_level.to_string(); - context - .compactor_metrics - .compact_read_next_level - .with_label_values(&[&group_label, next_level_label.as_str()]) - .inc_by(target_level_read_bytes); - context - .compactor_metrics - .compact_read_sstn_next_level - .with_label_values(&[&group_label, next_level_label.as_str()]) - .inc_by(target_table_infos.len() as u64); + metrics_report_for_task(&compact_task, &context); let timer = context .compactor_metrics @@ -356,151 +319,52 @@ pub async fn compact( let multi_filter = build_multi_compaction_filter(&compact_task); - let mut compact_table_ids = compact_task - .input_ssts - .iter() - .flat_map(|level| level.table_infos.iter()) - .flat_map(|sst| sst.table_ids.clone()) - .collect_vec(); - compact_table_ids.sort(); - compact_table_ids.dedup(); - let single_table = compact_table_ids.len() == 1; - let existing_table_ids: HashSet = HashSet::from_iter(compact_task.existing_table_ids.clone()); let compact_table_ids = HashSet::from_iter( - compact_table_ids - .into_iter() + compact_task + .input_ssts + .iter() + .flat_map(|level| level.table_infos.iter()) + .flat_map(|sst| sst.table_ids.clone()) .filter(|table_id| existing_table_ids.contains(table_id)), ); - let multi_filter_key_extractor = match filter_key_extractor_manager - .acquire(compact_table_ids.clone()) - .await + + let multi_filter_key_extractor = match build_filter_key_extractor( + &compact_task, + filter_key_extractor_manager, + &compact_table_ids, + ) + .await { - Err(e) => { - tracing::error!(error = %e.as_report(), "Failed to fetch filter key extractor tables [{:?}], it may caused by some RPC error", compact_task.existing_table_ids); + Some(multi_filter_key_extractor) => multi_filter_key_extractor, + None => { let task_status = TaskStatus::ExecuteFailed; return ( compact_done(compact_task, context.clone(), vec![], task_status), None, ); } - Ok(extractor) => extractor, }; - if let FilterKeyExtractorImpl::Multi(multi) = &multi_filter_key_extractor { - let found_tables = multi.get_existing_table_ids(); - let removed_tables = compact_table_ids - .iter() - .filter(|table_id| !found_tables.contains(table_id)) - .collect_vec(); - if !removed_tables.is_empty() { - tracing::error!("Failed to fetch filter key extractor tables [{:?}. [{:?}] may be removed by meta-service. ", compact_table_ids, removed_tables); - let task_status = TaskStatus::ExecuteFailed; - return ( - compact_done(compact_task, context.clone(), vec![], task_status), - None, - ); - } - } - - let multi_filter_key_extractor = Arc::new(multi_filter_key_extractor); - let has_tombstone = compact_task - .input_ssts - .iter() - .flat_map(|level| level.table_infos.iter()) - .any(|sst| sst.range_tombstone_count > 0); - let has_ttl = compact_task - .table_options - .iter() - .any(|(_, table_option)| table_option.retention_seconds.is_some_and(|ttl| ttl > 0)); let mut task_status = TaskStatus::Success; - // skip sst related to non-existent able_id to reduce io - let sstable_infos = compact_task - .input_ssts - .iter() - .flat_map(|level| level.table_infos.iter()) - .filter(|table_info| { - let table_ids = &table_info.table_ids; - table_ids - .iter() - .any(|table_id| existing_table_ids.contains(table_id)) - }) - .cloned() - .collect_vec(); - let compaction_size = sstable_infos - .iter() - .map(|table_info| table_info.file_size) - .sum::(); - let all_ssts_are_blocked_filter = sstable_infos - .iter() - .all(|table_info| table_info.bloom_filter_kind() == BloomFilterType::Blocked); + let optimize_by_copy_block = optimize_by_copy_block(&compact_task, &context); - let delete_key_count = sstable_infos - .iter() - .map(|table_info| table_info.stale_key_count + table_info.range_tombstone_count) - .sum::(); - let total_key_count = sstable_infos - .iter() - .map(|table_info| table_info.total_key_count) - .sum::(); - let optimize_by_copy_block = context.storage_opts.enable_fast_compaction - && all_ssts_are_blocked_filter - && !has_tombstone - && !has_ttl - && single_table - && compact_task.target_level > 0 - && compact_task.input_ssts.len() == 2 - && compaction_size < context.storage_opts.compactor_fast_max_compact_task_size - && delete_key_count * 100 - < context.storage_opts.compactor_fast_max_compact_delete_ratio as u64 * total_key_count - && compact_task.task_type() == TaskType::Dynamic; - - if !optimize_by_copy_block { - match generate_splits(&sstable_infos, compaction_size, context.clone()).await { - Ok(splits) => { - if !splits.is_empty() { - compact_task.splits = splits; - } - } - Err(e) => { - tracing::warn!(error = %e.as_report(), "Failed to generate_splits"); - task_status = TaskStatus::ExecuteFailed; - return ( - compact_done(compact_task, context.clone(), vec![], task_status), - None, - ); - } - } - } - let compact_task_statistics = statistics_compact_task(&compact_task); - // Number of splits (key ranges) is equal to number of compaction tasks - let parallelism = compact_task.splits.len(); - assert_ne!(parallelism, 0, "splits cannot be empty"); - if !context.acquire_task_quota(parallelism as u32) { - tracing::warn!( - "Not enough core parallelism to serve the task {} task_parallelism {} running_task_parallelism {} max_task_parallelism {}", - compact_task.task_id, - parallelism, - context.running_task_parallelism.load(Ordering::Relaxed), - context.max_task_parallelism.load(Ordering::Relaxed), - ); + if let Err(e) = + generate_splits_for_task(&mut compact_task, &context, optimize_by_copy_block).await + { + tracing::warn!(error = %e.as_report(), "Failed to generate_splits"); + task_status = TaskStatus::ExecuteFailed; return ( - compact_done( - compact_task, - context.clone(), - vec![], - TaskStatus::NoAvailCpuResourceCanceled, - ), + compact_done(compact_task, context.clone(), vec![], task_status), None, ); } - let _release_quota_guard = - scopeguard::guard((parallelism, context.clone()), |(parallelism, context)| { - context.release_task_quota(parallelism as u32); - }); - + let compact_task_statistics = statistics_compact_task(&compact_task); + // Number of splits (key ranges) is equal to number of compaction tasks + let parallelism = compact_task.splits.len(); + assert_ne!(parallelism, 0, "splits cannot be empty"); let mut output_ssts = Vec::with_capacity(parallelism); let mut compaction_futures = vec![]; let mut abort_handles = vec![]; @@ -707,7 +571,7 @@ pub async fn compact( } /// Fills in the compact task and tries to report the task result to meta node. -fn compact_done( +pub(crate) fn compact_done( mut compact_task: CompactTask, context: CompactorContext, output_ssts: Vec, @@ -931,6 +795,39 @@ where Ok(compaction_statistics) } +async fn build_filter_key_extractor( + compact_task: &CompactTask, + filter_key_extractor_manager: FilterKeyExtractorManager, + compact_table_ids: &HashSet, +) -> Option> { + let multi_filter_key_extractor = match filter_key_extractor_manager + .acquire(compact_table_ids.clone()) + .await + { + Err(e) => { + tracing::error!(error = %e.as_report(), "Failed to fetch filter key extractor tables [{:?}], it may caused by some RPC error", compact_task.existing_table_ids); + return None; + } + Ok(extractor) => extractor, + }; + + if let FilterKeyExtractorImpl::Multi(multi) = &multi_filter_key_extractor { + let found_tables = multi.get_existing_table_ids(); + let removed_tables = compact_table_ids + .iter() + .filter(|table_id| !found_tables.contains(table_id)) + .collect_vec(); + if !removed_tables.is_empty() { + tracing::error!("Failed to fetch filter key extractor tables [{:?}. [{:?}] may be removed by meta-service. ", compact_table_ids, removed_tables); + return None; + } + } + + let multi_filter_key_extractor = Arc::new(multi_filter_key_extractor); + + Some(multi_filter_key_extractor) +} + #[cfg(test)] pub mod tests { use risingwave_hummock_sdk::can_concat; @@ -945,7 +842,7 @@ pub mod tests { #[tokio::test] async fn test_partition_overlapping_level() { const TEST_KEYS_COUNT: usize = 10; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let mut table_infos = vec![]; for object_id in 0..10 { let start_index = object_id * TEST_KEYS_COUNT; diff --git a/src/storage/src/hummock/compactor/context.rs b/src/storage/src/hummock/compactor/context.rs index 4525dcdc773fb..e8295effc25ed 100644 --- a/src/storage/src/hummock/compactor/context.rs +++ b/src/storage/src/hummock/compactor/context.rs @@ -12,11 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; -use more_asserts::assert_ge; - use super::task_progress::TaskProgressManagerRef; use crate::hummock::compactor::CompactionExecutor; use crate::hummock::sstable_store::SstableStoreRef; @@ -65,10 +62,6 @@ pub struct CompactorContext { pub task_progress_manager: TaskProgressManagerRef, pub await_tree_reg: Option, - - pub running_task_parallelism: Arc, - - pub max_task_parallelism: Arc, } impl CompactorContext { @@ -97,56 +90,6 @@ impl CompactorContext { memory_limiter: MemoryLimiter::unlimit(), task_progress_manager: Default::default(), await_tree_reg, - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism: Arc::new(AtomicU32::new(u32::MAX)), - } - } - - pub fn acquire_task_quota(&self, parallelism: u32) -> bool { - let mut running_u32 = self.running_task_parallelism.load(Ordering::SeqCst); - let max_u32 = self.max_task_parallelism.load(Ordering::SeqCst); - - while parallelism + running_u32 <= max_u32 { - match self.running_task_parallelism.compare_exchange( - running_u32, - running_u32 + parallelism, - Ordering::SeqCst, - Ordering::SeqCst, - ) { - Ok(_) => { - return true; - } - Err(old_running_u32) => { - running_u32 = old_running_u32; - } - } - } - - false - } - - pub fn release_task_quota(&self, parallelism: u32) { - let prev = self - .running_task_parallelism - .fetch_sub(parallelism, Ordering::SeqCst); - - assert_ge!( - prev, - parallelism, - "running {} parallelism {}", - prev, - parallelism - ); - } - - pub fn get_free_quota(&self) -> u32 { - let running_u32 = self.running_task_parallelism.load(Ordering::SeqCst); - let max_u32 = self.max_task_parallelism.load(Ordering::SeqCst); - - if max_u32 > running_u32 { - max_u32 - running_u32 - } else { - 0 } } } diff --git a/src/storage/src/hummock/compactor/iterator.rs b/src/storage/src/hummock/compactor/iterator.rs index f89d70a756486..5431c51270efc 100644 --- a/src/storage/src/hummock/compactor/iterator.rs +++ b/src/storage/src/hummock/compactor/iterator.rs @@ -584,7 +584,7 @@ mod tests { #[tokio::test] async fn test_concat_iterator() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let mut table_infos = vec![]; for object_id in 0..3 { let start_index = object_id * TEST_KEYS_COUNT; @@ -704,7 +704,7 @@ mod tests { #[tokio::test] async fn test_concat_iterator_seek_idx() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let mut table_infos = vec![]; for object_id in 0..3 { let start_index = object_id * TEST_KEYS_COUNT + TEST_KEYS_COUNT / 2; diff --git a/src/storage/src/hummock/compactor/mod.rs b/src/storage/src/hummock/compactor/mod.rs index c18828bc93e72..be9a3ffad0385 100644 --- a/src/storage/src/hummock/compactor/mod.rs +++ b/src/storage/src/hummock/compactor/mod.rs @@ -20,7 +20,7 @@ use risingwave_pb::hummock::report_compaction_task_request::{ Event as ReportCompactionTaskEvent, HeartBeat as SharedHeartBeat, ReportTask as ReportSharedTask, }; -use risingwave_pb::hummock::{ReportFullScanTaskRequest, ReportVacuumTaskRequest}; +use risingwave_pb::hummock::{CompactTask, ReportFullScanTaskRequest, ReportVacuumTaskRequest}; use risingwave_rpc_client::GrpcCompactorProxyClient; use thiserror_ext::AsReport; use tokio::sync::mpsc; @@ -35,7 +35,7 @@ pub(super) mod task_progress; use std::collections::{HashMap, VecDeque}; use std::marker::PhantomData; -use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, SystemTime}; @@ -51,7 +51,7 @@ pub use context::{ use futures::{pin_mut, StreamExt}; pub use iterator::{ConcatSstableIterator, SstableStreamIterator}; use more_asserts::assert_ge; -use risingwave_hummock_sdk::table_stats::to_prost_table_stats_map; +use risingwave_hummock_sdk::table_stats::{to_prost_table_stats_map, TableStatsMap}; use risingwave_hummock_sdk::{compact_task_to_string, HummockCompactionTaskId, LocalSstableInfo}; use risingwave_pb::hummock::compact_task::TaskStatus; use risingwave_pb::hummock::subscribe_compaction_event_request::{ @@ -80,7 +80,8 @@ use super::{ use crate::filter_key_extractor::{ FilterKeyExtractorImpl, FilterKeyExtractorManager, StaticFilterKeyExtractorManager, }; -use crate::hummock::compactor::compactor_runner::compact_and_build_sst; +use crate::hummock::compactor::compaction_utils::calculate_task_parallelism; +use crate::hummock::compactor::compactor_runner::{compact_and_build_sst, compact_done}; use crate::hummock::iterator::{Forward, HummockIterator}; use crate::hummock::multi_builder::SplitTableOutput; use crate::hummock::vacuum::Vacuum; @@ -299,14 +300,14 @@ pub fn start_compactor( let stream_retry_interval = Duration::from_secs(30); let task_progress = compactor_context.task_progress_manager.clone(); let periodic_event_update_interval = Duration::from_millis(1000); - let pull_task_ack = Arc::new(AtomicBool::new(true)); + + let max_task_parallelism: u32 = (compactor_context.compaction_executor.worker_num() as f32 + * compactor_context.storage_opts.compactor_max_task_multiplier) + .ceil() as u32; + let running_task_parallelism = Arc::new(AtomicU32::new(0)); + const MAX_PULL_TASK_COUNT: u32 = 4; - let max_pull_task_count = std::cmp::min( - compactor_context - .max_task_parallelism - .load(Ordering::SeqCst), - MAX_PULL_TASK_COUNT, - ); + let max_pull_task_count = std::cmp::min(max_task_parallelism, MAX_PULL_TASK_COUNT); assert_ge!( compactor_context.storage_opts.compactor_max_task_multiplier, @@ -321,7 +322,8 @@ pub fn start_compactor( // This outer loop is to recreate stream. 'start_stream: loop { // reset state - pull_task_ack.store(true, Ordering::SeqCst); + // pull_task_ack.store(true, Ordering::SeqCst); + let mut pull_task_ack = true; tokio::select! { // Wait for interval. _ = min_interval.tick() => {}, @@ -365,7 +367,7 @@ pub fn start_compactor( event_loop_iteration_now = Instant::now(); } - let pull_task_ack = pull_task_ack.clone(); + let running_task_parallelism = running_task_parallelism.clone(); let request_sender = request_sender.clone(); let event: Option> = tokio::select! { _ = periodic_event_interval.tick() => { @@ -389,9 +391,9 @@ pub fn start_compactor( let mut pending_pull_task_count = 0; - if pull_task_ack.load(Ordering::SeqCst) { + if pull_task_ack { // TODO: Compute parallelism on meta side - pending_pull_task_count = compactor_context.get_free_quota().max(max_pull_task_count); + pending_pull_task_count = (max_task_parallelism - running_task_parallelism.load(Ordering::SeqCst)).max(max_pull_task_count); if pending_pull_task_count > 0 { if let Err(e) = request_sender.send(SubscribeCompactionEventRequest { @@ -410,14 +412,14 @@ pub fn start_compactor( // re subscribe stream continue 'start_stream; } else { - pull_task_ack.store(false, Ordering::SeqCst); + pull_task_ack = false; } } } tracing::info!( - running_parallelism_count = %compactor_context.running_task_parallelism.load(Ordering::Relaxed), - pull_task_ack = %pull_task_ack.load(Ordering::Relaxed), + running_parallelism_count = %running_task_parallelism.load(Ordering::SeqCst), + pull_task_ack = %pull_task_ack, pending_pull_task_count = %pending_pull_task_count ); @@ -433,6 +435,28 @@ pub fn start_compactor( } }; + fn send_report_task_event( + compact_task: &CompactTask, + table_stats: TableStatsMap, + request_sender: &mpsc::UnboundedSender, + ) { + if let Err(e) = request_sender.send(SubscribeCompactionEventRequest { + event: Some(RequestEvent::ReportTask(ReportTask { + task_id: compact_task.task_id, + task_status: compact_task.task_status, + sorted_output_ssts: compact_task.sorted_output_ssts.clone(), + table_stats_change: to_prost_table_stats_map(table_stats), + })), + create_at: SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Clock may have gone backwards") + .as_millis() as u64, + }) { + let task_id = compact_task.task_id; + tracing::warn!(error = %e.as_report(), "Failed to report task {task_id:?}"); + } + } + match event { Some(Ok(SubscribeCompactionEventResponse { event, create_at })) => { let event = match event { @@ -454,120 +478,180 @@ pub fn start_compactor( let meta_client = hummock_meta_client.clone(); let sstable_object_id_manager = sstable_object_id_manager.clone(); let filter_key_extractor_manager = filter_key_extractor_manager.clone(); - executor.spawn(async move { - match event { - ResponseEvent::CompactTask(compact_task) => { - let (tx, rx) = tokio::sync::oneshot::channel(); - let task_id = compact_task.task_id; - shutdown.lock().unwrap().insert(task_id, tx); - let ((compact_task, table_stats), _memory_tracker) = match sstable_object_id_manager.add_watermark_object_id(None).await + + match event { + ResponseEvent::CompactTask(compact_task) => { + let parallelism = + calculate_task_parallelism(&compact_task, &context); + + assert_ne!(parallelism, 0, "splits cannot be empty"); + + if (max_task_parallelism + - running_task_parallelism.load(Ordering::SeqCst)) + < parallelism as u32 + { + tracing::warn!( + "Not enough core parallelism to serve the task {} task_parallelism {} running_task_parallelism {} max_task_parallelism {}", + compact_task.task_id, + parallelism, + max_task_parallelism, + running_task_parallelism.load(Ordering::Relaxed), + ); + let (compact_task, table_stats) = compact_done( + compact_task, + context.clone(), + vec![], + TaskStatus::NoAvailCpuResourceCanceled, + ); + + send_report_task_event( + &compact_task, + table_stats, + &request_sender, + ); + + continue 'start_stream; + } + + running_task_parallelism + .fetch_add(parallelism as u32, Ordering::SeqCst); + executor.spawn(async move { + let (tx, rx) = tokio::sync::oneshot::channel(); + let task_id = compact_task.task_id; + shutdown.lock().unwrap().insert(task_id, tx); + let ((compact_task, table_stats), _memory_tracker) = + match sstable_object_id_manager + .add_watermark_object_id(None) + .await { Ok(tracker_id) => { - let sstable_object_id_manager_clone = sstable_object_id_manager.clone(); + let sstable_object_id_manager_clone = + sstable_object_id_manager.clone(); let _guard = scopeguard::guard( (tracker_id, sstable_object_id_manager_clone), |(tracker_id, sstable_object_id_manager)| { - sstable_object_id_manager.remove_watermark_object_id(tracker_id); + sstable_object_id_manager + .remove_watermark_object_id(tracker_id); }, ); - compactor_runner::compact(context.clone(), compact_task, rx, Box::new(sstable_object_id_manager.clone()), filter_key_extractor_manager.clone()).await - }, + + compactor_runner::compact( + context.clone(), + compact_task, + rx, + Box::new(sstable_object_id_manager.clone()), + filter_key_extractor_manager.clone(), + ) + .await + } Err(err) => { tracing::warn!(error = %err.as_report(), "Failed to track pending SST object id"); let mut compact_task = compact_task; - compact_task.set_task_status(TaskStatus::TrackSstObjectIdFailed); - ((compact_task, HashMap::default()),None) + compact_task.set_task_status( + TaskStatus::TrackSstObjectIdFailed, + ); + ((compact_task, HashMap::default()), None) } }; - shutdown.lock().unwrap().remove(&task_id); - - let enable_check_compaction_result = context.storage_opts.check_compaction_result; - let need_check_task = !compact_task.sorted_output_ssts.is_empty() && compact_task.task_status() == TaskStatus::Success; - if let Err(e) = request_sender.send(SubscribeCompactionEventRequest { - event: Some(RequestEvent::ReportTask( - ReportTask { - task_id: compact_task.task_id, - task_status: compact_task.task_status, - sorted_output_ssts: compact_task.sorted_output_ssts.clone(), - table_stats_change:to_prost_table_stats_map(table_stats), - } - )), - create_at: SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .expect("Clock may have gone backwards") - .as_millis() as u64, - }) { - tracing::warn!(error = %e.as_report(), "Failed to report task {task_id:?}"); - } - if enable_check_compaction_result && need_check_task { - match check_compaction_result(&compact_task, context.clone()).await { - Err(e) => { - tracing::warn!(error = %e.as_report(), "Failed to check compaction task {}",compact_task.task_id); - }, - Ok(true) => (), - Ok(false) => { - panic!("Failed to pass consistency check for result of compaction task:\n{:?}", compact_task_to_string(&compact_task)); - } - } - } - } - ResponseEvent::VacuumTask(vacuum_task) => { - match Vacuum::handle_vacuum_task( - context.sstable_store.clone(), - &vacuum_task.sstable_object_ids, - ) - .await{ - Ok(_) => { - Vacuum::report_vacuum_task(vacuum_task, meta_client).await; - } + shutdown.lock().unwrap().remove(&task_id); + running_task_parallelism.fetch_sub(parallelism as u32, Ordering::SeqCst); + + send_report_task_event( + &compact_task, + table_stats, + &request_sender, + ); + + let enable_check_compaction_result = + context.storage_opts.check_compaction_result; + let need_check_task = !compact_task.sorted_output_ssts.is_empty() && compact_task.task_status() == TaskStatus::Success; + + if enable_check_compaction_result && need_check_task { + match check_compaction_result(&compact_task, context.clone()) + .await + { Err(e) => { - tracing::warn!(error = %e.as_report(), "Failed to vacuum task") - } - } - } - ResponseEvent::FullScanTask(full_scan_task) => { - match Vacuum::handle_full_scan_task(full_scan_task, context.sstable_store.clone()).await { - Ok((object_ids, total_object_count, total_object_size)) => { - Vacuum::report_full_scan_task(object_ids, total_object_count, total_object_size, meta_client).await; + tracing::warn!(error = %e.as_report(), "Failed to check compaction task {}",compact_task.task_id); } - Err(e) => { - tracing::warn!(error = %e.as_report(), "Failed to iter object"); + Ok(true) => (), + Ok(false) => { + panic!("Failed to pass consistency check for result of compaction task:\n{:?}", compact_task_to_string(&compact_task)); } } } - ResponseEvent::ValidationTask(validation_task) => { - validate_ssts( - validation_task, - context.sstable_store.clone(), - ) - .await; + }); + } + ResponseEvent::VacuumTask(vacuum_task) => { + executor.spawn(async move { + match Vacuum::handle_vacuum_task( + context.sstable_store.clone(), + &vacuum_task.sstable_object_ids, + ) + .await + { + Ok(_) => { + Vacuum::report_vacuum_task(vacuum_task, meta_client).await; + } + Err(e) => { + tracing::warn!(error = %e.as_report(), "Failed to vacuum task") + } } - ResponseEvent::CancelCompactTask(cancel_compact_task) => { - if let Some(tx) = shutdown - .lock() - .unwrap() - .remove(&cancel_compact_task.task_id) - { - if tx.send(()).is_err() { - tracing::warn!( - "Cancellation of compaction task failed. task_id: {}", - cancel_compact_task.task_id - ); - } - } else { - tracing::warn!( - "Attempting to cancel non-existent compaction task. task_id: {}", - cancel_compact_task.task_id - ); + }); + } + ResponseEvent::FullScanTask(full_scan_task) => { + executor.spawn(async move { + match Vacuum::handle_full_scan_task( + full_scan_task, + context.sstable_store.clone(), + ) + .await + { + Ok((object_ids, total_object_count, total_object_size)) => { + Vacuum::report_full_scan_task( + object_ids, + total_object_count, + total_object_size, + meta_client, + ) + .await; + } + Err(e) => { + tracing::warn!(error = %e.as_report(), "Failed to iter object"); } } - - ResponseEvent::PullTaskAck(_pull_task_ack) => { - // set flag - pull_task_ack.store(true, Ordering::SeqCst); + }); + } + ResponseEvent::ValidationTask(validation_task) => { + executor.spawn(async move { + validate_ssts(validation_task, context.sstable_store.clone()) + .await; + }); + } + ResponseEvent::CancelCompactTask(cancel_compact_task) => { + if let Some(tx) = shutdown + .lock() + .unwrap() + .remove(&cancel_compact_task.task_id) + { + if tx.send(()).is_err() { + tracing::warn!( + "Cancellation of compaction task failed. task_id: {}", + cancel_compact_task.task_id + ); } + } else { + tracing::warn!( + "Attempting to cancel non-existent compaction task. task_id: {}", + cancel_compact_task.task_id + ); } - }); + } + + ResponseEvent::PullTaskAck(_pull_task_ack) => { + // set flag + pull_task_ack = true; + } + } } Some(Err(e)) => { tracing::warn!("Failed to consume stream. {}", e.message()); diff --git a/src/storage/src/hummock/compactor/shared_buffer_compact.rs b/src/storage/src/hummock/compactor/shared_buffer_compact.rs index 5fafa263f6442..34860aab90d5c 100644 --- a/src/storage/src/hummock/compactor/shared_buffer_compact.rs +++ b/src/storage/src/hummock/compactor/shared_buffer_compact.rs @@ -20,7 +20,7 @@ use std::sync::{Arc, LazyLock}; use await_tree::InstrumentAwait; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::future::{try_join, try_join_all}; use futures::{stream, FutureExt, StreamExt, TryFutureExt}; use itertools::Itertools; diff --git a/src/storage/src/hummock/error.rs b/src/storage/src/hummock/error.rs index 10e7ffbf325a8..3019e65fc4e36 100644 --- a/src/storage/src/hummock/error.rs +++ b/src/storage/src/hummock/error.rs @@ -61,6 +61,8 @@ pub enum HummockErrorInner { SstableUploadError(String), #[error("Read backup error: {0}")] ReadBackupError(String), + #[error("Foyer error: {0}")] + FoyerError(anyhow::Error), #[error("Other error: {0}")] Other(String), } @@ -146,6 +148,10 @@ impl HummockError { HummockErrorInner::ReadBackupError(error.to_string()).into() } + pub fn foyer_error(error: anyhow::Error) -> HummockError { + HummockErrorInner::FoyerError(error).into() + } + pub fn other(error: impl ToString) -> HummockError { HummockErrorInner::Other(error.to_string()).into() } diff --git a/src/storage/src/hummock/event_handler/hummock_event_handler.rs b/src/storage/src/hummock/event_handler/hummock_event_handler.rs index 682586f789955..18c34f4c25440 100644 --- a/src/storage/src/hummock/event_handler/hummock_event_handler.rs +++ b/src/storage/src/hummock/event_handler/hummock_event_handler.rs @@ -1067,7 +1067,7 @@ mod tests { version_update_rx, pinned_version, None, - mock_sstable_store(), + mock_sstable_store().await, Arc::new(HummockStateStoreMetrics::unused()), &storage_opts, Arc::new(move |_, _| { @@ -1239,7 +1239,7 @@ mod tests { version_update_rx, initial_version.clone(), None, - mock_sstable_store(), + mock_sstable_store().await, Arc::new(HummockStateStoreMetrics::unused()), &default_opts_for_test(), Arc::new(|_, _| unreachable!("should not spawn upload task")), diff --git a/src/storage/src/hummock/event_handler/refiller.rs b/src/storage/src/hummock/event_handler/refiller.rs index 1c592fac85a2d..a6d9863152ee0 100644 --- a/src/storage/src/hummock/event_handler/refiller.rs +++ b/src/storage/src/hummock/event_handler/refiller.rs @@ -14,13 +14,12 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::future::poll_fn; -use std::ops::{Deref, Range}; +use std::ops::Range; use std::sync::{Arc, LazyLock}; use std::task::{ready, Poll}; use std::time::{Duration, Instant}; -use foyer::common::code::Key; -use foyer::common::range::RangeBoundsExt; +use foyer::{HybridCacheEntry, RangeBoundsExt, Storage, StorageWriter}; use futures::future::{join_all, try_join_all}; use futures::{Future, FutureExt}; use itertools::Itertools; @@ -30,18 +29,15 @@ use prometheus::{ register_int_gauge_with_registry, Histogram, HistogramVec, IntGauge, Registry, }; use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY; -use risingwave_common::util::iter_util::ZipEqFast; use risingwave_hummock_sdk::compaction_group::hummock_version_ext::SstDeltaInfo; use risingwave_hummock_sdk::{HummockSstableObjectId, KeyComparator}; use thiserror_ext::AsReport; use tokio::sync::Semaphore; use tokio::task::JoinHandle; -use crate::hummock::file_cache::preclude::*; use crate::hummock::local_version::pinned_version::PinnedVersion; use crate::hummock::{ - CachedBlock, FileCacheCompression, HummockError, HummockResult, Sstable, SstableBlockIndex, - SstableStoreRef, TableHolder, + Block, HummockError, HummockResult, Sstable, SstableBlockIndex, SstableStoreRef, TableHolder, }; use crate::monitor::StoreLocalStatistic; use crate::opts::StorageOpts; @@ -295,9 +291,7 @@ impl CacheRefiller { /// Clear the queue for cache refill and return an event that merges all pending cache refill events /// into a single event that takes the earliest and latest version. pub(crate) fn clear(&mut self) -> Option { - let Some(last_item) = self.queue.pop_back() else { - return None; - }; + let last_item = self.queue.pop_back()?; let mut event = last_item.event; while let Some(item) = self.queue.pop_back() { assert_eq!( @@ -392,7 +386,7 @@ impl CacheRefillTask { fn get_units_to_refill_by_inheritance( context: &CacheRefillContext, ssts: &[TableHolder], - parent_ssts: &[impl Deref], + parent_ssts: impl IntoIterator>>, ) -> HashSet { let mut res = HashSet::default(); @@ -561,12 +555,12 @@ impl CacheRefillTask { } }); let parent_ssts = match try_join_all(futures).await { - Ok(parent_ssts) => parent_ssts.into_iter().flatten().collect_vec(), + Ok(parent_ssts) => parent_ssts.into_iter().flatten(), Err(e) => { return tracing::error!(error = %e.as_report(), "get old meta from cache error") } }; - let units = Self::get_units_to_refill_by_inheritance(context, &holders, &parent_ssts); + let units = Self::get_units_to_refill_by_inheritance(context, &holders, parent_ssts); let ssts: HashMap = holders.into_iter().map(|meta| (meta.id, meta)).collect(); @@ -598,8 +592,7 @@ impl CacheRefillTask { let blocks = unit.blks.size().unwrap(); let mut tasks = vec![]; - let mut writers = Vec::with_capacity(blocks); - let mut ranges = Vec::with_capacity(blocks); + let mut contexts = Vec::with_capacity(blocks); let mut admits = 0; let (range_first, _) = sst.calculate_block_info(unit.blks.start); @@ -611,25 +604,22 @@ impl CacheRefillTask { .inc_by(range.size().unwrap() as u64); for blk in unit.blks { - let (range, _uncompressed_capacity) = sst.calculate_block_info(blk); + let (range, uncompressed_capacity) = sst.calculate_block_info(blk); let key = SstableBlockIndex { sst_id: sst.id, block_idx: blk as u64, }; - // see `CachedBlock::serialized_len()` - let mut writer = sstable_store - .data_file_cache() - .writer(key, key.serialized_len() + 1 + 8 + range.size().unwrap()); + + let mut writer = sstable_store.block_cache().store().writer(key); if writer.judge() { admits += 1; } - writers.push(writer); - ranges.push(range); + contexts.push((writer, range, uncompressed_capacity)) } - if admits as f64 / writers.len() as f64 >= threshold { + if admits as f64 / contexts.len() as f64 >= threshold { let task = async move { GLOBAL_CACHE_REFILL_METRICS.data_refill_attempts_total.inc(); @@ -646,23 +636,20 @@ impl CacheRefillTask { .read(&sstable_store.get_sst_data_path(sst.id), range.clone()) .await?; let mut futures = vec![]; - for (mut writer, r) in writers.into_iter().zip_eq_fast(ranges) { + for (mut writer, r, uncompressed_capacity) in contexts { let offset = r.start - range.start; let len = r.end - r.start; let bytes = data.slice(offset..offset + len); let future = async move { - let value = CachedBlock::Fetched { - bytes, - uncompressed_capacity: writer.weight() - writer.key().serialized_len(), - }; - + let value = Box::new(Block::decode(bytes, uncompressed_capacity)?); writer.force(); - // TODO(MrCroxx): compress if raw is not compressed? - // skip compression for it may already be compressed. - writer.set_compression(FileCacheCompression::None); - let res = writer.finish(value).await.map_err(HummockError::file_cache); - if matches!(res, Ok(true)) { + if writer + .finish(value) + .await + .map_err(|e| HummockError::foyer_error(e.into()))? + .is_some() + { GLOBAL_CACHE_REFILL_METRICS .data_refill_success_bytes .inc_by(len as u64); @@ -670,7 +657,7 @@ impl CacheRefillTask { .data_refill_block_success_total .inc(); } - res + Ok::<_, HummockError>(()) }; futures.push(future); } diff --git a/src/storage/src/hummock/file_cache/mod.rs b/src/storage/src/hummock/file_cache/mod.rs deleted file mode 100644 index 2f2ac826410b0..0000000000000 --- a/src/storage/src/hummock/file_cache/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -mod recent_filter; -mod store; - -pub use recent_filter::*; -pub use store::*; diff --git a/src/storage/src/hummock/file_cache/store.rs b/src/storage/src/hummock/file_cache/store.rs deleted file mode 100644 index dcf121966886c..0000000000000 --- a/src/storage/src/hummock/file_cache/store.rs +++ /dev/null @@ -1,803 +0,0 @@ -// Copyright 2024 RisingWave Labs -// -// Licensed 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. - -use std::fmt::Debug; -use std::hash::Hash; -use std::path::PathBuf; -use std::sync::Arc; - -use bytes::{Buf, BufMut, Bytes}; -use foyer::common::code::{CodingResult, Cursor, Key, Value}; -use foyer::intrusive::eviction::lfu::LfuConfig; -use foyer::storage::admission::rated_ticket::RatedTicketAdmissionPolicy; -use foyer::storage::admission::AdmissionPolicy; -use foyer::storage::compress::Compression; -use foyer::storage::device::fs::FsDeviceConfig; -pub use foyer::storage::metrics::set_metrics_registry as set_foyer_metrics_registry; -use foyer::storage::reinsertion::ReinsertionPolicy; -use foyer::storage::runtime::{ - RuntimeConfig, RuntimeLazyStore, RuntimeLazyStoreConfig, RuntimeLazyStoreWriter, -}; -use foyer::storage::storage::{Storage, StorageWriter}; -use foyer::storage::store::{LfuFsStoreConfig, NoneStore, NoneStoreWriter}; -use risingwave_hummock_sdk::HummockSstableObjectId; - -use crate::hummock::{Block, HummockResult, Sstable, SstableMeta}; - -pub mod preclude { - pub use foyer::storage::storage::{ - AsyncStorageExt, ForceStorageExt, Storage, StorageExt, StorageWriter, - }; -} - -pub type Result = core::result::Result; - -pub type FileCacheEvictionConfig = foyer::intrusive::eviction::lfu::LfuConfig; -pub type DeviceConfig = foyer::storage::device::fs::FsDeviceConfig; - -pub type FileCacheResult = foyer::storage::error::Result; -pub type FileCacheError = foyer::storage::error::Error; -pub type FileCacheCompression = foyer::storage::compress::Compression; - -fn copy(src: impl AsRef<[u8]>, mut dst: impl BufMut + AsRef<[u8]>) -> usize { - let n = std::cmp::min(src.as_ref().len(), dst.as_ref().len()); - dst.put_slice(&src.as_ref()[..n]); - n -} - -#[derive(Debug)] -pub struct FileCacheConfig -where - K: Key, - V: Value, -{ - pub name: String, - pub dir: PathBuf, - pub capacity: usize, - pub file_capacity: usize, - pub device_align: usize, - pub device_io_size: usize, - pub flushers: usize, - pub reclaimers: usize, - pub recover_concurrency: usize, - pub lfu_window_to_cache_size_ratio: usize, - pub lfu_tiny_lru_capacity_ratio: f64, - pub insert_rate_limit: usize, - pub catalog_bits: usize, - pub admissions: Vec>>, - pub reinsertions: Vec>>, - pub compression: Compression, -} - -impl Clone for FileCacheConfig -where - K: Key, - V: Value, -{ - fn clone(&self) -> Self { - Self { - name: self.name.clone(), - dir: self.dir.clone(), - capacity: self.capacity, - file_capacity: self.file_capacity, - device_align: self.device_align, - device_io_size: self.device_io_size, - flushers: self.flushers, - reclaimers: self.reclaimers, - recover_concurrency: self.recover_concurrency, - lfu_window_to_cache_size_ratio: self.lfu_window_to_cache_size_ratio, - lfu_tiny_lru_capacity_ratio: self.lfu_tiny_lru_capacity_ratio, - insert_rate_limit: self.insert_rate_limit, - catalog_bits: self.catalog_bits, - admissions: self.admissions.clone(), - reinsertions: self.reinsertions.clone(), - compression: self.compression, - } - } -} - -#[derive(Debug)] -pub enum FileCacheWriter -where - K: Key, - V: Value, -{ - Foyer { - writer: RuntimeLazyStoreWriter, - }, - None { - writer: NoneStoreWriter, - }, -} - -impl From> for FileCacheWriter -where - K: Key, - V: Value, -{ - fn from(writer: RuntimeLazyStoreWriter) -> Self { - Self::Foyer { writer } - } -} - -impl From> for FileCacheWriter -where - K: Key, - V: Value, -{ - fn from(writer: NoneStoreWriter) -> Self { - Self::None { writer } - } -} - -impl StorageWriter for FileCacheWriter -where - K: Key, - V: Value, -{ - type Key = K; - type Value = V; - - fn key(&self) -> &Self::Key { - match self { - FileCacheWriter::Foyer { writer } => writer.key(), - FileCacheWriter::None { writer } => writer.key(), - } - } - - fn weight(&self) -> usize { - match self { - FileCacheWriter::Foyer { writer } => writer.weight(), - FileCacheWriter::None { writer } => writer.weight(), - } - } - - fn judge(&mut self) -> bool { - match self { - FileCacheWriter::Foyer { writer } => writer.judge(), - FileCacheWriter::None { writer } => writer.judge(), - } - } - - fn force(&mut self) { - match self { - FileCacheWriter::Foyer { writer } => writer.force(), - FileCacheWriter::None { writer } => writer.force(), - } - } - - async fn finish(self, value: Self::Value) -> FileCacheResult { - match self { - FileCacheWriter::Foyer { writer } => writer.finish(value).await, - FileCacheWriter::None { writer } => writer.finish(value).await, - } - } - - fn compression(&self) -> Compression { - match self { - FileCacheWriter::Foyer { writer } => writer.compression(), - FileCacheWriter::None { writer } => writer.compression(), - } - } - - fn set_compression(&mut self, compression: Compression) { - match self { - FileCacheWriter::Foyer { writer } => writer.set_compression(compression), - FileCacheWriter::None { writer } => writer.set_compression(compression), - } - } -} - -#[derive(Debug)] -pub enum FileCache -where - K: Key, - V: Value, -{ - Foyer { store: RuntimeLazyStore }, - None { store: NoneStore }, -} - -impl Clone for FileCache -where - K: Key, - V: Value, -{ - fn clone(&self) -> Self { - match self { - Self::Foyer { store } => Self::Foyer { - store: store.clone(), - }, - Self::None { store } => Self::None { - store: store.clone(), - }, - } - } -} - -impl FileCache -where - K: Key, - V: Value, -{ - pub fn none() -> Self { - Self::None { - store: NoneStore::default(), - } - } - - pub fn is_none(&self) -> bool { - matches!(self, FileCache::None { .. }) - } -} - -impl Storage for FileCache -where - K: Key, - V: Value, -{ - type Config = FileCacheConfig; - type Key = K; - type Value = V; - type Writer = FileCacheWriter; - - async fn open(config: Self::Config) -> FileCacheResult { - let mut admissions = config.admissions; - if config.insert_rate_limit > 0 { - admissions.push(Arc::new(RatedTicketAdmissionPolicy::new( - config.insert_rate_limit, - ))); - } - - let c = RuntimeLazyStoreConfig { - store: LfuFsStoreConfig { - name: config.name.clone(), - eviction_config: LfuConfig { - window_to_cache_size_ratio: config.lfu_window_to_cache_size_ratio, - tiny_lru_capacity_ratio: config.lfu_tiny_lru_capacity_ratio, - }, - device_config: FsDeviceConfig { - dir: config.dir, - capacity: config.capacity, - file_capacity: config.file_capacity, - align: config.device_align, - io_size: config.device_io_size, - }, - catalog_bits: config.catalog_bits, - admissions, - reinsertions: config.reinsertions, - flushers: config.flushers, - reclaimers: config.reclaimers, - clean_region_threshold: config.reclaimers + config.reclaimers / 2, - recover_concurrency: config.recover_concurrency, - compression: config.compression, - } - .into(), - runtime: RuntimeConfig { - worker_threads: None, - thread_name: Some(config.name), - }, - }; - let store = RuntimeLazyStore::open(c).await?; - Ok(Self::Foyer { store }) - } - - fn is_ready(&self) -> bool { - match self { - FileCache::Foyer { store } => store.is_ready(), - FileCache::None { store } => store.is_ready(), - } - } - - async fn close(&self) -> FileCacheResult<()> { - match self { - FileCache::Foyer { store } => store.close().await, - FileCache::None { store } => store.close().await, - } - } - - fn writer(&self, key: Self::Key, weight: usize) -> Self::Writer { - match self { - FileCache::Foyer { store } => store.writer(key, weight).into(), - FileCache::None { store } => store.writer(key, weight).into(), - } - } - - fn exists(&self, key: &Self::Key) -> FileCacheResult { - match self { - FileCache::Foyer { store } => store.exists(key), - FileCache::None { store } => store.exists(key), - } - } - - async fn lookup(&self, key: &Self::Key) -> FileCacheResult> { - match self { - FileCache::Foyer { store } => store.lookup(key).await, - FileCache::None { store } => store.lookup(key).await, - } - } - - fn remove(&self, key: &Self::Key) -> FileCacheResult { - match self { - FileCache::Foyer { store } => store.remove(key), - FileCache::None { store } => store.remove(key), - } - } - - fn clear(&self) -> FileCacheResult<()> { - match self { - FileCache::Foyer { store } => store.clear(), - FileCache::None { store } => store.clear(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub struct SstableBlockIndex { - pub sst_id: HummockSstableObjectId, - pub block_idx: u64, -} - -impl Key for SstableBlockIndex { - type Cursor = SstableBlockIndexCursor; - - fn serialized_len(&self) -> usize { - 8 + 8 // sst_id (8B) + block_idx (8B) - } - - fn read(mut buf: &[u8]) -> CodingResult { - let sst_id = buf.get_u64(); - let block_idx = buf.get_u64(); - Ok(Self { sst_id, block_idx }) - } - - fn into_cursor(self) -> Self::Cursor { - SstableBlockIndexCursor::new(self) - } -} - -#[derive(Debug)] -pub struct SstableBlockIndexCursor { - inner: SstableBlockIndex, - pos: u8, -} - -impl SstableBlockIndexCursor { - pub fn new(inner: SstableBlockIndex) -> Self { - Self { inner, pos: 0 } - } -} - -impl std::io::Read for SstableBlockIndexCursor { - fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result { - let pos = self.pos; - if self.pos < 8 { - self.pos += copy( - &self.inner.sst_id.to_be_bytes()[self.pos as usize..], - &mut buf, - ) as u8; - } - if self.pos < 16 { - self.pos += copy( - &self.inner.block_idx.to_be_bytes()[self.pos as usize - 8..], - &mut buf, - ) as u8; - } - let n = (self.pos - pos) as usize; - Ok(n) - } -} - -impl Cursor for SstableBlockIndexCursor { - type T = SstableBlockIndex; - - fn into_inner(self) -> Self::T { - self.inner - } -} - -/// [`CachedBlock`] uses different coding for writing to use/bypass compression. -/// -/// But when reading, it will always be `Loaded`. -#[derive(Debug, Clone)] -pub enum CachedBlock { - Loaded { - block: Box, - }, - Fetched { - bytes: Bytes, - uncompressed_capacity: usize, - }, -} - -impl CachedBlock { - pub fn should_compress(&self) -> bool { - match self { - CachedBlock::Loaded { .. } => true, - // TODO(MrCroxx): based on block original compression algorithm? - CachedBlock::Fetched { .. } => false, - } - } - - pub fn try_into_block(self) -> HummockResult> { - let block = match self { - CachedBlock::Loaded { block } => block, - // for the block was not loaded yet (refill + inflight), we need to decode it. - // TODO(MrCroxx): avoid decode twice? - CachedBlock::Fetched { - bytes, - uncompressed_capacity, - } => { - let block = Block::decode(bytes, uncompressed_capacity)?; - Box::new(block) - } - }; - Ok(block) - } -} - -impl Value for CachedBlock { - type Cursor = CachedBlockCursor; - - fn serialized_len(&self) -> usize { - 1 /* type */ + match self { - CachedBlock::Loaded { block } => block.raw().len(), - CachedBlock::Fetched { bytes, uncompressed_capacity: _ } => 8 + bytes.len(), - } - } - - fn read(mut buf: &[u8]) -> CodingResult { - let v = buf.get_u8(); - let res = match v { - 0 => { - let data = Bytes::copy_from_slice(buf); - let block = Block::decode_from_raw(data); - let block = Box::new(block); - Self::Loaded { block } - } - 1 => { - let uncompressed_capacity = buf.get_u64() as usize; - let data = Bytes::copy_from_slice(buf); - let block = Block::decode(data, uncompressed_capacity)?; - let block = Box::new(block); - Self::Loaded { block } - } - _ => unreachable!(), - }; - Ok(res) - } - - fn into_cursor(self) -> Self::Cursor { - CachedBlockCursor::new(self) - } -} - -#[derive(Debug)] -pub struct CachedBlockCursor { - inner: CachedBlock, - pos: usize, -} - -impl CachedBlockCursor { - pub fn new(inner: CachedBlock) -> Self { - Self { inner, pos: 0 } - } -} - -impl std::io::Read for CachedBlockCursor { - fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result { - let pos = self.pos; - match &self.inner { - CachedBlock::Loaded { block } => { - if self.pos < 1 { - self.pos += copy([0], &mut buf); - } - self.pos += copy(&block.raw()[self.pos - 1..], &mut buf); - } - CachedBlock::Fetched { - bytes, - uncompressed_capacity, - } => { - if self.pos < 1 { - self.pos += copy([1], &mut buf); - } - if self.pos < 9 { - self.pos += copy( - &uncompressed_capacity.to_be_bytes()[self.pos - 1..], - &mut buf, - ); - } - self.pos += copy(&bytes[self.pos - 9..], &mut buf); - } - } - let n = self.pos - pos; - Ok(n) - } -} - -impl Cursor for CachedBlockCursor { - type T = CachedBlock; - - fn into_inner(self) -> Self::T { - self.inner - } -} - -impl Value for Box { - type Cursor = BoxBlockCursor; - - fn serialized_len(&self) -> usize { - self.raw().len() - } - - fn read(buf: &[u8]) -> CodingResult { - let data = Bytes::copy_from_slice(buf); - let block = Block::decode_from_raw(data); - let block = Box::new(block); - Ok(block) - } - - fn into_cursor(self) -> Self::Cursor { - BoxBlockCursor::new(self) - } -} - -#[derive(Debug)] -pub struct BoxBlockCursor { - inner: Box, - pos: usize, -} - -impl BoxBlockCursor { - pub fn new(inner: Box) -> Self { - Self { inner, pos: 0 } - } -} - -impl std::io::Read for BoxBlockCursor { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let pos = self.pos; - self.pos += copy(&self.inner.raw()[self.pos..], buf); - let n = self.pos - pos; - Ok(n) - } -} - -impl Cursor for BoxBlockCursor { - type T = Box; - - fn into_inner(self) -> Self::T { - self.inner - } -} - -#[derive(Debug)] -pub struct CachedSstable(Arc); - -impl Clone for CachedSstable { - fn clone(&self) -> Self { - Self(Arc::clone(&self.0)) - } -} - -impl From> for CachedSstable { - fn from(value: Box) -> Self { - Self(Arc::new(*value)) - } -} - -impl From for Box { - fn from(value: CachedSstable) -> Self { - Box::new(Arc::unwrap_or_clone(value.0)) - } -} - -impl CachedSstable { - pub fn into_inner(self) -> Arc { - self.0 - } -} - -impl Value for CachedSstable { - type Cursor = CachedSstableCursor; - - fn weight(&self) -> usize { - self.0.estimate_size() - } - - fn serialized_len(&self) -> usize { - 8 + self.0.meta.encoded_size() // id (8B) + meta size - } - - fn read(mut buf: &[u8]) -> CodingResult { - let id = buf.get_u64(); - let meta = SstableMeta::decode(buf).unwrap(); - let sstable = Arc::new(Sstable::new(id, meta)); - Ok(Self(sstable)) - } - - fn into_cursor(self) -> Self::Cursor { - CachedSstableCursor::new(self) - } -} - -#[derive(Debug)] -pub struct CachedSstableCursor { - inner: CachedSstable, - pos: usize, - /// store pre-encoded bytes here, for it's hard to encode JIT - bytes: Vec, -} - -impl CachedSstableCursor { - pub fn new(inner: CachedSstable) -> Self { - let mut bytes = vec![]; - bytes.put_u64(inner.0.id); - inner.0.meta.encode_to(&mut bytes); - Self { - inner, - bytes, - pos: 0, - } - } -} - -impl std::io::Read for CachedSstableCursor { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let pos = self.pos; - self.pos += copy(&self.bytes[self.pos..], buf); - let n = self.pos - pos; - Ok(n) - } -} - -impl Cursor for CachedSstableCursor { - type T = CachedSstable; - - fn into_inner(self) -> Self::T { - self.inner - } -} - -#[cfg(test)] -mod tests { - use risingwave_common::catalog::TableId; - use risingwave_common::util::epoch::test_epoch; - use risingwave_hummock_sdk::key::FullKey; - - use super::*; - use crate::hummock::{BlockBuilder, BlockBuilderOptions, BlockMeta, CompressionAlgorithm}; - - pub fn construct_full_key_struct_for_test( - table_id: u32, - table_key: &[u8], - epoch: u64, - ) -> FullKey<&[u8]> { - FullKey::for_test(TableId::new(table_id), table_key, epoch) - } - - fn block_for_test() -> Box { - let options = BlockBuilderOptions { - compression_algorithm: CompressionAlgorithm::Lz4, - ..Default::default() - }; - - let mut builder = BlockBuilder::new(options); - builder.add_for_test( - construct_full_key_struct_for_test(0, b"k1", test_epoch(1)), - b"v01", - ); - builder.add_for_test( - construct_full_key_struct_for_test(0, b"k2", test_epoch(2)), - b"v02", - ); - builder.add_for_test( - construct_full_key_struct_for_test(0, b"k3", test_epoch(3)), - b"v03", - ); - builder.add_for_test( - construct_full_key_struct_for_test(0, b"k4", test_epoch(4)), - b"v04", - ); - - let uncompress = builder.uncompressed_block_size(); - Box::new(Block::decode(builder.build().to_vec().into(), uncompress).unwrap()) - } - - fn sstable_for_test() -> Sstable { - Sstable::new( - 114514, - SstableMeta { - block_metas: vec![ - BlockMeta { - smallest_key: b"0-smallest-key".to_vec(), - len: 100, - ..Default::default() - }, - BlockMeta { - smallest_key: b"5-some-key".to_vec(), - offset: 100, - len: 100, - ..Default::default() - }, - ], - bloom_filter: b"0123456789012345".to_vec(), - estimated_size: 123, - key_count: 123, - smallest_key: b"0-smallest-key".to_vec(), - largest_key: b"9-largest-key".to_vec(), - meta_offset: 123, - monotonic_tombstone_events: vec![], - version: 2, - }, - ) - } - - #[test] - fn test_cursor() { - { - let block = block_for_test(); - let mut cursor = block.into_cursor(); - let mut buf = vec![]; - std::io::copy(&mut cursor, &mut buf).unwrap(); - let target = cursor.into_inner(); - let block = Box::::read(&buf[..]).unwrap(); - assert_eq!(target.raw(), block.raw()); - } - - { - let sstable: CachedSstable = Box::new(sstable_for_test()).into(); - let mut cursor = sstable.into_cursor(); - let mut buf = vec![]; - std::io::copy(&mut cursor, &mut buf).unwrap(); - let target = cursor.into_inner(); - let sstable = CachedSstable::read(&buf[..]).unwrap(); - assert_eq!(target.0.id, sstable.0.id); - assert_eq!(target.0.meta, sstable.0.meta); - } - - { - let cached = CachedBlock::Loaded { - block: block_for_test(), - }; - let mut cursor = cached.into_cursor(); - let mut buf = vec![]; - std::io::copy(&mut cursor, &mut buf).unwrap(); - let target = cursor.into_inner(); - let cached = CachedBlock::read(&buf[..]).unwrap(); - let target = match target { - CachedBlock::Loaded { block } => block, - CachedBlock::Fetched { .. } => panic!(), - }; - let block = match cached { - CachedBlock::Loaded { block } => block, - CachedBlock::Fetched { .. } => panic!(), - }; - assert_eq!(target.raw(), block.raw()); - } - - { - let index = SstableBlockIndex { - sst_id: 114, - block_idx: 514, - }; - let mut cursor = index.into_cursor(); - let mut buf = vec![]; - std::io::copy(&mut cursor, &mut buf).unwrap(); - let target = cursor.into_inner(); - let index = SstableBlockIndex::read(&buf[..]).unwrap(); - assert_eq!(target, index); - } - } -} diff --git a/src/storage/src/hummock/iterator/backward_concat.rs b/src/storage/src/hummock/iterator/backward_concat.rs index 656d83e25b5eb..71b235aaa1c47 100644 --- a/src/storage/src/hummock/iterator/backward_concat.rs +++ b/src/storage/src/hummock/iterator/backward_concat.rs @@ -33,7 +33,7 @@ mod tests { #[tokio::test] async fn test_backward_concat_iterator() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), @@ -92,7 +92,7 @@ mod tests { #[tokio::test] async fn test_backward_concat_seek_exists() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table1 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), @@ -169,7 +169,7 @@ mod tests { #[tokio::test] async fn test_backward_concat_seek_not_exists() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), diff --git a/src/storage/src/hummock/iterator/backward_merge.rs b/src/storage/src/hummock/iterator/backward_merge.rs index f8464a777e078..02e47fe7ec641 100644 --- a/src/storage/src/hummock/iterator/backward_merge.rs +++ b/src/storage/src/hummock/iterator/backward_merge.rs @@ -23,7 +23,7 @@ mod test { #[tokio::test] async fn test_backward_merge_basic() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -76,7 +76,7 @@ mod test { #[tokio::test] async fn test_backward_merge_seek() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -151,7 +151,7 @@ mod test { #[tokio::test] async fn test_backward_merge_invalidate_reset() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), diff --git a/src/storage/src/hummock/iterator/backward_user.rs b/src/storage/src/hummock/iterator/backward_user.rs index 39e6f7eafa256..2418a086c4a39 100644 --- a/src/storage/src/hummock/iterator/backward_user.rs +++ b/src/storage/src/hummock/iterator/backward_user.rs @@ -313,12 +313,11 @@ mod tests { }; use crate::hummock::iterator::MergeIterator; use crate::hummock::test_utils::gen_test_sstable; - use crate::hummock::value::HummockValue; use crate::hummock::{BackwardSstableIterator, SstableStoreRef, TableHolder}; #[tokio::test] async fn test_backward_user_basic() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -370,7 +369,7 @@ mod tests { #[tokio::test] async fn test_backward_user_seek() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -441,7 +440,7 @@ mod tests { #[tokio::test] async fn test_backward_user_delete() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ (1, 300, HummockValue::delete()), @@ -480,7 +479,7 @@ mod tests { // left..=end #[tokio::test] async fn test_backward_user_range_inclusive() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ (0, 200, HummockValue::delete()), @@ -560,7 +559,7 @@ mod tests { // left..end #[tokio::test] async fn test_backward_user_range() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ (0, 200, HummockValue::delete()), @@ -637,7 +636,7 @@ mod tests { // ..=right #[tokio::test] async fn test_backward_user_range_to_inclusive() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ (0, 200, HummockValue::delete()), @@ -713,7 +712,7 @@ mod tests { // left.. #[tokio::test] async fn test_backward_user_range_from() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ (0, 200, HummockValue::delete()), @@ -909,7 +908,7 @@ mod tests { prev_time = time.next_epoch(); } } - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let sst = gen_test_sstable( default_builder_opt_for_test(), 0, @@ -1057,7 +1056,7 @@ mod tests { #[tokio::test] async fn test_min_epoch() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_with_incr_epoch( 0, default_builder_opt_for_test(), diff --git a/src/storage/src/hummock/iterator/change_log.rs b/src/storage/src/hummock/iterator/change_log.rs index f2b39c524cb18..7f4d17af6d740 100644 --- a/src/storage/src/hummock/iterator/change_log.rs +++ b/src/storage/src/hummock/iterator/change_log.rs @@ -56,10 +56,10 @@ struct ChangeLogIteratorInner< /// Indicate whether the current new value is delete. is_new_value_delete: bool, - /// Whether Indicate whether the current `old_value_iter` represents the old value in ChangeLogValue + /// Whether Indicate whether the current `old_value_iter` represents the old value in `ChangeLogValue` is_old_value_set: bool, - /// Whether the iterator is currently pointing at a valid key with ChangeLogValue + /// Whether the iterator is currently pointing at a valid key with `ChangeLogValue` is_current_pos_valid: bool, } diff --git a/src/storage/src/hummock/iterator/forward_concat.rs b/src/storage/src/hummock/iterator/forward_concat.rs index 425c181a77332..8837f0ae1ad5d 100644 --- a/src/storage/src/hummock/iterator/forward_concat.rs +++ b/src/storage/src/hummock/iterator/forward_concat.rs @@ -32,7 +32,7 @@ mod tests { #[tokio::test] async fn test_concat_iterator() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), @@ -93,7 +93,7 @@ mod tests { #[tokio::test] async fn test_concat_seek() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), @@ -168,7 +168,7 @@ mod tests { #[tokio::test] async fn test_concat_seek_not_exists() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), diff --git a/src/storage/src/hummock/iterator/forward_merge.rs b/src/storage/src/hummock/iterator/forward_merge.rs index 6e360d7f18c9c..60cb9a84f034b 100644 --- a/src/storage/src/hummock/iterator/forward_merge.rs +++ b/src/storage/src/hummock/iterator/forward_merge.rs @@ -110,7 +110,7 @@ mod test { #[tokio::test] async fn test_merge_invalidate_reset() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let read_options = Arc::new(SstableIteratorReadOptions::default()); let table0 = gen_iterator_test_sstable_info( 0, diff --git a/src/storage/src/hummock/iterator/forward_user.rs b/src/storage/src/hummock/iterator/forward_user.rs index 8a68c10d0b515..32456eaa27ca5 100644 --- a/src/storage/src/hummock/iterator/forward_user.rs +++ b/src/storage/src/hummock/iterator/forward_user.rs @@ -274,12 +274,11 @@ mod tests { SstableIterator, SstableIteratorReadOptions, SstableIteratorType, }; use crate::hummock::sstable_store::SstableStoreRef; - use crate::hummock::value::HummockValue; use crate::hummock::TableHolder; #[tokio::test] async fn test_basic() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let read_options = Arc::new(SstableIteratorReadOptions::default()); let table0 = gen_iterator_test_sstable_base( 0, @@ -333,7 +332,7 @@ mod tests { #[tokio::test] async fn test_seek() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -408,7 +407,7 @@ mod tests { #[tokio::test] async fn test_delete() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ @@ -475,7 +474,7 @@ mod tests { // left..=end #[tokio::test] async fn test_range_inclusive() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let table = generate_test_data(sstable_store.clone()).await; let read_options = Arc::new(SstableIteratorReadOptions::default()); @@ -537,7 +536,7 @@ mod tests { // left..end #[tokio::test] async fn test_range() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let kv_pairs = vec![ (0, 200, HummockValue::delete()), @@ -615,7 +614,7 @@ mod tests { // ..=right #[tokio::test] async fn test_range_to_inclusive() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let table = generate_test_data(sstable_store.clone()).await; @@ -680,7 +679,7 @@ mod tests { // left.. #[tokio::test] async fn test_range_from() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // key=[idx, epoch], value let table = generate_test_data(sstable_store.clone()).await; let read_options = Arc::new(SstableIteratorReadOptions::default()); @@ -747,7 +746,7 @@ mod tests { #[tokio::test] async fn test_min_epoch() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let read_options = Arc::new(SstableIteratorReadOptions::default()); let table0 = gen_iterator_test_sstable_with_incr_epoch( 0, diff --git a/src/storage/src/hummock/iterator/test_utils.rs b/src/storage/src/hummock/iterator/test_utils.rs index 2659029650f03..e9b1c7498612b 100644 --- a/src/storage/src/hummock/iterator/test_utils.rs +++ b/src/storage/src/hummock/iterator/test_utils.rs @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::iter::Iterator; use std::sync::Arc; use bytes::Bytes; +use foyer::HybridCacheBuilder; use itertools::Itertools; use risingwave_common::catalog::TableId; -use risingwave_common::config::{EvictionConfig, MetricLevel, ObjectStoreConfig}; +use risingwave_common::config::{MetricLevel, ObjectStoreConfig}; use risingwave_common::hash::VirtualNode; use risingwave_common::util::epoch::test_epoch; use risingwave_hummock_sdk::key::{prefix_slice_with_vnode, FullKey, TableKey, UserKey}; @@ -36,8 +36,8 @@ use crate::hummock::test_utils::{ gen_test_sstable, gen_test_sstable_info, gen_test_sstable_with_range_tombstone, }; use crate::hummock::{ - FileCache, HummockValue, SstableBuilderOptions, SstableIterator, SstableIteratorType, - SstableStoreConfig, SstableStoreRef, TableHolder, + HummockValue, SstableBuilderOptions, SstableIterator, SstableIteratorType, SstableStoreConfig, + SstableStoreRef, TableHolder, }; use crate::monitor::{global_hummock_state_store_metrics, ObjectStoreMetrics}; @@ -55,33 +55,44 @@ macro_rules! assert_bytes_eq { pub const TEST_KEYS_COUNT: usize = 10; -pub fn mock_sstable_store() -> SstableStoreRef { +pub async fn mock_sstable_store() -> SstableStoreRef { mock_sstable_store_with_object_store(Arc::new(ObjectStoreImpl::InMem( InMemObjectStore::new().monitored( Arc::new(ObjectStoreMetrics::unused()), Arc::new(ObjectStoreConfig::default()), ), ))) + .await } -pub fn mock_sstable_store_with_object_store(store: ObjectStoreRef) -> SstableStoreRef { +pub async fn mock_sstable_store_with_object_store(store: ObjectStoreRef) -> SstableStoreRef { let path = "test".to_string(); + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(64 << 20) + .with_shards(2) + .storage() + .build() + .await + .unwrap(); + let block_cache_v2 = HybridCacheBuilder::new() + .memory(64 << 20) + .with_shards(2) + .storage() + .build() + .await + .unwrap(); Arc::new(SstableStore::new(SstableStoreConfig { store, path, - block_cache_capacity: 64 << 20, - block_cache_shard_num: 2, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_capacity: 64 << 20, - meta_cache_shard_num: 2, - meta_cache_eviction: EvictionConfig::for_test(), + prefetch_buffer_capacity: 64 << 20, max_prefetch_block_number: 16, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: Arc::new(global_hummock_state_store_metrics(MetricLevel::Disabled)), + + meta_cache_v2, + block_cache_v2, })) } @@ -227,7 +238,7 @@ pub async fn gen_merge_iterator_interleave_test_sstable_iters( key_count: usize, count: usize, ) -> Vec { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let mut result = vec![]; for i in 0..count { let table = gen_iterator_test_sstable_base( diff --git a/src/storage/src/hummock/mod.rs b/src/storage/src/hummock/mod.rs index 5cf2d831d325e..21eb4a13e8c31 100644 --- a/src/storage/src/hummock/mod.rs +++ b/src/storage/src/hummock/mod.rs @@ -25,9 +25,6 @@ use risingwave_pb::hummock::SstableInfo; pub mod block_cache; pub use block_cache::*; -pub mod file_cache; -pub use file_cache::*; - pub mod sstable; pub use sstable::*; @@ -53,6 +50,9 @@ mod validator; pub mod value; pub mod write_limiter; +pub mod recent_filter; +pub use recent_filter::*; + pub mod block_stream; pub use error::*; diff --git a/src/storage/src/hummock/file_cache/recent_filter.rs b/src/storage/src/hummock/recent_filter.rs similarity index 100% rename from src/storage/src/hummock/file_cache/recent_filter.rs rename to src/storage/src/hummock/recent_filter.rs diff --git a/src/storage/src/hummock/shared_buffer/shared_buffer_batch.rs b/src/storage/src/hummock/shared_buffer/shared_buffer_batch.rs index ecffa11871000..ba9d3311b2e0b 100644 --- a/src/storage/src/hummock/shared_buffer/shared_buffer_batch.rs +++ b/src/storage/src/hummock/shared_buffer/shared_buffer_batch.rs @@ -968,7 +968,7 @@ impl DeleteRangeIterator for SharedBufferDeleteRangeIterator { #[cfg(test)] mod tests { - use std::ops::Bound::{Excluded, Included}; + use std::ops::Bound::Excluded; use itertools::{zip_eq, Itertools}; use risingwave_common::util::epoch::{test_epoch, EpochExt}; diff --git a/src/storage/src/hummock/sstable/backward_sstable_iterator.rs b/src/storage/src/hummock/sstable/backward_sstable_iterator.rs index 58c2439771cff..d923750de7859 100644 --- a/src/storage/src/hummock/sstable/backward_sstable_iterator.rs +++ b/src/storage/src/hummock/sstable/backward_sstable_iterator.rs @@ -15,7 +15,7 @@ use std::cmp::Ordering::{Equal, Less}; use std::sync::Arc; -use foyer::memory::CacheContext; +use foyer::CacheContext; use risingwave_hummock_sdk::key::FullKey; use crate::hummock::iterator::{Backward, HummockIterator, ValueMeta}; @@ -187,7 +187,7 @@ mod tests { #[tokio::test] async fn test_backward_sstable_iterator() { // build remote sstable - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let handle = gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone()) .await; @@ -212,7 +212,7 @@ mod tests { #[tokio::test] async fn test_backward_sstable_seek() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let sstable = gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone()) .await; diff --git a/src/storage/src/hummock/sstable/block.rs b/src/storage/src/hummock/sstable/block.rs index 1028348a7d349..9ba69882db663 100644 --- a/src/storage/src/hummock/sstable/block.rs +++ b/src/storage/src/hummock/sstable/block.rs @@ -22,7 +22,7 @@ use bytes::{Buf, BufMut, Bytes, BytesMut}; use risingwave_common::catalog::TableId; use risingwave_hummock_sdk::key::FullKey; use risingwave_hummock_sdk::KeyComparator; -use {lz4, zstd}; +use serde::{Deserialize, Serialize}; use super::utils::{bytes_diff_below_max_key_length, xxhash64_verify, CompressionAlgorithm}; use crate::hummock::sstable::utils; @@ -35,7 +35,7 @@ pub const DEFAULT_RESTART_INTERVAL: usize = 16; pub const DEFAULT_ENTRY_SIZE: usize = 24; // table_id(u64) + primary_key(u64) + epoch(u64) #[allow(non_camel_case_types)] -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum LenType { u8 = 1, u16 = 2, @@ -125,7 +125,7 @@ impl LenType { } } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RestartPoint { pub offset: u32, pub key_len_type: LenType, @@ -177,6 +177,26 @@ impl Debug for Block { } } +impl Serialize for Block { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serde_bytes::serialize(&self.data[..], serializer) + } +} + +impl<'de> Deserialize<'de> for Block { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data: Vec = serde_bytes::deserialize(deserializer)?; + let data = Bytes::from(data); + Ok(Block::decode_from_raw(data)) + } +} + impl Block { pub const HITMAP_ELEMS: usize = 4; @@ -793,9 +813,9 @@ impl BlockBuilder { #[cfg(test)] mod tests { - use risingwave_common::catalog::TableId; + use risingwave_common::util::epoch::test_epoch; - use risingwave_hummock_sdk::key::{FullKey, MAX_KEY_LEN}; + use risingwave_hummock_sdk::key::MAX_KEY_LEN; use super::*; use crate::hummock::{BlockHolder, BlockIterator}; @@ -1003,4 +1023,35 @@ mod tests { builder.clear(); } } + + #[test] + fn test_block_serde() { + fn assmut_serde<'de, T: Serialize + Deserialize<'de>>() {} + + assmut_serde::(); + assmut_serde::>(); + + let options = BlockBuilderOptions::default(); + let mut builder = BlockBuilder::new(options); + for i in 0..100 { + builder.add_for_test( + construct_full_key_struct_for_test(0, format!("k{i:8}").as_bytes(), i), + format!("v{i:8}").as_bytes(), + ); + } + + let capacity = builder.uncompressed_block_size(); + assert_eq!(capacity, builder.approximate_len() - 9); + let buf = builder.build().to_vec(); + + let block = Box::new(Block::decode(buf.into(), capacity).unwrap()); + + let buffer = bincode::serialize(&block).unwrap(); + let blk: Block = bincode::deserialize(&buffer).unwrap(); + + assert_eq!(block.data, blk.data); + assert_eq!(block.data_len, blk.data_len); + assert_eq!(block.table_id, blk.table_id,); + assert_eq!(block.restart_points, blk.restart_points); + } } diff --git a/src/storage/src/hummock/sstable/block_iterator.rs b/src/storage/src/hummock/sstable/block_iterator.rs index 7344d033ddde4..daf25024846c4 100644 --- a/src/storage/src/hummock/sstable/block_iterator.rs +++ b/src/storage/src/hummock/sstable/block_iterator.rs @@ -330,11 +330,10 @@ impl BlockIterator { #[cfg(test)] mod tests { - use risingwave_common::catalog::TableId; use risingwave_common::util::epoch::test_epoch; use super::*; - use crate::hummock::{Block, BlockBuilder, BlockBuilderOptions}; + use crate::hummock::{BlockBuilder, BlockBuilderOptions}; fn build_iterator_for_test() -> BlockIterator { let options = BlockBuilderOptions::default(); diff --git a/src/storage/src/hummock/sstable/builder.rs b/src/storage/src/hummock/sstable/builder.rs index f3f6c2345c70c..9b007f629f74a 100644 --- a/src/storage/src/hummock/sstable/builder.rs +++ b/src/storage/src/hummock/sstable/builder.rs @@ -626,9 +626,7 @@ pub(super) mod tests { default_builder_opt_for_test, gen_test_sstable_impl, mock_sst_writer, test_key_of, test_value_of, TEST_KEYS_COUNT, }; - use crate::hummock::{ - CachePolicy, Sstable, SstableWriterOptions, Xor16FilterBuilder, Xor8FilterBuilder, - }; + use crate::hummock::{CachePolicy, Sstable, SstableWriterOptions, Xor8FilterBuilder}; use crate::monitor::StoreLocalStatistic; #[tokio::test] @@ -690,7 +688,7 @@ pub(super) mod tests { }; // build remote table - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let sst_info = gen_test_sstable_impl::, F>( opts, 0, @@ -734,7 +732,7 @@ pub(super) mod tests { async fn test_no_bloom_filter_block() { let opts = SstableBuilderOptions::default(); // build remote table - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let writer_opts = SstableWriterOptions::default(); let object_id = 1; let writer = sstable_store diff --git a/src/storage/src/hummock/sstable/forward_sstable_iterator.rs b/src/storage/src/hummock/sstable/forward_sstable_iterator.rs index bcfd7eb86f081..fe258db352143 100644 --- a/src/storage/src/hummock/sstable/forward_sstable_iterator.rs +++ b/src/storage/src/hummock/sstable/forward_sstable_iterator.rs @@ -316,7 +316,7 @@ mod tests { use std::collections::Bound; use bytes::Bytes; - use foyer::memory::CacheContext; + use foyer::CacheContext; use itertools::Itertools; use rand::prelude::*; use risingwave_common::catalog::TableId; @@ -359,7 +359,7 @@ mod tests { #[tokio::test] async fn test_table_iterator() { // Build remote sstable - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let sstable = gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone()) .await; @@ -372,7 +372,7 @@ mod tests { #[tokio::test] async fn test_table_seek() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let sstable = gen_default_test_sstable(default_builder_opt_for_test(), 0, sstable_store.clone()) .await; @@ -463,7 +463,7 @@ mod tests { #[tokio::test] async fn test_prefetch_table_read() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // when upload data is successful, but upload meta is fail and delete is fail let kv_iter = (0..TEST_KEYS_COUNT).map(|i| (test_key_of(i), HummockValue::put(test_value_of(i)))); diff --git a/src/storage/src/hummock/sstable/mod.rs b/src/storage/src/hummock/sstable/mod.rs index adc1e5f4885f7..1125cc919bd58 100644 --- a/src/storage/src/hummock/sstable/mod.rs +++ b/src/storage/src/hummock/sstable/mod.rs @@ -27,6 +27,7 @@ pub use block_iterator::*; mod bloom; mod xor_filter; pub use bloom::BloomFilterBuilder; +use serde::{Deserialize, Serialize}; pub use xor_filter::{ BlockedXor16FilterBuilder, Xor16FilterBuilder, Xor8FilterBuilder, XorFilterReader, }; @@ -147,7 +148,7 @@ impl DeleteRangeTombstone { /// next event key wmk2 (7) (not inclusive). /// If there is no range deletes between current event key and next event key, `new_epoch` will be /// `HummockEpoch::MAX`. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MonotonicDeleteEvent { pub event_key: PointRange>, pub new_epoch: HummockEpoch, @@ -209,11 +210,25 @@ impl Display for MonotonicDeleteEvent { } } +#[derive(Serialize, Deserialize)] +struct SerdeSstable { + id: HummockSstableObjectId, + meta: SstableMeta, +} + +impl From for Sstable { + fn from(SerdeSstable { id, meta }: SerdeSstable) -> Self { + Sstable::new(id, meta) + } +} + /// [`Sstable`] is a handle for accessing SST. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(from = "SerdeSstable")] pub struct Sstable { pub id: HummockSstableObjectId, pub meta: SstableMeta, + #[serde(skip)] pub filter_reader: XorFilterReader, } @@ -280,7 +295,7 @@ impl Sstable { } } -#[derive(Clone, Default, Debug, Eq, PartialEq)] +#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct BlockMeta { pub smallest_key: Vec, pub offset: u32, @@ -350,7 +365,7 @@ impl BlockMeta { } } -#[derive(Default, Clone, PartialEq, Eq, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] pub struct SstableMeta { pub block_metas: Vec, pub bloom_filter: Vec, @@ -562,9 +577,14 @@ impl SstableIteratorReadOptions { #[cfg(test)] mod tests { use super::*; + use crate::hummock::iterator::test_utils::{ + default_builder_opt_for_test, iterator_test_key_of, + }; + use crate::hummock::test_utils::gen_test_sstable_data; + use crate::hummock::HummockValue; #[test] - pub fn test_sstable_meta_enc_dec() { + fn test_sstable_meta_enc_dec() { let meta = SstableMeta { block_metas: vec![ BlockMeta { @@ -593,5 +613,29 @@ mod tests { assert_eq!(sz, buf.len()); let decoded_meta = SstableMeta::decode(&buf[..]).unwrap(); assert_eq!(decoded_meta, meta); + + println!("buf: {}", buf.len()); + } + + #[tokio::test] + async fn test_sstable_serde() { + let (_, meta) = gen_test_sstable_data( + default_builder_opt_for_test(), + (0..100).clone().map(|x| { + ( + iterator_test_key_of(x), + HummockValue::put(format!("overlapped_new_{}", x).as_bytes().to_vec()), + ) + }), + ) + .await; + + let buffer = bincode::serialize(&meta).unwrap(); + + let m: SstableMeta = bincode::deserialize(&buffer).unwrap(); + + assert_eq!(meta, m); + + println!("{} vs {}", buffer.len(), meta.encoded_size()); } } diff --git a/src/storage/src/hummock/sstable/multi_builder.rs b/src/storage/src/hummock/sstable/multi_builder.rs index 4049811f78c9e..7354cb89c81fc 100644 --- a/src/storage/src/hummock/sstable/multi_builder.rs +++ b/src/storage/src/hummock/sstable/multi_builder.rs @@ -370,13 +370,12 @@ impl TableBuilderFactory for LocalTableBuilderFactory { #[cfg(test)] mod tests { use risingwave_common::catalog::TableId; - use risingwave_common::hash::VirtualNode; use risingwave_common::util::epoch::{test_epoch, EpochExt}; use super::*; use crate::hummock::iterator::test_utils::mock_sstable_store; use crate::hummock::test_utils::{default_builder_opt_for_test, test_key_of, test_user_key_of}; - use crate::hummock::{SstableBuilderOptions, DEFAULT_RESTART_INTERVAL}; + use crate::hummock::DEFAULT_RESTART_INTERVAL; #[tokio::test] async fn test_empty() { @@ -389,7 +388,7 @@ mod tests { bloom_false_positive: 0.1, ..Default::default() }; - let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store(), opts); + let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts); let builder = CapacitySplitTableBuilder::for_test(builder_factory); let results = builder.finish().await.unwrap(); assert!(results.is_empty()); @@ -406,7 +405,7 @@ mod tests { bloom_false_positive: 0.1, ..Default::default() }; - let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store(), opts); + let builder_factory = LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts); let mut builder = CapacitySplitTableBuilder::for_test(builder_factory); for i in 0..table_capacity { @@ -432,7 +431,7 @@ mod tests { let opts = default_builder_opt_for_test(); let mut builder = CapacitySplitTableBuilder::for_test(LocalTableBuilderFactory::new( 1001, - mock_sstable_store(), + mock_sstable_store().await, opts, )); let mut epoch = test_epoch(100); @@ -476,7 +475,7 @@ mod tests { let opts = default_builder_opt_for_test(); let mut builder = CapacitySplitTableBuilder::for_test(LocalTableBuilderFactory::new( 1001, - mock_sstable_store(), + mock_sstable_store().await, opts, )); builder @@ -501,7 +500,7 @@ mod tests { BTreeMap::from([(1_u32, 4_u32), (2_u32, 4_u32), (3_u32, 4_u32)]); let mut builder = CapacitySplitTableBuilder::new( - LocalTableBuilderFactory::new(1001, mock_sstable_store(), opts), + LocalTableBuilderFactory::new(1001, mock_sstable_store().await, opts), Arc::new(CompactorMetrics::unused()), None, table_partition_vnode, diff --git a/src/storage/src/hummock/sstable/xor_filter.rs b/src/storage/src/hummock/sstable/xor_filter.rs index e096676008f89..1df4333fca459 100644 --- a/src/storage/src/hummock/sstable/xor_filter.rs +++ b/src/storage/src/hummock/sstable/xor_filter.rs @@ -442,7 +442,7 @@ impl Clone for XorFilterReader { #[cfg(test)] mod tests { - use foyer::memory::CacheContext; + use foyer::CacheContext; use rand::RngCore; use risingwave_common::util::epoch::test_epoch; use risingwave_hummock_sdk::EpochWithGap; @@ -453,12 +453,12 @@ mod tests { use crate::hummock::sstable::{SstableBuilder, SstableBuilderOptions}; use crate::hummock::test_utils::{test_user_key_of, test_value_of, TEST_KEYS_COUNT}; use crate::hummock::value::HummockValue; - use crate::hummock::{BlockIterator, CachePolicy, Sstable, SstableWriterOptions}; + use crate::hummock::{BlockIterator, CachePolicy, SstableWriterOptions}; use crate::monitor::StoreLocalStatistic; #[tokio::test] async fn test_blocked_bloom_filter() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let writer_opts = SstableWriterOptions { capacity_hint: None, tracker: None, diff --git a/src/storage/src/hummock/sstable_store.rs b/src/storage/src/hummock/sstable_store.rs index e29383004f3a2..6dfbe5468426a 100644 --- a/src/storage/src/hummock/sstable_store.rs +++ b/src/storage/src/hummock/sstable_store.rs @@ -14,46 +14,44 @@ use std::clone::Clone; use std::collections::VecDeque; use std::future::Future; -use std::ops::Deref; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use ahash::RandomState; use await_tree::InstrumentAwait; use bytes::Bytes; use fail::fail_point; -use foyer::memory::{ - Cache, CacheContext, CacheEntry, CacheEventListener, EntryState, Key, LfuCacheConfig, - LruCacheConfig, LruConfig, S3FifoCacheConfig, Value, -}; +use foyer::{CacheContext, EntryState, HybridCache, HybridCacheBuilder, HybridCacheEntry}; use futures::{future, StreamExt}; use itertools::Itertools; -use risingwave_common::config::{EvictionConfig, StorageMemoryConfig}; +use risingwave_common::config::StorageMemoryConfig; use risingwave_hummock_sdk::{HummockSstableObjectId, OBJECT_SUFFIX}; use risingwave_hummock_trace::TracedCachePolicy; use risingwave_object_store::object::{ ObjectError, ObjectMetadataIter, ObjectStoreRef, ObjectStreamingUploader, }; use risingwave_pb::hummock::SstableInfo; +use serde::{Deserialize, Serialize}; use thiserror_ext::AsReport; use tokio::task::JoinHandle; use tokio::time::Instant; use zstd::zstd_safe::WriteBuf; use super::utils::MemoryTracker; -use super::{ - Block, BlockCache, BlockCacheConfig, BlockMeta, BlockResponse, CachedBlock, CachedSstable, - FileCache, RecentFilter, Sstable, SstableBlockIndex, SstableMeta, SstableWriter, -}; +use super::{Block, BlockMeta, BlockResponse, RecentFilter, Sstable, SstableMeta, SstableWriter}; use crate::hummock::block_stream::{ BlockDataStream, BlockStream, MemoryUsageTracker, PrefetchBlockStream, }; -use crate::hummock::file_cache::preclude::*; use crate::hummock::multi_builder::UploadJoinHandle; use crate::hummock::{BlockHolder, HummockError, HummockResult, MemoryLimiter}; use crate::monitor::{HummockStateStoreMetrics, MemoryCollector, StoreLocalStatistic}; -pub type TableHolder = CacheEntry, MetaCacheEventListener>; +pub type TableHolder = HybridCacheEntry>; + +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)] +pub struct SstableBlockIndex { + pub sst_id: HummockSstableObjectId, + pub block_idx: u64, +} // TODO: Define policy based on use cases (read / compaction / ...). #[derive(Clone, Copy, Eq, PartialEq)] @@ -62,8 +60,6 @@ pub enum CachePolicy { Disable, /// Try reading the cache and fill the cache afterwards. Fill(CacheContext), - /// Fill file cache only. - FillFileCache, /// Read the cache but not fill the cache afterwards. NotFill, } @@ -79,7 +75,6 @@ impl From for CachePolicy { match policy { TracedCachePolicy::Disable => Self::Disable, TracedCachePolicy::Fill(priority) => Self::Fill(priority.into()), - TracedCachePolicy::FileFileCache => Self::FillFileCache, TracedCachePolicy::NotFill => Self::NotFill, } } @@ -89,130 +84,32 @@ impl From for TracedCachePolicy { fn from(policy: CachePolicy) -> Self { match policy { CachePolicy::Disable => Self::Disable, - CachePolicy::FillFileCache => Self::FileFileCache, CachePolicy::Fill(priority) => Self::Fill(priority.into()), CachePolicy::NotFill => Self::NotFill, } } } -#[derive(Debug)] -pub struct BlockCacheEventListener { - data_file_cache: FileCache, - metrics: Arc, -} - -impl BlockCacheEventListener { - pub fn new( - data_file_cache: FileCache, - metrics: Arc, - ) -> Self { - Self { - data_file_cache, - metrics, - } - } -} - -impl CacheEventListener<(HummockSstableObjectId, u64), Box> for BlockCacheEventListener { - fn on_release( - &self, - key: (HummockSstableObjectId, u64), - value: Box, - _context: CacheContext, - _charges: usize, - ) { - let key = SstableBlockIndex { - sst_id: key.0, - block_idx: key.1, - }; - self.metrics - .block_efficiency_histogram - .with_label_values(&[&value.table_id().to_string()]) - .observe(value.efficiency()); - // temporarily avoid spawn task while task drop with madsim - // FYI: https://github.com/madsim-rs/madsim/issues/182 - #[cfg(not(madsim))] - self.data_file_cache - .insert_if_not_exists_async(key, CachedBlock::Loaded { block: value }); - } -} - -pub struct MetaCacheEventListener(FileCache); - -impl From> for MetaCacheEventListener { - fn from(value: FileCache) -> Self { - Self(value) - } -} - -impl CacheEventListener> for MetaCacheEventListener { - fn on_release( - &self, - key: HummockSstableObjectId, - value: Box, - _context: CacheContext, - _charges: usize, - ) { - // temporarily avoid spawn task while task drop with madsim - // FYI: https://github.com/madsim-rs/madsim/issues/182 - #[cfg(not(madsim))] - self.0.insert_if_not_exists_async(key, value.into()); - } -} - -pub enum CachedOrShared -where - K: Key, - V: Value, - L: CacheEventListener>, -{ - Cached(CacheEntry, L>), - Shared(Arc), -} - -impl Deref for CachedOrShared -where - K: Key, - V: Value, - L: CacheEventListener>, -{ - type Target = V; - - fn deref(&self) -> &Self::Target { - match self { - CachedOrShared::Cached(entry) => entry, - CachedOrShared::Shared(v) => v, - } - } -} - pub struct SstableStoreConfig { pub store: ObjectStoreRef, pub path: String, - pub block_cache_capacity: usize, - pub block_cache_shard_num: usize, - pub block_cache_eviction: EvictionConfig, - pub meta_cache_capacity: usize, - pub meta_cache_shard_num: usize, - pub meta_cache_eviction: EvictionConfig, + pub prefetch_buffer_capacity: usize, pub max_prefetch_block_number: usize, - pub data_file_cache: FileCache, - pub meta_file_cache: FileCache, pub recent_filter: Option>>, pub state_store_metrics: Arc, + + pub meta_cache_v2: HybridCache>, + pub block_cache_v2: HybridCache>, } pub struct SstableStore { path: String, store: ObjectStoreRef, - block_cache: BlockCache, - // TODO(MrCroxx): use no hash random state - meta_cache: Arc, MetaCacheEventListener>>, - data_file_cache: FileCache, - meta_file_cache: FileCache, + meta_cache_v2: HybridCache>, + block_cache_v2: HybridCache>, + /// Recent filter for `(sst_obj_id, blk_idx)`. /// /// `blk_idx == USIZE::MAX` stands for `sst_obj_id` only entry. @@ -227,60 +124,12 @@ impl SstableStore { // TODO: We should validate path early. Otherwise object store won't report invalid path // error until first write attempt. - let block_cache = BlockCache::new(BlockCacheConfig { - capacity: config.block_cache_capacity, - shard_num: config.block_cache_shard_num, - eviction: config.block_cache_eviction, - listener: BlockCacheEventListener::new( - config.data_file_cache.clone(), - config.state_store_metrics.clone(), - ), - }); - - // TODO(MrCroxx): reuse BlockCacheConfig here? - let meta_cache = { - let capacity = config.meta_cache_capacity; - let shards = config.meta_cache_shard_num; - let object_pool_capacity = config.meta_cache_shard_num * 1024; - let hash_builder = RandomState::default(); - let event_listener = MetaCacheEventListener::from(config.meta_file_cache.clone()); - match config.meta_cache_eviction { - EvictionConfig::Lru(eviction_config) => Cache::lru(LruCacheConfig { - capacity, - shards, - eviction_config, - object_pool_capacity, - hash_builder, - event_listener, - }), - EvictionConfig::Lfu(eviction_config) => Cache::lfu(LfuCacheConfig { - capacity, - shards, - eviction_config, - object_pool_capacity, - hash_builder, - event_listener, - }), - EvictionConfig::S3Fifo(eviction_config) => Cache::s3fifo(S3FifoCacheConfig { - capacity, - shards, - eviction_config, - object_pool_capacity, - hash_builder, - event_listener, - }), - } - }; - let meta_cache = Arc::new(meta_cache); - Self { path: config.path, store: config.store, - block_cache, - meta_cache, - data_file_cache: config.data_file_cache, - meta_file_cache: config.meta_file_cache, + meta_cache_v2: config.meta_cache_v2, + block_cache_v2: config.block_cache_v2, recent_filter: config.recent_filter, prefetch_buffer_usage: Arc::new(AtomicUsize::new(0)), @@ -291,55 +140,58 @@ impl SstableStore { /// For compactor, we do not need a high concurrency load for cache. Instead, we need the cache /// can be evict more effective. - pub fn for_compactor( + #[expect(clippy::borrowed_box)] + pub async fn for_compactor( store: ObjectStoreRef, path: String, block_cache_capacity: usize, meta_cache_capacity: usize, - ) -> Self { - let meta_cache = Arc::new(Cache::lru(LruCacheConfig { - capacity: meta_cache_capacity, - shards: 1, - eviction_config: LruConfig { - high_priority_pool_ratio: 0.0, - }, - object_pool_capacity: 1024, - hash_builder: RandomState::default(), - event_listener: FileCache::none().into(), - })); - Self { + ) -> HummockResult { + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(meta_cache_capacity) + .with_shards(1) + .with_weighter(|_: &HummockSstableObjectId, value: &Box| { + u64::BITS as usize / 8 + value.estimate_size() + }) + .storage() + .build() + .await + .map_err(HummockError::foyer_error)?; + + let block_cache_v2 = HybridCacheBuilder::new() + .memory(block_cache_capacity) + .with_shards(1) + .with_weighter(|_: &SstableBlockIndex, value: &Box| { + // FIXME(MrCroxx): Calculate block weight more accurately. + u64::BITS as usize * 2 / 8 + value.raw().len() + }) + .storage() + .build() + .await + .map_err(HummockError::foyer_error)?; + + Ok(Self { path, store, - block_cache: BlockCache::new(BlockCacheConfig { - capacity: block_cache_capacity, - shard_num: 1, - eviction: EvictionConfig::Lru(LruConfig { - high_priority_pool_ratio: 0.0, - }), - listener: BlockCacheEventListener::new( - FileCache::none(), - Arc::new(HummockStateStoreMetrics::unused()), - ), - }), - meta_cache, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), + prefetch_buffer_usage: Arc::new(AtomicUsize::new(0)), prefetch_buffer_capacity: block_cache_capacity, max_prefetch_block_number: 16, /* compactor won't use this parameter, so just assign a default value. */ recent_filter: None, - } + + meta_cache_v2, + block_cache_v2, + }) } pub async fn delete(&self, object_id: HummockSstableObjectId) -> HummockResult<()> { - // Data self.store .delete(self.get_sst_data_path(object_id).as_str()) .await?; - self.meta_cache.remove(&object_id); - self.meta_file_cache + self.meta_cache_v2 .remove(&object_id) - .map_err(HummockError::file_cache)?; + .map_err(HummockError::foyer_error)?; + // TODO(MrCroxx): support group remove in foyer. Ok(()) } @@ -357,21 +209,20 @@ impl SstableStore { self.store.delete_objects(&paths).await?; // Delete from cache. - for &object_id in object_id_list { - self.meta_cache.remove(&object_id); - self.meta_file_cache - .remove(&object_id) - .map_err(HummockError::file_cache)?; + for object_id in object_id_list { + self.meta_cache_v2 + .remove(object_id) + .map_err(HummockError::foyer_error)?; } Ok(()) } - pub fn delete_cache(&self, object_id: HummockSstableObjectId) { - self.meta_cache.remove(&object_id); - if let Err(e) = self.meta_file_cache.remove(&object_id) { - tracing::warn!(error = %e.as_report(), "meta file cache remove error"); - } + pub fn delete_cache(&self, object_id: HummockSstableObjectId) -> HummockResult<()> { + self.meta_cache_v2 + .remove(&object_id) + .map(|_| ()) + .map_err(HummockError::foyer_error) } async fn put_sst_data( @@ -404,7 +255,16 @@ impl SstableStore { ))); } stats.cache_data_block_total += 1; - if let Some(block) = self.block_cache.get(object_id, block_index as u64) { + if let Some(entry) = self + .block_cache_v2 + .get(&SstableBlockIndex { + sst_id: object_id, + block_idx: block_index as _, + }) + .await + .map_err(HummockError::foyer_error)? + { + let block = BlockHolder::from_hybrid_cache_entry(entry); return Ok(Box::new(PrefetchBlockStream::new( VecDeque::from([block]), block_index, @@ -417,7 +277,14 @@ impl SstableStore { let mut min_hit_index = end_index; let mut hit_count = 0; for idx in block_index..end_index { - if self.block_cache.exists_block(object_id, idx as u64) { + if self + .block_cache_v2 + .contains(&SstableBlockIndex { + sst_id: object_id, + block_idx: idx as _, + }) + .map_err(HummockError::foyer_error)? + { if min_hit_index > idx && idx > block_index { min_hit_index = idx; } @@ -482,8 +349,15 @@ impl SstableStore { } else { CacheContext::LruPriorityLow }; - self.block_cache - .insert(object_id, idx as u64, Box::new(block), cache_priority) + let entry = self.block_cache_v2.insert_with_context( + SstableBlockIndex { + sst_id: object_id, + block_idx: idx as _, + }, + Box::new(block), + cache_priority, + ); + BlockHolder::from_hybrid_cache_entry(entry) } else { BlockHolder::from_owned_block(Box::new(block)) }; @@ -507,32 +381,28 @@ impl SstableStore { ) -> HummockResult { let object_id = sst.id; let (range, uncompressed_capacity) = sst.calculate_block_info(block_index); + let store = self.store.clone(); stats.cache_data_block_total += 1; let file_size = sst.meta.estimated_size; - let mut fetch_block = || { - let file_cache = self.data_file_cache.clone(); - stats.cache_data_block_miss += 1; - let data_path = self.get_sst_data_path(object_id); - let store = self.store.clone(); - let use_file_cache = !matches!(policy, CachePolicy::Disable); + let data_path = self.get_sst_data_path(object_id); + + let disable_cache: fn() -> bool = || { + fail_point!("disable_block_cache", |_| true); + false + }; + + let policy = if disable_cache() { + CachePolicy::Disable + } else { + policy + }; + + // future: fetch block if hybrid cache miss + let fetch_block = move |context: CacheContext| { let range = range.clone(); async move { - let key = SstableBlockIndex { - sst_id: object_id, - block_idx: block_index as u64, - }; - if use_file_cache - && let Some(block) = file_cache - .lookup(&key) - .await - .map_err(HummockError::file_cache)? - { - let block = block.try_into_block()?; - return Ok(block); - } - let block_data = match store .read(&data_path, range.clone()) .verbose_instrument_await("get_block_response") @@ -541,66 +411,66 @@ impl SstableStore { Ok(data) => data, Err(e) => { tracing::error!( - "get_block_response meet error when read {:?} from sst-{}, total length: {}", - range, - object_id, - file_size - ); - return Err(HummockError::from(e)); + "get_block_response meet error when read {:?} from sst-{}, total length: {}", + range, + object_id, + file_size + ); + return Err(anyhow::Error::from(HummockError::from(e))); } }; - let block = Box::new(Block::decode(block_data, uncompressed_capacity)?); - - Ok(block) + let block = Box::new( + Block::decode(block_data, uncompressed_capacity) + .map_err(anyhow::Error::from)?, + ); + Ok((block, context)) } }; - let disable_cache: fn() -> bool = || { - fail_point!("disable_block_cache", |_| true); - false - }; - - let policy = if disable_cache() { - CachePolicy::Disable - } else { - policy - }; - if let Some(filter) = self.recent_filter.as_ref() { filter.extend([(object_id, usize::MAX), (object_id, block_index)]); } match policy { - CachePolicy::Fill(priority) => Ok(self.block_cache.get_or_insert_with( - object_id, - block_index as u64, - priority, - fetch_block, - )), - CachePolicy::FillFileCache => { - let block = fetch_block().await?; - self.data_file_cache.insert_async( + CachePolicy::Fill(context) => { + let entry = self.block_cache_v2.entry( SstableBlockIndex { sst_id: object_id, - block_idx: block_index as u64, - }, - CachedBlock::Loaded { - block: block.clone(), + block_idx: block_index as _, }, + move || fetch_block(context), ); - Ok(BlockResponse::Block(BlockHolder::from_owned_block(block))) + if matches!(entry.state(), EntryState::Miss) { + stats.cache_data_block_miss += 1; + } + Ok(BlockResponse::Entry(entry)) } - CachePolicy::NotFill => match self.block_cache.get(object_id, block_index as u64) { - Some(block) => Ok(BlockResponse::Block(block)), - None => fetch_block() + CachePolicy::NotFill => { + if let Some(entry) = self + .block_cache_v2 + .get(&SstableBlockIndex { + sst_id: object_id, + block_idx: block_index as _, + }) .await - .map(BlockHolder::from_owned_block) - .map(BlockResponse::Block), - }, - CachePolicy::Disable => fetch_block() - .await - .map(BlockHolder::from_owned_block) - .map(BlockResponse::Block), + .map_err(HummockError::foyer_error)? + { + Ok(BlockResponse::Block(BlockHolder::from_hybrid_cache_entry( + entry, + ))) + } else { + let (block, _) = fetch_block(CacheContext::default()) + .await + .map_err(HummockError::foyer_error)?; + Ok(BlockResponse::Block(BlockHolder::from_owned_block(block))) + } + } + CachePolicy::Disable => { + let (block, _) = fetch_block(CacheContext::default()) + .await + .map_err(HummockError::foyer_error)?; + Ok(BlockResponse::Block(BlockHolder::from_owned_block(block))) + } } } @@ -642,41 +512,27 @@ impl SstableStore { } #[cfg(any(test, feature = "test"))] - pub fn clear_block_cache(&self) { - self.block_cache.clear(); - if let Err(e) = self.data_file_cache.clear() { - tracing::warn!(error = %e.as_report(), "data file cache clear error"); - } + pub fn clear_block_cache(&self) -> HummockResult<()> { + self.block_cache_v2 + .clear() + .map_err(HummockError::foyer_error) } #[cfg(any(test, feature = "test"))] - pub fn clear_meta_cache(&self) { - self.meta_cache.clear(); - if let Err(e) = self.meta_file_cache.clear() { - tracing::warn!(error = %e.as_report(), "meta file cache clear error"); - } + pub fn clear_meta_cache(&self) -> HummockResult<()> { + self.meta_cache_v2 + .clear() + .map_err(HummockError::foyer_error) } pub async fn sstable_cached( &self, sst_obj_id: HummockSstableObjectId, - ) -> HummockResult< - Option>, - > { - if let Some(sst) = self.meta_cache.get(&sst_obj_id) { - return Ok(Some(CachedOrShared::Cached(sst))); - } - - if let Some(sst) = self - .meta_file_cache - .lookup(&sst_obj_id) + ) -> HummockResult>>> { + self.meta_cache_v2 + .get(&sst_obj_id) .await - .map_err(HummockError::file_cache)? - { - return Ok(Some(CachedOrShared::Shared(sst.into_inner()))); - } - - Ok(None) + .map_err(HummockError::foyer_error) } /// Returns `table_holder` @@ -686,33 +542,21 @@ impl SstableStore { stats: &mut StoreLocalStatistic, ) -> impl Future> + Send + 'static { let object_id = sst.get_object_id(); - let entry = self.meta_cache.entry(object_id, || { - let meta_file_cache = self.meta_file_cache.clone(); + + let entry = self.meta_cache_v2.entry(object_id, || { let store = self.store.clone(); let meta_path = self.get_sst_data_path(object_id); let stats_ptr = stats.remote_io_time.clone(); let range = sst.meta_offset as usize..sst.file_size as usize; async move { - if let Some(sst) = meta_file_cache - .lookup(&object_id) - .await - .map_err(HummockError::file_cache)? - { - // TODO(MrCroxx): Make meta cache receives Arc to reduce copy? - let sst: Box = sst.into(); - let charge = sst.estimate_size(); - return Ok((sst, charge, CacheContext::Default)); - } - let now = Instant::now(); let buf = store.read(&meta_path, range).await?; let meta = SstableMeta::decode(&buf[..])?; let sst = Sstable::new(object_id, meta); - let charge = sst.estimate_size(); let add = (now.elapsed().as_secs_f64() * 1000.0).ceil(); stats_ptr.fetch_add(add as u64, Ordering::Relaxed); - Ok((Box::new(sst), charge, CacheContext::Default)) + Ok((Box::new(sst), CacheContext::Default)) } }); @@ -722,7 +566,7 @@ impl SstableStore { stats.cache_meta_block_total += 1; - entry + async move { entry.await.map_err(HummockError::foyer_error) } } pub async fn list_object_metadata_from_object_store( @@ -746,13 +590,7 @@ impl SstableStore { pub fn insert_meta_cache(&self, object_id: HummockSstableObjectId, meta: SstableMeta) { let sst = Sstable::new(object_id, meta); - let charge = sst.estimate_size(); - self.meta_cache.insert_with_context( - object_id, - Box::new(sst), - charge, - CacheContext::Default, - ); + self.meta_cache_v2.insert(object_id, Box::new(sst)); } pub fn insert_block_cache( @@ -761,15 +599,17 @@ impl SstableStore { block_index: u64, block: Box, ) { - if let Some(filter) = self.recent_filter.as_ref() { - filter.extend([(object_id, usize::MAX), (object_id, block_index as usize)]); - } - self.block_cache - .insert(object_id, block_index, block, CacheContext::Default); + self.block_cache_v2.insert( + SstableBlockIndex { + sst_id: object_id, + block_idx: block_index, + }, + block, + ); } pub fn get_meta_memory_usage(&self) -> u64 { - self.meta_cache.usage() as u64 + self.meta_cache_v2.cache().usage() as _ } pub fn get_prefetch_memory_usage(&self) -> usize { @@ -810,12 +650,12 @@ impl SstableStore { self.recent_filter.as_ref() } - pub fn data_file_cache(&self) -> &FileCache { - &self.data_file_cache + pub fn meta_cache(&self) -> &HybridCache> { + &self.meta_cache_v2 } - pub fn data_cache(&self) -> &BlockCache { - &self.block_cache + pub fn block_cache(&self) -> &HybridCache> { + &self.block_cache_v2 } } @@ -847,7 +687,7 @@ impl MemoryCollector for HummockMemoryCollector { } fn get_data_memory_usage(&self) -> u64 { - self.sstable_store.block_cache.size() as u64 + self.sstable_store.block_cache_v2.cache().usage() as _ } fn get_uploading_memory_usage(&self) -> u64 { @@ -864,7 +704,7 @@ impl MemoryCollector for HummockMemoryCollector { } fn get_block_cache_memory_usage_ratio(&self) -> f64 { - self.sstable_store.block_cache.size() as f64 + self.get_data_memory_usage() as f64 / (self.storage_memory_config.block_cache_capacity_mb * 1024 * 1024) as f64 } @@ -1010,9 +850,11 @@ impl SstableWriter for BatchUploadWriter { // The `block_info` may be empty when there is only range-tombstones, because we // store them in meta-block. for (block_idx, block) in self.block_info.into_iter().enumerate() { - self.sstable_store.block_cache.insert( - self.object_id, - block_idx as u64, + self.sstable_store.block_cache_v2.insert_with_context( + SstableBlockIndex { + sst_id: self.object_id, + block_idx: block_idx as _, + }, Box::new(block), fill_cache_priority, ); @@ -1119,9 +961,11 @@ impl SstableWriter for StreamingUploadWriter { && !self.blocks.is_empty() { for (block_idx, block) in self.blocks.into_iter().enumerate() { - self.sstable_store.block_cache.insert( - self.object_id, - block_idx as u64, + self.sstable_store.block_cache_v2.insert_with_context( + SstableBlockIndex { + sst_id: self.object_id, + block_idx: block_idx as _, + }, Box::new(block), fill_high_priority_cache, ); @@ -1303,7 +1147,7 @@ mod tests { #[tokio::test] async fn test_batch_upload() { - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let x_range = 0..100; let (data, meta) = gen_test_sstable_data( default_builder_opt_for_test(), @@ -1333,7 +1177,7 @@ mod tests { #[tokio::test] async fn test_streaming_upload() { // Generate test data. - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let x_range = 0..100; let (data, meta) = gen_test_sstable_data( default_builder_opt_for_test(), @@ -1360,9 +1204,9 @@ mod tests { validate_sst(sstable_store, &info, meta, x_range).await; } - #[test] - fn test_basic() { - let sstable_store = mock_sstable_store(); + #[tokio::test] + async fn test_basic() { + let sstable_store = mock_sstable_store().await; let object_id = 123; let data_path = sstable_store.get_sst_data_path(object_id); assert_eq!(data_path, "test/123.data"); diff --git a/src/storage/src/hummock/store/hummock_storage.rs b/src/storage/src/hummock/store/hummock_storage.rs index d082ca89d84db..806ec7e46eeb3 100644 --- a/src/storage/src/hummock/store/hummock_storage.rs +++ b/src/storage/src/hummock/store/hummock_storage.rs @@ -60,7 +60,6 @@ use crate::mem_table::ImmutableMemtable; use crate::monitor::{CompactorMetrics, HummockStateStoreMetrics, StoreLocalStatistic}; use crate::opts::StorageOpts; use crate::store::*; -use crate::StateStore; struct HummockStorageShutdownGuard { shutdown_sender: HummockEventSender, diff --git a/src/storage/src/hummock/store/version.rs b/src/storage/src/hummock/store/version.rs index 9f94895e2bba7..f772fbbe79d93 100644 --- a/src/storage/src/hummock/store/version.rs +++ b/src/storage/src/hummock/store/version.rs @@ -90,7 +90,7 @@ impl StagingSstableInfo { imm_size: usize, ) -> Self { // the epochs are sorted from higher epoch to lower epoch - assert!(epochs.is_sorted_by(|epoch1, epoch2| epoch2.partial_cmp(epoch1))); + assert!(epochs.is_sorted_by(|epoch1, epoch2| epoch2 <= epoch1)); Self { sstable_infos, old_value_sstable_infos, diff --git a/src/storage/src/hummock/test_utils.rs b/src/storage/src/hummock/test_utils.rs index 445c7fd718d31..c49155efdd43e 100644 --- a/src/storage/src/hummock/test_utils.rs +++ b/src/storage/src/hummock/test_utils.rs @@ -17,7 +17,10 @@ use std::ops::Bound; use std::sync::Arc; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::{ + CacheContext, HybridCache, HybridCacheBuilder, StorageKey as HybridKey, + StorageValue as HybridValue, +}; use itertools::Itertools; use risingwave_common::catalog::TableId; use risingwave_common::config::EvictionConfig; @@ -356,6 +359,19 @@ pub fn create_small_table_cache() -> Arc() -> HybridCache +where + K: HybridKey, + V: HybridValue, +{ + HybridCacheBuilder::new() + .memory(10) + .storage() + .build() + .await + .unwrap() +} + pub mod delete_range { use super::*; use crate::hummock::shared_buffer::shared_buffer_batch::SharedBufferDeleteRangeIterator; diff --git a/src/storage/src/hummock/utils.rs b/src/storage/src/hummock/utils.rs index 9c6b3bbdf193f..a65f272e4fdb9 100644 --- a/src/storage/src/hummock/utils.rs +++ b/src/storage/src/hummock/utils.rs @@ -13,15 +13,17 @@ // limitations under the License. use std::cmp::Ordering; +use std::collections::VecDeque; use std::fmt::{Debug, Formatter}; use std::ops::Bound::{Excluded, Included, Unbounded}; use std::ops::{Bound, RangeBounds}; -use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering}; +use std::sync::atomic::{AtomicBool, AtomicU64, Ordering as AtomicOrdering}; use std::sync::Arc; use std::time::Duration; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::CacheContext; +use parking_lot::Mutex; use risingwave_common::catalog::{TableId, TableOption}; use risingwave_hummock_sdk::key::{ bound_table_key_range, EmptySliceRef, FullKey, TableKey, UserKey, @@ -29,8 +31,7 @@ use risingwave_hummock_sdk::key::{ use risingwave_hummock_sdk::version::HummockVersion; use risingwave_hummock_sdk::{can_concat, HummockEpoch}; use risingwave_pb::hummock::SstableInfo; -use tokio::sync::watch::Sender; -use tokio::sync::Notify; +use tokio::sync::oneshot::{channel, Receiver, Sender}; use super::{HummockError, HummockResult}; use crate::error::StorageResult; @@ -166,23 +167,54 @@ pub fn prune_nonoverlapping_ssts<'a>( ssts[start_table_idx..=end_table_idx].iter() } -#[derive(Debug)] +type RequestQueue = VecDeque<(Sender, u64)>; +enum MemoryRequest { + Ready(MemoryTracker), + Pending(Receiver), +} + struct MemoryLimiterInner { total_size: AtomicU64, - notify: Notify, + controller: Mutex, + has_waiter: AtomicBool, quota: u64, } impl MemoryLimiterInner { fn release_quota(&self, quota: u64) { - self.total_size.fetch_sub(quota, AtomicOrdering::Release); - self.notify.notify_waiters(); + self.total_size.fetch_sub(quota, AtomicOrdering::SeqCst); } fn add_memory(&self, quota: u64) { self.total_size.fetch_add(quota, AtomicOrdering::SeqCst); } + fn may_notify_waiters(self: &Arc) { + // check `has_waiter` to avoid access lock every times drop `MemoryTracker`. + if !self.has_waiter.load(AtomicOrdering::Acquire) { + return; + } + let mut notify_waiters = vec![]; + { + let mut waiters = self.controller.lock(); + while let Some((_, quota)) = waiters.front() { + if !self.try_require_memory(*quota) { + break; + } + let (tx, quota) = waiters.pop_front().unwrap(); + notify_waiters.push((tx, quota)); + } + + if waiters.is_empty() { + self.has_waiter.store(false, AtomicOrdering::Release); + } + } + + for (tx, quota) in notify_waiters { + let _ = tx.send(MemoryTracker::new(self.clone(), quota)); + } + } + fn try_require_memory(&self, quota: u64) -> bool { let mut current_quota = self.total_size.load(AtomicOrdering::Acquire); while self.permit_quota(current_quota, quota) { @@ -203,44 +235,23 @@ impl MemoryLimiterInner { false } - async fn require_memory(&self, quota: u64) { - let current_quota = self.total_size.load(AtomicOrdering::Acquire); - if self.permit_quota(current_quota, quota) - && self - .total_size - .compare_exchange( - current_quota, - current_quota + quota, - AtomicOrdering::SeqCst, - AtomicOrdering::SeqCst, - ) - .is_ok() - { - // fast path. - return; + fn require_memory(self: &Arc, quota: u64) -> MemoryRequest { + let mut waiters = self.controller.lock(); + let first_req = waiters.is_empty(); + if first_req { + // When this request is the first waiter but the previous `MemoryTracker` is just release a large quota, it may skip notifying this waiter because it has checked `has_waiter` and found it was false. So we must set it one and retry `try_require_memory` again to avoid deadlock. + self.has_waiter.store(true, AtomicOrdering::Release); } - loop { - let notified = self.notify.notified(); - let current_quota = self.total_size.load(AtomicOrdering::Acquire); - if self.permit_quota(current_quota, quota) { - match self.total_size.compare_exchange( - current_quota, - current_quota + quota, - AtomicOrdering::SeqCst, - AtomicOrdering::SeqCst, - ) { - Ok(_) => break, - Err(old_quota) => { - // The quota is enough but just changed by other threads. So just try to - // update again without waiting notify. - if self.permit_quota(old_quota, quota) { - continue; - } - } - } + // We must require again with lock because some other MemoryTracker may drop just after this thread gets mutex but before it enters queue. + if self.try_require_memory(quota) { + if first_req { + self.has_waiter.store(false, AtomicOrdering::Release); } - notified.await; + return MemoryRequest::Ready(MemoryTracker::new(self.clone(), quota)); } + let (tx, rx) = channel(); + waiters.push_back((tx, quota)); + MemoryRequest::Pending(rx) } fn permit_quota(&self, current_quota: u64, _request_quota: u64) -> bool { @@ -248,38 +259,51 @@ impl MemoryLimiterInner { } } -#[derive(Debug)] pub struct MemoryLimiter { inner: Arc, } +impl Debug for MemoryLimiter { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MemoryLimiter") + .field("quota", &self.inner.quota) + .field("usage", &self.inner.total_size) + .finish() + } +} + pub struct MemoryTracker { limiter: Arc, - quota: u64, + quota: Option, +} +impl MemoryTracker { + fn new(limiter: Arc, quota: u64) -> Self { + Self { + limiter, + quota: Some(quota), + } + } } impl Debug for MemoryTracker { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("quota").field("quota", &self.quota).finish() + f.debug_struct("MemoryTracker") + .field("quota", &self.quota) + .finish() } } impl MemoryLimiter { pub fn unlimit() -> Arc { - Arc::new(Self { - inner: Arc::new(MemoryLimiterInner { - total_size: AtomicU64::new(0), - notify: Notify::new(), - quota: u64::MAX - 1, - }), - }) + Arc::new(Self::new(u64::MAX)) } pub fn new(quota: u64) -> Self { Self { inner: Arc::new(MemoryLimiterInner { total_size: AtomicU64::new(0), - notify: Notify::new(), + controller: Mutex::new(VecDeque::default()), + has_waiter: AtomicBool::new(false), quota, }), } @@ -287,10 +311,7 @@ impl MemoryLimiter { pub fn try_require_memory(&self, quota: u64) -> Option { if self.inner.try_require_memory(quota) { - Some(MemoryTracker { - limiter: self.inner.clone(), - quota, - }) + Some(MemoryTracker::new(self.inner.clone(), quota)) } else { None } @@ -309,32 +330,27 @@ impl MemoryLimiter { self.inner.add_memory(quota); } - MemoryTracker { - limiter: self.inner.clone(), - quota, - } + MemoryTracker::new(self.inner.clone(), quota) } } impl MemoryLimiter { pub async fn require_memory(&self, quota: u64) -> MemoryTracker { - // Since the over provision limiter gets blocked only when the current usage exceeds the - // memory quota, it is allowed to apply for more than the memory quota. - self.inner.require_memory(quota).await; - MemoryTracker { - limiter: self.inner.clone(), - quota, + match self.inner.require_memory(quota) { + MemoryRequest::Ready(tracker) => tracker, + MemoryRequest::Pending(rx) => rx.await.unwrap(), } } } impl MemoryTracker { pub fn try_increase_memory(&mut self, target: u64) -> bool { - if self.quota >= target { + let quota = self.quota.unwrap(); + if quota >= target { return true; } - if self.limiter.try_require_memory(target - self.quota) { - self.quota = target; + if self.limiter.try_require_memory(target - quota) { + self.quota = Some(target); true } else { false @@ -342,9 +358,13 @@ impl MemoryTracker { } } +// We must notify waiters outside `MemoryTracker` to avoid dead-lock and loop-owner. impl Drop for MemoryTracker { fn drop(&mut self) { - self.limiter.release_quota(self.quota); + if let Some(quota) = self.quota.take() { + self.limiter.release_quota(quota); + self.limiter.may_notify_waiters(); + } } } @@ -576,7 +596,7 @@ pub(crate) fn filter_with_delete_range<'a>( } pub(crate) async fn wait_for_epoch( - notifier: &Sender, + notifier: &tokio::sync::watch::Sender, wait_epoch: u64, ) -> StorageResult<()> { let mut receiver = notifier.subscribe(); @@ -619,9 +639,12 @@ pub(crate) async fn wait_for_epoch( #[cfg(test)] mod tests { use std::future::{poll_fn, Future}; + use std::sync::Arc; use std::task::Poll; + use futures::future::join_all; use futures::FutureExt; + use rand::random; use crate::hummock::utils::MemoryLimiter; @@ -653,4 +676,42 @@ mod tests { drop(tracker3); assert_eq!(0, memory_limiter.get_memory_usage()); } + + #[tokio::test(flavor = "multi_thread", worker_threads = 8)] + async fn test_multi_thread_acquire_memory() { + const QUOTA: u64 = 10; + let memory_limiter = Arc::new(MemoryLimiter::new(200)); + let mut handles = vec![]; + for _ in 0..40 { + let limiter = memory_limiter.clone(); + let h = tokio::spawn(async move { + let mut buffers = vec![]; + let mut current_buffer_usage = (random::() % 8) + 2; + for _ in 0..1000 { + if buffers.len() < current_buffer_usage + && let Some(tracker) = limiter.try_require_memory(QUOTA) + { + buffers.push(tracker); + } else { + buffers.clear(); + current_buffer_usage = (random::() % 8) + 2; + let req = limiter.require_memory(QUOTA); + match tokio::time::timeout(std::time::Duration::from_millis(1), req).await { + Ok(tracker) => { + buffers.push(tracker); + } + Err(_) => { + continue; + } + } + } + let sleep_time = random::() % 3 + 1; + tokio::time::sleep(std::time::Duration::from_millis(sleep_time)).await; + } + }); + handles.push(h); + } + let h = join_all(handles); + let _ = h.await; + } } diff --git a/src/storage/src/lib.rs b/src/storage/src/lib.rs index 505eec276fbf4..21c0c7f49ae4c 100644 --- a/src/storage/src/lib.rs +++ b/src/storage/src/lib.rs @@ -14,7 +14,6 @@ #![feature(allocator_api)] #![feature(bound_as_ref)] -#![feature(bound_map)] #![feature(custom_test_frameworks)] #![feature(extract_if)] #![feature(coroutines)] @@ -37,7 +36,6 @@ #![recursion_limit = "256"] #![feature(error_generic_member_access)] #![feature(let_chains)] -#![feature(associated_type_bounds)] #![feature(exclusive_range_pattern)] #![feature(impl_trait_in_assoc_type)] #![feature(maybe_uninit_uninit_array)] diff --git a/src/storage/src/memory.rs b/src/storage/src/memory.rs index 5e645aa7ea57f..3bf4030ce62f8 100644 --- a/src/storage/src/memory.rs +++ b/src/storage/src/memory.rs @@ -350,7 +350,6 @@ mod batched_iter { #[cfg(test)] mod tests { use rand::Rng; - use risingwave_hummock_sdk::key::FullKey; use super::*; use crate::memory::sled::SledRangeKv; diff --git a/src/storage/src/monitor/traced_store.rs b/src/storage/src/monitor/traced_store.rs index 47c0de67729b6..df93f4c2d1a12 100644 --- a/src/storage/src/monitor/traced_store.rs +++ b/src/storage/src/monitor/traced_store.rs @@ -29,7 +29,6 @@ use crate::error::StorageResult; use crate::hummock::sstable_store::SstableStoreRef; use crate::hummock::{HummockStorage, SstableObjectIdManagerRef}; use crate::store::*; -use crate::StateStore; #[derive(Clone)] pub struct TracedStateStore { diff --git a/src/storage/src/row_serde/value_serde.rs b/src/storage/src/row_serde/value_serde.rs index bde7091597605..c4d4ef8b808f7 100644 --- a/src/storage/src/row_serde/value_serde.rs +++ b/src/storage/src/row_serde/value_serde.rs @@ -145,7 +145,6 @@ mod tests { use std::collections::HashSet; use risingwave_common::catalog::ColumnId; - use risingwave_common::row::OwnedRow; use risingwave_common::types::ScalarImpl::*; use risingwave_common::util::value_encoding::column_aware_row_encoding; use risingwave_common::util::value_encoding::column_aware_row_encoding::try_drop_invalid_columns; diff --git a/src/storage/src/storage_failpoints/test_iterator.rs b/src/storage/src/storage_failpoints/test_iterator.rs index 463c20ed469de..3495ce116e5bb 100644 --- a/src/storage/src/storage_failpoints/test_iterator.rs +++ b/src/storage/src/storage_failpoints/test_iterator.rs @@ -41,7 +41,7 @@ use crate::monitor::StoreLocalStatistic; async fn test_failpoints_concat_read_err() { fail::cfg("disable_block_cache", "return").unwrap(); let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), @@ -101,7 +101,7 @@ async fn test_failpoints_concat_read_err() { async fn test_failpoints_backward_concat_read_err() { fail::cfg("disable_block_cache", "return").unwrap(); let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_info( 0, default_builder_opt_for_test(), @@ -157,7 +157,7 @@ async fn test_failpoints_backward_concat_read_err() { async fn test_failpoints_merge_invalid_key() { fail::cfg("disable_block_cache", "return").unwrap(); let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -205,7 +205,7 @@ async fn test_failpoints_merge_invalid_key() { async fn test_failpoints_backward_merge_invalid_key() { fail::cfg("disable_block_cache", "return").unwrap(); let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -249,7 +249,7 @@ async fn test_failpoints_backward_merge_invalid_key() { async fn test_failpoints_user_read_err() { fail::cfg("disable_block_cache", "return").unwrap(); let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -309,7 +309,7 @@ async fn test_failpoints_user_read_err() { async fn test_failpoints_backward_user_read_err() { fail::cfg("disable_block_cache", "return").unwrap(); let mem_read_err = "mem_read_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; let table0 = gen_iterator_test_sstable_base( 0, default_builder_opt_for_test(), @@ -362,7 +362,7 @@ async fn test_failpoints_compactor_iterator_recreate() { let get_stream_err = "get_stream_err"; let stream_read_err = "stream_read_err"; let create_stream_err = "create_stream_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // when upload data is successful, but upload meta is fail and delete is fail let has_create = Arc::new(AtomicBool::new(false)); fail::cfg_callback(get_stream_err, move || { diff --git a/src/storage/src/storage_failpoints/test_sstable.rs b/src/storage/src/storage_failpoints/test_sstable.rs index 3361bdf55cde5..c89791767b4d0 100644 --- a/src/storage/src/storage_failpoints/test_sstable.rs +++ b/src/storage/src/storage_failpoints/test_sstable.rs @@ -35,7 +35,7 @@ use crate::monitor::StoreLocalStatistic; async fn test_failpoints_table_read() { let mem_read_err_fp = "mem_read_err"; // build remote table - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // We should close buffer, so that table iterator must read in object_stores let kv_iter = @@ -84,7 +84,7 @@ async fn test_failpoints_vacuum_and_metadata() { let data_upload_err = "data_upload_err"; let mem_upload_err = "mem_upload_err"; let mem_delete_err = "mem_delete_err"; - let sstable_store = mock_sstable_store(); + let sstable_store = mock_sstable_store().await; // when upload data is successful, but upload meta is fail and delete is fail fail::cfg_callback(data_upload_err, move || { diff --git a/src/storage/src/store.rs b/src/storage/src/store.rs index ade0ad7ffff64..f389691f94c20 100644 --- a/src/storage/src/store.rs +++ b/src/storage/src/store.rs @@ -66,7 +66,8 @@ pub fn to_owned_item((key, value): StateStoreIterItemRef<'_>) -> StorageResult: StateStoreIter + Sized { - type ItemStream: Stream> + Send; + type ItemStream Fn(T::ItemRef<'a>) -> StorageResult>: Stream> + + Send; fn into_stream Fn(T::ItemRef<'a>) -> StorageResult + Send>( self, @@ -151,7 +152,8 @@ impl> FusedStateStoreIter { } impl> StateStoreIterExt for I { - type ItemStream = impl Stream> + Send; + type ItemStream Fn(T::ItemRef<'a>) -> StorageResult> = + impl Stream> + Send; fn into_stream Fn(T::ItemRef<'a>) -> StorageResult + Send>( self, diff --git a/src/storage/src/store_impl.rs b/src/storage/src/store_impl.rs index cf267e508219e..143cf5cc50080 100644 --- a/src/storage/src/store_impl.rs +++ b/src/storage/src/store_impl.rs @@ -13,22 +13,25 @@ // limitations under the License. use std::fmt::Debug; -use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use enum_as_inner::EnumAsInner; +use foyer::{ + set_metrics_registry, FsDeviceConfigBuilder, HybridCacheBuilder, RatedTicketAdmissionPolicy, + RuntimeConfigBuilder, +}; use risingwave_common::monitor::GLOBAL_METRICS_REGISTRY; use risingwave_common_service::observer_manager::RpcNotificationClient; +use risingwave_hummock_sdk::HummockSstableObjectId; use risingwave_object_store::object::build_remote_object_store; use crate::error::StorageResult; use crate::filter_key_extractor::{RemoteTableAccessor, RpcFilterKeyExtractorManager}; -use crate::hummock::file_cache::preclude::*; use crate::hummock::hummock_meta_client::MonitoredHummockMetaClient; use crate::hummock::{ - set_foyer_metrics_registry, FileCache, FileCacheConfig, HummockError, HummockStorage, - RecentFilter, SstableStore, SstableStoreConfig, + Block, HummockError, HummockStorage, RecentFilter, Sstable, SstableBlockIndex, SstableStore, + SstableStoreConfig, }; use crate::memory::sled::SledStateStore; use crate::memory::MemoryStateStore; @@ -220,7 +223,6 @@ pub mod verify { use crate::storage_value::StorageValue; use crate::store::*; use crate::store_impl::AsHummock; - use crate::StateStore; fn assert_result_eq( first: &std::result::Result, @@ -556,6 +558,7 @@ pub mod verify { impl StateStoreImpl { #[cfg_attr(not(target_os = "linux"), allow(unused_variables))] #[allow(clippy::too_many_arguments)] + #[expect(clippy::borrowed_box)] pub async fn new( s: &str, opts: Arc, @@ -566,75 +569,116 @@ impl StateStoreImpl { compactor_metrics: Arc, await_tree_config: Option, ) -> StorageResult { - set_foyer_metrics_registry(GLOBAL_METRICS_REGISTRY.clone()); + const MB: usize = 1 << 20; + + set_metrics_registry(GLOBAL_METRICS_REGISTRY.clone()); + + let meta_cache_v2 = { + let mut builder = HybridCacheBuilder::new() + .memory(opts.meta_cache_capacity_mb * MB) + .with_shards(opts.meta_cache_shard_num) + .with_eviction_config(opts.meta_cache_eviction_config.clone()) + .with_object_pool_capacity(1024 * opts.meta_cache_shard_num) + .with_weighter(|_: &HummockSstableObjectId, value: &Box| { + u64::BITS as usize / 8 + value.estimate_size() + }) + .storage(); + + if !opts.meta_file_cache_dir.is_empty() { + builder = builder + .with_name("foyer.meta") + .with_device_config( + FsDeviceConfigBuilder::new(&opts.meta_file_cache_dir) + .with_capacity(opts.meta_file_cache_capacity_mb * MB) + .with_file_size(opts.meta_file_cache_file_capacity_mb * MB) + .with_align(opts.meta_file_cache_device_align) + .with_io_size(opts.meta_file_cache_device_io_size) + .build(), + ) + .with_catalog_shards(64) + .with_admission_policy(Arc::new(RatedTicketAdmissionPolicy::new( + opts.meta_file_cache_insert_rate_limit_mb * MB, + ))) + .with_flushers(opts.meta_file_cache_flushers) + .with_reclaimers(opts.meta_file_cache_reclaimers) + .with_clean_region_threshold( + opts.meta_file_cache_reclaimers + opts.meta_file_cache_reclaimers / 2, + ) + .with_recover_concurrency(opts.meta_file_cache_recover_concurrency) + .with_compression( + opts.meta_file_cache_compression + .as_str() + .try_into() + .map_err(HummockError::foyer_error)?, + ) + .with_runtime_config( + RuntimeConfigBuilder::new() + .with_thread_name("foyer.meta.runtime") + .build(), + ) + .with_lazy(true); + } - let (data_file_cache, recent_filter) = if opts.data_file_cache_dir.is_empty() { - (FileCache::none(), None) - } else { - const MB: usize = 1024 * 1024; - - let config = FileCacheConfig { - name: "data".to_string(), - dir: PathBuf::from(opts.data_file_cache_dir.clone()), - capacity: opts.data_file_cache_capacity_mb * MB, - file_capacity: opts.data_file_cache_file_capacity_mb * MB, - device_align: opts.data_file_cache_device_align, - device_io_size: opts.data_file_cache_device_io_size, - lfu_window_to_cache_size_ratio: opts.data_file_cache_lfu_window_to_cache_size_ratio, - lfu_tiny_lru_capacity_ratio: opts.data_file_cache_lfu_tiny_lru_capacity_ratio, - insert_rate_limit: opts.data_file_cache_insert_rate_limit_mb * MB, - flushers: opts.data_file_cache_flushers, - reclaimers: opts.data_file_cache_reclaimers, - recover_concurrency: opts.data_file_cache_recover_concurrency, - catalog_bits: opts.data_file_cache_catalog_bits, - admissions: vec![], - reinsertions: vec![], - compression: opts - .data_file_cache_compression - .as_str() - .try_into() - .map_err(HummockError::file_cache)?, - }; - let cache = FileCache::open(config) - .await - .map_err(HummockError::file_cache)?; - let filter = Some(Arc::new(RecentFilter::new( - opts.cache_refill_recent_filter_layers, - Duration::from_millis(opts.cache_refill_recent_filter_rotate_interval_ms as u64), - ))); - (cache, filter) + builder.build().await.map_err(HummockError::foyer_error)? }; - let meta_file_cache = if opts.meta_file_cache_dir.is_empty() { - FileCache::none() + let block_cache_v2 = { + let mut builder = HybridCacheBuilder::new() + .memory(opts.block_cache_capacity_mb * MB) + .with_shards(opts.block_cache_shard_num) + .with_eviction_config(opts.block_cache_eviction_config.clone()) + .with_object_pool_capacity(1024 * opts.block_cache_shard_num) + .with_weighter(|_: &SstableBlockIndex, value: &Box| { + // FIXME(MrCroxx): Calculate block weight more accurately. + u64::BITS as usize * 2 / 8 + value.raw().len() + }) + .storage(); + + if !opts.meta_file_cache_dir.is_empty() { + builder = builder + .with_name("foyer.block") + .with_device_config( + FsDeviceConfigBuilder::new(&opts.data_file_cache_dir) + .with_capacity(opts.data_file_cache_capacity_mb * MB) + .with_file_size(opts.data_file_cache_file_capacity_mb * MB) + .with_align(opts.data_file_cache_device_align) + .with_io_size(opts.data_file_cache_device_io_size) + .build(), + ) + .with_catalog_shards(64) + .with_admission_policy(Arc::new(RatedTicketAdmissionPolicy::new( + opts.data_file_cache_insert_rate_limit_mb * MB, + ))) + .with_flushers(opts.data_file_cache_flushers) + .with_reclaimers(opts.data_file_cache_reclaimers) + .with_clean_region_threshold( + opts.data_file_cache_reclaimers + opts.data_file_cache_reclaimers / 2, + ) + .with_recover_concurrency(opts.data_file_cache_recover_concurrency) + .with_compression( + opts.data_file_cache_compression + .as_str() + .try_into() + .map_err(HummockError::foyer_error)?, + ) + .with_runtime_config( + RuntimeConfigBuilder::new() + .with_thread_name("foyer.block.runtime") + .build(), + ) + .with_lazy(true); + } + + builder.build().await.map_err(HummockError::foyer_error)? + }; + + let recent_filter = if opts.data_file_cache_dir.is_empty() { + None } else { - const MB: usize = 1024 * 1024; - - let config = FileCacheConfig { - name: "meta".to_string(), - dir: PathBuf::from(opts.meta_file_cache_dir.clone()), - capacity: opts.meta_file_cache_capacity_mb * MB, - file_capacity: opts.meta_file_cache_file_capacity_mb * MB, - device_align: opts.meta_file_cache_device_align, - device_io_size: opts.meta_file_cache_device_io_size, - lfu_window_to_cache_size_ratio: opts.meta_file_cache_lfu_window_to_cache_size_ratio, - lfu_tiny_lru_capacity_ratio: opts.meta_file_cache_lfu_tiny_lru_capacity_ratio, - insert_rate_limit: opts.meta_file_cache_insert_rate_limit_mb * MB, - flushers: opts.meta_file_cache_flushers, - reclaimers: opts.meta_file_cache_reclaimers, - recover_concurrency: opts.meta_file_cache_recover_concurrency, - catalog_bits: opts.meta_file_cache_catalog_bits, - admissions: vec![], - reinsertions: vec![], - compression: opts - .meta_file_cache_compression - .as_str() - .try_into() - .map_err(HummockError::file_cache)?, - }; - FileCache::open(config) - .await - .map_err(HummockError::file_cache)? + Some(Arc::new(RecentFilter::new( + opts.cache_refill_recent_filter_layers, + Duration::from_millis(opts.cache_refill_recent_filter_rotate_interval_ms as u64), + ))) }; let store = match s { @@ -650,18 +694,13 @@ impl StateStoreImpl { let sstable_store = Arc::new(SstableStore::new(SstableStoreConfig { store: Arc::new(object_store), path: opts.data_directory.to_string(), - block_cache_capacity: opts.block_cache_capacity_mb * (1 << 20), - block_cache_shard_num: opts.block_cache_shard_num, - block_cache_eviction: opts.block_cache_eviction_config.clone(), - meta_cache_capacity: opts.meta_cache_capacity_mb * (1 << 20), - meta_cache_shard_num: opts.meta_cache_shard_num, - meta_cache_eviction: opts.meta_cache_eviction_config.clone(), prefetch_buffer_capacity: opts.prefetch_buffer_capacity_mb * (1 << 20), max_prefetch_block_number: opts.max_prefetch_block_number, - data_file_cache, - meta_file_cache, recent_filter, state_store_metrics: state_store_metrics.clone(), + + meta_cache_v2, + block_cache_v2, })); let notification_client = RpcNotificationClient::new(hummock_meta_client.get_inner().clone()); @@ -739,7 +778,6 @@ pub mod boxed_state_store { use crate::hummock::HummockStorage; use crate::store::*; use crate::store_impl::AsHummock; - use crate::StateStore; #[async_trait::async_trait] pub trait DynamicDispatchedStateStoreIter: Send { diff --git a/src/storage/src/table/batch_table/storage_table.rs b/src/storage/src/table/batch_table/storage_table.rs index 791e958b28774..1435eb21029a2 100644 --- a/src/storage/src/table/batch_table/storage_table.rs +++ b/src/storage/src/table/batch_table/storage_table.rs @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::default::Default; use std::ops::Bound::{self, Excluded, Included, Unbounded}; use std::ops::{Index, RangeBounds}; use std::sync::Arc; @@ -20,7 +19,7 @@ use std::sync::Arc; use auto_enums::auto_enum; use await_tree::InstrumentAwait; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::future::try_join_all; use futures::{Stream, StreamExt}; use futures_async_stream::try_stream; diff --git a/src/stream/spill_test/src/lib.rs b/src/stream/spill_test/src/lib.rs index 9c2d3d05bda70..deadc48ea9a33 100644 --- a/src/stream/spill_test/src/lib.rs +++ b/src/stream/spill_test/src/lib.rs @@ -13,9 +13,7 @@ // limitations under the License. #![feature(proc_macro_hygiene, stmt_expr_attributes)] #![feature(custom_test_frameworks)] -#![feature(bound_map)] #![feature(type_alias_impl_trait)] -#![feature(associated_type_bounds)] #[cfg(test)] mod test_mem_table; diff --git a/src/stream/src/common/compact_chunk.rs b/src/stream/src/common/compact_chunk.rs index 32dc4d0a996a6..5d6acb5125422 100644 --- a/src/stream/src/common/compact_chunk.rs +++ b/src/stream/src/common/compact_chunk.rs @@ -105,7 +105,7 @@ type OpRowMap<'a, 'b> = pub enum RowOp<'a> { Insert(RowRef<'a>), Delete(RowRef<'a>), - /// (old_value, new_value) + /// (`old_value`, `new_value`) Update((RowRef<'a>, RowRef<'a>)), } static LOG_SUPPERSSER: LazyLock = LazyLock::new(LogSuppresser::default); @@ -333,7 +333,6 @@ pub fn merge_chunk_row(stream_chunk: StreamChunk, pk_indices: &[usize]) -> Strea #[cfg(test)] mod tests { - use risingwave_common::array::StreamChunk; use risingwave_common::test_prelude::StreamChunkTestExt; use super::*; diff --git a/src/stream/src/common/log_store_impl/kv_log_store/reader.rs b/src/stream/src/common/log_store_impl/kv_log_store/reader.rs index 10e8d7d336696..7cad46b5b5fc0 100644 --- a/src/stream/src/common/log_store_impl/kv_log_store/reader.rs +++ b/src/stream/src/common/log_store_impl/kv_log_store/reader.rs @@ -19,7 +19,7 @@ use std::time::Duration; use anyhow::anyhow; use await_tree::InstrumentAwait; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::future::{try_join_all, BoxFuture}; use futures::{FutureExt, TryFutureExt}; use risingwave_common::array::StreamChunk; diff --git a/src/stream/src/common/log_store_impl/kv_log_store/serde.rs b/src/stream/src/common/log_store_impl/kv_log_store/serde.rs index 67167f466a50b..9eb7faf237ead 100644 --- a/src/stream/src/common/log_store_impl/kv_log_store/serde.rs +++ b/src/stream/src/common/log_store_impl/kv_log_store/serde.rs @@ -544,7 +544,7 @@ impl LogStoreRowOpStream { } } -pub(crate) type LogStoreItemMergeStream = +pub(crate) type LogStoreItemMergeStream = impl Stream>; pub(crate) fn merge_log_store_item_stream( iters: Vec, diff --git a/src/stream/src/common/table/state_table.rs b/src/stream/src/common/table/state_table.rs index c7e9b9800bc8b..5be9dd96feb02 100644 --- a/src/stream/src/common/table/state_table.rs +++ b/src/stream/src/common/table/state_table.rs @@ -13,14 +13,13 @@ // limitations under the License. use std::collections::HashMap; -use std::default::Default; use std::ops::Bound; use std::ops::Bound::*; use std::sync::Arc; use bytes::{BufMut, Bytes, BytesMut}; use either::Either; -use foyer::memory::CacheContext; +use foyer::CacheContext; use futures::{pin_mut, FutureExt, Stream, StreamExt, TryStreamExt}; use futures_async_stream::for_await; use itertools::{izip, Itertools}; diff --git a/src/stream/src/error.rs b/src/stream/src/error.rs index 7aaffc7824d5e..defefa7b474ae 100644 --- a/src/stream/src/error.rs +++ b/src/stream/src/error.rs @@ -87,7 +87,7 @@ pub enum ErrorKind { }, #[error(transparent)] - Internal( + Uncategorized( #[from] #[backtrace] anyhow::Error, diff --git a/src/stream/src/executor/backfill/arrangement_backfill.rs b/src/stream/src/executor/backfill/arrangement_backfill.rs index f43d7767b45ce..8624551042796 100644 --- a/src/stream/src/executor/backfill/arrangement_backfill.rs +++ b/src/stream/src/executor/backfill/arrangement_backfill.rs @@ -341,9 +341,17 @@ where // Before processing barrier, if did not snapshot read, // do a snapshot read first. // This is so we don't lose the tombstone iteration progress. - // If paused, we also can't read any snapshot records. - if !has_snapshot_read && !paused { - // If we have not snapshot read, builders must all be empty. + // Or if s3 read latency is high, we don't fail to read from s3. + // + // If paused, we can't read any snapshot records, skip this. + // + // If rate limit is set, respect the rate limit, check if we can read, + // If we can't, skip it. If no rate limit set, we can read. + let rate_limit_ready = rate_limiter + .as_ref() + .map(|r| r.check().is_ok()) + .unwrap_or(true); + if !has_snapshot_read && !paused && rate_limit_ready { debug_assert!(builders.values().all(|b| b.is_empty())); let (_, snapshot) = backfill_stream.into_inner(); #[for_await] diff --git a/src/stream/src/executor/chain.rs b/src/stream/src/executor/chain.rs index ebcbe1e4e49bb..96b9422a97b2c 100644 --- a/src/stream/src/executor/chain.rs +++ b/src/stream/src/executor/chain.rs @@ -103,7 +103,6 @@ impl Execute for ChainExecutor { #[cfg(test)] mod test { - use std::default::Default; use futures::StreamExt; use risingwave_common::array::stream_chunk::StreamChunkTestExt; diff --git a/src/stream/src/executor/dedup/append_only_dedup.rs b/src/stream/src/executor/dedup/append_only_dedup.rs index f73e196815400..f98847cf1de4b 100644 --- a/src/stream/src/executor/dedup/append_only_dedup.rs +++ b/src/stream/src/executor/dedup/append_only_dedup.rs @@ -182,19 +182,15 @@ impl Execute for AppendOnlyDedupExecutor { #[cfg(test)] mod tests { use std::sync::atomic::AtomicU64; - use std::sync::Arc; - use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema, TableId}; + use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, TableId}; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::types::DataType; use risingwave_common::util::epoch::test_epoch; use risingwave_common::util::sort_util::OrderType; use risingwave_storage::memory::MemoryStateStore; use super::*; - use crate::common::table::state_table::StateTable; use crate::executor::test_utils::MockSource; - use crate::executor::ActorContext; #[tokio::test] async fn test_dedup_executor() { diff --git a/src/stream/src/executor/dispatch.rs b/src/stream/src/executor/dispatch.rs index 6c644466f9683..9e6503c2d9a28 100644 --- a/src/stream/src/executor/dispatch.rs +++ b/src/stream/src/executor/dispatch.rs @@ -1029,26 +1029,21 @@ impl Dispatcher for SimpleDispatcher { #[cfg(test)] mod tests { use std::hash::{BuildHasher, Hasher}; - use std::sync::{Arc, Mutex}; + use std::sync::Mutex; use async_trait::async_trait; - use futures::{pin_mut, StreamExt}; - use itertools::Itertools; + use futures::pin_mut; use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::array::{Array, ArrayBuilder, I32ArrayBuilder, Op}; - use risingwave_common::catalog::Schema; + use risingwave_common::array::{Array, ArrayBuilder, I32ArrayBuilder}; use risingwave_common::config; - use risingwave_common::hash::VirtualNode; use risingwave_common::util::epoch::test_epoch; use risingwave_common::util::hash_util::Crc32FastBuilder; - use risingwave_common::util::iter_util::ZipEqFast; use risingwave_pb::stream_plan::DispatcherType; use super::*; use crate::executor::exchange::output::Output; use crate::executor::exchange::permit::channel_for_test; use crate::executor::receiver::ReceiverExecutor; - use crate::executor::Execute; use crate::task::test_utils::helper_make_local_actor; #[derive(Debug)] diff --git a/src/stream/src/executor/dml.rs b/src/stream/src/executor/dml.rs index b8839d76000c1..adcb3f01ab8bd 100644 --- a/src/stream/src/executor/dml.rs +++ b/src/stream/src/executor/dml.rs @@ -273,13 +273,9 @@ impl Execute for DmlExecutor { #[cfg(test)] mod tests { - use std::sync::Arc; - use risingwave_common::array::StreamChunk; - use risingwave_common::catalog::{ColumnId, Field, Schema, INITIAL_TABLE_VERSION_ID}; + use risingwave_common::catalog::{ColumnId, Field, INITIAL_TABLE_VERSION_ID}; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::transaction::transaction_id::TxnId; - use risingwave_common::types::DataType; use risingwave_common::util::epoch::test_epoch; use risingwave_dml::dml_manager::DmlManager; diff --git a/src/stream/src/executor/dynamic_filter.rs b/src/stream/src/executor/dynamic_filter.rs index ddbe8352b2e8a..e9c631202e8bb 100644 --- a/src/stream/src/executor/dynamic_filter.rs +++ b/src/stream/src/executor/dynamic_filter.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::ops::Bound::{self, *}; -use std::sync::Arc; use futures::stream; use risingwave_common::array::{Array, ArrayImpl, Op}; @@ -21,7 +20,7 @@ use risingwave_common::bail; use risingwave_common::buffer::{Bitmap, BitmapBuilder}; use risingwave_common::hash::VnodeBitmapExt; use risingwave_common::row::{self, once, OwnedRow as RowData}; -use risingwave_common::types::{DataType, Datum, DefaultOrd, ScalarImpl, ToDatumRef, ToOwnedDatum}; +use risingwave_common::types::{DefaultOrd, ToDatumRef, ToOwnedDatum}; use risingwave_common::util::iter_util::ZipEqDebug; use risingwave_expr::expr::{ build_func_non_strict, InputRefExpression, LiteralExpression, NonStrictExpression, @@ -489,9 +488,8 @@ impl Execute #[cfg(test)] mod tests { - use risingwave_common::array::stream_chunk::StreamChunkTestExt; use risingwave_common::array::*; - use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema, TableId}; + use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, TableId}; use risingwave_common::util::epoch::test_epoch; use risingwave_common::util::sort_util::OrderType; use risingwave_hummock_sdk::HummockReadEpoch; @@ -500,7 +498,6 @@ mod tests { use super::*; use crate::executor::test_utils::{MessageSender, MockSource, StreamExecutorTestExt}; - use crate::executor::{ActorContext, StreamExecutorResult}; async fn create_in_memory_state_table( mem_state: MemoryStateStore, diff --git a/src/stream/src/executor/error.rs b/src/stream/src/executor/error.rs index db70dcffa7fdd..41b8198b646a3 100644 --- a/src/stream/src/executor/error.rs +++ b/src/stream/src/executor/error.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::AsRef; - use risingwave_common::array::ArrayError; use risingwave_common::error::{BoxedError, NotImplemented}; use risingwave_common::util::value_encoding::error::ValueEncodingError; @@ -113,7 +111,7 @@ pub enum ErrorKind { NotImplemented(#[from] NotImplemented), #[error(transparent)] - Internal( + Uncategorized( #[from] #[backtrace] anyhow::Error, @@ -150,7 +148,7 @@ impl From for StreamExecutorError { impl From for StreamExecutorError { fn from(s: String) -> Self { - ErrorKind::Internal(anyhow::anyhow!(s)).into() + ErrorKind::Uncategorized(anyhow::anyhow!(s)).into() } } diff --git a/src/stream/src/executor/exchange/input.rs b/src/stream/src/executor/exchange/input.rs index 11796441326aa..0fc4845476db0 100644 --- a/src/stream/src/executor/exchange/input.rs +++ b/src/stream/src/executor/exchange/input.rs @@ -16,7 +16,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use anyhow::Context as _; -use futures::{pin_mut, Stream}; +use futures::pin_mut; use futures_async_stream::try_stream; use pin_project::pin_project; use risingwave_common::util::addr::{is_local_address, HostAddr}; @@ -25,9 +25,6 @@ use risingwave_rpc_client::ComputeClientPool; use super::error::ExchangeChannelClosed; use super::permit::Receiver; -use crate::error::StreamResult; -use crate::executor::error::StreamExecutorError; -use crate::executor::monitor::StreamingMetrics; use crate::executor::prelude::*; use crate::task::{ FragmentId, LocalBarrierManager, SharedContext, UpDownActorIds, UpDownFragmentIds, diff --git a/src/stream/src/executor/filter.rs b/src/stream/src/executor/filter.rs index 4d1ecb098bd8f..b183719a9a677 100644 --- a/src/stream/src/executor/filter.rs +++ b/src/stream/src/executor/filter.rs @@ -157,11 +157,8 @@ impl FilterExecutor { #[cfg(test)] mod tests { - use futures::StreamExt; use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::array::StreamChunk; - use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::DataType; + use risingwave_common::catalog::Field; use super::super::test_utils::expr::build_from_pretty; use super::super::test_utils::MockSource; diff --git a/src/stream/src/executor/hash_join.rs b/src/stream/src/executor/hash_join.rs index ea0253c9cbbc1..60e2e532eb1fa 100644 --- a/src/stream/src/executor/hash_join.rs +++ b/src/stream/src/executor/hash_join.rs @@ -17,7 +17,7 @@ use std::time::Duration; use itertools::Itertools; use multimap::MultiMap; -use risingwave_common::array::{Op, RowRef}; +use risingwave_common::array::Op; use risingwave_common::hash::{HashKey, NullBitmap}; use risingwave_common::types::{DefaultOrd, ToOwnedDatum}; use risingwave_common::util::epoch::EpochPair; @@ -30,7 +30,7 @@ use self::builder::JoinChunkBuilder; use super::barrier_align::*; use super::join::hash_join::*; use super::join::row::JoinRow; -use super::join::{JoinTypePrimitive, SideTypePrimitive, *}; +use super::join::*; use super::watermark::*; use crate::executor::join::builder::JoinStreamChunkBuilder; use crate::executor::prelude::*; @@ -1051,20 +1051,16 @@ impl HashJoinExecutor; diff --git a/src/stream/src/executor/join/hash_join.rs b/src/stream/src/executor/join/hash_join.rs index 88b3ede24b619..8805ea3cc3120 100644 --- a/src/stream/src/executor/join/hash_join.rs +++ b/src/stream/src/executor/join/hash_join.rs @@ -734,7 +734,6 @@ impl JoinEntryState { mod tests { use itertools::Itertools; use risingwave_common::array::*; - use risingwave_common::types::{DataType, ScalarImpl}; use risingwave_common::util::iter_util::ZipEqDebug; use super::*; diff --git a/src/stream/src/executor/lookup.rs b/src/stream/src/executor/lookup.rs index 8d62d27e37f06..6dc4183fe27da 100644 --- a/src/stream/src/executor/lookup.rs +++ b/src/stream/src/executor/lookup.rs @@ -13,7 +13,6 @@ // limitations under the License. use async_trait::async_trait; -use futures::StreamExt; mod cache; mod sides; diff --git a/src/stream/src/executor/lookup_union.rs b/src/stream/src/executor/lookup_union.rs index 2189a821fab11..8c0e4103b5b2c 100644 --- a/src/stream/src/executor/lookup_union.rs +++ b/src/stream/src/executor/lookup_union.rs @@ -15,7 +15,7 @@ use async_trait::async_trait; use futures::channel::mpsc; use futures::future::{join_all, select, Either}; -use futures::{FutureExt, SinkExt, StreamExt}; +use futures::{FutureExt, SinkExt}; use itertools::Itertools; use crate::executor::prelude::*; @@ -120,10 +120,8 @@ impl LookupUnionExecutor { #[cfg(test)] mod tests { use futures::TryStreamExt; - use risingwave_common::array::StreamChunk; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::catalog::Field; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::types::DataType; use risingwave_common::util::epoch::test_epoch; use super::*; diff --git a/src/stream/src/executor/merge.rs b/src/stream/src/executor/merge.rs index 7a5450716f9ac..6d3f81928a302 100644 --- a/src/stream/src/executor/merge.rs +++ b/src/stream/src/executor/merge.rs @@ -18,7 +18,6 @@ use std::task::{Context, Poll}; use anyhow::Context as _; use futures::stream::{FusedStream, FuturesUnordered, StreamFuture}; -use futures::StreamExt; use tokio::time::Instant; use super::exchange::input::BoxedInput; @@ -27,7 +26,7 @@ use super::*; use crate::executor::exchange::input::new_input; use crate::executor::prelude::*; use crate::executor::utils::ActorInputMetrics; -use crate::task::{FragmentId, SharedContext}; +use crate::task::SharedContext; /// `MergeExecutor` merges data from multiple channels. Dataflow from one channel /// will be stopped on barrier. @@ -413,16 +412,12 @@ impl SelectReceivers { #[cfg(test)] mod tests { - use std::collections::HashSet; use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; use std::time::Duration; use assert_matches::assert_matches; use futures::FutureExt; - use itertools::Itertools; - use risingwave_common::array::{Op, StreamChunk}; - use risingwave_common::types::ScalarImpl; + use risingwave_common::array::Op; use risingwave_common::util::epoch::test_epoch; use risingwave_pb::stream_plan::StreamMessage; use risingwave_pb::task_service::exchange_service_server::{ @@ -439,7 +434,6 @@ mod tests { use super::*; use crate::executor::exchange::input::RemoteInput; use crate::executor::exchange::permit::channel_for_test; - use crate::executor::{Barrier, Execute, Mutation}; use crate::task::test_utils::helper_make_local_actor; use crate::task::LocalBarrierManager; diff --git a/src/stream/src/executor/mview/materialize.rs b/src/stream/src/executor/mview/materialize.rs index 65c35e2094b73..9ad0dfbdfcea3 100644 --- a/src/stream/src/executor/mview/materialize.rs +++ b/src/stream/src/executor/mview/materialize.rs @@ -24,7 +24,7 @@ use itertools::Itertools; use risingwave_common::array::Op; use risingwave_common::buffer::Bitmap; use risingwave_common::catalog::{ColumnDesc, ColumnId, ConflictBehavior, TableId}; -use risingwave_common::row::{CompactedRow, OwnedRow, RowDeserializer}; +use risingwave_common::row::{CompactedRow, RowDeserializer}; use risingwave_common::types::DefaultOrd; use risingwave_common::util::chunk_coalesce::DataChunkBuilder; use risingwave_common::util::iter_util::{ZipEqDebug, ZipEqFast}; @@ -833,23 +833,17 @@ mod tests { use std::iter; use std::sync::atomic::AtomicU64; - use futures::stream::StreamExt; use rand::rngs::SmallRng; use rand::{Rng, RngCore, SeedableRng}; use risingwave_common::array::stream_chunk::{StreamChunkMut, StreamChunkTestExt}; - use risingwave_common::array::stream_chunk_builder::StreamChunkBuilder; - use risingwave_common::array::Op; - use risingwave_common::catalog::{ColumnDesc, ConflictBehavior, Field, Schema, TableId}; - use risingwave_common::row::OwnedRow; - use risingwave_common::types::DataType; + use risingwave_common::catalog::Field; use risingwave_common::util::epoch::test_epoch; - use risingwave_common::util::sort_util::{ColumnOrder, OrderType}; + use risingwave_common::util::sort_util::OrderType; use risingwave_hummock_sdk::HummockReadEpoch; use risingwave_storage::memory::MemoryStateStore; use risingwave_storage::table::batch_table::storage_table::StorageTable; use super::*; - use crate::executor::test_utils::prelude::StateTable; use crate::executor::test_utils::*; #[tokio::test] diff --git a/src/stream/src/executor/over_window/general.rs b/src/stream/src/executor/over_window/general.rs index 7cb29c39f7e37..c8ed97f8c59c1 100644 --- a/src/stream/src/executor/over_window/general.rs +++ b/src/stream/src/executor/over_window/general.rs @@ -30,7 +30,6 @@ use risingwave_expr::window_function::{ create_window_state, StateKey, WindowFuncCall, WindowStates, }; use risingwave_storage::row_serde::row_serde_util::serialize_pk_with_vnode; -use risingwave_storage::StateStore; use super::over_partition::{ new_empty_partition_cache, shrink_partition_cache, CacheKey, OverPartition, PartitionCache, @@ -38,8 +37,6 @@ use super::over_partition::{ }; use crate::cache::{new_unbounded, ManagedLruCache}; use crate::common::metrics::MetricsInfo; -use crate::common::table::state_table::StateTable; -use crate::executor::monitor::StreamingMetrics; use crate::executor::over_window::over_partition::AffectedRange; use crate::executor::prelude::*; diff --git a/src/stream/src/executor/over_window/over_partition.rs b/src/stream/src/executor/over_window/over_partition.rs index db431a17dafe4..baac154593f7a 100644 --- a/src/stream/src/executor/over_window/over_partition.rs +++ b/src/stream/src/executor/over_window/over_partition.rs @@ -32,6 +32,7 @@ use risingwave_expr::window_function::{ }; use risingwave_storage::store::PrefetchOptions; use risingwave_storage::StateStore; +use static_assertions::const_assert; use super::general::RowConverter; use crate::common::table::state_table::StateTable; @@ -88,27 +89,31 @@ pub(super) fn shrink_partition_cache( let (sk_start, sk_end) = recently_accessed_range.into_inner(); let (ck_start, ck_end) = (CacheKey::from(sk_start), CacheKey::from(sk_end)); + // find the cursor just before `ck_start` let mut cursor = range_cache.inner().upper_bound(Bound::Excluded(&ck_start)); for _ in 0..MAGIC_JITTER_PREVENTION { - if cursor.key().is_none() { + if cursor.prev().is_none() { + // already at the beginning break; } - cursor.move_prev(); } let start = cursor - .key() + .peek_prev() + .map(|(k, _)| k) .unwrap_or_else(|| range_cache.first_key_value().unwrap().0) .clone(); + // find the cursor just after `ck_end` let mut cursor = range_cache.inner().lower_bound(Bound::Excluded(&ck_end)); for _ in 0..MAGIC_JITTER_PREVENTION { - if cursor.key().is_none() { + if cursor.next().is_none() { + // already at the end break; } - cursor.move_next(); } let end = cursor - .key() + .peek_next() + .map(|(k, _)| k) .unwrap_or_else(|| range_cache.last_key_value().unwrap().0) .clone(); @@ -122,32 +127,39 @@ pub(super) fn shrink_partition_cache( let (sk_start, _sk_end) = recently_accessed_range.into_inner(); let ck_start = CacheKey::from(sk_start); - let mut capacity_remain = MAGIC_CACHE_SIZE; // precision is not important here, code simplicity is first + let mut capacity_remain = MAGIC_CACHE_SIZE; // precision is not important here, code simplicity is the first + const_assert!(MAGIC_JITTER_PREVENTION < MAGIC_CACHE_SIZE); - let mut cursor = range_cache.inner().upper_bound(Bound::Excluded(&ck_start)); + // find the cursor just before `ck_start` + let cursor_just_before_ck_start = + range_cache.inner().upper_bound(Bound::Excluded(&ck_start)); + + let mut cursor = cursor_just_before_ck_start.clone(); // go back for at most `MAGIC_JITTER_PREVENTION` entries for _ in 0..MAGIC_JITTER_PREVENTION { - if cursor.key().is_none() { + if cursor.prev().is_none() { + // already at the beginning break; } - cursor.move_prev(); capacity_remain -= 1; } let start = cursor - .key() + .peek_prev() + .map(|(k, _)| k) .unwrap_or_else(|| range_cache.first_key_value().unwrap().0) .clone(); - let mut cursor = range_cache.inner().lower_bound(Bound::Included(&ck_start)); + let mut cursor = cursor_just_before_ck_start; // go forward for at most `capacity_remain` entries for _ in 0..capacity_remain { - if cursor.key().is_none() { + if cursor.next().is_none() { + // already at the end break; } - cursor.move_next(); } let end = cursor - .key() + .peek_next() + .map(|(k, _)| k) .unwrap_or_else(|| range_cache.last_key_value().unwrap().0) .clone(); @@ -162,32 +174,39 @@ pub(super) fn shrink_partition_cache( let (_sk_start, sk_end) = recently_accessed_range.into_inner(); let ck_end = CacheKey::from(sk_end); - let mut capacity_remain = MAGIC_CACHE_SIZE; // precision is not important here, code simplicity is first + let mut capacity_remain = MAGIC_CACHE_SIZE; // precision is not important here, code simplicity is the first + const_assert!(MAGIC_JITTER_PREVENTION < MAGIC_CACHE_SIZE); + + // find the cursor just after `ck_end` + let cursor_just_after_ck_end = + range_cache.inner().lower_bound(Bound::Excluded(&ck_end)); - let mut cursor = range_cache.inner().lower_bound(Bound::Excluded(&ck_end)); + let mut cursor = cursor_just_after_ck_end.clone(); // go forward for at most `MAGIC_JITTER_PREVENTION` entries for _ in 0..MAGIC_JITTER_PREVENTION { - if cursor.key().is_none() { + if cursor.next().is_none() { + // already at the end break; } - cursor.move_next(); capacity_remain -= 1; } let end = cursor - .key() + .peek_next() + .map(|(k, _)| k) .unwrap_or_else(|| range_cache.last_key_value().unwrap().0) .clone(); - let mut cursor = range_cache.inner().upper_bound(Bound::Included(&ck_end)); + let mut cursor = cursor_just_after_ck_end; // go back for at most `capacity_remain` entries for _ in 0..capacity_remain { - if cursor.key().is_none() { + if cursor.prev().is_none() { + // already at the beginning break; } - cursor.move_prev(); } let start = cursor - .key() + .peek_prev() + .map(|(k, _)| k) .unwrap_or_else(|| range_cache.first_key_value().unwrap().0) .clone(); diff --git a/src/stream/src/executor/project.rs b/src/stream/src/executor/project.rs index e78238585c9fa..22ce33a1066d5 100644 --- a/src/stream/src/executor/project.rs +++ b/src/stream/src/executor/project.rs @@ -189,13 +189,11 @@ impl Inner { mod tests { use std::sync::atomic::{self, AtomicI64}; - use futures::StreamExt; use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::array::{DataChunk, StreamChunk}; - use risingwave_common::catalog::{Field, Schema}; - use risingwave_common::types::{DataType, Datum}; + use risingwave_common::array::DataChunk; + use risingwave_common::catalog::Field; use risingwave_common::util::epoch::test_epoch; - use risingwave_expr::expr::{self, Expression, ValueImpl}; + use risingwave_expr::expr::{self, ValueImpl}; use super::super::test_utils::MockSource; use super::super::*; diff --git a/src/stream/src/executor/receiver.rs b/src/stream/src/executor/receiver.rs index 3966b25febf5d..1493e3ce17cf9 100644 --- a/src/stream/src/executor/receiver.rs +++ b/src/stream/src/executor/receiver.rs @@ -191,15 +191,13 @@ impl Execute for ReceiverExecutor { #[cfg(test)] mod tests { use std::collections::HashMap; - use std::sync::Arc; use futures::{pin_mut, FutureExt}; - use risingwave_common::array::StreamChunk; use risingwave_common::util::epoch::test_epoch; use risingwave_pb::stream_plan::update_mutation::MergeUpdate; use super::*; - use crate::executor::{ActorContext, Barrier, Execute, Mutation, UpdateMutation}; + use crate::executor::UpdateMutation; use crate::task::test_utils::helper_make_local_actor; #[tokio::test] diff --git a/src/stream/src/executor/row_id_gen.rs b/src/stream/src/executor/row_id_gen.rs index c779092a307fb..2da75389fd9f7 100644 --- a/src/stream/src/executor/row_id_gen.rs +++ b/src/stream/src/executor/row_id_gen.rs @@ -123,16 +123,14 @@ impl Execute for RowIdGenExecutor { #[cfg(test)] mod tests { - use risingwave_common::array::{Array, PrimitiveArray}; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::array::PrimitiveArray; + use risingwave_common::catalog::Field; use risingwave_common::hash::VirtualNode; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::types::DataType; use risingwave_common::util::epoch::test_epoch; use super::*; use crate::executor::test_utils::MockSource; - use crate::executor::{ActorContext, Execute}; #[tokio::test] async fn test_row_id_gen_executor() { diff --git a/src/stream/src/executor/simple_agg.rs b/src/stream/src/executor/simple_agg.rs index 9c35627857828..e30ceaf73a851 100644 --- a/src/stream/src/executor/simple_agg.rs +++ b/src/stream/src/executor/simple_agg.rs @@ -295,9 +295,7 @@ mod tests { use risingwave_common::catalog::Field; use risingwave_common::types::*; use risingwave_common::util::epoch::test_epoch; - use risingwave_expr::aggregate::AggCall; use risingwave_storage::memory::MemoryStateStore; - use risingwave_storage::StateStore; use super::*; use crate::executor::test_utils::agg_executor::new_boxed_simple_agg_executor; diff --git a/src/stream/src/executor/sink.rs b/src/stream/src/executor/sink.rs index 1bd8d5e5c96a4..e12590527e3a8 100644 --- a/src/stream/src/executor/sink.rs +++ b/src/stream/src/executor/sink.rs @@ -15,7 +15,6 @@ use std::mem; use anyhow::anyhow; -use await_tree::InstrumentAwait; use futures::stream::select; use futures::{FutureExt, TryFutureExt, TryStreamExt}; use itertools::Itertools; @@ -481,7 +480,6 @@ mod test { use super::*; use crate::common::log_store_impl::in_mem::BoundedInMemLogStoreFactory; use crate::executor::test_utils::*; - use crate::executor::ActorContext; #[tokio::test] async fn test_force_append_only_sink() { diff --git a/src/stream/src/executor/sort.rs b/src/stream/src/executor/sort.rs index 37d08c746bba8..c2a01c8915a96 100644 --- a/src/stream/src/executor/sort.rs +++ b/src/stream/src/executor/sort.rs @@ -143,16 +143,13 @@ impl SortExecutor { #[cfg(test)] mod tests { use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::array::StreamChunk; - use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema, TableId}; - use risingwave_common::types::DataType; + use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, TableId}; use risingwave_common::util::epoch::test_epoch; use risingwave_common::util::sort_util::OrderType; use risingwave_storage::memory::MemoryStateStore; use super::*; use crate::executor::test_utils::{MessageSender, MockSource, StreamExecutorTestExt}; - use crate::executor::{ActorContext, BoxedMessageStream, Execute}; async fn create_executor( sort_column_index: usize, diff --git a/src/stream/src/executor/source/source_backfill_executor.rs b/src/stream/src/executor/source/source_backfill_executor.rs index 68dadf5806f30..0bc6e19f45fb5 100644 --- a/src/stream/src/executor/source/source_backfill_executor.rs +++ b/src/stream/src/executor/source/source_backfill_executor.rs @@ -733,11 +733,11 @@ impl SourceBackfillExecutorInner { if split_changed { stage .unfinished_splits - .retain(|split| target_state.get(split.id().as_ref()).is_some()); + .retain(|split| target_state.contains_key(split.id().as_ref())); let dropped_splits = stage .states - .extract_if(|split_id, _| target_state.get(split_id).is_none()) + .extract_if(|split_id, _| !target_state.contains_key(split_id)) .map(|(split_id, _)| split_id); if should_trim_state { @@ -826,7 +826,7 @@ impl SourceBackfillExecutorInner { ); let dropped_splits = - current_splits.extract_if(|split_id| target_splits.get(split_id).is_none()); + current_splits.extract_if(|split_id| !target_splits.contains(split_id)); if should_trim_state { // trim dropped splits' state diff --git a/src/stream/src/executor/source/source_executor.rs b/src/stream/src/executor/source/source_executor.rs index 455b6e0fc33e4..a53f48827454f 100644 --- a/src/stream/src/executor/source/source_executor.rs +++ b/src/stream/src/executor/source/source_executor.rs @@ -257,11 +257,11 @@ impl SourceExecutor { ); core.updated_splits_in_epoch - .retain(|split_id, _| target_state.get(split_id).is_some()); + .retain(|split_id, _| target_state.contains_key(split_id)); let dropped_splits = core .latest_split_info - .extract_if(|split_id, _| target_state.get(split_id).is_none()) + .extract_if(|split_id, _| !target_state.contains_key(split_id)) .map(|(_, split)| split) .collect_vec(); @@ -428,6 +428,24 @@ impl SourceExecutor { .await? { *ele = recover_state; + } else { + // This is a new split, not in state table. + if self.is_shared { + // For shared source, we start from latest and let the downstream SourceBackfillExecutors to read historical data. + // It's highly probable that the work of scanning historical data cannot be shared, + // so don't waste work on it. + // For more details, see https://github.com/risingwavelabs/risingwave/issues/16576#issuecomment-2095413297 + if ele.is_cdc_split() { + // shared CDC source already starts from latest. + continue; + } + match ele { + SplitImpl::Kafka(split) => { + split.seek_to_latest_offset(); + } + _ => unreachable!("only kafka source can be shared, got {:?}", ele), + } + } } } @@ -748,15 +766,11 @@ impl WaitEpochWorker { #[cfg(test)] mod tests { use std::collections::HashSet; - use std::time::Duration; - use futures::StreamExt; use maplit::{convert_args, hashmap}; - use risingwave_common::array::StreamChunk; - use risingwave_common::catalog::{ColumnId, Field, Schema, TableId}; + use risingwave_common::catalog::{ColumnId, Field, TableId}; use risingwave_common::system_param::local_manager::LocalSystemParamsManager; use risingwave_common::test_prelude::StreamChunkTestExt; - use risingwave_common::types::DataType; use risingwave_common::util::epoch::test_epoch; use risingwave_connector::source::datagen::DatagenSplit; use risingwave_connector::source::reader::desc::test_utils::create_source_desc_builder; @@ -768,7 +782,6 @@ mod tests { use super::*; use crate::executor::source::{default_source_internal_table, SourceStateTableHandler}; - use crate::executor::ActorContext; const MOCK_SOURCE_NAME: &str = "mock_source"; diff --git a/src/stream/src/executor/source/state_table_handler.rs b/src/stream/src/executor/source/state_table_handler.rs index 7fc12000656cd..1d14de9a493c9 100644 --- a/src/stream/src/executor/source/state_table_handler.rs +++ b/src/stream/src/executor/source/state_table_handler.rs @@ -257,11 +257,9 @@ pub fn default_source_internal_table(id: u32) -> PbTable { #[cfg(test)] pub(crate) mod tests { - use std::sync::Arc; - use risingwave_common::row::OwnedRow; - use risingwave_common::types::{Datum, ScalarImpl}; - use risingwave_common::util::epoch::{test_epoch, EpochPair}; + use risingwave_common::types::Datum; + use risingwave_common::util::epoch::test_epoch; use risingwave_connector::source::kafka::KafkaSplit; use risingwave_storage::memory::MemoryStateStore; use serde_json::Value; diff --git a/src/stream/src/executor/stateless_simple_agg.rs b/src/stream/src/executor/stateless_simple_agg.rs index 1360f4166008e..dd1f39580c367 100644 --- a/src/stream/src/executor/stateless_simple_agg.rs +++ b/src/stream/src/executor/stateless_simple_agg.rs @@ -125,16 +125,13 @@ impl StatelessSimpleAggExecutor { #[cfg(test)] mod tests { use assert_matches::assert_matches; - use futures::StreamExt; use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::array::StreamChunk; use risingwave_common::catalog::schema_test_utils; use risingwave_common::util::epoch::test_epoch; use super::*; use crate::executor::test_utils::agg_executor::generate_agg_schema; use crate::executor::test_utils::MockSource; - use crate::executor::{Execute, StatelessSimpleAggExecutor}; #[tokio::test] async fn test_no_chunk() { diff --git a/src/stream/src/executor/top_n/group_top_n.rs b/src/stream/src/executor/top_n/group_top_n.rs index 3a8a618e3c836..d6b2af690d838 100644 --- a/src/stream/src/executor/top_n/group_top_n.rs +++ b/src/stream/src/executor/top_n/group_top_n.rs @@ -267,11 +267,9 @@ mod tests { use std::sync::atomic::AtomicU64; use assert_matches::assert_matches; - use futures::StreamExt; use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::catalog::{Field, Schema}; + use risingwave_common::catalog::Field; use risingwave_common::hash::SerializedKey; - use risingwave_common::types::DataType; use risingwave_common::util::epoch::test_epoch; use risingwave_common::util::sort_util::OrderType; use risingwave_storage::memory::MemoryStateStore; @@ -279,7 +277,6 @@ mod tests { use super::*; use crate::executor::test_utils::top_n_executor::create_in_memory_state_table; use crate::executor::test_utils::MockSource; - use crate::executor::{ActorContext, Barrier, Execute, Message}; fn create_schema() -> Schema { Schema { diff --git a/src/stream/src/executor/top_n/top_n_appendonly.rs b/src/stream/src/executor/top_n/top_n_appendonly.rs index c99b911b951ef..1b45c82c9c83a 100644 --- a/src/stream/src/executor/top_n/top_n_appendonly.rs +++ b/src/stream/src/executor/top_n/top_n_appendonly.rs @@ -19,7 +19,7 @@ use risingwave_common::util::sort_util::ColumnOrder; use super::top_n_cache::AppendOnlyTopNCacheTrait; use super::utils::*; -use super::{ManagedTopNState, TopNCache, NO_GROUP_KEY}; +use super::{ManagedTopNState, TopNCache}; use crate::executor::prelude::*; /// If the input is append-only, `AppendOnlyGroupTopNExecutor` does not need diff --git a/src/stream/src/executor/top_n/top_n_plain.rs b/src/stream/src/executor/top_n/top_n_plain.rs index 30aee860a8fc8..63b9ca94961f8 100644 --- a/src/stream/src/executor/top_n/top_n_plain.rs +++ b/src/stream/src/executor/top_n/top_n_plain.rs @@ -185,7 +185,6 @@ where #[cfg(test)] mod tests { use assert_matches::assert_matches; - use futures::StreamExt; use risingwave_common::array::stream_chunk::StreamChunkTestExt; use risingwave_common::catalog::{Field, Schema}; use risingwave_common::types::DataType; @@ -201,7 +200,7 @@ mod tests { use risingwave_common::util::epoch::test_epoch; use super::*; - use crate::executor::{ActorContext, Execute}; + fn create_stream_chunks() -> Vec { let chunk1 = StreamChunk::from_pretty( " I I @@ -685,8 +684,6 @@ mod tests { use super::*; use crate::executor::test_utils::top_n_executor::create_in_memory_state_table_from_state_store; - use crate::executor::{ActorContext, Execute}; - fn create_source_new() -> Executor { let mut chunks = vec![ StreamChunk::from_pretty( @@ -1002,7 +999,6 @@ mod tests { use super::*; use crate::executor::test_utils::top_n_executor::create_in_memory_state_table_from_state_store; - use crate::executor::{ActorContext, Execute}; fn create_source() -> Executor { let mut chunks = vec![ diff --git a/src/stream/src/executor/troublemaker.rs b/src/stream/src/executor/troublemaker.rs index 7b93392fef419..95b7ad70f7a1f 100644 --- a/src/stream/src/executor/troublemaker.rs +++ b/src/stream/src/executor/troublemaker.rs @@ -13,7 +13,6 @@ // limitations under the License. use rand::Rng; -use risingwave_common::array::stream_chunk_builder::StreamChunkBuilder; use risingwave_common::array::stream_record::{Record, RecordType}; use risingwave_common::array::Op; use risingwave_common::field_generator::{FieldGeneratorImpl, VarcharProperty}; diff --git a/src/stream/src/executor/union.rs b/src/stream/src/executor/union.rs index ac8f3581dda18..f72f5170ce868 100644 --- a/src/stream/src/executor/union.rs +++ b/src/stream/src/executor/union.rs @@ -145,12 +145,9 @@ async fn merge(inputs: Vec) { mod tests { use async_stream::try_stream; use risingwave_common::array::stream_chunk::StreamChunkTestExt; - use risingwave_common::array::StreamChunk; - use risingwave_common::types::{DataType, ScalarImpl}; use risingwave_common::util::epoch::test_epoch; use super::*; - use crate::executor::Watermark; #[tokio::test] async fn union() { diff --git a/src/stream/src/executor/watermark_filter.rs b/src/stream/src/executor/watermark_filter.rs index 813ccbef28920..2aca1251dd058 100644 --- a/src/stream/src/executor/watermark_filter.rs +++ b/src/stream/src/executor/watermark_filter.rs @@ -17,7 +17,7 @@ use std::ops::Deref; use futures::future::{try_join, try_join_all}; use risingwave_common::hash::VnodeBitmapExt; -use risingwave_common::types::{DefaultOrd, ScalarImpl}; +use risingwave_common::types::DefaultOrd; use risingwave_common::{bail, row}; use risingwave_expr::expr::{ build_func_non_strict, ExpressionBoxExt, InputRefExpression, LiteralExpression, @@ -349,8 +349,7 @@ impl WatermarkFilterExecutor { #[cfg(test)] mod tests { use itertools::Itertools; - use risingwave_common::array::StreamChunk; - use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, Schema, TableDesc}; + use risingwave_common::catalog::{ColumnDesc, ColumnId, Field, TableDesc}; use risingwave_common::test_prelude::StreamChunkTestExt; use risingwave_common::types::Date; use risingwave_common::util::epoch::test_epoch; @@ -359,12 +358,10 @@ mod tests { use risingwave_pb::common::ColumnOrder; use risingwave_pb::plan_common::PbColumnCatalog; use risingwave_storage::memory::MemoryStateStore; - use risingwave_storage::table::TableDistribution; use super::*; use crate::executor::test_utils::expr::build_from_pretty; use crate::executor::test_utils::{MessageSender, MockSource}; - use crate::executor::{ActorContext, ExecutorInfo}; const WATERMARK_TYPE: DataType = DataType::Timestamp; diff --git a/src/stream/src/executor/wrapper/epoch_check.rs b/src/stream/src/executor/wrapper/epoch_check.rs index 0110d8a0d86e6..335c524700f01 100644 --- a/src/stream/src/executor/wrapper/epoch_check.rs +++ b/src/stream/src/executor/wrapper/epoch_check.rs @@ -75,7 +75,7 @@ pub async fn epoch_check(info: Arc, input: impl MessageStream) { #[cfg(test)] mod tests { use assert_matches::assert_matches; - use futures::{pin_mut, StreamExt}; + use futures::pin_mut; use risingwave_common::array::StreamChunk; use risingwave_common::util::epoch::test_epoch; diff --git a/src/stream/src/from_proto/batch_query.rs b/src/stream/src/from_proto/batch_query.rs index 5e86d7e6d5b30..a1659008724e7 100644 --- a/src/stream/src/from_proto/batch_query.rs +++ b/src/stream/src/from_proto/batch_query.rs @@ -16,7 +16,6 @@ use risingwave_common::catalog::ColumnId; use risingwave_pb::plan_common::StorageTableDesc; use risingwave_pb::stream_plan::BatchPlanNode; use risingwave_storage::table::batch_table::storage_table::StorageTable; -use risingwave_storage::StateStore; use super::*; use crate::executor::{BatchQueryExecutor, DummyExecutor}; diff --git a/src/stream/src/from_proto/group_top_n.rs b/src/stream/src/from_proto/group_top_n.rs index 51512cb9d94d3..fe5dfc1042cc8 100644 --- a/src/stream/src/from_proto/group_top_n.rs +++ b/src/stream/src/from_proto/group_top_n.rs @@ -22,7 +22,7 @@ use risingwave_pb::stream_plan::GroupTopNNode; use super::*; use crate::common::table::state_table::StateTable; -use crate::executor::{ActorContextRef, AppendOnlyGroupTopNExecutor, Executor, GroupTopNExecutor}; +use crate::executor::{ActorContextRef, AppendOnlyGroupTopNExecutor, GroupTopNExecutor}; use crate::task::AtomicU64Ref; pub struct GroupTopNExecutorBuilder; diff --git a/src/stream/src/from_proto/hash_join.rs b/src/stream/src/from_proto/hash_join.rs index ad69fd1ecd5ac..28a6aa72d4a6f 100644 --- a/src/stream/src/from_proto/hash_join.rs +++ b/src/stream/src/from_proto/hash_join.rs @@ -27,7 +27,7 @@ use super::*; use crate::common::table::state_table::StateTable; use crate::executor::hash_join::*; use crate::executor::monitor::StreamingMetrics; -use crate::executor::{ActorContextRef, Executor, JoinType}; +use crate::executor::{ActorContextRef, JoinType}; use crate::task::AtomicU64Ref; pub struct HashJoinExecutorBuilder; diff --git a/src/stream/src/from_proto/sink.rs b/src/stream/src/from_proto/sink.rs index 541dce562ba04..5e77be7beb7a0 100644 --- a/src/stream/src/from_proto/sink.rs +++ b/src/stream/src/from_proto/sink.rs @@ -180,7 +180,6 @@ impl ExecutorBuilder for SinkExecutorBuilder { ); let sink_write_param = SinkWriterParam { - connector_params: params.env.connector_params(), executor_id: params.executor_id, vnode_bitmap: params.vnode_bitmap.clone(), meta_client: params.env.meta_client().map(SinkMetaClient::MetaClient), diff --git a/src/stream/src/from_proto/source/fs_fetch.rs b/src/stream/src/from_proto/source/fs_fetch.rs index 6dd2be7263c29..1951365a47eed 100644 --- a/src/stream/src/from_proto/source/fs_fetch.rs +++ b/src/stream/src/from_proto/source/fs_fetch.rs @@ -53,7 +53,6 @@ impl ExecutorBuilder for FsFetchExecutorBuilder { source.row_id_index.map(|x| x as _), source.with_properties.clone(), source_info.clone(), - params.env.connector_params(), params.env.config().developer.connector_message_buffer_size, params.info.pk_indices.clone(), ); diff --git a/src/stream/src/from_proto/source/trad_source.rs b/src/stream/src/from_proto/source/trad_source.rs index 0013792d51326..8c00fb0a50830 100644 --- a/src/stream/src/from_proto/source/trad_source.rs +++ b/src/stream/src/from_proto/source/trad_source.rs @@ -118,7 +118,6 @@ pub fn create_source_desc_builder( row_id_index.map(|x| x as _), with_properties, source_info, - params.env.connector_params(), params.env.config().developer.connector_message_buffer_size, // `pk_indices` is used to ensure that a message will be skipped instead of parsed // with null pk when the pk column is missing. diff --git a/src/stream/src/from_proto/stream_cdc_scan.rs b/src/stream/src/from_proto/stream_cdc_scan.rs index a55afa6c2da78..43ddffda00607 100644 --- a/src/stream/src/from_proto/stream_cdc_scan.rs +++ b/src/stream/src/from_proto/stream_cdc_scan.rs @@ -23,7 +23,7 @@ use risingwave_pb::stream_plan::StreamCdcScanNode; use super::*; use crate::common::table::state_table::StateTable; -use crate::executor::{CdcBackfillExecutor, CdcScanOptions, Executor, ExternalStorageTable}; +use crate::executor::{CdcBackfillExecutor, CdcScanOptions, ExternalStorageTable}; pub struct StreamCdcScanExecutorBuilder; diff --git a/src/stream/src/lib.rs b/src/stream/src/lib.rs index ed516244e994c..a6215d6e47aa1 100644 --- a/src/stream/src/lib.rs +++ b/src/stream/src/lib.rs @@ -32,7 +32,6 @@ #![feature(lazy_cell)] #![feature(error_generic_member_access)] #![feature(btree_extract_if)] -#![feature(bound_map)] #![feature(iter_order_by)] #![feature(exact_size_is_empty)] #![feature(impl_trait_in_assoc_type)] diff --git a/src/stream/src/task/barrier_manager.rs b/src/stream/src/task/barrier_manager.rs index 8e9778d82bb29..c5d564d9c6ab8 100644 --- a/src/stream/src/task/barrier_manager.rs +++ b/src/stream/src/task/barrier_manager.rs @@ -888,12 +888,28 @@ impl LocalBarrierManager { pub fn try_find_root_actor_failure<'a>( actor_errors: impl IntoIterator, ) -> Option { + // Explicitly list all error kinds here to notice developers to update this function when + // there are changes in error kinds. + fn stream_executor_error_score(e: &StreamExecutorError) -> i32 { use crate::executor::error::ErrorKind; match e.inner() { - ErrorKind::ChannelClosed(_) | ErrorKind::ExchangeChannelClosed(_) => 0, - ErrorKind::Internal(_) => 1, - _ => 999, + // `ChannelClosed` or `ExchangeChannelClosed` is likely to be caused by actor exit + // and not the root cause. + ErrorKind::ChannelClosed(_) | ErrorKind::ExchangeChannelClosed(_) => 1, + + // Normal errors. + ErrorKind::Uncategorized(_) + | ErrorKind::Storage(_) + | ErrorKind::ArrayError(_) + | ErrorKind::ExprError(_) + | ErrorKind::SerdeError(_) + | ErrorKind::SinkError(_) + | ErrorKind::RpcError(_) + | ErrorKind::AlignBarrier(_, _) + | ErrorKind::ConnectorError(_) + | ErrorKind::DmlError(_) + | ErrorKind::NotImplemented(_) => 999, } } @@ -903,9 +919,18 @@ pub fn try_find_root_actor_failure<'a>( // `UnexpectedExit` wraps the original error. Score on the inner error. ErrorKind::UnexpectedExit { source, .. } => stream_error_score(source), - ErrorKind::Internal(_) => 1000, + // `BarrierSend` is likely to be caused by actor exit and not the root cause. + ErrorKind::BarrierSend { .. } => 1, + + // Executor errors first. ErrorKind::Executor(ee) => 2000 + stream_executor_error_score(ee), - _ => 3000, + + // Then other errors. + ErrorKind::Uncategorized(_) + | ErrorKind::Storage(_) + | ErrorKind::Expression(_) + | ErrorKind::Array(_) + | ErrorKind::Sink(_) => 1000, } } diff --git a/src/stream/src/task/barrier_manager/tests.rs b/src/stream/src/task/barrier_manager/tests.rs index 99efb00269b8a..5fbedcdfd0dcc 100644 --- a/src/stream/src/task/barrier_manager/tests.rs +++ b/src/stream/src/task/barrier_manager/tests.rs @@ -20,10 +20,8 @@ use std::task::Poll; use assert_matches::assert_matches; use futures::future::join_all; use futures::FutureExt; -use itertools::Itertools; use risingwave_common::util::epoch::test_epoch; use risingwave_pb::stream_service::{streaming_control_stream_request, InjectBarrierRequest}; -use tokio::sync::mpsc::unbounded_channel; use tokio_stream::wrappers::UnboundedReceiverStream; use super::*; diff --git a/src/stream/src/task/env.rs b/src/stream/src/task/env.rs index 9a0b26f25f0c5..a47eb8279224c 100644 --- a/src/stream/src/task/env.rs +++ b/src/stream/src/task/env.rs @@ -19,7 +19,6 @@ use risingwave_common::config::StreamingConfig; use risingwave_common::system_param::local_manager::LocalSystemParamsManagerRef; use risingwave_common::util::addr::HostAddr; use risingwave_connector::source::monitor::SourceMetrics; -use risingwave_connector::ConnectorParams; use risingwave_dml::dml_manager::DmlManagerRef; use risingwave_rpc_client::MetaClient; use risingwave_storage::StateStoreImpl; @@ -33,9 +32,6 @@ pub struct StreamEnvironment { /// Endpoint the stream manager listens on. server_addr: HostAddr, - /// Parameters used by connector nodes. - connector_params: ConnectorParams, - /// Streaming related configurations. config: Arc, @@ -65,7 +61,6 @@ impl StreamEnvironment { #[allow(clippy::too_many_arguments)] pub fn new( server_addr: HostAddr, - connector_params: ConnectorParams, config: Arc, worker_id: WorkerNodeId, state_store: StateStoreImpl, @@ -76,7 +71,6 @@ impl StreamEnvironment { ) -> Self { StreamEnvironment { server_addr, - connector_params, config, worker_id, state_store, @@ -93,11 +87,9 @@ impl StreamEnvironment { pub fn for_test() -> Self { use risingwave_common::system_param::local_manager::LocalSystemParamsManager; use risingwave_dml::dml_manager::DmlManager; - use risingwave_pb::connector_service::SinkPayloadFormat; use risingwave_storage::monitor::MonitoredStorageMetrics; StreamEnvironment { server_addr: "127.0.0.1:5688".parse().unwrap(), - connector_params: ConnectorParams::new(SinkPayloadFormat::Json), config: Arc::new(StreamingConfig::default()), worker_id: WorkerNodeId::default(), state_store: StateStoreImpl::shared_in_memory_store(Arc::new( @@ -127,10 +119,6 @@ impl StreamEnvironment { self.state_store.clone() } - pub fn connector_params(&self) -> ConnectorParams { - self.connector_params.clone() - } - pub fn dml_manager_ref(&self) -> DmlManagerRef { self.dml_manager.clone() } diff --git a/src/tests/compaction_test/src/compaction_test_runner.rs b/src/tests/compaction_test/src/compaction_test_runner.rs index 48e8311816d0a..0ca1c76696f39 100644 --- a/src/tests/compaction_test/src/compaction_test_runner.rs +++ b/src/tests/compaction_test/src/compaction_test_runner.rs @@ -23,7 +23,7 @@ use std::time::Duration; use anyhow::anyhow; use bytes::{BufMut, Bytes, BytesMut}; use clap::Parser; -use foyer::memory::CacheContext; +use foyer::CacheContext; use risingwave_common::catalog::TableId; use risingwave_common::config::{ extract_storage_memory_config, load_config, MetaConfig, NoOverride, diff --git a/src/tests/compaction_test/src/delete_range_runner.rs b/src/tests/compaction_test/src/delete_range_runner.rs index 71ee1baa8896b..58e9fde74c48e 100644 --- a/src/tests/compaction_test/src/delete_range_runner.rs +++ b/src/tests/compaction_test/src/delete_range_runner.rs @@ -15,18 +15,16 @@ use std::future::Future; use std::ops::{Bound, RangeBounds}; use std::pin::{pin, Pin}; -use std::sync::atomic::AtomicU32; use std::sync::Arc; use std::time::{Duration, SystemTime}; use bytes::Bytes; -use foyer::memory::CacheContext; +use foyer::{CacheContext, HybridCacheBuilder}; use rand::rngs::StdRng; use rand::{RngCore, SeedableRng}; use risingwave_common::catalog::TableId; use risingwave_common::config::{ - extract_storage_memory_config, load_config, EvictionConfig, NoOverride, ObjectStoreConfig, - RwConfig, + extract_storage_memory_config, load_config, NoOverride, ObjectStoreConfig, RwConfig, }; use risingwave_common::system_param::reader::SystemParamsRead; use risingwave_common::util::epoch::{test_epoch, EpochExt}; @@ -53,7 +51,7 @@ use risingwave_storage::hummock::compactor::{ use risingwave_storage::hummock::sstable_store::SstableStoreRef; use risingwave_storage::hummock::utils::cmp_delete_range_left_bounds; use risingwave_storage::hummock::{ - CachePolicy, FileCache, HummockStorage, MemoryLimiter, SstableObjectIdManager, SstableStore, + CachePolicy, HummockStorage, MemoryLimiter, SstableObjectIdManager, SstableStore, SstableStoreConfig, }; use risingwave_storage::monitor::{CompactorMetrics, HummockStateStoreMetrics}; @@ -210,21 +208,27 @@ async fn compaction_test( Arc::new(ObjectStoreConfig::default()), ) .await; + let meta_cache_v2 = HybridCacheBuilder::new() + .memory(storage_memory_config.meta_cache_capacity_mb * (1 << 20)) + .with_shards(storage_memory_config.meta_cache_shard_num) + .storage() + .build() + .await?; + let block_cache_v2 = HybridCacheBuilder::new() + .memory(storage_memory_config.block_cache_capacity_mb * (1 << 20)) + .with_shards(storage_memory_config.block_cache_shard_num) + .storage() + .build() + .await?; let sstable_store = Arc::new(SstableStore::new(SstableStoreConfig { store: Arc::new(remote_object_store), path: system_params.data_directory().to_string(), - block_cache_capacity: storage_memory_config.block_cache_capacity_mb * (1 << 20), - meta_cache_capacity: storage_memory_config.meta_cache_capacity_mb * (1 << 20), - block_cache_shard_num: storage_memory_config.block_cache_shard_num, - meta_cache_shard_num: storage_memory_config.meta_cache_shard_num, - block_cache_eviction: EvictionConfig::for_test(), - meta_cache_eviction: EvictionConfig::for_test(), prefetch_buffer_capacity: storage_memory_config.prefetch_buffer_capacity_mb * (1 << 20), max_prefetch_block_number: storage_opts.max_prefetch_block_number, - data_file_cache: FileCache::none(), - meta_file_cache: FileCache::none(), recent_filter: None, state_store_metrics: state_store_metrics.clone(), + meta_cache_v2, + block_cache_v2, })); let store = HummockStorage::new( @@ -600,24 +604,15 @@ fn run_compactor_thread( ) { let filter_key_extractor_manager = FilterKeyExtractorManager::RpcFilterKeyExtractorManager(filter_key_extractor_manager); - - let compaction_executor = Arc::new(CompactionExecutor::new(Some(1))); - let max_task_parallelism = Arc::new(AtomicU32::new( - (compaction_executor.worker_num() as f32 * storage_opts.compactor_max_task_multiplier) - .ceil() as u32, - )); let compactor_context = CompactorContext { storage_opts, sstable_store, compactor_metrics, is_share_buffer_compact: false, compaction_executor: Arc::new(CompactionExecutor::new(None)), - memory_limiter: MemoryLimiter::unlimit(), task_progress_manager: Default::default(), await_tree_reg: None, - running_task_parallelism: Arc::new(AtomicU32::new(0)), - max_task_parallelism, }; start_compactor( diff --git a/src/tests/compaction_test/src/lib.rs b/src/tests/compaction_test/src/lib.rs index 1e2207fc99a9b..e5fd10b10b176 100644 --- a/src/tests/compaction_test/src/lib.rs +++ b/src/tests/compaction_test/src/lib.rs @@ -22,7 +22,6 @@ #![warn(clippy::map_flatten)] #![warn(clippy::await_holding_lock)] #![deny(rustdoc::broken_intra_doc_links)] -#![feature(bound_map)] #![feature(register_tool)] #![register_tool(rw)] #![allow(rw::format_error)] // test code @@ -45,7 +44,7 @@ pub struct CompactionTestOpts { #[clap(long)] pub client_address: Option, - /// The state store string e.g. hummock+s3://test-bucket + /// The state store string e.g. `hummock+s3://test-bucket` #[clap(short, long)] pub state_store: String, diff --git a/src/tests/simulation/Cargo.toml b/src/tests/simulation/Cargo.toml index fc55e6356f4c5..31736ea848423 100644 --- a/src/tests/simulation/Cargo.toml +++ b/src/tests/simulation/Cargo.toml @@ -24,7 +24,7 @@ futures = { version = "0.3", default-features = false, features = ["alloc"] } glob = "0.3" itertools = { workspace = true } lru = { workspace = true } -madsim = "0.2.22" +madsim = "0.2.27" paste = "1" pin-project = "1.1" pretty_assertions = "1" diff --git a/src/tests/simulation/tests/integration_tests/batch/mod.rs b/src/tests/simulation/tests/integration_tests/batch/mod.rs index ef530efa9f46a..25c690cfdcbf8 100644 --- a/src/tests/simulation/tests/integration_tests/batch/mod.rs +++ b/src/tests/simulation/tests/integration_tests/batch/mod.rs @@ -54,7 +54,7 @@ fn cluster_config_no_compute_nodes() -> Configuration { file.write_all( "\ [meta] -max_heartbeat_interval_secs = 60 +max_heartbeat_interval_secs = 300 [system] barrier_interval_ms = 1000 @@ -63,6 +63,9 @@ checkpoint_frequency = 1 [server] telemetry_enabled = false metrics_level = \"Disabled\" + +[batch] +mask_worker_temporary_secs = 30 " .as_bytes(), ) @@ -163,7 +166,7 @@ async fn test_serving_cluster_availability() { tokio::time::sleep(Duration::from_secs(15)).await; query_and_assert(session.clone()).await; // wait for mask expire - tokio::time::sleep(Duration::from_secs(30)).await; + tokio::time::sleep(Duration::from_secs(60)).await; session.run(select).await.unwrap_err(); // wait for previous nodes expire tokio::time::sleep(Duration::from_secs(300)).await; diff --git a/src/tests/simulation/tests/integration_tests/scale/plan.rs b/src/tests/simulation/tests/integration_tests/scale/plan.rs index e1d8148632c62..d39e159fc61d9 100644 --- a/src/tests/simulation/tests/integration_tests/scale/plan.rs +++ b/src/tests/simulation/tests/integration_tests/scale/plan.rs @@ -13,7 +13,6 @@ // limitations under the License. use std::collections::HashMap; -use std::default::Default; use anyhow::Result; use itertools::Itertools; diff --git a/src/utils/delta_btree_map/src/lib.rs b/src/utils/delta_btree_map/src/lib.rs index c300955af567b..1f7e1a77190a0 100644 --- a/src/utils/delta_btree_map/src/lib.rs +++ b/src/utils/delta_btree_map/src/lib.rs @@ -76,16 +76,18 @@ impl<'a, K: Ord, V> DeltaBTreeMap<'a, K, V> { pub fn find(&self, key: &K) -> Option> { let ss_cursor = self.snapshot.lower_bound(Bound::Included(key)); let dt_cursor = self.delta.lower_bound(Bound::Included(key)); - let curr_key_value = if dt_cursor.key() == Some(key) { - match dt_cursor.key_value().unwrap() { + let ss_cursor_kv = ss_cursor.peek_next(); + let dt_cursor_kv = dt_cursor.peek_next(); + let curr_key_value = if dt_cursor_kv.map(|(k, _)| k) == Some(key) { + match dt_cursor_kv.unwrap() { (key, Change::Insert(value)) => (key, value), (_key, Change::Delete) => { // the key is deleted return None; } } - } else if ss_cursor.key() == Some(key) { - ss_cursor.key_value().unwrap() + } else if ss_cursor_kv.map(|(k, _)| k) == Some(key) { + ss_cursor_kv.unwrap() } else { // the key doesn't exist return None; @@ -102,16 +104,8 @@ impl<'a, K: Ord, V> DeltaBTreeMap<'a, K, V> { // the implementation is very similar to `CursorWithDelta::peek_next` let mut ss_cursor = self.snapshot.lower_bound(bound); let mut dt_cursor = self.delta.lower_bound(bound); - let next_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_next(); - tmp - }; - let next_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_next(); - tmp - }; + let next_ss_entry = || ss_cursor.next(); + let next_dt_entry = || dt_cursor.next(); let curr_key_value = CursorWithDelta::peek_impl(PeekDirection::Next, next_ss_entry, next_dt_entry); CursorWithDelta { @@ -126,16 +120,8 @@ impl<'a, K: Ord, V> DeltaBTreeMap<'a, K, V> { // the implementation is very similar to `CursorWithDelta::peek_prev` let mut ss_cursor = self.snapshot.upper_bound(bound); let mut dt_cursor = self.delta.upper_bound(bound); - let prev_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_prev(); - tmp - }; - let prev_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_prev(); - tmp - }; + let prev_ss_entry = || ss_cursor.prev(); + let prev_dt_entry = || dt_cursor.prev(); let curr_key_value = CursorWithDelta::peek_impl(PeekDirection::Prev, prev_ss_entry, prev_dt_entry); CursorWithDelta { @@ -244,22 +230,14 @@ impl<'a, K: Ord, V> CursorWithDelta<'a, K, V> { let mut ss_cursor = self.snapshot.lower_bound(Bound::Included(key)); let mut dt_cursor = self.delta.lower_bound(Bound::Included(key)); // either one of `ss_cursor.key()` and `dt_cursor.key()` == `Some(key)`, or both are - if ss_cursor.key() == Some(key) { - ss_cursor.move_next(); + if ss_cursor.peek_next().map(|(k, _)| k) == Some(key) { + ss_cursor.next(); } - if dt_cursor.key() == Some(key) { - dt_cursor.move_next(); + if dt_cursor.peek_next().map(|(k, _)| k) == Some(key) { + dt_cursor.next(); } - let next_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_next(); - tmp - }; - let next_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_next(); - tmp - }; + let next_ss_entry = || ss_cursor.next(); + let next_dt_entry = || dt_cursor.next(); Self::peek_impl(PeekDirection::Next, next_ss_entry, next_dt_entry) } else { // we are at the ghost position, now let's go back to the beginning @@ -275,22 +253,14 @@ impl<'a, K: Ord, V> CursorWithDelta<'a, K, V> { let mut ss_cursor = self.snapshot.upper_bound(Bound::Included(key)); let mut dt_cursor = self.delta.upper_bound(Bound::Included(key)); // either one of `ss_cursor.key()` and `dt_cursor.key()` == `Some(key)`, or both are - if ss_cursor.key() == Some(key) { - ss_cursor.move_prev(); + if ss_cursor.peek_prev().map(|(k, _)| k) == Some(key) { + ss_cursor.prev(); } - if dt_cursor.key() == Some(key) { - dt_cursor.move_prev(); + if dt_cursor.peek_prev().map(|(k, _)| k) == Some(key) { + dt_cursor.prev(); } - let next_ss_entry = || { - let tmp = ss_cursor.key_value(); - ss_cursor.move_prev(); - tmp - }; - let next_dt_entry = || { - let tmp = dt_cursor.key_value(); - dt_cursor.move_prev(); - tmp - }; + let next_ss_entry = || ss_cursor.prev(); + let next_dt_entry = || dt_cursor.prev(); Self::peek_impl(PeekDirection::Prev, next_ss_entry, next_dt_entry) } else { // we are at the ghost position, now let's go back to the end @@ -317,8 +287,6 @@ impl<'a, K: Ord, V> CursorWithDelta<'a, K, V> { #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use super::*; #[test] diff --git a/src/utils/pgwire/src/pg_message.rs b/src/utils/pgwire/src/pg_message.rs index 4898faf89b5d9..00c9a09b2eda6 100644 --- a/src/utils/pgwire/src/pg_message.rs +++ b/src/utils/pgwire/src/pg_message.rs @@ -697,7 +697,7 @@ macro_rules! from_usize { impl FromUsize for $t { #[inline] fn from_usize(x: usize) -> Result<$t> { - if x > <$t>::max_value() as usize { + if x > <$t>::MAX as usize { Err(Error::new(ErrorKind::InvalidInput, "value too large to transmit").into()) } else { Ok(x as $t) diff --git a/src/utils/pgwire/src/pg_protocol.rs b/src/utils/pgwire/src/pg_protocol.rs index 38eba6e92d5b6..5e9e7a056f261 100644 --- a/src/utils/pgwire/src/pg_protocol.rs +++ b/src/utils/pgwire/src/pg_protocol.rs @@ -767,7 +767,7 @@ where self.unnamed_portal.replace(portal); } else { assert!( - self.result_cache.get(&portal_name).is_none(), + !self.result_cache.contains_key(&portal_name), "Named portal never can be overridden." ); self.portal_store.insert(portal_name.clone(), portal); diff --git a/src/utils/pgwire/src/pg_server.rs b/src/utils/pgwire/src/pg_server.rs index 0538217de9acd..7305e35445aeb 100644 --- a/src/utils/pgwire/src/pg_server.rs +++ b/src/utils/pgwire/src/pg_server.rs @@ -15,7 +15,6 @@ use std::collections::HashMap; use std::future::Future; use std::io; -use std::result::Result; use std::str::FromStr; use std::sync::Arc; use std::time::Instant; diff --git a/standalone/grafana.ini b/standalone/grafana.ini new file mode 100644 index 0000000000000..c05dee52e831a --- /dev/null +++ b/standalone/grafana.ini @@ -0,0 +1,10 @@ +[server] +http_addr = 0.0.0.0 +http_port = 3001 + +[users] +default_theme = light + +[auth.anonymous] +enabled = true +org_role = Admin \ No newline at end of file diff --git a/standalone/prometheus.yml b/standalone/prometheus.yml new file mode 100644 index 0000000000000..b354503515509 --- /dev/null +++ b/standalone/prometheus.yml @@ -0,0 +1,40 @@ +global: + scrape_interval: 15s + evaluation_interval: 60s + external_labels: + rw_cluster: 20240506-185437 + + +scrape_configs: + - job_name: prometheus + static_configs: + - targets: ["127.0.0.1:9500"] + + - job_name: compute + static_configs: + - targets: ["127.0.0.1:1222"] + + - job_name: meta + static_configs: + - targets: ["127.0.0.1:1250"] + + - job_name: minio + metrics_path: /minio/v2/metrics/cluster + static_configs: + - targets: ["127.0.0.1:9301"] + + - job_name: compactor + static_configs: + - targets: ["127.0.0.1:1260"] + + - job_name: etcd + static_configs: + - targets: ["127.0.0.1:2379"] + + - job_name: frontend + static_configs: + - targets: ["127.0.0.1:2222"] + + - job_name: redpanda + static_configs: + - targets: []