Compare commits

..

3 Commits

Author SHA1 Message Date
discord9
2f8e8be042 test: build a failing example with LogicalPlanBuilder
Signed-off-by: discord9 <discord9@163.com>
2025-08-12 15:19:47 +08:00
discord9
9d30459a58 test: reproduce the panic, still no clue why
Signed-off-by: discord9 <discord9@163.com>
2025-08-11 19:37:48 +08:00
discord9
f1650a78f7 fix?: optimize projection after join
Signed-off-by: discord9 <discord9@163.com>
2025-08-07 19:55:32 +08:00
678 changed files with 17951 additions and 41226 deletions

View File

@@ -12,7 +12,7 @@ runs:
steps: steps:
- name: Install Etcd cluster - name: Install Etcd cluster
shell: bash shell: bash
run: | run: |
helm upgrade \ helm upgrade \
--install etcd oci://registry-1.docker.io/bitnamicharts/etcd \ --install etcd oci://registry-1.docker.io/bitnamicharts/etcd \
--set replicaCount=${{ inputs.etcd-replicas }} \ --set replicaCount=${{ inputs.etcd-replicas }} \
@@ -24,9 +24,4 @@ runs:
--set auth.rbac.token.enabled=false \ --set auth.rbac.token.enabled=false \
--set persistence.size=2Gi \ --set persistence.size=2Gi \
--create-namespace \ --create-namespace \
--set global.security.allowInsecureImages=true \
--set image.registry=docker.io \
--set image.repository=greptime/etcd \
--set image.tag=3.6.1-debian-12-r3 \
--version 12.0.8 \
-n ${{ inputs.namespace }} -n ${{ inputs.namespace }}

View File

@@ -1,8 +1,3 @@
logging:
level: "info"
format: "json"
filters:
- log_store=debug
meta: meta:
configData: |- configData: |-
[runtime] [runtime]

View File

@@ -12,7 +12,7 @@ runs:
steps: steps:
- name: Install Kafka cluster - name: Install Kafka cluster
shell: bash shell: bash
run: | run: |
helm upgrade \ helm upgrade \
--install kafka oci://registry-1.docker.io/bitnamicharts/kafka \ --install kafka oci://registry-1.docker.io/bitnamicharts/kafka \
--set controller.replicaCount=${{ inputs.controller-replicas }} \ --set controller.replicaCount=${{ inputs.controller-replicas }} \
@@ -23,8 +23,4 @@ runs:
--set listeners.controller.protocol=PLAINTEXT \ --set listeners.controller.protocol=PLAINTEXT \
--set listeners.client.protocol=PLAINTEXT \ --set listeners.client.protocol=PLAINTEXT \
--create-namespace \ --create-namespace \
--set image.registry=docker.io \
--set image.repository=greptime/kafka \
--set image.tag=3.9.0-debian-12-r1 \
--version 31.0.0 \
-n ${{ inputs.namespace }} -n ${{ inputs.namespace }}

View File

@@ -6,7 +6,9 @@ inputs:
description: "Number of PostgreSQL replicas" description: "Number of PostgreSQL replicas"
namespace: namespace:
default: "postgres-namespace" default: "postgres-namespace"
description: "The PostgreSQL namespace" postgres-version:
default: "14.2"
description: "PostgreSQL version"
storage-size: storage-size:
default: "1Gi" default: "1Gi"
description: "Storage size for PostgreSQL" description: "Storage size for PostgreSQL"
@@ -20,11 +22,7 @@ runs:
helm upgrade \ helm upgrade \
--install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql \ --install postgresql oci://registry-1.docker.io/bitnamicharts/postgresql \
--set replicaCount=${{ inputs.postgres-replicas }} \ --set replicaCount=${{ inputs.postgres-replicas }} \
--set global.security.allowInsecureImages=true \ --set image.tag=${{ inputs.postgres-version }} \
--set image.registry=docker.io \
--set image.repository=greptime/postgresql \
--set image.tag=17.5.0-debian-12-r3 \
--version 16.7.4 \
--set persistence.size=${{ inputs.storage-size }} \ --set persistence.size=${{ inputs.storage-size }} \
--set postgresql.username=greptimedb \ --set postgresql.username=greptimedb \
--set postgresql.password=admin \ --set postgresql.password=admin \

View File

@@ -35,8 +35,8 @@ HIGHER_VERSION=$(printf "%s\n%s" "$CLEAN_CURRENT" "$CLEAN_LATEST" | sort -V | ta
if [ "$HIGHER_VERSION" = "$CLEAN_CURRENT" ]; then if [ "$HIGHER_VERSION" = "$CLEAN_CURRENT" ]; then
echo "Current version ($CLEAN_CURRENT) is NEWER than or EQUAL to latest ($CLEAN_LATEST)" echo "Current version ($CLEAN_CURRENT) is NEWER than or EQUAL to latest ($CLEAN_LATEST)"
echo "is-current-version-latest=true" >> $GITHUB_OUTPUT echo "should-push-latest-tag=true" >> $GITHUB_OUTPUT
else else
echo "Current version ($CLEAN_CURRENT) is OLDER than latest ($CLEAN_LATEST)" echo "Current version ($CLEAN_CURRENT) is OLDER than latest ($CLEAN_LATEST)"
echo "is-current-version-latest=false" >> $GITHUB_OUTPUT echo "should-push-latest-tag=false" >> $GITHUB_OUTPUT
fi fi

View File

@@ -1,34 +0,0 @@
#!/bin/bash
# This script is used to pull the test dependency images that are stored in public ECR one by one to avoid rate limiting.
set -e
MAX_RETRIES=3
IMAGES=(
"greptime/zookeeper:3.7"
"greptime/kafka:3.9.0-debian-12-r1"
"greptime/etcd:3.6.1-debian-12-r3"
"greptime/minio:2024"
"greptime/mysql:5.7"
)
for image in "${IMAGES[@]}"; do
for ((attempt=1; attempt<=MAX_RETRIES; attempt++)); do
if docker pull "$image"; then
# Successfully pulled the image.
break
else
# Use some simple exponential backoff to avoid rate limiting.
if [ $attempt -lt $MAX_RETRIES ]; then
sleep_seconds=$((attempt * 5))
echo "Attempt $attempt failed for $image, waiting $sleep_seconds seconds"
sleep $sleep_seconds # 5s, 10s delays
else
echo "Failed to pull $image after $MAX_RETRIES attempts"
exit 1
fi
fi
done
done

View File

@@ -21,7 +21,7 @@ update_dev_builder_version() {
# Commit the changes. # Commit the changes.
git add Makefile git add Makefile
git commit -s -m "ci: update dev-builder image tag" git commit -m "ci: update dev-builder image tag"
git push origin $BRANCH_NAME git push origin $BRANCH_NAME
# Create a Pull Request. # Create a Pull Request.

View File

@@ -12,7 +12,6 @@ on:
- 'docker/**' - 'docker/**'
- '.gitignore' - '.gitignore'
- 'grafana/**' - 'grafana/**'
- 'Makefile'
workflow_dispatch: workflow_dispatch:
name: CI name: CI
@@ -618,12 +617,10 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
persist-credentials: false persist-credentials: false
- if: matrix.mode.kafka - if: matrix.mode.kafka
name: Setup kafka server name: Setup kafka server
working-directory: tests-integration/fixtures working-directory: tests-integration/fixtures
run: ../../.github/scripts/pull-test-deps-images.sh && docker compose up -d --wait kafka run: docker compose up -d --wait kafka
- name: Download pre-built binaries - name: Download pre-built binaries
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
@@ -685,30 +682,6 @@ jobs:
- name: Run cargo clippy - name: Run cargo clippy
run: make clippy run: make clippy
check-udeps:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' }}
name: Check Unused Dependencies
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
shared-key: "check-udeps"
cache-all-crates: "true"
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install cargo-udeps
run: cargo install cargo-udeps --locked
- name: Check unused dependencies
run: make check-udeps
conflict-check: conflict-check:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' }} if: ${{ github.repository == 'GreptimeTeam/greptimedb' }}
name: Check for conflict name: Check for conflict
@@ -724,7 +697,7 @@ jobs:
if: ${{ github.repository == 'GreptimeTeam/greptimedb' && github.event_name != 'merge_group' }} if: ${{ github.repository == 'GreptimeTeam/greptimedb' && github.event_name != 'merge_group' }}
runs-on: ubuntu-22.04-arm runs-on: ubuntu-22.04-arm
timeout-minutes: 60 timeout-minutes: 60
needs: [conflict-check, clippy, fmt, check-udeps] needs: [conflict-check, clippy, fmt]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -736,7 +709,7 @@ jobs:
- name: Install toolchain - name: Install toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
cache: false cache: false
- name: Rust Cache - name: Rust Cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
@@ -746,11 +719,9 @@ jobs:
save-if: ${{ github.ref == 'refs/heads/main' }} save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install latest nextest release - name: Install latest nextest release
uses: taiki-e/install-action@nextest uses: taiki-e/install-action@nextest
- name: Setup external services - name: Setup external services
working-directory: tests-integration/fixtures working-directory: tests-integration/fixtures
run: ../../.github/scripts/pull-test-deps-images.sh && docker compose up -d --wait run: docker compose up -d --wait
- name: Run nextest cases - name: Run nextest cases
run: cargo nextest run --workspace -F dashboard -F pg_kvbackend -F mysql_kvbackend run: cargo nextest run --workspace -F dashboard -F pg_kvbackend -F mysql_kvbackend
env: env:
@@ -767,11 +738,8 @@ jobs:
GT_MINIO_ACCESS_KEY: superpower_password GT_MINIO_ACCESS_KEY: superpower_password
GT_MINIO_REGION: us-west-2 GT_MINIO_REGION: us-west-2
GT_MINIO_ENDPOINT_URL: http://127.0.0.1:9000 GT_MINIO_ENDPOINT_URL: http://127.0.0.1:9000
GT_ETCD_TLS_ENDPOINTS: https://127.0.0.1:2378
GT_ETCD_ENDPOINTS: http://127.0.0.1:2379 GT_ETCD_ENDPOINTS: http://127.0.0.1:2379
GT_POSTGRES_ENDPOINTS: postgres://greptimedb:admin@127.0.0.1:5432/postgres GT_POSTGRES_ENDPOINTS: postgres://greptimedb:admin@127.0.0.1:5432/postgres
GT_POSTGRES15_ENDPOINTS: postgres://test_user:test_password@127.0.0.1:5433/postgres
GT_POSTGRES15_SCHEMA: test_schema
GT_MYSQL_ENDPOINTS: mysql://greptimedb:admin@127.0.0.1:3306/mysql GT_MYSQL_ENDPOINTS: mysql://greptimedb:admin@127.0.0.1:3306/mysql
GT_KAFKA_ENDPOINTS: 127.0.0.1:9092 GT_KAFKA_ENDPOINTS: 127.0.0.1:9092
GT_KAFKA_SASL_ENDPOINTS: 127.0.0.1:9093 GT_KAFKA_SASL_ENDPOINTS: 127.0.0.1:9093
@@ -804,11 +772,9 @@ jobs:
uses: taiki-e/install-action@nextest uses: taiki-e/install-action@nextest
- name: Install cargo-llvm-cov - name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov
- name: Setup external services - name: Setup external services
working-directory: tests-integration/fixtures working-directory: tests-integration/fixtures
run: ../../.github/scripts/pull-test-deps-images.sh && docker compose up -d --wait run: docker compose up -d --wait
- name: Run nextest cases - name: Run nextest cases
run: cargo llvm-cov nextest --workspace --lcov --output-path lcov.info -F dashboard -F pg_kvbackend -F mysql_kvbackend run: cargo llvm-cov nextest --workspace --lcov --output-path lcov.info -F dashboard -F pg_kvbackend -F mysql_kvbackend
env: env:
@@ -824,11 +790,8 @@ jobs:
GT_MINIO_ACCESS_KEY: superpower_password GT_MINIO_ACCESS_KEY: superpower_password
GT_MINIO_REGION: us-west-2 GT_MINIO_REGION: us-west-2
GT_MINIO_ENDPOINT_URL: http://127.0.0.1:9000 GT_MINIO_ENDPOINT_URL: http://127.0.0.1:9000
GT_ETCD_TLS_ENDPOINTS: https://127.0.0.1:2378
GT_ETCD_ENDPOINTS: http://127.0.0.1:2379 GT_ETCD_ENDPOINTS: http://127.0.0.1:2379
GT_POSTGRES_ENDPOINTS: postgres://greptimedb:admin@127.0.0.1:5432/postgres GT_POSTGRES_ENDPOINTS: postgres://greptimedb:admin@127.0.0.1:5432/postgres
GT_POSTGRES15_ENDPOINTS: postgres://test_user:test_password@127.0.0.1:5433/postgres
GT_POSTGRES15_SCHEMA: test_schema
GT_MYSQL_ENDPOINTS: mysql://greptimedb:admin@127.0.0.1:3306/mysql GT_MYSQL_ENDPOINTS: mysql://greptimedb:admin@127.0.0.1:3306/mysql
GT_KAFKA_ENDPOINTS: 127.0.0.1:9092 GT_KAFKA_ENDPOINTS: 127.0.0.1:9092
GT_KAFKA_SASL_ENDPOINTS: 127.0.0.1:9093 GT_KAFKA_SASL_ENDPOINTS: 127.0.0.1:9093

View File

@@ -10,7 +10,6 @@ on:
- 'docker/**' - 'docker/**'
- '.gitignore' - '.gitignore'
- 'grafana/**' - 'grafana/**'
- 'Makefile'
push: push:
branches: branches:
- main - main
@@ -22,7 +21,6 @@ on:
- 'docker/**' - 'docker/**'
- '.gitignore' - '.gitignore'
- 'grafana/**' - 'grafana/**'
- 'Makefile'
workflow_dispatch: workflow_dispatch:
name: CI name: CI
@@ -67,12 +65,6 @@ jobs:
steps: steps:
- run: 'echo "No action required"' - run: 'echo "No action required"'
check-udeps:
name: Unused Dependencies
runs-on: ubuntu-latest
steps:
- run: 'echo "No action required"'
coverage: coverage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@@ -111,8 +111,7 @@ jobs:
# The 'version' use as the global tag name of the release workflow. # The 'version' use as the global tag name of the release workflow.
version: ${{ steps.create-version.outputs.version }} version: ${{ steps.create-version.outputs.version }}
# The 'is-current-version-latest' determines whether to update 'latest' Docker tags and downstream repositories. should-push-latest-tag: ${{ steps.check-version.outputs.should-push-latest-tag }}
is-current-version-latest: ${{ steps.check-version.outputs.is-current-version-latest }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -322,7 +321,7 @@ jobs:
image-registry-username: ${{ secrets.DOCKERHUB_USERNAME }} image-registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
image-registry-password: ${{ secrets.DOCKERHUB_TOKEN }} image-registry-password: ${{ secrets.DOCKERHUB_TOKEN }}
version: ${{ needs.allocate-runners.outputs.version }} version: ${{ needs.allocate-runners.outputs.version }}
push-latest-tag: ${{ needs.allocate-runners.outputs.is-current-version-latest == 'true' && github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }} push-latest-tag: ${{ needs.allocate-runners.outputs.should-push-latest-tag == 'true' && github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }}
- name: Set build image result - name: Set build image result
id: set-build-image-result id: set-build-image-result
@@ -369,7 +368,7 @@ jobs:
dev-mode: false dev-mode: false
upload-to-s3: true upload-to-s3: true
update-version-info: true update-version-info: true
push-latest-tag: ${{ needs.allocate-runners.outputs.is-current-version-latest == 'true' && github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }} push-latest-tag: ${{ needs.allocate-runners.outputs.should-push-latest-tag == 'true' && github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }}
publish-github-release: publish-github-release:
name: Create GitHub release and upload artifacts name: Create GitHub release and upload artifacts
@@ -477,7 +476,7 @@ jobs:
bump-helm-charts-version: bump-helm-charts-version:
name: Bump helm charts version name: Bump helm charts version
if: ${{ github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' && needs.allocate-runners.outputs.is-current-version-latest == 'true' }} if: ${{ github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }}
needs: [allocate-runners, publish-github-release] needs: [allocate-runners, publish-github-release]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -498,7 +497,7 @@ jobs:
bump-homebrew-greptime-version: bump-homebrew-greptime-version:
name: Bump homebrew greptime version name: Bump homebrew greptime version
if: ${{ github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' && needs.allocate-runners.outputs.is-current-version-latest == 'true' }} if: ${{ github.ref_type == 'tag' && !contains(github.ref_name, 'nightly') && github.event_name != 'schedule' }}
needs: [allocate-runners, publish-github-release] needs: [allocate-runners, publish-github-release]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:

3
.gitignore vendored
View File

@@ -52,9 +52,6 @@ venv/
tests-fuzz/artifacts/ tests-fuzz/artifacts/
tests-fuzz/corpus/ tests-fuzz/corpus/
# cargo-udeps reports
udeps-report.json
# Nix # Nix
.direnv .direnv
.envrc .envrc

View File

@@ -55,18 +55,14 @@ GreptimeDB uses the [Apache 2.0 license](https://github.com/GreptimeTeam/greptim
- To ensure that community is free and confident in its ability to use your contributions, please sign the Contributor License Agreement (CLA) which will be incorporated in the pull request process. - To ensure that community is free and confident in its ability to use your contributions, please sign the Contributor License Agreement (CLA) which will be incorporated in the pull request process.
- Make sure all files have proper license header (running `docker run --rm -v $(pwd):/github/workspace ghcr.io/korandoru/hawkeye-native:v3 format` from the project root). - Make sure all files have proper license header (running `docker run --rm -v $(pwd):/github/workspace ghcr.io/korandoru/hawkeye-native:v3 format` from the project root).
- Make sure all your codes are formatted and follow the [coding style](https://pingcap.github.io/style-guide/rust/) and [style guide](docs/style-guide.md). - Make sure all your codes are formatted and follow the [coding style](https://pingcap.github.io/style-guide/rust/) and [style guide](docs/style-guide.md).
- Make sure all unit tests are passed using [nextest](https://nexte.st/index.html) `cargo nextest run --workspace --features pg_kvbackend,mysql_kvbackend` or `make test`. - Make sure all unit tests are passed using [nextest](https://nexte.st/index.html) `cargo nextest run`.
- Make sure all clippy warnings are fixed (you can check it locally by running `cargo clippy --workspace --all-targets -- -D warnings` or `make clippy`). - Make sure all clippy warnings are fixed (you can check it locally by running `cargo clippy --workspace --all-targets -- -D warnings`).
- Ensure there are no unused dependencies by running `make check-udeps` (clean them up with `make fix-udeps` if reported).
- If you must keep a target-specific dependency (e.g. under `[target.'cfg(...)'.dev-dependencies]`), add a cargo-udeps ignore entry in the same `Cargo.toml`, for example:
`[package.metadata.cargo-udeps.ignore]` with `development = ["rexpect"]` (or `dependencies`/`build` as appropriate).
- When modifying sample configuration files in `config/`, run `make config-docs` (which requires Docker to be installed) to update the configuration documentation and include it in your commit.
#### `pre-commit` Hooks #### `pre-commit` Hooks
You could setup the [`pre-commit`](https://pre-commit.com/#plugins) hooks to run these checks on every commit automatically. You could setup the [`pre-commit`](https://pre-commit.com/#plugins) hooks to run these checks on every commit automatically.
1. Install `pre-commit` 1. Install `pre-commit`
pip install pre-commit pip install pre-commit
@@ -74,7 +70,7 @@ You could setup the [`pre-commit`](https://pre-commit.com/#plugins) hooks to run
brew install pre-commit brew install pre-commit
2. Install the `pre-commit` hooks 2. Install the `pre-commit` hooks
$ pre-commit install $ pre-commit install
pre-commit installed at .git/hooks/pre-commit pre-commit installed at .git/hooks/pre-commit

4860
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -98,12 +98,11 @@ rust.unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tokio_unstable)'] }
# See for more detaiils: https://github.com/rust-lang/cargo/issues/11329 # See for more detaiils: https://github.com/rust-lang/cargo/issues/11329
ahash = { version = "0.8", features = ["compile-time-rng"] } ahash = { version = "0.8", features = ["compile-time-rng"] }
aquamarine = "0.6" aquamarine = "0.6"
arrow = { version = "56.0", features = ["prettyprint"] } arrow = { version = "54.2", features = ["prettyprint"] }
arrow-array = { version = "56.0", default-features = false, features = ["chrono-tz"] } arrow-array = { version = "54.2", default-features = false, features = ["chrono-tz"] }
arrow-buffer = "56.0" arrow-flight = "54.2"
arrow-flight = "56.0" arrow-ipc = { version = "54.2", default-features = false, features = ["lz4", "zstd"] }
arrow-ipc = { version = "56.0", default-features = false, features = ["lz4", "zstd"] } arrow-schema = { version = "54.2", features = ["serde"] }
arrow-schema = { version = "56.0", features = ["serde"] }
async-stream = "0.3" async-stream = "0.3"
async-trait = "0.1" async-trait = "0.1"
# Remember to update axum-extra, axum-macros when updating axum # Remember to update axum-extra, axum-macros when updating axum
@@ -122,30 +121,26 @@ clap = { version = "4.4", features = ["derive"] }
config = "0.13.0" config = "0.13.0"
crossbeam-utils = "0.8" crossbeam-utils = "0.8"
dashmap = "6.1" dashmap = "6.1"
datafusion = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-functions = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-functions = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-functions-aggregate-common = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-functions-aggregate-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-optimizer = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-optimizer = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-orc = { git = "https://github.com/GreptimeTeam/datafusion-orc", rev = "a0a5f902158f153119316eaeec868cff3fc8a99d" } datafusion-physical-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-physical-expr = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-physical-plan = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-physical-plan = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-sql = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-sql = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" } datafusion-substrait = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
datafusion-substrait = { git = "https://github.com/GreptimeTeam/datafusion.git", rev = "7d5214512740b4dfb742b6b3d91ed9affcc2c9d0" }
deadpool = "0.12" deadpool = "0.12"
deadpool-postgres = "0.14" deadpool-postgres = "0.14"
derive_builder = "0.20" derive_builder = "0.20"
dotenv = "0.15" dotenv = "0.15"
either = "1.15" either = "1.15"
etcd-client = { git = "https://github.com/GreptimeTeam/etcd-client", rev = "f62df834f0cffda355eba96691fe1a9a332b75a7", features = [ etcd-client = "0.14"
"tls",
"tls-roots",
] }
fst = "0.4.7" fst = "0.4.7"
futures = "0.3" futures = "0.3"
futures-util = "0.3" futures-util = "0.3"
greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "66eb089afa6baaa3ddfafabd0a4abbe317d012c3" } greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "ccfd4da48bc0254ed865e479cd981a3581b02d84" }
hex = "0.4" hex = "0.4"
http = "1" http = "1"
humantime = "2.1" humantime = "2.1"
@@ -156,7 +151,7 @@ itertools = "0.14"
jsonb = { git = "https://github.com/databendlabs/jsonb.git", rev = "8c8d2fc294a39f3ff08909d60f718639cfba3875", default-features = false } jsonb = { git = "https://github.com/databendlabs/jsonb.git", rev = "8c8d2fc294a39f3ff08909d60f718639cfba3875", default-features = false }
lazy_static = "1.4" lazy_static = "1.4"
local-ip-address = "0.6" local-ip-address = "0.6"
loki-proto = { git = "https://github.com/GreptimeTeam/loki-proto.git", rev = "3b7cd33234358b18ece977bf689dc6fb760f29ab" } loki-proto = { git = "https://github.com/GreptimeTeam/loki-proto.git", rev = "1434ecf23a2654025d86188fb5205e7a74b225d3" }
meter-core = { git = "https://github.com/GreptimeTeam/greptime-meter.git", rev = "5618e779cf2bb4755b499c630fba4c35e91898cb" } meter-core = { git = "https://github.com/GreptimeTeam/greptime-meter.git", rev = "5618e779cf2bb4755b499c630fba4c35e91898cb" }
mockall = "0.13" mockall = "0.13"
moka = "0.12" moka = "0.12"
@@ -164,9 +159,9 @@ nalgebra = "0.33"
nix = { version = "0.30.1", default-features = false, features = ["event", "fs", "process"] } nix = { version = "0.30.1", default-features = false, features = ["event", "fs", "process"] }
notify = "8.0" notify = "8.0"
num_cpus = "1.16" num_cpus = "1.16"
object_store_opendal = "0.54" object_store_opendal = "0.50"
once_cell = "1.18" once_cell = "1.18"
opentelemetry-proto = { version = "0.30", features = [ opentelemetry-proto = { version = "0.27", features = [
"gen-tonic", "gen-tonic",
"metrics", "metrics",
"trace", "trace",
@@ -175,14 +170,13 @@ opentelemetry-proto = { version = "0.30", features = [
] } ] }
ordered-float = { version = "4.3", features = ["serde"] } ordered-float = { version = "4.3", features = ["serde"] }
parking_lot = "0.12" parking_lot = "0.12"
parquet = { version = "56.0", default-features = false, features = ["arrow", "async", "object_store"] } parquet = { version = "54.2", default-features = false, features = ["arrow", "async", "object_store"] }
paste = "1.0" paste = "1.0"
pin-project = "1.0" pin-project = "1.0"
pretty_assertions = "1.4.0" pretty_assertions = "1.4.0"
prometheus = { version = "0.13.3", features = ["process"] } prometheus = { version = "0.13.3", features = ["process"] }
promql-parser = { version = "0.6", features = ["ser"] } promql-parser = { version = "0.6", features = ["ser"] }
prost = { version = "0.13", features = ["no-recursion-limit"] } prost = { version = "0.13", features = ["no-recursion-limit"] }
prost-types = "0.13"
raft-engine = { version = "0.4.1", default-features = false } raft-engine = { version = "0.4.1", default-features = false }
rand = "0.9" rand = "0.9"
ratelimit = "0.10" ratelimit = "0.10"
@@ -194,7 +188,7 @@ reqwest = { version = "0.12", default-features = false, features = [
"stream", "stream",
"multipart", "multipart",
] } ] }
rskafka = { git = "https://github.com/WenyXu/rskafka.git", rev = "7b0f31ed39db049b4ee2e5f1e95b5a30be9baf76", features = [ rskafka = { git = "https://github.com/influxdata/rskafka.git", rev = "8dbd01ed809f5a791833a594e85b144e36e45820", features = [
"transport-tls", "transport-tls",
] } ] }
rstest = "0.25" rstest = "0.25"
@@ -207,14 +201,15 @@ sea-query = "0.32"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["float_roundtrip"] } serde_json = { version = "1.0", features = ["float_roundtrip"] }
serde_with = "3" serde_with = "3"
shadow-rs = "1.1"
simd-json = "0.15" simd-json = "0.15"
similar-asserts = "1.6.0" similar-asserts = "1.6.0"
smallvec = { version = "1", features = ["serde"] } smallvec = { version = "1", features = ["serde"] }
snafu = "0.8" snafu = "0.8"
sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "39e4fc94c3c741981f77e9d63b5ce8c02e0a27ea", features = [ sqlparser = { git = "https://github.com/GreptimeTeam/sqlparser-rs.git", rev = "df6fcca80ce903f5beef7002cd2c1b062e7024f8", features = [
"visitor", "visitor",
"serde", "serde",
] } # branch = "v0.55.x" ] } # branch = "v0.54.x"
sqlx = { version = "0.8", features = [ sqlx = { version = "0.8", features = [
"runtime-tokio-rustls", "runtime-tokio-rustls",
"mysql", "mysql",
@@ -224,20 +219,20 @@ sqlx = { version = "0.8", features = [
strum = { version = "0.27", features = ["derive"] } strum = { version = "0.27", features = ["derive"] }
sysinfo = "0.33" sysinfo = "0.33"
tempfile = "3" tempfile = "3"
tokio = { version = "1.47", features = ["full"] } tokio = { version = "1.40", features = ["full"] }
tokio-postgres = "0.7" tokio-postgres = "0.7"
tokio-rustls = { version = "0.26.2", default-features = false } tokio-rustls = { version = "0.26.2", default-features = false }
tokio-stream = "0.1" tokio-stream = "0.1"
tokio-util = { version = "0.7", features = ["io-util", "compat"] } tokio-util = { version = "0.7", features = ["io-util", "compat"] }
toml = "0.8.8" toml = "0.8.8"
tonic = { version = "0.13", features = ["tls-ring", "gzip", "zstd"] } tonic = { version = "0.12", features = ["tls", "gzip", "zstd"] }
tower = "0.5" tower = "0.5"
tower-http = "0.6" tower-http = "0.6"
tracing = "0.1" tracing = "0.1"
tracing-appender = "0.2" tracing-appender = "0.2"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "fmt"] } tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "fmt"] }
typetag = "0.2" typetag = "0.2"
uuid = { version = "1.17", features = ["serde", "v4", "fast-rng"] } uuid = { version = "1.7", features = ["serde", "v4", "fast-rng"] }
vrl = "0.25" vrl = "0.25"
zstd = "0.13" zstd = "0.13"
# DO_NOT_REMOVE_THIS: END_OF_EXTERNAL_DEPENDENCIES # DO_NOT_REMOVE_THIS: END_OF_EXTERNAL_DEPENDENCIES
@@ -296,7 +291,7 @@ mito-codec = { path = "src/mito-codec" }
mito2 = { path = "src/mito2" } mito2 = { path = "src/mito2" }
object-store = { path = "src/object-store" } object-store = { path = "src/object-store" }
operator = { path = "src/operator" } operator = { path = "src/operator" }
otel-arrow-rust = { git = "https://github.com/GreptimeTeam/otel-arrow", rev = "2d64b7c0fa95642028a8205b36fe9ea0b023ec59", features = [ otel-arrow-rust = { git = "https://github.com/open-telemetry/otel-arrow", rev = "5d551412d2a12e689cde4d84c14ef29e36784e51", features = [
"server", "server",
] } ] }
partition = { path = "src/partition" } partition = { path = "src/partition" }

View File

@@ -8,7 +8,7 @@ CARGO_BUILD_OPTS := --locked
IMAGE_REGISTRY ?= docker.io IMAGE_REGISTRY ?= docker.io
IMAGE_NAMESPACE ?= greptime IMAGE_NAMESPACE ?= greptime
IMAGE_TAG ?= latest IMAGE_TAG ?= latest
DEV_BUILDER_IMAGE_TAG ?= 2025-05-19-f55023f3-20250829091211 DEV_BUILDER_IMAGE_TAG ?= 2025-05-19-b2377d4b-20250520045554
BUILDX_MULTI_PLATFORM_BUILD ?= false BUILDX_MULTI_PLATFORM_BUILD ?= false
BUILDX_BUILDER_NAME ?= gtbuilder BUILDX_BUILDER_NAME ?= gtbuilder
BASE_IMAGE ?= ubuntu BASE_IMAGE ?= ubuntu
@@ -22,7 +22,7 @@ SQLNESS_OPTS ?=
ETCD_VERSION ?= v3.5.9 ETCD_VERSION ?= v3.5.9
ETCD_IMAGE ?= quay.io/coreos/etcd:${ETCD_VERSION} ETCD_IMAGE ?= quay.io/coreos/etcd:${ETCD_VERSION}
RETRY_COUNT ?= 3 RETRY_COUNT ?= 3
NEXTEST_OPTS := --retries ${RETRY_COUNT} --features pg_kvbackend,mysql_kvbackend NEXTEST_OPTS := --retries ${RETRY_COUNT}
BUILD_JOBS ?= $(shell which nproc 1>/dev/null && expr $$(nproc) / 2) # If nproc is not available, we don't set the build jobs. BUILD_JOBS ?= $(shell which nproc 1>/dev/null && expr $$(nproc) / 2) # If nproc is not available, we don't set the build jobs.
ifeq ($(BUILD_JOBS), 0) # If the number of cores is less than 2, set the build jobs to 1. ifeq ($(BUILD_JOBS), 0) # If the number of cores is less than 2, set the build jobs to 1.
BUILD_JOBS := 1 BUILD_JOBS := 1
@@ -193,17 +193,6 @@ clippy: ## Check clippy rules.
fix-clippy: ## Fix clippy violations. fix-clippy: ## Fix clippy violations.
cargo clippy --workspace --all-targets --all-features --fix cargo clippy --workspace --all-targets --all-features --fix
.PHONY: check-udeps
check-udeps: ## Check unused dependencies.
cargo udeps --workspace --all-targets
.PHONY: fix-udeps
fix-udeps: ## Remove unused dependencies automatically.
@echo "Running cargo-udeps to find unused dependencies..."
@cargo udeps --workspace --all-targets --output json > udeps-report.json || true
@echo "Removing unused dependencies..."
@python3 scripts/fix-udeps.py udeps-report.json
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: ## Check code format. fmt-check: ## Check code format.
cargo fmt --all -- --check cargo fmt --all -- --check

View File

@@ -41,7 +41,6 @@
| `mysql.addr` | String | `127.0.0.1:4002` | The addr to bind the MySQL server. | | `mysql.addr` | String | `127.0.0.1:4002` | The addr to bind the MySQL server. |
| `mysql.runtime_size` | Integer | `2` | The number of server worker threads. | | `mysql.runtime_size` | Integer | `2` | The number of server worker threads. |
| `mysql.keep_alive` | String | `0s` | Server-side keep-alive time.<br/>Set to 0 (default) to disable. | | `mysql.keep_alive` | String | `0s` | Server-side keep-alive time.<br/>Set to 0 (default) to disable. |
| `mysql.prepared_stmt_cache_size` | Integer | `10000` | Maximum entries in the MySQL prepared statement cache; default is 10,000. |
| `mysql.tls` | -- | -- | -- | | `mysql.tls` | -- | -- | -- |
| `mysql.tls.mode` | String | `disable` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html<br/>- `disable` (default value)<br/>- `prefer`<br/>- `require`<br/>- `verify-ca`<br/>- `verify-full` | | `mysql.tls.mode` | String | `disable` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html<br/>- `disable` (default value)<br/>- `prefer`<br/>- `require`<br/>- `verify-ca`<br/>- `verify-full` |
| `mysql.tls.cert_path` | String | Unset | Certificate file path. | | `mysql.tls.cert_path` | String | Unset | Certificate file path. |
@@ -148,7 +147,7 @@
| `region_engine.mito.write_cache_ttl` | String | Unset | TTL for write cache. | | `region_engine.mito.write_cache_ttl` | String | Unset | TTL for write cache. |
| `region_engine.mito.sst_write_buffer_size` | String | `8MB` | Buffer size for SST writing. | | `region_engine.mito.sst_write_buffer_size` | String | `8MB` | Buffer size for SST writing. |
| `region_engine.mito.parallel_scan_channel_size` | Integer | `32` | Capacity of the channel to send data from parallel scan tasks to the main task. | | `region_engine.mito.parallel_scan_channel_size` | Integer | `32` | Capacity of the channel to send data from parallel scan tasks to the main task. |
| `region_engine.mito.max_concurrent_scan_files` | Integer | `384` | Maximum number of SST files to scan concurrently. | | `region_engine.mito.max_concurrent_scan_files` | Integer | `128` | Maximum number of SST files to scan concurrently. |
| `region_engine.mito.allow_stale_entries` | Bool | `false` | Whether to allow stale WAL entries read during replay. | | `region_engine.mito.allow_stale_entries` | Bool | `false` | Whether to allow stale WAL entries read during replay. |
| `region_engine.mito.min_compaction_interval` | String | `0m` | Minimum time interval between two compactions.<br/>To align with the old behavior, the default value is 0 (no restrictions). | | `region_engine.mito.min_compaction_interval` | String | `0m` | Minimum time interval between two compactions.<br/>To align with the old behavior, the default value is 0 (no restrictions). |
| `region_engine.mito.index` | -- | -- | The options for index in Mito engine. | | `region_engine.mito.index` | -- | -- | The options for index in Mito engine. |
@@ -187,13 +186,12 @@
| `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. | | `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. |
| `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. | | `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. |
| `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. | | `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. |
| `logging.otlp_endpoint` | String | `http://localhost:4318/v1/traces` | The OTLP tracing endpoint. | | `logging.otlp_endpoint` | String | `http://localhost:4318` | The OTLP tracing endpoint. |
| `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. | | `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. |
| `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. | | `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. |
| `logging.max_log_files` | Integer | `720` | The maximum amount of log files. | | `logging.max_log_files` | Integer | `720` | The maximum amount of log files. |
| `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. | | `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. |
| `logging.otlp_headers` | -- | -- | Additional OTLP headers, only valid when using OTLP http | | `logging.tracing_sample_ratio` | -- | -- | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio` | -- | Unset | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- | | `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- |
| `slow_query` | -- | -- | The slow query log options. | | `slow_query` | -- | -- | The slow query log options. |
| `slow_query.enable` | Bool | `false` | Whether to enable slow query log. | | `slow_query.enable` | Bool | `false` | Whether to enable slow query log. |
@@ -245,22 +243,11 @@
| `grpc.tls.cert_path` | String | Unset | Certificate file path. | | `grpc.tls.cert_path` | String | Unset | Certificate file path. |
| `grpc.tls.key_path` | String | Unset | Private key file path. | | `grpc.tls.key_path` | String | Unset | Private key file path. |
| `grpc.tls.watch` | Bool | `false` | Watch for Certificate and key file change and auto reload.<br/>For now, gRPC tls config does not support auto reload. | | `grpc.tls.watch` | Bool | `false` | Watch for Certificate and key file change and auto reload.<br/>For now, gRPC tls config does not support auto reload. |
| `internal_grpc` | -- | -- | The internal gRPC server options. Internal gRPC port for nodes inside cluster to access frontend. |
| `internal_grpc.bind_addr` | String | `127.0.0.1:4010` | The address to bind the gRPC server. |
| `internal_grpc.server_addr` | String | `127.0.0.1:4010` | The address advertised to the metasrv, and used for connections from outside the host.<br/>If left empty or unset, the server will automatically use the IP address of the first network interface<br/>on the host, with the same port number as the one specified in `grpc.bind_addr`. |
| `internal_grpc.runtime_size` | Integer | `8` | The number of server worker threads. |
| `internal_grpc.flight_compression` | String | `arrow_ipc` | Compression mode for frontend side Arrow IPC service. Available options:<br/>- `none`: disable all compression<br/>- `transport`: only enable gRPC transport compression (zstd)<br/>- `arrow_ipc`: only enable Arrow IPC compression (lz4)<br/>- `all`: enable all compression.<br/>Default to `none` |
| `internal_grpc.tls` | -- | -- | internal gRPC server TLS options, see `mysql.tls` section. |
| `internal_grpc.tls.mode` | String | `disable` | TLS mode. |
| `internal_grpc.tls.cert_path` | String | Unset | Certificate file path. |
| `internal_grpc.tls.key_path` | String | Unset | Private key file path. |
| `internal_grpc.tls.watch` | Bool | `false` | Watch for Certificate and key file change and auto reload.<br/>For now, gRPC tls config does not support auto reload. |
| `mysql` | -- | -- | MySQL server options. | | `mysql` | -- | -- | MySQL server options. |
| `mysql.enable` | Bool | `true` | Whether to enable. | | `mysql.enable` | Bool | `true` | Whether to enable. |
| `mysql.addr` | String | `127.0.0.1:4002` | The addr to bind the MySQL server. | | `mysql.addr` | String | `127.0.0.1:4002` | The addr to bind the MySQL server. |
| `mysql.runtime_size` | Integer | `2` | The number of server worker threads. | | `mysql.runtime_size` | Integer | `2` | The number of server worker threads. |
| `mysql.keep_alive` | String | `0s` | Server-side keep-alive time.<br/>Set to 0 (default) to disable. | | `mysql.keep_alive` | String | `0s` | Server-side keep-alive time.<br/>Set to 0 (default) to disable. |
| `mysql.prepared_stmt_cache_size` | Integer | `10000` | Maximum entries in the MySQL prepared statement cache; default is 10,000. |
| `mysql.tls` | -- | -- | -- | | `mysql.tls` | -- | -- | -- |
| `mysql.tls.mode` | String | `disable` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html<br/>- `disable` (default value)<br/>- `prefer`<br/>- `require`<br/>- `verify-ca`<br/>- `verify-full` | | `mysql.tls.mode` | String | `disable` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html<br/>- `disable` (default value)<br/>- `prefer`<br/>- `require`<br/>- `verify-ca`<br/>- `verify-full` |
| `mysql.tls.cert_path` | String | Unset | Certificate file path. | | `mysql.tls.cert_path` | String | Unset | Certificate file path. |
@@ -306,20 +293,19 @@
| `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. | | `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. |
| `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. | | `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. |
| `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. | | `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. |
| `logging.otlp_endpoint` | String | `http://localhost:4318/v1/traces` | The OTLP tracing endpoint. | | `logging.otlp_endpoint` | String | `http://localhost:4318` | The OTLP tracing endpoint. |
| `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. | | `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. |
| `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. | | `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. |
| `logging.max_log_files` | Integer | `720` | The maximum amount of log files. | | `logging.max_log_files` | Integer | `720` | The maximum amount of log files. |
| `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. | | `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. |
| `logging.otlp_headers` | -- | -- | Additional OTLP headers, only valid when using OTLP http | | `logging.tracing_sample_ratio` | -- | -- | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio` | -- | Unset | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- | | `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- |
| `slow_query` | -- | -- | The slow query log options. | | `slow_query` | -- | -- | The slow query log options. |
| `slow_query.enable` | Bool | `true` | Whether to enable slow query log. | | `slow_query.enable` | Bool | `true` | Whether to enable slow query log. |
| `slow_query.record_type` | String | `system_table` | The record type of slow queries. It can be `system_table` or `log`.<br/>If `system_table` is selected, the slow queries will be recorded in a system table `greptime_private.slow_queries`.<br/>If `log` is selected, the slow queries will be logged in a log file `greptimedb-slow-queries.*`. | | `slow_query.record_type` | String | `system_table` | The record type of slow queries. It can be `system_table` or `log`.<br/>If `system_table` is selected, the slow queries will be recorded in a system table `greptime_private.slow_queries`.<br/>If `log` is selected, the slow queries will be logged in a log file `greptimedb-slow-queries.*`. |
| `slow_query.threshold` | String | `30s` | The threshold of slow query. It can be human readable time string, for example: `10s`, `100ms`, `1s`. | | `slow_query.threshold` | String | `30s` | The threshold of slow query. It can be human readable time string, for example: `10s`, `100ms`, `1s`. |
| `slow_query.sample_ratio` | Float | `1.0` | The sampling ratio of slow query log. The value should be in the range of (0, 1]. For example, `0.1` means 10% of the slow queries will be logged and `1.0` means all slow queries will be logged. | | `slow_query.sample_ratio` | Float | `1.0` | The sampling ratio of slow query log. The value should be in the range of (0, 1]. For example, `0.1` means 10% of the slow queries will be logged and `1.0` means all slow queries will be logged. |
| `slow_query.ttl` | String | `90d` | The TTL of the `slow_queries` system table. Default is `90d` when `record_type` is `system_table`. | | `slow_query.ttl` | String | `30d` | The TTL of the `slow_queries` system table. Default is `30d` when `record_type` is `system_table`. |
| `export_metrics` | -- | -- | The frontend can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.<br/>This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. | | `export_metrics` | -- | -- | The frontend can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.<br/>This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. |
| `export_metrics.enable` | Bool | `false` | whether enable export metrics. | | `export_metrics.enable` | Bool | `false` | whether enable export metrics. |
| `export_metrics.write_interval` | String | `30s` | The interval of export metrics. | | `export_metrics.write_interval` | String | `30s` | The interval of export metrics. |
@@ -330,8 +316,6 @@
| `tracing.tokio_console_addr` | String | Unset | The tokio console address. | | `tracing.tokio_console_addr` | String | Unset | The tokio console address. |
| `memory` | -- | -- | The memory options. | | `memory` | -- | -- | The memory options. |
| `memory.enable_heap_profiling` | Bool | `true` | Whether to enable heap profiling activation during startup.<br/>When enabled, heap profiling will be activated if the `MALLOC_CONF` environment variable<br/>is set to "prof:true,prof_active:false". The official image adds this env variable.<br/>Default is true. | | `memory.enable_heap_profiling` | Bool | `true` | Whether to enable heap profiling activation during startup.<br/>When enabled, heap profiling will be activated if the `MALLOC_CONF` environment variable<br/>is set to "prof:true,prof_active:false". The official image adds this env variable.<br/>Default is true. |
| `event_recorder` | -- | -- | Configuration options for the event recorder. |
| `event_recorder.ttl` | String | `90d` | TTL for the events table that will be used to store the events. Default is `90d`. |
### Metasrv ### Metasrv
@@ -343,7 +327,6 @@
| `store_key_prefix` | String | `""` | If it's not empty, the metasrv will store all data with this key prefix. | | `store_key_prefix` | String | `""` | If it's not empty, the metasrv will store all data with this key prefix. |
| `backend` | String | `etcd_store` | The datastore for meta server.<br/>Available values:<br/>- `etcd_store` (default value)<br/>- `memory_store`<br/>- `postgres_store`<br/>- `mysql_store` | | `backend` | String | `etcd_store` | The datastore for meta server.<br/>Available values:<br/>- `etcd_store` (default value)<br/>- `memory_store`<br/>- `postgres_store`<br/>- `mysql_store` |
| `meta_table_name` | String | `greptime_metakv` | Table name in RDS to store metadata. Effect when using a RDS kvbackend.<br/>**Only used when backend is `postgres_store`.** | | `meta_table_name` | String | `greptime_metakv` | Table name in RDS to store metadata. Effect when using a RDS kvbackend.<br/>**Only used when backend is `postgres_store`.** |
| `meta_schema_name` | String | `greptime_schema` | Optional PostgreSQL schema for metadata table and election table name qualification.<br/>When PostgreSQL public schema is not writable (e.g., PostgreSQL 15+ with restricted public),<br/>set this to a writable schema. GreptimeDB will use `meta_schema_name`.`meta_table_name`.<br/>GreptimeDB will NOT create the schema automatically; please ensure it exists or the user has permission.<br/>**Only used when backend is `postgres_store`.** |
| `meta_election_lock_id` | Integer | `1` | Advisory lock id in PostgreSQL for election. Effect when using PostgreSQL as kvbackend<br/>Only used when backend is `postgres_store`. | | `meta_election_lock_id` | Integer | `1` | Advisory lock id in PostgreSQL for election. Effect when using PostgreSQL as kvbackend<br/>Only used when backend is `postgres_store`. |
| `selector` | String | `round_robin` | Datanode selector type.<br/>- `round_robin` (default value)<br/>- `lease_based`<br/>- `load_based`<br/>For details, please see "https://docs.greptime.com/developer-guide/metasrv/selector". | | `selector` | String | `round_robin` | Datanode selector type.<br/>- `round_robin` (default value)<br/>- `lease_based`<br/>- `load_based`<br/>For details, please see "https://docs.greptime.com/developer-guide/metasrv/selector". |
| `use_memory_store` | Bool | `false` | Store data in memory. | | `use_memory_store` | Bool | `false` | Store data in memory. |
@@ -355,7 +338,7 @@
| `runtime` | -- | -- | The runtime options. | | `runtime` | -- | -- | The runtime options. |
| `runtime.global_rt_size` | Integer | `8` | The number of threads to execute the runtime for global read operations. | | `runtime.global_rt_size` | Integer | `8` | The number of threads to execute the runtime for global read operations. |
| `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute the runtime for global write operations. | | `runtime.compact_rt_size` | Integer | `4` | The number of threads to execute the runtime for global write operations. |
| `backend_tls` | -- | -- | TLS configuration for kv store backend (applicable for etcd, PostgreSQL, and MySQL backends)<br/>When using etcd, PostgreSQL, or MySQL as metadata store, you can configure TLS here | | `backend_tls` | -- | -- | TLS configuration for kv store backend (only applicable for PostgreSQL/MySQL backends)<br/>When using PostgreSQL or MySQL as metadata store, you can configure TLS here |
| `backend_tls.mode` | String | `prefer` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html<br/>- "disable" - No TLS<br/>- "prefer" (default) - Try TLS, fallback to plain<br/>- "require" - Require TLS<br/>- "verify_ca" - Require TLS and verify CA<br/>- "verify_full" - Require TLS and verify hostname | | `backend_tls.mode` | String | `prefer` | TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html<br/>- "disable" - No TLS<br/>- "prefer" (default) - Try TLS, fallback to plain<br/>- "require" - Require TLS<br/>- "verify_ca" - Require TLS and verify CA<br/>- "verify_full" - Require TLS and verify hostname |
| `backend_tls.cert_path` | String | `""` | Path to client certificate file (for client authentication)<br/>Like "/path/to/client.crt" | | `backend_tls.cert_path` | String | `""` | Path to client certificate file (for client authentication)<br/>Like "/path/to/client.crt" |
| `backend_tls.key_path` | String | `""` | Path to client private key file (for client authentication)<br/>Like "/path/to/client.key" | | `backend_tls.key_path` | String | `""` | Path to client private key file (for client authentication)<br/>Like "/path/to/client.key" |
@@ -388,33 +371,28 @@
| `datanode.client.tcp_nodelay` | Bool | `true` | `TCP_NODELAY` option for accepted connections. | | `datanode.client.tcp_nodelay` | Bool | `true` | `TCP_NODELAY` option for accepted connections. |
| `wal` | -- | -- | -- | | `wal` | -- | -- | -- |
| `wal.provider` | String | `raft_engine` | -- | | `wal.provider` | String | `raft_engine` | -- |
| `wal.broker_endpoints` | Array | -- | The broker endpoints of the Kafka cluster.<br/><br/>**It's only used when the provider is `kafka`**. | | `wal.broker_endpoints` | Array | -- | The broker endpoints of the Kafka cluster. |
| `wal.auto_create_topics` | Bool | `true` | Automatically create topics for WAL.<br/>Set to `true` to automatically create topics for WAL.<br/>Otherwise, use topics named `topic_name_prefix_[0..num_topics)`<br/>**It's only used when the provider is `kafka`**. | | `wal.auto_create_topics` | Bool | `true` | Automatically create topics for WAL.<br/>Set to `true` to automatically create topics for WAL.<br/>Otherwise, use topics named `topic_name_prefix_[0..num_topics)` |
| `wal.auto_prune_interval` | String | `30m` | Interval of automatically WAL pruning.<br/>Set to `0s` to disable automatically WAL pruning which delete unused remote WAL entries periodically.<br/>**It's only used when the provider is `kafka`**. | | `wal.auto_prune_interval` | String | `0s` | Interval of automatically WAL pruning.<br/>Set to `0s` to disable automatically WAL pruning which delete unused remote WAL entries periodically. |
| `wal.flush_trigger_size` | String | `512MB` | Estimated size threshold to trigger a flush when using Kafka remote WAL.<br/>Since multiple regions may share a Kafka topic, the estimated size is calculated as:<br/> (latest_entry_id - flushed_entry_id) * avg_record_size<br/>MetaSrv triggers a flush for a region when this estimated size exceeds `flush_trigger_size`.<br/>- `latest_entry_id`: The latest entry ID in the topic.<br/>- `flushed_entry_id`: The last flushed entry ID for the region.<br/>Set to "0" to let the system decide the flush trigger size.<br/>**It's only used when the provider is `kafka`**. | | `wal.trigger_flush_threshold` | Integer | `0` | The threshold to trigger a flush operation of a region in automatically WAL pruning.<br/>Metasrv will send a flush request to flush the region when:<br/>`trigger_flush_threshold` + `prunable_entry_id` < `max_prunable_entry_id`<br/>where:<br/>- `prunable_entry_id` is the maximum entry id that can be pruned of the region.<br/>- `max_prunable_entry_id` is the maximum prunable entry id among all regions in the same topic.<br/>Set to `0` to disable the flush operation. |
| `wal.checkpoint_trigger_size` | String | `128MB` | Estimated size threshold to trigger a checkpoint when using Kafka remote WAL.<br/>The estimated size is calculated as:<br/> (latest_entry_id - last_checkpoint_entry_id) * avg_record_size<br/>MetaSrv triggers a checkpoint for a region when this estimated size exceeds `checkpoint_trigger_size`.<br/>Set to "0" to let the system decide the checkpoint trigger size.<br/>**It's only used when the provider is `kafka`**. | | `wal.auto_prune_parallelism` | Integer | `10` | Concurrent task limit for automatically WAL pruning. |
| `wal.auto_prune_parallelism` | Integer | `10` | Concurrent task limit for automatically WAL pruning.<br/>**It's only used when the provider is `kafka`**. | | `wal.num_topics` | Integer | `64` | Number of topics. |
| `wal.num_topics` | Integer | `64` | Number of topics used for remote WAL.<br/>**It's only used when the provider is `kafka`**. | | `wal.selector_type` | String | `round_robin` | Topic selector type.<br/>Available selector types:<br/>- `round_robin` (default) |
| `wal.selector_type` | String | `round_robin` | Topic selector type.<br/>Available selector types:<br/>- `round_robin` (default)<br/>**It's only used when the provider is `kafka`**. | | `wal.topic_name_prefix` | String | `greptimedb_wal_topic` | A Kafka topic is constructed by concatenating `topic_name_prefix` and `topic_id`.<br/>Only accepts strings that match the following regular expression pattern:<br/>[a-zA-Z_:-][a-zA-Z0-9_:\-\.@#]*<br/>i.g., greptimedb_wal_topic_0, greptimedb_wal_topic_1. |
| `wal.topic_name_prefix` | String | `greptimedb_wal_topic` | A Kafka topic is constructed by concatenating `topic_name_prefix` and `topic_id`.<br/>Only accepts strings that match the following regular expression pattern:<br/>[a-zA-Z_:-][a-zA-Z0-9_:\-\.@#]*<br/>i.g., greptimedb_wal_topic_0, greptimedb_wal_topic_1.<br/>**It's only used when the provider is `kafka`**. | | `wal.replication_factor` | Integer | `1` | Expected number of replicas of each partition. |
| `wal.replication_factor` | Integer | `1` | Expected number of replicas of each partition.<br/>**It's only used when the provider is `kafka`**. | | `wal.create_topic_timeout` | String | `30s` | Above which a topic creation operation will be cancelled. |
| `wal.create_topic_timeout` | String | `30s` | The timeout for creating a Kafka topic.<br/>**It's only used when the provider is `kafka`**. |
| `event_recorder` | -- | -- | Configuration options for the event recorder. | | `event_recorder` | -- | -- | Configuration options for the event recorder. |
| `event_recorder.ttl` | String | `90d` | TTL for the events table that will be used to store the events. Default is `90d`. | | `event_recorder.ttl` | String | `30d` | TTL for the events table that will be used to store the events. |
| `stats_persistence` | -- | -- | Configuration options for the stats persistence. |
| `stats_persistence.ttl` | String | `0s` | TTL for the stats table that will be used to store the stats.<br/>Set to `0s` to disable stats persistence.<br/>Default is `0s`.<br/>If you want to enable stats persistence, set the TTL to a value greater than 0.<br/>It is recommended to set a small value, e.g., `3h`. |
| `stats_persistence.interval` | String | `10m` | The interval to persist the stats. Default is `10m`.<br/>The minimum value is `10m`, if the value is less than `10m`, it will be overridden to `10m`. |
| `logging` | -- | -- | The logging options. | | `logging` | -- | -- | The logging options. |
| `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. | | `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. |
| `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. | | `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. |
| `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. | | `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. |
| `logging.otlp_endpoint` | String | `http://localhost:4318/v1/traces` | The OTLP tracing endpoint. | | `logging.otlp_endpoint` | String | `http://localhost:4318` | The OTLP tracing endpoint. |
| `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. | | `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. |
| `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. | | `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. |
| `logging.max_log_files` | Integer | `720` | The maximum amount of log files. | | `logging.max_log_files` | Integer | `720` | The maximum amount of log files. |
| `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. | | `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. |
| `logging.otlp_headers` | -- | -- | Additional OTLP headers, only valid when using OTLP http | | `logging.tracing_sample_ratio` | -- | -- | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio` | -- | Unset | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- | | `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- |
| `export_metrics` | -- | -- | The metasrv can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.<br/>This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. | | `export_metrics` | -- | -- | The metasrv can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.<br/>This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. |
| `export_metrics.enable` | Bool | `false` | whether enable export metrics. | | `export_metrics.enable` | Bool | `false` | whether enable export metrics. |
@@ -521,8 +499,6 @@
| `region_engine.mito.worker_channel_size` | Integer | `128` | Request channel size of each worker. | | `region_engine.mito.worker_channel_size` | Integer | `128` | Request channel size of each worker. |
| `region_engine.mito.worker_request_batch_size` | Integer | `64` | Max batch size for a worker to handle requests. | | `region_engine.mito.worker_request_batch_size` | Integer | `64` | Max batch size for a worker to handle requests. |
| `region_engine.mito.manifest_checkpoint_distance` | Integer | `10` | Number of meta action updated to trigger a new checkpoint for the manifest. | | `region_engine.mito.manifest_checkpoint_distance` | Integer | `10` | Number of meta action updated to trigger a new checkpoint for the manifest. |
| `region_engine.mito.experimental_manifest_keep_removed_file_count` | Integer | `256` | Number of removed files to keep in manifest's `removed_files` field before also<br/>remove them from `removed_files`. Mostly for debugging purpose.<br/>If set to 0, it will only use `keep_removed_file_ttl` to decide when to remove files<br/>from `removed_files` field. |
| `region_engine.mito.experimental_manifest_keep_removed_file_ttl` | String | `1h` | How long to keep removed files in the `removed_files` field of manifest<br/>after they are removed from manifest.<br/>files will only be removed from `removed_files` field<br/>if both `keep_removed_file_count` and `keep_removed_file_ttl` is reached. |
| `region_engine.mito.compress_manifest` | Bool | `false` | Whether to compress manifest and checkpoint file by gzip (default false). | | `region_engine.mito.compress_manifest` | Bool | `false` | Whether to compress manifest and checkpoint file by gzip (default false). |
| `region_engine.mito.max_background_flushes` | Integer | Auto | Max number of running background flush jobs (default: 1/2 of cpu cores). | | `region_engine.mito.max_background_flushes` | Integer | Auto | Max number of running background flush jobs (default: 1/2 of cpu cores). |
| `region_engine.mito.max_background_compactions` | Integer | Auto | Max number of running background compaction jobs (default: 1/4 of cpu cores). | | `region_engine.mito.max_background_compactions` | Integer | Auto | Max number of running background compaction jobs (default: 1/4 of cpu cores). |
@@ -540,7 +516,7 @@
| `region_engine.mito.write_cache_ttl` | String | Unset | TTL for write cache. | | `region_engine.mito.write_cache_ttl` | String | Unset | TTL for write cache. |
| `region_engine.mito.sst_write_buffer_size` | String | `8MB` | Buffer size for SST writing. | | `region_engine.mito.sst_write_buffer_size` | String | `8MB` | Buffer size for SST writing. |
| `region_engine.mito.parallel_scan_channel_size` | Integer | `32` | Capacity of the channel to send data from parallel scan tasks to the main task. | | `region_engine.mito.parallel_scan_channel_size` | Integer | `32` | Capacity of the channel to send data from parallel scan tasks to the main task. |
| `region_engine.mito.max_concurrent_scan_files` | Integer | `384` | Maximum number of SST files to scan concurrently. | | `region_engine.mito.max_concurrent_scan_files` | Integer | `128` | Maximum number of SST files to scan concurrently. |
| `region_engine.mito.allow_stale_entries` | Bool | `false` | Whether to allow stale WAL entries read during replay. | | `region_engine.mito.allow_stale_entries` | Bool | `false` | Whether to allow stale WAL entries read during replay. |
| `region_engine.mito.min_compaction_interval` | String | `0m` | Minimum time interval between two compactions.<br/>To align with the old behavior, the default value is 0 (no restrictions). | | `region_engine.mito.min_compaction_interval` | String | `0m` | Minimum time interval between two compactions.<br/>To align with the old behavior, the default value is 0 (no restrictions). |
| `region_engine.mito.index` | -- | -- | The options for index in Mito engine. | | `region_engine.mito.index` | -- | -- | The options for index in Mito engine. |
@@ -579,13 +555,12 @@
| `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. | | `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. |
| `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. | | `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. |
| `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. | | `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. |
| `logging.otlp_endpoint` | String | `http://localhost:4318/v1/traces` | The OTLP tracing endpoint. | | `logging.otlp_endpoint` | String | `http://localhost:4318` | The OTLP tracing endpoint. |
| `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. | | `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. |
| `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. | | `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. |
| `logging.max_log_files` | Integer | `720` | The maximum amount of log files. | | `logging.max_log_files` | Integer | `720` | The maximum amount of log files. |
| `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. | | `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. |
| `logging.otlp_headers` | -- | -- | Additional OTLP headers, only valid when using OTLP http | | `logging.tracing_sample_ratio` | -- | -- | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio` | -- | Unset | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- | | `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- |
| `export_metrics` | -- | -- | The datanode can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.<br/>This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. | | `export_metrics` | -- | -- | The datanode can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.<br/>This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. |
| `export_metrics.enable` | Bool | `false` | whether enable export metrics. | | `export_metrics.enable` | Bool | `false` | whether enable export metrics. |
@@ -616,12 +591,6 @@
| `flow.batching_mode.experimental_frontend_activity_timeout` | String | `60s` | Frontend activity timeout<br/>if frontend is down(not sending heartbeat) for more than frontend_activity_timeout,<br/>it will be removed from the list that flownode use to connect | | `flow.batching_mode.experimental_frontend_activity_timeout` | String | `60s` | Frontend activity timeout<br/>if frontend is down(not sending heartbeat) for more than frontend_activity_timeout,<br/>it will be removed from the list that flownode use to connect |
| `flow.batching_mode.experimental_max_filter_num_per_query` | Integer | `20` | Maximum number of filters allowed in a single query | | `flow.batching_mode.experimental_max_filter_num_per_query` | Integer | `20` | Maximum number of filters allowed in a single query |
| `flow.batching_mode.experimental_time_window_merge_threshold` | Integer | `3` | Time window merge distance | | `flow.batching_mode.experimental_time_window_merge_threshold` | Integer | `3` | Time window merge distance |
| `flow.batching_mode.read_preference` | String | `Leader` | Read preference of the Frontend client. |
| `flow.batching_mode.frontend_tls` | -- | -- | -- |
| `flow.batching_mode.frontend_tls.enabled` | Bool | `false` | Whether to enable TLS for client. |
| `flow.batching_mode.frontend_tls.server_ca_cert_path` | String | Unset | Server Certificate file path. |
| `flow.batching_mode.frontend_tls.client_cert_path` | String | Unset | Client Certificate file path. |
| `flow.batching_mode.frontend_tls.client_key_path` | String | Unset | Client Private key file path. |
| `grpc` | -- | -- | The gRPC server options. | | `grpc` | -- | -- | The gRPC server options. |
| `grpc.bind_addr` | String | `127.0.0.1:6800` | The address to bind the gRPC server. | | `grpc.bind_addr` | String | `127.0.0.1:6800` | The address to bind the gRPC server. |
| `grpc.server_addr` | String | `127.0.0.1:6800` | The address advertised to the metasrv,<br/>and used for connections from outside the host | | `grpc.server_addr` | String | `127.0.0.1:6800` | The address advertised to the metasrv,<br/>and used for connections from outside the host |
@@ -649,13 +618,12 @@
| `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. | | `logging.dir` | String | `./greptimedb_data/logs` | The directory to store the log files. If set to empty, logs will not be written to files. |
| `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. | | `logging.level` | String | Unset | The log level. Can be `info`/`debug`/`warn`/`error`. |
| `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. | | `logging.enable_otlp_tracing` | Bool | `false` | Enable OTLP tracing. |
| `logging.otlp_endpoint` | String | `http://localhost:4318/v1/traces` | The OTLP tracing endpoint. | | `logging.otlp_endpoint` | String | `http://localhost:4318` | The OTLP tracing endpoint. |
| `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. | | `logging.append_stdout` | Bool | `true` | Whether to append logs to stdout. |
| `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. | | `logging.log_format` | String | `text` | The log format. Can be `text`/`json`. |
| `logging.max_log_files` | Integer | `720` | The maximum amount of log files. | | `logging.max_log_files` | Integer | `720` | The maximum amount of log files. |
| `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. | | `logging.otlp_export_protocol` | String | `http` | The OTLP tracing export protocol. Can be `grpc`/`http`. |
| `logging.otlp_headers` | -- | -- | Additional OTLP headers, only valid when using OTLP http | | `logging.tracing_sample_ratio` | -- | -- | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio` | -- | Unset | The percentage of tracing will be sampled and exported.<br/>Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.<br/>ratio > 1 are treated as 1. Fractions < 0 are treated as 0 |
| `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- | | `logging.tracing_sample_ratio.default_ratio` | Float | `1.0` | -- |
| `tracing` | -- | -- | The tracing options. Only effect when compiled with `tokio-console` feature. | | `tracing` | -- | -- | The tracing options. Only effect when compiled with `tokio-console` feature. |
| `tracing.tokio_console_addr` | String | Unset | The tokio console address. | | `tracing.tokio_console_addr` | String | Unset | The tokio console address. |

View File

@@ -409,19 +409,6 @@ worker_request_batch_size = 64
## Number of meta action updated to trigger a new checkpoint for the manifest. ## Number of meta action updated to trigger a new checkpoint for the manifest.
manifest_checkpoint_distance = 10 manifest_checkpoint_distance = 10
## Number of removed files to keep in manifest's `removed_files` field before also
## remove them from `removed_files`. Mostly for debugging purpose.
## If set to 0, it will only use `keep_removed_file_ttl` to decide when to remove files
## from `removed_files` field.
experimental_manifest_keep_removed_file_count = 256
## How long to keep removed files in the `removed_files` field of manifest
## after they are removed from manifest.
## files will only be removed from `removed_files` field
## if both `keep_removed_file_count` and `keep_removed_file_ttl` is reached.
experimental_manifest_keep_removed_file_ttl = "1h"
## Whether to compress manifest and checkpoint file by gzip (default false). ## Whether to compress manifest and checkpoint file by gzip (default false).
compress_manifest = false compress_manifest = false
@@ -488,7 +475,7 @@ sst_write_buffer_size = "8MB"
parallel_scan_channel_size = 32 parallel_scan_channel_size = 32
## Maximum number of SST files to scan concurrently. ## Maximum number of SST files to scan concurrently.
max_concurrent_scan_files = 384 max_concurrent_scan_files = 128
## Whether to allow stale WAL entries read during replay. ## Whether to allow stale WAL entries read during replay.
allow_stale_entries = false allow_stale_entries = false
@@ -645,7 +632,7 @@ level = "info"
enable_otlp_tracing = false enable_otlp_tracing = false
## The OTLP tracing endpoint. ## The OTLP tracing endpoint.
otlp_endpoint = "http://localhost:4318/v1/traces" otlp_endpoint = "http://localhost:4318"
## Whether to append logs to stdout. ## Whether to append logs to stdout.
append_stdout = true append_stdout = true
@@ -659,13 +646,6 @@ max_log_files = 720
## The OTLP tracing export protocol. Can be `grpc`/`http`. ## The OTLP tracing export protocol. Can be `grpc`/`http`.
otlp_export_protocol = "http" otlp_export_protocol = "http"
## Additional OTLP headers, only valid when using OTLP http
[logging.otlp_headers]
## @toml2docs:none-default
#Authorization = "Bearer my-token"
## @toml2docs:none-default
#Database = "My database"
## The percentage of tracing will be sampled and exported. ## The percentage of tracing will be sampled and exported.
## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1. ## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.
## ratio > 1 are treated as 1. Fractions < 0 are treated as 0 ## ratio > 1 are treated as 1. Fractions < 0 are treated as 0

View File

@@ -30,20 +30,6 @@ node_id = 14
#+experimental_max_filter_num_per_query=20 #+experimental_max_filter_num_per_query=20
## Time window merge distance ## Time window merge distance
#+experimental_time_window_merge_threshold=3 #+experimental_time_window_merge_threshold=3
## Read preference of the Frontend client.
#+read_preference="Leader"
[flow.batching_mode.frontend_tls]
## Whether to enable TLS for client.
#+enabled=false
## Server Certificate file path.
## @toml2docs:none-default
#+server_ca_cert_path=""
## Client Certificate file path.
## @toml2docs:none-default
#+client_cert_path=""
## Client Private key file path.
## @toml2docs:none-default
#+client_key_path=""
## The gRPC server options. ## The gRPC server options.
[grpc] [grpc]
@@ -120,7 +106,7 @@ level = "info"
enable_otlp_tracing = false enable_otlp_tracing = false
## The OTLP tracing endpoint. ## The OTLP tracing endpoint.
otlp_endpoint = "http://localhost:4318/v1/traces" otlp_endpoint = "http://localhost:4318"
## Whether to append logs to stdout. ## Whether to append logs to stdout.
append_stdout = true append_stdout = true
@@ -134,13 +120,6 @@ max_log_files = 720
## The OTLP tracing export protocol. Can be `grpc`/`http`. ## The OTLP tracing export protocol. Can be `grpc`/`http`.
otlp_export_protocol = "http" otlp_export_protocol = "http"
## Additional OTLP headers, only valid when using OTLP http
[logging.otlp_headers]
## @toml2docs:none-default
#Authorization = "Bearer my-token"
## @toml2docs:none-default
#Database = "My database"
## The percentage of tracing will be sampled and exported. ## The percentage of tracing will be sampled and exported.
## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1. ## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.
## ratio > 1 are treated as 1. Fractions < 0 are treated as 0 ## ratio > 1 are treated as 1. Fractions < 0 are treated as 0

View File

@@ -79,42 +79,6 @@ key_path = ""
## For now, gRPC tls config does not support auto reload. ## For now, gRPC tls config does not support auto reload.
watch = false watch = false
## The internal gRPC server options. Internal gRPC port for nodes inside cluster to access frontend.
[internal_grpc]
## The address to bind the gRPC server.
bind_addr = "127.0.0.1:4010"
## The address advertised to the metasrv, and used for connections from outside the host.
## If left empty or unset, the server will automatically use the IP address of the first network interface
## on the host, with the same port number as the one specified in `grpc.bind_addr`.
server_addr = "127.0.0.1:4010"
## The number of server worker threads.
runtime_size = 8
## Compression mode for frontend side Arrow IPC service. Available options:
## - `none`: disable all compression
## - `transport`: only enable gRPC transport compression (zstd)
## - `arrow_ipc`: only enable Arrow IPC compression (lz4)
## - `all`: enable all compression.
## Default to `none`
flight_compression = "arrow_ipc"
## internal gRPC server TLS options, see `mysql.tls` section.
[internal_grpc.tls]
## TLS mode.
mode = "disable"
## Certificate file path.
## @toml2docs:none-default
cert_path = ""
## Private key file path.
## @toml2docs:none-default
key_path = ""
## Watch for Certificate and key file change and auto reload.
## For now, gRPC tls config does not support auto reload.
watch = false
## MySQL server options. ## MySQL server options.
[mysql] [mysql]
## Whether to enable. ## Whether to enable.
@@ -126,8 +90,6 @@ runtime_size = 2
## Server-side keep-alive time. ## Server-side keep-alive time.
## Set to 0 (default) to disable. ## Set to 0 (default) to disable.
keep_alive = "0s" keep_alive = "0s"
## Maximum entries in the MySQL prepared statement cache; default is 10,000.
prepared_stmt_cache_size = 10000
# MySQL server TLS options. # MySQL server TLS options.
[mysql.tls] [mysql.tls]
@@ -259,7 +221,7 @@ level = "info"
enable_otlp_tracing = false enable_otlp_tracing = false
## The OTLP tracing endpoint. ## The OTLP tracing endpoint.
otlp_endpoint = "http://localhost:4318/v1/traces" otlp_endpoint = "http://localhost:4318"
## Whether to append logs to stdout. ## Whether to append logs to stdout.
append_stdout = true append_stdout = true
@@ -273,13 +235,6 @@ max_log_files = 720
## The OTLP tracing export protocol. Can be `grpc`/`http`. ## The OTLP tracing export protocol. Can be `grpc`/`http`.
otlp_export_protocol = "http" otlp_export_protocol = "http"
## Additional OTLP headers, only valid when using OTLP http
[logging.otlp_headers]
## @toml2docs:none-default
#Authorization = "Bearer my-token"
## @toml2docs:none-default
#Database = "My database"
## The percentage of tracing will be sampled and exported. ## The percentage of tracing will be sampled and exported.
## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1. ## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.
## ratio > 1 are treated as 1. Fractions < 0 are treated as 0 ## ratio > 1 are treated as 1. Fractions < 0 are treated as 0
@@ -302,8 +257,8 @@ threshold = "30s"
## The sampling ratio of slow query log. The value should be in the range of (0, 1]. For example, `0.1` means 10% of the slow queries will be logged and `1.0` means all slow queries will be logged. ## The sampling ratio of slow query log. The value should be in the range of (0, 1]. For example, `0.1` means 10% of the slow queries will be logged and `1.0` means all slow queries will be logged.
sample_ratio = 1.0 sample_ratio = 1.0
## The TTL of the `slow_queries` system table. Default is `90d` when `record_type` is `system_table`. ## The TTL of the `slow_queries` system table. Default is `30d` when `record_type` is `system_table`.
ttl = "90d" ttl = "30d"
## The frontend can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API. ## The frontend can export its metrics and send to Prometheus compatible service (e.g. `greptimedb` itself) from remote-write API.
## This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape. ## This is only used for `greptimedb` to export its own metrics internally. It's different from prometheus scrape.
@@ -333,8 +288,3 @@ headers = { }
## is set to "prof:true,prof_active:false". The official image adds this env variable. ## is set to "prof:true,prof_active:false". The official image adds this env variable.
## Default is true. ## Default is true.
enable_heap_profiling = true enable_heap_profiling = true
## Configuration options for the event recorder.
[event_recorder]
## TTL for the events table that will be used to store the events. Default is `90d`.
ttl = "90d"

View File

@@ -23,14 +23,6 @@ backend = "etcd_store"
## **Only used when backend is `postgres_store`.** ## **Only used when backend is `postgres_store`.**
meta_table_name = "greptime_metakv" meta_table_name = "greptime_metakv"
## Optional PostgreSQL schema for metadata table and election table name qualification.
## When PostgreSQL public schema is not writable (e.g., PostgreSQL 15+ with restricted public),
## set this to a writable schema. GreptimeDB will use `meta_schema_name`.`meta_table_name`.
## GreptimeDB will NOT create the schema automatically; please ensure it exists or the user has permission.
## **Only used when backend is `postgres_store`.**
meta_schema_name = "greptime_schema"
## Advisory lock id in PostgreSQL for election. Effect when using PostgreSQL as kvbackend ## Advisory lock id in PostgreSQL for election. Effect when using PostgreSQL as kvbackend
## Only used when backend is `postgres_store`. ## Only used when backend is `postgres_store`.
meta_election_lock_id = 1 meta_election_lock_id = 1
@@ -73,8 +65,8 @@ node_max_idle_time = "24hours"
## The number of threads to execute the runtime for global write operations. ## The number of threads to execute the runtime for global write operations.
#+ compact_rt_size = 4 #+ compact_rt_size = 4
## TLS configuration for kv store backend (applicable for etcd, PostgreSQL, and MySQL backends) ## TLS configuration for kv store backend (only applicable for PostgreSQL/MySQL backends)
## When using etcd, PostgreSQL, or MySQL as metadata store, you can configure TLS here ## When using PostgreSQL or MySQL as metadata store, you can configure TLS here
[backend_tls] [backend_tls]
## TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html ## TLS mode, refer to https://www.postgresql.org/docs/current/libpq-ssl.html
## - "disable" - No TLS ## - "disable" - No TLS
@@ -184,69 +176,50 @@ tcp_nodelay = true
# - `kafka`: metasrv **have to be** configured with kafka wal config when using kafka wal provider in datanode. # - `kafka`: metasrv **have to be** configured with kafka wal config when using kafka wal provider in datanode.
provider = "raft_engine" provider = "raft_engine"
# Kafka wal config.
## The broker endpoints of the Kafka cluster. ## The broker endpoints of the Kafka cluster.
##
## **It's only used when the provider is `kafka`**.
broker_endpoints = ["127.0.0.1:9092"] broker_endpoints = ["127.0.0.1:9092"]
## Automatically create topics for WAL. ## Automatically create topics for WAL.
## Set to `true` to automatically create topics for WAL. ## Set to `true` to automatically create topics for WAL.
## Otherwise, use topics named `topic_name_prefix_[0..num_topics)` ## Otherwise, use topics named `topic_name_prefix_[0..num_topics)`
## **It's only used when the provider is `kafka`**.
auto_create_topics = true auto_create_topics = true
## Interval of automatically WAL pruning. ## Interval of automatically WAL pruning.
## Set to `0s` to disable automatically WAL pruning which delete unused remote WAL entries periodically. ## Set to `0s` to disable automatically WAL pruning which delete unused remote WAL entries periodically.
## **It's only used when the provider is `kafka`**. auto_prune_interval = "0s"
auto_prune_interval = "30m"
## The threshold to trigger a flush operation of a region in automatically WAL pruning.
## Estimated size threshold to trigger a flush when using Kafka remote WAL. ## Metasrv will send a flush request to flush the region when:
## Since multiple regions may share a Kafka topic, the estimated size is calculated as: ## `trigger_flush_threshold` + `prunable_entry_id` < `max_prunable_entry_id`
## (latest_entry_id - flushed_entry_id) * avg_record_size ## where:
## MetaSrv triggers a flush for a region when this estimated size exceeds `flush_trigger_size`. ## - `prunable_entry_id` is the maximum entry id that can be pruned of the region.
## - `latest_entry_id`: The latest entry ID in the topic. ## - `max_prunable_entry_id` is the maximum prunable entry id among all regions in the same topic.
## - `flushed_entry_id`: The last flushed entry ID for the region. ## Set to `0` to disable the flush operation.
## Set to "0" to let the system decide the flush trigger size. trigger_flush_threshold = 0
## **It's only used when the provider is `kafka`**.
flush_trigger_size = "512MB"
## Estimated size threshold to trigger a checkpoint when using Kafka remote WAL.
## The estimated size is calculated as:
## (latest_entry_id - last_checkpoint_entry_id) * avg_record_size
## MetaSrv triggers a checkpoint for a region when this estimated size exceeds `checkpoint_trigger_size`.
## Set to "0" to let the system decide the checkpoint trigger size.
## **It's only used when the provider is `kafka`**.
checkpoint_trigger_size = "128MB"
## Concurrent task limit for automatically WAL pruning. ## Concurrent task limit for automatically WAL pruning.
## **It's only used when the provider is `kafka`**.
auto_prune_parallelism = 10 auto_prune_parallelism = 10
## Number of topics used for remote WAL. ## Number of topics.
## **It's only used when the provider is `kafka`**.
num_topics = 64 num_topics = 64
## Topic selector type. ## Topic selector type.
## Available selector types: ## Available selector types:
## - `round_robin` (default) ## - `round_robin` (default)
## **It's only used when the provider is `kafka`**.
selector_type = "round_robin" selector_type = "round_robin"
## A Kafka topic is constructed by concatenating `topic_name_prefix` and `topic_id`. ## A Kafka topic is constructed by concatenating `topic_name_prefix` and `topic_id`.
## Only accepts strings that match the following regular expression pattern: ## Only accepts strings that match the following regular expression pattern:
## [a-zA-Z_:-][a-zA-Z0-9_:\-\.@#]* ## [a-zA-Z_:-][a-zA-Z0-9_:\-\.@#]*
## i.g., greptimedb_wal_topic_0, greptimedb_wal_topic_1. ## i.g., greptimedb_wal_topic_0, greptimedb_wal_topic_1.
## **It's only used when the provider is `kafka`**.
topic_name_prefix = "greptimedb_wal_topic" topic_name_prefix = "greptimedb_wal_topic"
## Expected number of replicas of each partition. ## Expected number of replicas of each partition.
## **It's only used when the provider is `kafka`**.
replication_factor = 1 replication_factor = 1
## The timeout for creating a Kafka topic. ## Above which a topic creation operation will be cancelled.
## **It's only used when the provider is `kafka`**.
create_topic_timeout = "30s" create_topic_timeout = "30s"
# The Kafka SASL configuration. # The Kafka SASL configuration.
@@ -269,20 +242,8 @@ create_topic_timeout = "30s"
## Configuration options for the event recorder. ## Configuration options for the event recorder.
[event_recorder] [event_recorder]
## TTL for the events table that will be used to store the events. Default is `90d`. ## TTL for the events table that will be used to store the events.
ttl = "90d" ttl = "30d"
## Configuration options for the stats persistence.
[stats_persistence]
## TTL for the stats table that will be used to store the stats.
## Set to `0s` to disable stats persistence.
## Default is `0s`.
## If you want to enable stats persistence, set the TTL to a value greater than 0.
## It is recommended to set a small value, e.g., `3h`.
ttl = "0s"
## The interval to persist the stats. Default is `10m`.
## The minimum value is `10m`, if the value is less than `10m`, it will be overridden to `10m`.
interval = "10m"
## The logging options. ## The logging options.
[logging] [logging]
@@ -297,7 +258,7 @@ level = "info"
enable_otlp_tracing = false enable_otlp_tracing = false
## The OTLP tracing endpoint. ## The OTLP tracing endpoint.
otlp_endpoint = "http://localhost:4318/v1/traces" otlp_endpoint = "http://localhost:4318"
## Whether to append logs to stdout. ## Whether to append logs to stdout.
append_stdout = true append_stdout = true
@@ -311,14 +272,6 @@ max_log_files = 720
## The OTLP tracing export protocol. Can be `grpc`/`http`. ## The OTLP tracing export protocol. Can be `grpc`/`http`.
otlp_export_protocol = "http" otlp_export_protocol = "http"
## Additional OTLP headers, only valid when using OTLP http
[logging.otlp_headers]
## @toml2docs:none-default
#Authorization = "Bearer my-token"
## @toml2docs:none-default
#Database = "My database"
## The percentage of tracing will be sampled and exported. ## The percentage of tracing will be sampled and exported.
## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1. ## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.
## ratio > 1 are treated as 1. Fractions < 0 are treated as 0 ## ratio > 1 are treated as 1. Fractions < 0 are treated as 0

View File

@@ -85,8 +85,7 @@ runtime_size = 2
## Server-side keep-alive time. ## Server-side keep-alive time.
## Set to 0 (default) to disable. ## Set to 0 (default) to disable.
keep_alive = "0s" keep_alive = "0s"
## Maximum entries in the MySQL prepared statement cache; default is 10,000.
prepared_stmt_cache_size= 10000
# MySQL server TLS options. # MySQL server TLS options.
[mysql.tls] [mysql.tls]
@@ -567,7 +566,7 @@ sst_write_buffer_size = "8MB"
parallel_scan_channel_size = 32 parallel_scan_channel_size = 32
## Maximum number of SST files to scan concurrently. ## Maximum number of SST files to scan concurrently.
max_concurrent_scan_files = 384 max_concurrent_scan_files = 128
## Whether to allow stale WAL entries read during replay. ## Whether to allow stale WAL entries read during replay.
allow_stale_entries = false allow_stale_entries = false
@@ -724,7 +723,7 @@ level = "info"
enable_otlp_tracing = false enable_otlp_tracing = false
## The OTLP tracing endpoint. ## The OTLP tracing endpoint.
otlp_endpoint = "http://localhost:4318/v1/traces" otlp_endpoint = "http://localhost:4318"
## Whether to append logs to stdout. ## Whether to append logs to stdout.
append_stdout = true append_stdout = true
@@ -738,13 +737,6 @@ max_log_files = 720
## The OTLP tracing export protocol. Can be `grpc`/`http`. ## The OTLP tracing export protocol. Can be `grpc`/`http`.
otlp_export_protocol = "http" otlp_export_protocol = "http"
## Additional OTLP headers, only valid when using OTLP http
[logging.otlp_headers]
## @toml2docs:none-default
#Authorization = "Bearer my-token"
## @toml2docs:none-default
#Database = "My database"
## The percentage of tracing will be sampled and exported. ## The percentage of tracing will be sampled and exported.
## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1. ## Valid range `[0, 1]`, 1 means all traces are sampled, 0 means all traces are not sampled, the default value is 1.
## ratio > 1 are treated as 1. Fractions < 0 are treated as 0 ## ratio > 1 are treated as 1. Fractions < 0 are treated as 0

View File

@@ -13,8 +13,7 @@ RUN apt-get update && apt-get install -y \
git \ git \
unzip \ unzip \
build-essential \ build-essential \
pkg-config \ pkg-config
openssh-client
# Install protoc # Install protoc
ARG PROTOBUF_VERSION=29.3 ARG PROTOBUF_VERSION=29.3

View File

@@ -19,7 +19,7 @@ ARG PROTOBUF_VERSION=29.3
RUN curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip && \ RUN curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOBUF_VERSION}/protoc-${PROTOBUF_VERSION}-linux-x86_64.zip && \
unzip protoc-${PROTOBUF_VERSION}-linux-x86_64.zip -d protoc3; unzip protoc-${PROTOBUF_VERSION}-linux-x86_64.zip -d protoc3;
RUN mv protoc3/bin/* /usr/local/bin/ RUN mv protoc3/bin/* /usr/local/bin/
RUN mv protoc3/include/* /usr/local/include/ RUN mv protoc3/include/* /usr/local/include/

View File

@@ -34,48 +34,6 @@ services:
networks: networks:
- greptimedb - greptimedb
etcd-tls:
<<: *etcd_common_settings
container_name: etcd-tls
ports:
- 2378:2378
- 2381:2381
command:
- --name=etcd-tls
- --data-dir=/var/lib/etcd
- --initial-advertise-peer-urls=https://etcd-tls:2381
- --listen-peer-urls=https://0.0.0.0:2381
- --listen-client-urls=https://0.0.0.0:2378
- --advertise-client-urls=https://etcd-tls:2378
- --heartbeat-interval=250
- --election-timeout=1250
- --initial-cluster=etcd-tls=https://etcd-tls:2381
- --initial-cluster-state=new
- --initial-cluster-token=etcd-tls-cluster
- --cert-file=/certs/server.crt
- --key-file=/certs/server-key.pem
- --peer-cert-file=/certs/server.crt
- --peer-key-file=/certs/server-key.pem
- --trusted-ca-file=/certs/ca.crt
- --peer-trusted-ca-file=/certs/ca.crt
- --client-cert-auth
- --peer-client-cert-auth
volumes:
- ./greptimedb-cluster-docker-compose/etcd-tls:/var/lib/etcd
- ./greptimedb-cluster-docker-compose/certs:/certs:ro
environment:
- ETCDCTL_API=3
- ETCDCTL_CACERT=/certs/ca.crt
- ETCDCTL_CERT=/certs/server.crt
- ETCDCTL_KEY=/certs/server-key.pem
healthcheck:
test: [ "CMD", "etcdctl", "--endpoints=https://etcd-tls:2378", "--cacert=/certs/ca.crt", "--cert=/certs/server.crt", "--key=/certs/server-key.pem", "endpoint", "health" ]
interval: 10s
timeout: 5s
retries: 5
networks:
- greptimedb
metasrv: metasrv:
image: *greptimedb_image image: *greptimedb_image
container_name: metasrv container_name: metasrv

View File

@@ -0,0 +1,72 @@
Currently, our query engine is based on DataFusion, so all aggregate function is executed by DataFusion, through its UDAF interface. You can find DataFusion's UDAF example [here](https://github.com/apache/datafusion/tree/main/datafusion-examples/examples/simple_udaf.rs). Basically, we provide the same way as DataFusion to write aggregate functions: both are centered in a struct called "Accumulator" to accumulates states along the way in aggregation.
However, DataFusion's UDAF implementation has a huge restriction, that it requires user to provide a concrete "Accumulator". Take `Median` aggregate function for example, to aggregate a `u32` datatype column, you have to write a `MedianU32`, and use `SELECT MEDIANU32(x)` in SQL. `MedianU32` cannot be used to aggregate a `i32` datatype column. Or, there's another way: you can use a special type that can hold all kinds of data (like our `Value` enum or Arrow's `ScalarValue`), and `match` all the way up to do aggregate calculations. It might work, though rather tedious. (But I think it's DataFusion's preferred way to write UDAF.)
So is there a way we can make an aggregate function that automatically match the input data's type? For example, a `Median` aggregator that can work on both `u32` column and `i32`? The answer is yes until we find a way to bypass DataFusion's restriction, a restriction that DataFusion simply doesn't pass the input data's type when creating an Accumulator.
> There's an example in `my_sum_udaf_example.rs`, take that as quick start.
# 1. Impl `AggregateFunctionCreator` trait for your accumulator creator.
You must first define a struct that will be used to create your accumulator. For example,
```Rust
#[as_aggr_func_creator]
#[derive(Debug, AggrFuncTypeStore)]
struct MySumAccumulatorCreator {}
```
Attribute macro `#[as_aggr_func_creator]` and derive macro `#[derive(Debug, AggrFuncTypeStore)]` must both be annotated on the struct. They work together to provide a storage of aggregate function's input data types, which are needed for creating generic accumulator later.
> Note that the `as_aggr_func_creator` macro will add fields to the struct, so the struct cannot be defined as an empty struct without field like `struct Foo;`, neither as a new type like `struct Foo(bar)`.
Then impl `AggregateFunctionCreator` trait on it. The definition of the trait is:
```Rust
pub trait AggregateFunctionCreator: Send + Sync + Debug {
fn creator(&self) -> AccumulatorCreatorFunction;
fn output_type(&self) -> ConcreteDataType;
fn state_types(&self) -> Vec<ConcreteDataType>;
}
```
You can use input data's type in methods that return output type and state types (just invoke `input_types()`).
The output type is aggregate function's output data's type. For example, `SUM` aggregate function's output type is `u64` for a `u32` datatype column. The state types are accumulator's internal states' types. Take `AVG` aggregate function on a `i32` column as example, its state types are `i64` (for sum) and `u64` (for count).
The `creator` function is where you define how an accumulator (that will be used in DataFusion) is created. You define "how" to create the accumulator (instead of "what" to create), using the input data's type as arguments. With input datatype known, you can create accumulator generically.
# 2. Impl `Accumulator` trait for your accumulator.
The accumulator is where you store the aggregate calculation states and evaluate a result. You must impl `Accumulator` trait for it. The trait's definition is:
```Rust
pub trait Accumulator: Send + Sync + Debug {
fn state(&self) -> Result<Vec<Value>>;
fn update_batch(&mut self, values: &[VectorRef]) -> Result<()>;
fn merge_batch(&mut self, states: &[VectorRef]) -> Result<()>;
fn evaluate(&self) -> Result<Value>;
}
```
The DataFusion basically executes aggregate like this:
1. Partitioning all input data for aggregate. Create an accumulator for each part.
2. Call `update_batch` on each accumulator with partitioned data, to let you update your aggregate calculation.
3. Call `state` to get each accumulator's internal state, the medial calculation result.
4. Call `merge_batch` to merge all accumulator's internal state to one.
5. Execute `evaluate` on the chosen one to get the final calculation result.
Once you know the meaning of each method, you can easily write your accumulator. You can refer to `Median` accumulator or `SUM` accumulator defined in file `my_sum_udaf_example.rs` for more details.
# 3. Register your aggregate function to our query engine.
You can call `register_aggregate_function` method in query engine to register your aggregate function. To do that, you have to new an instance of struct `AggregateFunctionMeta`. The struct has three fields, first is the name of your aggregate function's name. The function name is case-sensitive due to DataFusion's restriction. We strongly recommend using lowercase for your name. If you have to use uppercase name, wrap your aggregate function with quotation marks. For example, if you define an aggregate function named "my_aggr", you can use "`SELECT MY_AGGR(x)`"; if you define "my_AGGR", you have to use "`SELECT "my_AGGR"(x)`".
The second field is arg_counts ,the count of the arguments. Like accumulator `percentile`, calculating the p_number of the column. We need to input the value of column and the value of p to calculate, and so the count of the arguments is two.
The third field is a function about how to create your accumulator creator that you defined in step 1 above. Create creator, that's a bit intertwined, but it is how we make DataFusion use a newly created aggregate function each time it executes a SQL, preventing the stored input types from affecting each other. The key detail can be starting looking at our `DfContextProviderAdapter` struct's `get_aggregate_meta` method.
# (Optional) 4. Make your aggregate function automatically registered.
If you've written a great aggregate function that wants to let everyone use it, you can make it automatically register to our query engine at start time. It's quick and simple, just refer to the `AggregateFunctions::register` function in `common/function/src/scalars/aggregate/mod.rs`.

View File

@@ -1,112 +0,0 @@
---
Feature Name: Async Index Build
Tracking Issue: https://github.com/GreptimeTeam/greptimedb/issues/6756
Date: 2025-08-16
Author: "SNC123 <sinhco@outlook.com>"
---
# Summary
This RFC proposes an asynchronous index build mechanism in the database, with a configuration option to choose between synchronous and asynchronous modes, aiming to improve flexibility and adapt to different workload requirements.
# Motivation
Currently, index creation is performed synchronously, which may lead to prolonged write suspension and impact business continuity. As data volume grows, the time required for index building increases significantly. An asynchronous solution is urgently needed to enhance user experience and system throughput.
# Details
## Overview
The following table highlights the difference between async and sync index approach:
| Approach | Trigger | Data Source | Additional Index Metadata Installation | Fine-grained `FileMeta` Index |
| :--- | :--- | :--- | :--- | :--- |
| Sync Index | On `write_sst` | Memory (on flush) / Disk (on compact) | Not required(already installed synchronously) | Not required |
| Async Index | 4 trigger types | Disk | Required | Required |
The index build mode (synchronous or asynchronous) can be selected via configuration file.
### Four Trigger Types
This RFC introduces four `IndexBuildType`s to trigger index building:
- **Manual Rebuild**: Triggered by the user via `ADMIN build_index("table_name")`, for scenarios like recovering from failed builds or migrating data. SST files whose `ColumnIndexMetadata` (see below) is already consistent with the `RegionMetadata` will be skipped.
- **Schema Change**: Automatically triggered when the schema of an indexed column is altered.
- **Flush**: Automatically builds indexes for new SST files created by a flush.
- **Compact**: Automatically builds indexes for new SST files created by a compaction.
### Additional Index Metadata Installation
Previously, index information in the in-memory `FileMeta` was updated synchronously. The async approach requires an explicit installation step.
A race condition can occur when compaction and index building run concurrently, leading to:
1. Building an index for a file that is about to be deleted by compaction.
2. Creating an unnecessary index file and an incorrect manifest record.
3. On restart, replaying the manifest could load metadata for a non-existent file.
To prevent this, the system checks if a file's `FileMeta` is in a `compacting` state before updating the manifest. If it is, the installation is aborted.
### Fine-grained `FileMeta` Index
The original `FileMeta` only stored file-level index information. However, manual rebuilds require column-level details to identify files inconsistent with the current DDL. Therefore, the `indexes` field in `FileMeta` is updated as follows:
```rust
struct FileMeta {
...
// From file-level:
// available_indexes: SmallVec<[IndexType; 4]>
// To column-level:
indexes: Vec<ColumnIndexMetadata>,
...
}
pub struct ColumnIndexMetadata {
pub column_id: ColumnId,
pub created_indexes: IndexTypes,
}
```
## Process
The index building process is similar to a flush and is illustrated below:
```mermaid
sequenceDiagram
Region0->>Region0: Triggered by one of 4 conditions, targets specific files
loop For each target file
Region0->>IndexBuildScheduler: Submits an index build task
end
IndexBuildScheduler->>IndexBuildTask: Executes the task
IndexBuildTask->>Storage Interfaces: Reads SST data from disk
IndexBuildTask->>IndexBuildTask: Builds the index file
alt Index file size > 0
IndexBuildTask->>Region0: Sends IndexBuildFinished notification
end
alt File exists in Version and is not compacting
Region0->>Storage Interfaces: Updates manifest and Version
end
```
### Task Triggering and Scheduling
The process starts with one of the four `IndexBuildType` triggers. In `handle_rebuild_index`, the `RegionWorkerLoop` identifies target SSTs from the request or the current region version. It then creates an `IndexBuildTask` for each file and submits it to the `index_build_scheduler`.
Similar to Flush and Compact operations, index build tasks are ultimately dispatched to the LocalScheduler. Resource usage can be adjusted via configuration files. Since asynchronous index tasks are both memory-intensive and IO-intensive but have lower priority, it is recommended to allocate fewer resources to them compared to compaction and flush tasks—for example, limiting them to 1/8 of the CPU cores.
### Index Building and Notification
The scheduled `IndexBuildTask` executes its `index_build` method. It uses an `indexer_builder` to create an `Indexer` that reads SST data and builds the index. If a new index file is created (`IndexOutput.file_size > 0`), the task sends an `IndexBuildFinished` notification back to the `RegionWorkerLoop`.
### Index Metadata Installation
Upon receiving the `IndexBuildFinished` notification in `handle_index_build_finished`, the `RegionWorkerLoop` verifies that the file still exists in the current `version` and is not being compacted. If the check passes, it calls `manifest_ctx.update_manifest` to apply a `RegionEdit` with the new index information, completing the installation.
# Drawbacks
Asynchronous index building may consume extra system resources, potentially affecting overall performance during peak periods.
There may be a delay before the new index becomes available for queries, which could impact certain use cases.
# Unresolved Questions and Future Work
**Resource Management and Throttling**: The resource consumption (CPU, I/O) of background index building can be managed and limited to some extent by configuring a dedicated background thread pool. However, this approach cannot fully eliminate resource contention, especially under heavy workloads or when I/O is highly competitive. Additional throttling mechanisms or dynamic prioritization may still be necessary to avoid impacting foreground operations.
# Alternatives
Instead of being triggered by events like Flush or Compact, index building could be performed in batches during scheduled maintenance windows. This offers predictable resource usage but delays index availability.

View File

@@ -15,6 +15,8 @@
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
buildInputs = with pkgs; [ buildInputs = with pkgs; [
libgit2
libz
]; ];
lib = nixpkgs.lib; lib = nixpkgs.lib;
rustToolchain = fenix.packages.${system}.fromToolchainName { rustToolchain = fenix.packages.${system}.fromToolchainName {

File diff suppressed because it is too large Load Diff

View File

@@ -87,13 +87,6 @@
| Other Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation!~"read\|write\|list\|Writer::write\|Writer::close\|Reader::read"}[$__rate_interval])))` | `timeseries` | Other Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` | | Other Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{instance=~"$datanode", operation!~"read\|write\|list\|Writer::write\|Writer::close\|Reader::read"}[$__rate_interval])))` | `timeseries` | Other Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` | | Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{instance=~"$datanode"}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{instance=~"$datanode", error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` | | OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{instance=~"$datanode", error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` |
# Remote WAL
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Triggered region flush total | `meta_triggered_region_flush_total` | `timeseries` | Triggered region flush total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Triggered region checkpoint total | `meta_triggered_region_checkpoint_total` | `timeseries` | Triggered region checkpoint total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Topic estimated replay size | `meta_topic_estimated_replay_size` | `timeseries` | Topic estimated max replay size | `prometheus` | `bytes` | `{{pod}}-{{topic_name}}` |
| Kafka logstore's bytes traffic | `rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])` | `timeseries` | Kafka logstore's bytes traffic | `prometheus` | `bytes` | `{{pod}}-{{logstore}}` |
# Metasrv # Metasrv
| Title | Query | Type | Description | Datasource | Unit | Legend Format | | Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
@@ -110,8 +103,6 @@
| Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (greptime_meta_kv_request_elapsed_bucket))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}-{{op}} p99` | | Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (greptime_meta_kv_request_elapsed_bucket))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}-{{op}} p99` |
| Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `none` | `{{pod}}-{{op}} p99` | | Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `none` | `{{pod}}-{{op}} p99` |
| DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_tables_bucket))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_view))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_flow))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_drop_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_alter_table))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` | | DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_tables_bucket))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_view))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_flow))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_drop_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_alter_table))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` |
| Reconciliation stats | `greptime_meta_reconciliation_stats` | `timeseries` | Reconciliation stats | `prometheus` | `s` | `{{pod}}-{{table_type}}-{{type}}` |
| Reconciliation steps | `histogram_quantile(0.9, greptime_meta_reconciliation_procedure_bucket)` | `timeseries` | Elapsed of Reconciliation steps | `prometheus` | `s` | `{{procedure_name}}-{{step}}-P90` |
# Flownode # Flownode
| Title | Query | Type | Description | Datasource | Unit | Legend Format | | Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |

View File

@@ -802,48 +802,6 @@ groups:
type: prometheus type: prometheus
uid: ${metrics} uid: ${metrics}
legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]' legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]'
- title: Remote WAL
panels:
- title: Triggered region flush total
type: timeseries
description: Triggered region flush total
unit: none
queries:
- expr: meta_triggered_region_flush_total
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{topic_name}}'
- title: Triggered region checkpoint total
type: timeseries
description: Triggered region checkpoint total
unit: none
queries:
- expr: meta_triggered_region_checkpoint_total
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{topic_name}}'
- title: Topic estimated replay size
type: timeseries
description: Topic estimated max replay size
unit: bytes
queries:
- expr: meta_topic_estimated_replay_size
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{topic_name}}'
- title: Kafka logstore's bytes traffic
type: timeseries
description: Kafka logstore's bytes traffic
unit: bytes
queries:
- expr: rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{logstore}}'
- title: Metasrv - title: Metasrv
panels: panels:
- title: Region migration datanode - title: Region migration datanode
@@ -990,26 +948,6 @@ groups:
type: prometheus type: prometheus
uid: ${metrics} uid: ${metrics}
legendFormat: AlterTable-{{step}} p90 legendFormat: AlterTable-{{step}} p90
- title: Reconciliation stats
type: timeseries
description: Reconciliation stats
unit: s
queries:
- expr: greptime_meta_reconciliation_stats
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{table_type}}-{{type}}'
- title: Reconciliation steps
type: timeseries
description: 'Elapsed of Reconciliation steps '
unit: s
queries:
- expr: histogram_quantile(0.9, greptime_meta_reconciliation_procedure_bucket)
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{procedure_name}}-{{step}}-P90'
- title: Flownode - title: Flownode
panels: panels:
- title: Flow Ingest / Output Rate - title: Flow Ingest / Output Rate

File diff suppressed because it is too large Load Diff

View File

@@ -87,13 +87,6 @@
| Other Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{ operation!~"read\|write\|list\|Writer::write\|Writer::close\|Reader::read"}[$__rate_interval])))` | `timeseries` | Other Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` | | Other Request P99 per Instance | `histogram_quantile(0.99, sum by(instance, pod, le, scheme, operation) (rate(opendal_operation_duration_seconds_bucket{ operation!~"read\|write\|list\|Writer::write\|Writer::close\|Reader::read"}[$__rate_interval])))` | `timeseries` | Other Request P99 per Instance. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` | | Opendal traffic | `sum by(instance, pod, scheme, operation) (rate(opendal_operation_bytes_sum{}[$__rate_interval]))` | `timeseries` | Total traffic as in bytes by instance and operation | `prometheus` | `decbytes` | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]` |
| OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{ error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` | | OpenDAL errors per Instance | `sum by(instance, pod, scheme, operation, error) (rate(opendal_operation_errors_total{ error!="NotFound"}[$__rate_interval]))` | `timeseries` | OpenDAL error counts per Instance. | `prometheus` | -- | `[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]` |
# Remote WAL
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- |
| Triggered region flush total | `meta_triggered_region_flush_total` | `timeseries` | Triggered region flush total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Triggered region checkpoint total | `meta_triggered_region_checkpoint_total` | `timeseries` | Triggered region checkpoint total | `prometheus` | `none` | `{{pod}}-{{topic_name}}` |
| Topic estimated replay size | `meta_topic_estimated_replay_size` | `timeseries` | Topic estimated max replay size | `prometheus` | `bytes` | `{{pod}}-{{topic_name}}` |
| Kafka logstore's bytes traffic | `rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])` | `timeseries` | Kafka logstore's bytes traffic | `prometheus` | `bytes` | `{{pod}}-{{logstore}}` |
# Metasrv # Metasrv
| Title | Query | Type | Description | Datasource | Unit | Legend Format | | Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
@@ -110,8 +103,6 @@
| Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (greptime_meta_kv_request_elapsed_bucket))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}-{{op}} p99` | | Meta KV Ops Latency | `histogram_quantile(0.99, sum by(pod, le, op, target) (greptime_meta_kv_request_elapsed_bucket))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `{{pod}}-{{op}} p99` |
| Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `none` | `{{pod}}-{{op}} p99` | | Rate of meta KV Ops | `rate(greptime_meta_kv_request_elapsed_count[$__rate_interval])` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `none` | `{{pod}}-{{op}} p99` |
| DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_tables_bucket))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_view))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_flow))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_drop_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_alter_table))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` | | DDL Latency | `histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_tables_bucket))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_view))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_create_flow))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_drop_table))`<br/>`histogram_quantile(0.9, sum by(le, pod, step) (greptime_meta_procedure_alter_table))` | `timeseries` | Gauge of load information of each datanode, collected via heartbeat between datanode and metasrv. This information is for metasrv to schedule workloads. | `prometheus` | `s` | `CreateLogicalTables-{{step}} p90` |
| Reconciliation stats | `greptime_meta_reconciliation_stats` | `timeseries` | Reconciliation stats | `prometheus` | `s` | `{{pod}}-{{table_type}}-{{type}}` |
| Reconciliation steps | `histogram_quantile(0.9, greptime_meta_reconciliation_procedure_bucket)` | `timeseries` | Elapsed of Reconciliation steps | `prometheus` | `s` | `{{procedure_name}}-{{step}}-P90` |
# Flownode # Flownode
| Title | Query | Type | Description | Datasource | Unit | Legend Format | | Title | Query | Type | Description | Datasource | Unit | Legend Format |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |

View File

@@ -802,48 +802,6 @@ groups:
type: prometheus type: prometheus
uid: ${metrics} uid: ${metrics}
legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]' legendFormat: '[{{instance}}]-[{{pod}}]-[{{scheme}}]-[{{operation}}]-[{{error}}]'
- title: Remote WAL
panels:
- title: Triggered region flush total
type: timeseries
description: Triggered region flush total
unit: none
queries:
- expr: meta_triggered_region_flush_total
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{topic_name}}'
- title: Triggered region checkpoint total
type: timeseries
description: Triggered region checkpoint total
unit: none
queries:
- expr: meta_triggered_region_checkpoint_total
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{topic_name}}'
- title: Topic estimated replay size
type: timeseries
description: Topic estimated max replay size
unit: bytes
queries:
- expr: meta_topic_estimated_replay_size
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{topic_name}}'
- title: Kafka logstore's bytes traffic
type: timeseries
description: Kafka logstore's bytes traffic
unit: bytes
queries:
- expr: rate(greptime_logstore_kafka_client_bytes_total[$__rate_interval])
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{logstore}}'
- title: Metasrv - title: Metasrv
panels: panels:
- title: Region migration datanode - title: Region migration datanode
@@ -990,26 +948,6 @@ groups:
type: prometheus type: prometheus
uid: ${metrics} uid: ${metrics}
legendFormat: AlterTable-{{step}} p90 legendFormat: AlterTable-{{step}} p90
- title: Reconciliation stats
type: timeseries
description: Reconciliation stats
unit: s
queries:
- expr: greptime_meta_reconciliation_stats
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{pod}}-{{table_type}}-{{type}}'
- title: Reconciliation steps
type: timeseries
description: 'Elapsed of Reconciliation steps '
unit: s
queries:
- expr: histogram_quantile(0.9, greptime_meta_reconciliation_procedure_bucket)
datasource:
type: prometheus
uid: ${metrics}
legendFormat: '{{procedure_name}}-{{step}}-P90'
- title: Flownode - title: Flownode
panels: panels:
- title: Flow Ingest / Output Rate - title: Flow Ingest / Output Rate

View File

@@ -1,265 +0,0 @@
# Copyright 2023 Greptime Team
#
# 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 json
import os
import re
import sys
def load_udeps_report(report_path):
try:
with open(report_path, "r") as f:
return json.load(f)
except FileNotFoundError:
print(f"Error: Report file '{report_path}' not found.")
return None
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in report file: {e}")
return None
def extract_unused_dependencies(report):
"""
Extract and organize unused dependencies from the cargo-udeps JSON report.
The cargo-udeps report has this structure:
{
"unused_deps": {
"package_name v0.1.0 (/path/to/package)": {
"normal": ["dep1", "dep2"],
"development": ["dev_dep1"],
"build": ["build_dep1"],
"manifest_path": "/path/to/Cargo.toml"
}
}
}
Args:
report (dict): The parsed JSON report from cargo-udeps
Returns:
dict: Organized unused dependencies by package name:
{
"package_name": {
"dependencies": [("dep1", "normal"), ("dev_dep1", "dev")],
"manifest_path": "/path/to/Cargo.toml"
}
}
"""
if not report or "unused_deps" not in report:
return {}
unused_deps = {}
for package_full_name, deps_info in report["unused_deps"].items():
package_name = package_full_name.split(" ")[0]
all_unused = []
if deps_info.get("normal"):
all_unused.extend([(dep, "normal") for dep in deps_info["normal"]])
if deps_info.get("development"):
all_unused.extend([(dep, "dev") for dep in deps_info["development"]])
if deps_info.get("build"):
all_unused.extend([(dep, "build") for dep in deps_info["build"]])
if all_unused:
unused_deps[package_name] = {
"dependencies": all_unused,
"manifest_path": deps_info.get("manifest_path", "unknown"),
}
return unused_deps
def get_section_pattern(dep_type):
"""
Get regex patterns to identify different dependency sections in Cargo.toml.
Args:
dep_type (str): Type of dependency ("normal", "dev", or "build")
Returns:
list: List of regex patterns to match the appropriate section headers
"""
patterns = {
"normal": [r"\[dependencies\]", r"\[dependencies\..*?\]"],
"dev": [r"\[dev-dependencies\]", r"\[dev-dependencies\..*?\]"],
"build": [r"\[build-dependencies\]", r"\[build-dependencies\..*?\]"],
}
return patterns.get(dep_type, [])
def remove_dependency_line(content, dep_name, section_start, section_end):
"""
Remove a dependency line from a specific section of a Cargo.toml file.
Args:
content (str): The entire content of the Cargo.toml file
dep_name (str): Name of the dependency to remove (e.g., "serde", "tokio")
section_start (int): Starting position of the section in the content
section_end (int): Ending position of the section in the content
Returns:
tuple: (new_content, removed) where:
- new_content (str): The modified content with dependency removed
- removed (bool): True if dependency was found and removed, False otherwise
Example input content format:
content = '''
[package]
name = "my-crate"
version = "0.1.0"
[dependencies]
serde = "1.0"
tokio = { version = "1.0", features = ["full"] }
serde_json.workspace = true
[dev-dependencies]
tempfile = "3.0"
'''
# If dep_name = "serde", section_start = start of [dependencies],
# section_end = start of [dev-dependencies], this function will:
# 1. Extract the section: "serde = "1.0"\ntokio = { version = "1.0", features = ["full"] }\nserde_json.workspace = true\n"
# 2. Find and remove the line: "serde = "1.0""
# 3. Return the modified content with that line removed
"""
section_content = content[section_start:section_end]
dep_patterns = [
rf"^{re.escape(dep_name)}\s*=.*$", # e.g., "serde = "1.0""
rf"^{re.escape(dep_name)}\.workspace\s*=.*$", # e.g., "serde_json.workspace = true"
]
for pattern in dep_patterns:
match = re.search(pattern, section_content, re.MULTILINE)
if match:
line_start = section_start + match.start() # Start of the matched line
line_end = section_start + match.end() # End of the matched line
if line_end < len(content) and content[line_end] == "\n":
line_end += 1
return content[:line_start] + content[line_end:], True
return content, False
def remove_dependency_from_toml(file_path, dep_name, dep_type):
"""
Remove a specific dependency from a Cargo.toml file.
Args:
file_path (str): Path to the Cargo.toml file
dep_name (str): Name of the dependency to remove
dep_type (str): Type of dependency ("normal", "dev", or "build")
Returns:
bool: True if dependency was successfully removed, False otherwise
"""
try:
with open(file_path, "r") as f:
content = f.read()
section_patterns = get_section_pattern(dep_type)
if not section_patterns:
return False
for pattern in section_patterns:
section_match = re.search(pattern, content, re.IGNORECASE)
if not section_match:
continue
section_start = section_match.end()
next_section = re.search(r"\n\s*\[", content[section_start:])
section_end = (
section_start + next_section.start() if next_section else len(content)
)
new_content, removed = remove_dependency_line(
content, dep_name, section_start, section_end
)
if removed:
with open(file_path, "w") as f:
f.write(new_content)
return True
return False
except Exception as e:
print(f"Error processing {file_path}: {e}")
return False
def process_unused_dependencies(unused_deps):
"""
Process and remove all unused dependencies from their respective Cargo.toml files.
Args:
unused_deps (dict): Dictionary of unused dependencies organized by package:
{
"package_name": {
"dependencies": [("dep1", "normal"), ("dev_dep1", "dev")],
"manifest_path": "/path/to/Cargo.toml"
}
}
"""
if not unused_deps:
print("No unused dependencies found.")
return
total_removed = 0
total_failed = 0
for package, info in unused_deps.items():
deps = info["dependencies"]
manifest_path = info["manifest_path"]
if not os.path.exists(manifest_path):
print(f"Manifest file not found: {manifest_path}")
total_failed += len(deps)
continue
for dep, dep_type in deps:
if remove_dependency_from_toml(manifest_path, dep, dep_type):
print(f"Removed {dep} from {package}")
total_removed += 1
else:
print(f"Failed to remove {dep} from {package}")
total_failed += 1
print(f"Removed {total_removed} dependencies")
if total_failed > 0:
print(f"Failed to remove {total_failed} dependencies")
def main():
if len(sys.argv) > 1:
report_path = sys.argv[1]
else:
report_path = "udeps-report.json"
report = load_udeps_report(report_path)
if report is None:
sys.exit(1)
unused_deps = extract_unused_dependencies(report)
process_unused_dependencies(unused_deps)
if __name__ == "__main__":
main()

View File

@@ -1,71 +0,0 @@
#!/bin/bash
# Generate TLS certificates for etcd testing
# This script creates certificates for TLS-enabled etcd in testing environments
set -euo pipefail
CERT_DIR="${1:-$(dirname "$0")/../tests-integration/fixtures/etcd-tls-certs}"
DAYS="${2:-365}"
echo "Generating TLS certificates for etcd in ${CERT_DIR}..."
mkdir -p "${CERT_DIR}"
cd "${CERT_DIR}"
echo "Generating CA private key..."
openssl genrsa -out ca-key.pem 2048
echo "Generating CA certificate..."
openssl req -new -x509 -key ca-key.pem -out ca.crt -days "${DAYS}" \
-subj "/C=US/ST=CA/L=SF/O=Greptime/CN=etcd-ca"
# Create server certificate config with Subject Alternative Names
echo "Creating server certificate configuration..."
cat > server.conf << 'EOF'
[req]
distinguished_name = req
[v3_req]
basicConstraints = CA:FALSE
keyUsage = keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = etcd-tls
DNS.3 = 127.0.0.1
IP.1 = 127.0.0.1
IP.2 = ::1
EOF
echo "Generating server private key..."
openssl genrsa -out server-key.pem 2048
echo "Generating server certificate signing request..."
openssl req -new -key server-key.pem -out server.csr \
-subj "/CN=etcd-tls"
echo "Generating server certificate..."
openssl x509 -req -in server.csr -CA ca.crt \
-CAkey ca-key.pem -CAcreateserial -out server.crt \
-days "${DAYS}" -extensions v3_req -extfile server.conf
echo "Generating client private key..."
openssl genrsa -out client-key.pem 2048
echo "Generating client certificate signing request..."
openssl req -new -key client-key.pem -out client.csr \
-subj "/CN=etcd-client"
echo "Generating client certificate..."
openssl x509 -req -in client.csr -CA ca.crt \
-CAkey ca-key.pem -CAcreateserial -out client.crt \
-days "${DAYS}"
echo "Setting proper file permissions..."
chmod 644 ca.crt server.crt client.crt
chmod 600 ca-key.pem server-key.pem client-key.pem
# Clean up intermediate files
rm -f server.csr client.csr server.conf
echo "TLS certificates generated successfully in ${CERT_DIR}"

View File

@@ -19,3 +19,6 @@ paste.workspace = true
prost.workspace = true prost.workspace = true
serde_json.workspace = true serde_json.workspace = true
snafu.workspace = true snafu.workspace = true
[build-dependencies]
tonic-build = "0.11"

View File

@@ -14,8 +14,6 @@
pub mod column_def; pub mod column_def;
pub mod helper;
pub mod meta { pub mod meta {
pub use greptime_proto::v1::meta::*; pub use greptime_proto::v1::meta::*;
} }

View File

@@ -1,65 +0,0 @@
// Copyright 2023 Greptime Team
//
// 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 greptime_proto::v1::value::ValueData;
use greptime_proto::v1::{ColumnDataType, ColumnSchema, Row, SemanticType, Value};
/// Create a time index [ColumnSchema] with column's name and datatype.
/// Other fields are left default.
/// Useful when you just want to create a simple [ColumnSchema] without providing much struct fields.
pub fn time_index_column_schema(name: &str, datatype: ColumnDataType) -> ColumnSchema {
ColumnSchema {
column_name: name.to_string(),
datatype: datatype as i32,
semantic_type: SemanticType::Timestamp as i32,
..Default::default()
}
}
/// Create a tag [ColumnSchema] with column's name and datatype.
/// Other fields are left default.
/// Useful when you just want to create a simple [ColumnSchema] without providing much struct fields.
pub fn tag_column_schema(name: &str, datatype: ColumnDataType) -> ColumnSchema {
ColumnSchema {
column_name: name.to_string(),
datatype: datatype as i32,
semantic_type: SemanticType::Tag as i32,
..Default::default()
}
}
/// Create a field [ColumnSchema] with column's name and datatype.
/// Other fields are left default.
/// Useful when you just want to create a simple [ColumnSchema] without providing much struct fields.
pub fn field_column_schema(name: &str, datatype: ColumnDataType) -> ColumnSchema {
ColumnSchema {
column_name: name.to_string(),
datatype: datatype as i32,
semantic_type: SemanticType::Field as i32,
..Default::default()
}
}
/// Create a [Row] from [ValueData]s.
/// Useful when you don't want to write much verbose codes.
pub fn row(values: Vec<ValueData>) -> Row {
Row {
values: values
.into_iter()
.map(|x| Value {
value_data: Some(x),
})
.collect::<Vec<_>>(),
}
}

View File

@@ -32,7 +32,6 @@ pub enum PermissionReq<'a> {
PromStoreRead, PromStoreRead,
Otlp, Otlp,
LogWrite, LogWrite,
BulkInsert,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@@ -21,7 +21,6 @@ bytes.workspace = true
common-base.workspace = true common-base.workspace = true
common-catalog.workspace = true common-catalog.workspace = true
common-error.workspace = true common-error.workspace = true
common-event-recorder.workspace = true
common-frontend.workspace = true common-frontend.workspace = true
common-macro.workspace = true common-macro.workspace = true
common-meta.workspace = true common-meta.workspace = true

View File

@@ -44,7 +44,6 @@ use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
use table::dist_table::DistTable; use table::dist_table::DistTable;
use table::metadata::{TableId, TableInfoRef}; use table::metadata::{TableId, TableInfoRef};
use table::table::numbers::{NumbersTable, NUMBERS_TABLE_NAME}; use table::table::numbers::{NumbersTable, NUMBERS_TABLE_NAME};
use table::table::PartitionRules;
use table::table_name::TableName; use table::table_name::TableName;
use table::TableRef; use table::TableRef;
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
@@ -133,8 +132,6 @@ impl KvBackendCatalogManager {
{ {
let mut new_table_info = (*table.table_info()).clone(); let mut new_table_info = (*table.table_info()).clone();
let mut phy_part_cols_not_in_logical_table = vec![];
// Remap partition key indices from physical table to logical table // Remap partition key indices from physical table to logical table
new_table_info.meta.partition_key_indices = physical_table_info_value new_table_info.meta.partition_key_indices = physical_table_info_value
.table_info .table_info
@@ -151,30 +148,15 @@ impl KvBackendCatalogManager {
.get(physical_index) .get(physical_index)
.and_then(|physical_column| { .and_then(|physical_column| {
// Find the corresponding index in the logical table schema // Find the corresponding index in the logical table schema
let idx = new_table_info new_table_info
.meta .meta
.schema .schema
.column_index_by_name(physical_column.name.as_str()); .column_index_by_name(physical_column.name.as_str())
if idx.is_none() {
// not all part columns in physical table that are also in logical table
phy_part_cols_not_in_logical_table
.push(physical_column.name.clone());
}
idx
}) })
}) })
.collect(); .collect();
let partition_rules = if !phy_part_cols_not_in_logical_table.is_empty() { let new_table = DistTable::table(Arc::new(new_table_info));
Some(PartitionRules {
extra_phy_cols_not_in_logical_table: phy_part_cols_not_in_logical_table,
})
} else {
None
};
let new_table = DistTable::table_partitioned(Arc::new(new_table_info), partition_rules);
return Ok(new_table); return Ok(new_table);
} }

View File

@@ -38,7 +38,7 @@ use crate::{CatalogManager, DeregisterTableRequest, RegisterSchemaRequest, Regis
type SchemaEntries = HashMap<String, HashMap<String, TableRef>>; type SchemaEntries = HashMap<String, HashMap<String, TableRef>>;
/// Simple in-memory list of catalogs used for tests. /// Simple in-memory list of catalogs
#[derive(Clone)] #[derive(Clone)]
pub struct MemoryCatalogManager { pub struct MemoryCatalogManager {
/// Collection of catalogs containing schemas and ultimately Tables /// Collection of catalogs containing schemas and ultimately Tables

View File

@@ -21,17 +21,17 @@ use std::time::{Duration, Instant, UNIX_EPOCH};
use api::v1::frontend::{KillProcessRequest, ListProcessRequest, ProcessInfo}; use api::v1::frontend::{KillProcessRequest, ListProcessRequest, ProcessInfo};
use common_base::cancellation::CancellationHandle; use common_base::cancellation::CancellationHandle;
use common_event_recorder::EventRecorderRef;
use common_frontend::selector::{FrontendSelector, MetaClientSelector}; use common_frontend::selector::{FrontendSelector, MetaClientSelector};
use common_frontend::slow_query_event::SlowQueryEvent; use common_frontend::slow_query_event::SlowQueryEvent;
use common_telemetry::logging::SlowQueriesRecordType; use common_telemetry::{debug, error, info, warn};
use common_telemetry::{debug, info, slow, warn};
use common_time::util::current_time_millis; use common_time::util::current_time_millis;
use meta_client::MetaClientRef; use meta_client::MetaClientRef;
use promql_parser::parser::EvalStmt; use promql_parser::parser::EvalStmt;
use rand::random; use rand::random;
use session::context::QueryContextRef;
use snafu::{ensure, OptionExt, ResultExt}; use snafu::{ensure, OptionExt, ResultExt};
use sql::statements::statement::Statement; use sql::statements::statement::Statement;
use tokio::sync::mpsc::Sender;
use crate::error; use crate::error;
use crate::metrics::{PROCESS_KILL_COUNT, PROCESS_LIST_COUNT}; use crate::metrics::{PROCESS_KILL_COUNT, PROCESS_LIST_COUNT};
@@ -249,8 +249,6 @@ pub struct Ticket {
pub(crate) manager: ProcessManagerRef, pub(crate) manager: ProcessManagerRef,
pub(crate) id: ProcessId, pub(crate) id: ProcessId,
pub cancellation_handle: Arc<CancellationHandle>, pub cancellation_handle: Arc<CancellationHandle>,
// Keep the handle of the slow query timer to ensure it will trigger the event recording when dropped.
_slow_query_timer: Option<SlowQueryTimer>, _slow_query_timer: Option<SlowQueryTimer>,
} }
@@ -297,37 +295,38 @@ impl Debug for CancellableProcess {
pub struct SlowQueryTimer { pub struct SlowQueryTimer {
start: Instant, start: Instant,
stmt: QueryStatement, stmt: QueryStatement,
threshold: Duration, query_ctx: QueryContextRef,
sample_ratio: f64, threshold: Option<Duration>,
record_type: SlowQueriesRecordType, sample_ratio: Option<f64>,
recorder: EventRecorderRef, tx: Sender<SlowQueryEvent>,
} }
impl SlowQueryTimer { impl SlowQueryTimer {
pub fn new( pub fn new(
stmt: QueryStatement, stmt: QueryStatement,
threshold: Duration, query_ctx: QueryContextRef,
sample_ratio: f64, threshold: Option<Duration>,
record_type: SlowQueriesRecordType, sample_ratio: Option<f64>,
recorder: EventRecorderRef, tx: Sender<SlowQueryEvent>,
) -> Self { ) -> Self {
Self { Self {
start: Instant::now(), start: Instant::now(),
stmt, stmt,
query_ctx,
threshold, threshold,
sample_ratio, sample_ratio,
record_type, tx,
recorder,
} }
} }
} }
impl SlowQueryTimer { impl SlowQueryTimer {
fn send_slow_query_event(&self, elapsed: Duration) { fn send_slow_query_event(&self, elapsed: Duration, threshold: Duration) {
let mut slow_query_event = SlowQueryEvent { let mut slow_query_event = SlowQueryEvent {
cost: elapsed.as_millis() as u64, cost: elapsed.as_millis() as u64,
threshold: self.threshold.as_millis() as u64, threshold: threshold.as_millis() as u64,
query: "".to_string(), query: "".to_string(),
query_ctx: self.query_ctx.clone(),
// The following fields are only used for PromQL queries. // The following fields are only used for PromQL queries.
is_promql: false, is_promql: false,
@@ -364,37 +363,29 @@ impl SlowQueryTimer {
} }
} }
match self.record_type { // Send SlowQueryEvent to the handler.
// Send the slow query event to the event recorder to persist it as the system table. if let Err(e) = self.tx.try_send(slow_query_event) {
SlowQueriesRecordType::SystemTable => { error!(e; "Failed to send slow query event");
self.recorder.record(Box::new(slow_query_event));
}
// Record the slow query in a specific logs file.
SlowQueriesRecordType::Log => {
slow!(
cost = slow_query_event.cost,
threshold = slow_query_event.threshold,
query = slow_query_event.query,
is_promql = slow_query_event.is_promql,
promql_range = slow_query_event.promql_range,
promql_step = slow_query_event.promql_step,
promql_start = slow_query_event.promql_start,
promql_end = slow_query_event.promql_end,
);
}
} }
} }
} }
impl Drop for SlowQueryTimer { impl Drop for SlowQueryTimer {
fn drop(&mut self) { fn drop(&mut self) {
// Calculate the elaspsed duration since the timer is created. if let Some(threshold) = self.threshold {
let elapsed = self.start.elapsed(); // Calculate the elaspsed duration since the timer is created.
if elapsed > self.threshold { let elapsed = self.start.elapsed();
// Only capture a portion of slow queries based on sample_ratio. if elapsed > threshold {
// Generate a random number in [0, 1) and compare it with sample_ratio. if let Some(ratio) = self.sample_ratio {
if self.sample_ratio >= 1.0 || random::<f64>() <= self.sample_ratio { // Only capture a portion of slow queries based on sample_ratio.
self.send_slow_query_event(elapsed); // Generate a random number in [0, 1) and compare it with sample_ratio.
if ratio >= 1.0 || random::<f64>() <= ratio {
self.send_slow_query_event(elapsed, threshold);
}
} else {
// Captures all slow queries if sample_ratio is not set.
self.send_slow_query_event(elapsed, threshold);
}
} }
} }
} }

View File

@@ -30,7 +30,8 @@ use datatypes::prelude::{ConcreteDataType, ScalarVectorBuilder, VectorRef};
use datatypes::schema::{ColumnSchema, Schema, SchemaRef}; use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
use datatypes::value::Value; use datatypes::value::Value;
use datatypes::vectors::{ use datatypes::vectors::{
StringVectorBuilder, TimestampSecondVectorBuilder, UInt32VectorBuilder, UInt64VectorBuilder, StringVectorBuilder, TimestampMicrosecondVectorBuilder, UInt32VectorBuilder,
UInt64VectorBuilder,
}; };
use futures::TryStreamExt; use futures::TryStreamExt;
use snafu::{OptionExt, ResultExt}; use snafu::{OptionExt, ResultExt};
@@ -106,17 +107,17 @@ impl InformationSchemaTables {
ColumnSchema::new(AUTO_INCREMENT, ConcreteDataType::uint64_datatype(), true), ColumnSchema::new(AUTO_INCREMENT, ConcreteDataType::uint64_datatype(), true),
ColumnSchema::new( ColumnSchema::new(
CREATE_TIME, CREATE_TIME,
ConcreteDataType::timestamp_second_datatype(), ConcreteDataType::timestamp_microsecond_datatype(),
true, true,
), ),
ColumnSchema::new( ColumnSchema::new(
UPDATE_TIME, UPDATE_TIME,
ConcreteDataType::timestamp_second_datatype(), ConcreteDataType::timestamp_microsecond_datatype(),
true, true,
), ),
ColumnSchema::new( ColumnSchema::new(
CHECK_TIME, CHECK_TIME,
ConcreteDataType::timestamp_second_datatype(), ConcreteDataType::timestamp_microsecond_datatype(),
true, true,
), ),
ColumnSchema::new(TABLE_COLLATION, ConcreteDataType::string_datatype(), true), ColumnSchema::new(TABLE_COLLATION, ConcreteDataType::string_datatype(), true),
@@ -193,9 +194,9 @@ struct InformationSchemaTablesBuilder {
max_index_length: UInt64VectorBuilder, max_index_length: UInt64VectorBuilder,
data_free: UInt64VectorBuilder, data_free: UInt64VectorBuilder,
auto_increment: UInt64VectorBuilder, auto_increment: UInt64VectorBuilder,
create_time: TimestampSecondVectorBuilder, create_time: TimestampMicrosecondVectorBuilder,
update_time: TimestampSecondVectorBuilder, update_time: TimestampMicrosecondVectorBuilder,
check_time: TimestampSecondVectorBuilder, check_time: TimestampMicrosecondVectorBuilder,
table_collation: StringVectorBuilder, table_collation: StringVectorBuilder,
checksum: UInt64VectorBuilder, checksum: UInt64VectorBuilder,
create_options: StringVectorBuilder, create_options: StringVectorBuilder,
@@ -230,9 +231,9 @@ impl InformationSchemaTablesBuilder {
max_index_length: UInt64VectorBuilder::with_capacity(INIT_CAPACITY), max_index_length: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
data_free: UInt64VectorBuilder::with_capacity(INIT_CAPACITY), data_free: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
auto_increment: UInt64VectorBuilder::with_capacity(INIT_CAPACITY), auto_increment: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
create_time: TimestampSecondVectorBuilder::with_capacity(INIT_CAPACITY), create_time: TimestampMicrosecondVectorBuilder::with_capacity(INIT_CAPACITY),
update_time: TimestampSecondVectorBuilder::with_capacity(INIT_CAPACITY), update_time: TimestampMicrosecondVectorBuilder::with_capacity(INIT_CAPACITY),
check_time: TimestampSecondVectorBuilder::with_capacity(INIT_CAPACITY), check_time: TimestampMicrosecondVectorBuilder::with_capacity(INIT_CAPACITY),
table_collation: StringVectorBuilder::with_capacity(INIT_CAPACITY), table_collation: StringVectorBuilder::with_capacity(INIT_CAPACITY),
checksum: UInt64VectorBuilder::with_capacity(INIT_CAPACITY), checksum: UInt64VectorBuilder::with_capacity(INIT_CAPACITY),
create_options: StringVectorBuilder::with_capacity(INIT_CAPACITY), create_options: StringVectorBuilder::with_capacity(INIT_CAPACITY),
@@ -379,7 +380,7 @@ impl InformationSchemaTablesBuilder {
self.create_options self.create_options
.push(Some(table_info.meta.options.to_string().as_ref())); .push(Some(table_info.meta.options.to_string().as_ref()));
self.create_time self.create_time
.push(Some(table_info.meta.created_on.timestamp().into())); .push(Some(table_info.meta.created_on.timestamp_millis().into()));
self.temporary self.temporary
.push(if matches!(table_type, TableType::Temporary) { .push(if matches!(table_type, TableType::Temporary) {

View File

@@ -133,7 +133,7 @@ impl Predicate {
let Expr::Column(c) = *expr else { let Expr::Column(c) = *expr else {
unreachable!(); unreachable!();
}; };
let Expr::Literal(ScalarValue::Utf8(Some(pattern)), _) = *pattern else { let Expr::Literal(ScalarValue::Utf8(Some(pattern))) = *pattern else {
unreachable!(); unreachable!();
}; };
@@ -148,8 +148,8 @@ impl Predicate {
// left OP right // left OP right
Expr::BinaryExpr(bin) => match (*bin.left, bin.op, *bin.right) { Expr::BinaryExpr(bin) => match (*bin.left, bin.op, *bin.right) {
// left == right // left == right
(Expr::Literal(scalar, _), Operator::Eq, Expr::Column(c)) (Expr::Literal(scalar), Operator::Eq, Expr::Column(c))
| (Expr::Column(c), Operator::Eq, Expr::Literal(scalar, _)) => { | (Expr::Column(c), Operator::Eq, Expr::Literal(scalar)) => {
let Ok(v) = Value::try_from(scalar) else { let Ok(v) = Value::try_from(scalar) else {
return None; return None;
}; };
@@ -157,8 +157,8 @@ impl Predicate {
Some(Predicate::Eq(c.name, v)) Some(Predicate::Eq(c.name, v))
} }
// left != right // left != right
(Expr::Literal(scalar, _), Operator::NotEq, Expr::Column(c)) (Expr::Literal(scalar), Operator::NotEq, Expr::Column(c))
| (Expr::Column(c), Operator::NotEq, Expr::Literal(scalar, _)) => { | (Expr::Column(c), Operator::NotEq, Expr::Literal(scalar)) => {
let Ok(v) = Value::try_from(scalar) else { let Ok(v) = Value::try_from(scalar) else {
return None; return None;
}; };
@@ -189,7 +189,7 @@ impl Predicate {
let mut values = Vec::with_capacity(list.len()); let mut values = Vec::with_capacity(list.len());
for scalar in list { for scalar in list {
// Safety: checked by `is_all_scalars` // Safety: checked by `is_all_scalars`
let Expr::Literal(scalar, _) = scalar else { let Expr::Literal(scalar) = scalar else {
unreachable!(); unreachable!();
}; };
@@ -237,7 +237,7 @@ fn like_utf8(s: &str, pattern: &str, case_insensitive: &bool) -> Option<bool> {
} }
fn is_string_literal(expr: &Expr) -> bool { fn is_string_literal(expr: &Expr) -> bool {
matches!(expr, Expr::Literal(ScalarValue::Utf8(Some(_)), _)) matches!(expr, Expr::Literal(ScalarValue::Utf8(Some(_))))
} }
fn is_column(expr: &Expr) -> bool { fn is_column(expr: &Expr) -> bool {
@@ -286,14 +286,14 @@ impl Predicates {
/// Returns true when the values are all [`DfExpr::Literal`]. /// Returns true when the values are all [`DfExpr::Literal`].
fn is_all_scalars(list: &[Expr]) -> bool { fn is_all_scalars(list: &[Expr]) -> bool {
list.iter().all(|v| matches!(v, Expr::Literal(_, _))) list.iter().all(|v| matches!(v, Expr::Literal(_)))
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use datafusion::common::Column; use datafusion::common::{Column, ScalarValue};
use datafusion::logical_expr::expr::InList; use datafusion::logical_expr::expr::InList;
use datafusion::logical_expr::{BinaryExpr, Literal}; use datafusion::logical_expr::BinaryExpr;
use super::*; use super::*;
@@ -378,7 +378,7 @@ mod tests {
let expr = Expr::Like(Like { let expr = Expr::Like(Like {
negated: false, negated: false,
expr: Box::new(column("a")), expr: Box::new(column("a")),
pattern: Box::new("%abc".lit()), pattern: Box::new(string_literal("%abc")),
case_insensitive: true, case_insensitive: true,
escape_char: None, escape_char: None,
}); });
@@ -405,7 +405,7 @@ mod tests {
let expr = Expr::Like(Like { let expr = Expr::Like(Like {
negated: false, negated: false,
expr: Box::new(column("a")), expr: Box::new(column("a")),
pattern: Box::new("%abc".lit()), pattern: Box::new(string_literal("%abc")),
case_insensitive: false, case_insensitive: false,
escape_char: None, escape_char: None,
}); });
@@ -425,7 +425,7 @@ mod tests {
let expr = Expr::Like(Like { let expr = Expr::Like(Like {
negated: true, negated: true,
expr: Box::new(column("a")), expr: Box::new(column("a")),
pattern: Box::new("%abc".lit()), pattern: Box::new(string_literal("%abc")),
case_insensitive: true, case_insensitive: true,
escape_char: None, escape_char: None,
}); });
@@ -440,6 +440,10 @@ mod tests {
Expr::Column(Column::from_name(name)) Expr::Column(Column::from_name(name))
} }
fn string_literal(v: &str) -> Expr {
Expr::Literal(ScalarValue::Utf8(Some(v.to_string())))
}
fn match_string_value(v: &Value, expected: &str) -> bool { fn match_string_value(v: &Value, expected: &str) -> bool {
matches!(v, Value::String(bs) if bs.as_utf8() == expected) matches!(v, Value::String(bs) if bs.as_utf8() == expected)
} }
@@ -459,13 +463,13 @@ mod tests {
let expr1 = Expr::BinaryExpr(BinaryExpr { let expr1 = Expr::BinaryExpr(BinaryExpr {
left: Box::new(column("a")), left: Box::new(column("a")),
op: Operator::Eq, op: Operator::Eq,
right: Box::new("a_value".lit()), right: Box::new(string_literal("a_value")),
}); });
let expr2 = Expr::BinaryExpr(BinaryExpr { let expr2 = Expr::BinaryExpr(BinaryExpr {
left: Box::new(column("b")), left: Box::new(column("b")),
op: Operator::NotEq, op: Operator::NotEq,
right: Box::new("b_value".lit()), right: Box::new(string_literal("b_value")),
}); });
(expr1, expr2) (expr1, expr2)
@@ -504,7 +508,7 @@ mod tests {
let inlist_expr = Expr::InList(InList { let inlist_expr = Expr::InList(InList {
expr: Box::new(column("a")), expr: Box::new(column("a")),
list: vec!["a1".lit(), "a2".lit()], list: vec![string_literal("a1"), string_literal("a2")],
negated: false, negated: false,
}); });
@@ -514,7 +518,7 @@ mod tests {
let inlist_expr = Expr::InList(InList { let inlist_expr = Expr::InList(InList {
expr: Box::new(column("a")), expr: Box::new(column("a")),
list: vec!["a1".lit(), "a2".lit()], list: vec![string_literal("a1"), string_literal("a2")],
negated: true, negated: true,
}); });
let inlist_p = Predicate::from_expr(inlist_expr).unwrap(); let inlist_p = Predicate::from_expr(inlist_expr).unwrap();

View File

@@ -32,7 +32,7 @@ use dummy_catalog::DummyCatalogList;
use table::TableRef; use table::TableRef;
use crate::error::{ use crate::error::{
CastManagerSnafu, DecodePlanSnafu, GetViewCacheSnafu, ProjectViewColumnsSnafu, CastManagerSnafu, DatafusionSnafu, DecodePlanSnafu, GetViewCacheSnafu, ProjectViewColumnsSnafu,
QueryAccessDeniedSnafu, Result, TableNotExistSnafu, ViewInfoNotFoundSnafu, QueryAccessDeniedSnafu, Result, TableNotExistSnafu, ViewInfoNotFoundSnafu,
ViewPlanColumnsChangedSnafu, ViewPlanColumnsChangedSnafu,
}; };
@@ -199,10 +199,10 @@ impl DfTableSourceProvider {
logical_plan logical_plan
}; };
Ok(Arc::new(ViewTable::new( Ok(Arc::new(
logical_plan, ViewTable::try_new(logical_plan, Some(view_info.definition.to_string()))
Some(view_info.definition.to_string()), .context(DatafusionSnafu)?,
))) ))
} }
} }

View File

@@ -66,9 +66,6 @@ pub struct BenchTableMetadataCommand {
#[cfg(feature = "pg_kvbackend")] #[cfg(feature = "pg_kvbackend")]
#[clap(long)] #[clap(long)]
postgres_addr: Option<String>, postgres_addr: Option<String>,
#[cfg(feature = "pg_kvbackend")]
#[clap(long)]
postgres_schema: Option<String>,
#[cfg(feature = "mysql_kvbackend")] #[cfg(feature = "mysql_kvbackend")]
#[clap(long)] #[clap(long)]
mysql_addr: Option<String>, mysql_addr: Option<String>,

View File

@@ -19,9 +19,8 @@ use common_error::ext::BoxedError;
use common_meta::kv_backend::chroot::ChrootKvBackend; use common_meta::kv_backend::chroot::ChrootKvBackend;
use common_meta::kv_backend::etcd::EtcdStore; use common_meta::kv_backend::etcd::EtcdStore;
use common_meta::kv_backend::KvBackendRef; use common_meta::kv_backend::KvBackendRef;
use meta_srv::bootstrap::create_etcd_client_with_tls; use meta_srv::bootstrap::create_etcd_client;
use meta_srv::metasrv::BackendImpl; use meta_srv::metasrv::BackendImpl;
use servers::tls::{TlsMode, TlsOption};
use crate::error::{EmptyStoreAddrsSnafu, UnsupportedMemoryBackendSnafu}; use crate::error::{EmptyStoreAddrsSnafu, UnsupportedMemoryBackendSnafu};
@@ -56,30 +55,6 @@ pub(crate) struct StoreConfig {
#[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))] #[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))]
#[clap(long, default_value = common_meta::kv_backend::DEFAULT_META_TABLE_NAME)] #[clap(long, default_value = common_meta::kv_backend::DEFAULT_META_TABLE_NAME)]
meta_table_name: String, meta_table_name: String,
/// Optional PostgreSQL schema for metadata table (defaults to current search_path if unset).
#[cfg(feature = "pg_kvbackend")]
#[clap(long)]
meta_schema_name: Option<String>,
/// TLS mode for backend store connections (etcd, PostgreSQL, MySQL)
#[clap(long = "backend-tls-mode", value_enum, default_value = "disable")]
backend_tls_mode: TlsMode,
/// Path to TLS certificate file for backend store connections
#[clap(long = "backend-tls-cert-path", default_value = "")]
backend_tls_cert_path: String,
/// Path to TLS private key file for backend store connections
#[clap(long = "backend-tls-key-path", default_value = "")]
backend_tls_key_path: String,
/// Path to TLS CA certificate file for backend store connections
#[clap(long = "backend-tls-ca-cert-path", default_value = "")]
backend_tls_ca_cert_path: String,
/// Enable watching TLS certificate files for changes
#[clap(long = "backend-tls-watch")]
backend_tls_watch: bool,
} }
impl StoreConfig { impl StoreConfig {
@@ -92,18 +67,7 @@ impl StoreConfig {
} else { } else {
let kvbackend = match self.backend { let kvbackend = match self.backend {
BackendImpl::EtcdStore => { BackendImpl::EtcdStore => {
let tls_config = if self.backend_tls_mode != TlsMode::Disable { let etcd_client = create_etcd_client(store_addrs)
Some(TlsOption {
mode: self.backend_tls_mode.clone(),
cert_path: self.backend_tls_cert_path.clone(),
key_path: self.backend_tls_key_path.clone(),
ca_cert_path: self.backend_tls_ca_cert_path.clone(),
watch: self.backend_tls_watch,
})
} else {
None
};
let etcd_client = create_etcd_client_with_tls(store_addrs, tls_config.as_ref())
.await .await
.map_err(BoxedError::new)?; .map_err(BoxedError::new)?;
Ok(EtcdStore::with_etcd_client(etcd_client, max_txn_ops)) Ok(EtcdStore::with_etcd_client(etcd_client, max_txn_ops))
@@ -114,10 +78,8 @@ impl StoreConfig {
let pool = meta_srv::bootstrap::create_postgres_pool(store_addrs, None) let pool = meta_srv::bootstrap::create_postgres_pool(store_addrs, None)
.await .await
.map_err(BoxedError::new)?; .map_err(BoxedError::new)?;
let schema_name = self.meta_schema_name.as_deref();
Ok(common_meta::kv_backend::rds::PgStore::with_pg_pool( Ok(common_meta::kv_backend::rds::PgStore::with_pg_pool(
pool, pool,
schema_name,
table_name, table_name,
max_txn_ops, max_txn_ops,
) )

View File

@@ -74,19 +74,11 @@ pub fn make_create_region_request_for_peer(
let catalog = &create_table_expr.catalog_name; let catalog = &create_table_expr.catalog_name;
let schema = &create_table_expr.schema_name; let schema = &create_table_expr.schema_name;
let storage_path = region_storage_path(catalog, schema); let storage_path = region_storage_path(catalog, schema);
let partition_exprs = region_routes
.iter()
.map(|r| (r.region.id.region_number(), r.region.partition_expr()))
.collect::<HashMap<_, _>>();
for region_number in &regions_on_this_peer { for region_number in &regions_on_this_peer {
let region_id = RegionId::new(logical_table_id, *region_number); let region_id = RegionId::new(logical_table_id, *region_number);
let region_request = request_builder.build_one( let region_request =
region_id, request_builder.build_one(region_id, storage_path.clone(), &HashMap::new());
storage_path.clone(),
&HashMap::new(),
&partition_exprs,
);
requests.push(region_request); requests.push(region_request);
} }

View File

@@ -29,7 +29,6 @@ datatypes.workspace = true
enum_dispatch = "0.3" enum_dispatch = "0.3"
futures.workspace = true futures.workspace = true
futures-util.workspace = true futures-util.workspace = true
humantime.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
moka = { workspace = true, features = ["future"] } moka = { workspace = true, features = ["future"] }
parking_lot.workspace = true parking_lot.workspace = true
@@ -39,7 +38,6 @@ query.workspace = true
rand.workspace = true rand.workspace = true
serde_json.workspace = true serde_json.workspace = true
snafu.workspace = true snafu.workspace = true
store-api.workspace = true
substrait.workspace = true substrait.workspace = true
tokio.workspace = true tokio.workspace = true
tokio-stream = { workspace = true, features = ["net"] } tokio-stream = { workspace = true, features = ["net"] }

View File

@@ -17,7 +17,7 @@ use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use common_grpc::channel_manager::{ChannelConfig, ChannelManager}; use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
use common_meta::node_manager::{DatanodeManager, DatanodeRef, FlownodeManager, FlownodeRef}; use common_meta::node_manager::{DatanodeRef, FlownodeRef, NodeManager};
use common_meta::peer::Peer; use common_meta::peer::Peer;
use moka::future::{Cache, CacheBuilder}; use moka::future::{Cache, CacheBuilder};
@@ -45,7 +45,7 @@ impl Debug for NodeClients {
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl DatanodeManager for NodeClients { impl NodeManager for NodeClients {
async fn datanode(&self, datanode: &Peer) -> DatanodeRef { async fn datanode(&self, datanode: &Peer) -> DatanodeRef {
let client = self.get_client(datanode).await; let client = self.get_client(datanode).await;
@@ -60,10 +60,7 @@ impl DatanodeManager for NodeClients {
*accept_compression, *accept_compression,
)) ))
} }
}
#[async_trait::async_trait]
impl FlownodeManager for NodeClients {
async fn flownode(&self, flownode: &Peer) -> FlownodeRef { async fn flownode(&self, flownode: &Peer) -> FlownodeRef {
let client = self.get_client(flownode).await; let client = self.get_client(flownode).await;

View File

@@ -75,24 +75,12 @@ pub struct Database {
} }
pub struct DatabaseClient { pub struct DatabaseClient {
pub addr: String,
pub inner: GreptimeDatabaseClient<Channel>, pub inner: GreptimeDatabaseClient<Channel>,
} }
impl DatabaseClient {
/// Returns a closure that logs the error when the request fails.
pub fn inspect_err<'a>(&'a self, context: &'a str) -> impl Fn(&tonic::Status) + 'a {
let addr = &self.addr;
move |status| {
error!("Failed to {context} request, peer: {addr}, status: {status:?}");
}
}
}
fn make_database_client(client: &Client) -> Result<DatabaseClient> { fn make_database_client(client: &Client) -> Result<DatabaseClient> {
let (addr, channel) = client.find_channel()?; let (_, channel) = client.find_channel()?;
Ok(DatabaseClient { Ok(DatabaseClient {
addr,
inner: GreptimeDatabaseClient::new(channel) inner: GreptimeDatabaseClient::new(channel)
.max_decoding_message_size(client.max_grpc_recv_message_size()) .max_decoding_message_size(client.max_grpc_recv_message_size())
.max_encoding_message_size(client.max_grpc_send_message_size()), .max_encoding_message_size(client.max_grpc_send_message_size()),
@@ -179,19 +167,14 @@ impl Database {
requests: InsertRequests, requests: InsertRequests,
hints: &[(&str, &str)], hints: &[(&str, &str)],
) -> Result<u32> { ) -> Result<u32> {
let mut client = make_database_client(&self.client)?; let mut client = make_database_client(&self.client)?.inner;
let request = self.to_rpc_request(Request::Inserts(requests)); let request = self.to_rpc_request(Request::Inserts(requests));
let mut request = tonic::Request::new(request); let mut request = tonic::Request::new(request);
let metadata = request.metadata_mut(); let metadata = request.metadata_mut();
Self::put_hints(metadata, hints)?; Self::put_hints(metadata, hints)?;
let response = client let response = client.handle(request).await?.into_inner();
.inner
.handle(request)
.await
.inspect_err(client.inspect_err("insert_with_hints"))?
.into_inner();
from_grpc_response(response) from_grpc_response(response)
} }
@@ -206,19 +189,14 @@ impl Database {
requests: RowInsertRequests, requests: RowInsertRequests,
hints: &[(&str, &str)], hints: &[(&str, &str)],
) -> Result<u32> { ) -> Result<u32> {
let mut client = make_database_client(&self.client)?; let mut client = make_database_client(&self.client)?.inner;
let request = self.to_rpc_request(Request::RowInserts(requests)); let request = self.to_rpc_request(Request::RowInserts(requests));
let mut request = tonic::Request::new(request); let mut request = tonic::Request::new(request);
let metadata = request.metadata_mut(); let metadata = request.metadata_mut();
Self::put_hints(metadata, hints)?; Self::put_hints(metadata, hints)?;
let response = client let response = client.handle(request).await?.into_inner();
.inner
.handle(request)
.await
.inspect_err(client.inspect_err("row_inserts_with_hints"))?
.into_inner();
from_grpc_response(response) from_grpc_response(response)
} }
@@ -239,14 +217,9 @@ impl Database {
/// Make a request to the database. /// Make a request to the database.
pub async fn handle(&self, request: Request) -> Result<u32> { pub async fn handle(&self, request: Request) -> Result<u32> {
let mut client = make_database_client(&self.client)?; let mut client = make_database_client(&self.client)?.inner;
let request = self.to_rpc_request(request); let request = self.to_rpc_request(request);
let response = client let response = client.handle(request).await?.into_inner();
.inner
.handle(request)
.await
.inspect_err(client.inspect_err("handle"))?
.into_inner();
from_grpc_response(response) from_grpc_response(response)
} }
@@ -258,7 +231,7 @@ impl Database {
max_retries: u32, max_retries: u32,
hints: &[(&str, &str)], hints: &[(&str, &str)],
) -> Result<u32> { ) -> Result<u32> {
let mut client = make_database_client(&self.client)?; let mut client = make_database_client(&self.client)?.inner;
let mut retries = 0; let mut retries = 0;
let request = self.to_rpc_request(request); let request = self.to_rpc_request(request);
@@ -267,11 +240,7 @@ impl Database {
let mut tonic_request = tonic::Request::new(request.clone()); let mut tonic_request = tonic::Request::new(request.clone());
let metadata = tonic_request.metadata_mut(); let metadata = tonic_request.metadata_mut();
Self::put_hints(metadata, hints)?; Self::put_hints(metadata, hints)?;
let raw_response = client let raw_response = client.handle(tonic_request).await;
.inner
.handle(tonic_request)
.await
.inspect_err(client.inspect_err("handle"));
match (raw_response, retries < max_retries) { match (raw_response, retries < max_retries) {
(Ok(resp), _) => return from_grpc_response(resp.into_inner()), (Ok(resp), _) => return from_grpc_response(resp.into_inner()),
(Err(err), true) => { (Err(err), true) => {
@@ -473,8 +442,8 @@ impl Database {
}) = &self.ctx.auth_header }) = &self.ctx.auth_header
{ {
let encoded = BASE64_STANDARD.encode(format!("{username}:{password}")); let encoded = BASE64_STANDARD.encode(format!("{username}:{password}"));
let value = MetadataValue::from_str(&format!("Basic {encoded}")) let value =
.context(InvalidTonicMetadataValueSnafu)?; MetadataValue::from_str(&encoded).context(InvalidTonicMetadataValueSnafu)?;
request.metadata_mut().insert("x-greptime-auth", value); request.metadata_mut().insert("x-greptime-auth", value);
} }

View File

@@ -133,13 +133,6 @@ pub enum Error {
#[snafu(implicit)] #[snafu(implicit)]
location: Location, location: Location,
}, },
#[snafu(display("External error"))]
External {
#[snafu(implicit)]
location: Location,
source: BoxedError,
},
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
@@ -161,7 +154,6 @@ impl ErrorExt for Error {
Error::IllegalGrpcClientState { .. } => StatusCode::Unexpected, Error::IllegalGrpcClientState { .. } => StatusCode::Unexpected,
Error::InvalidTonicMetadataValue { .. } => StatusCode::InvalidArguments, Error::InvalidTonicMetadataValue { .. } => StatusCode::InvalidArguments,
Error::ConvertSchema { source, .. } => source.status_code(), Error::ConvertSchema { source, .. } => source.status_code(),
Error::External { source, .. } => source.status_code(),
} }
} }

View File

@@ -1,68 +0,0 @@
// Copyright 2023 Greptime Team
//
// 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::time::Duration;
use api::v1::RowInsertRequests;
use humantime::format_duration;
use store_api::mito_engine_options::{APPEND_MODE_KEY, TTL_KEY, TWCS_TIME_WINDOW};
use crate::error::Result;
/// Context holds the catalog and schema information.
pub struct Context<'a> {
/// The catalog name.
pub catalog: &'a str,
/// The schema name.
pub schema: &'a str,
}
/// Options for insert operations.
#[derive(Debug, Clone, Copy)]
pub struct InsertOptions {
/// Time-to-live for the inserted data.
pub ttl: Duration,
/// Whether to use append mode for the insert.
pub append_mode: bool,
/// Time window for twcs compaction.
pub twcs_compaction_time_window: Option<Duration>,
}
impl InsertOptions {
/// Converts the insert options to a list of key-value string hints.
pub fn to_hints(&self) -> Vec<(&'static str, String)> {
let mut hints = vec![
(TTL_KEY, format_duration(self.ttl).to_string()),
(APPEND_MODE_KEY, self.append_mode.to_string()),
];
if let Some(time_window) = self.twcs_compaction_time_window {
hints.push((TWCS_TIME_WINDOW, format_duration(time_window).to_string()));
}
hints
}
}
/// [`Inserter`] allows different components to share a unified mechanism for inserting data.
///
/// An implementation may perform the insert locally (e.g., via a direct procedure call) or
/// delegate/forward it to another node for processing (e.g., MetaSrv forwarding to an
/// available Frontend).
#[async_trait::async_trait]
pub trait Inserter: Send + Sync {
async fn insert_rows(&self, context: &Context<'_>, requests: RowInsertRequests) -> Result<()>;
fn set_options(&mut self, options: &InsertOptions);
}

View File

@@ -19,7 +19,6 @@ pub mod client_manager;
pub mod database; pub mod database;
pub mod error; pub mod error;
pub mod flow; pub mod flow;
pub mod inserter;
pub mod load_balance; pub mod load_balance;
mod metrics; mod metrics;
pub mod region; pub mod region;

View File

@@ -103,6 +103,3 @@ tempfile.workspace = true
[target.'cfg(not(windows))'.dev-dependencies] [target.'cfg(not(windows))'.dev-dependencies]
rexpect = "0.5" rexpect = "0.5"
[package.metadata.cargo-udeps.ignore]
development = ["rexpect"]

View File

@@ -376,8 +376,7 @@ impl StartCommand {
flow_auth_header, flow_auth_header,
opts.query.clone(), opts.query.clone(),
opts.flow.batching_mode.clone(), opts.flow.batching_mode.clone(),
) );
.context(StartFlownodeSnafu)?;
let frontend_client = Arc::new(frontend_client); let frontend_client = Arc::new(frontend_client);
let flownode_builder = FlownodeBuilder::new( let flownode_builder = FlownodeBuilder::new(
opts.clone(), opts.clone(),

View File

@@ -41,7 +41,6 @@ use frontend::server::Services;
use meta_client::{MetaClientOptions, MetaClientType}; use meta_client::{MetaClientOptions, MetaClientType};
use servers::addrs; use servers::addrs;
use servers::export_metrics::ExportMetricsTask; use servers::export_metrics::ExportMetricsTask;
use servers::grpc::GrpcOptions;
use servers::tls::{TlsMode, TlsOption}; use servers::tls::{TlsMode, TlsOption};
use snafu::{OptionExt, ResultExt}; use snafu::{OptionExt, ResultExt};
use tracing_appender::non_blocking::WorkerGuard; use tracing_appender::non_blocking::WorkerGuard;
@@ -145,14 +144,6 @@ pub struct StartCommand {
/// on the host, with the same port number as the one specified in `rpc_bind_addr`. /// on the host, with the same port number as the one specified in `rpc_bind_addr`.
#[clap(long, alias = "rpc-hostname")] #[clap(long, alias = "rpc-hostname")]
rpc_server_addr: Option<String>, rpc_server_addr: Option<String>,
/// The address to bind the internal gRPC server.
#[clap(long, alias = "internal-rpc-addr")]
internal_rpc_bind_addr: Option<String>,
/// The address advertised to the metasrv, and used for connections from outside the host.
/// If left empty or unset, the server will automatically use the IP address of the first network interface
/// on the host, with the same port number as the one specified in `internal_rpc_bind_addr`.
#[clap(long, alias = "internal-rpc-hostname")]
internal_rpc_server_addr: Option<String>,
#[clap(long)] #[clap(long)]
http_addr: Option<String>, http_addr: Option<String>,
#[clap(long)] #[clap(long)]
@@ -250,31 +241,6 @@ impl StartCommand {
opts.grpc.server_addr.clone_from(addr); opts.grpc.server_addr.clone_from(addr);
} }
if let Some(addr) = &self.internal_rpc_bind_addr {
if let Some(internal_grpc) = &mut opts.internal_grpc {
internal_grpc.bind_addr = addr.to_string();
} else {
let grpc_options = GrpcOptions {
bind_addr: addr.to_string(),
..Default::default()
};
opts.internal_grpc = Some(grpc_options);
}
}
if let Some(addr) = &self.internal_rpc_server_addr {
if let Some(internal_grpc) = &mut opts.internal_grpc {
internal_grpc.server_addr = addr.to_string();
} else {
let grpc_options = GrpcOptions {
server_addr: addr.to_string(),
..Default::default()
};
opts.internal_grpc = Some(grpc_options);
}
}
if let Some(addr) = &self.mysql_addr { if let Some(addr) = &self.mysql_addr {
opts.mysql.enable = true; opts.mysql.enable = true;
opts.mysql.addr.clone_from(addr); opts.mysql.addr.clone_from(addr);
@@ -313,7 +279,7 @@ impl StartCommand {
&opts.component.logging, &opts.component.logging,
&opts.component.tracing, &opts.component.tracing,
opts.component.node_id.clone(), opts.component.node_id.clone(),
Some(&opts.component.slow_query), opts.component.slow_query.as_ref(),
); );
log_versions(verbose_version(), short_version(), APP_NAME); log_versions(verbose_version(), short_version(), APP_NAME);
@@ -482,8 +448,6 @@ mod tests {
http_addr: Some("127.0.0.1:1234".to_string()), http_addr: Some("127.0.0.1:1234".to_string()),
mysql_addr: Some("127.0.0.1:5678".to_string()), mysql_addr: Some("127.0.0.1:5678".to_string()),
postgres_addr: Some("127.0.0.1:5432".to_string()), postgres_addr: Some("127.0.0.1:5432".to_string()),
internal_rpc_bind_addr: Some("127.0.0.1:4010".to_string()),
internal_rpc_server_addr: Some("10.0.0.24:4010".to_string()),
influxdb_enable: Some(false), influxdb_enable: Some(false),
disable_dashboard: Some(false), disable_dashboard: Some(false),
..Default::default() ..Default::default()
@@ -496,10 +460,6 @@ mod tests {
assert_eq!(opts.mysql.addr, "127.0.0.1:5678"); assert_eq!(opts.mysql.addr, "127.0.0.1:5678");
assert_eq!(opts.postgres.addr, "127.0.0.1:5432"); assert_eq!(opts.postgres.addr, "127.0.0.1:5432");
let internal_grpc = opts.internal_grpc.as_ref().unwrap();
assert_eq!(internal_grpc.bind_addr, "127.0.0.1:4010");
assert_eq!(internal_grpc.server_addr, "10.0.0.24:4010");
let default_opts = FrontendOptions::default().component; let default_opts = FrontendOptions::default().component;
assert_eq!(opts.grpc.bind_addr, default_opts.grpc.bind_addr); assert_eq!(opts.grpc.bind_addr, default_opts.grpc.bind_addr);

View File

@@ -157,7 +157,7 @@ pub struct StandaloneOptions {
pub init_regions_in_background: bool, pub init_regions_in_background: bool,
pub init_regions_parallelism: usize, pub init_regions_parallelism: usize,
pub max_in_flight_write_bytes: Option<ReadableSize>, pub max_in_flight_write_bytes: Option<ReadableSize>,
pub slow_query: SlowQueryOptions, pub slow_query: Option<SlowQueryOptions>,
pub query: QueryOptions, pub query: QueryOptions,
pub memory: MemoryOptions, pub memory: MemoryOptions,
} }
@@ -191,7 +191,7 @@ impl Default for StandaloneOptions {
init_regions_in_background: false, init_regions_in_background: false,
init_regions_parallelism: 16, init_regions_parallelism: 16,
max_in_flight_write_bytes: None, max_in_flight_write_bytes: None,
slow_query: SlowQueryOptions::default(), slow_query: Some(SlowQueryOptions::default()),
query: QueryOptions::default(), query: QueryOptions::default(),
memory: MemoryOptions::default(), memory: MemoryOptions::default(),
} }
@@ -486,7 +486,7 @@ impl StartCommand {
&opts.component.logging, &opts.component.logging,
&opts.component.tracing, &opts.component.tracing,
None, None,
Some(&opts.component.slow_query), opts.component.slow_query.as_ref(),
); );
log_versions(verbose_version(), short_version(), APP_NAME); log_versions(verbose_version(), short_version(), APP_NAME);
@@ -834,7 +834,6 @@ impl InformationExtension for StandaloneInformationExtension {
region_manifest: region_stat.manifest.into(), region_manifest: region_stat.manifest.into(),
data_topic_latest_entry_id: region_stat.data_topic_latest_entry_id, data_topic_latest_entry_id: region_stat.data_topic_latest_entry_id,
metadata_topic_latest_entry_id: region_stat.metadata_topic_latest_entry_id, metadata_topic_latest_entry_id: region_stat.metadata_topic_latest_entry_id,
write_bytes: 0,
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -146,7 +146,6 @@ fn test_load_frontend_example_config() {
grpc: GrpcOptions::default() grpc: GrpcOptions::default()
.with_bind_addr("127.0.0.1:4001") .with_bind_addr("127.0.0.1:4001")
.with_server_addr("127.0.0.1:4001"), .with_server_addr("127.0.0.1:4001"),
internal_grpc: Some(GrpcOptions::internal_default()),
http: HttpOptions { http: HttpOptions {
cors_allowed_origins: vec!["https://example.com".to_string()], cors_allowed_origins: vec!["https://example.com".to_string()],
..Default::default() ..Default::default()
@@ -199,7 +198,6 @@ fn test_load_metasrv_example_config() {
ca_cert_path: String::new(), ca_cert_path: String::new(),
watch: false, watch: false,
}), }),
meta_schema_name: Some("greptime_schema".to_string()),
..Default::default() ..Default::default()
}, },
..Default::default() ..Default::default()

View File

@@ -20,7 +20,6 @@ pub mod range_read;
#[allow(clippy::all)] #[allow(clippy::all)]
pub mod readable_size; pub mod readable_size;
pub mod secrets; pub mod secrets;
pub mod serde;
pub type AffectedRows = usize; pub type AffectedRows = usize;

View File

@@ -1,31 +0,0 @@
// Copyright 2023 Greptime Team
//
// 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 serde::{Deserialize, Deserializer};
/// Deserialize an empty string as the default value.
pub fn empty_string_as_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Default + Deserialize<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(T::default())
} else {
T::deserialize(serde::de::value::StringDeserializer::<D::Error>::new(s))
.map_err(serde::de::Error::custom)
}
}

View File

@@ -25,17 +25,19 @@ common-error.workspace = true
common-macro.workspace = true common-macro.workspace = true
common-recordbatch.workspace = true common-recordbatch.workspace = true
common-runtime.workspace = true common-runtime.workspace = true
common-telemetry.workspace = true
datafusion.workspace = true datafusion.workspace = true
datafusion-orc.workspace = true
datatypes.workspace = true datatypes.workspace = true
derive_builder.workspace = true
futures.workspace = true futures.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
object-store.workspace = true object-store.workspace = true
object_store_opendal.workspace = true object_store_opendal.workspace = true
orc-rust = { version = "0.6.3", default-features = false, features = ["async"] } orc-rust = { git = "https://github.com/datafusion-contrib/orc-rust", rev = "3134cab581a8e91b942d6a23aca2916ea965f6bb", default-features = false, features = [
"async",
] }
parquet.workspace = true parquet.workspace = true
paste.workspace = true paste.workspace = true
rand.workspace = true
regex = "1.7" regex = "1.7"
serde.workspace = true serde.workspace = true
snafu.workspace = true snafu.workspace = true
@@ -45,4 +47,6 @@ tokio-util.workspace = true
url = "2.3" url = "2.3"
[dev-dependencies] [dev-dependencies]
common-telemetry.workspace = true
common-test-util.workspace = true common-test-util.workspace = true
uuid.workspace = true

View File

@@ -12,11 +12,16 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use arrow_schema::Schema; use std::sync::Arc;
use arrow_schema::{ArrowError, Schema, SchemaRef};
use async_trait::async_trait; use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use common_recordbatch::adapter::RecordBatchStreamTypeAdapter;
use datafusion::datasource::physical_plan::{FileMeta, FileOpenFuture, FileOpener};
use datafusion::error::{DataFusionError, Result as DfResult};
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures::FutureExt; use futures::{FutureExt, StreamExt, TryStreamExt};
use object_store::ObjectStore; use object_store::ObjectStore;
use orc_rust::arrow_reader::ArrowReaderBuilder; use orc_rust::arrow_reader::ArrowReaderBuilder;
use orc_rust::async_arrow_reader::ArrowStreamReader; use orc_rust::async_arrow_reader::ArrowStreamReader;
@@ -92,6 +97,67 @@ impl FileFormat for OrcFormat {
} }
} }
#[derive(Debug, Clone)]
pub struct OrcOpener {
object_store: Arc<ObjectStore>,
output_schema: SchemaRef,
projection: Option<Vec<usize>>,
}
impl OrcOpener {
pub fn new(
object_store: ObjectStore,
output_schema: SchemaRef,
projection: Option<Vec<usize>>,
) -> Self {
Self {
object_store: Arc::from(object_store),
output_schema,
projection,
}
}
}
impl FileOpener for OrcOpener {
fn open(&self, meta: FileMeta) -> DfResult<FileOpenFuture> {
let object_store = self.object_store.clone();
let projected_schema = if let Some(projection) = &self.projection {
let projected_schema = self
.output_schema
.project(projection)
.map_err(|e| DataFusionError::External(Box::new(e)))?;
Arc::new(projected_schema)
} else {
self.output_schema.clone()
};
let projection = self.projection.clone();
Ok(Box::pin(async move {
let path = meta.location().to_string();
let meta = object_store
.stat(&path)
.await
.map_err(|e| DataFusionError::External(Box::new(e)))?;
let reader = object_store
.reader(&path)
.await
.map_err(|e| DataFusionError::External(Box::new(e)))?;
let stream_reader =
new_orc_stream_reader(ReaderAdapter::new(reader, meta.content_length()))
.await
.map_err(|e| DataFusionError::External(Box::new(e)))?;
let stream =
RecordBatchStreamTypeAdapter::new(projected_schema, stream_reader, projection);
let adopted = stream.map_err(|e| ArrowError::ExternalError(Box::new(e)));
Ok(adopted.boxed())
}))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use common_test_util::find_workspace_path; use common_test_util::find_workspace_path;

View File

@@ -31,7 +31,6 @@ use datatypes::schema::SchemaRef;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures::StreamExt; use futures::StreamExt;
use object_store::{FuturesAsyncReader, ObjectStore}; use object_store::{FuturesAsyncReader, ObjectStore};
use parquet::arrow::arrow_reader::ArrowReaderOptions;
use parquet::arrow::AsyncArrowWriter; use parquet::arrow::AsyncArrowWriter;
use parquet::basic::{Compression, Encoding, ZstdLevel}; use parquet::basic::{Compression, Encoding, ZstdLevel};
use parquet::file::properties::{WriterProperties, WriterPropertiesBuilder}; use parquet::file::properties::{WriterProperties, WriterPropertiesBuilder};
@@ -66,7 +65,7 @@ impl FileFormat for ParquetFormat {
.compat(); .compat();
let metadata = reader let metadata = reader
.get_metadata(None) .get_metadata()
.await .await
.context(error::ReadParquetSnafuSnafu)?; .context(error::ReadParquetSnafuSnafu)?;
@@ -147,7 +146,7 @@ impl LazyParquetFileReader {
impl AsyncFileReader for LazyParquetFileReader { impl AsyncFileReader for LazyParquetFileReader {
fn get_bytes( fn get_bytes(
&mut self, &mut self,
range: std::ops::Range<u64>, range: std::ops::Range<usize>,
) -> BoxFuture<'_, ParquetResult<bytes::Bytes>> { ) -> BoxFuture<'_, ParquetResult<bytes::Bytes>> {
Box::pin(async move { Box::pin(async move {
self.maybe_initialize() self.maybe_initialize()
@@ -158,16 +157,13 @@ impl AsyncFileReader for LazyParquetFileReader {
}) })
} }
fn get_metadata<'a>( fn get_metadata(&mut self) -> BoxFuture<'_, ParquetResult<Arc<ParquetMetaData>>> {
&'a mut self,
options: Option<&'a ArrowReaderOptions>,
) -> BoxFuture<'a, parquet::errors::Result<Arc<ParquetMetaData>>> {
Box::pin(async move { Box::pin(async move {
self.maybe_initialize() self.maybe_initialize()
.await .await
.map_err(|e| ParquetError::External(Box::new(e)))?; .map_err(|e| ParquetError::External(Box::new(e)))?;
// Safety: Must initialized // Safety: Must initialized
self.reader.as_mut().unwrap().get_metadata(options).await self.reader.as_mut().unwrap().get_metadata().await
}) })
} }
} }

View File

@@ -19,39 +19,35 @@ use std::vec;
use common_test_util::find_workspace_path; use common_test_util::find_workspace_path;
use datafusion::assert_batches_eq; use datafusion::assert_batches_eq;
use datafusion::datasource::file_format::file_compression_type::FileCompressionType;
use datafusion::datasource::physical_plan::{ use datafusion::datasource::physical_plan::{
CsvSource, FileScanConfig, FileSource, FileStream, JsonSource, ParquetSource, CsvConfig, CsvOpener, FileOpener, FileScanConfig, FileStream, JsonOpener, ParquetExec,
}; };
use datafusion::datasource::source::DataSourceExec;
use datafusion::execution::context::TaskContext; use datafusion::execution::context::TaskContext;
use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet; use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
use datafusion::physical_plan::ExecutionPlan; use datafusion::physical_plan::ExecutionPlan;
use datafusion::prelude::SessionContext; use datafusion::prelude::SessionContext;
use datafusion_orc::OrcSource;
use futures::StreamExt; use futures::StreamExt;
use object_store::ObjectStore;
use super::FORMAT_TYPE; use super::FORMAT_TYPE;
use crate::file_format::orc::{OrcFormat, OrcOpener};
use crate::file_format::parquet::DefaultParquetFileReaderFactory; use crate::file_format::parquet::DefaultParquetFileReaderFactory;
use crate::file_format::{FileFormat, Format, OrcFormat}; use crate::file_format::{FileFormat, Format};
use crate::test_util::{scan_config, test_basic_schema, test_store}; use crate::test_util::{scan_config, test_basic_schema, test_store};
use crate::{error, test_util}; use crate::{error, test_util};
struct Test<'a> { struct Test<'a, T: FileOpener> {
config: FileScanConfig, config: FileScanConfig,
file_source: Arc<dyn FileSource>, opener: T,
expected: Vec<&'a str>, expected: Vec<&'a str>,
} }
impl Test<'_> { impl<T: FileOpener> Test<'_, T> {
async fn run(self, store: &ObjectStore) { pub async fn run(self) {
let store = Arc::new(object_store_opendal::OpendalStore::new(store.clone()));
let file_opener = self.file_source.create_file_opener(store, &self.config, 0);
let result = FileStream::new( let result = FileStream::new(
&self.config, &self.config,
0, 0,
file_opener, self.opener,
&ExecutionPlanMetricsSet::new(), &ExecutionPlanMetricsSet::new(),
) )
.unwrap() .unwrap()
@@ -66,16 +62,26 @@ impl Test<'_> {
#[tokio::test] #[tokio::test]
async fn test_json_opener() { async fn test_json_opener() {
let store = test_store("/"); let store = test_store("/");
let store = Arc::new(object_store_opendal::OpendalStore::new(store));
let schema = test_basic_schema(); let schema = test_basic_schema();
let file_source = Arc::new(JsonSource::new()).with_batch_size(test_util::TEST_BATCH_SIZE);
let json_opener = || {
JsonOpener::new(
test_util::TEST_BATCH_SIZE,
schema.clone(),
FileCompressionType::UNCOMPRESSED,
store.clone(),
)
};
let path = &find_workspace_path("/src/common/datasource/tests/json/basic.json") let path = &find_workspace_path("/src/common/datasource/tests/json/basic.json")
.display() .display()
.to_string(); .to_string();
let tests = [ let tests = [
Test { Test {
config: scan_config(schema.clone(), None, path, file_source.clone()), config: scan_config(schema.clone(), None, path),
file_source: file_source.clone(), opener: json_opener(),
expected: vec![ expected: vec![
"+-----+-------+", "+-----+-------+",
"| num | str |", "| num | str |",
@@ -87,8 +93,8 @@ async fn test_json_opener() {
], ],
}, },
Test { Test {
config: scan_config(schema, Some(1), path, file_source.clone()), config: scan_config(schema.clone(), Some(1), path),
file_source, opener: json_opener(),
expected: vec![ expected: vec![
"+-----+------+", "+-----+------+",
"| num | str |", "| num | str |",
@@ -100,26 +106,37 @@ async fn test_json_opener() {
]; ];
for test in tests { for test in tests {
test.run(&store).await; test.run().await;
} }
} }
#[tokio::test] #[tokio::test]
async fn test_csv_opener() { async fn test_csv_opener() {
let store = test_store("/"); let store = test_store("/");
let store = Arc::new(object_store_opendal::OpendalStore::new(store));
let schema = test_basic_schema(); let schema = test_basic_schema();
let path = &find_workspace_path("/src/common/datasource/tests/csv/basic.csv") let path = &find_workspace_path("/src/common/datasource/tests/csv/basic.csv")
.display() .display()
.to_string(); .to_string();
let csv_config = Arc::new(CsvConfig::new(
test_util::TEST_BATCH_SIZE,
schema.clone(),
None,
true,
b',',
b'"',
None,
store,
None,
));
let file_source = CsvSource::new(true, b',', b'"') let csv_opener = || CsvOpener::new(csv_config.clone(), FileCompressionType::UNCOMPRESSED);
.with_batch_size(test_util::TEST_BATCH_SIZE)
.with_schema(schema.clone());
let tests = [ let tests = [
Test { Test {
config: scan_config(schema.clone(), None, path, file_source.clone()), config: scan_config(schema.clone(), None, path),
file_source: file_source.clone(), opener: csv_opener(),
expected: vec![ expected: vec![
"+-----+-------+", "+-----+-------+",
"| num | str |", "| num | str |",
@@ -131,8 +148,8 @@ async fn test_csv_opener() {
], ],
}, },
Test { Test {
config: scan_config(schema, Some(1), path, file_source.clone()), config: scan_config(schema.clone(), Some(1), path),
file_source, opener: csv_opener(),
expected: vec![ expected: vec![
"+-----+------+", "+-----+------+",
"| num | str |", "| num | str |",
@@ -144,7 +161,7 @@ async fn test_csv_opener() {
]; ];
for test in tests { for test in tests {
test.run(&store).await; test.run().await;
} }
} }
@@ -157,12 +174,12 @@ async fn test_parquet_exec() {
let path = &find_workspace_path("/src/common/datasource/tests/parquet/basic.parquet") let path = &find_workspace_path("/src/common/datasource/tests/parquet/basic.parquet")
.display() .display()
.to_string(); .to_string();
let base_config = scan_config(schema.clone(), None, path);
let parquet_source = ParquetSource::default() let exec = ParquetExec::builder(base_config)
.with_parquet_file_reader_factory(Arc::new(DefaultParquetFileReaderFactory::new(store))); .with_parquet_file_reader_factory(Arc::new(DefaultParquetFileReaderFactory::new(store)))
.build();
let config = scan_config(schema, None, path, Arc::new(parquet_source));
let exec = DataSourceExec::from_data_source(config);
let ctx = SessionContext::new(); let ctx = SessionContext::new();
let context = Arc::new(TaskContext::from(&ctx)); let context = Arc::new(TaskContext::from(&ctx));
@@ -191,18 +208,20 @@ async fn test_parquet_exec() {
#[tokio::test] #[tokio::test]
async fn test_orc_opener() { async fn test_orc_opener() {
let path = &find_workspace_path("/src/common/datasource/tests/orc/test.orc") let root = find_workspace_path("/src/common/datasource/tests/orc")
.display() .display()
.to_string(); .to_string();
let store = test_store(&root);
let schema = OrcFormat.infer_schema(&store, "test.orc").await.unwrap();
let schema = Arc::new(schema);
let store = test_store("/"); let orc_opener = OrcOpener::new(store.clone(), schema.clone(), None);
let schema = Arc::new(OrcFormat.infer_schema(&store, path).await.unwrap()); let path = "test.orc";
let file_source = Arc::new(OrcSource::default());
let tests = [ let tests = [
Test { Test {
config: scan_config(schema.clone(), None, path, file_source.clone()), config: scan_config(schema.clone(), None, path),
file_source: file_source.clone(), opener: orc_opener.clone(),
expected: vec![ expected: vec![
"+----------+-----+-------+------------+-----+-----+-------+--------------------+------------------------+-----------+---------------+------------+----------------+---------------+-------------------+--------------+---------------+---------------+----------------------------+-------------+", "+----------+-----+-------+------------+-----+-----+-------+--------------------+------------------------+-----------+---------------+------------+----------------+---------------+-------------------+--------------+---------------+---------------+----------------------------+-------------+",
"| double_a | a | b | str_direct | d | e | f | int_short_repeated | int_neg_short_repeated | int_delta | int_neg_delta | int_direct | int_neg_direct | bigint_direct | bigint_neg_direct | bigint_other | utf8_increase | utf8_decrease | timestamp_simple | date_simple |", "| double_a | a | b | str_direct | d | e | f | int_short_repeated | int_neg_short_repeated | int_delta | int_neg_delta | int_direct | int_neg_direct | bigint_direct | bigint_neg_direct | bigint_other | utf8_increase | utf8_decrease | timestamp_simple | date_simple |",
@@ -216,8 +235,8 @@ async fn test_orc_opener() {
], ],
}, },
Test { Test {
config: scan_config(schema.clone(), Some(1), path, file_source.clone()), config: scan_config(schema.clone(), Some(1), path),
file_source, opener: orc_opener.clone(),
expected: vec![ expected: vec![
"+----------+-----+------+------------+---+-----+-------+--------------------+------------------------+-----------+---------------+------------+----------------+---------------+-------------------+--------------+---------------+---------------+-------------------------+-------------+", "+----------+-----+------+------------+---+-----+-------+--------------------+------------------------+-----------+---------------+------------+----------------+---------------+-------------------+--------------+---------------+---------------+-------------------------+-------------+",
"| double_a | a | b | str_direct | d | e | f | int_short_repeated | int_neg_short_repeated | int_delta | int_neg_delta | int_direct | int_neg_direct | bigint_direct | bigint_neg_direct | bigint_other | utf8_increase | utf8_decrease | timestamp_simple | date_simple |", "| double_a | a | b | str_direct | d | e | f | int_short_repeated | int_neg_short_repeated | int_delta | int_neg_delta | int_direct | int_neg_direct | bigint_direct | bigint_neg_direct | bigint_other | utf8_increase | utf8_decrease | timestamp_simple | date_simple |",
@@ -229,7 +248,7 @@ async fn test_orc_opener() {
]; ];
for test in tests { for test in tests {
test.run(&store).await; test.run().await;
} }
} }

View File

@@ -16,12 +16,12 @@ use std::sync::Arc;
use arrow_schema::{DataType, Field, Schema, SchemaRef}; use arrow_schema::{DataType, Field, Schema, SchemaRef};
use common_test_util::temp_dir::{create_temp_dir, TempDir}; use common_test_util::temp_dir::{create_temp_dir, TempDir};
use datafusion::common::{Constraints, Statistics};
use datafusion::datasource::file_format::file_compression_type::FileCompressionType; use datafusion::datasource::file_format::file_compression_type::FileCompressionType;
use datafusion::datasource::listing::PartitionedFile; use datafusion::datasource::listing::PartitionedFile;
use datafusion::datasource::object_store::ObjectStoreUrl; use datafusion::datasource::object_store::ObjectStoreUrl;
use datafusion::datasource::physical_plan::{ use datafusion::datasource::physical_plan::{
CsvSource, FileGroup, FileScanConfig, FileScanConfigBuilder, FileSource, FileStream, CsvConfig, CsvOpener, FileScanConfig, FileStream, JsonOpener,
JsonOpener, JsonSource,
}; };
use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet; use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
use object_store::services::Fs; use object_store::services::Fs;
@@ -68,20 +68,21 @@ pub fn test_basic_schema() -> SchemaRef {
Arc::new(schema) Arc::new(schema)
} }
pub(crate) fn scan_config( pub fn scan_config(file_schema: SchemaRef, limit: Option<usize>, filename: &str) -> FileScanConfig {
file_schema: SchemaRef,
limit: Option<usize>,
filename: &str,
file_source: Arc<dyn FileSource>,
) -> FileScanConfig {
// object_store only recognize the Unix style path, so make it happy. // object_store only recognize the Unix style path, so make it happy.
let filename = &filename.replace('\\', "/"); let filename = &filename.replace('\\', "/");
let file_group = FileGroup::new(vec![PartitionedFile::new(filename.to_string(), 4096)]); let statistics = Statistics::new_unknown(file_schema.as_ref());
FileScanConfig {
FileScanConfigBuilder::new(ObjectStoreUrl::local_filesystem(), file_schema, file_source) object_store_url: ObjectStoreUrl::parse("empty://").unwrap(), // won't be used
.with_file_group(file_group) file_schema,
.with_limit(limit) file_groups: vec![vec![PartitionedFile::new(filename.to_string(), 10)]],
.build() constraints: Constraints::empty(),
statistics,
projection: None,
limit,
table_partition_cols: vec![],
output_ordering: vec![],
}
} }
pub async fn setup_stream_to_json_test(origin_path: &str, threshold: impl Fn(usize) -> usize) { pub async fn setup_stream_to_json_test(origin_path: &str, threshold: impl Fn(usize) -> usize) {
@@ -98,14 +99,9 @@ pub async fn setup_stream_to_json_test(origin_path: &str, threshold: impl Fn(usi
let size = store.read(origin_path).await.unwrap().len(); let size = store.read(origin_path).await.unwrap().len();
let config = scan_config(schema, None, origin_path, Arc::new(JsonSource::new())); let config = scan_config(schema.clone(), None, origin_path);
let stream = FileStream::new(
&config, let stream = FileStream::new(&config, 0, json_opener, &ExecutionPlanMetricsSet::new()).unwrap();
0,
Arc::new(json_opener),
&ExecutionPlanMetricsSet::new(),
)
.unwrap();
let (tmp_store, dir) = test_tmp_store("test_stream_to_json"); let (tmp_store, dir) = test_tmp_store("test_stream_to_json");
@@ -131,17 +127,24 @@ pub async fn setup_stream_to_csv_test(origin_path: &str, threshold: impl Fn(usiz
let schema = test_basic_schema(); let schema = test_basic_schema();
let csv_source = CsvSource::new(true, b',', b'"') let csv_config = Arc::new(CsvConfig::new(
.with_schema(schema.clone()) TEST_BATCH_SIZE,
.with_batch_size(TEST_BATCH_SIZE); schema.clone(),
let config = scan_config(schema, None, origin_path, csv_source.clone()); None,
true,
b',',
b'"',
None,
Arc::new(object_store_opendal::OpendalStore::new(store.clone())),
None,
));
let csv_opener = CsvOpener::new(csv_config, FileCompressionType::UNCOMPRESSED);
let size = store.read(origin_path).await.unwrap().len(); let size = store.read(origin_path).await.unwrap().len();
let csv_opener = csv_source.create_file_opener( let config = scan_config(schema.clone(), None, origin_path);
Arc::new(object_store_opendal::OpendalStore::new(store.clone())),
&config,
0,
);
let stream = FileStream::new(&config, 0, csv_opener, &ExecutionPlanMetricsSet::new()).unwrap(); let stream = FileStream::new(&config, 0, csv_opener, &ExecutionPlanMetricsSet::new()).unwrap();
let (tmp_store, dir) = test_tmp_store("test_stream_to_csv"); let (tmp_store, dir) = test_tmp_store("test_stream_to_csv");

View File

@@ -251,6 +251,7 @@ macro_rules! define_from_tonic_status {
.get(key) .get(key)
.and_then(|v| String::from_utf8(v.as_bytes().to_vec()).ok()) .and_then(|v| String::from_utf8(v.as_bytes().to_vec()).ok())
} }
let code = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_CODE) let code = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_CODE)
.and_then(|s| { .and_then(|s| {
if let Ok(code) = s.parse::<u32>() { if let Ok(code) = s.parse::<u32>() {
@@ -289,8 +290,6 @@ macro_rules! define_into_tonic_status {
use tonic::metadata::MetadataMap; use tonic::metadata::MetadataMap;
use $crate::GREPTIME_DB_HEADER_ERROR_CODE; use $crate::GREPTIME_DB_HEADER_ERROR_CODE;
common_telemetry::error!(err; "Failed to handle request");
let mut headers = HeaderMap::<HeaderValue>::with_capacity(2); let mut headers = HeaderMap::<HeaderValue>::with_capacity(2);
// If either of the status_code or error msg cannot convert to valid HTTP header value // If either of the status_code or error msg cannot convert to valid HTTP header value

View File

@@ -12,9 +12,6 @@ common-error.workspace = true
common-macro.workspace = true common-macro.workspace = true
common-telemetry.workspace = true common-telemetry.workspace = true
common-time.workspace = true common-time.workspace = true
humantime.workspace = true
humantime-serde.workspace = true
itertools.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
snafu.workspace = true snafu.workspace = true

View File

@@ -22,6 +22,12 @@ use snafu::{Location, Snafu};
#[snafu(visibility(pub))] #[snafu(visibility(pub))]
#[stack_trace_debug] #[stack_trace_debug]
pub enum Error { pub enum Error {
#[snafu(display("No available frontend"))]
NoAvailableFrontend {
#[snafu(implicit)]
location: Location,
},
#[snafu(display("Mismatched schema, expected: {:?}, actual: {:?}", expected, actual))] #[snafu(display("Mismatched schema, expected: {:?}, actual: {:?}", expected, actual))]
MismatchedSchema { MismatchedSchema {
#[snafu(implicit)] #[snafu(implicit)]
@@ -63,7 +69,9 @@ impl ErrorExt for Error {
Error::MismatchedSchema { .. } | Error::SerializeEvent { .. } => { Error::MismatchedSchema { .. } | Error::SerializeEvent { .. } => {
StatusCode::InvalidArguments StatusCode::InvalidArguments
} }
Error::InsertEvents { .. } | Error::KvBackend { .. } => StatusCode::Internal, Error::NoAvailableFrontend { .. }
| Error::InsertEvents { .. }
| Error::KvBackend { .. } => StatusCode::Internal,
} }
} }

View File

@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#![feature(duration_constructors)]
pub mod error; pub mod error;
pub mod recorder; pub mod recorder;

View File

@@ -15,7 +15,7 @@
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::sync::Arc; use std::sync::{Arc, OnceLock};
use std::time::Duration; use std::time::Duration;
use api::v1::column_data_type_extension::TypeExt; use api::v1::column_data_type_extension::TypeExt;
@@ -28,8 +28,6 @@ use async_trait::async_trait;
use backon::{BackoffBuilder, ExponentialBuilder}; use backon::{BackoffBuilder, ExponentialBuilder};
use common_telemetry::{debug, error, info, warn}; use common_telemetry::{debug, error, info, warn};
use common_time::timestamp::{TimeUnit, Timestamp}; use common_time::timestamp::{TimeUnit, Timestamp};
use humantime::format_duration;
use itertools::Itertools;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use store_api::mito_engine_options::{APPEND_MODE_KEY, TTL_KEY}; use store_api::mito_engine_options::{APPEND_MODE_KEY, TTL_KEY};
use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::mpsc::{channel, Receiver, Sender};
@@ -52,12 +50,12 @@ pub const EVENTS_TABLE_TIMESTAMP_COLUMN_NAME: &str = "timestamp";
/// EventRecorderRef is the reference to the event recorder. /// EventRecorderRef is the reference to the event recorder.
pub type EventRecorderRef = Arc<dyn EventRecorder>; pub type EventRecorderRef = Arc<dyn EventRecorder>;
static EVENTS_TABLE_TTL: OnceLock<String> = OnceLock::new();
/// The time interval for flushing batched events to the event handler. /// The time interval for flushing batched events to the event handler.
pub const DEFAULT_FLUSH_INTERVAL_SECONDS: Duration = Duration::from_secs(5); pub const DEFAULT_FLUSH_INTERVAL_SECONDS: Duration = Duration::from_secs(5);
/// The default TTL(90 days) for the events table. // The default TTL for the events table.
const DEFAULT_EVENTS_TABLE_TTL: Duration = Duration::from_days(90); const DEFAULT_EVENTS_TABLE_TTL: &str = "30d";
/// The default compaction time window for the events table.
pub const DEFAULT_COMPACTION_TIME_WINDOW: Duration = Duration::from_days(1);
// The capacity of the tokio channel for transmitting events to background processor. // The capacity of the tokio channel for transmitting events to background processor.
const DEFAULT_CHANNEL_SIZE: usize = 2048; const DEFAULT_CHANNEL_SIZE: usize = 2048;
// The size of the buffer for batching events before flushing to event handler. // The size of the buffer for batching events before flushing to event handler.
@@ -74,11 +72,6 @@ const DEFAULT_MAX_RETRY_TIMES: u64 = 3;
/// ///
/// The event can also add the extra schema and row to the event by overriding the `extra_schema` and `extra_row` methods. /// The event can also add the extra schema and row to the event by overriding the `extra_schema` and `extra_row` methods.
pub trait Event: Send + Sync + Debug { pub trait Event: Send + Sync + Debug {
/// Returns the table name of the event.
fn table_name(&self) -> &str {
DEFAULT_EVENTS_TABLE_NAME
}
/// Returns the type of the event. /// Returns the type of the event.
fn event_type(&self) -> &str; fn event_type(&self) -> &str;
@@ -114,68 +107,88 @@ pub trait Eventable: Send + Sync + Debug {
} }
} }
/// Groups events by its `event_type`. /// Returns the hints for the insert operation.
#[allow(clippy::borrowed_box)] pub fn insert_hints() -> Vec<(&'static str, &'static str)> {
pub fn group_events_by_type(events: &[Box<dyn Event>]) -> HashMap<&str, Vec<&Box<dyn Event>>> { vec![
events (
.iter() TTL_KEY,
.into_grouping_map_by(|event| event.event_type()) EVENTS_TABLE_TTL
.collect() .get()
.map(|s| s.as_str())
.unwrap_or(DEFAULT_EVENTS_TABLE_TTL),
),
(APPEND_MODE_KEY, "true"),
]
} }
/// Builds the row inserts request for the events that will be persisted to the events table. The `events` should have the same event type, or it will return an error. /// Builds the row inserts request for the events that will be persisted to the events table.
#[allow(clippy::borrowed_box)] pub fn build_row_inserts_request(events: &[Box<dyn Event>]) -> Result<RowInsertRequests> {
pub fn build_row_inserts_request(events: &[&Box<dyn Event>]) -> Result<RowInsertRequests> { // Aggregate the events by the event type.
// Ensure all the events are the same type. let mut event_groups: HashMap<&str, Vec<&Box<dyn Event>>> = HashMap::new();
validate_events(events)?;
// We already validated the events, so it's safe to get the first event to build the schema for the RowInsertRequest.
let event = &events[0];
let mut schema: Vec<ColumnSchema> = Vec::with_capacity(3 + event.extra_schema().len());
schema.extend(vec![
ColumnSchema {
column_name: EVENTS_TABLE_TYPE_COLUMN_NAME.to_string(),
datatype: ColumnDataType::String.into(),
semantic_type: SemanticType::Tag.into(),
..Default::default()
},
ColumnSchema {
column_name: EVENTS_TABLE_PAYLOAD_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Binary as i32,
semantic_type: SemanticType::Field as i32,
datatype_extension: Some(ColumnDataTypeExtension {
type_ext: Some(TypeExt::JsonType(JsonTypeExtension::JsonBinary.into())),
}),
..Default::default()
},
ColumnSchema {
column_name: EVENTS_TABLE_TIMESTAMP_COLUMN_NAME.to_string(),
datatype: ColumnDataType::TimestampNanosecond.into(),
semantic_type: SemanticType::Timestamp.into(),
..Default::default()
},
]);
schema.extend(event.extra_schema());
let mut rows: Vec<Row> = Vec::with_capacity(events.len());
for event in events { for event in events {
let extra_row = event.extra_row()?; event_groups
let mut values = Vec::with_capacity(3 + extra_row.values.len()); .entry(event.event_type())
values.extend([ .or_default()
ValueData::StringValue(event.event_type().to_string()).into(), .push(event);
ValueData::BinaryValue(event.json_payload()?.into_bytes()).into(),
ValueData::TimestampNanosecondValue(event.timestamp().value()).into(),
]);
values.extend(extra_row.values);
rows.push(Row { values });
} }
Ok(RowInsertRequests { let mut row_insert_requests = RowInsertRequests {
inserts: vec![RowInsertRequest { inserts: Vec::with_capacity(event_groups.len()),
table_name: event.table_name().to_string(), };
for (_, events) in event_groups {
validate_events(&events)?;
// We already validated the events, so it's safe to get the first event to build the schema for the RowInsertRequest.
let event = &events[0];
let mut schema = vec![
ColumnSchema {
column_name: EVENTS_TABLE_TYPE_COLUMN_NAME.to_string(),
datatype: ColumnDataType::String.into(),
semantic_type: SemanticType::Tag.into(),
..Default::default()
},
ColumnSchema {
column_name: EVENTS_TABLE_PAYLOAD_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Binary as i32,
semantic_type: SemanticType::Field as i32,
datatype_extension: Some(ColumnDataTypeExtension {
type_ext: Some(TypeExt::JsonType(JsonTypeExtension::JsonBinary.into())),
}),
..Default::default()
},
ColumnSchema {
column_name: EVENTS_TABLE_TIMESTAMP_COLUMN_NAME.to_string(),
datatype: ColumnDataType::TimestampNanosecond.into(),
semantic_type: SemanticType::Timestamp.into(),
..Default::default()
},
];
schema.extend(event.extra_schema());
let rows = events
.iter()
.map(|event| {
let mut row = Row {
values: vec![
ValueData::StringValue(event.event_type().to_string()).into(),
ValueData::BinaryValue(event.json_payload()?.as_bytes().to_vec()).into(),
ValueData::TimestampNanosecondValue(event.timestamp().value()).into(),
],
};
row.values.extend(event.extra_row()?.values);
Ok(row)
})
.collect::<Result<Vec<_>>>()?;
row_insert_requests.inserts.push(RowInsertRequest {
table_name: DEFAULT_EVENTS_TABLE_NAME.to_string(),
rows: Some(Rows { schema, rows }), rows: Some(Rows { schema, rows }),
}], });
}) }
Ok(row_insert_requests)
} }
// Ensure the events with the same event type have the same extra schema. // Ensure the events with the same event type have the same extra schema.
@@ -204,34 +217,6 @@ pub trait EventRecorder: Send + Sync + Debug + 'static {
fn close(&self); fn close(&self);
} }
/// EventHandlerOptions is the options for the event handler.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EventHandlerOptions {
/// TTL for the events table that will be used to store the events.
pub ttl: Duration,
/// Append mode for the events table that will be used to store the events.
pub append_mode: bool,
}
impl Default for EventHandlerOptions {
fn default() -> Self {
Self {
ttl: DEFAULT_EVENTS_TABLE_TTL,
append_mode: true,
}
}
}
impl EventHandlerOptions {
/// Converts the options to the hints for the insert operation.
pub fn to_hints(&self) -> Vec<(&str, String)> {
vec![
(TTL_KEY, format_duration(self.ttl).to_string()),
(APPEND_MODE_KEY, self.append_mode.to_string()),
]
}
}
/// EventHandler trait defines the interface for how to handle the event. /// EventHandler trait defines the interface for how to handle the event.
#[async_trait] #[async_trait]
pub trait EventHandler: Send + Sync + 'static { pub trait EventHandler: Send + Sync + 'static {
@@ -244,14 +229,13 @@ pub trait EventHandler: Send + Sync + 'static {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EventRecorderOptions { pub struct EventRecorderOptions {
/// TTL for the events table that will be used to store the events. /// TTL for the events table that will be used to store the events.
#[serde(with = "humantime_serde")] pub ttl: String,
pub ttl: Duration,
} }
impl Default for EventRecorderOptions { impl Default for EventRecorderOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
ttl: DEFAULT_EVENTS_TABLE_TTL, ttl: DEFAULT_EVENTS_TABLE_TTL.to_string(),
} }
} }
} }
@@ -268,7 +252,9 @@ pub struct EventRecorderImpl {
} }
impl EventRecorderImpl { impl EventRecorderImpl {
pub fn new(event_handler: Box<dyn EventHandler>) -> Self { pub fn new(event_handler: Box<dyn EventHandler>, opts: EventRecorderOptions) -> Self {
info!("Creating event recorder with options: {:?}", opts);
let (tx, rx) = channel(DEFAULT_CHANNEL_SIZE); let (tx, rx) = channel(DEFAULT_CHANNEL_SIZE);
let cancel_token = CancellationToken::new(); let cancel_token = CancellationToken::new();
@@ -293,6 +279,14 @@ impl EventRecorderImpl {
recorder.handle = Some(handle); recorder.handle = Some(handle);
// It only sets the ttl once, so it's safe to skip the error.
if EVENTS_TABLE_TTL.set(opts.ttl.clone()).is_err() {
info!(
"Events table ttl already set to {}, skip setting it",
opts.ttl
);
}
recorder recorder
} }
} }
@@ -477,7 +471,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_event_recorder() { async fn test_event_recorder() {
let mut event_recorder = EventRecorderImpl::new(Box::new(TestEventHandlerImpl {})); let mut event_recorder = EventRecorderImpl::new(
Box::new(TestEventHandlerImpl {}),
EventRecorderOptions::default(),
);
event_recorder.record(Box::new(TestEvent {})); event_recorder.record(Box::new(TestEvent {}));
// Sleep for a while to let the event be sent to the event handler. // Sleep for a while to let the event be sent to the event handler.
@@ -518,8 +515,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_event_recorder_should_panic() { async fn test_event_recorder_should_panic() {
let mut event_recorder = let mut event_recorder = EventRecorderImpl::new(
EventRecorderImpl::new(Box::new(TestEventHandlerImplShouldPanic {})); Box::new(TestEventHandlerImplShouldPanic {}),
EventRecorderOptions::default(),
);
event_recorder.record(Box::new(TestEvent {})); event_recorder.record(Box::new(TestEvent {}));
@@ -536,135 +535,4 @@ mod tests {
assert!(handle.await.unwrap_err().is_panic()); assert!(handle.await.unwrap_err().is_panic());
} }
} }
#[derive(Debug)]
struct TestEventA {}
impl Event for TestEventA {
fn event_type(&self) -> &str {
"A"
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
struct TestEventB {}
impl Event for TestEventB {
fn table_name(&self) -> &str {
"table_B"
}
fn event_type(&self) -> &str {
"B"
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[derive(Debug)]
struct TestEventC {}
impl Event for TestEventC {
fn table_name(&self) -> &str {
"table_C"
}
fn event_type(&self) -> &str {
"C"
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[test]
fn test_group_events_by_type() {
let events: Vec<Box<dyn Event>> = vec![
Box::new(TestEventA {}),
Box::new(TestEventB {}),
Box::new(TestEventA {}),
Box::new(TestEventC {}),
Box::new(TestEventB {}),
Box::new(TestEventC {}),
Box::new(TestEventA {}),
];
let event_groups = group_events_by_type(&events);
assert_eq!(event_groups.len(), 3);
assert_eq!(event_groups.get("A").unwrap().len(), 3);
assert_eq!(event_groups.get("B").unwrap().len(), 2);
assert_eq!(event_groups.get("C").unwrap().len(), 2);
}
#[test]
fn test_build_row_inserts_request() {
let events: Vec<Box<dyn Event>> = vec![
Box::new(TestEventA {}),
Box::new(TestEventB {}),
Box::new(TestEventA {}),
Box::new(TestEventC {}),
Box::new(TestEventB {}),
Box::new(TestEventC {}),
Box::new(TestEventA {}),
];
let event_groups = group_events_by_type(&events);
assert_eq!(event_groups.len(), 3);
assert_eq!(event_groups.get("A").unwrap().len(), 3);
assert_eq!(event_groups.get("B").unwrap().len(), 2);
assert_eq!(event_groups.get("C").unwrap().len(), 2);
for (event_type, events) in event_groups {
let row_inserts_request = build_row_inserts_request(&events).unwrap();
if event_type == "A" {
assert_eq!(row_inserts_request.inserts.len(), 1);
assert_eq!(
row_inserts_request.inserts[0].table_name,
DEFAULT_EVENTS_TABLE_NAME
);
assert_eq!(
row_inserts_request.inserts[0]
.rows
.as_ref()
.unwrap()
.rows
.len(),
3
);
} else if event_type == "B" {
assert_eq!(row_inserts_request.inserts.len(), 1);
assert_eq!(row_inserts_request.inserts[0].table_name, "table_B");
assert_eq!(
row_inserts_request.inserts[0]
.rows
.as_ref()
.unwrap()
.rows
.len(),
2
);
} else if event_type == "C" {
assert_eq!(row_inserts_request.inserts.len(), 1);
assert_eq!(row_inserts_request.inserts[0].table_name, "table_C");
assert_eq!(
row_inserts_request.inserts[0]
.rows
.as_ref()
.unwrap()
.rows
.len(),
2
);
} else {
panic!("Unexpected event type: {}", event_type);
}
}
}
} }

View File

@@ -5,18 +5,13 @@ edition.workspace = true
license.workspace = true license.workspace = true
[dependencies] [dependencies]
api.workspace = true
async-trait.workspace = true async-trait.workspace = true
common-error.workspace = true common-error.workspace = true
common-event-recorder.workspace = true
common-grpc.workspace = true common-grpc.workspace = true
common-macro.workspace = true common-macro.workspace = true
common-meta.workspace = true common-meta.workspace = true
common-time.workspace = true
greptime-proto.workspace = true greptime-proto.workspace = true
humantime.workspace = true
meta-client.workspace = true meta-client.workspace = true
serde.workspace = true
session.workspace = true session.workspace = true
snafu.workspace = true snafu.workspace = true
tonic.workspace = true tonic.workspace = true

View File

@@ -12,121 +12,17 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::any::Any; use session::context::QueryContextRef;
use api::v1::value::ValueData; #[derive(Debug)]
use api::v1::{ColumnDataType, ColumnSchema, Row, SemanticType};
use common_event_recorder::error::Result;
use common_event_recorder::Event;
use serde::Serialize;
pub const SLOW_QUERY_TABLE_NAME: &str = "slow_queries";
pub const SLOW_QUERY_TABLE_COST_COLUMN_NAME: &str = "cost";
pub const SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME: &str = "threshold";
pub const SLOW_QUERY_TABLE_QUERY_COLUMN_NAME: &str = "query";
pub const SLOW_QUERY_TABLE_TIMESTAMP_COLUMN_NAME: &str = "timestamp";
pub const SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME: &str = "is_promql";
pub const SLOW_QUERY_TABLE_PROMQL_START_COLUMN_NAME: &str = "promql_start";
pub const SLOW_QUERY_TABLE_PROMQL_END_COLUMN_NAME: &str = "promql_end";
pub const SLOW_QUERY_TABLE_PROMQL_RANGE_COLUMN_NAME: &str = "promql_range";
pub const SLOW_QUERY_TABLE_PROMQL_STEP_COLUMN_NAME: &str = "promql_step";
pub const SLOW_QUERY_EVENT_TYPE: &str = "slow_query";
/// SlowQueryEvent is the event of slow query.
#[derive(Debug, Serialize)]
pub struct SlowQueryEvent { pub struct SlowQueryEvent {
pub cost: u64, pub cost: u64,
pub threshold: u64, pub threshold: u64,
pub query: String, pub query: String,
pub is_promql: bool, pub is_promql: bool,
pub query_ctx: QueryContextRef,
pub promql_range: Option<u64>, pub promql_range: Option<u64>,
pub promql_step: Option<u64>, pub promql_step: Option<u64>,
pub promql_start: Option<i64>, pub promql_start: Option<i64>,
pub promql_end: Option<i64>, pub promql_end: Option<i64>,
} }
impl Event for SlowQueryEvent {
fn table_name(&self) -> &str {
SLOW_QUERY_TABLE_NAME
}
fn event_type(&self) -> &str {
SLOW_QUERY_EVENT_TYPE
}
fn extra_schema(&self) -> Vec<ColumnSchema> {
vec![
ColumnSchema {
column_name: SLOW_QUERY_TABLE_COST_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Uint64.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_THRESHOLD_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Uint64.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_QUERY_COLUMN_NAME.to_string(),
datatype: ColumnDataType::String.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_IS_PROMQL_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Boolean.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_PROMQL_RANGE_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Uint64.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_PROMQL_STEP_COLUMN_NAME.to_string(),
datatype: ColumnDataType::Uint64.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_PROMQL_START_COLUMN_NAME.to_string(),
datatype: ColumnDataType::TimestampMillisecond.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
ColumnSchema {
column_name: SLOW_QUERY_TABLE_PROMQL_END_COLUMN_NAME.to_string(),
datatype: ColumnDataType::TimestampMillisecond.into(),
semantic_type: SemanticType::Field.into(),
..Default::default()
},
]
}
fn extra_row(&self) -> Result<Row> {
Ok(Row {
values: vec![
ValueData::U64Value(self.cost).into(),
ValueData::U64Value(self.threshold).into(),
ValueData::StringValue(self.query.to_string()).into(),
ValueData::BoolValue(self.is_promql).into(),
ValueData::U64Value(self.promql_range.unwrap_or(0)).into(),
ValueData::U64Value(self.promql_step.unwrap_or(0)).into(),
ValueData::TimestampMillisecondValue(self.promql_start.unwrap_or(0)).into(),
ValueData::TimestampMillisecondValue(self.promql_end.unwrap_or(0)).into(),
],
})
}
fn json_payload(&self) -> Result<String> {
Ok("".to_string())
}
fn as_any(&self) -> &dyn Any {
self
}
}

View File

@@ -57,6 +57,7 @@ serde_json.workspace = true
session.workspace = true session.workspace = true
snafu.workspace = true snafu.workspace = true
sql.workspace = true sql.workspace = true
statrs = "0.16"
store-api.workspace = true store-api.workspace = true
table.workspace = true table.workspace = true
uddsketch = { git = "https://github.com/GreptimeTeam/timescaledb-toolkit.git", rev = "84828fe8fb494a6a61412a3da96517fc80f7bb20" } uddsketch = { git = "https://github.com/GreptimeTeam/timescaledb-toolkit.git", rev = "84828fe8fb494a6a61412a3da96517fc80f7bb20" }

View File

@@ -21,6 +21,8 @@ mod reconcile_database;
mod reconcile_table; mod reconcile_table;
mod remove_region_follower; mod remove_region_follower;
use std::sync::Arc;
use add_region_follower::AddRegionFollowerFunction; use add_region_follower::AddRegionFollowerFunction;
use flush_compact_region::{CompactRegionFunction, FlushRegionFunction}; use flush_compact_region::{CompactRegionFunction, FlushRegionFunction};
use flush_compact_table::{CompactTableFunction, FlushTableFunction}; use flush_compact_table::{CompactTableFunction, FlushTableFunction};
@@ -33,22 +35,22 @@ use remove_region_follower::RemoveRegionFollowerFunction;
use crate::flush_flow::FlushFlowFunction; use crate::flush_flow::FlushFlowFunction;
use crate::function_registry::FunctionRegistry; use crate::function_registry::FunctionRegistry;
/// Administration functions /// Table functions
pub(crate) struct AdminFunction; pub(crate) struct AdminFunction;
impl AdminFunction { impl AdminFunction {
/// Register all admin functions to [`FunctionRegistry`]. /// Register all table functions to [`FunctionRegistry`].
pub fn register(registry: &FunctionRegistry) { pub fn register(registry: &FunctionRegistry) {
registry.register(MigrateRegionFunction::factory()); registry.register_async(Arc::new(MigrateRegionFunction));
registry.register(AddRegionFollowerFunction::factory()); registry.register_async(Arc::new(AddRegionFollowerFunction));
registry.register(RemoveRegionFollowerFunction::factory()); registry.register_async(Arc::new(RemoveRegionFollowerFunction));
registry.register(FlushRegionFunction::factory()); registry.register_async(Arc::new(FlushRegionFunction));
registry.register(CompactRegionFunction::factory()); registry.register_async(Arc::new(CompactRegionFunction));
registry.register(FlushTableFunction::factory()); registry.register_async(Arc::new(FlushTableFunction));
registry.register(CompactTableFunction::factory()); registry.register_async(Arc::new(CompactTableFunction));
registry.register(FlushFlowFunction::factory()); registry.register_async(Arc::new(FlushFlowFunction));
registry.register(ReconcileCatalogFunction::factory()); registry.register_async(Arc::new(ReconcileCatalogFunction));
registry.register(ReconcileDatabaseFunction::factory()); registry.register_async(Arc::new(ReconcileDatabaseFunction));
registry.register(ReconcileTableFunction::factory()); registry.register_async(Arc::new(ReconcileTableFunction));
} }
} }

View File

@@ -18,8 +18,7 @@ use common_query::error::{
InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result, InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use datafusion_expr::{Signature, TypeSignature, Volatility}; use common_query::prelude::{Signature, TypeSignature, Volatility};
use datatypes::data_type::DataType;
use datatypes::prelude::ConcreteDataType; use datatypes::prelude::ConcreteDataType;
use datatypes::value::{Value, ValueRef}; use datatypes::value::{Value, ValueRef};
use session::context::QueryContextRef; use session::context::QueryContextRef;
@@ -83,13 +82,7 @@ fn signature() -> Signature {
Signature::one_of( Signature::one_of(
vec![ vec![
// add_region_follower(region_id, peer) // add_region_follower(region_id, peer)
TypeSignature::Uniform( TypeSignature::Uniform(2, ConcreteDataType::numerics()),
2,
ConcreteDataType::numerics()
.into_iter()
.map(|dt| dt.as_arrow_type())
.collect(),
),
], ],
Volatility::Immutable, Volatility::Immutable,
) )
@@ -99,57 +92,38 @@ fn signature() -> Signature {
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use arrow::array::UInt64Array; use common_query::prelude::TypeSignature;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::{UInt64Vector, VectorRef};
use datafusion_expr::ColumnarValue;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[test] #[test]
fn test_add_region_follower_misc() { fn test_add_region_follower_misc() {
let factory: ScalarFunctionFactory = AddRegionFollowerFunction::factory().into(); let f = AddRegionFollowerFunction;
let f = factory.provide(FunctionContext::mock());
assert_eq!("add_region_follower", f.name()); assert_eq!("add_region_follower", f.name());
assert_eq!(DataType::UInt64, f.return_type(&[]).unwrap()); assert_eq!(
ConcreteDataType::uint64_datatype(),
f.return_type(&[]).unwrap()
);
assert!(matches!(f.signature(), assert!(matches!(f.signature(),
datafusion_expr::Signature { Signature {
type_signature: datafusion_expr::TypeSignature::OneOf(sigs), type_signature: TypeSignature::OneOf(sigs),
volatility: datafusion_expr::Volatility::Immutable volatility: Volatility::Immutable
} if sigs.len() == 1)); } if sigs.len() == 1));
} }
#[tokio::test] #[tokio::test]
async fn test_add_region_follower() { async fn test_add_region_follower() {
let factory: ScalarFunctionFactory = AddRegionFollowerFunction::factory().into(); let f = AddRegionFollowerFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![1, 1];
let f = provider.as_async().unwrap(); let args = args
.into_iter()
.map(|arg| Arc::new(UInt64Vector::from_slice([arg])) as _)
.collect::<Vec<_>>();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
args: vec![ let expect: VectorRef = Arc::new(UInt64Vector::from_slice([0u64]));
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))), assert_eq!(result, expect);
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![2]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::UInt64, false)),
Arc::new(Field::new("arg_1", DataType::UInt64, false)),
],
return_field: Arc::new(Field::new("result", DataType::UInt64, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<UInt64Array>().unwrap();
assert_eq!(result_array.value(0), 0u64);
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(scalar, datafusion_common::ScalarValue::UInt64(Some(0)));
}
}
} }
} }

View File

@@ -16,8 +16,7 @@ use common_macro::admin_fn;
use common_query::error::{ use common_query::error::{
InvalidFuncArgsSnafu, MissingTableMutationHandlerSnafu, Result, UnsupportedInputDataTypeSnafu, InvalidFuncArgsSnafu, MissingTableMutationHandlerSnafu, Result, UnsupportedInputDataTypeSnafu,
}; };
use datafusion_expr::{Signature, Volatility}; use common_query::prelude::{Signature, Volatility};
use datatypes::data_type::DataType;
use datatypes::prelude::*; use datatypes::prelude::*;
use session::context::QueryContextRef; use session::context::QueryContextRef;
use snafu::ensure; use snafu::ensure;
@@ -67,99 +66,71 @@ define_region_function!(FlushRegionFunction, flush_region, flush_region);
define_region_function!(CompactRegionFunction, compact_region, compact_region); define_region_function!(CompactRegionFunction, compact_region, compact_region);
fn signature() -> Signature { fn signature() -> Signature {
Signature::uniform( Signature::uniform(1, ConcreteDataType::numerics(), Volatility::Immutable)
1,
ConcreteDataType::numerics()
.into_iter()
.map(|dt| dt.as_arrow_type())
.collect(),
Volatility::Immutable,
)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use arrow::array::UInt64Array; use common_query::prelude::TypeSignature;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::UInt64Vector;
use datafusion_expr::ColumnarValue;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
macro_rules! define_region_function_test { macro_rules! define_region_function_test {
($name: ident, $func: ident) => { ($name: ident, $func: ident) => {
paste::paste! { paste::paste! {
#[test] #[test]
fn [<test_ $name _misc>]() { fn [<test_ $name _misc>]() {
let factory: ScalarFunctionFactory = $func::factory().into(); let f = $func;
let f = factory.provide(FunctionContext::mock());
assert_eq!(stringify!($name), f.name()); assert_eq!(stringify!($name), f.name());
assert_eq!( assert_eq!(
DataType::UInt64, ConcreteDataType::uint64_datatype(),
f.return_type(&[]).unwrap() f.return_type(&[]).unwrap()
); );
assert!(matches!(f.signature(), assert!(matches!(f.signature(),
datafusion_expr::Signature { Signature {
type_signature: datafusion_expr::TypeSignature::Uniform(1, valid_types), type_signature: TypeSignature::Uniform(1, valid_types),
volatility: datafusion_expr::Volatility::Immutable volatility: Volatility::Immutable
} if valid_types == &ConcreteDataType::numerics().into_iter().map(|dt| { use datatypes::data_type::DataType; dt.as_arrow_type() }).collect::<Vec<_>>())); } if valid_types == ConcreteDataType::numerics()));
} }
#[tokio::test] #[tokio::test]
async fn [<test_ $name _missing_table_mutation>]() { async fn [<test_ $name _missing_table_mutation>]() {
let factory: ScalarFunctionFactory = $func::factory().into(); let f = $func;
let provider = factory.provide(FunctionContext::default());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let args = vec![99];
args: vec![
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![99]))), let args = args
], .into_iter()
arg_fields: vec![ .map(|arg| Arc::new(UInt64Vector::from_slice([arg])) as _)
Arc::new(Field::new("arg_0", DataType::UInt64, false)), .collect::<Vec<_>>();
],
return_field: Arc::new(Field::new("result", DataType::UInt64, true)), let result = f.eval(FunctionContext::default(), &args).await.unwrap_err();
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap_err();
assert_eq!( assert_eq!(
"Execution error: Handler error: Missing TableMutationHandler, not expected", "Missing TableMutationHandler, not expected",
result.to_string() result.to_string()
); );
} }
#[tokio::test] #[tokio::test]
async fn [<test_ $name>]() { async fn [<test_ $name>]() {
let factory: ScalarFunctionFactory = $func::factory().into(); let f = $func;
let provider = factory.provide(FunctionContext::mock());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs {
args: vec![
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![99]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::UInt64, false)),
],
return_field: Arc::new(Field::new("result", DataType::UInt64, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result { let args = vec![99];
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<UInt64Array>().unwrap(); let args = args
assert_eq!(result_array.value(0), 42u64); .into_iter()
} .map(|arg| Arc::new(UInt64Vector::from_slice([arg])) as _)
ColumnarValue::Scalar(scalar) => { .collect::<Vec<_>>();
assert_eq!(scalar, datafusion_common::ScalarValue::UInt64(Some(42)));
} let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
}
let expect: VectorRef = Arc::new(UInt64Vector::from_slice([42]));
assert_eq!(expect, result);
} }
} }
}; };

View File

@@ -15,15 +15,14 @@
use std::str::FromStr; use std::str::FromStr;
use api::v1::region::{compact_request, StrictWindow}; use api::v1::region::{compact_request, StrictWindow};
use arrow::datatypes::DataType as ArrowDataType;
use common_error::ext::BoxedError; use common_error::ext::BoxedError;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_query::error::{ use common_query::error::{
InvalidFuncArgsSnafu, MissingTableMutationHandlerSnafu, Result, TableMutationSnafu, InvalidFuncArgsSnafu, MissingTableMutationHandlerSnafu, Result, TableMutationSnafu,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use common_query::prelude::{Signature, Volatility};
use common_telemetry::info; use common_telemetry::info;
use datafusion_expr::{Signature, Volatility};
use datatypes::prelude::*; use datatypes::prelude::*;
use session::context::QueryContextRef; use session::context::QueryContextRef;
use session::table_name::table_name_to_full_name; use session::table_name::table_name_to_full_name;
@@ -106,11 +105,18 @@ pub(crate) async fn compact_table(
} }
fn flush_signature() -> Signature { fn flush_signature() -> Signature {
Signature::uniform(1, vec![ArrowDataType::Utf8], Volatility::Immutable) Signature::uniform(
1,
vec![ConcreteDataType::string_datatype()],
Volatility::Immutable,
)
} }
fn compact_signature() -> Signature { fn compact_signature() -> Signature {
Signature::variadic(vec![ArrowDataType::Utf8], Volatility::Immutable) Signature::variadic(
vec![ConcreteDataType::string_datatype()],
Volatility::Immutable,
)
} }
/// Parses `compact_table` UDF parameters. This function accepts following combinations: /// Parses `compact_table` UDF parameters. This function accepts following combinations:
@@ -198,87 +204,66 @@ mod tests {
use std::sync::Arc; use std::sync::Arc;
use api::v1::region::compact_request::Options; use api::v1::region::compact_request::Options;
use arrow::array::StringArray;
use arrow::datatypes::{DataType, Field};
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME}; use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
use datafusion_expr::ColumnarValue; use common_query::prelude::TypeSignature;
use datatypes::vectors::{StringVector, UInt64Vector};
use session::context::QueryContext; use session::context::QueryContext;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
macro_rules! define_table_function_test { macro_rules! define_table_function_test {
($name: ident, $func: ident) => { ($name: ident, $func: ident) => {
paste::paste!{ paste::paste!{
#[test] #[test]
fn [<test_ $name _misc>]() { fn [<test_ $name _misc>]() {
let factory: ScalarFunctionFactory = $func::factory().into(); let f = $func;
let f = factory.provide(FunctionContext::mock());
assert_eq!(stringify!($name), f.name()); assert_eq!(stringify!($name), f.name());
assert_eq!( assert_eq!(
DataType::UInt64, ConcreteDataType::uint64_datatype(),
f.return_type(&[]).unwrap() f.return_type(&[]).unwrap()
); );
assert!(matches!(f.signature(), assert!(matches!(f.signature(),
datafusion_expr::Signature { Signature {
type_signature: datafusion_expr::TypeSignature::Uniform(1, valid_types), type_signature: TypeSignature::Uniform(1, valid_types),
volatility: datafusion_expr::Volatility::Immutable volatility: Volatility::Immutable
} if valid_types == &vec![ArrowDataType::Utf8])); } if valid_types == vec![ConcreteDataType::string_datatype()]));
} }
#[tokio::test] #[tokio::test]
async fn [<test_ $name _missing_table_mutation>]() { async fn [<test_ $name _missing_table_mutation>]() {
let factory: ScalarFunctionFactory = $func::factory().into(); let f = $func;
let provider = factory.provide(FunctionContext::default());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let args = vec!["test"];
args: vec![
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))), let args = args
], .into_iter()
arg_fields: vec![ .map(|arg| Arc::new(StringVector::from(vec![arg])) as _)
Arc::new(Field::new("arg_0", DataType::Utf8, false)), .collect::<Vec<_>>();
],
return_field: Arc::new(Field::new("result", DataType::UInt64, true)), let result = f.eval(FunctionContext::default(), &args).await.unwrap_err();
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap_err();
assert_eq!( assert_eq!(
"Execution error: Handler error: Missing TableMutationHandler, not expected", "Missing TableMutationHandler, not expected",
result.to_string() result.to_string()
); );
} }
#[tokio::test] #[tokio::test]
async fn [<test_ $name>]() { async fn [<test_ $name>]() {
let factory: ScalarFunctionFactory = $func::factory().into(); let f = $func;
let provider = factory.provide(FunctionContext::mock());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs {
args: vec![
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::UInt64, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result { let args = vec!["test"];
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<arrow::array::UInt64Array>().unwrap(); let args = args
assert_eq!(result_array.value(0), 42u64); .into_iter()
} .map(|arg| Arc::new(StringVector::from(vec![arg])) as _)
ColumnarValue::Scalar(scalar) => { .collect::<Vec<_>>();
assert_eq!(scalar, datafusion_common::ScalarValue::UInt64(Some(42)));
} let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
}
let expect: VectorRef = Arc::new(UInt64Vector::from_slice([42]));
assert_eq!(expect, result);
} }
} }
} }

View File

@@ -17,8 +17,7 @@ use std::time::Duration;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_meta::rpc::procedure::MigrateRegionRequest; use common_meta::rpc::procedure::MigrateRegionRequest;
use common_query::error::{InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result}; use common_query::error::{InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result};
use datafusion_expr::{Signature, TypeSignature, Volatility}; use common_query::prelude::{Signature, TypeSignature, Volatility};
use datatypes::data_type::DataType;
use datatypes::prelude::ConcreteDataType; use datatypes::prelude::ConcreteDataType;
use datatypes::value::{Value, ValueRef}; use datatypes::value::{Value, ValueRef};
use session::context::QueryContextRef; use session::context::QueryContextRef;
@@ -104,21 +103,9 @@ fn signature() -> Signature {
Signature::one_of( Signature::one_of(
vec![ vec![
// migrate_region(region_id, from_peer, to_peer) // migrate_region(region_id, from_peer, to_peer)
TypeSignature::Uniform( TypeSignature::Uniform(3, ConcreteDataType::numerics()),
3,
ConcreteDataType::numerics()
.into_iter()
.map(|dt| dt.as_arrow_type())
.collect(),
),
// migrate_region(region_id, from_peer, to_peer, timeout(secs)) // migrate_region(region_id, from_peer, to_peer, timeout(secs))
TypeSignature::Uniform( TypeSignature::Uniform(4, ConcreteDataType::numerics()),
4,
ConcreteDataType::numerics()
.into_iter()
.map(|dt| dt.as_arrow_type())
.collect(),
),
], ],
Volatility::Immutable, Volatility::Immutable,
) )
@@ -128,89 +115,59 @@ fn signature() -> Signature {
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use arrow::array::{StringArray, UInt64Array}; use common_query::prelude::TypeSignature;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::{StringVector, UInt64Vector, VectorRef};
use datafusion_expr::ColumnarValue;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[test] #[test]
fn test_migrate_region_misc() { fn test_migrate_region_misc() {
let factory: ScalarFunctionFactory = MigrateRegionFunction::factory().into(); let f = MigrateRegionFunction;
let f = factory.provide(FunctionContext::mock());
assert_eq!("migrate_region", f.name()); assert_eq!("migrate_region", f.name());
assert_eq!(DataType::Utf8, f.return_type(&[]).unwrap()); assert_eq!(
ConcreteDataType::string_datatype(),
f.return_type(&[]).unwrap()
);
assert!(matches!(f.signature(), assert!(matches!(f.signature(),
datafusion_expr::Signature { Signature {
type_signature: datafusion_expr::TypeSignature::OneOf(sigs), type_signature: TypeSignature::OneOf(sigs),
volatility: datafusion_expr::Volatility::Immutable volatility: Volatility::Immutable
} if sigs.len() == 2)); } if sigs.len() == 2));
} }
#[tokio::test] #[tokio::test]
async fn test_missing_procedure_service() { async fn test_missing_procedure_service() {
let factory: ScalarFunctionFactory = MigrateRegionFunction::factory().into(); let f = MigrateRegionFunction;
let provider = factory.provide(FunctionContext::default());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let args = vec![1, 1, 1];
args: vec![
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))), let args = args
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))), .into_iter()
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))), .map(|arg| Arc::new(UInt64Vector::from_slice([arg])) as _)
], .collect::<Vec<_>>();
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::UInt64, false)), let result = f.eval(FunctionContext::default(), &args).await.unwrap_err();
Arc::new(Field::new("arg_1", DataType::UInt64, false)),
Arc::new(Field::new("arg_2", DataType::UInt64, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap_err();
assert_eq!( assert_eq!(
"Execution error: Handler error: Missing ProcedureServiceHandler, not expected", "Missing ProcedureServiceHandler, not expected",
result.to_string() result.to_string()
); );
} }
#[tokio::test] #[tokio::test]
async fn test_migrate_region() { async fn test_migrate_region() {
let factory: ScalarFunctionFactory = MigrateRegionFunction::factory().into(); let f = MigrateRegionFunction;
let provider = factory.provide(FunctionContext::mock());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let args = vec![1, 1, 1];
args: vec![
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))),
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))),
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::UInt64, false)),
Arc::new(Field::new("arg_1", DataType::UInt64, false)),
Arc::new(Field::new("arg_2", DataType::UInt64, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result { let args = args
ColumnarValue::Array(array) => { .into_iter()
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap(); .map(|arg| Arc::new(UInt64Vector::from_slice([arg])) as _)
assert_eq!(result_array.value(0), "test_pid"); .collect::<Vec<_>>();
}
ColumnarValue::Scalar(scalar) => { let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
assert_eq!(
scalar, let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string())) assert_eq!(expect, result);
);
}
}
} }
} }

View File

@@ -14,15 +14,13 @@
use api::v1::meta::reconcile_request::Target; use api::v1::meta::reconcile_request::Target;
use api::v1::meta::{ReconcileCatalog, ReconcileRequest}; use api::v1::meta::{ReconcileCatalog, ReconcileRequest};
use arrow::datatypes::DataType as ArrowDataType;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_query::error::{ use common_query::error::{
InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result, InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use common_query::prelude::{Signature, TypeSignature, Volatility};
use common_telemetry::info; use common_telemetry::info;
use datafusion_expr::{Signature, TypeSignature, Volatility};
use datatypes::data_type::DataType;
use datatypes::prelude::*; use datatypes::prelude::*;
use session::context::QueryContextRef; use session::context::QueryContextRef;
@@ -106,15 +104,15 @@ fn signature() -> Signature {
let mut signs = Vec::with_capacity(2 + nums.len()); let mut signs = Vec::with_capacity(2 + nums.len());
signs.extend([ signs.extend([
// reconcile_catalog() // reconcile_catalog()
TypeSignature::Nullary, TypeSignature::NullAry,
// reconcile_catalog(resolve_strategy) // reconcile_catalog(resolve_strategy)
TypeSignature::Exact(vec![ArrowDataType::Utf8]), TypeSignature::Exact(vec![ConcreteDataType::string_datatype()]),
]); ]);
for sign in nums { for sign in nums {
// reconcile_catalog(resolve_strategy, parallelism) // reconcile_catalog(resolve_strategy, parallelism)
signs.push(TypeSignature::Exact(vec![ signs.push(TypeSignature::Exact(vec![
ArrowDataType::Utf8, ConcreteDataType::string_datatype(),
sign.as_arrow_type(), sign,
])); ]));
} }
Signature::one_of(signs, Volatility::Immutable) Signature::one_of(signs, Volatility::Immutable)
@@ -122,149 +120,60 @@ fn signature() -> Signature {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::assert_matches::assert_matches;
use std::sync::Arc; use std::sync::Arc;
use arrow::array::{StringArray, UInt64Array}; use common_query::error::Error;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::{StringVector, UInt64Vector, VectorRef};
use datafusion_expr::ColumnarValue;
use crate::admin::reconcile_catalog::ReconcileCatalogFunction; use crate::admin::reconcile_catalog::ReconcileCatalogFunction;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[tokio::test] #[tokio::test]
async fn test_reconcile_catalog() { async fn test_reconcile_catalog() {
common_telemetry::init_default_ut_logging(); common_telemetry::init_default_ut_logging();
// reconcile_catalog() // reconcile_catalog()
let factory: ScalarFunctionFactory = ReconcileCatalogFunction::factory().into(); let f = ReconcileCatalogFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![];
let f = provider.as_async().unwrap(); let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
let func_args = datafusion::logical_expr::ScalarFunctionArgs { assert_eq!(expect, result);
args: vec![],
arg_fields: vec![],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// reconcile_catalog(resolve_strategy) // reconcile_catalog(resolve_strategy)
let factory: ScalarFunctionFactory = ReconcileCatalogFunction::factory().into(); let f = ReconcileCatalogFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![Arc::new(StringVector::from(vec!["UseMetasrv"])) as _];
let f = provider.as_async().unwrap(); let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
let func_args = datafusion::logical_expr::ScalarFunctionArgs { assert_eq!(expect, result);
args: vec![ColumnarValue::Array(Arc::new(StringArray::from(vec![
"UseMetasrv",
])))],
arg_fields: vec![Arc::new(Field::new("arg_0", DataType::Utf8, false))],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// reconcile_catalog(resolve_strategy, parallelism) // reconcile_catalog(resolve_strategy, parallelism)
let factory: ScalarFunctionFactory = ReconcileCatalogFunction::factory().into(); let f = ReconcileCatalogFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["UseLatest"])) as _,
Arc::new(UInt64Vector::from_slice([10])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { ];
args: vec![ let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![10]))), assert_eq!(expect, result);
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::UInt64, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// unsupported input data type // unsupported input data type
let factory: ScalarFunctionFactory = ReconcileCatalogFunction::factory().into(); let f = ReconcileCatalogFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["UseLatest"])) as _,
Arc::new(StringVector::from(vec!["test"])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { ];
args: vec![ let err = f.eval(FunctionContext::mock(), &args).await.unwrap_err();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), assert_matches!(err, Error::UnsupportedInputDataType { .. });
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let _err = f.invoke_async_with_args(func_args).await.unwrap_err();
// Note: Error type is DataFusionError at this level, not common_query::Error
// invalid function args // invalid function args
let factory: ScalarFunctionFactory = ReconcileCatalogFunction::factory().into(); let f = ReconcileCatalogFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["UseLatest"])) as _,
Arc::new(UInt64Vector::from_slice([10])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { Arc::new(StringVector::from(vec!["10"])) as _,
args: vec![ ];
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), let err = f.eval(FunctionContext::mock(), &args).await.unwrap_err();
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![10]))), assert_matches!(err, Error::InvalidFuncArgs { .. });
ColumnarValue::Array(Arc::new(StringArray::from(vec!["10"]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::UInt64, false)),
Arc::new(Field::new("arg_2", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let _err = f.invoke_async_with_args(func_args).await.unwrap_err();
// Note: Error type is DataFusionError at this level, not common_query::Error
} }
} }

View File

@@ -14,15 +14,13 @@
use api::v1::meta::reconcile_request::Target; use api::v1::meta::reconcile_request::Target;
use api::v1::meta::{ReconcileDatabase, ReconcileRequest}; use api::v1::meta::{ReconcileDatabase, ReconcileRequest};
use arrow::datatypes::DataType as ArrowDataType;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_query::error::{ use common_query::error::{
InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result, InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use common_query::prelude::{Signature, TypeSignature, Volatility};
use common_telemetry::info; use common_telemetry::info;
use datafusion_expr::{Signature, TypeSignature, Volatility};
use datatypes::data_type::DataType;
use datatypes::prelude::*; use datatypes::prelude::*;
use session::context::QueryContextRef; use session::context::QueryContextRef;
@@ -115,16 +113,19 @@ fn signature() -> Signature {
let mut signs = Vec::with_capacity(2 + nums.len()); let mut signs = Vec::with_capacity(2 + nums.len());
signs.extend([ signs.extend([
// reconcile_database(datanode_name) // reconcile_database(datanode_name)
TypeSignature::Exact(vec![ArrowDataType::Utf8]), TypeSignature::Exact(vec![ConcreteDataType::string_datatype()]),
// reconcile_database(database_name, resolve_strategy) // reconcile_database(database_name, resolve_strategy)
TypeSignature::Exact(vec![ArrowDataType::Utf8, ArrowDataType::Utf8]), TypeSignature::Exact(vec![
ConcreteDataType::string_datatype(),
ConcreteDataType::string_datatype(),
]),
]); ]);
for sign in nums { for sign in nums {
// reconcile_database(database_name, resolve_strategy, parallelism) // reconcile_database(database_name, resolve_strategy, parallelism)
signs.push(TypeSignature::Exact(vec![ signs.push(TypeSignature::Exact(vec![
ArrowDataType::Utf8, ConcreteDataType::string_datatype(),
ArrowDataType::Utf8, ConcreteDataType::string_datatype(),
sign.as_arrow_type(), sign,
])); ]));
} }
Signature::one_of(signs, Volatility::Immutable) Signature::one_of(signs, Volatility::Immutable)
@@ -132,160 +133,66 @@ fn signature() -> Signature {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::assert_matches::assert_matches;
use std::sync::Arc; use std::sync::Arc;
use arrow::array::{StringArray, UInt32Array}; use common_query::error::Error;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::{StringVector, UInt32Vector, VectorRef};
use datafusion_expr::ColumnarValue;
use crate::admin::reconcile_database::ReconcileDatabaseFunction; use crate::admin::reconcile_database::ReconcileDatabaseFunction;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[tokio::test] #[tokio::test]
async fn test_reconcile_catalog() { async fn test_reconcile_catalog() {
common_telemetry::init_default_ut_logging(); common_telemetry::init_default_ut_logging();
// reconcile_database(database_name) // reconcile_database(database_name)
let factory: ScalarFunctionFactory = ReconcileDatabaseFunction::factory().into(); let f = ReconcileDatabaseFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![Arc::new(StringVector::from(vec!["test"])) as _];
let f = provider.as_async().unwrap(); let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
let func_args = datafusion::logical_expr::ScalarFunctionArgs { assert_eq!(expect, result);
args: vec![ColumnarValue::Array(Arc::new(StringArray::from(vec![
"test",
])))],
arg_fields: vec![Arc::new(Field::new("arg_0", DataType::Utf8, false))],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// reconcile_database(database_name, resolve_strategy) // reconcile_database(database_name, resolve_strategy)
let factory: ScalarFunctionFactory = ReconcileDatabaseFunction::factory().into(); let f = ReconcileDatabaseFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["test"])) as _,
Arc::new(StringVector::from(vec!["UseLatest"])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { ];
args: vec![ let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))), let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), assert_eq!(expect, result);
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// reconcile_database(database_name, resolve_strategy, parallelism) // reconcile_database(database_name, resolve_strategy, parallelism)
let factory: ScalarFunctionFactory = ReconcileDatabaseFunction::factory().into(); let f = ReconcileDatabaseFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["test"])) as _,
Arc::new(StringVector::from(vec!["UseLatest"])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { Arc::new(UInt32Vector::from_slice([10])) as _,
args: vec![ ];
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))), let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
ColumnarValue::Array(Arc::new(UInt32Array::from(vec![10]))), assert_eq!(expect, result);
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::Utf8, false)),
Arc::new(Field::new("arg_2", DataType::UInt32, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// invalid function args // invalid function args
let factory: ScalarFunctionFactory = ReconcileDatabaseFunction::factory().into(); let f = ReconcileDatabaseFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["UseLatest"])) as _,
Arc::new(UInt32Vector::from_slice([10])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { Arc::new(StringVector::from(vec!["v1"])) as _,
args: vec![ Arc::new(StringVector::from(vec!["v2"])) as _,
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), ];
ColumnarValue::Array(Arc::new(UInt32Array::from(vec![10]))), let err = f.eval(FunctionContext::mock(), &args).await.unwrap_err();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["v1"]))), assert_matches!(err, Error::InvalidFuncArgs { .. });
ColumnarValue::Array(Arc::new(StringArray::from(vec!["v2"]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::UInt32, false)),
Arc::new(Field::new("arg_2", DataType::Utf8, false)),
Arc::new(Field::new("arg_3", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let _err = f.invoke_async_with_args(func_args).await.unwrap_err();
// Note: Error type is DataFusionError at this level, not common_query::Error
// unsupported input data type // unsupported input data type
let factory: ScalarFunctionFactory = ReconcileDatabaseFunction::factory().into(); let f = ReconcileDatabaseFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["UseLatest"])) as _,
Arc::new(UInt32Vector::from_slice([10])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { Arc::new(StringVector::from(vec!["v1"])) as _,
args: vec![ ];
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseLatest"]))), let err = f.eval(FunctionContext::mock(), &args).await.unwrap_err();
ColumnarValue::Array(Arc::new(UInt32Array::from(vec![10]))), assert_matches!(err, Error::UnsupportedInputDataType { .. });
ColumnarValue::Array(Arc::new(StringArray::from(vec!["v1"]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::UInt32, false)),
Arc::new(Field::new("arg_2", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let _err = f.invoke_async_with_args(func_args).await.unwrap_err();
// Note: Error type is DataFusionError at this level, not common_query::Error
} }
} }

View File

@@ -14,15 +14,14 @@
use api::v1::meta::reconcile_request::Target; use api::v1::meta::reconcile_request::Target;
use api::v1::meta::{ReconcileRequest, ReconcileTable, ResolveStrategy}; use api::v1::meta::{ReconcileRequest, ReconcileTable, ResolveStrategy};
use arrow::datatypes::DataType as ArrowDataType;
use common_catalog::format_full_table_name; use common_catalog::format_full_table_name;
use common_error::ext::BoxedError; use common_error::ext::BoxedError;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_query::error::{ use common_query::error::{
MissingProcedureServiceHandlerSnafu, Result, TableMutationSnafu, UnsupportedInputDataTypeSnafu, MissingProcedureServiceHandlerSnafu, Result, TableMutationSnafu, UnsupportedInputDataTypeSnafu,
}; };
use common_query::prelude::{Signature, TypeSignature, Volatility};
use common_telemetry::info; use common_telemetry::info;
use datafusion_expr::{Signature, TypeSignature, Volatility};
use datatypes::prelude::*; use datatypes::prelude::*;
use session::context::QueryContextRef; use session::context::QueryContextRef;
use session::table_name::table_name_to_full_name; use session::table_name::table_name_to_full_name;
@@ -94,9 +93,12 @@ fn signature() -> Signature {
Signature::one_of( Signature::one_of(
vec![ vec![
// reconcile_table(table_name) // reconcile_table(table_name)
TypeSignature::Exact(vec![ArrowDataType::Utf8]), TypeSignature::Exact(vec![ConcreteDataType::string_datatype()]),
// reconcile_table(table_name, resolve_strategy) // reconcile_table(table_name, resolve_strategy)
TypeSignature::Exact(vec![ArrowDataType::Utf8, ArrowDataType::Utf8]), TypeSignature::Exact(vec![
ConcreteDataType::string_datatype(),
ConcreteDataType::string_datatype(),
]),
], ],
Volatility::Immutable, Volatility::Immutable,
) )
@@ -104,101 +106,44 @@ fn signature() -> Signature {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::assert_matches::assert_matches;
use std::sync::Arc; use std::sync::Arc;
use arrow::array::StringArray; use common_query::error::Error;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::{StringVector, VectorRef};
use datafusion_expr::ColumnarValue;
use crate::admin::reconcile_table::ReconcileTableFunction; use crate::admin::reconcile_table::ReconcileTableFunction;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[tokio::test] #[tokio::test]
async fn test_reconcile_table() { async fn test_reconcile_table() {
common_telemetry::init_default_ut_logging(); common_telemetry::init_default_ut_logging();
// reconcile_table(table_name) // reconcile_table(table_name)
let factory: ScalarFunctionFactory = ReconcileTableFunction::factory().into(); let f = ReconcileTableFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![Arc::new(StringVector::from(vec!["test"])) as _];
let f = provider.as_async().unwrap(); let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
let func_args = datafusion::logical_expr::ScalarFunctionArgs { assert_eq!(expect, result);
args: vec![ColumnarValue::Array(Arc::new(StringArray::from(vec![
"test",
])))],
arg_fields: vec![Arc::new(Field::new("arg_0", DataType::Utf8, false))],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// reconcile_table(table_name, resolve_strategy) // reconcile_table(table_name, resolve_strategy)
let factory: ScalarFunctionFactory = ReconcileTableFunction::factory().into(); let f = ReconcileTableFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["test"])) as _,
Arc::new(StringVector::from(vec!["UseMetasrv"])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { ];
args: vec![ let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))), let expect: VectorRef = Arc::new(StringVector::from(vec!["test_pid"]));
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseMetasrv"]))), assert_eq!(expect, result);
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap();
assert_eq!(result_array.value(0), "test_pid");
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(
scalar,
datafusion_common::ScalarValue::Utf8(Some("test_pid".to_string()))
);
}
}
// unsupported input data type // unsupported input data type
let factory: ScalarFunctionFactory = ReconcileTableFunction::factory().into(); let f = ReconcileTableFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![
let f = provider.as_async().unwrap(); Arc::new(StringVector::from(vec!["test"])) as _,
Arc::new(StringVector::from(vec!["UseMetasrv"])) as _,
let func_args = datafusion::logical_expr::ScalarFunctionArgs { Arc::new(StringVector::from(vec!["10"])) as _,
args: vec![ ];
ColumnarValue::Array(Arc::new(StringArray::from(vec!["test"]))), let err = f.eval(FunctionContext::mock(), &args).await.unwrap_err();
ColumnarValue::Array(Arc::new(StringArray::from(vec!["UseMetasrv"]))), assert_matches!(err, Error::UnsupportedInputDataType { .. });
ColumnarValue::Array(Arc::new(StringArray::from(vec!["10"]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::Utf8, false)),
Arc::new(Field::new("arg_1", DataType::Utf8, false)),
Arc::new(Field::new("arg_2", DataType::Utf8, false)),
],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let _err = f.invoke_async_with_args(func_args).await.unwrap_err();
// Note: Error type is DataFusionError at this level, not common_query::Error
} }
} }

View File

@@ -18,8 +18,7 @@ use common_query::error::{
InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result, InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use datafusion_expr::{Signature, TypeSignature, Volatility}; use common_query::prelude::{Signature, TypeSignature, Volatility};
use datatypes::data_type::DataType;
use datatypes::prelude::ConcreteDataType; use datatypes::prelude::ConcreteDataType;
use datatypes::value::{Value, ValueRef}; use datatypes::value::{Value, ValueRef};
use session::context::QueryContextRef; use session::context::QueryContextRef;
@@ -83,13 +82,7 @@ fn signature() -> Signature {
Signature::one_of( Signature::one_of(
vec![ vec![
// remove_region_follower(region_id, peer_id) // remove_region_follower(region_id, peer_id)
TypeSignature::Uniform( TypeSignature::Uniform(2, ConcreteDataType::numerics()),
2,
ConcreteDataType::numerics()
.into_iter()
.map(|dt| dt.as_arrow_type())
.collect(),
),
], ],
Volatility::Immutable, Volatility::Immutable,
) )
@@ -99,57 +92,38 @@ fn signature() -> Signature {
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use arrow::array::UInt64Array; use common_query::prelude::TypeSignature;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::{UInt64Vector, VectorRef};
use datafusion_expr::ColumnarValue;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[test] #[test]
fn test_remove_region_follower_misc() { fn test_remove_region_follower_misc() {
let factory: ScalarFunctionFactory = RemoveRegionFollowerFunction::factory().into(); let f = RemoveRegionFollowerFunction;
let f = factory.provide(FunctionContext::mock());
assert_eq!("remove_region_follower", f.name()); assert_eq!("remove_region_follower", f.name());
assert_eq!(DataType::UInt64, f.return_type(&[]).unwrap()); assert_eq!(
ConcreteDataType::uint64_datatype(),
f.return_type(&[]).unwrap()
);
assert!(matches!(f.signature(), assert!(matches!(f.signature(),
datafusion_expr::Signature { Signature {
type_signature: datafusion_expr::TypeSignature::OneOf(sigs), type_signature: TypeSignature::OneOf(sigs),
volatility: datafusion_expr::Volatility::Immutable volatility: Volatility::Immutable
} if sigs.len() == 1)); } if sigs.len() == 1));
} }
#[tokio::test] #[tokio::test]
async fn test_remove_region_follower() { async fn test_remove_region_follower() {
let factory: ScalarFunctionFactory = RemoveRegionFollowerFunction::factory().into(); let f = RemoveRegionFollowerFunction;
let provider = factory.provide(FunctionContext::mock()); let args = vec![1, 1];
let f = provider.as_async().unwrap(); let args = args
.into_iter()
.map(|arg| Arc::new(UInt64Vector::from_slice([arg])) as _)
.collect::<Vec<_>>();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
args: vec![ let expect: VectorRef = Arc::new(UInt64Vector::from_slice([0u64]));
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))), assert_eq!(result, expect);
ColumnarValue::Array(Arc::new(UInt64Array::from(vec![1]))),
],
arg_fields: vec![
Arc::new(Field::new("arg_0", DataType::UInt64, false)),
Arc::new(Field::new("arg_1", DataType::UInt64, false)),
],
return_field: Arc::new(Field::new("result", DataType::UInt64, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result {
ColumnarValue::Array(array) => {
let result_array = array.as_any().downcast_ref::<UInt64Array>().unwrap();
assert_eq!(result_array.value(0), 0u64);
}
ColumnarValue::Scalar(scalar) => {
assert_eq!(scalar, datafusion_common::ScalarValue::UInt64(Some(0)));
}
}
} }
} }

View File

@@ -25,14 +25,14 @@
use std::sync::Arc; use std::sync::Arc;
use arrow::array::StructArray; use arrow::array::StructArray;
use arrow_schema::{FieldRef, Fields}; use arrow_schema::Fields;
use common_telemetry::debug; use common_telemetry::debug;
use datafusion::functions_aggregate::all_default_aggregate_functions; use datafusion::functions_aggregate::all_default_aggregate_functions;
use datafusion::optimizer::analyzer::type_coercion::TypeCoercion; use datafusion::optimizer::analyzer::type_coercion::TypeCoercion;
use datafusion::optimizer::AnalyzerRule; use datafusion::optimizer::AnalyzerRule;
use datafusion::physical_planner::create_aggregate_expr_and_maybe_filter; use datafusion::physical_planner::create_aggregate_expr_and_maybe_filter;
use datafusion_common::{Column, ScalarValue}; use datafusion_common::{Column, ScalarValue};
use datafusion_expr::expr::{AggregateFunction, AggregateFunctionParams}; use datafusion_expr::expr::AggregateFunction;
use datafusion_expr::function::StateFieldsArgs; use datafusion_expr::function::StateFieldsArgs;
use datafusion_expr::{ use datafusion_expr::{
Accumulator, Aggregate, AggregateUDF, AggregateUDFImpl, Expr, ExprSchemable, LogicalPlan, Accumulator, Aggregate, AggregateUDF, AggregateUDFImpl, Expr, ExprSchemable, LogicalPlan,
@@ -146,7 +146,6 @@ impl StateMergeHelper {
}; };
let original_input_types = aggr_func let original_input_types = aggr_func
.params
.args .args
.iter() .iter()
.map(|e| e.get_type(&aggr.input.schema())) .map(|e| e.get_type(&aggr.input.schema()))
@@ -157,7 +156,11 @@ impl StateMergeHelper {
let expr = AggregateFunction { let expr = AggregateFunction {
func: Arc::new(state_func.into()), func: Arc::new(state_func.into()),
params: aggr_func.params.clone(), args: aggr_func.args.clone(),
distinct: aggr_func.distinct,
filter: aggr_func.filter.clone(),
order_by: aggr_func.order_by.clone(),
null_treatment: aggr_func.null_treatment,
}; };
let expr = Expr::AggregateFunction(expr); let expr = Expr::AggregateFunction(expr);
let lower_state_output_col_name = expr.schema_name().to_string(); let lower_state_output_col_name = expr.schema_name().to_string();
@@ -179,10 +182,11 @@ impl StateMergeHelper {
let arg = Expr::Column(Column::new_unqualified(lower_state_output_col_name)); let arg = Expr::Column(Column::new_unqualified(lower_state_output_col_name));
let expr = AggregateFunction { let expr = AggregateFunction {
func: Arc::new(merge_func.into()), func: Arc::new(merge_func.into()),
params: AggregateFunctionParams { args: vec![arg],
args: vec![arg], distinct: aggr_func.distinct,
..aggr_func.params.clone() filter: aggr_func.filter.clone(),
}, order_by: aggr_func.order_by.clone(),
null_treatment: aggr_func.null_treatment,
}; };
// alias to the original aggregate expr's schema name, so parent plan can refer to it // alias to the original aggregate expr's schema name, so parent plan can refer to it
@@ -243,8 +247,15 @@ impl StateWrapper {
pub fn deduce_aggr_return_type( pub fn deduce_aggr_return_type(
&self, &self,
acc_args: &datafusion_expr::function::AccumulatorArgs, acc_args: &datafusion_expr::function::AccumulatorArgs,
) -> datafusion_common::Result<FieldRef> { ) -> datafusion_common::Result<DataType> {
self.inner.return_field(acc_args.schema.fields()) let input_exprs = acc_args.exprs;
let input_schema = acc_args.schema;
let input_types = input_exprs
.iter()
.map(|e| e.data_type(input_schema))
.collect::<Result<Vec<_>, _>>()?;
let return_type = self.inner.return_type(&input_types)?;
Ok(return_type)
} }
} }
@@ -254,13 +265,14 @@ impl AggregateUDFImpl for StateWrapper {
acc_args: datafusion_expr::function::AccumulatorArgs<'b>, acc_args: datafusion_expr::function::AccumulatorArgs<'b>,
) -> datafusion_common::Result<Box<dyn Accumulator>> { ) -> datafusion_common::Result<Box<dyn Accumulator>> {
// fix and recover proper acc args for the original aggregate function. // fix and recover proper acc args for the original aggregate function.
let state_type = acc_args.return_type().clone(); let state_type = acc_args.return_type.clone();
let inner = { let inner = {
let old_return_type = self.deduce_aggr_return_type(&acc_args)?;
let acc_args = datafusion_expr::function::AccumulatorArgs { let acc_args = datafusion_expr::function::AccumulatorArgs {
return_field: self.deduce_aggr_return_type(&acc_args)?, return_type: &old_return_type,
schema: acc_args.schema, schema: acc_args.schema,
ignore_nulls: acc_args.ignore_nulls, ignore_nulls: acc_args.ignore_nulls,
order_bys: acc_args.order_bys, ordering_req: acc_args.ordering_req,
is_reversed: acc_args.is_reversed, is_reversed: acc_args.is_reversed,
name: acc_args.name, name: acc_args.name,
is_distinct: acc_args.is_distinct, is_distinct: acc_args.is_distinct,
@@ -285,15 +297,11 @@ impl AggregateUDFImpl for StateWrapper {
/// Return state_fields as the output struct type. /// Return state_fields as the output struct type.
/// ///
fn return_type(&self, arg_types: &[DataType]) -> datafusion_common::Result<DataType> { fn return_type(&self, arg_types: &[DataType]) -> datafusion_common::Result<DataType> {
let input_fields = &arg_types let old_return_type = self.inner.return_type(arg_types)?;
.iter()
.map(|x| Arc::new(Field::new("x", x.clone(), false)))
.collect::<Vec<_>>();
let state_fields_args = StateFieldsArgs { let state_fields_args = StateFieldsArgs {
name: self.inner().name(), name: self.inner().name(),
input_fields, input_types: arg_types,
return_field: self.inner.return_field(input_fields)?, return_type: &old_return_type,
// TODO(discord9): how to get this?, probably ok? // TODO(discord9): how to get this?, probably ok?
ordering_fields: &[], ordering_fields: &[],
is_distinct: false, is_distinct: false,
@@ -307,11 +315,12 @@ impl AggregateUDFImpl for StateWrapper {
fn state_fields( fn state_fields(
&self, &self,
args: datafusion_expr::function::StateFieldsArgs, args: datafusion_expr::function::StateFieldsArgs,
) -> datafusion_common::Result<Vec<FieldRef>> { ) -> datafusion_common::Result<Vec<Field>> {
let old_return_type = self.inner.return_type(args.input_types)?;
let state_fields_args = StateFieldsArgs { let state_fields_args = StateFieldsArgs {
name: args.name, name: args.name,
input_fields: args.input_fields, input_types: args.input_types,
return_field: self.inner.return_field(args.input_fields)?, return_type: &old_return_type,
ordering_fields: args.ordering_fields, ordering_fields: args.ordering_fields,
is_distinct: args.is_distinct, is_distinct: args.is_distinct,
}; };
@@ -493,7 +502,7 @@ impl AggregateUDFImpl for MergeWrapper {
fn state_fields( fn state_fields(
&self, &self,
_args: datafusion_expr::function::StateFieldsArgs, _args: datafusion_expr::function::StateFieldsArgs,
) -> datafusion_common::Result<Vec<FieldRef>> { ) -> datafusion_common::Result<Vec<Field>> {
self.original_phy_expr.state_fields() self.original_phy_expr.state_fields()
} }
} }

View File

@@ -35,7 +35,7 @@ use datafusion::prelude::SessionContext;
use datafusion_common::{Column, TableReference}; use datafusion_common::{Column, TableReference};
use datafusion_expr::expr::AggregateFunction; use datafusion_expr::expr::AggregateFunction;
use datafusion_expr::sqlparser::ast::NullTreatment; use datafusion_expr::sqlparser::ast::NullTreatment;
use datafusion_expr::{lit, Aggregate, Expr, LogicalPlan, SortExpr, TableScan}; use datafusion_expr::{Aggregate, Expr, LogicalPlan, SortExpr, TableScan};
use datafusion_physical_expr::aggregate::AggregateExprBuilder; use datafusion_physical_expr::aggregate::AggregateExprBuilder;
use datafusion_physical_expr::{EquivalenceProperties, Partitioning}; use datafusion_physical_expr::{EquivalenceProperties, Partitioning};
use datatypes::arrow_array::StringArray; use datatypes::arrow_array::StringArray;
@@ -234,7 +234,7 @@ async fn test_sum_udaf() {
vec![Expr::Column(Column::new_unqualified("number"))], vec![Expr::Column(Column::new_unqualified("number"))],
false, false,
None, None,
vec![], None,
None, None,
))], ))],
) )
@@ -250,7 +250,7 @@ async fn test_sum_udaf() {
vec![Expr::Column(Column::new_unqualified("number"))], vec![Expr::Column(Column::new_unqualified("number"))],
false, false,
None, None,
vec![], None,
None, None,
))], ))],
) )
@@ -290,7 +290,7 @@ async fn test_sum_udaf() {
vec![Expr::Column(Column::new_unqualified("__sum_state(number)"))], vec![Expr::Column(Column::new_unqualified("__sum_state(number)"))],
false, false,
None, None,
vec![], None,
None, None,
)) ))
.alias("sum(number)")], .alias("sum(number)")],
@@ -378,7 +378,7 @@ async fn test_avg_udaf() {
vec![Expr::Column(Column::new_unqualified("number"))], vec![Expr::Column(Column::new_unqualified("number"))],
false, false,
None, None,
vec![], None,
None, None,
))], ))],
) )
@@ -395,7 +395,7 @@ async fn test_avg_udaf() {
vec![Expr::Column(Column::new_unqualified("number"))], vec![Expr::Column(Column::new_unqualified("number"))],
false, false,
None, None,
vec![], None,
None, None,
))], ))],
) )
@@ -449,7 +449,7 @@ async fn test_avg_udaf() {
vec![Expr::Column(Column::new_unqualified("__avg_state(number)"))], vec![Expr::Column(Column::new_unqualified("__avg_state(number)"))],
false, false,
None, None,
vec![], None,
None, None,
)) ))
.alias("avg(number)")], .alias("avg(number)")],
@@ -551,7 +551,7 @@ async fn test_udaf_correct_eval_result() {
expected_fn: Option<ExpectedFn>, expected_fn: Option<ExpectedFn>,
distinct: bool, distinct: bool,
filter: Option<Box<Expr>>, filter: Option<Box<Expr>>,
order_by: Vec<SortExpr>, order_by: Option<Vec<SortExpr>>,
null_treatment: Option<NullTreatment>, null_treatment: Option<NullTreatment>,
} }
type ExpectedFn = fn(ArrayRef) -> bool; type ExpectedFn = fn(ArrayRef) -> bool;
@@ -575,7 +575,7 @@ async fn test_udaf_correct_eval_result() {
expected_fn: None, expected_fn: None,
distinct: false, distinct: false,
filter: None, filter: None,
order_by: vec![], order_by: None,
null_treatment: None, null_treatment: None,
}, },
TestCase { TestCase {
@@ -596,7 +596,7 @@ async fn test_udaf_correct_eval_result() {
expected_fn: None, expected_fn: None,
distinct: false, distinct: false,
filter: None, filter: None,
order_by: vec![], order_by: None,
null_treatment: None, null_treatment: None,
}, },
TestCase { TestCase {
@@ -619,7 +619,7 @@ async fn test_udaf_correct_eval_result() {
expected_fn: None, expected_fn: None,
distinct: false, distinct: false,
filter: None, filter: None,
order_by: vec![], order_by: None,
null_treatment: None, null_treatment: None,
}, },
TestCase { TestCase {
@@ -630,8 +630,8 @@ async fn test_udaf_correct_eval_result() {
true, true,
)])), )])),
args: vec![ args: vec![
lit(128i64), Expr::Literal(ScalarValue::Int64(Some(128))),
lit(0.05f64), Expr::Literal(ScalarValue::Float64(Some(0.05))),
Expr::Column(Column::new_unqualified("number")), Expr::Column(Column::new_unqualified("number")),
], ],
input: vec![Arc::new(Float64Array::from(vec![ input: vec![Arc::new(Float64Array::from(vec![
@@ -659,7 +659,7 @@ async fn test_udaf_correct_eval_result() {
}), }),
distinct: false, distinct: false,
filter: None, filter: None,
order_by: vec![], order_by: None,
null_treatment: None, null_treatment: None,
}, },
TestCase { TestCase {
@@ -690,7 +690,7 @@ async fn test_udaf_correct_eval_result() {
}), }),
distinct: false, distinct: false,
filter: None, filter: None,
order_by: vec![], order_by: None,
null_treatment: None, null_treatment: None,
}, },
// TODO(discord9): udd_merge/hll_merge/geo_path/quantile_aggr tests // TODO(discord9): udd_merge/hll_merge/geo_path/quantile_aggr tests

View File

@@ -41,7 +41,7 @@ use datatypes::arrow::array::{
Array, ArrayRef, AsArray, BooleanArray, Int64Array, ListArray, UInt64Array, Array, ArrayRef, AsArray, BooleanArray, Int64Array, ListArray, UInt64Array,
}; };
use datatypes::arrow::buffer::{OffsetBuffer, ScalarBuffer}; use datatypes::arrow::buffer::{OffsetBuffer, ScalarBuffer};
use datatypes::arrow::datatypes::{DataType, Field, FieldRef}; use datatypes::arrow::datatypes::{DataType, Field};
use crate::function_registry::FunctionRegistry; use crate::function_registry::FunctionRegistry;
@@ -94,14 +94,14 @@ impl AggregateUDFImpl for CountHash {
false false
} }
fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<FieldRef>> { fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<Field>> {
Ok(vec![Arc::new(Field::new_list( Ok(vec![Field::new_list(
format_state_name(args.name, "count_hash"), format_state_name(args.name, "count_hash"),
Field::new_list_field(DataType::UInt64, true), Field::new_list_field(DataType::UInt64, true),
// For count_hash accumulator, null list item stands for an // For count_hash accumulator, null list item stands for an
// empty value set (i.e., all NULL value so far for that group). // empty value set (i.e., all NULL value so far for that group).
true, true,
))]) )])
} }
fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> { fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {

View File

@@ -21,7 +21,7 @@ pub(crate) struct GeoFunction;
impl GeoFunction { impl GeoFunction {
pub fn register(registry: &FunctionRegistry) { pub fn register(registry: &FunctionRegistry) {
registry.register_aggr(encoding::JsonEncodePathAccumulator::uadf_impl());
registry.register_aggr(geo_path::GeoPathAccumulator::uadf_impl()); registry.register_aggr(geo_path::GeoPathAccumulator::uadf_impl());
registry.register_aggr(encoding::JsonPathAccumulator::uadf_impl());
} }
} }

View File

@@ -14,332 +14,223 @@
use std::sync::Arc; use std::sync::Arc;
use arrow::array::AsArray; use common_error::ext::{BoxedError, PlainError};
use datafusion::arrow::array::{Array, ArrayRef}; use common_error::status_code::StatusCode;
use datafusion::common::cast::as_primitive_array; use common_macro::{as_aggr_func_creator, AggrFuncTypeStore};
use datafusion::error::{DataFusionError, Result as DfResult}; use common_query::error::{self, InvalidInputStateSnafu, Result};
use datafusion::logical_expr::{Accumulator as DfAccumulator, AggregateUDF, Volatility}; use common_query::logical_plan::accumulator::AggrFuncTypeStore;
use datafusion::prelude::create_udaf; use common_query::logical_plan::{
use datafusion_common::cast::{as_list_array, as_struct_array}; create_aggregate_function, Accumulator, AggregateFunctionCreator,
use datafusion_common::ScalarValue;
use datatypes::arrow::array::{Float64Array, Int64Array, ListArray, StructArray};
use datatypes::arrow::datatypes::{
DataType, Field, Float64Type, Int64Type, TimeUnit, TimestampNanosecondType,
}; };
use datatypes::compute::{self, sort_to_indices}; use common_query::prelude::AccumulatorCreatorFunction;
use common_time::Timestamp;
use datafusion_expr::AggregateUDF;
use datatypes::prelude::ConcreteDataType;
use datatypes::value::{ListValue, Value};
use datatypes::vectors::VectorRef;
use snafu::{ensure, ResultExt};
pub const JSON_ENCODE_PATH_NAME: &str = "json_encode_path"; use crate::scalars::geo::helpers::{ensure_columns_len, ensure_columns_n};
const LATITUDE_FIELD: &str = "lat"; /// Accumulator of lat, lng, timestamp tuples
const LONGITUDE_FIELD: &str = "lng"; #[derive(Debug)]
const TIMESTAMP_FIELD: &str = "timestamp"; pub struct JsonPathAccumulator {
const DEFAULT_LIST_FIELD_NAME: &str = "item"; timestamp_type: ConcreteDataType,
#[derive(Debug, Default)]
pub struct JsonEncodePathAccumulator {
lat: Vec<Option<f64>>, lat: Vec<Option<f64>>,
lng: Vec<Option<f64>>, lng: Vec<Option<f64>>,
timestamp: Vec<Option<i64>>, timestamp: Vec<Option<Timestamp>>,
} }
impl JsonEncodePathAccumulator { impl JsonPathAccumulator {
pub fn new() -> Self { fn new(timestamp_type: ConcreteDataType) -> Self {
Self::default() Self {
} lat: Vec::default(),
lng: Vec::default(),
pub fn uadf_impl() -> AggregateUDF { timestamp: Vec::default(),
create_udaf( timestamp_type,
JSON_ENCODE_PATH_NAME,
// Input types: lat, lng, timestamp
vec![
DataType::Float64,
DataType::Float64,
DataType::Timestamp(TimeUnit::Nanosecond, None),
],
// Output type: geojson compatible linestring
Arc::new(DataType::Utf8),
Volatility::Immutable,
// Create the accumulator
Arc::new(|_| Ok(Box::new(Self::new()))),
// Intermediate state types
Arc::new(vec![DataType::Struct(
vec![
Field::new(
LATITUDE_FIELD,
DataType::List(Arc::new(Field::new(
DEFAULT_LIST_FIELD_NAME,
DataType::Float64,
true,
))),
false,
),
Field::new(
LONGITUDE_FIELD,
DataType::List(Arc::new(Field::new(
DEFAULT_LIST_FIELD_NAME,
DataType::Float64,
true,
))),
false,
),
Field::new(
TIMESTAMP_FIELD,
DataType::List(Arc::new(Field::new(
DEFAULT_LIST_FIELD_NAME,
DataType::Int64,
true,
))),
false,
),
]
.into(),
)]),
)
}
}
impl DfAccumulator for JsonEncodePathAccumulator {
fn update_batch(&mut self, values: &[ArrayRef]) -> datafusion::error::Result<()> {
if values.len() != 3 {
return Err(DataFusionError::Internal(format!(
"Expected 3 columns for json_encode_path, got {}",
values.len()
)));
} }
}
let lat_array = as_primitive_array::<Float64Type>(&values[0])?; /// Create a new `AggregateUDF` for the `json_encode_path` aggregate function.
let lng_array = as_primitive_array::<Float64Type>(&values[1])?; pub fn uadf_impl() -> AggregateUDF {
let ts_array = as_primitive_array::<TimestampNanosecondType>(&values[2])?; create_aggregate_function(
"json_encode_path".to_string(),
3,
Arc::new(JsonPathEncodeFunctionCreator::default()),
)
.into()
}
}
let size = lat_array.len(); impl Accumulator for JsonPathAccumulator {
self.lat.reserve(size); fn state(&self) -> Result<Vec<Value>> {
self.lng.reserve(size); Ok(vec![
Value::List(ListValue::new(
self.lat.iter().map(|i| Value::from(*i)).collect(),
ConcreteDataType::float64_datatype(),
)),
Value::List(ListValue::new(
self.lng.iter().map(|i| Value::from(*i)).collect(),
ConcreteDataType::float64_datatype(),
)),
Value::List(ListValue::new(
self.timestamp.iter().map(|i| Value::from(*i)).collect(),
self.timestamp_type.clone(),
)),
])
}
fn update_batch(&mut self, columns: &[VectorRef]) -> Result<()> {
// update batch as in datafusion just provides the accumulator original
// input.
//
// columns is vec of [`lat`, `lng`, `timestamp`]
// where
// - `lat` is a vector of `Value::Float64` or similar type. Each item in
// the vector is a row in given dataset.
// - so on so forth for `lng` and `timestamp`
ensure_columns_n!(columns, 3);
let lat = &columns[0];
let lng = &columns[1];
let ts = &columns[2];
let size = lat.len();
for idx in 0..size { for idx in 0..size {
self.lat.push(if lat_array.is_null(idx) { self.lat.push(lat.get(idx).as_f64_lossy());
None self.lng.push(lng.get(idx).as_f64_lossy());
} else { self.timestamp.push(ts.get(idx).as_timestamp());
Some(lat_array.value(idx))
});
self.lng.push(if lng_array.is_null(idx) {
None
} else {
Some(lng_array.value(idx))
});
self.timestamp.push(if ts_array.is_null(idx) {
None
} else {
Some(ts_array.value(idx))
});
} }
Ok(()) Ok(())
} }
fn evaluate(&mut self) -> DfResult<ScalarValue> { fn merge_batch(&mut self, states: &[VectorRef]) -> Result<()> {
let unordered_lng_array = Float64Array::from(self.lng.clone()); // merge batch as in datafusion gives state accumulated from the data
let unordered_lat_array = Float64Array::from(self.lat.clone()); // returned from child accumulators' state() call
let ts_array = Int64Array::from(self.timestamp.clone()); // In our particular implementation, the data structure is like
//
// states is vec of [`lat`, `lng`, `timestamp`]
// where
// - `lat` is a vector of `Value::List`. Each item in the list is all
// coordinates from a child accumulator.
// - so on so forth for `lng` and `timestamp`
let ordered_indices = sort_to_indices(&ts_array, None, None)?; ensure_columns_n!(states, 3);
let lat_array = compute::take(&unordered_lat_array, &ordered_indices, None)?;
let lng_array = compute::take(&unordered_lng_array, &ordered_indices, None)?;
let len = ts_array.len(); let lat_lists = &states[0];
let lat_array = lat_array.as_primitive::<Float64Type>(); let lng_lists = &states[1];
let lng_array = lng_array.as_primitive::<Float64Type>(); let ts_lists = &states[2];
let mut coords = Vec::with_capacity(len); let len = lat_lists.len();
for i in 0..len {
let lng = lng_array.value(i);
let lat = lat_array.value(i);
coords.push(vec![lng, lat]);
}
let result = serde_json::to_string(&coords) for idx in 0..len {
.map_err(|e| DataFusionError::Execution(format!("Failed to encode json, {}", e)))?; if let Some(lat_list) = lat_lists
.get(idx)
.as_list()
.map_err(BoxedError::new)
.context(error::ExecuteSnafu)?
{
for v in lat_list.items() {
self.lat.push(v.as_f64_lossy());
}
}
Ok(ScalarValue::Utf8(Some(result))) if let Some(lng_list) = lng_lists
} .get(idx)
.as_list()
.map_err(BoxedError::new)
.context(error::ExecuteSnafu)?
{
for v in lng_list.items() {
self.lng.push(v.as_f64_lossy());
}
}
fn size(&self) -> usize { if let Some(ts_list) = ts_lists
// Base size of JsonEncodePathAccumulator struct fields .get(idx)
let mut total_size = std::mem::size_of::<Self>(); .as_list()
.map_err(BoxedError::new)
// Size of vectors (approximation) .context(error::ExecuteSnafu)?
total_size += self.lat.capacity() * std::mem::size_of::<Option<f64>>(); {
total_size += self.lng.capacity() * std::mem::size_of::<Option<f64>>(); for v in ts_list.items() {
total_size += self.timestamp.capacity() * std::mem::size_of::<Option<i64>>(); self.timestamp.push(v.as_timestamp());
}
total_size }
}
fn state(&mut self) -> datafusion::error::Result<Vec<ScalarValue>> {
let lat_array = Arc::new(ListArray::from_iter_primitive::<Float64Type, _, _>(vec![
Some(self.lat.clone()),
]));
let lng_array = Arc::new(ListArray::from_iter_primitive::<Float64Type, _, _>(vec![
Some(self.lng.clone()),
]));
let ts_array = Arc::new(ListArray::from_iter_primitive::<Int64Type, _, _>(vec![
Some(self.timestamp.clone()),
]));
let state_struct = StructArray::new(
vec![
Field::new(
LATITUDE_FIELD,
DataType::List(Arc::new(Field::new("item", DataType::Float64, true))),
false,
),
Field::new(
LONGITUDE_FIELD,
DataType::List(Arc::new(Field::new("item", DataType::Float64, true))),
false,
),
Field::new(
TIMESTAMP_FIELD,
DataType::List(Arc::new(Field::new("item", DataType::Int64, true))),
false,
),
]
.into(),
vec![lat_array, lng_array, ts_array],
None,
);
Ok(vec![ScalarValue::Struct(Arc::new(state_struct))])
}
fn merge_batch(&mut self, states: &[ArrayRef]) -> datafusion::error::Result<()> {
if states.len() != 1 {
return Err(DataFusionError::Internal(format!(
"Expected 1 states for json_encode_path, got {}",
states.len()
)));
}
for state in states {
let state = as_struct_array(state)?;
let lat_list = as_list_array(state.column(0))?.value(0);
let lat_array = as_primitive_array::<Float64Type>(&lat_list)?;
let lng_list = as_list_array(state.column(1))?.value(0);
let lng_array = as_primitive_array::<Float64Type>(&lng_list)?;
let ts_list = as_list_array(state.column(2))?.value(0);
let ts_array = as_primitive_array::<Int64Type>(&ts_list)?;
self.lat.extend(lat_array);
self.lng.extend(lng_array);
self.timestamp.extend(ts_array);
} }
Ok(()) Ok(())
} }
}
#[cfg(test)] fn evaluate(&self) -> Result<Value> {
mod tests { let mut work_vec: Vec<(&Option<f64>, &Option<f64>, &Option<Timestamp>)> = self
use datafusion::arrow::array::{Float64Array, TimestampNanosecondArray}; .lat
use datafusion::scalar::ScalarValue; .iter()
.zip(self.lng.iter())
.zip(self.timestamp.iter())
.map(|((a, b), c)| (a, b, c))
.collect();
use super::*; // sort by timestamp, we treat null timestamp as 0
work_vec.sort_unstable_by_key(|tuple| tuple.2.unwrap_or_else(|| Timestamp::new_second(0)));
#[test] let result = serde_json::to_string(
fn test_json_encode_path_basic() { &work_vec
let mut accumulator = JsonEncodePathAccumulator::new(); .into_iter()
// note that we transform to lng,lat for geojson compatibility
.map(|(lat, lng, _)| vec![lng, lat])
.collect::<Vec<Vec<&Option<f64>>>>(),
)
.map_err(|e| {
BoxedError::new(PlainError::new(
format!("Serialization failure: {}", e),
StatusCode::EngineExecuteQuery,
))
})
.context(error::ExecuteSnafu)?;
// Create test data Ok(Value::String(result.into()))
let lat_array = Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0])); }
let lng_array = Arc::new(Float64Array::from(vec![4.0, 5.0, 6.0])); }
let ts_array = Arc::new(TimestampNanosecondArray::from(vec![100, 200, 300]));
/// This function accept rows of lat, lng and timestamp, sort with timestamp and
// Update batch /// encoding them into a geojson-like path.
accumulator ///
.update_batch(&[lat_array, lng_array, ts_array]) /// Example:
.unwrap(); ///
/// ```sql
// Evaluate /// SELECT json_encode_path(lat, lon, timestamp) FROM table [group by ...];
let result = accumulator.evaluate().unwrap(); /// ```
assert_eq!( ///
result, #[as_aggr_func_creator]
ScalarValue::Utf8(Some("[[4.0,1.0],[5.0,2.0],[6.0,3.0]]".to_string())) #[derive(Debug, Default, AggrFuncTypeStore)]
); pub struct JsonPathEncodeFunctionCreator {}
}
impl AggregateFunctionCreator for JsonPathEncodeFunctionCreator {
#[test] fn creator(&self) -> AccumulatorCreatorFunction {
fn test_json_encode_path_sort_by_timestamp() { let creator: AccumulatorCreatorFunction = Arc::new(move |types: &[ConcreteDataType]| {
let mut accumulator = JsonEncodePathAccumulator::new(); let ts_type = types[2].clone();
Ok(Box::new(JsonPathAccumulator::new(ts_type)))
// Create test data with unordered timestamps });
let lat_array = Arc::new(Float64Array::from(vec![1.0, 2.0, 3.0]));
let lng_array = Arc::new(Float64Array::from(vec![4.0, 5.0, 6.0])); creator
let ts_array = Arc::new(TimestampNanosecondArray::from(vec![300, 100, 200])); }
// Update batch fn output_type(&self) -> Result<ConcreteDataType> {
accumulator Ok(ConcreteDataType::string_datatype())
.update_batch(&[lat_array, lng_array, ts_array]) }
.unwrap();
fn state_types(&self) -> Result<Vec<ConcreteDataType>> {
// Evaluate let input_types = self.input_types()?;
let result = accumulator.evaluate().unwrap(); ensure!(input_types.len() == 3, InvalidInputStateSnafu);
assert_eq!(
result, let timestamp_type = input_types[2].clone();
ScalarValue::Utf8(Some("[[5.0,2.0],[6.0,3.0],[4.0,1.0]]".to_string()))
); Ok(vec![
} ConcreteDataType::list_datatype(ConcreteDataType::float64_datatype()),
ConcreteDataType::list_datatype(ConcreteDataType::float64_datatype()),
#[test] ConcreteDataType::list_datatype(timestamp_type),
fn test_json_encode_path_merge() { ])
let mut accumulator1 = JsonEncodePathAccumulator::new();
let mut accumulator2 = JsonEncodePathAccumulator::new();
// Create test data for first accumulator
let lat_array1 = Arc::new(Float64Array::from(vec![1.0]));
let lng_array1 = Arc::new(Float64Array::from(vec![4.0]));
let ts_array1 = Arc::new(TimestampNanosecondArray::from(vec![100]));
// Create test data for second accumulator
let lat_array2 = Arc::new(Float64Array::from(vec![2.0]));
let lng_array2 = Arc::new(Float64Array::from(vec![5.0]));
let ts_array2 = Arc::new(TimestampNanosecondArray::from(vec![200]));
// Update batches
accumulator1
.update_batch(&[lat_array1, lng_array1, ts_array1])
.unwrap();
accumulator2
.update_batch(&[lat_array2, lng_array2, ts_array2])
.unwrap();
// Get states
let state1 = accumulator1.state().unwrap();
let state2 = accumulator2.state().unwrap();
// Create a merged accumulator
let mut merged = JsonEncodePathAccumulator::new();
// Extract the struct arrays from the states
let state_array1 = match &state1[0] {
ScalarValue::Struct(array) => array.clone(),
_ => panic!("Expected Struct scalar value"),
};
let state_array2 = match &state2[0] {
ScalarValue::Struct(array) => array.clone(),
_ => panic!("Expected Struct scalar value"),
};
// Merge state arrays
merged.merge_batch(&[state_array1]).unwrap();
merged.merge_batch(&[state_array2]).unwrap();
// Evaluate merged result
let result = merged.evaluate().unwrap();
assert_eq!(
result,
ScalarValue::Utf8(Some("[[4.0,1.0],[5.0,2.0]]".to_string()))
);
} }
} }

View File

@@ -12,20 +12,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::borrow::Cow;
use std::sync::Arc; use std::sync::Arc;
use arrow::array::{Array, ArrayRef, AsArray, BinaryArray, StringArray}; use common_macro::{as_aggr_func_creator, AggrFuncTypeStore};
use arrow_schema::{DataType, Field}; use common_query::error::{CreateAccumulatorSnafu, Error, InvalidFuncArgsSnafu};
use datafusion::logical_expr::{Signature, TypeSignature, Volatility}; use common_query::logical_plan::{
use datafusion_common::{Result, ScalarValue}; create_aggregate_function, Accumulator, AggregateFunctionCreator,
use datafusion_expr::{Accumulator, AggregateUDF, SimpleAggregateUDF};
use datafusion_functions_aggregate_common::accumulator::AccumulatorArgs;
use nalgebra::{Const, DVectorView, Dyn, OVector};
use crate::scalars::vector::impl_conv::{
binlit_as_veclit, parse_veclit_from_strlit, veclit_to_binlit,
}; };
use common_query::prelude::AccumulatorCreatorFunction;
use datafusion_expr::AggregateUDF;
use datatypes::prelude::{ConcreteDataType, Value, *};
use datatypes::vectors::VectorRef;
use nalgebra::{Const, DVectorView, Dyn, OVector};
use snafu::ensure;
use crate::scalars::vector::impl_conv::{as_veclit, as_veclit_if_const, veclit_to_binlit};
/// Aggregates by multiplying elements across the same dimension, returns a vector. /// Aggregates by multiplying elements across the same dimension, returns a vector.
#[derive(Debug, Default)] #[derive(Debug, Default)]
@@ -34,42 +35,57 @@ pub struct VectorProduct {
has_null: bool, has_null: bool,
} }
#[as_aggr_func_creator]
#[derive(Debug, Default, AggrFuncTypeStore)]
pub struct VectorProductCreator {}
impl AggregateFunctionCreator for VectorProductCreator {
fn creator(&self) -> AccumulatorCreatorFunction {
let creator: AccumulatorCreatorFunction = Arc::new(move |types: &[ConcreteDataType]| {
ensure!(
types.len() == 1,
InvalidFuncArgsSnafu {
err_msg: format!(
"The length of the args is not correct, expect exactly one, have: {}",
types.len()
)
}
);
let input_type = &types[0];
match input_type {
ConcreteDataType::String(_) | ConcreteDataType::Binary(_) => {
Ok(Box::new(VectorProduct::default()))
}
_ => {
let err_msg = format!(
"\"VEC_PRODUCT\" aggregate function not support data type {:?}",
input_type.logical_type_id(),
);
CreateAccumulatorSnafu { err_msg }.fail()?
}
}
});
creator
}
fn output_type(&self) -> common_query::error::Result<ConcreteDataType> {
Ok(ConcreteDataType::binary_datatype())
}
fn state_types(&self) -> common_query::error::Result<Vec<ConcreteDataType>> {
Ok(vec![self.output_type()?])
}
}
impl VectorProduct { impl VectorProduct {
/// Create a new `AggregateUDF` for the `vec_product` aggregate function. /// Create a new `AggregateUDF` for the `vec_product` aggregate function.
pub fn uadf_impl() -> AggregateUDF { pub fn uadf_impl() -> AggregateUDF {
let signature = Signature::one_of( create_aggregate_function(
vec![ "vec_product".to_string(),
TypeSignature::Exact(vec![DataType::Utf8]), 1,
TypeSignature::Exact(vec![DataType::Binary]), Arc::new(VectorProductCreator::default()),
], )
Volatility::Immutable, .into()
);
let udaf = SimpleAggregateUDF::new_with_signature(
"vec_product",
signature,
DataType::Binary,
Arc::new(Self::accumulator),
vec![Arc::new(Field::new("x", DataType::Binary, true))],
);
AggregateUDF::from(udaf)
}
fn accumulator(args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
if args.schema.fields().len() != 1 {
return Err(datafusion_common::DataFusionError::Internal(format!(
"expect creating `VEC_PRODUCT` with only one input field, actual {}",
args.schema.fields().len()
)));
}
let t = args.schema.field(0).data_type();
if !matches!(t, DataType::Utf8 | DataType::Binary) {
return Err(datafusion_common::DataFusionError::Internal(format!(
"unexpected input datatype {t} when creating `VEC_PRODUCT`"
)));
}
Ok(Box::new(VectorProduct::default()))
} }
fn inner(&mut self, len: usize) -> &mut OVector<f32, Dyn> { fn inner(&mut self, len: usize) -> &mut OVector<f32, Dyn> {
@@ -78,82 +94,67 @@ impl VectorProduct {
}) })
} }
fn update(&mut self, values: &[ArrayRef], is_update: bool) -> Result<()> { fn update(&mut self, values: &[VectorRef], is_update: bool) -> Result<(), Error> {
if values.is_empty() || self.has_null { if values.is_empty() || self.has_null {
return Ok(()); return Ok(());
}; };
let column = &values[0];
let len = column.len();
let vectors = match values[0].data_type() { match as_veclit_if_const(column)? {
DataType::Utf8 => { Some(column) => {
let arr: &StringArray = values[0].as_string(); let vec_column = DVectorView::from_slice(&column, column.len()).scale(len as f32);
arr.iter() *self.inner(vec_column.len()) =
.filter_map(|x| x.map(|s| parse_veclit_from_strlit(s).map_err(Into::into))) (*self.inner(vec_column.len())).component_mul(&vec_column);
.map(|x| x.map(Cow::Owned))
.collect::<Result<Vec<_>>>()?
} }
DataType::Binary => { None => {
let arr: &BinaryArray = values[0].as_binary(); for i in 0..len {
arr.iter() let Some(arg0) = as_veclit(column.get_ref(i))? else {
.filter_map(|x| x.map(|b| binlit_as_veclit(b).map_err(Into::into))) if is_update {
.collect::<Result<Vec<_>>>()? self.has_null = true;
self.product = None;
}
return Ok(());
};
let vec_column = DVectorView::from_slice(&arg0, arg0.len());
*self.inner(vec_column.len()) =
(*self.inner(vec_column.len())).component_mul(&vec_column);
}
} }
_ => {
return Err(datafusion_common::DataFusionError::NotImplemented(format!(
"unsupported data type {} for `VEC_PRODUCT`",
values[0].data_type()
)))
}
};
if vectors.len() != values[0].len() {
if is_update {
self.has_null = true;
self.product = None;
}
return Ok(());
} }
vectors.iter().for_each(|v| {
let v = DVectorView::from_slice(v, v.len());
let inner = self.inner(v.len());
*inner = inner.component_mul(&v);
});
Ok(()) Ok(())
} }
} }
impl Accumulator for VectorProduct { impl Accumulator for VectorProduct {
fn state(&mut self) -> Result<Vec<ScalarValue>> { fn state(&self) -> common_query::error::Result<Vec<Value>> {
self.evaluate().map(|v| vec![v]) self.evaluate().map(|v| vec![v])
} }
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> { fn update_batch(&mut self, values: &[VectorRef]) -> common_query::error::Result<()> {
self.update(values, true) self.update(values, true)
} }
fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> { fn merge_batch(&mut self, states: &[VectorRef]) -> common_query::error::Result<()> {
self.update(states, false) self.update(states, false)
} }
fn evaluate(&mut self) -> Result<ScalarValue> { fn evaluate(&self) -> common_query::error::Result<Value> {
match &self.product { match &self.product {
None => Ok(ScalarValue::Binary(None)), None => Ok(Value::Null),
Some(vector) => Ok(ScalarValue::Binary(Some(veclit_to_binlit( Some(vector) => {
vector.as_slice(), let v = vector.as_slice();
)))), Ok(Value::from(veclit_to_binlit(v)))
}
} }
} }
fn size(&self) -> usize {
size_of_val(self)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use datatypes::scalars::ScalarVector; use datatypes::vectors::{ConstantVector, StringVector};
use datatypes::vectors::{ConstantVector, StringVector, Vector};
use super::*; use super::*;
@@ -164,60 +165,59 @@ mod tests {
vec_product.update_batch(&[]).unwrap(); vec_product.update_batch(&[]).unwrap();
assert!(vec_product.product.is_none()); assert!(vec_product.product.is_none());
assert!(!vec_product.has_null); assert!(!vec_product.has_null);
assert_eq!(ScalarValue::Binary(None), vec_product.evaluate().unwrap()); assert_eq!(Value::Null, vec_product.evaluate().unwrap());
// test update one not-null value // test update one not-null value
let mut vec_product = VectorProduct::default(); let mut vec_product = VectorProduct::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![Some( let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![Some(
"[1.0,2.0,3.0]".to_string(), "[1.0,2.0,3.0]".to_string(),
)]))]; )]))];
vec_product.update_batch(&v).unwrap(); vec_product.update_batch(&v).unwrap();
assert_eq!( assert_eq!(
ScalarValue::Binary(Some(veclit_to_binlit(&[1.0, 2.0, 3.0]))), Value::from(veclit_to_binlit(&[1.0, 2.0, 3.0])),
vec_product.evaluate().unwrap() vec_product.evaluate().unwrap()
); );
// test update one null value // test update one null value
let mut vec_product = VectorProduct::default(); let mut vec_product = VectorProduct::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![Option::<String>::None]))]; let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![Option::<String>::None]))];
vec_product.update_batch(&v).unwrap(); vec_product.update_batch(&v).unwrap();
assert_eq!(ScalarValue::Binary(None), vec_product.evaluate().unwrap()); assert_eq!(Value::Null, vec_product.evaluate().unwrap());
// test update no null-value batch // test update no null-value batch
let mut vec_product = VectorProduct::default(); let mut vec_product = VectorProduct::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![ let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![
Some("[1.0,2.0,3.0]".to_string()), Some("[1.0,2.0,3.0]".to_string()),
Some("[4.0,5.0,6.0]".to_string()), Some("[4.0,5.0,6.0]".to_string()),
Some("[7.0,8.0,9.0]".to_string()), Some("[7.0,8.0,9.0]".to_string()),
]))]; ]))];
vec_product.update_batch(&v).unwrap(); vec_product.update_batch(&v).unwrap();
assert_eq!( assert_eq!(
ScalarValue::Binary(Some(veclit_to_binlit(&[28.0, 80.0, 162.0]))), Value::from(veclit_to_binlit(&[28.0, 80.0, 162.0])),
vec_product.evaluate().unwrap() vec_product.evaluate().unwrap()
); );
// test update null-value batch // test update null-value batch
let mut vec_product = VectorProduct::default(); let mut vec_product = VectorProduct::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![ let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![
Some("[1.0,2.0,3.0]".to_string()), Some("[1.0,2.0,3.0]".to_string()),
None, None,
Some("[7.0,8.0,9.0]".to_string()), Some("[7.0,8.0,9.0]".to_string()),
]))]; ]))];
vec_product.update_batch(&v).unwrap(); vec_product.update_batch(&v).unwrap();
assert_eq!(ScalarValue::Binary(None), vec_product.evaluate().unwrap()); assert_eq!(Value::Null, vec_product.evaluate().unwrap());
// test update with constant vector // test update with constant vector
let mut vec_product = VectorProduct::default(); let mut vec_product = VectorProduct::default();
let v: Vec<ArrayRef> = vec![Arc::new(ConstantVector::new( let v: Vec<VectorRef> = vec![Arc::new(ConstantVector::new(
Arc::new(StringVector::from_vec(vec!["[1.0,2.0,3.0]".to_string()])), Arc::new(StringVector::from_vec(vec!["[1.0,2.0,3.0]".to_string()])),
4, 4,
)) ))];
.to_arrow_array()];
vec_product.update_batch(&v).unwrap(); vec_product.update_batch(&v).unwrap();
assert_eq!( assert_eq!(
ScalarValue::Binary(Some(veclit_to_binlit(&[1.0, 16.0, 81.0]))), Value::from(veclit_to_binlit(&[4.0, 8.0, 12.0])),
vec_product.evaluate().unwrap() vec_product.evaluate().unwrap()
); );
} }

View File

@@ -14,18 +14,19 @@
use std::sync::Arc; use std::sync::Arc;
use arrow::array::{Array, ArrayRef, AsArray, BinaryArray, StringArray}; use common_macro::{as_aggr_func_creator, AggrFuncTypeStore};
use arrow_schema::{DataType, Field}; use common_query::error::{CreateAccumulatorSnafu, Error, InvalidFuncArgsSnafu};
use datafusion_common::{Result, ScalarValue}; use common_query::logical_plan::{
use datafusion_expr::{ create_aggregate_function, Accumulator, AggregateFunctionCreator,
Accumulator, AggregateUDF, Signature, SimpleAggregateUDF, TypeSignature, Volatility,
}; };
use datafusion_functions_aggregate_common::accumulator::AccumulatorArgs; use common_query::prelude::AccumulatorCreatorFunction;
use datafusion_expr::AggregateUDF;
use datatypes::prelude::{ConcreteDataType, Value, *};
use datatypes::vectors::VectorRef;
use nalgebra::{Const, DVectorView, Dyn, OVector}; use nalgebra::{Const, DVectorView, Dyn, OVector};
use snafu::ensure;
use crate::scalars::vector::impl_conv::{ use crate::scalars::vector::impl_conv::{as_veclit, as_veclit_if_const, veclit_to_binlit};
binlit_as_veclit, parse_veclit_from_strlit, veclit_to_binlit,
};
/// The accumulator for the `vec_sum` aggregate function. /// The accumulator for the `vec_sum` aggregate function.
#[derive(Debug, Default)] #[derive(Debug, Default)]
@@ -34,42 +35,57 @@ pub struct VectorSum {
has_null: bool, has_null: bool,
} }
#[as_aggr_func_creator]
#[derive(Debug, Default, AggrFuncTypeStore)]
pub struct VectorSumCreator {}
impl AggregateFunctionCreator for VectorSumCreator {
fn creator(&self) -> AccumulatorCreatorFunction {
let creator: AccumulatorCreatorFunction = Arc::new(move |types: &[ConcreteDataType]| {
ensure!(
types.len() == 1,
InvalidFuncArgsSnafu {
err_msg: format!(
"The length of the args is not correct, expect exactly one, have: {}",
types.len()
)
}
);
let input_type = &types[0];
match input_type {
ConcreteDataType::String(_) | ConcreteDataType::Binary(_) => {
Ok(Box::new(VectorSum::default()))
}
_ => {
let err_msg = format!(
"\"VEC_SUM\" aggregate function not support data type {:?}",
input_type.logical_type_id(),
);
CreateAccumulatorSnafu { err_msg }.fail()?
}
}
});
creator
}
fn output_type(&self) -> common_query::error::Result<ConcreteDataType> {
Ok(ConcreteDataType::binary_datatype())
}
fn state_types(&self) -> common_query::error::Result<Vec<ConcreteDataType>> {
Ok(vec![self.output_type()?])
}
}
impl VectorSum { impl VectorSum {
/// Create a new `AggregateUDF` for the `vec_sum` aggregate function. /// Create a new `AggregateUDF` for the `vec_sum` aggregate function.
pub fn uadf_impl() -> AggregateUDF { pub fn uadf_impl() -> AggregateUDF {
let signature = Signature::one_of( create_aggregate_function(
vec![ "vec_sum".to_string(),
TypeSignature::Exact(vec![DataType::Utf8]), 1,
TypeSignature::Exact(vec![DataType::Binary]), Arc::new(VectorSumCreator::default()),
], )
Volatility::Immutable, .into()
);
let udaf = SimpleAggregateUDF::new_with_signature(
"vec_sum",
signature,
DataType::Binary,
Arc::new(Self::accumulator),
vec![Arc::new(Field::new("x", DataType::Binary, true))],
);
AggregateUDF::from(udaf)
}
fn accumulator(args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
if args.schema.fields().len() != 1 {
return Err(datafusion_common::DataFusionError::Internal(format!(
"expect creating `VEC_SUM` with only one input field, actual {}",
args.schema.fields().len()
)));
}
let t = args.schema.field(0).data_type();
if !matches!(t, DataType::Utf8 | DataType::Binary) {
return Err(datafusion_common::DataFusionError::Internal(format!(
"unexpected input datatype {t} when creating `VEC_SUM`"
)));
}
Ok(Box::new(VectorSum::default()))
} }
fn inner(&mut self, len: usize) -> &mut OVector<f32, Dyn> { fn inner(&mut self, len: usize) -> &mut OVector<f32, Dyn> {
@@ -77,87 +93,62 @@ impl VectorSum {
.get_or_insert_with(|| OVector::zeros_generic(Dyn(len), Const::<1>)) .get_or_insert_with(|| OVector::zeros_generic(Dyn(len), Const::<1>))
} }
fn update(&mut self, values: &[ArrayRef], is_update: bool) -> Result<()> { fn update(&mut self, values: &[VectorRef], is_update: bool) -> Result<(), Error> {
if values.is_empty() || self.has_null { if values.is_empty() || self.has_null {
return Ok(()); return Ok(());
}; };
let column = &values[0];
let len = column.len();
match values[0].data_type() { match as_veclit_if_const(column)? {
DataType::Utf8 => { Some(column) => {
let arr: &StringArray = values[0].as_string(); let vec_column = DVectorView::from_slice(&column, column.len()).scale(len as f32);
for s in arr.iter() { *self.inner(vec_column.len()) += vec_column;
let Some(s) = s else { }
None => {
for i in 0..len {
let Some(arg0) = as_veclit(column.get_ref(i))? else {
if is_update { if is_update {
self.has_null = true; self.has_null = true;
self.sum = None; self.sum = None;
} }
return Ok(()); return Ok(());
}; };
let values = parse_veclit_from_strlit(s)?; let vec_column = DVectorView::from_slice(&arg0, arg0.len());
let vec_column = DVectorView::from_slice(&values, values.len());
*self.inner(vec_column.len()) += vec_column; *self.inner(vec_column.len()) += vec_column;
} }
} }
DataType::Binary => {
let arr: &BinaryArray = values[0].as_binary();
for b in arr.iter() {
let Some(b) = b else {
if is_update {
self.has_null = true;
self.sum = None;
}
return Ok(());
};
let values = binlit_as_veclit(b)?;
let vec_column = DVectorView::from_slice(&values, values.len());
*self.inner(vec_column.len()) += vec_column;
}
}
_ => {
return Err(datafusion_common::DataFusionError::NotImplemented(format!(
"unsupported data type {} for `VEC_SUM`",
values[0].data_type()
)))
}
} }
Ok(()) Ok(())
} }
} }
impl Accumulator for VectorSum { impl Accumulator for VectorSum {
fn state(&mut self) -> Result<Vec<ScalarValue>> { fn state(&self) -> common_query::error::Result<Vec<Value>> {
self.evaluate().map(|v| vec![v]) self.evaluate().map(|v| vec![v])
} }
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> { fn update_batch(&mut self, values: &[VectorRef]) -> common_query::error::Result<()> {
self.update(values, true) self.update(values, true)
} }
fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> { fn merge_batch(&mut self, states: &[VectorRef]) -> common_query::error::Result<()> {
self.update(states, false) self.update(states, false)
} }
fn evaluate(&mut self) -> Result<ScalarValue> { fn evaluate(&self) -> common_query::error::Result<Value> {
match &self.sum { match &self.sum {
None => Ok(ScalarValue::Binary(None)), None => Ok(Value::Null),
Some(vector) => Ok(ScalarValue::Binary(Some(veclit_to_binlit( Some(vector) => Ok(Value::from(veclit_to_binlit(vector.as_slice()))),
vector.as_slice(),
)))),
} }
} }
fn size(&self) -> usize {
size_of_val(self)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use arrow::array::StringArray; use datatypes::vectors::{ConstantVector, StringVector};
use datatypes::scalars::ScalarVector;
use datatypes::vectors::{ConstantVector, StringVector, Vector};
use super::*; use super::*;
@@ -168,58 +159,57 @@ mod tests {
vec_sum.update_batch(&[]).unwrap(); vec_sum.update_batch(&[]).unwrap();
assert!(vec_sum.sum.is_none()); assert!(vec_sum.sum.is_none());
assert!(!vec_sum.has_null); assert!(!vec_sum.has_null);
assert_eq!(ScalarValue::Binary(None), vec_sum.evaluate().unwrap()); assert_eq!(Value::Null, vec_sum.evaluate().unwrap());
// test update one not-null value // test update one not-null value
let mut vec_sum = VectorSum::default(); let mut vec_sum = VectorSum::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![Some( let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![Some(
"[1.0,2.0,3.0]".to_string(), "[1.0,2.0,3.0]".to_string(),
)]))]; )]))];
vec_sum.update_batch(&v).unwrap(); vec_sum.update_batch(&v).unwrap();
assert_eq!( assert_eq!(
ScalarValue::Binary(Some(veclit_to_binlit(&[1.0, 2.0, 3.0]))), Value::from(veclit_to_binlit(&[1.0, 2.0, 3.0])),
vec_sum.evaluate().unwrap() vec_sum.evaluate().unwrap()
); );
// test update one null value // test update one null value
let mut vec_sum = VectorSum::default(); let mut vec_sum = VectorSum::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![Option::<String>::None]))]; let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![Option::<String>::None]))];
vec_sum.update_batch(&v).unwrap(); vec_sum.update_batch(&v).unwrap();
assert_eq!(ScalarValue::Binary(None), vec_sum.evaluate().unwrap()); assert_eq!(Value::Null, vec_sum.evaluate().unwrap());
// test update no null-value batch // test update no null-value batch
let mut vec_sum = VectorSum::default(); let mut vec_sum = VectorSum::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![ let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![
Some("[1.0,2.0,3.0]".to_string()), Some("[1.0,2.0,3.0]".to_string()),
Some("[4.0,5.0,6.0]".to_string()), Some("[4.0,5.0,6.0]".to_string()),
Some("[7.0,8.0,9.0]".to_string()), Some("[7.0,8.0,9.0]".to_string()),
]))]; ]))];
vec_sum.update_batch(&v).unwrap(); vec_sum.update_batch(&v).unwrap();
assert_eq!( assert_eq!(
ScalarValue::Binary(Some(veclit_to_binlit(&[12.0, 15.0, 18.0]))), Value::from(veclit_to_binlit(&[12.0, 15.0, 18.0])),
vec_sum.evaluate().unwrap() vec_sum.evaluate().unwrap()
); );
// test update null-value batch // test update null-value batch
let mut vec_sum = VectorSum::default(); let mut vec_sum = VectorSum::default();
let v: Vec<ArrayRef> = vec![Arc::new(StringArray::from(vec![ let v: Vec<VectorRef> = vec![Arc::new(StringVector::from(vec![
Some("[1.0,2.0,3.0]".to_string()), Some("[1.0,2.0,3.0]".to_string()),
None, None,
Some("[7.0,8.0,9.0]".to_string()), Some("[7.0,8.0,9.0]".to_string()),
]))]; ]))];
vec_sum.update_batch(&v).unwrap(); vec_sum.update_batch(&v).unwrap();
assert_eq!(ScalarValue::Binary(None), vec_sum.evaluate().unwrap()); assert_eq!(Value::Null, vec_sum.evaluate().unwrap());
// test update with constant vector // test update with constant vector
let mut vec_sum = VectorSum::default(); let mut vec_sum = VectorSum::default();
let v: Vec<ArrayRef> = vec![Arc::new(ConstantVector::new( let v: Vec<VectorRef> = vec![Arc::new(ConstantVector::new(
Arc::new(StringVector::from_vec(vec!["[1.0,2.0,3.0]".to_string()])), Arc::new(StringVector::from_vec(vec!["[1.0,2.0,3.0]".to_string()])),
4, 4,
)) ))];
.to_arrow_array()];
vec_sum.update_batch(&v).unwrap(); vec_sum.update_batch(&v).unwrap();
assert_eq!( assert_eq!(
ScalarValue::Binary(Some(veclit_to_binlit(&[4.0, 8.0, 12.0]))), Value::from(veclit_to_binlit(&[4.0, 8.0, 12.0])),
vec_sum.evaluate().unwrap() vec_sum.evaluate().unwrap()
); );
} }

View File

@@ -12,24 +12,28 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use arrow::datatypes::DataType as ArrowDataType;
use common_error::ext::BoxedError; use common_error::ext::BoxedError;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_query::error::{ use common_query::error::{
ExecuteSnafu, InvalidFuncArgsSnafu, MissingFlowServiceHandlerSnafu, Result, ExecuteSnafu, InvalidFuncArgsSnafu, MissingFlowServiceHandlerSnafu, Result,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use datafusion_expr::{Signature, Volatility}; use common_query::prelude::Signature;
use datafusion::logical_expr::Volatility;
use datatypes::value::{Value, ValueRef}; use datatypes::value::{Value, ValueRef};
use session::context::QueryContextRef; use session::context::QueryContextRef;
use snafu::{ensure, ResultExt}; use snafu::{ensure, ResultExt};
use sql::ast::ObjectNamePartExt;
use sql::parser::ParserContext; use sql::parser::ParserContext;
use store_api::storage::ConcreteDataType;
use crate::handlers::FlowServiceHandlerRef; use crate::handlers::FlowServiceHandlerRef;
fn flush_signature() -> Signature { fn flush_signature() -> Signature {
Signature::uniform(1, vec![ArrowDataType::Utf8], Volatility::Immutable) Signature::uniform(
1,
vec![ConcreteDataType::string_datatype()],
Volatility::Immutable,
)
} }
#[admin_fn( #[admin_fn(
@@ -81,9 +85,9 @@ fn parse_flush_flow(
let (catalog_name, flow_name) = match &obj_name.0[..] { let (catalog_name, flow_name) = match &obj_name.0[..] {
[flow_name] => ( [flow_name] => (
query_ctx.current_catalog().to_string(), query_ctx.current_catalog().to_string(),
flow_name.to_string_unquoted(), flow_name.value.clone(),
), ),
[catalog, flow_name] => (catalog.to_string_unquoted(), flow_name.to_string_unquoted()), [catalog, flow_name] => (catalog.value.clone(), flow_name.value.clone()),
_ => { _ => {
return InvalidFuncArgsSnafu { return InvalidFuncArgsSnafu {
err_msg: format!( err_msg: format!(
@@ -101,55 +105,44 @@ fn parse_flush_flow(
mod test { mod test {
use std::sync::Arc; use std::sync::Arc;
use datatypes::scalars::ScalarVector;
use datatypes::vectors::StringVector;
use session::context::QueryContext; use session::context::QueryContext;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[test] #[test]
fn test_flush_flow_metadata() { fn test_flush_flow_metadata() {
let factory: ScalarFunctionFactory = FlushFlowFunction::factory().into(); let f = FlushFlowFunction;
let f = factory.provide(FunctionContext::mock());
assert_eq!("flush_flow", f.name()); assert_eq!("flush_flow", f.name());
assert_eq!(ArrowDataType::UInt64, f.return_type(&[]).unwrap()); assert_eq!(
let expected_signature = datafusion_expr::Signature::uniform( ConcreteDataType::uint64_datatype(),
1, f.return_type(&[]).unwrap()
vec![ArrowDataType::Utf8], );
datafusion_expr::Volatility::Immutable, assert_eq!(
f.signature(),
Signature::uniform(
1,
vec![ConcreteDataType::string_datatype()],
Volatility::Immutable,
)
); );
assert_eq!(*f.signature(), expected_signature);
} }
#[tokio::test] #[tokio::test]
async fn test_missing_flow_service() { async fn test_missing_flow_service() {
let factory: ScalarFunctionFactory = FlushFlowFunction::factory().into(); let f = FlushFlowFunction;
let binding = factory.provide(FunctionContext::default());
let f = binding.as_async().unwrap();
let flow_name_array = Arc::new(arrow::array::StringArray::from(vec!["flow_name"])); let args = vec!["flow_name"];
let args = args
.into_iter()
.map(|arg| Arc::new(StringVector::from_slice(&[arg])) as _)
.collect::<Vec<_>>();
let columnar_args = vec![datafusion_expr::ColumnarValue::Array(flow_name_array as _)]; let result = f.eval(FunctionContext::default(), &args).await.unwrap_err();
let func_args = datafusion::logical_expr::ScalarFunctionArgs {
args: columnar_args,
arg_fields: vec![Arc::new(arrow::datatypes::Field::new(
"arg_0",
ArrowDataType::Utf8,
false,
))],
return_field: Arc::new(arrow::datatypes::Field::new(
"result",
ArrowDataType::UInt64,
true,
)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap_err();
assert_eq!( assert_eq!(
"Execution error: Handler error: Missing FlowServiceHandler, not expected", "Missing FlowServiceHandler, not expected",
result.to_string() result.to_string()
); );
} }

View File

@@ -41,12 +41,6 @@ impl FunctionContext {
} }
} }
impl std::fmt::Display for FunctionContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FunctionContext {{ query_ctx: {} }}", self.query_ctx)
}
}
impl Default for FunctionContext { impl Default for FunctionContext {
fn default() -> Self { fn default() -> Self {
Self { Self {
@@ -73,3 +67,22 @@ pub trait Function: fmt::Display + Sync + Send {
} }
pub type FunctionRef = Arc<dyn Function>; pub type FunctionRef = Arc<dyn Function>;
/// Async Scalar function trait
#[async_trait::async_trait]
pub trait AsyncFunction: fmt::Display + Sync + Send {
/// Returns the name of the function, should be unique.
fn name(&self) -> &str;
/// The returned data type of function execution.
fn return_type(&self, input_types: &[ConcreteDataType]) -> Result<ConcreteDataType>;
/// The signature of function.
fn signature(&self) -> Signature;
/// Evaluate the function, e.g. run/execute the function.
/// TODO(dennis): simplify the signature and refactor all the admin functions.
async fn eval(&self, _func_ctx: FunctionContext, _columns: &[VectorRef]) -> Result<VectorRef>;
}
pub type AsyncFunctionRef = Arc<dyn AsyncFunction>;

View File

@@ -22,8 +22,8 @@ use crate::scalars::udf::create_udf;
/// A factory for creating `ScalarUDF` that require a function context. /// A factory for creating `ScalarUDF` that require a function context.
#[derive(Clone)] #[derive(Clone)]
pub struct ScalarFunctionFactory { pub struct ScalarFunctionFactory {
pub name: String, name: String,
pub factory: Arc<dyn Fn(FunctionContext) -> ScalarUDF + Send + Sync>, factory: Arc<dyn Fn(FunctionContext) -> ScalarUDF + Send + Sync>,
} }
impl ScalarFunctionFactory { impl ScalarFunctionFactory {

View File

@@ -24,7 +24,7 @@ use crate::aggrs::aggr_wrapper::StateMergeHelper;
use crate::aggrs::approximate::ApproximateFunction; use crate::aggrs::approximate::ApproximateFunction;
use crate::aggrs::count_hash::CountHash; use crate::aggrs::count_hash::CountHash;
use crate::aggrs::vector::VectorFunction as VectorAggrFunction; use crate::aggrs::vector::VectorFunction as VectorAggrFunction;
use crate::function::{Function, FunctionRef}; use crate::function::{AsyncFunctionRef, Function, FunctionRef};
use crate::function_factory::ScalarFunctionFactory; use crate::function_factory::ScalarFunctionFactory;
use crate::scalars::date::DateFunction; use crate::scalars::date::DateFunction;
use crate::scalars::expression::ExpressionFunction; use crate::scalars::expression::ExpressionFunction;
@@ -42,18 +42,11 @@ use crate::system::SystemFunction;
#[derive(Default)] #[derive(Default)]
pub struct FunctionRegistry { pub struct FunctionRegistry {
functions: RwLock<HashMap<String, ScalarFunctionFactory>>, functions: RwLock<HashMap<String, ScalarFunctionFactory>>,
async_functions: RwLock<HashMap<String, AsyncFunctionRef>>,
aggregate_functions: RwLock<HashMap<String, AggregateUDF>>, aggregate_functions: RwLock<HashMap<String, AggregateUDF>>,
} }
impl FunctionRegistry { impl FunctionRegistry {
/// Register a function in the registry by converting it into a `ScalarFunctionFactory`.
///
/// # Arguments
///
/// * `func` - An object that can be converted into a `ScalarFunctionFactory`.
///
/// The function is inserted into the internal function map, keyed by its name.
/// If a function with the same name already exists, it will be replaced.
pub fn register(&self, func: impl Into<ScalarFunctionFactory>) { pub fn register(&self, func: impl Into<ScalarFunctionFactory>) {
let func = func.into(); let func = func.into();
let _ = self let _ = self
@@ -63,12 +56,18 @@ impl FunctionRegistry {
.insert(func.name().to_string(), func); .insert(func.name().to_string(), func);
} }
/// Register a scalar function in the registry.
pub fn register_scalar(&self, func: impl Function + 'static) { pub fn register_scalar(&self, func: impl Function + 'static) {
self.register(Arc::new(func) as FunctionRef); self.register(Arc::new(func) as FunctionRef);
} }
/// Register an aggregate function in the registry. pub fn register_async(&self, func: AsyncFunctionRef) {
let _ = self
.async_functions
.write()
.unwrap()
.insert(func.name().to_string(), func);
}
pub fn register_aggr(&self, func: AggregateUDF) { pub fn register_aggr(&self, func: AggregateUDF) {
let _ = self let _ = self
.aggregate_functions .aggregate_functions
@@ -77,16 +76,28 @@ impl FunctionRegistry {
.insert(func.name().to_string(), func); .insert(func.name().to_string(), func);
} }
pub fn get_async_function(&self, name: &str) -> Option<AsyncFunctionRef> {
self.async_functions.read().unwrap().get(name).cloned()
}
pub fn async_functions(&self) -> Vec<AsyncFunctionRef> {
self.async_functions
.read()
.unwrap()
.values()
.cloned()
.collect()
}
#[cfg(test)]
pub fn get_function(&self, name: &str) -> Option<ScalarFunctionFactory> { pub fn get_function(&self, name: &str) -> Option<ScalarFunctionFactory> {
self.functions.read().unwrap().get(name).cloned() self.functions.read().unwrap().get(name).cloned()
} }
/// Returns a list of all scalar functions registered in the registry.
pub fn scalar_functions(&self) -> Vec<ScalarFunctionFactory> { pub fn scalar_functions(&self) -> Vec<ScalarFunctionFactory> {
self.functions.read().unwrap().values().cloned().collect() self.functions.read().unwrap().values().cloned().collect()
} }
/// Returns a list of all aggregate functions registered in the registry.
pub fn aggregate_functions(&self) -> Vec<AggregateUDF> { pub fn aggregate_functions(&self) -> Vec<AggregateUDF> {
self.aggregate_functions self.aggregate_functions
.read() .read()
@@ -96,7 +107,6 @@ impl FunctionRegistry {
.collect() .collect()
} }
/// Returns true if an aggregate function with the given name exists in the registry.
pub fn is_aggr_func_exist(&self, name: &str) -> bool { pub fn is_aggr_func_exist(&self, name: &str) -> bool {
self.aggregate_functions.read().unwrap().contains_key(name) self.aggregate_functions.read().unwrap().contains_key(name)
} }

View File

@@ -34,33 +34,6 @@ pub struct ClampFunction;
const CLAMP_NAME: &str = "clamp"; const CLAMP_NAME: &str = "clamp";
/// Ensure the vector is constant and not empty (i.e., all values are identical)
fn ensure_constant_vector(vector: &VectorRef) -> Result<()> {
ensure!(
!vector.is_empty(),
InvalidFuncArgsSnafu {
err_msg: "Expect at least one value",
}
);
if vector.is_const() {
return Ok(());
}
let first = vector.get_ref(0);
for i in 1..vector.len() {
let v = vector.get_ref(i);
if first != v {
return InvalidFuncArgsSnafu {
err_msg: "All values in min/max argument must be identical",
}
.fail();
}
}
Ok(())
}
impl Function for ClampFunction { impl Function for ClampFunction {
fn name(&self) -> &str { fn name(&self) -> &str {
CLAMP_NAME CLAMP_NAME
@@ -107,9 +80,16 @@ impl Function for ClampFunction {
), ),
} }
); );
ensure!(
ensure_constant_vector(&columns[1])?; (columns[1].len() == 1 || columns[1].is_const())
ensure_constant_vector(&columns[2])?; && (columns[2].len() == 1 || columns[2].is_const()),
InvalidFuncArgsSnafu {
err_msg: format!(
"The second and third args should be scalar, have: {:?}, {:?}",
columns[1], columns[2]
),
}
);
with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| { with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| {
let input_array = columns[0].to_arrow_array(); let input_array = columns[0].to_arrow_array();
@@ -224,8 +204,15 @@ impl Function for ClampMinFunction {
), ),
} }
); );
ensure!(
ensure_constant_vector(&columns[1])?; columns[1].len() == 1 || columns[1].is_const(),
InvalidFuncArgsSnafu {
err_msg: format!(
"The second arg (min) should be scalar, have: {:?}",
columns[1]
),
}
);
with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| { with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| {
let input_array = columns[0].to_arrow_array(); let input_array = columns[0].to_arrow_array();
@@ -305,8 +292,15 @@ impl Function for ClampMaxFunction {
), ),
} }
); );
ensure!(
ensure_constant_vector(&columns[1])?; columns[1].len() == 1 || columns[1].is_const(),
InvalidFuncArgsSnafu {
err_msg: format!(
"The second arg (max) should be scalar, have: {:?}",
columns[1]
),
}
);
with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| { with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| {
let input_array = columns[0].to_arrow_array(); let input_array = columns[0].to_arrow_array();
@@ -543,8 +537,8 @@ mod test {
let func = ClampFunction; let func = ClampFunction;
let args = [ let args = [
Arc::new(Float64Vector::from(input)) as _, Arc::new(Float64Vector::from(input)) as _,
Arc::new(Float64Vector::from_vec(vec![min, max])) as _, Arc::new(Float64Vector::from_vec(vec![min, min])) as _,
Arc::new(Float64Vector::from_vec(vec![max, min])) as _, Arc::new(Float64Vector::from_vec(vec![max])) as _,
]; ];
let result = func.eval(&FunctionContext::default(), args.as_slice()); let result = func.eval(&FunctionContext::default(), args.as_slice());
assert!(result.is_err()); assert!(result.is_err());

View File

@@ -16,12 +16,15 @@ use std::any::Any;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use common_query::error::FromScalarValueSnafu;
use common_query::prelude::ColumnarValue; use common_query::prelude::ColumnarValue;
use datafusion::logical_expr::{ScalarFunctionArgs, ScalarUDFImpl}; use datafusion::logical_expr::{ScalarFunctionArgs, ScalarUDFImpl};
use datafusion_expr::ScalarUDF; use datafusion_expr::ScalarUDF;
use datatypes::data_type::DataType; use datatypes::data_type::DataType;
use datatypes::prelude::*; use datatypes::prelude::*;
use datatypes::vectors::Helper;
use session::context::QueryContextRef; use session::context::QueryContextRef;
use snafu::ResultExt;
use crate::function::{FunctionContext, FunctionRef}; use crate::function::{FunctionContext, FunctionRef};
use crate::state::FunctionState; use crate::state::FunctionState;
@@ -73,7 +76,13 @@ impl ScalarUDFImpl for ScalarUdf {
let columns = args let columns = args
.args .args
.iter() .iter()
.map(|x| ColumnarValue::try_from(x).and_then(|y| y.try_into_vector(args.number_rows))) .map(|x| {
ColumnarValue::try_from(x).and_then(|y| match y {
ColumnarValue::Vector(z) => Ok(z),
ColumnarValue::Scalar(z) => Helper::try_from_scalar_value(z, args.number_rows)
.context(FromScalarValueSnafu),
})
})
.collect::<common_query::error::Result<Vec<_>>>()?; .collect::<common_query::error::Result<Vec<_>>>()?;
let v = self let v = self
.function .function
@@ -104,8 +113,6 @@ mod tests {
use common_query::prelude::ScalarValue; use common_query::prelude::ScalarValue;
use datafusion::arrow::array::BooleanArray; use datafusion::arrow::array::BooleanArray;
use datafusion_common::config::ConfigOptions;
use datatypes::arrow::datatypes::Field;
use datatypes::data_type::ConcreteDataType; use datatypes::data_type::ConcreteDataType;
use datatypes::prelude::VectorRef; use datatypes::prelude::VectorRef;
use datatypes::vectors::{BooleanVector, ConstantVector}; use datatypes::vectors::{BooleanVector, ConstantVector};
@@ -155,21 +162,10 @@ mod tests {
]))), ]))),
]; ];
let arg_fields = vec![
Arc::new(Field::new("a", args[0].data_type(), false)),
Arc::new(Field::new("b", args[1].data_type(), false)),
];
let return_field = Arc::new(Field::new(
"x",
ConcreteDataType::boolean_datatype().as_arrow_type(),
false,
));
let args = ScalarFunctionArgs { let args = ScalarFunctionArgs {
args, args,
arg_fields,
number_rows: 4, number_rows: 4,
return_field, return_type: &ConcreteDataType::boolean_datatype().as_arrow_type(),
config_options: Arc::new(ConfigOptions::default()),
}; };
match udf.invoke_with_args(args).unwrap() { match udf.invoke_with_args(args).unwrap() {
datafusion_expr::ColumnarValue::Array(x) => { datafusion_expr::ColumnarValue::Array(x) => {

View File

@@ -19,6 +19,8 @@ mod procedure_state;
mod timezone; mod timezone;
mod version; mod version;
use std::sync::Arc;
use build::BuildFunction; use build::BuildFunction;
use database::{ use database::{
ConnectionIdFunction, CurrentSchemaFunction, DatabaseFunction, PgBackendPidFunction, ConnectionIdFunction, CurrentSchemaFunction, DatabaseFunction, PgBackendPidFunction,
@@ -44,7 +46,7 @@ impl SystemFunction {
registry.register_scalar(PgBackendPidFunction); registry.register_scalar(PgBackendPidFunction);
registry.register_scalar(ConnectionIdFunction); registry.register_scalar(ConnectionIdFunction);
registry.register_scalar(TimezoneFunction); registry.register_scalar(TimezoneFunction);
registry.register(ProcedureStateFunction::factory()); registry.register_async(Arc::new(ProcedureStateFunction));
PGCatalogFunction::register(registry); PGCatalogFunction::register(registry);
} }
} }

View File

@@ -13,14 +13,13 @@
// limitations under the License. // limitations under the License.
use api::v1::meta::ProcedureStatus; use api::v1::meta::ProcedureStatus;
use arrow::datatypes::DataType as ArrowDataType;
use common_macro::admin_fn; use common_macro::admin_fn;
use common_meta::rpc::procedure::ProcedureStateResponse; use common_meta::rpc::procedure::ProcedureStateResponse;
use common_query::error::{ use common_query::error::{
InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result, InvalidFuncArgsSnafu, MissingProcedureServiceHandlerSnafu, Result,
UnsupportedInputDataTypeSnafu, UnsupportedInputDataTypeSnafu,
}; };
use datafusion_expr::{Signature, Volatility}; use common_query::prelude::{Signature, Volatility};
use datatypes::prelude::*; use datatypes::prelude::*;
use serde::Serialize; use serde::Serialize;
use session::context::QueryContextRef; use session::context::QueryContextRef;
@@ -82,86 +81,73 @@ pub(crate) async fn procedure_state(
} }
fn signature() -> Signature { fn signature() -> Signature {
Signature::uniform(1, vec![ArrowDataType::Utf8], Volatility::Immutable) Signature::uniform(
1,
vec![ConcreteDataType::string_datatype()],
Volatility::Immutable,
)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::sync::Arc; use std::sync::Arc;
use arrow::array::StringArray; use common_query::prelude::TypeSignature;
use arrow::datatypes::{DataType, Field}; use datatypes::vectors::StringVector;
use datafusion_expr::ColumnarValue;
use super::*; use super::*;
use crate::function::FunctionContext; use crate::function::{AsyncFunction, FunctionContext};
use crate::function_factory::ScalarFunctionFactory;
#[test] #[test]
fn test_procedure_state_misc() { fn test_procedure_state_misc() {
let factory: ScalarFunctionFactory = ProcedureStateFunction::factory().into(); let f = ProcedureStateFunction;
let f = factory.provide(FunctionContext::mock());
assert_eq!("procedure_state", f.name()); assert_eq!("procedure_state", f.name());
assert_eq!(DataType::Utf8, f.return_type(&[]).unwrap()); assert_eq!(
ConcreteDataType::string_datatype(),
f.return_type(&[]).unwrap()
);
assert!(matches!(f.signature(), assert!(matches!(f.signature(),
datafusion_expr::Signature { Signature {
type_signature: datafusion_expr::TypeSignature::Uniform(1, valid_types), type_signature: TypeSignature::Uniform(1, valid_types),
volatility: datafusion_expr::Volatility::Immutable volatility: Volatility::Immutable
} if valid_types == &vec![ArrowDataType::Utf8])); } if valid_types == vec![ConcreteDataType::string_datatype()]
));
} }
#[tokio::test] #[tokio::test]
async fn test_missing_procedure_service() { async fn test_missing_procedure_service() {
let factory: ScalarFunctionFactory = ProcedureStateFunction::factory().into(); let f = ProcedureStateFunction;
let binding = factory.provide(FunctionContext::default());
let f = binding.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let args = vec!["pid"];
args: vec![ColumnarValue::Array(Arc::new(StringArray::from(vec![
"pid", let args = args
])))], .into_iter()
arg_fields: vec![Arc::new(Field::new("arg_0", DataType::Utf8, false))], .map(|arg| Arc::new(StringVector::from_slice(&[arg])) as _)
return_field: Arc::new(Field::new("result", DataType::Utf8, true)), .collect::<Vec<_>>();
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()), let result = f.eval(FunctionContext::default(), &args).await.unwrap_err();
}; assert_eq!(
let result = f.invoke_async_with_args(func_args).await; "Missing ProcedureServiceHandler, not expected",
assert!(result.is_err()); result.to_string()
);
} }
#[tokio::test] #[tokio::test]
async fn test_procedure_state() { async fn test_procedure_state() {
let factory: ScalarFunctionFactory = ProcedureStateFunction::factory().into(); let f = ProcedureStateFunction;
let provider = factory.provide(FunctionContext::mock());
let f = provider.as_async().unwrap();
let func_args = datafusion::logical_expr::ScalarFunctionArgs { let args = vec!["pid"];
args: vec![ColumnarValue::Array(Arc::new(StringArray::from(vec![
"pid",
])))],
arg_fields: vec![Arc::new(Field::new("arg_0", DataType::Utf8, false))],
return_field: Arc::new(Field::new("result", DataType::Utf8, true)),
number_rows: 1,
config_options: Arc::new(datafusion_common::config::ConfigOptions::default()),
};
let result = f.invoke_async_with_args(func_args).await.unwrap();
match result { let args = args
ColumnarValue::Array(array) => { .into_iter()
let result_array = array.as_any().downcast_ref::<StringArray>().unwrap(); .map(|arg| Arc::new(StringVector::from_slice(&[arg])) as _)
assert_eq!( .collect::<Vec<_>>();
result_array.value(0),
"{\"status\":\"Done\",\"error\":\"OK\"}" let result = f.eval(FunctionContext::mock(), &args).await.unwrap();
);
} let expect: VectorRef = Arc::new(StringVector::from(vec![
ColumnarValue::Scalar(scalar) => { "{\"status\":\"Done\",\"error\":\"OK\"}",
assert_eq!( ]));
scalar, assert_eq!(expect, result);
datafusion_common::ScalarValue::Utf8(Some(
"{\"status\":\"Done\",\"error\":\"OK\"}".to_string()
))
);
}
}
} }
} }

View File

@@ -20,7 +20,7 @@ common-telemetry.workspace = true
common-time.workspace = true common-time.workspace = true
dashmap.workspace = true dashmap.workspace = true
datatypes.workspace = true datatypes.workspace = true
flatbuffers = "25.2" flatbuffers = "24"
hyper.workspace = true hyper.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
prost.workspace = true prost.workspace = true

Some files were not shown because too many files have changed in this diff Show More