mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-04 20:32:56 +00:00
Compare commits
141 Commits
flow/min_o
...
flow/faste
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67a60646b4 | ||
|
|
1c3bde7e4e | ||
|
|
e045a0dbdf | ||
|
|
2f765c8fd4 | ||
|
|
d99cd98c01 | ||
|
|
a858f55257 | ||
|
|
916967ea59 | ||
|
|
c58d8aa94a | ||
|
|
eeb061ca74 | ||
|
|
f7282fde28 | ||
|
|
a4bd11fb9c | ||
|
|
6dc9e8ddb4 | ||
|
|
af03e89139 | ||
|
|
e7a64b7dc0 | ||
|
|
29739b556e | ||
|
|
77e50d0e08 | ||
|
|
c2f1447345 | ||
|
|
30f7955d2b | ||
|
|
3508fddd74 | ||
|
|
351c741c70 | ||
|
|
bb43d604a4 | ||
|
|
9576bcb9ae | ||
|
|
dc17e6e517 | ||
|
|
563d25ee04 | ||
|
|
7d17782fd5 | ||
|
|
c5360601f5 | ||
|
|
9b5baa965c | ||
|
|
76a5145def | ||
|
|
7b2703760b | ||
|
|
81ea172ce4 | ||
|
|
f7c363f969 | ||
|
|
5f2daae087 | ||
|
|
b1b0d0136f | ||
|
|
599f289f59 | ||
|
|
385f12a62e | ||
|
|
6b90e2b6b4 | ||
|
|
a4f3e96e96 | ||
|
|
2b0f27da51 | ||
|
|
e0382eeb7c | ||
|
|
4aa6add8dc | ||
|
|
645988975e | ||
|
|
a203909de3 | ||
|
|
616e76941a | ||
|
|
bc42d35c2a | ||
|
|
524bdfff22 | ||
|
|
6bed0b6ba0 | ||
|
|
dec8c52b18 | ||
|
|
753a7e1a24 | ||
|
|
6684200fce | ||
|
|
5fcb97724f | ||
|
|
ff559b2688 | ||
|
|
8473a34fc9 | ||
|
|
df0ebf0378 | ||
|
|
4a665fd27b | ||
|
|
b4d6441716 | ||
|
|
bdd50a2263 | ||
|
|
f87b12b2aa | ||
|
|
07eec083b9 | ||
|
|
4737285275 | ||
|
|
55f5e09885 | ||
|
|
9ab36e9a6f | ||
|
|
4bb5d00a4b | ||
|
|
1d07864b29 | ||
|
|
9be75361a4 | ||
|
|
9c1df68a5f | ||
|
|
0209461155 | ||
|
|
e728cb33fb | ||
|
|
cde7e11983 | ||
|
|
944b4b3e49 | ||
|
|
7953b090c0 | ||
|
|
7aa9af5ba6 | ||
|
|
7a9444c85b | ||
|
|
bb12be3310 | ||
|
|
24019334ee | ||
|
|
116d5cf82b | ||
|
|
90a3894564 | ||
|
|
39d3e0651d | ||
|
|
a49edc6ca6 | ||
|
|
7cd6be41ce | ||
|
|
15616d0c43 | ||
|
|
b43e315c67 | ||
|
|
36ab1ceef7 | ||
|
|
3fb1b726c6 | ||
|
|
c423bb31fe | ||
|
|
e026f766d2 | ||
|
|
9d08f2532a | ||
|
|
e072726ea8 | ||
|
|
e78c3e1eaa | ||
|
|
89e3c8edab | ||
|
|
d4826b998d | ||
|
|
d9faa5c801 | ||
|
|
12c3a3205b | ||
|
|
5231505021 | ||
|
|
6ece560f8c | ||
|
|
2ab08a8f93 | ||
|
|
086ae9cdcd | ||
|
|
6da8e00243 | ||
|
|
4b04c402b6 | ||
|
|
a59b6c36d2 | ||
|
|
f6ce6fe385 | ||
|
|
4d4bfb7d8b | ||
|
|
6e1e8f19e6 | ||
|
|
49cb4da6d2 | ||
|
|
0d0236ddab | ||
|
|
f8edb53b30 | ||
|
|
438791b3e4 | ||
|
|
50e4c916e7 | ||
|
|
16e7f7b64b | ||
|
|
53c4fd478e | ||
|
|
ecbbd2fbdb | ||
|
|
3e3a12385c | ||
|
|
079daf5db9 | ||
|
|
56b9ab5279 | ||
|
|
be4e0d589e | ||
|
|
2a3445c72c | ||
|
|
9d997d593c | ||
|
|
10bf9b11f6 | ||
|
|
f4f8d65a39 | ||
|
|
b31990e881 | ||
|
|
6da633e70d | ||
|
|
9633e794c7 | ||
|
|
eaf1e1198f | ||
|
|
505bf25505 | ||
|
|
f1b29ece3c | ||
|
|
74df12e8c0 | ||
|
|
be6a5d2da8 | ||
|
|
7468a8ab2a | ||
|
|
5bb0466ff2 | ||
|
|
f6db419afd | ||
|
|
05b708ed2e | ||
|
|
f4c3950f57 | ||
|
|
88c4409df4 | ||
|
|
c10b8f8474 | ||
|
|
041b683a8d | ||
|
|
03bb6e4f28 | ||
|
|
09e5a6580f | ||
|
|
f9f905ae14 | ||
|
|
1d53dd26ae | ||
|
|
01796c9cc0 | ||
|
|
9469a8f8f2 | ||
|
|
2fabe346a1 |
@@ -12,3 +12,6 @@ fetch = true
|
|||||||
checkout = true
|
checkout = true
|
||||||
list_files = true
|
list_files = true
|
||||||
internal_use_git2 = false
|
internal_use_git2 = false
|
||||||
|
|
||||||
|
[env]
|
||||||
|
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ inputs:
|
|||||||
meta-replicas:
|
meta-replicas:
|
||||||
default: 2
|
default: 2
|
||||||
description: "Number of Metasrv replicas"
|
description: "Number of Metasrv replicas"
|
||||||
image-registry:
|
image-registry:
|
||||||
default: "docker.io"
|
default: "docker.io"
|
||||||
description: "Image registry"
|
description: "Image registry"
|
||||||
image-repository:
|
image-repository:
|
||||||
default: "greptime/greptimedb"
|
default: "greptime/greptimedb"
|
||||||
description: "Image repository"
|
description: "Image repository"
|
||||||
image-tag:
|
image-tag:
|
||||||
default: "latest"
|
default: "latest"
|
||||||
description: 'Image tag'
|
description: 'Image tag'
|
||||||
etcd-endpoints:
|
etcd-endpoints:
|
||||||
@@ -32,12 +32,12 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install GreptimeDB operator
|
- name: Install GreptimeDB operator
|
||||||
uses: nick-fields/retry@v3
|
uses: nick-fields/retry@v3
|
||||||
with:
|
with:
|
||||||
timeout_minutes: 3
|
timeout_minutes: 3
|
||||||
max_attempts: 3
|
max_attempts: 3
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
helm repo add greptime https://greptimeteam.github.io/helm-charts/
|
helm repo add greptime https://greptimeteam.github.io/helm-charts/
|
||||||
helm repo update
|
helm repo update
|
||||||
helm upgrade \
|
helm upgrade \
|
||||||
--install \
|
--install \
|
||||||
@@ -48,10 +48,10 @@ runs:
|
|||||||
--wait-for-jobs
|
--wait-for-jobs
|
||||||
- name: Install GreptimeDB cluster
|
- name: Install GreptimeDB cluster
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
helm upgrade \
|
helm upgrade \
|
||||||
--install my-greptimedb \
|
--install my-greptimedb \
|
||||||
--set meta.etcdEndpoints=${{ inputs.etcd-endpoints }} \
|
--set meta.backendStorage.etcd.endpoints=${{ inputs.etcd-endpoints }} \
|
||||||
--set meta.enableRegionFailover=${{ inputs.enable-region-failover }} \
|
--set meta.enableRegionFailover=${{ inputs.enable-region-failover }} \
|
||||||
--set image.registry=${{ inputs.image-registry }} \
|
--set image.registry=${{ inputs.image-registry }} \
|
||||||
--set image.repository=${{ inputs.image-repository }} \
|
--set image.repository=${{ inputs.image-repository }} \
|
||||||
@@ -72,7 +72,7 @@ runs:
|
|||||||
- name: Wait for GreptimeDB
|
- name: Wait for GreptimeDB
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
while true; do
|
while true; do
|
||||||
PHASE=$(kubectl -n my-greptimedb get gtc my-greptimedb -o jsonpath='{.status.clusterPhase}')
|
PHASE=$(kubectl -n my-greptimedb get gtc my-greptimedb -o jsonpath='{.status.clusterPhase}')
|
||||||
if [ "$PHASE" == "Running" ]; then
|
if [ "$PHASE" == "Running" ]; then
|
||||||
echo "Cluster is ready"
|
echo "Cluster is ready"
|
||||||
@@ -86,10 +86,10 @@ runs:
|
|||||||
- name: Print GreptimeDB info
|
- name: Print GreptimeDB info
|
||||||
if: always()
|
if: always()
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
kubectl get all --show-labels -n my-greptimedb
|
kubectl get all --show-labels -n my-greptimedb
|
||||||
- name: Describe Nodes
|
- name: Describe Nodes
|
||||||
if: always()
|
if: always()
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
kubectl describe nodes
|
kubectl describe nodes
|
||||||
|
|||||||
15
.github/labeler.yaml
vendored
Normal file
15
.github/labeler.yaml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
ci:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: .github/**
|
||||||
|
|
||||||
|
docker:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: docker/**
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: docs/**
|
||||||
|
|
||||||
|
dashboard:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: grafana/**
|
||||||
42
.github/scripts/check-version.sh
vendored
Executable file
42
.github/scripts/check-version.sh
vendored
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Get current version
|
||||||
|
CURRENT_VERSION=$1
|
||||||
|
if [ -z "$CURRENT_VERSION" ]; then
|
||||||
|
echo "Error: Failed to get current version"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the latest version from GitHub Releases
|
||||||
|
API_RESPONSE=$(curl -s "https://api.github.com/repos/GreptimeTeam/greptimedb/releases/latest")
|
||||||
|
|
||||||
|
if [ -z "$API_RESPONSE" ] || [ "$(echo "$API_RESPONSE" | jq -r '.message')" = "Not Found" ]; then
|
||||||
|
echo "Error: Failed to fetch latest version from GitHub"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the latest version
|
||||||
|
LATEST_VERSION=$(echo "$API_RESPONSE" | jq -r '.tag_name')
|
||||||
|
|
||||||
|
if [ -z "$LATEST_VERSION" ] || [ "$LATEST_VERSION" = "null" ]; then
|
||||||
|
echo "Error: No valid version found in GitHub releases"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Cleaned up version number format (removed possible 'v' prefix and -nightly suffix)
|
||||||
|
CLEAN_CURRENT=$(echo "$CURRENT_VERSION" | sed 's/^v//' | sed 's/-nightly-.*//')
|
||||||
|
CLEAN_LATEST=$(echo "$LATEST_VERSION" | sed 's/^v//' | sed 's/-nightly-.*//')
|
||||||
|
|
||||||
|
echo "Current version: $CLEAN_CURRENT"
|
||||||
|
echo "Latest release version: $CLEAN_LATEST"
|
||||||
|
|
||||||
|
# Use sort -V to compare versions
|
||||||
|
HIGHER_VERSION=$(printf "%s\n%s" "$CLEAN_CURRENT" "$CLEAN_LATEST" | sort -V | tail -n1)
|
||||||
|
|
||||||
|
if [ "$HIGHER_VERSION" = "$CLEAN_CURRENT" ]; then
|
||||||
|
echo "Current version ($CLEAN_CURRENT) is NEWER than or EQUAL to latest ($CLEAN_LATEST)"
|
||||||
|
echo "should-push-latest-tag=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "Current version ($CLEAN_CURRENT) is OLDER than latest ($CLEAN_LATEST)"
|
||||||
|
echo "should-push-latest-tag=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
4
.github/scripts/deploy-greptimedb.sh
vendored
4
.github/scripts/deploy-greptimedb.sh
vendored
@@ -68,7 +68,7 @@ function deploy_greptimedb_cluster() {
|
|||||||
|
|
||||||
helm install "$cluster_name" greptime/greptimedb-cluster \
|
helm install "$cluster_name" greptime/greptimedb-cluster \
|
||||||
--set image.tag="$GREPTIMEDB_IMAGE_TAG" \
|
--set image.tag="$GREPTIMEDB_IMAGE_TAG" \
|
||||||
--set meta.etcdEndpoints="etcd.$install_namespace:2379" \
|
--set meta.backendStorage.etcd.endpoints="etcd.$install_namespace:2379" \
|
||||||
-n "$install_namespace"
|
-n "$install_namespace"
|
||||||
|
|
||||||
# Wait for greptimedb cluster to be ready.
|
# Wait for greptimedb cluster to be ready.
|
||||||
@@ -103,7 +103,7 @@ function deploy_greptimedb_cluster_with_s3_storage() {
|
|||||||
|
|
||||||
helm install "$cluster_name" greptime/greptimedb-cluster -n "$install_namespace" \
|
helm install "$cluster_name" greptime/greptimedb-cluster -n "$install_namespace" \
|
||||||
--set image.tag="$GREPTIMEDB_IMAGE_TAG" \
|
--set image.tag="$GREPTIMEDB_IMAGE_TAG" \
|
||||||
--set meta.etcdEndpoints="etcd.$install_namespace:2379" \
|
--set meta.backendStorage.etcd.endpoints="etcd.$install_namespace:2379" \
|
||||||
--set storage.s3.bucket="$AWS_CI_TEST_BUCKET" \
|
--set storage.s3.bucket="$AWS_CI_TEST_BUCKET" \
|
||||||
--set storage.s3.region="$AWS_REGION" \
|
--set storage.s3.region="$AWS_REGION" \
|
||||||
--set storage.s3.root="$DATA_ROOT" \
|
--set storage.s3.root="$DATA_ROOT" \
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ update_helm_charts_version() {
|
|||||||
|
|
||||||
# Commit the changes.
|
# Commit the changes.
|
||||||
git add .
|
git add .
|
||||||
git commit -m "chore: Update GreptimeDB version to ${VERSION}"
|
git commit -s -m "chore: Update GreptimeDB version to ${VERSION}"
|
||||||
git push origin $BRANCH_NAME
|
git push origin $BRANCH_NAME
|
||||||
|
|
||||||
# Create a Pull Request.
|
# Create a Pull Request.
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ update_homebrew_greptime_version() {
|
|||||||
|
|
||||||
# Commit the changes.
|
# Commit the changes.
|
||||||
git add .
|
git add .
|
||||||
git commit -m "chore: Update GreptimeDB version to ${VERSION}"
|
git commit -s -m "chore: Update GreptimeDB version to ${VERSION}"
|
||||||
git push origin $BRANCH_NAME
|
git push origin $BRANCH_NAME
|
||||||
|
|
||||||
# Create a Pull Request.
|
# Create a Pull Request.
|
||||||
|
|||||||
42
.github/workflows/pr-labeling.yaml
vendored
Normal file
42
.github/workflows/pr-labeling.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: 'PR Labeling'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout sources
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
configuration-path: ".github/labeler.yaml"
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
|
||||||
|
size-label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: pascalgn/size-label-action@v0.5.5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
with:
|
||||||
|
sizes: >
|
||||||
|
{
|
||||||
|
"0": "XS",
|
||||||
|
"100": "S",
|
||||||
|
"300": "M",
|
||||||
|
"1000": "L",
|
||||||
|
"1500": "XL",
|
||||||
|
"2000": "XXL"
|
||||||
|
}
|
||||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -110,6 +110,8 @@ 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 }}
|
||||||
|
|
||||||
|
should-push-latest-tag: ${{ steps.check-version.outputs.should-push-latest-tag }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -135,6 +137,11 @@ jobs:
|
|||||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||||
NIGHTLY_RELEASE_PREFIX: ${{ env.NIGHTLY_RELEASE_PREFIX }}
|
NIGHTLY_RELEASE_PREFIX: ${{ env.NIGHTLY_RELEASE_PREFIX }}
|
||||||
|
|
||||||
|
- name: Check version
|
||||||
|
id: check-version
|
||||||
|
run: |
|
||||||
|
./.github/scripts/check-version.sh "${{ steps.create-version.outputs.version }}"
|
||||||
|
|
||||||
- name: Allocate linux-amd64 runner
|
- name: Allocate linux-amd64 runner
|
||||||
if: ${{ inputs.build_linux_amd64_artifacts || github.event_name == 'push' || github.event_name == 'schedule' }}
|
if: ${{ inputs.build_linux_amd64_artifacts || github.event_name == 'push' || github.event_name == 'schedule' }}
|
||||||
uses: ./.github/actions/start-runner
|
uses: ./.github/actions/start-runner
|
||||||
@@ -314,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: ${{ 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
|
||||||
@@ -332,7 +339,7 @@ jobs:
|
|||||||
build-windows-artifacts,
|
build-windows-artifacts,
|
||||||
release-images-to-dockerhub,
|
release-images-to-dockerhub,
|
||||||
]
|
]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest-16-cores
|
||||||
# When we push to ACR, it's easy to fail due to some unknown network issues.
|
# When we push to ACR, it's easy to fail due to some unknown network issues.
|
||||||
# However, we don't want to fail the whole workflow because of this.
|
# However, we don't want to fail the whole workflow because of this.
|
||||||
# The ACR have daily sync with DockerHub, so don't worry about the image not being updated.
|
# The ACR have daily sync with DockerHub, so don't worry about the image not being updated.
|
||||||
@@ -361,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: ${{ 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
|
||||||
|
|||||||
10
.github/workflows/semantic-pull-request.yml
vendored
10
.github/workflows/semantic-pull-request.yml
vendored
@@ -11,17 +11,17 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
|
||||||
pull-requests: write # Add permissions to modify PRs
|
|
||||||
issues: write
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
- uses: ./.github/actions/setup-cyborg
|
- uses: ./.github/actions/setup-cyborg
|
||||||
- name: Check Pull Request
|
- name: Check Pull Request
|
||||||
working-directory: cyborg
|
working-directory: cyborg
|
||||||
|
|||||||
419
Cargo.lock
generated
419
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
@@ -30,6 +30,7 @@ members = [
|
|||||||
"src/common/recordbatch",
|
"src/common/recordbatch",
|
||||||
"src/common/runtime",
|
"src/common/runtime",
|
||||||
"src/common/session",
|
"src/common/session",
|
||||||
|
"src/common/sql",
|
||||||
"src/common/stat",
|
"src/common/stat",
|
||||||
"src/common/substrait",
|
"src/common/substrait",
|
||||||
"src/common/telemetry",
|
"src/common/telemetry",
|
||||||
@@ -49,6 +50,7 @@ members = [
|
|||||||
"src/meta-client",
|
"src/meta-client",
|
||||||
"src/meta-srv",
|
"src/meta-srv",
|
||||||
"src/metric-engine",
|
"src/metric-engine",
|
||||||
|
"src/mito-codec",
|
||||||
"src/mito2",
|
"src/mito2",
|
||||||
"src/object-store",
|
"src/object-store",
|
||||||
"src/operator",
|
"src/operator",
|
||||||
@@ -70,11 +72,13 @@ members = [
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.15.0"
|
version = "0.16.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
|
|
||||||
[workspace.lints]
|
[workspace.lints]
|
||||||
|
clippy.print_stdout = "warn"
|
||||||
|
clippy.print_stderr = "warn"
|
||||||
clippy.dbg_macro = "warn"
|
clippy.dbg_macro = "warn"
|
||||||
clippy.implicit_clone = "warn"
|
clippy.implicit_clone = "warn"
|
||||||
clippy.result_large_err = "allow"
|
clippy.result_large_err = "allow"
|
||||||
@@ -116,24 +120,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/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-functions = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-functions = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-optimizer = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-functions-aggregate-common = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-physical-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-optimizer = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-physical-plan = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-physical-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-sql = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-physical-plan = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
datafusion-substrait = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "e104c7cf62b11dd5fe41461b82514978234326b4" }
|
datafusion-sql = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
|
datafusion-substrait = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "12c0381babd52c681043957e9d6ee083a03f7646" }
|
||||||
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"
|
||||||
etcd-client = "0.14"
|
etcd-client = "0.14"
|
||||||
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 = "454c52634c3bac27de10bf0d85d5533eed1cf03f" }
|
greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "ceb1af4fa9309ce65bda0367db7b384df2bb4d4f" }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
http = "1"
|
http = "1"
|
||||||
humantime = "2.1"
|
humantime = "2.1"
|
||||||
@@ -166,9 +172,7 @@ parquet = { version = "54.2", default-features = false, features = ["arrow", "as
|
|||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
pin-project = "1.0"
|
pin-project = "1.0"
|
||||||
prometheus = { version = "0.13.3", features = ["process"] }
|
prometheus = { version = "0.13.3", features = ["process"] }
|
||||||
promql-parser = { git = "https://github.com/GreptimeTeam/promql-parser.git", rev = "0410e8b459dda7cb222ce9596f8bf3971bd07bd2", features = [
|
promql-parser = { version = "0.6", features = ["ser"] }
|
||||||
"ser",
|
|
||||||
] }
|
|
||||||
prost = { version = "0.13", features = ["no-recursion-limit"] }
|
prost = { version = "0.13", features = ["no-recursion-limit"] }
|
||||||
raft-engine = { version = "0.4.1", default-features = false }
|
raft-engine = { version = "0.4.1", default-features = false }
|
||||||
rand = "0.9"
|
rand = "0.9"
|
||||||
@@ -257,6 +261,7 @@ common-query = { path = "src/common/query" }
|
|||||||
common-recordbatch = { path = "src/common/recordbatch" }
|
common-recordbatch = { path = "src/common/recordbatch" }
|
||||||
common-runtime = { path = "src/common/runtime" }
|
common-runtime = { path = "src/common/runtime" }
|
||||||
common-session = { path = "src/common/session" }
|
common-session = { path = "src/common/session" }
|
||||||
|
common-sql = { path = "src/common/sql" }
|
||||||
common-telemetry = { path = "src/common/telemetry" }
|
common-telemetry = { path = "src/common/telemetry" }
|
||||||
common-test-util = { path = "src/common/test-util" }
|
common-test-util = { path = "src/common/test-util" }
|
||||||
common-time = { path = "src/common/time" }
|
common-time = { path = "src/common/time" }
|
||||||
@@ -274,6 +279,7 @@ log-store = { path = "src/log-store" }
|
|||||||
meta-client = { path = "src/meta-client" }
|
meta-client = { path = "src/meta-client" }
|
||||||
meta-srv = { path = "src/meta-srv" }
|
meta-srv = { path = "src/meta-srv" }
|
||||||
metric-engine = { path = "src/metric-engine" }
|
metric-engine = { path = "src/metric-engine" }
|
||||||
|
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" }
|
||||||
|
|||||||
@@ -75,9 +75,9 @@
|
|||||||
| --------- | ----------- |
|
| --------- | ----------- |
|
||||||
| [Unified Observability Data](https://docs.greptime.com/user-guide/concepts/why-greptimedb) | Store metrics, logs, and traces as timestamped, contextual wide events. Query via [SQL](https://docs.greptime.com/user-guide/query-data/sql), [PromQL](https://docs.greptime.com/user-guide/query-data/promql), and [streaming](https://docs.greptime.com/user-guide/flow-computation/overview). |
|
| [Unified Observability Data](https://docs.greptime.com/user-guide/concepts/why-greptimedb) | Store metrics, logs, and traces as timestamped, contextual wide events. Query via [SQL](https://docs.greptime.com/user-guide/query-data/sql), [PromQL](https://docs.greptime.com/user-guide/query-data/promql), and [streaming](https://docs.greptime.com/user-guide/flow-computation/overview). |
|
||||||
| [High Performance & Cost Effective](https://docs.greptime.com/user-guide/manage-data/data-index) | Written in Rust, with a distributed query engine, [rich indexing](https://docs.greptime.com/user-guide/manage-data/data-index), and optimized columnar storage, delivering sub-second responses at PB scale. |
|
| [High Performance & Cost Effective](https://docs.greptime.com/user-guide/manage-data/data-index) | Written in Rust, with a distributed query engine, [rich indexing](https://docs.greptime.com/user-guide/manage-data/data-index), and optimized columnar storage, delivering sub-second responses at PB scale. |
|
||||||
| [Cloud-Native Architecture](https://docs.greptime.com/user-guide/concepts/architecture) | Designed for [Kubernetes](https://docs.greptime.com/user-guide/deployments/deploy-on-kubernetes/greptimedb-operator-management), with compute/storage separation, native object storage (AWS S3, Azure Blob, etc.) and seamless cross-cloud access. |
|
| [Cloud-Native Architecture](https://docs.greptime.com/user-guide/concepts/architecture) | Designed for [Kubernetes](https://docs.greptime.com/user-guide/deployments-administration/deploy-on-kubernetes/greptimedb-operator-management), with compute/storage separation, native object storage (AWS S3, Azure Blob, etc.) and seamless cross-cloud access. |
|
||||||
| [Developer-Friendly](https://docs.greptime.com/user-guide/protocols/overview) | Access via SQL/PromQL interfaces, REST API, MySQL/PostgreSQL protocols, and popular ingestion [protocols](https://docs.greptime.com/user-guide/protocols/overview). |
|
| [Developer-Friendly](https://docs.greptime.com/user-guide/protocols/overview) | Access via SQL/PromQL interfaces, REST API, MySQL/PostgreSQL protocols, and popular ingestion [protocols](https://docs.greptime.com/user-guide/protocols/overview). |
|
||||||
| [Flexible Deployment](https://docs.greptime.com/user-guide/deployments/overview) | Deploy anywhere: edge (including ARM/[Android](https://docs.greptime.com/user-guide/deployments/run-on-android)) or cloud, with unified APIs and efficient data sync. |
|
| [Flexible Deployment](https://docs.greptime.com/user-guide/deployments-administration/overview) | Deploy anywhere: edge (including ARM/[Android](https://docs.greptime.com/user-guide/deployments-administration/run-on-android)) or cloud, with unified APIs and efficient data sync. |
|
||||||
|
|
||||||
Learn more in [Why GreptimeDB](https://docs.greptime.com/user-guide/concepts/why-greptimedb) and [Observability 2.0 and the Database for It](https://greptime.com/blogs/2025-04-25-greptimedb-observability2-new-database).
|
Learn more in [Why GreptimeDB](https://docs.greptime.com/user-guide/concepts/why-greptimedb) and [Observability 2.0 and the Database for It](https://greptime.com/blogs/2025-04-25-greptimedb-observability2-new-database).
|
||||||
|
|
||||||
@@ -189,7 +189,8 @@ We invite you to engage and contribute!
|
|||||||
- [Official Website](https://greptime.com/)
|
- [Official Website](https://greptime.com/)
|
||||||
- [Blog](https://greptime.com/blogs/)
|
- [Blog](https://greptime.com/blogs/)
|
||||||
- [LinkedIn](https://www.linkedin.com/company/greptime/)
|
- [LinkedIn](https://www.linkedin.com/company/greptime/)
|
||||||
- [Twitter](https://twitter.com/greptime)
|
- [X (Twitter)](https://X.com/greptime)
|
||||||
|
- [YouTube](https://www.youtube.com/@greptime)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,7 @@
|
|||||||
| `storage.http_client.connect_timeout` | String | `30s` | The timeout for only the connect phase of a http client. |
|
| `storage.http_client.connect_timeout` | String | `30s` | The timeout for only the connect phase of a http client. |
|
||||||
| `storage.http_client.timeout` | String | `30s` | The total request timeout, applied from when the request starts connecting until the response body has finished.<br/>Also considered a total deadline. |
|
| `storage.http_client.timeout` | String | `30s` | The total request timeout, applied from when the request starts connecting until the response body has finished.<br/>Also considered a total deadline. |
|
||||||
| `storage.http_client.pool_idle_timeout` | String | `90s` | The timeout for idle sockets being kept-alive. |
|
| `storage.http_client.pool_idle_timeout` | String | `90s` | The timeout for idle sockets being kept-alive. |
|
||||||
|
| `storage.http_client.skip_ssl_validation` | Bool | `false` | To skip the ssl verification<br/>**Security Notice**: Setting `skip_ssl_validation = true` disables certificate verification, making connections vulnerable to man-in-the-middle attacks. Only use this in development or trusted private networks. |
|
||||||
| `[[region_engine]]` | -- | -- | The region engine options. You can configure multiple region engines. |
|
| `[[region_engine]]` | -- | -- | The region engine options. You can configure multiple region engines. |
|
||||||
| `region_engine.mito` | -- | -- | The Mito engine options. |
|
| `region_engine.mito` | -- | -- | The Mito engine options. |
|
||||||
| `region_engine.mito.num_workers` | Integer | `8` | Number of region workers. |
|
| `region_engine.mito.num_workers` | Integer | `8` | Number of region workers. |
|
||||||
@@ -184,10 +185,11 @@
|
|||||||
| `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:4317` | 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.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` | -- | -- | 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. |
|
||||||
@@ -232,6 +234,7 @@
|
|||||||
| `grpc.bind_addr` | String | `127.0.0.1:4001` | The address to bind the gRPC server. |
|
| `grpc.bind_addr` | String | `127.0.0.1:4001` | The address to bind the gRPC server. |
|
||||||
| `grpc.server_addr` | String | `127.0.0.1:4001` | 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`. |
|
| `grpc.server_addr` | String | `127.0.0.1:4001` | 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`. |
|
||||||
| `grpc.runtime_size` | Integer | `8` | The number of server worker threads. |
|
| `grpc.runtime_size` | Integer | `8` | The number of server worker threads. |
|
||||||
|
| `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` |
|
||||||
| `grpc.tls` | -- | -- | gRPC server TLS options, see `mysql.tls` section. |
|
| `grpc.tls` | -- | -- | gRPC server TLS options, see `mysql.tls` section. |
|
||||||
| `grpc.tls.mode` | String | `disable` | TLS mode. |
|
| `grpc.tls.mode` | String | `disable` | TLS mode. |
|
||||||
| `grpc.tls.cert_path` | String | Unset | Certificate file path. |
|
| `grpc.tls.cert_path` | String | Unset | Certificate file path. |
|
||||||
@@ -286,10 +289,11 @@
|
|||||||
| `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:4317` | 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.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` | -- | -- | 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. |
|
||||||
@@ -321,6 +325,7 @@
|
|||||||
| `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. |
|
||||||
| `enable_region_failover` | Bool | `false` | Whether to enable region failover.<br/>This feature is only available on GreptimeDB running on cluster mode and<br/>- Using Remote WAL<br/>- Using shared storage (e.g., s3). |
|
| `enable_region_failover` | Bool | `false` | Whether to enable region failover.<br/>This feature is only available on GreptimeDB running on cluster mode and<br/>- Using Remote WAL<br/>- Using shared storage (e.g., s3). |
|
||||||
|
| `region_failure_detector_initialization_delay` | String | `10m` | The delay before starting region failure detection.<br/>This delay helps prevent Metasrv from triggering unnecessary region failovers before all Datanodes are fully started.<br/>Especially useful when the cluster is not deployed with GreptimeDB Operator and maintenance mode is not enabled. |
|
||||||
| `allow_region_failover_on_local_wal` | Bool | `false` | Whether to allow region failover on local WAL.<br/>**This option is not recommended to be set to true, because it may lead to data loss during failover.** |
|
| `allow_region_failover_on_local_wal` | Bool | `false` | Whether to allow region failover on local WAL.<br/>**This option is not recommended to be set to true, because it may lead to data loss during failover.** |
|
||||||
| `node_max_idle_time` | String | `24hours` | Max allowed idle time before removing node info from metasrv memory. |
|
| `node_max_idle_time` | String | `24hours` | Max allowed idle time before removing node info from metasrv memory. |
|
||||||
| `enable_telemetry` | Bool | `true` | Whether to enable greptimedb telemetry. Enabled by default. |
|
| `enable_telemetry` | Bool | `true` | Whether to enable greptimedb telemetry. Enabled by default. |
|
||||||
@@ -368,10 +373,11 @@
|
|||||||
| `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:4317` | 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.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` | -- | -- | 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. |
|
||||||
@@ -404,6 +410,7 @@
|
|||||||
| `grpc.runtime_size` | Integer | `8` | The number of server worker threads. |
|
| `grpc.runtime_size` | Integer | `8` | The number of server worker threads. |
|
||||||
| `grpc.max_recv_message_size` | String | `512MB` | The maximum receive message size for gRPC server. |
|
| `grpc.max_recv_message_size` | String | `512MB` | The maximum receive message size for gRPC server. |
|
||||||
| `grpc.max_send_message_size` | String | `512MB` | The maximum send message size for gRPC server. |
|
| `grpc.max_send_message_size` | String | `512MB` | The maximum send message size for gRPC server. |
|
||||||
|
| `grpc.flight_compression` | String | `arrow_ipc` | Compression mode for datanode 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` |
|
||||||
| `grpc.tls` | -- | -- | gRPC server TLS options, see `mysql.tls` section. |
|
| `grpc.tls` | -- | -- | gRPC server TLS options, see `mysql.tls` section. |
|
||||||
| `grpc.tls.mode` | String | `disable` | TLS mode. |
|
| `grpc.tls.mode` | String | `disable` | TLS mode. |
|
||||||
| `grpc.tls.cert_path` | String | Unset | Certificate file path. |
|
| `grpc.tls.cert_path` | String | Unset | Certificate file path. |
|
||||||
@@ -429,8 +436,8 @@
|
|||||||
| `wal.provider` | String | `raft_engine` | The provider of the WAL.<br/>- `raft_engine`: the wal is stored in the local file system by raft-engine.<br/>- `kafka`: it's remote wal that data is stored in Kafka. |
|
| `wal.provider` | String | `raft_engine` | The provider of the WAL.<br/>- `raft_engine`: the wal is stored in the local file system by raft-engine.<br/>- `kafka`: it's remote wal that data is stored in Kafka. |
|
||||||
| `wal.dir` | String | Unset | The directory to store the WAL files.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.dir` | String | Unset | The directory to store the WAL files.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
| `wal.file_size` | String | `128MB` | The size of the WAL segment file.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.file_size` | String | `128MB` | The size of the WAL segment file.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
| `wal.purge_threshold` | String | `1GB` | The threshold of the WAL size to trigger a flush.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.purge_threshold` | String | `1GB` | The threshold of the WAL size to trigger a purge.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
| `wal.purge_interval` | String | `1m` | The interval to trigger a flush.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.purge_interval` | String | `1m` | The interval to trigger a purge.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
| `wal.read_batch_size` | Integer | `128` | The read batch size.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.read_batch_size` | Integer | `128` | The read batch size.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
| `wal.sync_write` | Bool | `false` | Whether to use sync write.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.sync_write` | Bool | `false` | Whether to use sync write.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
| `wal.enable_log_recycle` | Bool | `true` | Whether to reuse logically truncated log files.<br/>**It's only used when the provider is `raft_engine`**. |
|
| `wal.enable_log_recycle` | Bool | `true` | Whether to reuse logically truncated log files.<br/>**It's only used when the provider is `raft_engine`**. |
|
||||||
@@ -469,6 +476,7 @@
|
|||||||
| `storage.http_client.connect_timeout` | String | `30s` | The timeout for only the connect phase of a http client. |
|
| `storage.http_client.connect_timeout` | String | `30s` | The timeout for only the connect phase of a http client. |
|
||||||
| `storage.http_client.timeout` | String | `30s` | The total request timeout, applied from when the request starts connecting until the response body has finished.<br/>Also considered a total deadline. |
|
| `storage.http_client.timeout` | String | `30s` | The total request timeout, applied from when the request starts connecting until the response body has finished.<br/>Also considered a total deadline. |
|
||||||
| `storage.http_client.pool_idle_timeout` | String | `90s` | The timeout for idle sockets being kept-alive. |
|
| `storage.http_client.pool_idle_timeout` | String | `90s` | The timeout for idle sockets being kept-alive. |
|
||||||
|
| `storage.http_client.skip_ssl_validation` | Bool | `false` | To skip the ssl verification<br/>**Security Notice**: Setting `skip_ssl_validation = true` disables certificate verification, making connections vulnerable to man-in-the-middle attacks. Only use this in development or trusted private networks. |
|
||||||
| `[[region_engine]]` | -- | -- | The region engine options. You can configure multiple region engines. |
|
| `[[region_engine]]` | -- | -- | The region engine options. You can configure multiple region engines. |
|
||||||
| `region_engine.mito` | -- | -- | The Mito engine options. |
|
| `region_engine.mito` | -- | -- | The Mito engine options. |
|
||||||
| `region_engine.mito.num_workers` | Integer | `8` | Number of region workers. |
|
| `region_engine.mito.num_workers` | Integer | `8` | Number of region workers. |
|
||||||
@@ -530,10 +538,11 @@
|
|||||||
| `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:4317` | 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.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` | -- | -- | 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. |
|
||||||
@@ -580,11 +589,14 @@
|
|||||||
| `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:4317` | 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.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` | -- | -- | 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. |
|
||||||
|
| `query` | -- | -- | -- |
|
||||||
|
| `query.parallelism` | Integer | `1` | Parallelism of the query engine for query sent by flownode.<br/>Default to 1, so it won't use too much cpu or memory |
|
||||||
|
|||||||
@@ -44,6 +44,13 @@ runtime_size = 8
|
|||||||
max_recv_message_size = "512MB"
|
max_recv_message_size = "512MB"
|
||||||
## The maximum send message size for gRPC server.
|
## The maximum send message size for gRPC server.
|
||||||
max_send_message_size = "512MB"
|
max_send_message_size = "512MB"
|
||||||
|
## Compression mode for datanode 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"
|
||||||
|
|
||||||
## gRPC server TLS options, see `mysql.tls` section.
|
## gRPC server TLS options, see `mysql.tls` section.
|
||||||
[grpc.tls]
|
[grpc.tls]
|
||||||
@@ -122,11 +129,11 @@ dir = "./greptimedb_data/wal"
|
|||||||
## **It's only used when the provider is `raft_engine`**.
|
## **It's only used when the provider is `raft_engine`**.
|
||||||
file_size = "128MB"
|
file_size = "128MB"
|
||||||
|
|
||||||
## The threshold of the WAL size to trigger a flush.
|
## The threshold of the WAL size to trigger a purge.
|
||||||
## **It's only used when the provider is `raft_engine`**.
|
## **It's only used when the provider is `raft_engine`**.
|
||||||
purge_threshold = "1GB"
|
purge_threshold = "1GB"
|
||||||
|
|
||||||
## The interval to trigger a flush.
|
## The interval to trigger a purge.
|
||||||
## **It's only used when the provider is `raft_engine`**.
|
## **It's only used when the provider is `raft_engine`**.
|
||||||
purge_interval = "1m"
|
purge_interval = "1m"
|
||||||
|
|
||||||
@@ -360,6 +367,10 @@ timeout = "30s"
|
|||||||
## The timeout for idle sockets being kept-alive.
|
## The timeout for idle sockets being kept-alive.
|
||||||
pool_idle_timeout = "90s"
|
pool_idle_timeout = "90s"
|
||||||
|
|
||||||
|
## To skip the ssl verification
|
||||||
|
## **Security Notice**: Setting `skip_ssl_validation = true` disables certificate verification, making connections vulnerable to man-in-the-middle attacks. Only use this in development or trusted private networks.
|
||||||
|
skip_ssl_validation = false
|
||||||
|
|
||||||
# Custom storage options
|
# Custom storage options
|
||||||
# [[storage.providers]]
|
# [[storage.providers]]
|
||||||
# name = "S3"
|
# name = "S3"
|
||||||
@@ -618,7 +629,7 @@ level = "info"
|
|||||||
enable_otlp_tracing = false
|
enable_otlp_tracing = false
|
||||||
|
|
||||||
## The OTLP tracing endpoint.
|
## The OTLP tracing endpoint.
|
||||||
otlp_endpoint = "http://localhost:4317"
|
otlp_endpoint = "http://localhost:4318"
|
||||||
|
|
||||||
## Whether to append logs to stdout.
|
## Whether to append logs to stdout.
|
||||||
append_stdout = true
|
append_stdout = true
|
||||||
@@ -629,6 +640,9 @@ log_format = "text"
|
|||||||
## The maximum amount of log files.
|
## The maximum amount of log files.
|
||||||
max_log_files = 720
|
max_log_files = 720
|
||||||
|
|
||||||
|
## The OTLP tracing export protocol. Can be `grpc`/`http`.
|
||||||
|
otlp_export_protocol = "http"
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ level = "info"
|
|||||||
enable_otlp_tracing = false
|
enable_otlp_tracing = false
|
||||||
|
|
||||||
## The OTLP tracing endpoint.
|
## The OTLP tracing endpoint.
|
||||||
otlp_endpoint = "http://localhost:4317"
|
otlp_endpoint = "http://localhost:4318"
|
||||||
|
|
||||||
## Whether to append logs to stdout.
|
## Whether to append logs to stdout.
|
||||||
append_stdout = true
|
append_stdout = true
|
||||||
@@ -94,6 +94,9 @@ log_format = "text"
|
|||||||
## The maximum amount of log files.
|
## The maximum amount of log files.
|
||||||
max_log_files = 720
|
max_log_files = 720
|
||||||
|
|
||||||
|
## The OTLP tracing export protocol. Can be `grpc`/`http`.
|
||||||
|
otlp_export_protocol = "http"
|
||||||
|
|
||||||
## 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
|
||||||
@@ -105,3 +108,8 @@ default_ratio = 1.0
|
|||||||
## The tokio console address.
|
## The tokio console address.
|
||||||
## @toml2docs:none-default
|
## @toml2docs:none-default
|
||||||
#+ tokio_console_addr = "127.0.0.1"
|
#+ tokio_console_addr = "127.0.0.1"
|
||||||
|
|
||||||
|
[query]
|
||||||
|
## Parallelism of the query engine for query sent by flownode.
|
||||||
|
## Default to 1, so it won't use too much cpu or memory
|
||||||
|
parallelism = 1
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ bind_addr = "127.0.0.1:4001"
|
|||||||
server_addr = "127.0.0.1:4001"
|
server_addr = "127.0.0.1:4001"
|
||||||
## The number of server worker threads.
|
## The number of server worker threads.
|
||||||
runtime_size = 8
|
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"
|
||||||
|
|
||||||
## gRPC server TLS options, see `mysql.tls` section.
|
## gRPC server TLS options, see `mysql.tls` section.
|
||||||
[grpc.tls]
|
[grpc.tls]
|
||||||
@@ -211,7 +218,7 @@ level = "info"
|
|||||||
enable_otlp_tracing = false
|
enable_otlp_tracing = false
|
||||||
|
|
||||||
## The OTLP tracing endpoint.
|
## The OTLP tracing endpoint.
|
||||||
otlp_endpoint = "http://localhost:4317"
|
otlp_endpoint = "http://localhost:4318"
|
||||||
|
|
||||||
## Whether to append logs to stdout.
|
## Whether to append logs to stdout.
|
||||||
append_stdout = true
|
append_stdout = true
|
||||||
@@ -222,6 +229,9 @@ log_format = "text"
|
|||||||
## The maximum amount of log files.
|
## The maximum amount of log files.
|
||||||
max_log_files = 720
|
max_log_files = 720
|
||||||
|
|
||||||
|
## The OTLP tracing export protocol. Can be `grpc`/`http`.
|
||||||
|
otlp_export_protocol = "http"
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ use_memory_store = false
|
|||||||
## - Using shared storage (e.g., s3).
|
## - Using shared storage (e.g., s3).
|
||||||
enable_region_failover = false
|
enable_region_failover = false
|
||||||
|
|
||||||
|
## The delay before starting region failure detection.
|
||||||
|
## This delay helps prevent Metasrv from triggering unnecessary region failovers before all Datanodes are fully started.
|
||||||
|
## Especially useful when the cluster is not deployed with GreptimeDB Operator and maintenance mode is not enabled.
|
||||||
|
region_failure_detector_initialization_delay = '10m'
|
||||||
|
|
||||||
## Whether to allow region failover on local WAL.
|
## Whether to allow region failover on local WAL.
|
||||||
## **This option is not recommended to be set to true, because it may lead to data loss during failover.**
|
## **This option is not recommended to be set to true, because it may lead to data loss during failover.**
|
||||||
allow_region_failover_on_local_wal = false
|
allow_region_failover_on_local_wal = false
|
||||||
@@ -220,7 +225,7 @@ level = "info"
|
|||||||
enable_otlp_tracing = false
|
enable_otlp_tracing = false
|
||||||
|
|
||||||
## The OTLP tracing endpoint.
|
## The OTLP tracing endpoint.
|
||||||
otlp_endpoint = "http://localhost:4317"
|
otlp_endpoint = "http://localhost:4318"
|
||||||
|
|
||||||
## Whether to append logs to stdout.
|
## Whether to append logs to stdout.
|
||||||
append_stdout = true
|
append_stdout = true
|
||||||
@@ -231,6 +236,9 @@ log_format = "text"
|
|||||||
## The maximum amount of log files.
|
## The maximum amount of log files.
|
||||||
max_log_files = 720
|
max_log_files = 720
|
||||||
|
|
||||||
|
## The OTLP tracing export protocol. Can be `grpc`/`http`.
|
||||||
|
otlp_export_protocol = "http"
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -458,6 +458,10 @@ timeout = "30s"
|
|||||||
## The timeout for idle sockets being kept-alive.
|
## The timeout for idle sockets being kept-alive.
|
||||||
pool_idle_timeout = "90s"
|
pool_idle_timeout = "90s"
|
||||||
|
|
||||||
|
## To skip the ssl verification
|
||||||
|
## **Security Notice**: Setting `skip_ssl_validation = true` disables certificate verification, making connections vulnerable to man-in-the-middle attacks. Only use this in development or trusted private networks.
|
||||||
|
skip_ssl_validation = false
|
||||||
|
|
||||||
# Custom storage options
|
# Custom storage options
|
||||||
# [[storage.providers]]
|
# [[storage.providers]]
|
||||||
# name = "S3"
|
# name = "S3"
|
||||||
@@ -716,7 +720,7 @@ level = "info"
|
|||||||
enable_otlp_tracing = false
|
enable_otlp_tracing = false
|
||||||
|
|
||||||
## The OTLP tracing endpoint.
|
## The OTLP tracing endpoint.
|
||||||
otlp_endpoint = "http://localhost:4317"
|
otlp_endpoint = "http://localhost:4318"
|
||||||
|
|
||||||
## Whether to append logs to stdout.
|
## Whether to append logs to stdout.
|
||||||
append_stdout = true
|
append_stdout = true
|
||||||
@@ -727,6 +731,9 @@ log_format = "text"
|
|||||||
## The maximum amount of log files.
|
## The maximum amount of log files.
|
||||||
max_log_files = 720
|
max_log_files = 720
|
||||||
|
|
||||||
|
## The OTLP tracing export protocol. Can be `grpc`/`http`.
|
||||||
|
otlp_export_protocol = "http"
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -55,12 +55,25 @@ async function main() {
|
|||||||
await client.rest.issues.addLabels({
|
await client.rest.issues.addLabels({
|
||||||
owner, repo, issue_number: number, labels: [labelDocsRequired],
|
owner, repo, issue_number: number, labels: [labelDocsRequired],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Get available assignees for the docs repo
|
||||||
|
const assigneesResponse = await docsClient.rest.issues.listAssignees({
|
||||||
|
owner: 'GreptimeTeam',
|
||||||
|
repo: 'docs',
|
||||||
|
})
|
||||||
|
const validAssignees = assigneesResponse.data.map(assignee => assignee.login)
|
||||||
|
core.info(`Available assignees: ${validAssignees.join(', ')}`)
|
||||||
|
|
||||||
|
// Check if the actor is a valid assignee, otherwise fallback to fengjiachun
|
||||||
|
const assignee = validAssignees.includes(actor) ? actor : 'fengjiachun'
|
||||||
|
core.info(`Assigning issue to: ${assignee}`)
|
||||||
|
|
||||||
await docsClient.rest.issues.create({
|
await docsClient.rest.issues.create({
|
||||||
owner: 'GreptimeTeam',
|
owner: 'GreptimeTeam',
|
||||||
repo: 'docs',
|
repo: 'docs',
|
||||||
title: `Update docs for ${title}`,
|
title: `Update docs for ${title}`,
|
||||||
body: `A document change request is generated from ${html_url}`,
|
body: `A document change request is generated from ${html_url}`,
|
||||||
assignee: actor,
|
assignee: assignee,
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
core.info(`Created issue ${res.data}`)
|
core.info(`Created issue ${res.data}`)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ Please refer to [SQL query](./query.sql) for GreptimeDB and Clickhouse, and [que
|
|||||||
|
|
||||||
## Addition
|
## Addition
|
||||||
- You can tune GreptimeDB's configuration to get better performance.
|
- You can tune GreptimeDB's configuration to get better performance.
|
||||||
- You can setup GreptimeDB to use S3 as storage, see [here](https://docs.greptime.com/user-guide/deployments/configuration#storage-options).
|
- You can setup GreptimeDB to use S3 as storage, see [here](https://docs.greptime.com/user-guide/deployments-administration/configuration#storage-options).
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ We highly recommend using the self-monitoring feature provided by [GreptimeDB Op
|
|||||||
- **Metrics Dashboards**
|
- **Metrics Dashboards**
|
||||||
|
|
||||||
- `dashboards/metrics/cluster/dashboard.json`: The Grafana dashboard for the GreptimeDB cluster. Read the [dashboard.md](./dashboards/metrics/cluster/dashboard.md) for more details.
|
- `dashboards/metrics/cluster/dashboard.json`: The Grafana dashboard for the GreptimeDB cluster. Read the [dashboard.md](./dashboards/metrics/cluster/dashboard.md) for more details.
|
||||||
|
|
||||||
- `dashboards/metrics/standalone/dashboard.json`: The Grafana dashboard for the standalone GreptimeDB instance. **It's generated from the `cluster/dashboard.json` by removing the instance filter through the `make dashboards` command**. Read the [dashboard.md](./dashboards/metrics/standalone/dashboard.md) for more details.
|
- `dashboards/metrics/standalone/dashboard.json`: The Grafana dashboard for the standalone GreptimeDB instance. **It's generated from the `cluster/dashboard.json` by removing the instance filter through the `make dashboards` command**. Read the [dashboard.md](./dashboards/metrics/standalone/dashboard.md) for more details.
|
||||||
|
|
||||||
- **Logs Dashboard**
|
- **Logs Dashboard**
|
||||||
@@ -83,7 +83,7 @@ If you use the [Helm Chart](https://github.com/GreptimeTeam/helm-charts) to depl
|
|||||||
- `monitoring.enabled=true`: Deploys a standalone GreptimeDB instance dedicated to monitoring the cluster;
|
- `monitoring.enabled=true`: Deploys a standalone GreptimeDB instance dedicated to monitoring the cluster;
|
||||||
- `grafana.enabled=true`: Deploys Grafana and automatically imports the monitoring dashboard;
|
- `grafana.enabled=true`: Deploys Grafana and automatically imports the monitoring dashboard;
|
||||||
|
|
||||||
The standalone GreptimeDB instance will collect metrics from your cluster, and the dashboard will be available in the Grafana UI. For detailed deployment instructions, please refer to our [Kubernetes deployment guide](https://docs.greptime.com/nightly/user-guide/deployments/deploy-on-kubernetes/getting-started).
|
The standalone GreptimeDB instance will collect metrics from your cluster, and the dashboard will be available in the Grafana UI. For detailed deployment instructions, please refer to our [Kubernetes deployment guide](https://docs.greptime.com/user-guide/deployments-administration-administration/deploy-on-kubernetes/getting-started).
|
||||||
|
|
||||||
### Self-host Prometheus and import dashboards manually
|
### Self-host Prometheus and import dashboards manually
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
|||||||
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
|
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
|
||||||
| Compaction Input/Output Bytes | `sum by(instance, pod) (greptime_mito_compaction_input_bytes)`<br/>`sum by(instance, pod) (greptime_mito_compaction_output_bytes)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-input` |
|
| Compaction Input/Output Bytes | `sum by(instance, pod) (greptime_mito_compaction_input_bytes)`<br/>`sum by(instance, pod) (greptime_mito_compaction_output_bytes)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-input` |
|
||||||
| Region Worker Handle Bulk Insert Requests | `histogram_quantile(0.95, sum by(le,instance, stage, pod) (rate(greptime_region_worker_handle_write_bucket[$__rate_interval])))`<br/>`sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_sum[$__rate_interval]))/sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to handle bulk insert region requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
| Region Worker Handle Bulk Insert Requests | `histogram_quantile(0.95, sum by(le,instance, stage, pod) (rate(greptime_region_worker_handle_write_bucket[$__rate_interval])))`<br/>`sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_sum[$__rate_interval]))/sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to handle bulk insert region requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
||||||
|
| Active Series and Field Builders Count | `sum by(instance, pod) (greptime_mito_memtable_active_series_count)`<br/>`sum by(instance, pod) (greptime_mito_memtable_field_builder_count)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]-series` |
|
||||||
| Region Worker Convert Requests | `histogram_quantile(0.95, sum by(le, instance, stage, pod) (rate(greptime_datanode_convert_region_request_bucket[$__rate_interval])))`<br/>`sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_sum[$__rate_interval]))/sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to decode requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
| Region Worker Convert Requests | `histogram_quantile(0.95, sum by(le, instance, stage, pod) (rate(greptime_datanode_convert_region_request_bucket[$__rate_interval])))`<br/>`sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_sum[$__rate_interval]))/sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to decode requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
||||||
# OpenDAL
|
# OpenDAL
|
||||||
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
|
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
|
||||||
|
|||||||
@@ -612,6 +612,21 @@ groups:
|
|||||||
type: prometheus
|
type: prometheus
|
||||||
uid: ${metrics}
|
uid: ${metrics}
|
||||||
legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG'
|
legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG'
|
||||||
|
- title: Active Series and Field Builders Count
|
||||||
|
type: timeseries
|
||||||
|
description: Compaction oinput output bytes
|
||||||
|
unit: none
|
||||||
|
queries:
|
||||||
|
- expr: sum by(instance, pod) (greptime_mito_memtable_active_series_count)
|
||||||
|
datasource:
|
||||||
|
type: prometheus
|
||||||
|
uid: ${metrics}
|
||||||
|
legendFormat: '[{{instance}}]-[{{pod}}]-series'
|
||||||
|
- expr: sum by(instance, pod) (greptime_mito_memtable_field_builder_count)
|
||||||
|
datasource:
|
||||||
|
type: prometheus
|
||||||
|
uid: ${metrics}
|
||||||
|
legendFormat: '[{{instance}}]-[{{pod}}]-field_builders'
|
||||||
- title: Region Worker Convert Requests
|
- title: Region Worker Convert Requests
|
||||||
type: timeseries
|
type: timeseries
|
||||||
description: Per-stage elapsed time for region worker to decode requests.
|
description: Per-stage elapsed time for region worker to decode requests.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
|||||||
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
|
| Inflight Flush | `greptime_mito_inflight_flush_count` | `timeseries` | Ongoing flush task count | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]` |
|
||||||
| Compaction Input/Output Bytes | `sum by(instance, pod) (greptime_mito_compaction_input_bytes)`<br/>`sum by(instance, pod) (greptime_mito_compaction_output_bytes)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-input` |
|
| Compaction Input/Output Bytes | `sum by(instance, pod) (greptime_mito_compaction_input_bytes)`<br/>`sum by(instance, pod) (greptime_mito_compaction_output_bytes)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `bytes` | `[{{instance}}]-[{{pod}}]-input` |
|
||||||
| Region Worker Handle Bulk Insert Requests | `histogram_quantile(0.95, sum by(le,instance, stage, pod) (rate(greptime_region_worker_handle_write_bucket[$__rate_interval])))`<br/>`sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_sum[$__rate_interval]))/sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to handle bulk insert region requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
| Region Worker Handle Bulk Insert Requests | `histogram_quantile(0.95, sum by(le,instance, stage, pod) (rate(greptime_region_worker_handle_write_bucket[$__rate_interval])))`<br/>`sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_sum[$__rate_interval]))/sum by(instance, stage, pod) (rate(greptime_region_worker_handle_write_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to handle bulk insert region requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
||||||
|
| Active Series and Field Builders Count | `sum by(instance, pod) (greptime_mito_memtable_active_series_count)`<br/>`sum by(instance, pod) (greptime_mito_memtable_field_builder_count)` | `timeseries` | Compaction oinput output bytes | `prometheus` | `none` | `[{{instance}}]-[{{pod}}]-series` |
|
||||||
| Region Worker Convert Requests | `histogram_quantile(0.95, sum by(le, instance, stage, pod) (rate(greptime_datanode_convert_region_request_bucket[$__rate_interval])))`<br/>`sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_sum[$__rate_interval]))/sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to decode requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
| Region Worker Convert Requests | `histogram_quantile(0.95, sum by(le, instance, stage, pod) (rate(greptime_datanode_convert_region_request_bucket[$__rate_interval])))`<br/>`sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_sum[$__rate_interval]))/sum by(le,instance, stage, pod) (rate(greptime_datanode_convert_region_request_count[$__rate_interval]))` | `timeseries` | Per-stage elapsed time for region worker to decode requests. | `prometheus` | `s` | `[{{instance}}]-[{{pod}}]-[{{stage}}]-P95` |
|
||||||
# OpenDAL
|
# OpenDAL
|
||||||
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
|
| Title | Query | Type | Description | Datasource | Unit | Legend Format |
|
||||||
|
|||||||
@@ -612,6 +612,21 @@ groups:
|
|||||||
type: prometheus
|
type: prometheus
|
||||||
uid: ${metrics}
|
uid: ${metrics}
|
||||||
legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG'
|
legendFormat: '[{{instance}}]-[{{pod}}]-[{{stage}}]-AVG'
|
||||||
|
- title: Active Series and Field Builders Count
|
||||||
|
type: timeseries
|
||||||
|
description: Compaction oinput output bytes
|
||||||
|
unit: none
|
||||||
|
queries:
|
||||||
|
- expr: sum by(instance, pod) (greptime_mito_memtable_active_series_count)
|
||||||
|
datasource:
|
||||||
|
type: prometheus
|
||||||
|
uid: ${metrics}
|
||||||
|
legendFormat: '[{{instance}}]-[{{pod}}]-series'
|
||||||
|
- expr: sum by(instance, pod) (greptime_mito_memtable_field_builder_count)
|
||||||
|
datasource:
|
||||||
|
type: prometheus
|
||||||
|
uid: ${metrics}
|
||||||
|
legendFormat: '[{{instance}}]-[{{pod}}]-field_builders'
|
||||||
- title: Region Worker Convert Requests
|
- title: Region Worker Convert Requests
|
||||||
type: timeseries
|
type: timeseries
|
||||||
description: Per-stage elapsed time for region worker to decode requests.
|
description: Per-stage elapsed time for region worker to decode requests.
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ excludes = [
|
|||||||
"src/operator/src/expr_helper/trigger.rs",
|
"src/operator/src/expr_helper/trigger.rs",
|
||||||
"src/sql/src/statements/create/trigger.rs",
|
"src/sql/src/statements/create/trigger.rs",
|
||||||
"src/sql/src/statements/show/trigger.rs",
|
"src/sql/src/statements/show/trigger.rs",
|
||||||
|
"src/sql/src/statements/drop/trigger.rs",
|
||||||
"src/sql/src/parsers/create_parser/trigger.rs",
|
"src/sql/src/parsers/create_parser/trigger.rs",
|
||||||
"src/sql/src/parsers/show_parser/trigger.rs",
|
"src/sql/src/parsers/show_parser/trigger.rs",
|
||||||
|
"src/mito2/src/extension.rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[properties]
|
[properties]
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use greptime_proto::v1::region::RegionResponse as RegionResponseV1;
|
|||||||
pub struct RegionResponse {
|
pub struct RegionResponse {
|
||||||
pub affected_rows: AffectedRows,
|
pub affected_rows: AffectedRows,
|
||||||
pub extensions: HashMap<String, Vec<u8>>,
|
pub extensions: HashMap<String, Vec<u8>>,
|
||||||
|
pub metadata: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegionResponse {
|
impl RegionResponse {
|
||||||
@@ -29,6 +30,7 @@ impl RegionResponse {
|
|||||||
Self {
|
Self {
|
||||||
affected_rows: region_response.affected_rows as _,
|
affected_rows: region_response.affected_rows as _,
|
||||||
extensions: region_response.extensions,
|
extensions: region_response.extensions,
|
||||||
|
metadata: region_response.metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +39,16 @@ impl RegionResponse {
|
|||||||
Self {
|
Self {
|
||||||
affected_rows,
|
affected_rows,
|
||||||
extensions: Default::default(),
|
extensions: Default::default(),
|
||||||
|
metadata: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates one response with metadata.
|
||||||
|
pub fn from_metadata(metadata: Vec<u8>) -> Self {
|
||||||
|
Self {
|
||||||
|
affected_rows: 0,
|
||||||
|
extensions: Default::default(),
|
||||||
|
metadata,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,18 +226,20 @@ mod tests {
|
|||||||
assert!(options.is_none());
|
assert!(options.is_none());
|
||||||
|
|
||||||
let mut schema = ColumnSchema::new("test", ConcreteDataType::string_datatype(), true)
|
let mut schema = ColumnSchema::new("test", ConcreteDataType::string_datatype(), true)
|
||||||
.with_fulltext_options(FulltextOptions {
|
.with_fulltext_options(FulltextOptions::new_unchecked(
|
||||||
enable: true,
|
true,
|
||||||
analyzer: FulltextAnalyzer::English,
|
FulltextAnalyzer::English,
|
||||||
case_sensitive: false,
|
false,
|
||||||
backend: FulltextBackend::Bloom,
|
FulltextBackend::Bloom,
|
||||||
})
|
10240,
|
||||||
|
0.01,
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
schema.set_inverted_index(true);
|
schema.set_inverted_index(true);
|
||||||
let options = options_from_column_schema(&schema).unwrap();
|
let options = options_from_column_schema(&schema).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
options.options.get(FULLTEXT_GRPC_KEY).unwrap(),
|
options.options.get(FULLTEXT_GRPC_KEY).unwrap(),
|
||||||
"{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\"}"
|
"{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\",\"granularity\":10240,\"false-positive-rate-in-10000\":100}"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
options.options.get(INVERTED_INDEX_GRPC_KEY).unwrap(),
|
options.options.get(INVERTED_INDEX_GRPC_KEY).unwrap(),
|
||||||
@@ -247,16 +249,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_options_with_fulltext() {
|
fn test_options_with_fulltext() {
|
||||||
let fulltext = FulltextOptions {
|
let fulltext = FulltextOptions::new_unchecked(
|
||||||
enable: true,
|
true,
|
||||||
analyzer: FulltextAnalyzer::English,
|
FulltextAnalyzer::English,
|
||||||
case_sensitive: false,
|
false,
|
||||||
backend: FulltextBackend::Bloom,
|
FulltextBackend::Bloom,
|
||||||
};
|
10240,
|
||||||
|
0.01,
|
||||||
|
);
|
||||||
let options = options_from_fulltext(&fulltext).unwrap().unwrap();
|
let options = options_from_fulltext(&fulltext).unwrap().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
options.options.get(FULLTEXT_GRPC_KEY).unwrap(),
|
options.options.get(FULLTEXT_GRPC_KEY).unwrap(),
|
||||||
"{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\"}"
|
"{\"enable\":true,\"analyzer\":\"English\",\"case-sensitive\":false,\"backend\":\"bloom\",\"granularity\":10240,\"false-positive-rate-in-10000\":100}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ edition.workspace = true
|
|||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
enterprise = []
|
||||||
testing = []
|
testing = []
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
@@ -17,8 +18,10 @@ arrow-schema.workspace = true
|
|||||||
async-stream.workspace = true
|
async-stream.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
|
common-base.workspace = true
|
||||||
common-catalog.workspace = true
|
common-catalog.workspace = true
|
||||||
common-error.workspace = true
|
common-error.workspace = true
|
||||||
|
common-frontend.workspace = true
|
||||||
common-macro.workspace = true
|
common-macro.workspace = true
|
||||||
common-meta.workspace = true
|
common-meta.workspace = true
|
||||||
common-procedure.workspace = true
|
common-procedure.workspace = true
|
||||||
|
|||||||
@@ -277,6 +277,26 @@ pub enum Error {
|
|||||||
#[snafu(implicit)]
|
#[snafu(implicit)]
|
||||||
location: Location,
|
location: Location,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to invoke frontend services"))]
|
||||||
|
InvokeFrontend {
|
||||||
|
source: common_frontend::error::Error,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Meta client is not provided"))]
|
||||||
|
MetaClientMissing {
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to find frontend node: {}", addr))]
|
||||||
|
FrontendNotFound {
|
||||||
|
addr: String,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
@@ -345,6 +365,10 @@ impl ErrorExt for Error {
|
|||||||
Error::GetViewCache { source, .. } | Error::GetTableCache { source, .. } => {
|
Error::GetViewCache { source, .. } | Error::GetTableCache { source, .. } => {
|
||||||
source.status_code()
|
source.status_code()
|
||||||
}
|
}
|
||||||
|
Error::InvokeFrontend { source, .. } => source.status_code(),
|
||||||
|
Error::FrontendNotFound { .. } | Error::MetaClientMissing { .. } => {
|
||||||
|
StatusCode::Unexpected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
|
|
||||||
pub use client::{CachedKvBackend, CachedKvBackendBuilder, MetaKvBackend};
|
pub use client::{CachedKvBackend, CachedKvBackendBuilder, MetaKvBackend};
|
||||||
|
|
||||||
|
mod builder;
|
||||||
mod client;
|
mod client;
|
||||||
mod manager;
|
mod manager;
|
||||||
mod table_cache;
|
mod table_cache;
|
||||||
|
|
||||||
|
pub use builder::KvBackendCatalogManagerBuilder;
|
||||||
pub use manager::KvBackendCatalogManager;
|
pub use manager::KvBackendCatalogManager;
|
||||||
pub use table_cache::{new_table_cache, TableCache, TableCacheRef};
|
pub use table_cache::{new_table_cache, TableCache, TableCacheRef};
|
||||||
|
|||||||
131
src/catalog/src/kvbackend/builder.rs
Normal file
131
src/catalog/src/kvbackend/builder.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
// 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::sync::Arc;
|
||||||
|
|
||||||
|
use common_catalog::consts::DEFAULT_CATALOG_NAME;
|
||||||
|
use common_meta::cache::LayeredCacheRegistryRef;
|
||||||
|
use common_meta::key::flow::FlowMetadataManager;
|
||||||
|
use common_meta::key::TableMetadataManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use common_procedure::ProcedureManagerRef;
|
||||||
|
use moka::sync::Cache;
|
||||||
|
use partition::manager::PartitionRuleManager;
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
use crate::information_schema::InformationSchemaTableFactoryRef;
|
||||||
|
use crate::information_schema::{InformationExtensionRef, InformationSchemaProvider};
|
||||||
|
use crate::kvbackend::manager::{SystemCatalog, CATALOG_CACHE_MAX_CAPACITY};
|
||||||
|
use crate::kvbackend::KvBackendCatalogManager;
|
||||||
|
use crate::process_manager::ProcessManagerRef;
|
||||||
|
use crate::system_schema::pg_catalog::PGCatalogProvider;
|
||||||
|
|
||||||
|
pub struct KvBackendCatalogManagerBuilder {
|
||||||
|
information_extension: InformationExtensionRef,
|
||||||
|
backend: KvBackendRef,
|
||||||
|
cache_registry: LayeredCacheRegistryRef,
|
||||||
|
procedure_manager: Option<ProcedureManagerRef>,
|
||||||
|
process_manager: Option<ProcessManagerRef>,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
extra_information_table_factories:
|
||||||
|
std::collections::HashMap<String, InformationSchemaTableFactoryRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KvBackendCatalogManagerBuilder {
|
||||||
|
pub fn new(
|
||||||
|
information_extension: InformationExtensionRef,
|
||||||
|
backend: KvBackendRef,
|
||||||
|
cache_registry: LayeredCacheRegistryRef,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
information_extension,
|
||||||
|
backend,
|
||||||
|
cache_registry,
|
||||||
|
procedure_manager: None,
|
||||||
|
process_manager: None,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
extra_information_table_factories: std::collections::HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_procedure_manager(mut self, procedure_manager: ProcedureManagerRef) -> Self {
|
||||||
|
self.procedure_manager = Some(procedure_manager);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_process_manager(mut self, process_manager: ProcessManagerRef) -> Self {
|
||||||
|
self.process_manager = Some(process_manager);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the extra information tables.
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub fn with_extra_information_table_factories(
|
||||||
|
mut self,
|
||||||
|
factories: std::collections::HashMap<String, InformationSchemaTableFactoryRef>,
|
||||||
|
) -> Self {
|
||||||
|
self.extra_information_table_factories = factories;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Arc<KvBackendCatalogManager> {
|
||||||
|
let Self {
|
||||||
|
information_extension,
|
||||||
|
backend,
|
||||||
|
cache_registry,
|
||||||
|
procedure_manager,
|
||||||
|
process_manager,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
extra_information_table_factories,
|
||||||
|
} = self;
|
||||||
|
Arc::new_cyclic(|me| KvBackendCatalogManager {
|
||||||
|
information_extension,
|
||||||
|
partition_manager: Arc::new(PartitionRuleManager::new(
|
||||||
|
backend.clone(),
|
||||||
|
cache_registry
|
||||||
|
.get()
|
||||||
|
.expect("Failed to get table_route_cache"),
|
||||||
|
)),
|
||||||
|
table_metadata_manager: Arc::new(TableMetadataManager::new(backend.clone())),
|
||||||
|
system_catalog: SystemCatalog {
|
||||||
|
catalog_manager: me.clone(),
|
||||||
|
catalog_cache: Cache::new(CATALOG_CACHE_MAX_CAPACITY),
|
||||||
|
pg_catalog_cache: Cache::new(CATALOG_CACHE_MAX_CAPACITY),
|
||||||
|
information_schema_provider: {
|
||||||
|
let provider = InformationSchemaProvider::new(
|
||||||
|
DEFAULT_CATALOG_NAME.to_string(),
|
||||||
|
me.clone(),
|
||||||
|
Arc::new(FlowMetadataManager::new(backend.clone())),
|
||||||
|
process_manager.clone(),
|
||||||
|
backend.clone(),
|
||||||
|
);
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
let provider = provider
|
||||||
|
.with_extra_table_factories(extra_information_table_factories.clone());
|
||||||
|
Arc::new(provider)
|
||||||
|
},
|
||||||
|
pg_catalog_provider: Arc::new(PGCatalogProvider::new(
|
||||||
|
DEFAULT_CATALOG_NAME.to_string(),
|
||||||
|
me.clone(),
|
||||||
|
)),
|
||||||
|
backend,
|
||||||
|
process_manager,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
extra_information_table_factories,
|
||||||
|
},
|
||||||
|
cache_registry,
|
||||||
|
procedure_manager,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,21 +22,24 @@ use common_catalog::consts::{
|
|||||||
PG_CATALOG_NAME,
|
PG_CATALOG_NAME,
|
||||||
};
|
};
|
||||||
use common_error::ext::BoxedError;
|
use common_error::ext::BoxedError;
|
||||||
use common_meta::cache::{LayeredCacheRegistryRef, ViewInfoCacheRef};
|
use common_meta::cache::{
|
||||||
|
LayeredCacheRegistryRef, TableRoute, TableRouteCacheRef, ViewInfoCacheRef,
|
||||||
|
};
|
||||||
use common_meta::key::catalog_name::CatalogNameKey;
|
use common_meta::key::catalog_name::CatalogNameKey;
|
||||||
use common_meta::key::flow::FlowMetadataManager;
|
use common_meta::key::flow::FlowMetadataManager;
|
||||||
use common_meta::key::schema_name::SchemaNameKey;
|
use common_meta::key::schema_name::SchemaNameKey;
|
||||||
use common_meta::key::table_info::TableInfoValue;
|
use common_meta::key::table_info::{TableInfoManager, TableInfoValue};
|
||||||
use common_meta::key::table_name::TableNameKey;
|
use common_meta::key::table_name::TableNameKey;
|
||||||
use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
|
use common_meta::key::TableMetadataManagerRef;
|
||||||
use common_meta::kv_backend::KvBackendRef;
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
use common_procedure::ProcedureManagerRef;
|
use common_procedure::ProcedureManagerRef;
|
||||||
use futures_util::stream::BoxStream;
|
use futures_util::stream::BoxStream;
|
||||||
use futures_util::{StreamExt, TryStreamExt};
|
use futures_util::{StreamExt, TryStreamExt};
|
||||||
use moka::sync::Cache;
|
use moka::sync::Cache;
|
||||||
use partition::manager::{PartitionRuleManager, PartitionRuleManagerRef};
|
use partition::manager::PartitionRuleManagerRef;
|
||||||
use session::context::{Channel, QueryContext};
|
use session::context::{Channel, QueryContext};
|
||||||
use snafu::prelude::*;
|
use snafu::prelude::*;
|
||||||
|
use store_api::metric_engine_consts::METRIC_ENGINE_NAME;
|
||||||
use table::dist_table::DistTable;
|
use table::dist_table::DistTable;
|
||||||
use table::metadata::TableId;
|
use table::metadata::TableId;
|
||||||
use table::table::numbers::{NumbersTable, NUMBERS_TABLE_NAME};
|
use table::table::numbers::{NumbersTable, NUMBERS_TABLE_NAME};
|
||||||
@@ -49,8 +52,11 @@ use crate::error::{
|
|||||||
CacheNotFoundSnafu, GetTableCacheSnafu, InvalidTableInfoInCatalogSnafu, ListCatalogsSnafu,
|
CacheNotFoundSnafu, GetTableCacheSnafu, InvalidTableInfoInCatalogSnafu, ListCatalogsSnafu,
|
||||||
ListSchemasSnafu, ListTablesSnafu, Result, TableMetadataManagerSnafu,
|
ListSchemasSnafu, ListTablesSnafu, Result, TableMetadataManagerSnafu,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
use crate::information_schema::InformationSchemaTableFactoryRef;
|
||||||
use crate::information_schema::{InformationExtensionRef, InformationSchemaProvider};
|
use crate::information_schema::{InformationExtensionRef, InformationSchemaProvider};
|
||||||
use crate::kvbackend::TableCacheRef;
|
use crate::kvbackend::TableCacheRef;
|
||||||
|
use crate::process_manager::ProcessManagerRef;
|
||||||
use crate::system_schema::pg_catalog::PGCatalogProvider;
|
use crate::system_schema::pg_catalog::PGCatalogProvider;
|
||||||
use crate::system_schema::SystemSchemaProvider;
|
use crate::system_schema::SystemSchemaProvider;
|
||||||
use crate::CatalogManager;
|
use crate::CatalogManager;
|
||||||
@@ -63,57 +69,22 @@ use crate::CatalogManager;
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct KvBackendCatalogManager {
|
pub struct KvBackendCatalogManager {
|
||||||
/// Provides the extension methods for the `information_schema` tables
|
/// Provides the extension methods for the `information_schema` tables
|
||||||
information_extension: InformationExtensionRef,
|
pub(super) information_extension: InformationExtensionRef,
|
||||||
/// Manages partition rules.
|
/// Manages partition rules.
|
||||||
partition_manager: PartitionRuleManagerRef,
|
pub(super) partition_manager: PartitionRuleManagerRef,
|
||||||
/// Manages table metadata.
|
/// Manages table metadata.
|
||||||
table_metadata_manager: TableMetadataManagerRef,
|
pub(super) table_metadata_manager: TableMetadataManagerRef,
|
||||||
/// A sub-CatalogManager that handles system tables
|
/// A sub-CatalogManager that handles system tables
|
||||||
system_catalog: SystemCatalog,
|
pub(super) system_catalog: SystemCatalog,
|
||||||
/// Cache registry for all caches.
|
/// Cache registry for all caches.
|
||||||
cache_registry: LayeredCacheRegistryRef,
|
pub(super) cache_registry: LayeredCacheRegistryRef,
|
||||||
/// Only available in `Standalone` mode.
|
/// Only available in `Standalone` mode.
|
||||||
procedure_manager: Option<ProcedureManagerRef>,
|
pub(super) procedure_manager: Option<ProcedureManagerRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const CATALOG_CACHE_MAX_CAPACITY: u64 = 128;
|
pub(super) const CATALOG_CACHE_MAX_CAPACITY: u64 = 128;
|
||||||
|
|
||||||
impl KvBackendCatalogManager {
|
impl KvBackendCatalogManager {
|
||||||
pub fn new(
|
|
||||||
information_extension: InformationExtensionRef,
|
|
||||||
backend: KvBackendRef,
|
|
||||||
cache_registry: LayeredCacheRegistryRef,
|
|
||||||
procedure_manager: Option<ProcedureManagerRef>,
|
|
||||||
) -> Arc<Self> {
|
|
||||||
Arc::new_cyclic(|me| Self {
|
|
||||||
information_extension,
|
|
||||||
partition_manager: Arc::new(PartitionRuleManager::new(
|
|
||||||
backend.clone(),
|
|
||||||
cache_registry
|
|
||||||
.get()
|
|
||||||
.expect("Failed to get table_route_cache"),
|
|
||||||
)),
|
|
||||||
table_metadata_manager: Arc::new(TableMetadataManager::new(backend.clone())),
|
|
||||||
system_catalog: SystemCatalog {
|
|
||||||
catalog_manager: me.clone(),
|
|
||||||
catalog_cache: Cache::new(CATALOG_CACHE_MAX_CAPACITY),
|
|
||||||
pg_catalog_cache: Cache::new(CATALOG_CACHE_MAX_CAPACITY),
|
|
||||||
information_schema_provider: Arc::new(InformationSchemaProvider::new(
|
|
||||||
DEFAULT_CATALOG_NAME.to_string(),
|
|
||||||
me.clone(),
|
|
||||||
Arc::new(FlowMetadataManager::new(backend.clone())),
|
|
||||||
)),
|
|
||||||
pg_catalog_provider: Arc::new(PGCatalogProvider::new(
|
|
||||||
DEFAULT_CATALOG_NAME.to_string(),
|
|
||||||
me.clone(),
|
|
||||||
)),
|
|
||||||
backend,
|
|
||||||
},
|
|
||||||
cache_registry,
|
|
||||||
procedure_manager,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view_info_cache(&self) -> Result<ViewInfoCacheRef> {
|
pub fn view_info_cache(&self) -> Result<ViewInfoCacheRef> {
|
||||||
self.cache_registry.get().context(CacheNotFoundSnafu {
|
self.cache_registry.get().context(CacheNotFoundSnafu {
|
||||||
name: "view_info_cache",
|
name: "view_info_cache",
|
||||||
@@ -136,6 +107,61 @@ impl KvBackendCatalogManager {
|
|||||||
pub fn procedure_manager(&self) -> Option<ProcedureManagerRef> {
|
pub fn procedure_manager(&self) -> Option<ProcedureManagerRef> {
|
||||||
self.procedure_manager.clone()
|
self.procedure_manager.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Override logical table's partition key indices with physical table's.
|
||||||
|
async fn override_logical_table_partition_key_indices(
|
||||||
|
table_route_cache: &TableRouteCacheRef,
|
||||||
|
table_info_manager: &TableInfoManager,
|
||||||
|
table: TableRef,
|
||||||
|
) -> Result<TableRef> {
|
||||||
|
// If the table is not a metric table, return the table directly.
|
||||||
|
if table.table_info().meta.engine != METRIC_ENGINE_NAME {
|
||||||
|
return Ok(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(table_route_value) = table_route_cache
|
||||||
|
.get(table.table_info().table_id())
|
||||||
|
.await
|
||||||
|
.context(TableMetadataManagerSnafu)?
|
||||||
|
&& let TableRoute::Logical(logical_route) = &*table_route_value
|
||||||
|
&& let Some(physical_table_info_value) = table_info_manager
|
||||||
|
.get(logical_route.physical_table_id())
|
||||||
|
.await
|
||||||
|
.context(TableMetadataManagerSnafu)?
|
||||||
|
{
|
||||||
|
let mut new_table_info = (*table.table_info()).clone();
|
||||||
|
|
||||||
|
// Remap partition key indices from physical table to logical table
|
||||||
|
new_table_info.meta.partition_key_indices = physical_table_info_value
|
||||||
|
.table_info
|
||||||
|
.meta
|
||||||
|
.partition_key_indices
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&physical_index| {
|
||||||
|
// Get the column name from the physical table using the physical index
|
||||||
|
physical_table_info_value
|
||||||
|
.table_info
|
||||||
|
.meta
|
||||||
|
.schema
|
||||||
|
.column_schemas
|
||||||
|
.get(physical_index)
|
||||||
|
.and_then(|physical_column| {
|
||||||
|
// Find the corresponding index in the logical table schema
|
||||||
|
new_table_info
|
||||||
|
.meta
|
||||||
|
.schema
|
||||||
|
.column_index_by_name(physical_column.name.as_str())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let new_table = DistTable::table(Arc::new(new_table_info));
|
||||||
|
|
||||||
|
return Ok(new_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(table)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -262,16 +288,28 @@ impl CatalogManager for KvBackendCatalogManager {
|
|||||||
let table_cache: TableCacheRef = self.cache_registry.get().context(CacheNotFoundSnafu {
|
let table_cache: TableCacheRef = self.cache_registry.get().context(CacheNotFoundSnafu {
|
||||||
name: "table_cache",
|
name: "table_cache",
|
||||||
})?;
|
})?;
|
||||||
if let Some(table) = table_cache
|
|
||||||
|
let table = table_cache
|
||||||
.get_by_ref(&TableName {
|
.get_by_ref(&TableName {
|
||||||
catalog_name: catalog_name.to_string(),
|
catalog_name: catalog_name.to_string(),
|
||||||
schema_name: schema_name.to_string(),
|
schema_name: schema_name.to_string(),
|
||||||
table_name: table_name.to_string(),
|
table_name: table_name.to_string(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.context(GetTableCacheSnafu)?
|
.context(GetTableCacheSnafu)?;
|
||||||
{
|
|
||||||
return Ok(Some(table));
|
if let Some(table) = table {
|
||||||
|
let table_route_cache: TableRouteCacheRef =
|
||||||
|
self.cache_registry.get().context(CacheNotFoundSnafu {
|
||||||
|
name: "table_route_cache",
|
||||||
|
})?;
|
||||||
|
return Self::override_logical_table_partition_key_indices(
|
||||||
|
&table_route_cache,
|
||||||
|
self.table_metadata_manager.table_info_manager(),
|
||||||
|
table,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(Some);
|
||||||
}
|
}
|
||||||
|
|
||||||
if channel == Channel::Postgres {
|
if channel == Channel::Postgres {
|
||||||
@@ -284,7 +322,7 @@ impl CatalogManager for KvBackendCatalogManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(None);
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn tables_by_ids(
|
async fn tables_by_ids(
|
||||||
@@ -336,8 +374,20 @@ impl CatalogManager for KvBackendCatalogManager {
|
|||||||
let catalog = catalog.to_string();
|
let catalog = catalog.to_string();
|
||||||
let schema = schema.to_string();
|
let schema = schema.to_string();
|
||||||
let semaphore = Arc::new(Semaphore::new(CONCURRENCY));
|
let semaphore = Arc::new(Semaphore::new(CONCURRENCY));
|
||||||
|
let table_route_cache: Result<TableRouteCacheRef> =
|
||||||
|
self.cache_registry.get().context(CacheNotFoundSnafu {
|
||||||
|
name: "table_route_cache",
|
||||||
|
});
|
||||||
|
|
||||||
common_runtime::spawn_global(async move {
|
common_runtime::spawn_global(async move {
|
||||||
|
let table_route_cache = match table_route_cache {
|
||||||
|
Ok(table_route_cache) => table_route_cache,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = tx.send(Err(e)).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let table_id_stream = metadata_manager
|
let table_id_stream = metadata_manager
|
||||||
.table_name_manager()
|
.table_name_manager()
|
||||||
.tables(&catalog, &schema)
|
.tables(&catalog, &schema)
|
||||||
@@ -364,6 +414,7 @@ impl CatalogManager for KvBackendCatalogManager {
|
|||||||
let metadata_manager = metadata_manager.clone();
|
let metadata_manager = metadata_manager.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let semaphore = semaphore.clone();
|
let semaphore = semaphore.clone();
|
||||||
|
let table_route_cache = table_route_cache.clone();
|
||||||
common_runtime::spawn_global(async move {
|
common_runtime::spawn_global(async move {
|
||||||
// we don't explicitly close the semaphore so just ignore the potential error.
|
// we don't explicitly close the semaphore so just ignore the potential error.
|
||||||
let _ = semaphore.acquire().await;
|
let _ = semaphore.acquire().await;
|
||||||
@@ -381,6 +432,16 @@ impl CatalogManager for KvBackendCatalogManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for table in table_info_values.into_values().map(build_table) {
|
for table in table_info_values.into_values().map(build_table) {
|
||||||
|
let table = if let Ok(table) = table {
|
||||||
|
Self::override_logical_table_partition_key_indices(
|
||||||
|
&table_route_cache,
|
||||||
|
metadata_manager.table_info_manager(),
|
||||||
|
table,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
table
|
||||||
|
};
|
||||||
if tx.send(table).await.is_err() {
|
if tx.send(table).await.is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -410,15 +471,19 @@ fn build_table(table_info_value: TableInfoValue) -> Result<TableRef> {
|
|||||||
/// - information_schema.{tables}
|
/// - information_schema.{tables}
|
||||||
/// - pg_catalog.{tables}
|
/// - pg_catalog.{tables}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct SystemCatalog {
|
pub(super) struct SystemCatalog {
|
||||||
catalog_manager: Weak<KvBackendCatalogManager>,
|
pub(super) catalog_manager: Weak<KvBackendCatalogManager>,
|
||||||
catalog_cache: Cache<String, Arc<InformationSchemaProvider>>,
|
pub(super) catalog_cache: Cache<String, Arc<InformationSchemaProvider>>,
|
||||||
pg_catalog_cache: Cache<String, Arc<PGCatalogProvider>>,
|
pub(super) pg_catalog_cache: Cache<String, Arc<PGCatalogProvider>>,
|
||||||
|
|
||||||
// system_schema_provider for default catalog
|
// system_schema_provider for default catalog
|
||||||
information_schema_provider: Arc<InformationSchemaProvider>,
|
pub(super) information_schema_provider: Arc<InformationSchemaProvider>,
|
||||||
pg_catalog_provider: Arc<PGCatalogProvider>,
|
pub(super) pg_catalog_provider: Arc<PGCatalogProvider>,
|
||||||
backend: KvBackendRef,
|
pub(super) backend: KvBackendRef,
|
||||||
|
pub(super) process_manager: Option<ProcessManagerRef>,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub(super) extra_information_table_factories:
|
||||||
|
std::collections::HashMap<String, InformationSchemaTableFactoryRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemCatalog {
|
impl SystemCatalog {
|
||||||
@@ -482,11 +547,17 @@ impl SystemCatalog {
|
|||||||
if schema == INFORMATION_SCHEMA_NAME {
|
if schema == INFORMATION_SCHEMA_NAME {
|
||||||
let information_schema_provider =
|
let information_schema_provider =
|
||||||
self.catalog_cache.get_with_by_ref(catalog, move || {
|
self.catalog_cache.get_with_by_ref(catalog, move || {
|
||||||
Arc::new(InformationSchemaProvider::new(
|
let provider = InformationSchemaProvider::new(
|
||||||
catalog.to_string(),
|
catalog.to_string(),
|
||||||
self.catalog_manager.clone(),
|
self.catalog_manager.clone(),
|
||||||
Arc::new(FlowMetadataManager::new(self.backend.clone())),
|
Arc::new(FlowMetadataManager::new(self.backend.clone())),
|
||||||
))
|
self.process_manager.clone(),
|
||||||
|
self.backend.clone(),
|
||||||
|
);
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
let provider = provider
|
||||||
|
.with_extra_table_factories(self.extra_information_table_factories.clone());
|
||||||
|
Arc::new(provider)
|
||||||
});
|
});
|
||||||
information_schema_provider.table(table_name)
|
information_schema_provider.table(table_name)
|
||||||
} else if schema == PG_CATALOG_NAME && channel == Channel::Postgres {
|
} else if schema == PG_CATALOG_NAME && channel == Channel::Postgres {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#![feature(assert_matches)]
|
#![feature(assert_matches)]
|
||||||
#![feature(try_blocks)]
|
#![feature(try_blocks)]
|
||||||
|
#![feature(let_chains)]
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
@@ -40,6 +41,7 @@ pub mod information_schema {
|
|||||||
pub use crate::system_schema::information_schema::*;
|
pub use crate::system_schema::information_schema::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod process_manager;
|
||||||
pub mod table_source;
|
pub mod table_source;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|||||||
@@ -352,10 +352,13 @@ impl MemoryCatalogManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_catalog_entry(self: &Arc<Self>, catalog: String) -> SchemaEntries {
|
fn create_catalog_entry(self: &Arc<Self>, catalog: String) -> SchemaEntries {
|
||||||
|
let backend = Arc::new(MemoryKvBackend::new());
|
||||||
let information_schema_provider = InformationSchemaProvider::new(
|
let information_schema_provider = InformationSchemaProvider::new(
|
||||||
catalog,
|
catalog,
|
||||||
Arc::downgrade(self) as Weak<dyn CatalogManager>,
|
Arc::downgrade(self) as Weak<dyn CatalogManager>,
|
||||||
Arc::new(FlowMetadataManager::new(Arc::new(MemoryKvBackend::new()))),
|
Arc::new(FlowMetadataManager::new(backend.clone())),
|
||||||
|
None, // we don't need ProcessManager on regions server.
|
||||||
|
backend,
|
||||||
);
|
);
|
||||||
let information_schema = information_schema_provider.tables().clone();
|
let information_schema = information_schema_provider.tables().clone();
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,20 @@ lazy_static! {
|
|||||||
register_histogram!("greptime_catalog_kv_get", "catalog kv get").unwrap();
|
register_histogram!("greptime_catalog_kv_get", "catalog kv get").unwrap();
|
||||||
pub static ref METRIC_CATALOG_KV_BATCH_GET: Histogram =
|
pub static ref METRIC_CATALOG_KV_BATCH_GET: Histogram =
|
||||||
register_histogram!("greptime_catalog_kv_batch_get", "catalog kv batch get").unwrap();
|
register_histogram!("greptime_catalog_kv_batch_get", "catalog kv batch get").unwrap();
|
||||||
|
|
||||||
|
/// Count of running process in each catalog.
|
||||||
|
pub static ref PROCESS_LIST_COUNT: IntGaugeVec = register_int_gauge_vec!(
|
||||||
|
"greptime_process_list_count",
|
||||||
|
"Running process count per catalog",
|
||||||
|
&["catalog"]
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
/// Count of killed process in each catalog.
|
||||||
|
pub static ref PROCESS_KILL_COUNT: IntCounterVec = register_int_counter_vec!(
|
||||||
|
"greptime_process_kill_count",
|
||||||
|
"Completed kill process requests count",
|
||||||
|
&["catalog"]
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
500
src/catalog/src/process_manager.rs
Normal file
500
src/catalog/src/process_manager.rs
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
// 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::collections::hash_map::Entry;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use api::v1::frontend::{KillProcessRequest, ListProcessRequest, ProcessInfo};
|
||||||
|
use common_base::cancellation::CancellationHandle;
|
||||||
|
use common_frontend::selector::{FrontendSelector, MetaClientSelector};
|
||||||
|
use common_telemetry::{debug, info, warn};
|
||||||
|
use common_time::util::current_time_millis;
|
||||||
|
use meta_client::MetaClientRef;
|
||||||
|
use snafu::{ensure, OptionExt, ResultExt};
|
||||||
|
|
||||||
|
use crate::error;
|
||||||
|
use crate::metrics::{PROCESS_KILL_COUNT, PROCESS_LIST_COUNT};
|
||||||
|
|
||||||
|
pub type ProcessId = u32;
|
||||||
|
pub type ProcessManagerRef = Arc<ProcessManager>;
|
||||||
|
|
||||||
|
/// Query process manager.
|
||||||
|
pub struct ProcessManager {
|
||||||
|
/// Local frontend server address,
|
||||||
|
server_addr: String,
|
||||||
|
/// Next process id for local queries.
|
||||||
|
next_id: AtomicU32,
|
||||||
|
/// Running process per catalog.
|
||||||
|
catalogs: RwLock<HashMap<String, HashMap<ProcessId, CancellableProcess>>>,
|
||||||
|
/// Frontend selector to locate frontend nodes.
|
||||||
|
frontend_selector: Option<MetaClientSelector>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessManager {
|
||||||
|
/// Create a [ProcessManager] instance with server address and kv client.
|
||||||
|
pub fn new(server_addr: String, meta_client: Option<MetaClientRef>) -> Self {
|
||||||
|
let frontend_selector = meta_client.map(MetaClientSelector::new);
|
||||||
|
Self {
|
||||||
|
server_addr,
|
||||||
|
next_id: Default::default(),
|
||||||
|
catalogs: Default::default(),
|
||||||
|
frontend_selector,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessManager {
|
||||||
|
/// Registers a submitted query. Use the provided id if present.
|
||||||
|
#[must_use]
|
||||||
|
pub fn register_query(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
catalog: String,
|
||||||
|
schemas: Vec<String>,
|
||||||
|
query: String,
|
||||||
|
client: String,
|
||||||
|
query_id: Option<ProcessId>,
|
||||||
|
) -> Ticket {
|
||||||
|
let id = query_id.unwrap_or_else(|| self.next_id.fetch_add(1, Ordering::Relaxed));
|
||||||
|
let process = ProcessInfo {
|
||||||
|
id,
|
||||||
|
catalog: catalog.clone(),
|
||||||
|
schemas,
|
||||||
|
query,
|
||||||
|
start_timestamp: current_time_millis(),
|
||||||
|
client,
|
||||||
|
frontend: self.server_addr.clone(),
|
||||||
|
};
|
||||||
|
let cancellation_handle = Arc::new(CancellationHandle::default());
|
||||||
|
let cancellable_process = CancellableProcess::new(cancellation_handle.clone(), process);
|
||||||
|
|
||||||
|
self.catalogs
|
||||||
|
.write()
|
||||||
|
.unwrap()
|
||||||
|
.entry(catalog.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(id, cancellable_process);
|
||||||
|
|
||||||
|
Ticket {
|
||||||
|
catalog,
|
||||||
|
manager: self.clone(),
|
||||||
|
id,
|
||||||
|
cancellation_handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates the next process id.
|
||||||
|
pub fn next_id(&self) -> u32 {
|
||||||
|
self.next_id.fetch_add(1, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// De-register a query from process list.
|
||||||
|
pub fn deregister_query(&self, catalog: String, id: ProcessId) {
|
||||||
|
if let Entry::Occupied(mut o) = self.catalogs.write().unwrap().entry(catalog) {
|
||||||
|
let process = o.get_mut().remove(&id);
|
||||||
|
debug!("Deregister process: {:?}", process);
|
||||||
|
if o.get().is_empty() {
|
||||||
|
o.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List local running processes in given catalog.
|
||||||
|
pub fn local_processes(&self, catalog: Option<&str>) -> error::Result<Vec<ProcessInfo>> {
|
||||||
|
let catalogs = self.catalogs.read().unwrap();
|
||||||
|
let result = if let Some(catalog) = catalog {
|
||||||
|
if let Some(catalogs) = catalogs.get(catalog) {
|
||||||
|
catalogs.values().map(|p| p.process.clone()).collect()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
catalogs
|
||||||
|
.values()
|
||||||
|
.flat_map(|v| v.values().map(|p| p.process.clone()))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_all_processes(
|
||||||
|
&self,
|
||||||
|
catalog: Option<&str>,
|
||||||
|
) -> error::Result<Vec<ProcessInfo>> {
|
||||||
|
let mut processes = vec![];
|
||||||
|
if let Some(remote_frontend_selector) = self.frontend_selector.as_ref() {
|
||||||
|
let frontends = remote_frontend_selector
|
||||||
|
.select(|node| node.peer.addr != self.server_addr)
|
||||||
|
.await
|
||||||
|
.context(error::InvokeFrontendSnafu)?;
|
||||||
|
for mut f in frontends {
|
||||||
|
let result = f
|
||||||
|
.list_process(ListProcessRequest {
|
||||||
|
catalog: catalog.unwrap_or_default().to_string(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.context(error::InvokeFrontendSnafu);
|
||||||
|
match result {
|
||||||
|
Ok(resp) => {
|
||||||
|
processes.extend(resp.processes);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!(e; "Skipping failing node: {:?}", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processes.extend(self.local_processes(catalog)?);
|
||||||
|
Ok(processes)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kills query with provided catalog and id.
|
||||||
|
pub async fn kill_process(
|
||||||
|
&self,
|
||||||
|
server_addr: String,
|
||||||
|
catalog: String,
|
||||||
|
id: ProcessId,
|
||||||
|
) -> error::Result<bool> {
|
||||||
|
if server_addr == self.server_addr {
|
||||||
|
self.kill_local_process(catalog, id).await
|
||||||
|
} else {
|
||||||
|
let mut nodes = self
|
||||||
|
.frontend_selector
|
||||||
|
.as_ref()
|
||||||
|
.context(error::MetaClientMissingSnafu)?
|
||||||
|
.select(|node| node.peer.addr == server_addr)
|
||||||
|
.await
|
||||||
|
.context(error::InvokeFrontendSnafu)?;
|
||||||
|
ensure!(
|
||||||
|
!nodes.is_empty(),
|
||||||
|
error::FrontendNotFoundSnafu { addr: server_addr }
|
||||||
|
);
|
||||||
|
|
||||||
|
let request = KillProcessRequest {
|
||||||
|
server_addr,
|
||||||
|
catalog,
|
||||||
|
process_id: id,
|
||||||
|
};
|
||||||
|
nodes[0]
|
||||||
|
.kill_process(request)
|
||||||
|
.await
|
||||||
|
.context(error::InvokeFrontendSnafu)?;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kills local query with provided catalog and id.
|
||||||
|
pub async fn kill_local_process(&self, catalog: String, id: ProcessId) -> error::Result<bool> {
|
||||||
|
if let Some(catalogs) = self.catalogs.write().unwrap().get_mut(&catalog) {
|
||||||
|
if let Some(process) = catalogs.remove(&id) {
|
||||||
|
process.handle.cancel();
|
||||||
|
info!(
|
||||||
|
"Killed process, catalog: {}, id: {:?}",
|
||||||
|
process.process.catalog, process.process.id
|
||||||
|
);
|
||||||
|
PROCESS_KILL_COUNT.with_label_values(&[&catalog]).inc();
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
debug!("Failed to kill process, id not found: {}", id);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug!("Failed to kill process, catalog not found: {}", catalog);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Ticket {
|
||||||
|
pub(crate) catalog: String,
|
||||||
|
pub(crate) manager: ProcessManagerRef,
|
||||||
|
pub(crate) id: ProcessId,
|
||||||
|
pub cancellation_handle: Arc<CancellationHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Ticket {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.manager
|
||||||
|
.deregister_query(std::mem::take(&mut self.catalog), self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CancellableProcess {
|
||||||
|
handle: Arc<CancellationHandle>,
|
||||||
|
process: ProcessInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for CancellableProcess {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
PROCESS_LIST_COUNT
|
||||||
|
.with_label_values(&[&self.process.catalog])
|
||||||
|
.dec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CancellableProcess {
|
||||||
|
fn new(handle: Arc<CancellationHandle>, process: ProcessInfo) -> Self {
|
||||||
|
PROCESS_LIST_COUNT
|
||||||
|
.with_label_values(&[&process.catalog])
|
||||||
|
.inc();
|
||||||
|
Self { handle, process }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for CancellableProcess {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CancellableProcess")
|
||||||
|
.field("cancelled", &self.handle.is_cancelled())
|
||||||
|
.field("process", &self.process)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::process_manager::ProcessManager;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_register_query() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
let ticket = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"SELECT * FROM table".to_string(),
|
||||||
|
"".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let running_processes = process_manager.local_processes(None).unwrap();
|
||||||
|
assert_eq!(running_processes.len(), 1);
|
||||||
|
assert_eq!(&running_processes[0].frontend, "127.0.0.1:8000");
|
||||||
|
assert_eq!(running_processes[0].id, ticket.id);
|
||||||
|
assert_eq!(&running_processes[0].query, "SELECT * FROM table");
|
||||||
|
|
||||||
|
drop(ticket);
|
||||||
|
assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_register_query_with_custom_id() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
let custom_id = 12345;
|
||||||
|
|
||||||
|
let ticket = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"SELECT * FROM table".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
Some(custom_id),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(ticket.id, custom_id);
|
||||||
|
|
||||||
|
let running_processes = process_manager.local_processes(None).unwrap();
|
||||||
|
assert_eq!(running_processes.len(), 1);
|
||||||
|
assert_eq!(running_processes[0].id, custom_id);
|
||||||
|
assert_eq!(&running_processes[0].client, "client1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_multiple_queries_same_catalog() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
let ticket1 = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["schema1".to_string()],
|
||||||
|
"SELECT * FROM table1".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ticket2 = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["schema2".to_string()],
|
||||||
|
"SELECT * FROM table2".to_string(),
|
||||||
|
"client2".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let running_processes = process_manager.local_processes(Some("public")).unwrap();
|
||||||
|
assert_eq!(running_processes.len(), 2);
|
||||||
|
|
||||||
|
// Verify both processes are present
|
||||||
|
let ids: Vec<u32> = running_processes.iter().map(|p| p.id).collect();
|
||||||
|
assert!(ids.contains(&ticket1.id));
|
||||||
|
assert!(ids.contains(&ticket2.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_multiple_catalogs() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
let _ticket1 = process_manager.clone().register_query(
|
||||||
|
"catalog1".to_string(),
|
||||||
|
vec!["schema1".to_string()],
|
||||||
|
"SELECT * FROM table1".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ticket2 = process_manager.clone().register_query(
|
||||||
|
"catalog2".to_string(),
|
||||||
|
vec!["schema2".to_string()],
|
||||||
|
"SELECT * FROM table2".to_string(),
|
||||||
|
"client2".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test listing processes for specific catalog
|
||||||
|
let catalog1_processes = process_manager.local_processes(Some("catalog1")).unwrap();
|
||||||
|
assert_eq!(catalog1_processes.len(), 1);
|
||||||
|
assert_eq!(&catalog1_processes[0].catalog, "catalog1");
|
||||||
|
|
||||||
|
let catalog2_processes = process_manager.local_processes(Some("catalog2")).unwrap();
|
||||||
|
assert_eq!(catalog2_processes.len(), 1);
|
||||||
|
assert_eq!(&catalog2_processes[0].catalog, "catalog2");
|
||||||
|
|
||||||
|
// Test listing all processes
|
||||||
|
let all_processes = process_manager.local_processes(None).unwrap();
|
||||||
|
assert_eq!(all_processes.len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_deregister_query() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
let ticket = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"SELECT * FROM table".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert_eq!(process_manager.local_processes(None).unwrap().len(), 1);
|
||||||
|
process_manager.deregister_query("public".to_string(), ticket.id);
|
||||||
|
assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellation_handle() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
let ticket = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"SELECT * FROM table".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!ticket.cancellation_handle.is_cancelled());
|
||||||
|
ticket.cancellation_handle.cancel();
|
||||||
|
assert!(ticket.cancellation_handle.is_cancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_kill_local_process() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
let ticket = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"SELECT * FROM table".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
assert!(!ticket.cancellation_handle.is_cancelled());
|
||||||
|
let killed = process_manager
|
||||||
|
.kill_process(
|
||||||
|
"127.0.0.1:8000".to_string(),
|
||||||
|
"public".to_string(),
|
||||||
|
ticket.id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(killed);
|
||||||
|
assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_kill_nonexistent_process() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
let killed = process_manager
|
||||||
|
.kill_process("127.0.0.1:8000".to_string(), "public".to_string(), 999)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(!killed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_kill_process_nonexistent_catalog() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
let killed = process_manager
|
||||||
|
.kill_process("127.0.0.1:8000".to_string(), "nonexistent".to_string(), 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(!killed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_process_info_fields() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
let _ticket = process_manager.clone().register_query(
|
||||||
|
"test_catalog".to_string(),
|
||||||
|
vec!["schema1".to_string(), "schema2".to_string()],
|
||||||
|
"SELECT COUNT(*) FROM users WHERE age > 18".to_string(),
|
||||||
|
"test_client".to_string(),
|
||||||
|
Some(42),
|
||||||
|
);
|
||||||
|
|
||||||
|
let processes = process_manager.local_processes(None).unwrap();
|
||||||
|
assert_eq!(processes.len(), 1);
|
||||||
|
|
||||||
|
let process = &processes[0];
|
||||||
|
assert_eq!(process.id, 42);
|
||||||
|
assert_eq!(&process.catalog, "test_catalog");
|
||||||
|
assert_eq!(process.schemas, vec!["schema1", "schema2"]);
|
||||||
|
assert_eq!(&process.query, "SELECT COUNT(*) FROM users WHERE age > 18");
|
||||||
|
assert_eq!(&process.client, "test_client");
|
||||||
|
assert_eq!(&process.frontend, "127.0.0.1:8000");
|
||||||
|
assert!(process.start_timestamp > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_ticket_drop_deregisters_process() {
|
||||||
|
let process_manager = Arc::new(ProcessManager::new("127.0.0.1:8000".to_string(), None));
|
||||||
|
|
||||||
|
{
|
||||||
|
let _ticket = process_manager.clone().register_query(
|
||||||
|
"public".to_string(),
|
||||||
|
vec!["test".to_string()],
|
||||||
|
"SELECT * FROM table".to_string(),
|
||||||
|
"client1".to_string(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Process should be registered
|
||||||
|
assert_eq!(process_manager.local_processes(None).unwrap().len(), 1);
|
||||||
|
} // ticket goes out of scope here
|
||||||
|
|
||||||
|
// Process should be automatically deregistered
|
||||||
|
assert_eq!(process_manager.local_processes(None).unwrap().len(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
pub mod information_schema;
|
pub mod information_schema;
|
||||||
mod memory_table;
|
mod memory_table;
|
||||||
pub mod pg_catalog;
|
pub mod pg_catalog;
|
||||||
mod predicate;
|
pub mod predicate;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -96,7 +96,7 @@ trait SystemSchemaProviderInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait SystemTable {
|
pub trait SystemTable {
|
||||||
fn table_id(&self) -> TableId;
|
fn table_id(&self) -> TableId;
|
||||||
|
|
||||||
fn table_name(&self) -> &'static str;
|
fn table_name(&self) -> &'static str;
|
||||||
@@ -110,7 +110,7 @@ pub(crate) trait SystemTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type SystemTableRef = Arc<dyn SystemTable + Send + Sync>;
|
pub type SystemTableRef = Arc<dyn SystemTable + Send + Sync>;
|
||||||
|
|
||||||
struct SystemTableDataSource {
|
struct SystemTableDataSource {
|
||||||
table: SystemTableRef,
|
table: SystemTableRef,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ mod information_memory_table;
|
|||||||
pub mod key_column_usage;
|
pub mod key_column_usage;
|
||||||
mod partitions;
|
mod partitions;
|
||||||
mod procedure_info;
|
mod procedure_info;
|
||||||
|
pub mod process_list;
|
||||||
pub mod region_peers;
|
pub mod region_peers;
|
||||||
mod region_statistics;
|
mod region_statistics;
|
||||||
mod runtime_metrics;
|
mod runtime_metrics;
|
||||||
@@ -37,11 +38,13 @@ use common_meta::cluster::NodeInfo;
|
|||||||
use common_meta::datanode::RegionStat;
|
use common_meta::datanode::RegionStat;
|
||||||
use common_meta::key::flow::flow_state::FlowStat;
|
use common_meta::key::flow::flow_state::FlowStat;
|
||||||
use common_meta::key::flow::FlowMetadataManager;
|
use common_meta::key::flow::FlowMetadataManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
use common_procedure::ProcedureInfo;
|
use common_procedure::ProcedureInfo;
|
||||||
use common_recordbatch::SendableRecordBatchStream;
|
use common_recordbatch::SendableRecordBatchStream;
|
||||||
use datatypes::schema::SchemaRef;
|
use datatypes::schema::SchemaRef;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
|
use process_list::InformationSchemaProcessList;
|
||||||
use store_api::storage::{ScanRequest, TableId};
|
use store_api::storage::{ScanRequest, TableId};
|
||||||
use table::metadata::TableType;
|
use table::metadata::TableType;
|
||||||
use table::TableRef;
|
use table::TableRef;
|
||||||
@@ -50,6 +53,7 @@ use views::InformationSchemaViews;
|
|||||||
|
|
||||||
use self::columns::InformationSchemaColumns;
|
use self::columns::InformationSchemaColumns;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
use crate::process_manager::ProcessManagerRef;
|
||||||
use crate::system_schema::information_schema::cluster_info::InformationSchemaClusterInfo;
|
use crate::system_schema::information_schema::cluster_info::InformationSchemaClusterInfo;
|
||||||
use crate::system_schema::information_schema::flows::InformationSchemaFlows;
|
use crate::system_schema::information_schema::flows::InformationSchemaFlows;
|
||||||
use crate::system_schema::information_schema::information_memory_table::get_schema_columns;
|
use crate::system_schema::information_schema::information_memory_table::get_schema_columns;
|
||||||
@@ -109,12 +113,36 @@ macro_rules! setup_memory_table {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub struct MakeInformationTableRequest {
|
||||||
|
pub catalog_name: String,
|
||||||
|
pub catalog_manager: Weak<dyn CatalogManager>,
|
||||||
|
pub kv_backend: KvBackendRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A factory trait for making information schema tables.
|
||||||
|
///
|
||||||
|
/// This trait allows for extensibility of the information schema by providing
|
||||||
|
/// a way to dynamically create custom information schema tables.
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub trait InformationSchemaTableFactory {
|
||||||
|
fn make_information_table(&self, req: MakeInformationTableRequest) -> SystemTableRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub type InformationSchemaTableFactoryRef = Arc<dyn InformationSchemaTableFactory + Send + Sync>;
|
||||||
|
|
||||||
/// The `information_schema` tables info provider.
|
/// The `information_schema` tables info provider.
|
||||||
pub struct InformationSchemaProvider {
|
pub struct InformationSchemaProvider {
|
||||||
catalog_name: String,
|
catalog_name: String,
|
||||||
catalog_manager: Weak<dyn CatalogManager>,
|
catalog_manager: Weak<dyn CatalogManager>,
|
||||||
|
process_manager: Option<ProcessManagerRef>,
|
||||||
flow_metadata_manager: Arc<FlowMetadataManager>,
|
flow_metadata_manager: Arc<FlowMetadataManager>,
|
||||||
tables: HashMap<String, TableRef>,
|
tables: HashMap<String, TableRef>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
kv_backend: KvBackendRef,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
extra_table_factories: HashMap<String, InformationSchemaTableFactoryRef>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemSchemaProvider for InformationSchemaProvider {
|
impl SystemSchemaProvider for InformationSchemaProvider {
|
||||||
@@ -124,6 +152,7 @@ impl SystemSchemaProvider for InformationSchemaProvider {
|
|||||||
&self.tables
|
&self.tables
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemSchemaProviderInner for InformationSchemaProvider {
|
impl SystemSchemaProviderInner for InformationSchemaProvider {
|
||||||
fn catalog_name(&self) -> &str {
|
fn catalog_name(&self) -> &str {
|
||||||
&self.catalog_name
|
&self.catalog_name
|
||||||
@@ -207,7 +236,26 @@ impl SystemSchemaProviderInner for InformationSchemaProvider {
|
|||||||
self.catalog_manager.clone(),
|
self.catalog_manager.clone(),
|
||||||
),
|
),
|
||||||
) as _),
|
) as _),
|
||||||
_ => None,
|
PROCESS_LIST => self
|
||||||
|
.process_manager
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| Arc::new(InformationSchemaProcessList::new(p.clone())) as _),
|
||||||
|
table_name => {
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
return self.extra_table_factories.get(table_name).map(|factory| {
|
||||||
|
let req = MakeInformationTableRequest {
|
||||||
|
catalog_name: self.catalog_name.clone(),
|
||||||
|
catalog_manager: self.catalog_manager.clone(),
|
||||||
|
kv_backend: self.kv_backend.clone(),
|
||||||
|
};
|
||||||
|
factory.make_information_table(req)
|
||||||
|
});
|
||||||
|
#[cfg(not(feature = "enterprise"))]
|
||||||
|
{
|
||||||
|
let _ = table_name;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,12 +265,18 @@ impl InformationSchemaProvider {
|
|||||||
catalog_name: String,
|
catalog_name: String,
|
||||||
catalog_manager: Weak<dyn CatalogManager>,
|
catalog_manager: Weak<dyn CatalogManager>,
|
||||||
flow_metadata_manager: Arc<FlowMetadataManager>,
|
flow_metadata_manager: Arc<FlowMetadataManager>,
|
||||||
|
process_manager: Option<ProcessManagerRef>,
|
||||||
|
kv_backend: KvBackendRef,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let mut provider = Self {
|
let mut provider = Self {
|
||||||
catalog_name,
|
catalog_name,
|
||||||
catalog_manager,
|
catalog_manager,
|
||||||
flow_metadata_manager,
|
flow_metadata_manager,
|
||||||
|
process_manager,
|
||||||
tables: HashMap::new(),
|
tables: HashMap::new(),
|
||||||
|
kv_backend,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
extra_table_factories: HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
provider.build_tables();
|
provider.build_tables();
|
||||||
@@ -230,6 +284,16 @@ impl InformationSchemaProvider {
|
|||||||
provider
|
provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub(crate) fn with_extra_table_factories(
|
||||||
|
mut self,
|
||||||
|
factories: HashMap<String, InformationSchemaTableFactoryRef>,
|
||||||
|
) -> Self {
|
||||||
|
self.extra_table_factories = factories;
|
||||||
|
self.build_tables();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn build_tables(&mut self) {
|
fn build_tables(&mut self) {
|
||||||
let mut tables = HashMap::new();
|
let mut tables = HashMap::new();
|
||||||
|
|
||||||
@@ -277,16 +341,22 @@ impl InformationSchemaProvider {
|
|||||||
self.build_table(TABLE_CONSTRAINTS).unwrap(),
|
self.build_table(TABLE_CONSTRAINTS).unwrap(),
|
||||||
);
|
);
|
||||||
tables.insert(FLOWS.to_string(), self.build_table(FLOWS).unwrap());
|
tables.insert(FLOWS.to_string(), self.build_table(FLOWS).unwrap());
|
||||||
|
if let Some(process_list) = self.build_table(PROCESS_LIST) {
|
||||||
|
tables.insert(PROCESS_LIST.to_string(), process_list);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
for name in self.extra_table_factories.keys() {
|
||||||
|
tables.insert(name.to_string(), self.build_table(name).expect(name));
|
||||||
|
}
|
||||||
// Add memory tables
|
// Add memory tables
|
||||||
for name in MEMORY_TABLES.iter() {
|
for name in MEMORY_TABLES.iter() {
|
||||||
tables.insert((*name).to_string(), self.build_table(name).expect(name));
|
tables.insert((*name).to_string(), self.build_table(name).expect(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.tables = tables;
|
self.tables = tables;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait InformationTable {
|
pub trait InformationTable {
|
||||||
fn table_id(&self) -> TableId;
|
fn table_id(&self) -> TableId;
|
||||||
|
|
||||||
fn table_name(&self) -> &'static str;
|
fn table_name(&self) -> &'static str;
|
||||||
|
|||||||
189
src/catalog/src/system_schema/information_schema/process_list.rs
Normal file
189
src/catalog/src/system_schema/information_schema/process_list.rs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
// 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::sync::Arc;
|
||||||
|
|
||||||
|
use common_catalog::consts::INFORMATION_SCHEMA_PROCESS_LIST_TABLE_ID;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
use common_frontend::DisplayProcessId;
|
||||||
|
use common_recordbatch::adapter::RecordBatchStreamAdapter;
|
||||||
|
use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
|
||||||
|
use common_time::util::current_time_millis;
|
||||||
|
use common_time::{Duration, Timestamp};
|
||||||
|
use datafusion::physical_plan::stream::RecordBatchStreamAdapter as DfRecordBatchStreamAdapter;
|
||||||
|
use datatypes::prelude::ConcreteDataType as CDT;
|
||||||
|
use datatypes::scalars::ScalarVectorBuilder;
|
||||||
|
use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
|
||||||
|
use datatypes::value::Value;
|
||||||
|
use datatypes::vectors::{
|
||||||
|
DurationMillisecondVectorBuilder, StringVectorBuilder, TimestampMillisecondVectorBuilder,
|
||||||
|
VectorRef,
|
||||||
|
};
|
||||||
|
use snafu::ResultExt;
|
||||||
|
use store_api::storage::{ScanRequest, TableId};
|
||||||
|
|
||||||
|
use crate::error::{self, InternalSnafu};
|
||||||
|
use crate::information_schema::Predicates;
|
||||||
|
use crate::process_manager::ProcessManagerRef;
|
||||||
|
use crate::system_schema::information_schema::InformationTable;
|
||||||
|
|
||||||
|
/// Column names of `information_schema.process_list`
|
||||||
|
pub const ID: &str = "id";
|
||||||
|
pub const CATALOG: &str = "catalog";
|
||||||
|
pub const SCHEMAS: &str = "schemas";
|
||||||
|
pub const QUERY: &str = "query";
|
||||||
|
pub const CLIENT: &str = "client";
|
||||||
|
pub const FRONTEND: &str = "frontend";
|
||||||
|
pub const START_TIMESTAMP: &str = "start_timestamp";
|
||||||
|
pub const ELAPSED_TIME: &str = "elapsed_time";
|
||||||
|
|
||||||
|
/// `information_schema.process_list` table implementation that tracks running
|
||||||
|
/// queries in current cluster.
|
||||||
|
pub struct InformationSchemaProcessList {
|
||||||
|
schema: SchemaRef,
|
||||||
|
process_manager: ProcessManagerRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InformationSchemaProcessList {
|
||||||
|
pub fn new(process_manager: ProcessManagerRef) -> Self {
|
||||||
|
Self {
|
||||||
|
schema: Self::schema(),
|
||||||
|
process_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema() -> SchemaRef {
|
||||||
|
Arc::new(Schema::new(vec![
|
||||||
|
ColumnSchema::new(ID, CDT::string_datatype(), false),
|
||||||
|
ColumnSchema::new(CATALOG, CDT::string_datatype(), false),
|
||||||
|
ColumnSchema::new(SCHEMAS, CDT::string_datatype(), false),
|
||||||
|
ColumnSchema::new(QUERY, CDT::string_datatype(), false),
|
||||||
|
ColumnSchema::new(CLIENT, CDT::string_datatype(), false),
|
||||||
|
ColumnSchema::new(FRONTEND, CDT::string_datatype(), false),
|
||||||
|
ColumnSchema::new(
|
||||||
|
START_TIMESTAMP,
|
||||||
|
CDT::timestamp_millisecond_datatype(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
ColumnSchema::new(ELAPSED_TIME, CDT::duration_millisecond_datatype(), false),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InformationTable for InformationSchemaProcessList {
|
||||||
|
fn table_id(&self) -> TableId {
|
||||||
|
INFORMATION_SCHEMA_PROCESS_LIST_TABLE_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_name(&self) -> &'static str {
|
||||||
|
"process_list"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schema(&self) -> SchemaRef {
|
||||||
|
self.schema.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_stream(&self, request: ScanRequest) -> error::Result<SendableRecordBatchStream> {
|
||||||
|
let process_manager = self.process_manager.clone();
|
||||||
|
let stream = Box::pin(DfRecordBatchStreamAdapter::new(
|
||||||
|
self.schema.arrow_schema().clone(),
|
||||||
|
futures::stream::once(async move {
|
||||||
|
make_process_list(process_manager, request)
|
||||||
|
.await
|
||||||
|
.map(RecordBatch::into_df_record_batch)
|
||||||
|
.map_err(|e| datafusion::error::DataFusionError::External(Box::new(e)))
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(Box::pin(
|
||||||
|
RecordBatchStreamAdapter::try_new(stream)
|
||||||
|
.map_err(BoxedError::new)
|
||||||
|
.context(InternalSnafu)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build running process list.
|
||||||
|
async fn make_process_list(
|
||||||
|
process_manager: ProcessManagerRef,
|
||||||
|
request: ScanRequest,
|
||||||
|
) -> error::Result<RecordBatch> {
|
||||||
|
let predicates = Predicates::from_scan_request(&Some(request));
|
||||||
|
let current_time = current_time_millis();
|
||||||
|
// todo(hl): find a way to extract user catalog to filter queries from other users.
|
||||||
|
let queries = process_manager.list_all_processes(None).await?;
|
||||||
|
|
||||||
|
let mut id_builder = StringVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut catalog_builder = StringVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut schemas_builder = StringVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut query_builder = StringVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut client_builder = StringVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut frontend_builder = StringVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut start_time_builder = TimestampMillisecondVectorBuilder::with_capacity(queries.len());
|
||||||
|
let mut elapsed_time_builder = DurationMillisecondVectorBuilder::with_capacity(queries.len());
|
||||||
|
|
||||||
|
for process in queries {
|
||||||
|
let display_id = DisplayProcessId {
|
||||||
|
server_addr: process.frontend.to_string(),
|
||||||
|
id: process.id,
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
let schemas = process.schemas.join(",");
|
||||||
|
let id = Value::from(display_id);
|
||||||
|
let catalog = Value::from(process.catalog);
|
||||||
|
let schemas = Value::from(schemas);
|
||||||
|
let query = Value::from(process.query);
|
||||||
|
let client = Value::from(process.client);
|
||||||
|
let frontend = Value::from(process.frontend);
|
||||||
|
let start_timestamp = Value::from(Timestamp::new_millisecond(process.start_timestamp));
|
||||||
|
let elapsed_time = Value::from(Duration::new_millisecond(
|
||||||
|
current_time - process.start_timestamp,
|
||||||
|
));
|
||||||
|
let row = [
|
||||||
|
(ID, &id),
|
||||||
|
(CATALOG, &catalog),
|
||||||
|
(SCHEMAS, &schemas),
|
||||||
|
(QUERY, &query),
|
||||||
|
(CLIENT, &client),
|
||||||
|
(FRONTEND, &frontend),
|
||||||
|
(START_TIMESTAMP, &start_timestamp),
|
||||||
|
(ELAPSED_TIME, &elapsed_time),
|
||||||
|
];
|
||||||
|
if predicates.eval(&row) {
|
||||||
|
id_builder.push(id.as_string().as_deref());
|
||||||
|
catalog_builder.push(catalog.as_string().as_deref());
|
||||||
|
schemas_builder.push(schemas.as_string().as_deref());
|
||||||
|
query_builder.push(query.as_string().as_deref());
|
||||||
|
client_builder.push(client.as_string().as_deref());
|
||||||
|
frontend_builder.push(frontend.as_string().as_deref());
|
||||||
|
start_time_builder.push(start_timestamp.as_timestamp().map(|t| t.value().into()));
|
||||||
|
elapsed_time_builder.push(elapsed_time.as_duration().map(|d| d.value().into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordBatch::new(
|
||||||
|
InformationSchemaProcessList::schema(),
|
||||||
|
vec![
|
||||||
|
Arc::new(id_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(catalog_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(schemas_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(query_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(client_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(frontend_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(start_time_builder.finish()) as VectorRef,
|
||||||
|
Arc::new(elapsed_time_builder.finish()) as VectorRef,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.context(error::CreateRecordBatchSnafu)
|
||||||
|
}
|
||||||
@@ -47,3 +47,5 @@ pub const VIEWS: &str = "views";
|
|||||||
pub const FLOWS: &str = "flows";
|
pub const FLOWS: &str = "flows";
|
||||||
pub const PROCEDURE_INFO: &str = "procedure_info";
|
pub const PROCEDURE_INFO: &str = "procedure_info";
|
||||||
pub const REGION_STATISTICS: &str = "region_statistics";
|
pub const REGION_STATISTICS: &str = "region_statistics";
|
||||||
|
pub const PROCESS_LIST: &str = "process_list";
|
||||||
|
pub const TRIGGER_LIST: &str = "trigger_list";
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ mod tests {
|
|||||||
use session::context::QueryContext;
|
use session::context::QueryContext;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::kvbackend::KvBackendCatalogManagerBuilder;
|
||||||
use crate::memory::MemoryCatalogManager;
|
use crate::memory::MemoryCatalogManager;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -323,12 +324,13 @@ mod tests {
|
|||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let catalog_manager = KvBackendCatalogManager::new(
|
let catalog_manager = KvBackendCatalogManagerBuilder::new(
|
||||||
Arc::new(NoopInformationExtension),
|
Arc::new(NoopInformationExtension),
|
||||||
backend.clone(),
|
backend.clone(),
|
||||||
layered_cache_registry,
|
layered_cache_registry,
|
||||||
None,
|
)
|
||||||
);
|
.build();
|
||||||
|
|
||||||
let table_metadata_manager = TableMetadataManager::new(backend);
|
let table_metadata_manager = TableMetadataManager::new(backend);
|
||||||
let mut view_info = common_meta::key::test_utils::new_test_table_info(1024, vec![]);
|
let mut view_info = common_meta::key::test_utils::new_test_table_info(1024, vec![]);
|
||||||
view_info.table_type = TableType::View;
|
view_info.table_type = TableType::View;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ mysql_kvbackend = ["common-meta/mysql_kvbackend", "meta-srv/mysql_kvbackend"]
|
|||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-stream.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
auth.workspace = true
|
auth.workspace = true
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
@@ -42,7 +43,6 @@ common-time.workspace = true
|
|||||||
common-version.workspace = true
|
common-version.workspace = true
|
||||||
common-wal.workspace = true
|
common-wal.workspace = true
|
||||||
datatypes.workspace = true
|
datatypes.workspace = true
|
||||||
either = "1.8"
|
|
||||||
etcd-client.workspace = true
|
etcd-client.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
humantime.workspace = true
|
humantime.workspace = true
|
||||||
@@ -50,6 +50,7 @@ meta-client.workspace = true
|
|||||||
meta-srv.workspace = true
|
meta-srv.workspace = true
|
||||||
nu-ansi-term = "0.46"
|
nu-ansi-term = "0.46"
|
||||||
object-store.workspace = true
|
object-store.workspace = true
|
||||||
|
operator.workspace = true
|
||||||
query.workspace = true
|
query.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
@@ -65,6 +66,7 @@ tokio.workspace = true
|
|||||||
tracing-appender.workspace = true
|
tracing-appender.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
common-meta = { workspace = true, features = ["testing"] }
|
||||||
common-version.workspace = true
|
common-version.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ where
|
|||||||
info!("{desc}, average operation cost: {cost:.2} ms");
|
info!("{desc}, average operation cost: {cost:.2} ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Command to benchmark table metadata operations.
|
||||||
#[derive(Debug, Default, Parser)]
|
#[derive(Debug, Default, Parser)]
|
||||||
pub struct BenchTableMetadataCommand {
|
pub struct BenchTableMetadataCommand {
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
@@ -159,6 +160,7 @@ fn create_table_info(table_id: TableId, table_name: TableName) -> RawTableInfo {
|
|||||||
options: Default::default(),
|
options: Default::default(),
|
||||||
region_numbers: (1..=100).collect(),
|
region_numbers: (1..=100).collect(),
|
||||||
partition_key_indices: vec![],
|
partition_key_indices: vec![],
|
||||||
|
column_ids: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
RawTableInfo {
|
RawTableInfo {
|
||||||
|
|||||||
39
src/cli/src/data.rs
Normal file
39
src/cli/src/data.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
mod export;
|
||||||
|
mod import;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
|
||||||
|
use crate::data::export::ExportCommand;
|
||||||
|
use crate::data::import::ImportCommand;
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// Command for data operations including exporting data from and importing data into GreptimeDB.
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum DataCommand {
|
||||||
|
Export(ExportCommand),
|
||||||
|
Import(ImportCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataCommand {
|
||||||
|
pub async fn build(&self) -> std::result::Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
match self {
|
||||||
|
DataCommand::Export(cmd) => cmd.build().await,
|
||||||
|
DataCommand::Import(cmd) => cmd.build().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,7 @@ enum ExportTarget {
|
|||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Command for exporting data from the GreptimeDB.
|
||||||
#[derive(Debug, Default, Parser)]
|
#[derive(Debug, Default, Parser)]
|
||||||
pub struct ExportCommand {
|
pub struct ExportCommand {
|
||||||
/// Server address to connect
|
/// Server address to connect
|
||||||
@@ -40,6 +40,7 @@ enum ImportTarget {
|
|||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Command to import data from a directory into a GreptimeDB instance.
|
||||||
#[derive(Debug, Default, Parser)]
|
#[derive(Debug, Default, Parser)]
|
||||||
pub struct ImportCommand {
|
pub struct ImportCommand {
|
||||||
/// Server address to connect
|
/// Server address to connect
|
||||||
@@ -17,8 +17,10 @@ use std::any::Any;
|
|||||||
use common_error::ext::{BoxedError, ErrorExt};
|
use common_error::ext::{BoxedError, ErrorExt};
|
||||||
use common_error::status_code::StatusCode;
|
use common_error::status_code::StatusCode;
|
||||||
use common_macro::stack_trace_debug;
|
use common_macro::stack_trace_debug;
|
||||||
|
use common_meta::peer::Peer;
|
||||||
use object_store::Error as ObjectStoreError;
|
use object_store::Error as ObjectStoreError;
|
||||||
use snafu::{Location, Snafu};
|
use snafu::{Location, Snafu};
|
||||||
|
use store_api::storage::TableId;
|
||||||
|
|
||||||
#[derive(Snafu)]
|
#[derive(Snafu)]
|
||||||
#[snafu(visibility(pub))]
|
#[snafu(visibility(pub))]
|
||||||
@@ -30,6 +32,7 @@ pub enum Error {
|
|||||||
location: Location,
|
location: Location,
|
||||||
msg: String,
|
msg: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[snafu(display("Failed to create default catalog and schema"))]
|
#[snafu(display("Failed to create default catalog and schema"))]
|
||||||
InitMetadata {
|
InitMetadata {
|
||||||
#[snafu(implicit)]
|
#[snafu(implicit)]
|
||||||
@@ -72,6 +75,20 @@ pub enum Error {
|
|||||||
source: common_meta::error::Error,
|
source: common_meta::error::Error,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to get table metadata"))]
|
||||||
|
TableMetadata {
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
source: common_meta::error::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Unexpected error: {}", msg))]
|
||||||
|
Unexpected {
|
||||||
|
msg: String,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
#[snafu(display("Missing config, msg: {}", msg))]
|
#[snafu(display("Missing config, msg: {}", msg))]
|
||||||
MissingConfig {
|
MissingConfig {
|
||||||
msg: String,
|
msg: String,
|
||||||
@@ -221,6 +238,13 @@ pub enum Error {
|
|||||||
location: Location,
|
location: Location,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Table not found: {table_id}"))]
|
||||||
|
TableNotFound {
|
||||||
|
table_id: TableId,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
#[snafu(display("OpenDAL operator failed"))]
|
#[snafu(display("OpenDAL operator failed"))]
|
||||||
OpenDal {
|
OpenDal {
|
||||||
#[snafu(implicit)]
|
#[snafu(implicit)]
|
||||||
@@ -228,22 +252,67 @@ pub enum Error {
|
|||||||
#[snafu(source)]
|
#[snafu(source)]
|
||||||
error: ObjectStoreError,
|
error: ObjectStoreError,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[snafu(display("S3 config need be set"))]
|
#[snafu(display("S3 config need be set"))]
|
||||||
S3ConfigNotSet {
|
S3ConfigNotSet {
|
||||||
#[snafu(implicit)]
|
#[snafu(implicit)]
|
||||||
location: Location,
|
location: Location,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[snafu(display("Output directory not set"))]
|
#[snafu(display("Output directory not set"))]
|
||||||
OutputDirNotSet {
|
OutputDirNotSet {
|
||||||
#[snafu(implicit)]
|
#[snafu(implicit)]
|
||||||
location: Location,
|
location: Location,
|
||||||
},
|
},
|
||||||
#[snafu(display("KV backend not set: {}", backend))]
|
|
||||||
KvBackendNotSet {
|
#[snafu(display("Empty store addresses"))]
|
||||||
backend: String,
|
EmptyStoreAddrs {
|
||||||
#[snafu(implicit)]
|
#[snafu(implicit)]
|
||||||
location: Location,
|
location: Location,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Unsupported memory backend"))]
|
||||||
|
UnsupportedMemoryBackend {
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("File path invalid: {}", msg))]
|
||||||
|
InvalidFilePath {
|
||||||
|
msg: String,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Invalid arguments: {}", msg))]
|
||||||
|
InvalidArguments {
|
||||||
|
msg: String,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to init backend"))]
|
||||||
|
InitBackend {
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
#[snafu(source)]
|
||||||
|
error: ObjectStoreError,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Covert column schemas to defs failed"))]
|
||||||
|
CovertColumnSchemasToDefs {
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
source: operator::error::Error,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to send request to datanode: {}", peer))]
|
||||||
|
SendRequestToDatanode {
|
||||||
|
peer: Peer,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
source: common_meta::error::Error,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -251,9 +320,9 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
impl ErrorExt for Error {
|
impl ErrorExt for Error {
|
||||||
fn status_code(&self) -> StatusCode {
|
fn status_code(&self) -> StatusCode {
|
||||||
match self {
|
match self {
|
||||||
Error::InitMetadata { source, .. } | Error::InitDdlManager { source, .. } => {
|
Error::InitMetadata { source, .. }
|
||||||
source.status_code()
|
| Error::InitDdlManager { source, .. }
|
||||||
}
|
| Error::TableMetadata { source, .. } => source.status_code(),
|
||||||
|
|
||||||
Error::MissingConfig { .. }
|
Error::MissingConfig { .. }
|
||||||
| Error::LoadLayeredConfig { .. }
|
| Error::LoadLayeredConfig { .. }
|
||||||
@@ -262,8 +331,14 @@ impl ErrorExt for Error {
|
|||||||
| Error::ConnectEtcd { .. }
|
| Error::ConnectEtcd { .. }
|
||||||
| Error::CreateDir { .. }
|
| Error::CreateDir { .. }
|
||||||
| Error::EmptyResult { .. }
|
| Error::EmptyResult { .. }
|
||||||
|
| Error::InvalidFilePath { .. }
|
||||||
|
| Error::UnsupportedMemoryBackend { .. }
|
||||||
|
| Error::InvalidArguments { .. }
|
||||||
| Error::ParseProxyOpts { .. } => StatusCode::InvalidArguments,
|
| Error::ParseProxyOpts { .. } => StatusCode::InvalidArguments,
|
||||||
|
|
||||||
|
Error::CovertColumnSchemasToDefs { source, .. } => source.status_code(),
|
||||||
|
Error::SendRequestToDatanode { source, .. } => source.status_code(),
|
||||||
|
|
||||||
Error::StartProcedureManager { source, .. }
|
Error::StartProcedureManager { source, .. }
|
||||||
| Error::StopProcedureManager { source, .. } => source.status_code(),
|
| Error::StopProcedureManager { source, .. } => source.status_code(),
|
||||||
Error::StartWalOptionsAllocator { source, .. } => source.status_code(),
|
Error::StartWalOptionsAllocator { source, .. } => source.status_code(),
|
||||||
@@ -271,6 +346,7 @@ impl ErrorExt for Error {
|
|||||||
Error::ParseSql { source, .. } | Error::PlanStatement { source, .. } => {
|
Error::ParseSql { source, .. } | Error::PlanStatement { source, .. } => {
|
||||||
source.status_code()
|
source.status_code()
|
||||||
}
|
}
|
||||||
|
Error::Unexpected { .. } => StatusCode::Unexpected,
|
||||||
|
|
||||||
Error::SerdeJson { .. }
|
Error::SerdeJson { .. }
|
||||||
| Error::FileIo { .. }
|
| Error::FileIo { .. }
|
||||||
@@ -279,15 +355,16 @@ impl ErrorExt for Error {
|
|||||||
| Error::BuildClient { .. } => StatusCode::Unexpected,
|
| Error::BuildClient { .. } => StatusCode::Unexpected,
|
||||||
|
|
||||||
Error::Other { source, .. } => source.status_code(),
|
Error::Other { source, .. } => source.status_code(),
|
||||||
Error::OpenDal { .. } => StatusCode::Internal,
|
Error::OpenDal { .. } | Error::InitBackend { .. } => StatusCode::Internal,
|
||||||
Error::S3ConfigNotSet { .. }
|
Error::S3ConfigNotSet { .. }
|
||||||
| Error::OutputDirNotSet { .. }
|
| Error::OutputDirNotSet { .. }
|
||||||
| Error::KvBackendNotSet { .. } => StatusCode::InvalidArguments,
|
| Error::EmptyStoreAddrs { .. } => StatusCode::InvalidArguments,
|
||||||
|
|
||||||
Error::BuildRuntime { source, .. } => source.status_code(),
|
Error::BuildRuntime { source, .. } => source.status_code(),
|
||||||
|
|
||||||
Error::CacheRequired { .. } | Error::BuildCacheRegistry { .. } => StatusCode::Internal,
|
Error::CacheRequired { .. } | Error::BuildCacheRegistry { .. } => StatusCode::Internal,
|
||||||
Error::MetaClientInit { source, .. } => source.status_code(),
|
Error::MetaClientInit { source, .. } => source.status_code(),
|
||||||
|
Error::TableNotFound { .. } => StatusCode::TableNotFound,
|
||||||
Error::SchemaNotFound { .. } => StatusCode::DatabaseNotFound,
|
Error::SchemaNotFound { .. } => StatusCode::DatabaseNotFound,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
// 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.
|
||||||
|
|
||||||
|
#![allow(clippy::print_stdout)]
|
||||||
mod bench;
|
mod bench;
|
||||||
|
mod data;
|
||||||
mod database;
|
mod database;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod export;
|
mod metadata;
|
||||||
mod import;
|
|
||||||
mod meta_snapshot;
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
@@ -26,9 +26,8 @@ pub use database::DatabaseClient;
|
|||||||
use error::Result;
|
use error::Result;
|
||||||
|
|
||||||
pub use crate::bench::BenchTableMetadataCommand;
|
pub use crate::bench::BenchTableMetadataCommand;
|
||||||
pub use crate::export::ExportCommand;
|
pub use crate::data::DataCommand;
|
||||||
pub use crate::import::ImportCommand;
|
pub use crate::metadata::MetadataCommand;
|
||||||
pub use crate::meta_snapshot::{MetaRestoreCommand, MetaSnapshotCommand};
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait Tool: Send + Sync {
|
pub trait Tool: Send + Sync {
|
||||||
|
|||||||
52
src/cli/src/metadata.rs
Normal file
52
src/cli/src/metadata.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
mod control;
|
||||||
|
mod repair;
|
||||||
|
mod snapshot;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
|
||||||
|
use crate::metadata::control::{DelCommand, GetCommand};
|
||||||
|
use crate::metadata::repair::RepairLogicalTablesCommand;
|
||||||
|
use crate::metadata::snapshot::SnapshotCommand;
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// Command for managing metadata operations,
|
||||||
|
/// including saving and restoring metadata snapshots,
|
||||||
|
/// controlling metadata operations, and diagnosing and repairing metadata.
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum MetadataCommand {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Snapshot(SnapshotCommand),
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Get(GetCommand),
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Del(DelCommand),
|
||||||
|
RepairLogicalTables(RepairLogicalTablesCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetadataCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
match self {
|
||||||
|
MetadataCommand::Snapshot(cmd) => cmd.build().await,
|
||||||
|
MetadataCommand::RepairLogicalTables(cmd) => cmd.build().await,
|
||||||
|
MetadataCommand::Get(cmd) => cmd.build().await,
|
||||||
|
MetadataCommand::Del(cmd) => cmd.build().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/cli/src/metadata/common.rs
Normal file
116
src/cli/src/metadata/common.rs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// 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::sync::Arc;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
use common_meta::kv_backend::chroot::ChrootKvBackend;
|
||||||
|
use common_meta::kv_backend::etcd::EtcdStore;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use meta_srv::bootstrap::create_etcd_client;
|
||||||
|
use meta_srv::metasrv::BackendImpl;
|
||||||
|
|
||||||
|
use crate::error::{EmptyStoreAddrsSnafu, UnsupportedMemoryBackendSnafu};
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub(crate) struct StoreConfig {
|
||||||
|
/// The endpoint of store. one of etcd, postgres or mysql.
|
||||||
|
///
|
||||||
|
/// For postgres store, the format is:
|
||||||
|
/// "password=password dbname=postgres user=postgres host=localhost port=5432"
|
||||||
|
///
|
||||||
|
/// For etcd store, the format is:
|
||||||
|
/// "127.0.0.1:2379"
|
||||||
|
///
|
||||||
|
/// For mysql store, the format is:
|
||||||
|
/// "mysql://user:password@ip:port/dbname"
|
||||||
|
#[clap(long, alias = "store-addr", value_delimiter = ',', num_args = 1..)]
|
||||||
|
store_addrs: Vec<String>,
|
||||||
|
|
||||||
|
/// The maximum number of operations in a transaction. Only used when using [etcd-store].
|
||||||
|
#[clap(long, default_value = "128")]
|
||||||
|
max_txn_ops: usize,
|
||||||
|
|
||||||
|
/// The metadata store backend.
|
||||||
|
#[clap(long, value_enum, default_value = "etcd-store")]
|
||||||
|
backend: BackendImpl,
|
||||||
|
|
||||||
|
/// The key prefix of the metadata store.
|
||||||
|
#[clap(long, default_value = "")]
|
||||||
|
store_key_prefix: String,
|
||||||
|
|
||||||
|
/// The table name in RDS to store metadata. Only used when using [postgres-store] or [mysql-store].
|
||||||
|
#[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))]
|
||||||
|
#[clap(long, default_value = common_meta::kv_backend::DEFAULT_META_TABLE_NAME)]
|
||||||
|
meta_table_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoreConfig {
|
||||||
|
/// Builds a [`KvBackendRef`] from the store configuration.
|
||||||
|
pub async fn build(&self) -> Result<KvBackendRef, BoxedError> {
|
||||||
|
let max_txn_ops = self.max_txn_ops;
|
||||||
|
let store_addrs = &self.store_addrs;
|
||||||
|
if store_addrs.is_empty() {
|
||||||
|
EmptyStoreAddrsSnafu.fail().map_err(BoxedError::new)
|
||||||
|
} else {
|
||||||
|
let kvbackend = match self.backend {
|
||||||
|
BackendImpl::EtcdStore => {
|
||||||
|
let etcd_client = create_etcd_client(store_addrs)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
Ok(EtcdStore::with_etcd_client(etcd_client, max_txn_ops))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "pg_kvbackend")]
|
||||||
|
BackendImpl::PostgresStore => {
|
||||||
|
let table_name = &self.meta_table_name;
|
||||||
|
let pool = meta_srv::bootstrap::create_postgres_pool(store_addrs)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
Ok(common_meta::kv_backend::rds::PgStore::with_pg_pool(
|
||||||
|
pool,
|
||||||
|
table_name,
|
||||||
|
max_txn_ops,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "mysql_kvbackend")]
|
||||||
|
BackendImpl::MysqlStore => {
|
||||||
|
let table_name = &self.meta_table_name;
|
||||||
|
let pool = meta_srv::bootstrap::create_mysql_pool(store_addrs)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
Ok(common_meta::kv_backend::rds::MySqlStore::with_mysql_pool(
|
||||||
|
pool,
|
||||||
|
table_name,
|
||||||
|
max_txn_ops,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?)
|
||||||
|
}
|
||||||
|
BackendImpl::MemoryStore => UnsupportedMemoryBackendSnafu
|
||||||
|
.fail()
|
||||||
|
.map_err(BoxedError::new),
|
||||||
|
};
|
||||||
|
if self.store_key_prefix.is_empty() {
|
||||||
|
kvbackend
|
||||||
|
} else {
|
||||||
|
let chroot_kvbackend =
|
||||||
|
ChrootKvBackend::new(self.store_key_prefix.as_bytes().to_vec(), kvbackend?);
|
||||||
|
Ok(Arc::new(chroot_kvbackend))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/cli/src/metadata/control.rs
Normal file
22
src/cli/src/metadata/control.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
mod del;
|
||||||
|
mod get;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_utils;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
pub(crate) use del::DelCommand;
|
||||||
|
pub(crate) use get::GetCommand;
|
||||||
42
src/cli/src/metadata/control/del.rs
Normal file
42
src/cli/src/metadata/control/del.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
mod key;
|
||||||
|
mod table;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
|
||||||
|
use crate::metadata::control::del::key::DelKeyCommand;
|
||||||
|
use crate::metadata::control::del::table::DelTableCommand;
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// The prefix of the tombstone keys.
|
||||||
|
pub(crate) const CLI_TOMBSTONE_PREFIX: &str = "__cli_tombstone/";
|
||||||
|
|
||||||
|
/// Subcommand for deleting metadata from the metadata store.
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum DelCommand {
|
||||||
|
Key(DelKeyCommand),
|
||||||
|
Table(DelTableCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DelCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
match self {
|
||||||
|
DelCommand::Key(cmd) => cmd.build().await,
|
||||||
|
DelCommand::Table(cmd) => cmd.build().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/cli/src/metadata/control/del/key.rs
Normal file
132
src/cli/src/metadata/control/del/key.rs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// 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 async_trait::async_trait;
|
||||||
|
use clap::Parser;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
use common_meta::key::tombstone::TombstoneManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use common_meta::rpc::store::RangeRequest;
|
||||||
|
|
||||||
|
use crate::metadata::common::StoreConfig;
|
||||||
|
use crate::metadata::control::del::CLI_TOMBSTONE_PREFIX;
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// Delete key-value pairs logically from the metadata store.
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub struct DelKeyCommand {
|
||||||
|
/// The key to delete from the metadata store.
|
||||||
|
key: String,
|
||||||
|
|
||||||
|
/// Delete key-value pairs with the given prefix.
|
||||||
|
#[clap(long)]
|
||||||
|
prefix: bool,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
store: StoreConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DelKeyCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
let kv_backend = self.store.build().await?;
|
||||||
|
Ok(Box::new(DelKeyTool {
|
||||||
|
key: self.key.to_string(),
|
||||||
|
prefix: self.prefix,
|
||||||
|
key_deleter: KeyDeleter::new(kv_backend),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeyDeleter {
|
||||||
|
kv_backend: KvBackendRef,
|
||||||
|
tombstone_manager: TombstoneManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyDeleter {
|
||||||
|
fn new(kv_backend: KvBackendRef) -> Self {
|
||||||
|
Self {
|
||||||
|
kv_backend: kv_backend.clone(),
|
||||||
|
tombstone_manager: TombstoneManager::new_with_prefix(kv_backend, CLI_TOMBSTONE_PREFIX),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, key: &str, prefix: bool) -> Result<usize, BoxedError> {
|
||||||
|
let mut req = RangeRequest::default().with_keys_only();
|
||||||
|
if prefix {
|
||||||
|
req = req.with_prefix(key.as_bytes());
|
||||||
|
} else {
|
||||||
|
req = req.with_key(key.as_bytes());
|
||||||
|
}
|
||||||
|
let resp = self.kv_backend.range(req).await.map_err(BoxedError::new)?;
|
||||||
|
let keys = resp.kvs.iter().map(|kv| kv.key.clone()).collect::<Vec<_>>();
|
||||||
|
self.tombstone_manager
|
||||||
|
.create(keys)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DelKeyTool {
|
||||||
|
key: String,
|
||||||
|
prefix: bool,
|
||||||
|
key_deleter: KeyDeleter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tool for DelKeyTool {
|
||||||
|
async fn do_work(&self) -> Result<(), BoxedError> {
|
||||||
|
let deleted = self.key_deleter.delete(&self.key, self.prefix).await?;
|
||||||
|
// Print the number of deleted keys.
|
||||||
|
println!("{}", deleted);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use common_meta::kv_backend::chroot::ChrootKvBackend;
|
||||||
|
use common_meta::kv_backend::memory::MemoryKvBackend;
|
||||||
|
use common_meta::kv_backend::{KvBackend, KvBackendRef};
|
||||||
|
use common_meta::rpc::store::RangeRequest;
|
||||||
|
|
||||||
|
use crate::metadata::control::del::key::KeyDeleter;
|
||||||
|
use crate::metadata::control::del::CLI_TOMBSTONE_PREFIX;
|
||||||
|
use crate::metadata::control::test_utils::put_key;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_keys() {
|
||||||
|
let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
|
||||||
|
let key_deleter = KeyDeleter::new(kv_backend.clone());
|
||||||
|
put_key(&kv_backend, "foo", "bar").await;
|
||||||
|
put_key(&kv_backend, "foo/bar", "baz").await;
|
||||||
|
put_key(&kv_backend, "foo/baz", "qux").await;
|
||||||
|
let deleted = key_deleter.delete("foo", true).await.unwrap();
|
||||||
|
assert_eq!(deleted, 3);
|
||||||
|
let deleted = key_deleter.delete("foo/bar", false).await.unwrap();
|
||||||
|
assert_eq!(deleted, 0);
|
||||||
|
|
||||||
|
let chroot = ChrootKvBackend::new(CLI_TOMBSTONE_PREFIX.as_bytes().to_vec(), kv_backend);
|
||||||
|
let req = RangeRequest::default().with_prefix(b"foo");
|
||||||
|
let resp = chroot.range(req).await.unwrap();
|
||||||
|
assert_eq!(resp.kvs.len(), 3);
|
||||||
|
assert_eq!(resp.kvs[0].key, b"foo");
|
||||||
|
assert_eq!(resp.kvs[0].value, b"bar");
|
||||||
|
assert_eq!(resp.kvs[1].key, b"foo/bar");
|
||||||
|
assert_eq!(resp.kvs[1].value, b"baz");
|
||||||
|
assert_eq!(resp.kvs[2].key, b"foo/baz");
|
||||||
|
assert_eq!(resp.kvs[2].value, b"qux");
|
||||||
|
}
|
||||||
|
}
|
||||||
235
src/cli/src/metadata/control/del/table.rs
Normal file
235
src/cli/src/metadata/control/del/table.rs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
// 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 async_trait::async_trait;
|
||||||
|
use clap::Parser;
|
||||||
|
use client::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||||
|
use common_catalog::format_full_table_name;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
use common_meta::ddl::utils::get_region_wal_options;
|
||||||
|
use common_meta::key::table_name::TableNameManager;
|
||||||
|
use common_meta::key::TableMetadataManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use store_api::storage::TableId;
|
||||||
|
|
||||||
|
use crate::error::{InvalidArgumentsSnafu, TableNotFoundSnafu};
|
||||||
|
use crate::metadata::common::StoreConfig;
|
||||||
|
use crate::metadata::control::del::CLI_TOMBSTONE_PREFIX;
|
||||||
|
use crate::metadata::control::utils::get_table_id_by_name;
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// Delete table metadata logically from the metadata store.
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub struct DelTableCommand {
|
||||||
|
/// The table id to delete from the metadata store.
|
||||||
|
#[clap(long)]
|
||||||
|
table_id: Option<u32>,
|
||||||
|
|
||||||
|
/// The table name to delete from the metadata store.
|
||||||
|
#[clap(long)]
|
||||||
|
table_name: Option<String>,
|
||||||
|
|
||||||
|
/// The schema name of the table.
|
||||||
|
#[clap(long, default_value = DEFAULT_SCHEMA_NAME)]
|
||||||
|
schema_name: String,
|
||||||
|
|
||||||
|
/// The catalog name of the table.
|
||||||
|
#[clap(long, default_value = DEFAULT_CATALOG_NAME)]
|
||||||
|
catalog_name: String,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
store: StoreConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DelTableCommand {
|
||||||
|
fn validate(&self) -> Result<(), BoxedError> {
|
||||||
|
if matches!(
|
||||||
|
(&self.table_id, &self.table_name),
|
||||||
|
(Some(_), Some(_)) | (None, None)
|
||||||
|
) {
|
||||||
|
return Err(BoxedError::new(
|
||||||
|
InvalidArgumentsSnafu {
|
||||||
|
msg: "You must specify either --table-id or --table-name.",
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DelTableCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
self.validate()?;
|
||||||
|
let kv_backend = self.store.build().await?;
|
||||||
|
Ok(Box::new(DelTableTool {
|
||||||
|
table_id: self.table_id,
|
||||||
|
table_name: self.table_name.clone(),
|
||||||
|
schema_name: self.schema_name.clone(),
|
||||||
|
catalog_name: self.catalog_name.clone(),
|
||||||
|
table_name_manager: TableNameManager::new(kv_backend.clone()),
|
||||||
|
table_metadata_deleter: TableMetadataDeleter::new(kv_backend),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DelTableTool {
|
||||||
|
table_id: Option<u32>,
|
||||||
|
table_name: Option<String>,
|
||||||
|
schema_name: String,
|
||||||
|
catalog_name: String,
|
||||||
|
table_name_manager: TableNameManager,
|
||||||
|
table_metadata_deleter: TableMetadataDeleter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tool for DelTableTool {
|
||||||
|
async fn do_work(&self) -> Result<(), BoxedError> {
|
||||||
|
let table_id = if let Some(table_name) = &self.table_name {
|
||||||
|
let catalog_name = &self.catalog_name;
|
||||||
|
let schema_name = &self.schema_name;
|
||||||
|
|
||||||
|
let Some(table_id) = get_table_id_by_name(
|
||||||
|
&self.table_name_manager,
|
||||||
|
catalog_name,
|
||||||
|
schema_name,
|
||||||
|
table_name,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
println!(
|
||||||
|
"Table({}) not found",
|
||||||
|
format_full_table_name(catalog_name, schema_name, table_name)
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
table_id
|
||||||
|
} else {
|
||||||
|
// Safety: we have validated that table_id or table_name is not None
|
||||||
|
self.table_id.unwrap()
|
||||||
|
};
|
||||||
|
self.table_metadata_deleter.delete(table_id).await?;
|
||||||
|
println!("Table({}) deleted", table_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TableMetadataDeleter {
|
||||||
|
table_metadata_manager: TableMetadataManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableMetadataDeleter {
|
||||||
|
fn new(kv_backend: KvBackendRef) -> Self {
|
||||||
|
Self {
|
||||||
|
table_metadata_manager: TableMetadataManager::new_with_custom_tombstone_prefix(
|
||||||
|
kv_backend,
|
||||||
|
CLI_TOMBSTONE_PREFIX,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete(&self, table_id: TableId) -> Result<(), BoxedError> {
|
||||||
|
let (table_info, table_route) = self
|
||||||
|
.table_metadata_manager
|
||||||
|
.get_full_table_info(table_id)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
let Some(table_info) = table_info else {
|
||||||
|
return Err(BoxedError::new(TableNotFoundSnafu { table_id }.build()));
|
||||||
|
};
|
||||||
|
let Some(table_route) = table_route else {
|
||||||
|
return Err(BoxedError::new(TableNotFoundSnafu { table_id }.build()));
|
||||||
|
};
|
||||||
|
let physical_table_id = self
|
||||||
|
.table_metadata_manager
|
||||||
|
.table_route_manager()
|
||||||
|
.get_physical_table_id(table_id)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
|
||||||
|
let table_name = table_info.table_name();
|
||||||
|
let region_wal_options = get_region_wal_options(
|
||||||
|
&self.table_metadata_manager,
|
||||||
|
&table_route,
|
||||||
|
physical_table_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
|
||||||
|
self.table_metadata_manager
|
||||||
|
.delete_table_metadata(table_id, &table_name, &table_route, ®ion_wal_options)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use common_error::ext::ErrorExt;
|
||||||
|
use common_error::status_code::StatusCode;
|
||||||
|
use common_meta::key::table_route::TableRouteValue;
|
||||||
|
use common_meta::key::TableMetadataManager;
|
||||||
|
use common_meta::kv_backend::chroot::ChrootKvBackend;
|
||||||
|
use common_meta::kv_backend::memory::MemoryKvBackend;
|
||||||
|
use common_meta::kv_backend::{KvBackend, KvBackendRef};
|
||||||
|
use common_meta::rpc::store::RangeRequest;
|
||||||
|
|
||||||
|
use crate::metadata::control::del::table::TableMetadataDeleter;
|
||||||
|
use crate::metadata::control::del::CLI_TOMBSTONE_PREFIX;
|
||||||
|
use crate::metadata::control::test_utils::prepare_physical_table_metadata;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_table_not_found() {
|
||||||
|
let kv_backend = Arc::new(MemoryKvBackend::new()) as KvBackendRef;
|
||||||
|
|
||||||
|
let table_metadata_deleter = TableMetadataDeleter::new(kv_backend);
|
||||||
|
let table_id = 1;
|
||||||
|
let err = table_metadata_deleter.delete(table_id).await.unwrap_err();
|
||||||
|
assert_eq!(err.status_code(), StatusCode::TableNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_delete_table_metadata() {
|
||||||
|
let kv_backend = Arc::new(MemoryKvBackend::new());
|
||||||
|
let table_metadata_manager = TableMetadataManager::new(kv_backend.clone());
|
||||||
|
let table_id = 1024;
|
||||||
|
let (table_info, table_route) = prepare_physical_table_metadata("my_table", table_id).await;
|
||||||
|
table_metadata_manager
|
||||||
|
.create_table_metadata(
|
||||||
|
table_info,
|
||||||
|
TableRouteValue::Physical(table_route),
|
||||||
|
HashMap::new(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let total_keys = kv_backend.len();
|
||||||
|
assert!(total_keys > 0);
|
||||||
|
|
||||||
|
let table_metadata_deleter = TableMetadataDeleter::new(kv_backend.clone());
|
||||||
|
table_metadata_deleter.delete(table_id).await.unwrap();
|
||||||
|
|
||||||
|
// Check the tombstone keys are deleted
|
||||||
|
let chroot =
|
||||||
|
ChrootKvBackend::new(CLI_TOMBSTONE_PREFIX.as_bytes().to_vec(), kv_backend.clone());
|
||||||
|
let req = RangeRequest::default().with_range(vec![0], vec![0]);
|
||||||
|
let resp = chroot.range(req).await.unwrap();
|
||||||
|
assert_eq!(resp.kvs.len(), total_keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
247
src/cli/src/metadata/control/get.rs
Normal file
247
src/cli/src/metadata/control/get.rs
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
// 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::cmp::min;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use client::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||||
|
use common_catalog::format_full_table_name;
|
||||||
|
use common_error::ext::BoxedError;
|
||||||
|
use common_meta::key::table_info::TableInfoKey;
|
||||||
|
use common_meta::key::table_route::TableRouteKey;
|
||||||
|
use common_meta::key::TableMetadataManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use common_meta::range_stream::{PaginationStream, DEFAULT_PAGE_SIZE};
|
||||||
|
use common_meta::rpc::store::RangeRequest;
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
|
||||||
|
use crate::error::InvalidArgumentsSnafu;
|
||||||
|
use crate::metadata::common::StoreConfig;
|
||||||
|
use crate::metadata::control::utils::{decode_key_value, get_table_id_by_name, json_fromatter};
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// Getting metadata from metadata store.
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum GetCommand {
|
||||||
|
Key(GetKeyCommand),
|
||||||
|
Table(GetTableCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
match self {
|
||||||
|
GetCommand::Key(cmd) => cmd.build().await,
|
||||||
|
GetCommand::Table(cmd) => cmd.build().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get key-value pairs from the metadata store.
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub struct GetKeyCommand {
|
||||||
|
/// The key to get from the metadata store.
|
||||||
|
#[clap(default_value = "")]
|
||||||
|
key: String,
|
||||||
|
|
||||||
|
/// Whether to perform a prefix query. If true, returns all key-value pairs where the key starts with the given prefix.
|
||||||
|
#[clap(long, default_value = "false")]
|
||||||
|
prefix: bool,
|
||||||
|
|
||||||
|
/// The maximum number of key-value pairs to return. If 0, returns all key-value pairs.
|
||||||
|
#[clap(long, default_value = "0")]
|
||||||
|
limit: u64,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
store: StoreConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetKeyCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
let kvbackend = self.store.build().await?;
|
||||||
|
Ok(Box::new(GetKeyTool {
|
||||||
|
kvbackend,
|
||||||
|
key: self.key.clone(),
|
||||||
|
prefix: self.prefix,
|
||||||
|
limit: self.limit,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GetKeyTool {
|
||||||
|
kvbackend: KvBackendRef,
|
||||||
|
key: String,
|
||||||
|
prefix: bool,
|
||||||
|
limit: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tool for GetKeyTool {
|
||||||
|
async fn do_work(&self) -> Result<(), BoxedError> {
|
||||||
|
let mut req = RangeRequest::default();
|
||||||
|
if self.prefix {
|
||||||
|
req = req.with_prefix(self.key.as_bytes());
|
||||||
|
} else {
|
||||||
|
req = req.with_key(self.key.as_bytes());
|
||||||
|
}
|
||||||
|
let page_size = if self.limit > 0 {
|
||||||
|
min(self.limit as usize, DEFAULT_PAGE_SIZE)
|
||||||
|
} else {
|
||||||
|
DEFAULT_PAGE_SIZE
|
||||||
|
};
|
||||||
|
let pagination_stream =
|
||||||
|
PaginationStream::new(self.kvbackend.clone(), req, page_size, decode_key_value);
|
||||||
|
let mut stream = Box::pin(pagination_stream.into_stream());
|
||||||
|
let mut counter = 0;
|
||||||
|
|
||||||
|
while let Some((key, value)) = stream.try_next().await.map_err(BoxedError::new)? {
|
||||||
|
print!("{}\n{}\n", key, value);
|
||||||
|
counter += 1;
|
||||||
|
if self.limit > 0 && counter >= self.limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get table metadata from the metadata store via table id.
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub struct GetTableCommand {
|
||||||
|
/// Get table metadata by table id.
|
||||||
|
#[clap(long)]
|
||||||
|
table_id: Option<u32>,
|
||||||
|
|
||||||
|
/// Get table metadata by table name.
|
||||||
|
#[clap(long)]
|
||||||
|
table_name: Option<String>,
|
||||||
|
|
||||||
|
/// The schema name of the table.
|
||||||
|
#[clap(long, default_value = DEFAULT_SCHEMA_NAME)]
|
||||||
|
schema_name: String,
|
||||||
|
|
||||||
|
/// The catalog name of the table.
|
||||||
|
#[clap(long, default_value = DEFAULT_CATALOG_NAME)]
|
||||||
|
catalog_name: String,
|
||||||
|
|
||||||
|
/// Pretty print the output.
|
||||||
|
#[clap(long, default_value = "false")]
|
||||||
|
pretty: bool,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
store: StoreConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTableCommand {
|
||||||
|
pub fn validate(&self) -> Result<(), BoxedError> {
|
||||||
|
if matches!(
|
||||||
|
(&self.table_id, &self.table_name),
|
||||||
|
(Some(_), Some(_)) | (None, None)
|
||||||
|
) {
|
||||||
|
return Err(BoxedError::new(
|
||||||
|
InvalidArgumentsSnafu {
|
||||||
|
msg: "You must specify either --table-id or --table-name.",
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GetTableTool {
|
||||||
|
kvbackend: KvBackendRef,
|
||||||
|
table_id: Option<u32>,
|
||||||
|
table_name: Option<String>,
|
||||||
|
schema_name: String,
|
||||||
|
catalog_name: String,
|
||||||
|
pretty: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tool for GetTableTool {
|
||||||
|
async fn do_work(&self) -> Result<(), BoxedError> {
|
||||||
|
let table_metadata_manager = TableMetadataManager::new(self.kvbackend.clone());
|
||||||
|
let table_name_manager = table_metadata_manager.table_name_manager();
|
||||||
|
let table_info_manager = table_metadata_manager.table_info_manager();
|
||||||
|
let table_route_manager = table_metadata_manager.table_route_manager();
|
||||||
|
|
||||||
|
let table_id = if let Some(table_name) = &self.table_name {
|
||||||
|
let catalog_name = &self.catalog_name;
|
||||||
|
let schema_name = &self.schema_name;
|
||||||
|
|
||||||
|
let Some(table_id) =
|
||||||
|
get_table_id_by_name(table_name_manager, catalog_name, schema_name, table_name)
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
println!(
|
||||||
|
"Table({}) not found",
|
||||||
|
format_full_table_name(catalog_name, schema_name, table_name)
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
table_id
|
||||||
|
} else {
|
||||||
|
// Safety: we have validated that table_id or table_name is not None
|
||||||
|
self.table_id.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let table_info = table_info_manager
|
||||||
|
.get(table_id)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
if let Some(table_info) = table_info {
|
||||||
|
println!(
|
||||||
|
"{}\n{}",
|
||||||
|
TableInfoKey::new(table_id),
|
||||||
|
json_fromatter(self.pretty, &*table_info)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("Table info not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
let table_route = table_route_manager
|
||||||
|
.table_route_storage()
|
||||||
|
.get(table_id)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
if let Some(table_route) = table_route {
|
||||||
|
println!(
|
||||||
|
"{}\n{}",
|
||||||
|
TableRouteKey::new(table_id),
|
||||||
|
json_fromatter(self.pretty, &table_route)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("Table route not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetTableCommand {
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
self.validate()?;
|
||||||
|
let kvbackend = self.store.build().await?;
|
||||||
|
Ok(Box::new(GetTableTool {
|
||||||
|
kvbackend,
|
||||||
|
table_id: self.table_id,
|
||||||
|
table_name: self.table_name.clone(),
|
||||||
|
schema_name: self.schema_name.clone(),
|
||||||
|
catalog_name: self.catalog_name.clone(),
|
||||||
|
pretty: self.pretty,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/cli/src/metadata/control/test_utils.rs
Normal file
51
src/cli/src/metadata/control/test_utils.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// 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 common_meta::ddl::test_util::test_create_physical_table_task;
|
||||||
|
use common_meta::key::table_route::PhysicalTableRouteValue;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use common_meta::peer::Peer;
|
||||||
|
use common_meta::rpc::router::{Region, RegionRoute};
|
||||||
|
use common_meta::rpc::store::PutRequest;
|
||||||
|
use store_api::storage::{RegionId, TableId};
|
||||||
|
use table::metadata::RawTableInfo;
|
||||||
|
|
||||||
|
/// Puts a key-value pair into the kv backend.
|
||||||
|
pub async fn put_key(kv_backend: &KvBackendRef, key: &str, value: &str) {
|
||||||
|
let put_req = PutRequest::new()
|
||||||
|
.with_key(key.as_bytes())
|
||||||
|
.with_value(value.as_bytes());
|
||||||
|
kv_backend.put(put_req).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares the physical table metadata for testing.
|
||||||
|
///
|
||||||
|
/// Returns the table info and the table route.
|
||||||
|
pub async fn prepare_physical_table_metadata(
|
||||||
|
table_name: &str,
|
||||||
|
table_id: TableId,
|
||||||
|
) -> (RawTableInfo, PhysicalTableRouteValue) {
|
||||||
|
let mut create_physical_table_task = test_create_physical_table_task(table_name);
|
||||||
|
let table_route = PhysicalTableRouteValue::new(vec![RegionRoute {
|
||||||
|
region: Region {
|
||||||
|
id: RegionId::new(table_id, 1),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
leader_peer: Some(Peer::empty(1)),
|
||||||
|
..Default::default()
|
||||||
|
}]);
|
||||||
|
create_physical_table_task.set_table_id(table_id);
|
||||||
|
|
||||||
|
(create_physical_table_task.table_info, table_route)
|
||||||
|
}
|
||||||
57
src/cli/src/metadata/control/utils.rs
Normal file
57
src/cli/src/metadata/control/utils.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// 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 common_error::ext::BoxedError;
|
||||||
|
use common_meta::error::Result as CommonMetaResult;
|
||||||
|
use common_meta::key::table_name::{TableNameKey, TableNameManager};
|
||||||
|
use common_meta::rpc::KeyValue;
|
||||||
|
use serde::Serialize;
|
||||||
|
use store_api::storage::TableId;
|
||||||
|
|
||||||
|
/// Decodes a key-value pair into a string.
|
||||||
|
pub fn decode_key_value(kv: KeyValue) -> CommonMetaResult<(String, String)> {
|
||||||
|
let key = String::from_utf8_lossy(&kv.key).to_string();
|
||||||
|
let value = String::from_utf8_lossy(&kv.value).to_string();
|
||||||
|
Ok((key, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a value as a JSON string.
|
||||||
|
pub fn json_fromatter<T>(pretty: bool, value: &T) -> String
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
if pretty {
|
||||||
|
serde_json::to_string_pretty(value).unwrap()
|
||||||
|
} else {
|
||||||
|
serde_json::to_string(value).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the table id by table name.
|
||||||
|
pub async fn get_table_id_by_name(
|
||||||
|
table_name_manager: &TableNameManager,
|
||||||
|
catalog_name: &str,
|
||||||
|
schema_name: &str,
|
||||||
|
table_name: &str,
|
||||||
|
) -> Result<Option<TableId>, BoxedError> {
|
||||||
|
let table_name_key = TableNameKey::new(catalog_name, schema_name, table_name);
|
||||||
|
let Some(table_name_value) = table_name_manager
|
||||||
|
.get(table_name_key)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
Ok(Some(table_name_value.table_id()))
|
||||||
|
}
|
||||||
368
src/cli/src/metadata/repair.rs
Normal file
368
src/cli/src/metadata/repair.rs
Normal file
@@ -0,0 +1,368 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
mod alter_table;
|
||||||
|
mod create_table;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use clap::Parser;
|
||||||
|
use client::api::v1::CreateTableExpr;
|
||||||
|
use client::client_manager::NodeClients;
|
||||||
|
use client::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||||
|
use common_error::ext::{BoxedError, ErrorExt};
|
||||||
|
use common_error::status_code::StatusCode;
|
||||||
|
use common_grpc::channel_manager::ChannelConfig;
|
||||||
|
use common_meta::error::Error as CommonMetaError;
|
||||||
|
use common_meta::key::TableMetadataManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use common_meta::node_manager::NodeManagerRef;
|
||||||
|
use common_meta::peer::Peer;
|
||||||
|
use common_meta::rpc::router::{find_leaders, RegionRoute};
|
||||||
|
use common_telemetry::{error, info, warn};
|
||||||
|
use futures::TryStreamExt;
|
||||||
|
use snafu::{ensure, ResultExt};
|
||||||
|
use store_api::storage::TableId;
|
||||||
|
|
||||||
|
use crate::error::{
|
||||||
|
InvalidArgumentsSnafu, Result, SendRequestToDatanodeSnafu, TableMetadataSnafu, UnexpectedSnafu,
|
||||||
|
};
|
||||||
|
use crate::metadata::common::StoreConfig;
|
||||||
|
use crate::metadata::utils::{FullTableMetadata, IteratorInput, TableMetadataIterator};
|
||||||
|
use crate::Tool;
|
||||||
|
|
||||||
|
/// Repair metadata of logical tables.
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub struct RepairLogicalTablesCommand {
|
||||||
|
/// The names of the tables to repair.
|
||||||
|
#[clap(long, value_delimiter = ',', alias = "table-name")]
|
||||||
|
table_names: Vec<String>,
|
||||||
|
|
||||||
|
/// The id of the table to repair.
|
||||||
|
#[clap(long, value_delimiter = ',', alias = "table-id")]
|
||||||
|
table_ids: Vec<TableId>,
|
||||||
|
|
||||||
|
/// The schema of the tables to repair.
|
||||||
|
#[clap(long, default_value = DEFAULT_SCHEMA_NAME)]
|
||||||
|
schema_name: String,
|
||||||
|
|
||||||
|
/// The catalog of the tables to repair.
|
||||||
|
#[clap(long, default_value = DEFAULT_CATALOG_NAME)]
|
||||||
|
catalog_name: String,
|
||||||
|
|
||||||
|
/// Whether to fail fast if any repair operation fails.
|
||||||
|
#[clap(long)]
|
||||||
|
fail_fast: bool,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
store: StoreConfig,
|
||||||
|
|
||||||
|
/// The timeout for the client to operate the datanode.
|
||||||
|
#[clap(long, default_value_t = 30)]
|
||||||
|
client_timeout_secs: u64,
|
||||||
|
|
||||||
|
/// The timeout for the client to connect to the datanode.
|
||||||
|
#[clap(long, default_value_t = 3)]
|
||||||
|
client_connect_timeout_secs: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepairLogicalTablesCommand {
|
||||||
|
fn validate(&self) -> Result<()> {
|
||||||
|
ensure!(
|
||||||
|
!self.table_names.is_empty() || !self.table_ids.is_empty(),
|
||||||
|
InvalidArgumentsSnafu {
|
||||||
|
msg: "You must specify --table-names or --table-ids.",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepairLogicalTablesCommand {
|
||||||
|
pub async fn build(&self) -> std::result::Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
self.validate().map_err(BoxedError::new)?;
|
||||||
|
let kv_backend = self.store.build().await?;
|
||||||
|
let node_client_channel_config = ChannelConfig::new()
|
||||||
|
.timeout(Duration::from_secs(self.client_timeout_secs))
|
||||||
|
.connect_timeout(Duration::from_secs(self.client_connect_timeout_secs));
|
||||||
|
let node_manager = Arc::new(NodeClients::new(node_client_channel_config));
|
||||||
|
|
||||||
|
Ok(Box::new(RepairTool {
|
||||||
|
table_names: self.table_names.clone(),
|
||||||
|
table_ids: self.table_ids.clone(),
|
||||||
|
schema_name: self.schema_name.clone(),
|
||||||
|
catalog_name: self.catalog_name.clone(),
|
||||||
|
fail_fast: self.fail_fast,
|
||||||
|
kv_backend,
|
||||||
|
node_manager,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RepairTool {
|
||||||
|
table_names: Vec<String>,
|
||||||
|
table_ids: Vec<TableId>,
|
||||||
|
schema_name: String,
|
||||||
|
catalog_name: String,
|
||||||
|
fail_fast: bool,
|
||||||
|
kv_backend: KvBackendRef,
|
||||||
|
node_manager: NodeManagerRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tool for RepairTool {
|
||||||
|
async fn do_work(&self) -> std::result::Result<(), BoxedError> {
|
||||||
|
self.repair_tables().await.map_err(BoxedError::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RepairTool {
|
||||||
|
fn generate_iterator_input(&self) -> Result<IteratorInput> {
|
||||||
|
if !self.table_names.is_empty() {
|
||||||
|
let table_names = &self.table_names;
|
||||||
|
let catalog = &self.catalog_name;
|
||||||
|
let schema_name = &self.schema_name;
|
||||||
|
|
||||||
|
let table_names = table_names
|
||||||
|
.iter()
|
||||||
|
.map(|table_name| {
|
||||||
|
(
|
||||||
|
catalog.to_string(),
|
||||||
|
schema_name.to_string(),
|
||||||
|
table_name.to_string(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
return Ok(IteratorInput::new_table_names(table_names));
|
||||||
|
} else if !self.table_ids.is_empty() {
|
||||||
|
return Ok(IteratorInput::new_table_ids(self.table_ids.clone()));
|
||||||
|
};
|
||||||
|
|
||||||
|
InvalidArgumentsSnafu {
|
||||||
|
msg: "You must specify --table-names or --table-id.",
|
||||||
|
}
|
||||||
|
.fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn repair_tables(&self) -> Result<()> {
|
||||||
|
let input = self.generate_iterator_input()?;
|
||||||
|
let mut table_metadata_iterator =
|
||||||
|
Box::pin(TableMetadataIterator::new(self.kv_backend.clone(), input).into_stream());
|
||||||
|
let table_metadata_manager = TableMetadataManager::new(self.kv_backend.clone());
|
||||||
|
|
||||||
|
let mut skipped_table = 0;
|
||||||
|
let mut success_table = 0;
|
||||||
|
while let Some(full_table_metadata) = table_metadata_iterator.try_next().await? {
|
||||||
|
let full_table_name = full_table_metadata.full_table_name();
|
||||||
|
if !full_table_metadata.is_metric_engine() {
|
||||||
|
warn!(
|
||||||
|
"Skipping repair for non-metric engine table: {}",
|
||||||
|
full_table_name
|
||||||
|
);
|
||||||
|
skipped_table += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if full_table_metadata.is_physical_table() {
|
||||||
|
warn!("Skipping repair for physical table: {}", full_table_name);
|
||||||
|
skipped_table += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (physical_table_id, physical_table_route) = table_metadata_manager
|
||||||
|
.table_route_manager()
|
||||||
|
.get_physical_table_route(full_table_metadata.table_id)
|
||||||
|
.await
|
||||||
|
.context(TableMetadataSnafu)?;
|
||||||
|
|
||||||
|
if let Err(err) = self
|
||||||
|
.repair_table(
|
||||||
|
&full_table_metadata,
|
||||||
|
physical_table_id,
|
||||||
|
&physical_table_route.region_routes,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
err;
|
||||||
|
"Failed to repair table: {}, skipped table: {}",
|
||||||
|
full_table_name,
|
||||||
|
skipped_table,
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.fail_fast {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
success_table += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Repair logical tables result: {} tables repaired, {} tables skipped",
|
||||||
|
success_table, skipped_table
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn alter_table_on_datanodes(
|
||||||
|
&self,
|
||||||
|
full_table_metadata: &FullTableMetadata,
|
||||||
|
physical_region_routes: &[RegionRoute],
|
||||||
|
) -> Result<Vec<(Peer, CommonMetaError)>> {
|
||||||
|
let logical_table_id = full_table_metadata.table_id;
|
||||||
|
let alter_table_expr = alter_table::generate_alter_table_expr_for_all_columns(
|
||||||
|
&full_table_metadata.table_info,
|
||||||
|
)?;
|
||||||
|
let node_manager = self.node_manager.clone();
|
||||||
|
|
||||||
|
let mut failed_peers = Vec::new();
|
||||||
|
info!(
|
||||||
|
"Sending alter table requests to all datanodes for table: {}, number of regions:{}.",
|
||||||
|
full_table_metadata.full_table_name(),
|
||||||
|
physical_region_routes.len()
|
||||||
|
);
|
||||||
|
let leaders = find_leaders(physical_region_routes);
|
||||||
|
for peer in &leaders {
|
||||||
|
let alter_table_request = alter_table::make_alter_region_request_for_peer(
|
||||||
|
logical_table_id,
|
||||||
|
&alter_table_expr,
|
||||||
|
peer,
|
||||||
|
physical_region_routes,
|
||||||
|
)?;
|
||||||
|
let datanode = node_manager.datanode(peer).await;
|
||||||
|
if let Err(err) = datanode.handle(alter_table_request).await {
|
||||||
|
failed_peers.push((peer.clone(), err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(failed_peers)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_table_on_datanode(
|
||||||
|
&self,
|
||||||
|
create_table_expr: &CreateTableExpr,
|
||||||
|
logical_table_id: TableId,
|
||||||
|
physical_table_id: TableId,
|
||||||
|
peer: &Peer,
|
||||||
|
physical_region_routes: &[RegionRoute],
|
||||||
|
) -> Result<()> {
|
||||||
|
let node_manager = self.node_manager.clone();
|
||||||
|
let datanode = node_manager.datanode(peer).await;
|
||||||
|
let create_table_request = create_table::make_create_region_request_for_peer(
|
||||||
|
logical_table_id,
|
||||||
|
physical_table_id,
|
||||||
|
create_table_expr,
|
||||||
|
peer,
|
||||||
|
physical_region_routes,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
datanode
|
||||||
|
.handle(create_table_request)
|
||||||
|
.await
|
||||||
|
.with_context(|_| SendRequestToDatanodeSnafu { peer: peer.clone() })?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn repair_table(
|
||||||
|
&self,
|
||||||
|
full_table_metadata: &FullTableMetadata,
|
||||||
|
physical_table_id: TableId,
|
||||||
|
physical_region_routes: &[RegionRoute],
|
||||||
|
) -> Result<()> {
|
||||||
|
let full_table_name = full_table_metadata.full_table_name();
|
||||||
|
// First we sends alter table requests to all datanodes with all columns.
|
||||||
|
let failed_peers = self
|
||||||
|
.alter_table_on_datanodes(full_table_metadata, physical_region_routes)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if failed_peers.is_empty() {
|
||||||
|
info!(
|
||||||
|
"All alter table requests sent successfully for table: {}",
|
||||||
|
full_table_name
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
warn!(
|
||||||
|
"Sending alter table requests to datanodes for table: {} failed for the datanodes: {:?}",
|
||||||
|
full_table_name,
|
||||||
|
failed_peers.iter().map(|(peer, _)| peer.id).collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
let create_table_expr =
|
||||||
|
create_table::generate_create_table_expr(&full_table_metadata.table_info)?;
|
||||||
|
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
for (peer, err) in failed_peers {
|
||||||
|
if err.status_code() != StatusCode::RegionNotFound {
|
||||||
|
error!(
|
||||||
|
err;
|
||||||
|
"Sending alter table requests to datanode: {} for table: {} failed",
|
||||||
|
peer.id,
|
||||||
|
full_table_name,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
"Region not found for table: {}, datanode: {}, trying to create the logical table on that datanode",
|
||||||
|
full_table_name,
|
||||||
|
peer.id
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the alter table request fails for any datanode, we attempt to create the table on that datanode
|
||||||
|
// as a fallback mechanism to ensure table consistency across the cluster.
|
||||||
|
if let Err(err) = self
|
||||||
|
.create_table_on_datanode(
|
||||||
|
&create_table_expr,
|
||||||
|
full_table_metadata.table_id,
|
||||||
|
physical_table_id,
|
||||||
|
&peer,
|
||||||
|
physical_region_routes,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
err;
|
||||||
|
"Failed to create table on datanode: {} for table: {}",
|
||||||
|
peer.id, full_table_name
|
||||||
|
);
|
||||||
|
errors.push(err);
|
||||||
|
if self.fail_fast {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"Created table on datanode: {} for table: {}",
|
||||||
|
peer.id, full_table_name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
return UnexpectedSnafu {
|
||||||
|
msg: format!(
|
||||||
|
"Failed to create table on datanodes for table: {}",
|
||||||
|
full_table_name,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/cli/src/metadata/repair/alter_table.rs
Normal file
84
src/cli/src/metadata/repair/alter_table.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// 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 client::api::v1::alter_table_expr::Kind;
|
||||||
|
use client::api::v1::region::{region_request, AlterRequests, RegionRequest, RegionRequestHeader};
|
||||||
|
use client::api::v1::{AddColumn, AddColumns, AlterTableExpr};
|
||||||
|
use common_meta::ddl::alter_logical_tables::make_alter_region_request;
|
||||||
|
use common_meta::peer::Peer;
|
||||||
|
use common_meta::rpc::router::{find_leader_regions, RegionRoute};
|
||||||
|
use operator::expr_helper::column_schemas_to_defs;
|
||||||
|
use snafu::ResultExt;
|
||||||
|
use store_api::storage::{RegionId, TableId};
|
||||||
|
use table::metadata::RawTableInfo;
|
||||||
|
|
||||||
|
use crate::error::{CovertColumnSchemasToDefsSnafu, Result};
|
||||||
|
|
||||||
|
/// Generates alter table expression for all columns.
|
||||||
|
pub fn generate_alter_table_expr_for_all_columns(
|
||||||
|
table_info: &RawTableInfo,
|
||||||
|
) -> Result<AlterTableExpr> {
|
||||||
|
let schema = &table_info.meta.schema;
|
||||||
|
|
||||||
|
let mut alter_table_expr = AlterTableExpr {
|
||||||
|
catalog_name: table_info.catalog_name.to_string(),
|
||||||
|
schema_name: table_info.schema_name.to_string(),
|
||||||
|
table_name: table_info.name.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let primary_keys = table_info
|
||||||
|
.meta
|
||||||
|
.primary_key_indices
|
||||||
|
.iter()
|
||||||
|
.map(|i| schema.column_schemas[*i].name.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let add_columns = column_schemas_to_defs(schema.column_schemas.clone(), &primary_keys)
|
||||||
|
.context(CovertColumnSchemasToDefsSnafu)?;
|
||||||
|
|
||||||
|
alter_table_expr.kind = Some(Kind::AddColumns(AddColumns {
|
||||||
|
add_columns: add_columns
|
||||||
|
.into_iter()
|
||||||
|
.map(|col| AddColumn {
|
||||||
|
column_def: Some(col),
|
||||||
|
location: None,
|
||||||
|
add_if_not_exists: true,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(alter_table_expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes an alter region request for a peer.
|
||||||
|
pub fn make_alter_region_request_for_peer(
|
||||||
|
logical_table_id: TableId,
|
||||||
|
alter_table_expr: &AlterTableExpr,
|
||||||
|
peer: &Peer,
|
||||||
|
region_routes: &[RegionRoute],
|
||||||
|
) -> Result<RegionRequest> {
|
||||||
|
let regions_on_this_peer = find_leader_regions(region_routes, peer);
|
||||||
|
let mut requests = Vec::with_capacity(regions_on_this_peer.len());
|
||||||
|
for region_number in ®ions_on_this_peer {
|
||||||
|
let region_id = RegionId::new(logical_table_id, *region_number);
|
||||||
|
let request = make_alter_region_request(region_id, alter_table_expr);
|
||||||
|
requests.push(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RegionRequest {
|
||||||
|
header: Some(RegionRequestHeader::default()),
|
||||||
|
body: Some(region_request::Body::Alters(AlterRequests { requests })),
|
||||||
|
})
|
||||||
|
}
|
||||||
89
src/cli/src/metadata/repair/create_table.rs
Normal file
89
src/cli/src/metadata/repair/create_table.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// 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::collections::HashMap;
|
||||||
|
|
||||||
|
use client::api::v1::region::{region_request, CreateRequests, RegionRequest, RegionRequestHeader};
|
||||||
|
use client::api::v1::CreateTableExpr;
|
||||||
|
use common_meta::ddl::create_logical_tables::create_region_request_builder;
|
||||||
|
use common_meta::ddl::utils::region_storage_path;
|
||||||
|
use common_meta::peer::Peer;
|
||||||
|
use common_meta::rpc::router::{find_leader_regions, RegionRoute};
|
||||||
|
use operator::expr_helper::column_schemas_to_defs;
|
||||||
|
use snafu::ResultExt;
|
||||||
|
use store_api::storage::{RegionId, TableId};
|
||||||
|
use table::metadata::RawTableInfo;
|
||||||
|
|
||||||
|
use crate::error::{CovertColumnSchemasToDefsSnafu, Result};
|
||||||
|
|
||||||
|
/// Generates a `CreateTableExpr` from a `RawTableInfo`.
|
||||||
|
pub fn generate_create_table_expr(table_info: &RawTableInfo) -> Result<CreateTableExpr> {
|
||||||
|
let schema = &table_info.meta.schema;
|
||||||
|
let primary_keys = table_info
|
||||||
|
.meta
|
||||||
|
.primary_key_indices
|
||||||
|
.iter()
|
||||||
|
.map(|i| schema.column_schemas[*i].name.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let timestamp_index = schema.timestamp_index.as_ref().unwrap();
|
||||||
|
let time_index = schema.column_schemas[*timestamp_index].name.clone();
|
||||||
|
let column_defs = column_schemas_to_defs(schema.column_schemas.clone(), &primary_keys)
|
||||||
|
.context(CovertColumnSchemasToDefsSnafu)?;
|
||||||
|
let table_options = HashMap::from(&table_info.meta.options);
|
||||||
|
|
||||||
|
Ok(CreateTableExpr {
|
||||||
|
catalog_name: table_info.catalog_name.to_string(),
|
||||||
|
schema_name: table_info.schema_name.to_string(),
|
||||||
|
table_name: table_info.name.to_string(),
|
||||||
|
desc: String::default(),
|
||||||
|
column_defs,
|
||||||
|
time_index,
|
||||||
|
primary_keys,
|
||||||
|
create_if_not_exists: true,
|
||||||
|
table_options,
|
||||||
|
table_id: None,
|
||||||
|
engine: table_info.meta.engine.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a create region request for a peer.
|
||||||
|
pub fn make_create_region_request_for_peer(
|
||||||
|
logical_table_id: TableId,
|
||||||
|
physical_table_id: TableId,
|
||||||
|
create_table_expr: &CreateTableExpr,
|
||||||
|
peer: &Peer,
|
||||||
|
region_routes: &[RegionRoute],
|
||||||
|
) -> Result<RegionRequest> {
|
||||||
|
let regions_on_this_peer = find_leader_regions(region_routes, peer);
|
||||||
|
let mut requests = Vec::with_capacity(regions_on_this_peer.len());
|
||||||
|
let request_builder =
|
||||||
|
create_region_request_builder(create_table_expr, physical_table_id).unwrap();
|
||||||
|
|
||||||
|
let catalog = &create_table_expr.catalog_name;
|
||||||
|
let schema = &create_table_expr.schema_name;
|
||||||
|
let storage_path = region_storage_path(catalog, schema);
|
||||||
|
|
||||||
|
for region_number in ®ions_on_this_peer {
|
||||||
|
let region_id = RegionId::new(logical_table_id, *region_number);
|
||||||
|
let region_request =
|
||||||
|
request_builder.build_one(region_id, storage_path.clone(), &HashMap::new());
|
||||||
|
requests.push(region_request);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(RegionRequest {
|
||||||
|
header: Some(RegionRequestHeader::default()),
|
||||||
|
body: Some(region_request::Body::Creates(CreateRequests { requests })),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -12,96 +12,38 @@
|
|||||||
// 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::sync::Arc;
|
use std::path::Path;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
use common_base::secrets::{ExposeSecret, SecretString};
|
use common_base::secrets::{ExposeSecret, SecretString};
|
||||||
use common_error::ext::BoxedError;
|
use common_error::ext::BoxedError;
|
||||||
use common_meta::kv_backend::chroot::ChrootKvBackend;
|
|
||||||
use common_meta::kv_backend::etcd::EtcdStore;
|
|
||||||
use common_meta::kv_backend::KvBackendRef;
|
|
||||||
use common_meta::snapshot::MetadataSnapshotManager;
|
use common_meta::snapshot::MetadataSnapshotManager;
|
||||||
use meta_srv::bootstrap::create_etcd_client;
|
|
||||||
use meta_srv::metasrv::BackendImpl;
|
|
||||||
use object_store::services::{Fs, S3};
|
use object_store::services::{Fs, S3};
|
||||||
use object_store::ObjectStore;
|
use object_store::ObjectStore;
|
||||||
use snafu::ResultExt;
|
use snafu::{OptionExt, ResultExt};
|
||||||
|
|
||||||
use crate::error::{KvBackendNotSetSnafu, OpenDalSnafu, S3ConfigNotSetSnafu};
|
use crate::error::{InvalidFilePathSnafu, OpenDalSnafu, S3ConfigNotSetSnafu};
|
||||||
|
use crate::metadata::common::StoreConfig;
|
||||||
use crate::Tool;
|
use crate::Tool;
|
||||||
#[derive(Debug, Default, Parser)]
|
|
||||||
struct MetaConnection {
|
/// Subcommand for metadata snapshot operations, including saving snapshots, restoring from snapshots, and viewing snapshot information.
|
||||||
/// The endpoint of store. one of etcd, pg or mysql.
|
#[derive(Subcommand)]
|
||||||
#[clap(long, alias = "store-addr", value_delimiter = ',', num_args = 1..)]
|
pub enum SnapshotCommand {
|
||||||
store_addrs: Vec<String>,
|
/// Save a snapshot of the current metadata state to a specified location.
|
||||||
/// The database backend.
|
Save(SaveCommand),
|
||||||
#[clap(long, value_enum)]
|
/// Restore metadata from a snapshot.
|
||||||
backend: Option<BackendImpl>,
|
Restore(RestoreCommand),
|
||||||
#[clap(long, default_value = "")]
|
/// Explore metadata from a snapshot.
|
||||||
store_key_prefix: String,
|
Info(InfoCommand),
|
||||||
#[cfg(any(feature = "pg_kvbackend", feature = "mysql_kvbackend"))]
|
|
||||||
#[clap(long,default_value = common_meta::kv_backend::DEFAULT_META_TABLE_NAME)]
|
|
||||||
meta_table_name: String,
|
|
||||||
#[clap(long, default_value = "128")]
|
|
||||||
max_txn_ops: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaConnection {
|
impl SnapshotCommand {
|
||||||
pub async fn build(&self) -> Result<KvBackendRef, BoxedError> {
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
let max_txn_ops = self.max_txn_ops;
|
match self {
|
||||||
let store_addrs = &self.store_addrs;
|
SnapshotCommand::Save(cmd) => cmd.build().await,
|
||||||
if store_addrs.is_empty() {
|
SnapshotCommand::Restore(cmd) => cmd.build().await,
|
||||||
KvBackendNotSetSnafu { backend: "all" }
|
SnapshotCommand::Info(cmd) => cmd.build().await,
|
||||||
.fail()
|
|
||||||
.map_err(BoxedError::new)
|
|
||||||
} else {
|
|
||||||
let kvbackend = match self.backend {
|
|
||||||
Some(BackendImpl::EtcdStore) => {
|
|
||||||
let etcd_client = create_etcd_client(store_addrs)
|
|
||||||
.await
|
|
||||||
.map_err(BoxedError::new)?;
|
|
||||||
Ok(EtcdStore::with_etcd_client(etcd_client, max_txn_ops))
|
|
||||||
}
|
|
||||||
#[cfg(feature = "pg_kvbackend")]
|
|
||||||
Some(BackendImpl::PostgresStore) => {
|
|
||||||
let table_name = &self.meta_table_name;
|
|
||||||
let pool = meta_srv::bootstrap::create_postgres_pool(store_addrs)
|
|
||||||
.await
|
|
||||||
.map_err(BoxedError::new)?;
|
|
||||||
Ok(common_meta::kv_backend::rds::PgStore::with_pg_pool(
|
|
||||||
pool,
|
|
||||||
table_name,
|
|
||||||
max_txn_ops,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(BoxedError::new)?)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "mysql_kvbackend")]
|
|
||||||
Some(BackendImpl::MysqlStore) => {
|
|
||||||
let table_name = &self.meta_table_name;
|
|
||||||
let pool = meta_srv::bootstrap::create_mysql_pool(store_addrs)
|
|
||||||
.await
|
|
||||||
.map_err(BoxedError::new)?;
|
|
||||||
Ok(common_meta::kv_backend::rds::MySqlStore::with_mysql_pool(
|
|
||||||
pool,
|
|
||||||
table_name,
|
|
||||||
max_txn_ops,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(BoxedError::new)?)
|
|
||||||
}
|
|
||||||
_ => KvBackendNotSetSnafu { backend: "all" }
|
|
||||||
.fail()
|
|
||||||
.map_err(BoxedError::new),
|
|
||||||
};
|
|
||||||
if self.store_key_prefix.is_empty() {
|
|
||||||
kvbackend
|
|
||||||
} else {
|
|
||||||
let chroot_kvbackend =
|
|
||||||
ChrootKvBackend::new(self.store_key_prefix.as_bytes().to_vec(), kvbackend?);
|
|
||||||
Ok(Arc::new(chroot_kvbackend))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,10 +112,10 @@ impl S3Config {
|
|||||||
/// It will dump the metadata snapshot to local file or s3 bucket.
|
/// It will dump the metadata snapshot to local file or s3 bucket.
|
||||||
/// The snapshot file will be in binary format.
|
/// The snapshot file will be in binary format.
|
||||||
#[derive(Debug, Default, Parser)]
|
#[derive(Debug, Default, Parser)]
|
||||||
pub struct MetaSnapshotCommand {
|
pub struct SaveCommand {
|
||||||
/// The connection to the metadata store.
|
/// The store configuration.
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
connection: MetaConnection,
|
store: StoreConfig,
|
||||||
/// The s3 config.
|
/// The s3 config.
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
s3_config: S3Config,
|
s3_config: S3Config,
|
||||||
@@ -196,9 +138,9 @@ fn create_local_file_object_store(root: &str) -> Result<ObjectStore, BoxedError>
|
|||||||
Ok(object_store)
|
Ok(object_store)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaSnapshotCommand {
|
impl SaveCommand {
|
||||||
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
let kvbackend = self.connection.build().await?;
|
let kvbackend = self.store.build().await?;
|
||||||
let output_dir = &self.output_dir;
|
let output_dir = &self.output_dir;
|
||||||
let object_store = self.s3_config.build(output_dir).map_err(BoxedError::new)?;
|
let object_store = self.s3_config.build(output_dir).map_err(BoxedError::new)?;
|
||||||
if let Some(store) = object_store {
|
if let Some(store) = object_store {
|
||||||
@@ -218,7 +160,7 @@ impl MetaSnapshotCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MetaSnapshotTool {
|
struct MetaSnapshotTool {
|
||||||
inner: MetadataSnapshotManager,
|
inner: MetadataSnapshotManager,
|
||||||
target_file: String,
|
target_file: String,
|
||||||
}
|
}
|
||||||
@@ -234,14 +176,16 @@ impl Tool for MetaSnapshotTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restore metadata snapshot tool.
|
/// Restore metadata from a snapshot file.
|
||||||
/// This tool is used to restore metadata snapshot from etcd, pg or mysql.
|
///
|
||||||
/// It will restore the metadata snapshot from local file or s3 bucket.
|
/// This command restores the metadata state from a previously saved snapshot.
|
||||||
|
/// The snapshot can be loaded from either a local file system or an S3 bucket,
|
||||||
|
/// depending on the provided configuration.
|
||||||
#[derive(Debug, Default, Parser)]
|
#[derive(Debug, Default, Parser)]
|
||||||
pub struct MetaRestoreCommand {
|
pub struct RestoreCommand {
|
||||||
/// The connection to the metadata store.
|
/// The store configuration.
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
connection: MetaConnection,
|
store: StoreConfig,
|
||||||
/// The s3 config.
|
/// The s3 config.
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
s3_config: S3Config,
|
s3_config: S3Config,
|
||||||
@@ -255,9 +199,9 @@ pub struct MetaRestoreCommand {
|
|||||||
force: bool,
|
force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetaRestoreCommand {
|
impl RestoreCommand {
|
||||||
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
let kvbackend = self.connection.build().await?;
|
let kvbackend = self.store.build().await?;
|
||||||
let input_dir = &self.input_dir;
|
let input_dir = &self.input_dir;
|
||||||
let object_store = self.s3_config.build(input_dir).map_err(BoxedError::new)?;
|
let object_store = self.s3_config.build(input_dir).map_err(BoxedError::new)?;
|
||||||
if let Some(store) = object_store {
|
if let Some(store) = object_store {
|
||||||
@@ -279,7 +223,7 @@ impl MetaRestoreCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MetaRestoreTool {
|
struct MetaRestoreTool {
|
||||||
inner: MetadataSnapshotManager,
|
inner: MetadataSnapshotManager,
|
||||||
source_file: String,
|
source_file: String,
|
||||||
force: bool,
|
force: bool,
|
||||||
@@ -327,3 +271,92 @@ impl Tool for MetaRestoreTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Explore metadata from a snapshot file.
|
||||||
|
///
|
||||||
|
/// This command allows filtering the metadata by a specific key and limiting the number of results.
|
||||||
|
/// It prints the filtered metadata to the console.
|
||||||
|
#[derive(Debug, Default, Parser)]
|
||||||
|
pub struct InfoCommand {
|
||||||
|
/// The s3 config.
|
||||||
|
#[clap(flatten)]
|
||||||
|
s3_config: S3Config,
|
||||||
|
/// The name of the target snapshot file. we will add the file extension automatically.
|
||||||
|
#[clap(long, default_value = "metadata_snapshot")]
|
||||||
|
file_name: String,
|
||||||
|
/// The query string to filter the metadata.
|
||||||
|
#[clap(long, default_value = "*")]
|
||||||
|
inspect_key: String,
|
||||||
|
/// The limit of the metadata to query.
|
||||||
|
#[clap(long)]
|
||||||
|
limit: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MetaInfoTool {
|
||||||
|
inner: ObjectStore,
|
||||||
|
source_file: String,
|
||||||
|
inspect_key: String,
|
||||||
|
limit: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Tool for MetaInfoTool {
|
||||||
|
async fn do_work(&self) -> std::result::Result<(), BoxedError> {
|
||||||
|
let result = MetadataSnapshotManager::info(
|
||||||
|
&self.inner,
|
||||||
|
&self.source_file,
|
||||||
|
&self.inspect_key,
|
||||||
|
self.limit,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
for item in result {
|
||||||
|
println!("{}", item);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InfoCommand {
|
||||||
|
fn decide_object_store_root_for_local_store(
|
||||||
|
file_path: &str,
|
||||||
|
) -> Result<(&str, &str), BoxedError> {
|
||||||
|
let path = Path::new(file_path);
|
||||||
|
let parent = path
|
||||||
|
.parent()
|
||||||
|
.and_then(|p| p.to_str())
|
||||||
|
.context(InvalidFilePathSnafu { msg: file_path })
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
let file_name = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|f| f.to_str())
|
||||||
|
.context(InvalidFilePathSnafu { msg: file_path })
|
||||||
|
.map_err(BoxedError::new)?;
|
||||||
|
let root = if parent.is_empty() { "." } else { parent };
|
||||||
|
Ok((root, file_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn build(&self) -> Result<Box<dyn Tool>, BoxedError> {
|
||||||
|
let object_store = self.s3_config.build("").map_err(BoxedError::new)?;
|
||||||
|
if let Some(store) = object_store {
|
||||||
|
let tool = MetaInfoTool {
|
||||||
|
inner: store,
|
||||||
|
source_file: self.file_name.clone(),
|
||||||
|
inspect_key: self.inspect_key.clone(),
|
||||||
|
limit: self.limit,
|
||||||
|
};
|
||||||
|
Ok(Box::new(tool))
|
||||||
|
} else {
|
||||||
|
let (root, file_name) =
|
||||||
|
Self::decide_object_store_root_for_local_store(&self.file_name)?;
|
||||||
|
let object_store = create_local_file_object_store(root)?;
|
||||||
|
let tool = MetaInfoTool {
|
||||||
|
inner: object_store,
|
||||||
|
source_file: file_name.to_string(),
|
||||||
|
inspect_key: self.inspect_key.clone(),
|
||||||
|
limit: self.limit,
|
||||||
|
};
|
||||||
|
Ok(Box::new(tool))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/cli/src/metadata/utils.rs
Normal file
178
src/cli/src/metadata/utils.rs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// 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::collections::VecDeque;
|
||||||
|
|
||||||
|
use async_stream::try_stream;
|
||||||
|
use common_catalog::consts::METRIC_ENGINE;
|
||||||
|
use common_catalog::format_full_table_name;
|
||||||
|
use common_meta::key::table_name::TableNameKey;
|
||||||
|
use common_meta::key::table_route::TableRouteValue;
|
||||||
|
use common_meta::key::TableMetadataManager;
|
||||||
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
|
use futures::Stream;
|
||||||
|
use snafu::{OptionExt, ResultExt};
|
||||||
|
use store_api::storage::TableId;
|
||||||
|
use table::metadata::RawTableInfo;
|
||||||
|
|
||||||
|
use crate::error::{Result, TableMetadataSnafu, UnexpectedSnafu};
|
||||||
|
|
||||||
|
/// The input for the iterator.
|
||||||
|
pub enum IteratorInput {
|
||||||
|
TableIds(VecDeque<TableId>),
|
||||||
|
TableNames(VecDeque<(String, String, String)>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IteratorInput {
|
||||||
|
/// Creates a new iterator input from a list of table ids.
|
||||||
|
pub fn new_table_ids(table_ids: Vec<TableId>) -> Self {
|
||||||
|
Self::TableIds(table_ids.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new iterator input from a list of table names.
|
||||||
|
pub fn new_table_names(table_names: Vec<(String, String, String)>) -> Self {
|
||||||
|
Self::TableNames(table_names.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator for retrieving table metadata from the metadata store.
|
||||||
|
///
|
||||||
|
/// This struct provides functionality to iterate over table metadata based on
|
||||||
|
/// either [`TableId`] and their associated regions or fully qualified table names.
|
||||||
|
pub struct TableMetadataIterator {
|
||||||
|
input: IteratorInput,
|
||||||
|
table_metadata_manager: TableMetadataManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The full table metadata.
|
||||||
|
pub struct FullTableMetadata {
|
||||||
|
pub table_id: TableId,
|
||||||
|
pub table_info: RawTableInfo,
|
||||||
|
pub table_route: TableRouteValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FullTableMetadata {
|
||||||
|
/// Returns true if it's [TableRouteValue::Physical].
|
||||||
|
pub fn is_physical_table(&self) -> bool {
|
||||||
|
self.table_route.is_physical()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if it's a metric engine table.
|
||||||
|
pub fn is_metric_engine(&self) -> bool {
|
||||||
|
self.table_info.meta.engine == METRIC_ENGINE
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the full table name.
|
||||||
|
pub fn full_table_name(&self) -> String {
|
||||||
|
format_full_table_name(
|
||||||
|
&self.table_info.catalog_name,
|
||||||
|
&self.table_info.schema_name,
|
||||||
|
&self.table_info.name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableMetadataIterator {
|
||||||
|
pub fn new(kvbackend: KvBackendRef, input: IteratorInput) -> Self {
|
||||||
|
let table_metadata_manager = TableMetadataManager::new(kvbackend);
|
||||||
|
Self {
|
||||||
|
input,
|
||||||
|
table_metadata_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the next table metadata.
|
||||||
|
///
|
||||||
|
/// This method handles two types of inputs:
|
||||||
|
/// - TableIds: Returns metadata for a specific [`TableId`].
|
||||||
|
/// - TableNames: Returns metadata for a table identified by its full name (catalog.schema.table).
|
||||||
|
///
|
||||||
|
/// Returns `None` when there are no more tables to process.
|
||||||
|
pub async fn next(&mut self) -> Result<Option<FullTableMetadata>> {
|
||||||
|
match &mut self.input {
|
||||||
|
IteratorInput::TableIds(table_ids) => {
|
||||||
|
if let Some(table_id) = table_ids.pop_front() {
|
||||||
|
let full_table_metadata = self.get_table_metadata(table_id).await?;
|
||||||
|
return Ok(Some(full_table_metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IteratorInput::TableNames(table_names) => {
|
||||||
|
if let Some(full_table_name) = table_names.pop_front() {
|
||||||
|
let table_id = self.get_table_id_by_name(full_table_name).await?;
|
||||||
|
let full_table_metadata = self.get_table_metadata(table_id).await?;
|
||||||
|
return Ok(Some(full_table_metadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts the iterator into a stream of table metadata.
|
||||||
|
pub fn into_stream(mut self) -> impl Stream<Item = Result<FullTableMetadata>> {
|
||||||
|
try_stream!({
|
||||||
|
while let Some(full_table_metadata) = self.next().await? {
|
||||||
|
yield full_table_metadata;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_table_id_by_name(
|
||||||
|
&mut self,
|
||||||
|
(catalog_name, schema_name, table_name): (String, String, String),
|
||||||
|
) -> Result<TableId> {
|
||||||
|
let key = TableNameKey::new(&catalog_name, &schema_name, &table_name);
|
||||||
|
let table_id = self
|
||||||
|
.table_metadata_manager
|
||||||
|
.table_name_manager()
|
||||||
|
.get(key)
|
||||||
|
.await
|
||||||
|
.context(TableMetadataSnafu)?
|
||||||
|
.with_context(|| UnexpectedSnafu {
|
||||||
|
msg: format!(
|
||||||
|
"Table not found: {}",
|
||||||
|
format_full_table_name(&catalog_name, &schema_name, &table_name)
|
||||||
|
),
|
||||||
|
})?
|
||||||
|
.table_id();
|
||||||
|
Ok(table_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_table_metadata(&mut self, table_id: TableId) -> Result<FullTableMetadata> {
|
||||||
|
let (table_info, table_route) = self
|
||||||
|
.table_metadata_manager
|
||||||
|
.get_full_table_info(table_id)
|
||||||
|
.await
|
||||||
|
.context(TableMetadataSnafu)?;
|
||||||
|
|
||||||
|
let table_info = table_info
|
||||||
|
.with_context(|| UnexpectedSnafu {
|
||||||
|
msg: format!("Table info not found for table id: {table_id}"),
|
||||||
|
})?
|
||||||
|
.into_inner()
|
||||||
|
.table_info;
|
||||||
|
let table_route = table_route
|
||||||
|
.with_context(|| UnexpectedSnafu {
|
||||||
|
msg: format!("Table route not found for table id: {table_id}"),
|
||||||
|
})?
|
||||||
|
.into_inner();
|
||||||
|
|
||||||
|
Ok(FullTableMetadata {
|
||||||
|
table_id,
|
||||||
|
table_info,
|
||||||
|
table_route,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -162,12 +162,23 @@ impl Client {
|
|||||||
.as_bytes() as usize
|
.as_bytes() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_flight_client(&self) -> Result<FlightClient> {
|
pub fn make_flight_client(
|
||||||
|
&self,
|
||||||
|
send_compression: bool,
|
||||||
|
accept_compression: bool,
|
||||||
|
) -> Result<FlightClient> {
|
||||||
let (addr, channel) = self.find_channel()?;
|
let (addr, channel) = self.find_channel()?;
|
||||||
|
|
||||||
let client = FlightServiceClient::new(channel)
|
let mut client = FlightServiceClient::new(channel)
|
||||||
.max_decoding_message_size(self.max_grpc_recv_message_size())
|
.max_decoding_message_size(self.max_grpc_recv_message_size())
|
||||||
.max_encoding_message_size(self.max_grpc_send_message_size());
|
.max_encoding_message_size(self.max_grpc_send_message_size());
|
||||||
|
// todo(hl): support compression methods.
|
||||||
|
if send_compression {
|
||||||
|
client = client.send_compressed(CompressionEncoding::Zstd);
|
||||||
|
}
|
||||||
|
if accept_compression {
|
||||||
|
client = client.accept_compressed(CompressionEncoding::Zstd);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(FlightClient { addr, client })
|
Ok(FlightClient { addr, client })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,16 @@ 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;
|
||||||
|
|
||||||
Arc::new(RegionRequester::new(client))
|
let ChannelConfig {
|
||||||
|
send_compression,
|
||||||
|
accept_compression,
|
||||||
|
..
|
||||||
|
} = self.channel_manager.config();
|
||||||
|
Arc::new(RegionRequester::new(
|
||||||
|
client,
|
||||||
|
*send_compression,
|
||||||
|
*accept_compression,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn flownode(&self, flownode: &Peer) -> FlownodeRef {
|
async fn flownode(&self, flownode: &Peer) -> FlownodeRef {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use base64::prelude::BASE64_STANDARD;
|
|||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use common_catalog::build_db_string;
|
use common_catalog::build_db_string;
|
||||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||||
use common_error::ext::{BoxedError, ErrorExt};
|
use common_error::ext::BoxedError;
|
||||||
use common_grpc::flight::do_put::DoPutResponse;
|
use common_grpc::flight::do_put::DoPutResponse;
|
||||||
use common_grpc::flight::{FlightDecoder, FlightMessage};
|
use common_grpc::flight::{FlightDecoder, FlightMessage};
|
||||||
use common_query::Output;
|
use common_query::Output;
|
||||||
@@ -48,7 +48,7 @@ use tonic::transport::Channel;
|
|||||||
|
|
||||||
use crate::error::{
|
use crate::error::{
|
||||||
ConvertFlightDataSnafu, Error, FlightGetSnafu, IllegalFlightMessagesSnafu,
|
ConvertFlightDataSnafu, Error, FlightGetSnafu, IllegalFlightMessagesSnafu,
|
||||||
InvalidTonicMetadataValueSnafu, ServerSnafu,
|
InvalidTonicMetadataValueSnafu,
|
||||||
};
|
};
|
||||||
use crate::{error, from_grpc_response, Client, Result};
|
use crate::{error, from_grpc_response, Client, Result};
|
||||||
|
|
||||||
@@ -196,12 +196,22 @@ impl Database {
|
|||||||
|
|
||||||
/// Retry if connection fails, max_retries is the max number of retries, so the total wait time
|
/// Retry if connection fails, max_retries is the max number of retries, so the total wait time
|
||||||
/// is `max_retries * GRPC_CONN_TIMEOUT`
|
/// is `max_retries * GRPC_CONN_TIMEOUT`
|
||||||
pub async fn handle_with_retry(&self, request: Request, max_retries: u32) -> Result<u32> {
|
pub async fn handle_with_retry(
|
||||||
|
&self,
|
||||||
|
request: Request,
|
||||||
|
max_retries: u32,
|
||||||
|
hints: &[(&str, &str)],
|
||||||
|
) -> Result<u32> {
|
||||||
let mut client = make_database_client(&self.client)?.inner;
|
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);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let raw_response = client.handle(request.clone()).await;
|
let mut tonic_request = tonic::Request::new(request.clone());
|
||||||
|
let metadata = tonic_request.metadata_mut();
|
||||||
|
Self::put_hints(metadata, hints)?;
|
||||||
|
let raw_response = client.handle(tonic_request).await;
|
||||||
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) => {
|
||||||
@@ -287,26 +297,21 @@ impl Database {
|
|||||||
let mut request = tonic::Request::new(request);
|
let mut request = tonic::Request::new(request);
|
||||||
Self::put_hints(request.metadata_mut(), hints)?;
|
Self::put_hints(request.metadata_mut(), hints)?;
|
||||||
|
|
||||||
let mut client = self.client.make_flight_client()?;
|
let mut client = self.client.make_flight_client(false, false)?;
|
||||||
|
|
||||||
let response = client.mut_inner().do_get(request).await.or_else(|e| {
|
let response = client.mut_inner().do_get(request).await.or_else(|e| {
|
||||||
let tonic_code = e.code();
|
let tonic_code = e.code();
|
||||||
let e: Error = e.into();
|
let e: Error = e.into();
|
||||||
let code = e.status_code();
|
|
||||||
let msg = e.to_string();
|
|
||||||
let error =
|
|
||||||
Err(BoxedError::new(ServerSnafu { code, msg }.build())).with_context(|_| {
|
|
||||||
FlightGetSnafu {
|
|
||||||
addr: client.addr().to_string(),
|
|
||||||
tonic_code,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
error!(
|
error!(
|
||||||
"Failed to do Flight get, addr: {}, code: {}, source: {:?}",
|
"Failed to do Flight get, addr: {}, code: {}, source: {:?}",
|
||||||
client.addr(),
|
client.addr(),
|
||||||
tonic_code,
|
tonic_code,
|
||||||
error
|
e
|
||||||
);
|
);
|
||||||
|
let error = Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
|
||||||
|
addr: client.addr().to_string(),
|
||||||
|
tonic_code,
|
||||||
|
});
|
||||||
error
|
error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -409,7 +414,7 @@ impl Database {
|
|||||||
MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?,
|
MetadataValue::from_str(db_to_put).context(InvalidTonicMetadataValueSnafu)?,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut client = self.client.make_flight_client()?;
|
let mut client = self.client.make_flight_client(false, false)?;
|
||||||
let response = client.mut_inner().do_put(request).await?;
|
let response = client.mut_inner().do_put(request).await?;
|
||||||
let response = response
|
let response = response
|
||||||
.into_inner()
|
.into_inner()
|
||||||
@@ -436,8 +441,11 @@ mod tests {
|
|||||||
|
|
||||||
use api::v1::auth_header::AuthScheme;
|
use api::v1::auth_header::AuthScheme;
|
||||||
use api::v1::{AuthHeader, Basic};
|
use api::v1::{AuthHeader, Basic};
|
||||||
|
use common_error::status_code::StatusCode;
|
||||||
|
use tonic::{Code, Status};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::error::TonicSnafu;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_flight_ctx() {
|
fn test_flight_ctx() {
|
||||||
@@ -460,4 +468,19 @@ mod tests {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_tonic_status() {
|
||||||
|
let expected = TonicSnafu {
|
||||||
|
code: StatusCode::Internal,
|
||||||
|
msg: "blabla".to_string(),
|
||||||
|
tonic_code: Code::Internal,
|
||||||
|
}
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let status = Status::new(Code::Internal, "blabla");
|
||||||
|
let actual: Error = status.into();
|
||||||
|
|
||||||
|
assert_eq!(expected.to_string(), actual.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
|
||||||
|
use common_error::define_from_tonic_status;
|
||||||
use common_error::ext::{BoxedError, ErrorExt};
|
use common_error::ext::{BoxedError, ErrorExt};
|
||||||
use common_error::status_code::{convert_tonic_code_to_status_code, StatusCode};
|
use common_error::status_code::StatusCode;
|
||||||
use common_error::{GREPTIME_DB_HEADER_ERROR_CODE, GREPTIME_DB_HEADER_ERROR_MSG};
|
|
||||||
use common_macro::stack_trace_debug;
|
use common_macro::stack_trace_debug;
|
||||||
use snafu::{location, Location, Snafu};
|
use snafu::{location, Location, Snafu};
|
||||||
use tonic::metadata::errors::InvalidMetadataValue;
|
use tonic::metadata::errors::InvalidMetadataValue;
|
||||||
use tonic::{Code, Status};
|
use tonic::Code;
|
||||||
|
|
||||||
#[derive(Snafu)]
|
#[derive(Snafu)]
|
||||||
#[snafu(visibility(pub))]
|
#[snafu(visibility(pub))]
|
||||||
@@ -124,6 +124,15 @@ pub enum Error {
|
|||||||
location: Location,
|
location: Location,
|
||||||
source: datatypes::error::Error,
|
source: datatypes::error::Error,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[snafu(display("{}", msg))]
|
||||||
|
Tonic {
|
||||||
|
code: StatusCode,
|
||||||
|
msg: String,
|
||||||
|
tonic_code: Code,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -135,7 +144,7 @@ impl ErrorExt for Error {
|
|||||||
| Error::MissingField { .. }
|
| Error::MissingField { .. }
|
||||||
| Error::IllegalDatabaseResponse { .. } => StatusCode::Internal,
|
| Error::IllegalDatabaseResponse { .. } => StatusCode::Internal,
|
||||||
|
|
||||||
Error::Server { code, .. } => *code,
|
Error::Server { code, .. } | Error::Tonic { code, .. } => *code,
|
||||||
Error::FlightGet { source, .. }
|
Error::FlightGet { source, .. }
|
||||||
| Error::RegionServer { source, .. }
|
| Error::RegionServer { source, .. }
|
||||||
| Error::FlowServer { source, .. } => source.status_code(),
|
| Error::FlowServer { source, .. } => source.status_code(),
|
||||||
@@ -153,34 +162,7 @@ impl ErrorExt for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Status> for Error {
|
define_from_tonic_status!(Error, Tonic);
|
||||||
fn from(e: Status) -> Self {
|
|
||||||
fn get_metadata_value(e: &Status, key: &str) -> Option<String> {
|
|
||||||
e.metadata()
|
|
||||||
.get(key)
|
|
||||||
.and_then(|v| String::from_utf8(v.as_bytes().to_vec()).ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
let code = get_metadata_value(&e, GREPTIME_DB_HEADER_ERROR_CODE).and_then(|s| {
|
|
||||||
if let Ok(code) = s.parse::<u32>() {
|
|
||||||
StatusCode::from_u32(code)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let tonic_code = e.code();
|
|
||||||
let code = code.unwrap_or_else(|| convert_tonic_code_to_status_code(tonic_code));
|
|
||||||
|
|
||||||
let msg = get_metadata_value(&e, GREPTIME_DB_HEADER_ERROR_MSG)
|
|
||||||
.unwrap_or_else(|| e.message().to_string());
|
|
||||||
|
|
||||||
Self::Server {
|
|
||||||
code,
|
|
||||||
msg,
|
|
||||||
location: location!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn should_retry(&self) -> bool {
|
pub fn should_retry(&self) -> bool {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// 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 api::v1::flow::{FlowRequest, FlowResponse};
|
use api::v1::flow::{DirtyWindowRequest, DirtyWindowRequests, FlowRequest, FlowResponse};
|
||||||
use api::v1::region::InsertRequests;
|
use api::v1::region::InsertRequests;
|
||||||
use common_error::ext::BoxedError;
|
use common_error::ext::BoxedError;
|
||||||
use common_meta::node_manager::Flownode;
|
use common_meta::node_manager::Flownode;
|
||||||
@@ -44,6 +44,16 @@ impl Flownode for FlowRequester {
|
|||||||
.map_err(BoxedError::new)
|
.map_err(BoxedError::new)
|
||||||
.context(common_meta::error::ExternalSnafu)
|
.context(common_meta::error::ExternalSnafu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_mark_window_dirty(
|
||||||
|
&self,
|
||||||
|
req: DirtyWindowRequest,
|
||||||
|
) -> common_meta::error::Result<FlowResponse> {
|
||||||
|
self.handle_mark_window_dirty(req)
|
||||||
|
.await
|
||||||
|
.map_err(BoxedError::new)
|
||||||
|
.context(common_meta::error::ExternalSnafu)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlowRequester {
|
impl FlowRequester {
|
||||||
@@ -91,4 +101,20 @@ impl FlowRequester {
|
|||||||
.into_inner();
|
.into_inner();
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_mark_window_dirty(&self, req: DirtyWindowRequest) -> Result<FlowResponse> {
|
||||||
|
let (addr, mut client) = self.client.raw_flow_client()?;
|
||||||
|
let response = client
|
||||||
|
.handle_mark_dirty_time_window(DirtyWindowRequests {
|
||||||
|
requests: vec![req],
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.or_else(|e| {
|
||||||
|
let code = e.code();
|
||||||
|
let err: crate::error::Error = e.into();
|
||||||
|
Err(BoxedError::new(err)).context(FlowServerSnafu { addr, code })
|
||||||
|
})?
|
||||||
|
.into_inner();
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use arc_swap::ArcSwapOption;
|
|||||||
use arrow_flight::Ticket;
|
use arrow_flight::Ticket;
|
||||||
use async_stream::stream;
|
use async_stream::stream;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use common_error::ext::{BoxedError, ErrorExt};
|
use common_error::ext::BoxedError;
|
||||||
use common_error::status_code::StatusCode;
|
use common_error::status_code::StatusCode;
|
||||||
use common_grpc::flight::{FlightDecoder, FlightMessage};
|
use common_grpc::flight::{FlightDecoder, FlightMessage};
|
||||||
use common_meta::error::{self as meta_error, Result as MetaResult};
|
use common_meta::error::{self as meta_error, Result as MetaResult};
|
||||||
@@ -46,6 +46,8 @@ use crate::{metrics, Client, Error};
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RegionRequester {
|
pub struct RegionRequester {
|
||||||
client: Client,
|
client: Client,
|
||||||
|
send_compression: bool,
|
||||||
|
accept_compression: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -89,34 +91,34 @@ impl Datanode for RegionRequester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RegionRequester {
|
impl RegionRequester {
|
||||||
pub fn new(client: Client) -> Self {
|
pub fn new(client: Client, send_compression: bool, accept_compression: bool) -> Self {
|
||||||
Self { client }
|
Self {
|
||||||
|
client,
|
||||||
|
send_compression,
|
||||||
|
accept_compression,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn do_get_inner(&self, ticket: Ticket) -> Result<SendableRecordBatchStream> {
|
pub async fn do_get_inner(&self, ticket: Ticket) -> Result<SendableRecordBatchStream> {
|
||||||
let mut flight_client = self.client.make_flight_client()?;
|
let mut flight_client = self
|
||||||
|
.client
|
||||||
|
.make_flight_client(self.send_compression, self.accept_compression)?;
|
||||||
let response = flight_client
|
let response = flight_client
|
||||||
.mut_inner()
|
.mut_inner()
|
||||||
.do_get(ticket)
|
.do_get(ticket)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.or_else(|e| {
|
||||||
let tonic_code = e.code();
|
let tonic_code = e.code();
|
||||||
let e: error::Error = e.into();
|
let e: error::Error = e.into();
|
||||||
let code = e.status_code();
|
|
||||||
let msg = e.to_string();
|
|
||||||
let error = ServerSnafu { code, msg }
|
|
||||||
.fail::<()>()
|
|
||||||
.map_err(BoxedError::new)
|
|
||||||
.with_context(|_| FlightGetSnafu {
|
|
||||||
tonic_code,
|
|
||||||
addr: flight_client.addr().to_string(),
|
|
||||||
})
|
|
||||||
.unwrap_err();
|
|
||||||
error!(
|
error!(
|
||||||
e; "Failed to do Flight get, addr: {}, code: {}",
|
e; "Failed to do Flight get, addr: {}, code: {}",
|
||||||
flight_client.addr(),
|
flight_client.addr(),
|
||||||
tonic_code
|
tonic_code
|
||||||
);
|
);
|
||||||
|
let error = Err(BoxedError::new(e)).with_context(|_| FlightGetSnafu {
|
||||||
|
addr: flight_client.addr().to_string(),
|
||||||
|
tonic_code,
|
||||||
|
});
|
||||||
error
|
error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ default = [
|
|||||||
"meta-srv/pg_kvbackend",
|
"meta-srv/pg_kvbackend",
|
||||||
"meta-srv/mysql_kvbackend",
|
"meta-srv/mysql_kvbackend",
|
||||||
]
|
]
|
||||||
enterprise = ["common-meta/enterprise", "frontend/enterprise", "meta-srv/enterprise"]
|
enterprise = ["common-meta/enterprise", "frontend/enterprise", "meta-srv/enterprise", "catalog/enterprise"]
|
||||||
tokio-console = ["common-telemetry/tokio-console"]
|
tokio-console = ["common-telemetry/tokio-console"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
@@ -52,7 +52,6 @@ common-version.workspace = true
|
|||||||
common-wal.workspace = true
|
common-wal.workspace = true
|
||||||
datanode.workspace = true
|
datanode.workspace = true
|
||||||
datatypes.workspace = true
|
datatypes.workspace = true
|
||||||
either = "1.8"
|
|
||||||
etcd-client.workspace = true
|
etcd-client.workspace = true
|
||||||
file-engine.workspace = true
|
file-engine.workspace = true
|
||||||
flow.workspace = true
|
flow.workspace = true
|
||||||
@@ -67,6 +66,7 @@ metric-engine.workspace = true
|
|||||||
mito2.workspace = true
|
mito2.workspace = true
|
||||||
moka.workspace = true
|
moka.workspace = true
|
||||||
nu-ansi-term = "0.46"
|
nu-ansi-term = "0.46"
|
||||||
|
object-store.workspace = true
|
||||||
plugins.workspace = true
|
plugins.workspace = true
|
||||||
prometheus.workspace = true
|
prometheus.workspace = true
|
||||||
prost.workspace = true
|
prost.workspace = true
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ use cmd::error::{InitTlsProviderSnafu, Result};
|
|||||||
use cmd::options::GlobalOptions;
|
use cmd::options::GlobalOptions;
|
||||||
use cmd::{cli, datanode, flownode, frontend, metasrv, standalone, App};
|
use cmd::{cli, datanode, flownode, frontend, metasrv, standalone, App};
|
||||||
use common_base::Plugins;
|
use common_base::Plugins;
|
||||||
use common_version::version;
|
use common_version::{verbose_version, version};
|
||||||
use servers::install_ring_crypto_provider;
|
use servers::install_ring_crypto_provider;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "greptime", author, version, long_version = version(), about)]
|
#[command(name = "greptime", author, version, long_version = verbose_version(), about)]
|
||||||
#[command(propagate_version = true)]
|
#[command(propagate_version = true)]
|
||||||
pub(crate) struct Command {
|
pub(crate) struct Command {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
@@ -143,10 +143,8 @@ async fn start(cli: Command) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup_human_panic() {
|
fn setup_human_panic() {
|
||||||
human_panic::setup_panic!(
|
human_panic::setup_panic!(human_panic::Metadata::new("GreptimeDB", version())
|
||||||
human_panic::Metadata::new("GreptimeDB", env!("CARGO_PKG_VERSION"))
|
.homepage("https://github.com/GreptimeTeam/greptimedb/discussions"));
|
||||||
.homepage("https://github.com/GreptimeTeam/greptimedb/discussions")
|
|
||||||
);
|
|
||||||
|
|
||||||
common_telemetry::set_panic_hook();
|
common_telemetry::set_panic_hook();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ mod tests {
|
|||||||
let output_dir = tempfile::tempdir().unwrap();
|
let output_dir = tempfile::tempdir().unwrap();
|
||||||
let cli = cli::Command::parse_from([
|
let cli = cli::Command::parse_from([
|
||||||
"cli",
|
"cli",
|
||||||
|
"data",
|
||||||
"export",
|
"export",
|
||||||
"--addr",
|
"--addr",
|
||||||
"127.0.0.1:4000",
|
"127.0.0.1:4000",
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ mod tests {
|
|||||||
|
|
||||||
use common_config::ENV_VAR_SEP;
|
use common_config::ENV_VAR_SEP;
|
||||||
use common_test_util::temp_dir::create_named_temp_file;
|
use common_test_util::temp_dir::create_named_temp_file;
|
||||||
use datanode::config::{FileConfig, GcsConfig, ObjectStoreConfig, S3Config};
|
use object_store::config::{FileConfig, GcsConfig, ObjectStoreConfig, S3Config};
|
||||||
use servers::heartbeat_options::HeartbeatOptions;
|
use servers::heartbeat_options::HeartbeatOptions;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use catalog::kvbackend::MetaKvBackend;
|
|||||||
use common_base::Plugins;
|
use common_base::Plugins;
|
||||||
use common_meta::cache::LayeredCacheRegistryBuilder;
|
use common_meta::cache::LayeredCacheRegistryBuilder;
|
||||||
use common_telemetry::info;
|
use common_telemetry::info;
|
||||||
use common_version::{short_version, version};
|
use common_version::{short_version, verbose_version};
|
||||||
use datanode::datanode::DatanodeBuilder;
|
use datanode::datanode::DatanodeBuilder;
|
||||||
use datanode::service::DatanodeServiceBuilder;
|
use datanode::service::DatanodeServiceBuilder;
|
||||||
use meta_client::MetaClientType;
|
use meta_client::MetaClientType;
|
||||||
@@ -67,7 +67,7 @@ impl InstanceBuilder {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
log_versions(version(), short_version(), APP_NAME);
|
log_versions(verbose_version(), short_version(), APP_NAME);
|
||||||
create_resource_limit_metrics(APP_NAME);
|
create_resource_limit_metrics(APP_NAME);
|
||||||
|
|
||||||
plugins::setup_datanode_plugins(plugins, &opts.plugins, dn_opts)
|
plugins::setup_datanode_plugins(plugins, &opts.plugins, dn_opts)
|
||||||
@@ -93,6 +93,7 @@ impl InstanceBuilder {
|
|||||||
MetaClientType::Datanode { member_id },
|
MetaClientType::Datanode { member_id },
|
||||||
meta_client_options,
|
meta_client_options,
|
||||||
Some(&plugins),
|
Some(&plugins),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context(MetaClientInitSnafu)?;
|
.context(MetaClientInitSnafu)?;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
|
use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
|
||||||
use catalog::information_extension::DistributedInformationExtension;
|
use catalog::information_extension::DistributedInformationExtension;
|
||||||
use catalog::kvbackend::{CachedKvBackendBuilder, KvBackendCatalogManager, MetaKvBackend};
|
use catalog::kvbackend::{CachedKvBackendBuilder, KvBackendCatalogManagerBuilder, MetaKvBackend};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use client::client_manager::NodeClients;
|
use client::client_manager::NodeClients;
|
||||||
use common_base::Plugins;
|
use common_base::Plugins;
|
||||||
@@ -32,7 +32,7 @@ use common_meta::key::flow::FlowMetadataManager;
|
|||||||
use common_meta::key::TableMetadataManager;
|
use common_meta::key::TableMetadataManager;
|
||||||
use common_telemetry::info;
|
use common_telemetry::info;
|
||||||
use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
|
use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
|
||||||
use common_version::{short_version, version};
|
use common_version::{short_version, verbose_version};
|
||||||
use flow::{
|
use flow::{
|
||||||
get_flow_auth_options, FlownodeBuilder, FlownodeInstance, FlownodeServiceBuilder,
|
get_flow_auth_options, FlownodeBuilder, FlownodeInstance, FlownodeServiceBuilder,
|
||||||
FrontendClient, FrontendInvoker,
|
FrontendClient, FrontendInvoker,
|
||||||
@@ -55,14 +55,32 @@ type FlownodeOptions = GreptimeOptions<flow::FlownodeOptions>;
|
|||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
flownode: FlownodeInstance,
|
flownode: FlownodeInstance,
|
||||||
|
|
||||||
|
// The components of flownode, which make it easier to expand based
|
||||||
|
// on the components.
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
components: Components,
|
||||||
|
|
||||||
// Keep the logging guard to prevent the worker from being dropped.
|
// Keep the logging guard to prevent the worker from being dropped.
|
||||||
_guard: Vec<WorkerGuard>,
|
_guard: Vec<WorkerGuard>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub struct Components {
|
||||||
|
pub catalog_manager: catalog::CatalogManagerRef,
|
||||||
|
pub fe_client: Arc<FrontendClient>,
|
||||||
|
pub kv_backend: common_meta::kv_backend::KvBackendRef,
|
||||||
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
pub fn new(flownode: FlownodeInstance, guard: Vec<WorkerGuard>) -> Self {
|
pub fn new(
|
||||||
|
flownode: FlownodeInstance,
|
||||||
|
#[cfg(feature = "enterprise")] components: Components,
|
||||||
|
guard: Vec<WorkerGuard>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
flownode,
|
flownode,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
components,
|
||||||
_guard: guard,
|
_guard: guard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +93,11 @@ impl Instance {
|
|||||||
pub fn flownode_mut(&mut self) -> &mut FlownodeInstance {
|
pub fn flownode_mut(&mut self) -> &mut FlownodeInstance {
|
||||||
&mut self.flownode
|
&mut self.flownode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub fn components(&self) -> &Components {
|
||||||
|
&self.components
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
@@ -256,7 +279,7 @@ impl StartCommand {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
log_versions(version(), short_version(), APP_NAME);
|
log_versions(verbose_version(), short_version(), APP_NAME);
|
||||||
create_resource_limit_metrics(APP_NAME);
|
create_resource_limit_metrics(APP_NAME);
|
||||||
|
|
||||||
info!("Flownode start command: {:#?}", self);
|
info!("Flownode start command: {:#?}", self);
|
||||||
@@ -283,6 +306,7 @@ impl StartCommand {
|
|||||||
MetaClientType::Flownode { member_id },
|
MetaClientType::Flownode { member_id },
|
||||||
meta_config,
|
meta_config,
|
||||||
None,
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context(MetaClientInitSnafu)?;
|
.context(MetaClientInitSnafu)?;
|
||||||
@@ -318,12 +342,12 @@ impl StartCommand {
|
|||||||
|
|
||||||
let information_extension =
|
let information_extension =
|
||||||
Arc::new(DistributedInformationExtension::new(meta_client.clone()));
|
Arc::new(DistributedInformationExtension::new(meta_client.clone()));
|
||||||
let catalog_manager = KvBackendCatalogManager::new(
|
let catalog_manager = KvBackendCatalogManagerBuilder::new(
|
||||||
information_extension,
|
information_extension,
|
||||||
cached_meta_backend.clone(),
|
cached_meta_backend.clone(),
|
||||||
layered_cache_registry.clone(),
|
layered_cache_registry.clone(),
|
||||||
None,
|
)
|
||||||
);
|
.build();
|
||||||
|
|
||||||
let table_metadata_manager =
|
let table_metadata_manager =
|
||||||
Arc::new(TableMetadataManager::new(cached_meta_backend.clone()));
|
Arc::new(TableMetadataManager::new(cached_meta_backend.clone()));
|
||||||
@@ -346,21 +370,25 @@ impl StartCommand {
|
|||||||
|
|
||||||
let flow_metadata_manager = Arc::new(FlowMetadataManager::new(cached_meta_backend.clone()));
|
let flow_metadata_manager = Arc::new(FlowMetadataManager::new(cached_meta_backend.clone()));
|
||||||
let flow_auth_header = get_flow_auth_options(&opts).context(StartFlownodeSnafu)?;
|
let flow_auth_header = get_flow_auth_options(&opts).context(StartFlownodeSnafu)?;
|
||||||
let frontend_client =
|
let frontend_client = FrontendClient::from_meta_client(
|
||||||
FrontendClient::from_meta_client(meta_client.clone(), flow_auth_header);
|
meta_client.clone(),
|
||||||
|
flow_auth_header,
|
||||||
|
opts.query.clone(),
|
||||||
|
);
|
||||||
|
let frontend_client = Arc::new(frontend_client);
|
||||||
let flownode_builder = FlownodeBuilder::new(
|
let flownode_builder = FlownodeBuilder::new(
|
||||||
opts.clone(),
|
opts.clone(),
|
||||||
plugins,
|
plugins,
|
||||||
table_metadata_manager,
|
table_metadata_manager,
|
||||||
catalog_manager.clone(),
|
catalog_manager.clone(),
|
||||||
flow_metadata_manager,
|
flow_metadata_manager,
|
||||||
Arc::new(frontend_client),
|
frontend_client.clone(),
|
||||||
)
|
)
|
||||||
.with_heartbeat_task(heartbeat_task);
|
.with_heartbeat_task(heartbeat_task);
|
||||||
|
|
||||||
let mut flownode = flownode_builder.build().await.context(StartFlownodeSnafu)?;
|
let mut flownode = flownode_builder.build().await.context(StartFlownodeSnafu)?;
|
||||||
let services = FlownodeServiceBuilder::new(&opts)
|
let services = FlownodeServiceBuilder::new(&opts)
|
||||||
.with_grpc_server(flownode.flownode_server().clone())
|
.with_default_grpc_server(flownode.flownode_server())
|
||||||
.enable_http_service()
|
.enable_http_service()
|
||||||
.build()
|
.build()
|
||||||
.context(StartFlownodeSnafu)?;
|
.context(StartFlownodeSnafu)?;
|
||||||
@@ -392,6 +420,16 @@ impl StartCommand {
|
|||||||
.set_frontend_invoker(invoker)
|
.set_frontend_invoker(invoker)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(Instance::new(flownode, guard))
|
#[cfg(feature = "enterprise")]
|
||||||
|
let components = Components {
|
||||||
|
catalog_manager: catalog_manager.clone(),
|
||||||
|
fe_client: frontend_client,
|
||||||
|
kv_backend: cached_meta_backend,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "enterprise"))]
|
||||||
|
return Ok(Instance::new(flownode, guard));
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
Ok(Instance::new(flownode, components, guard))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ use std::time::Duration;
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
|
use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
|
||||||
use catalog::information_extension::DistributedInformationExtension;
|
use catalog::information_extension::DistributedInformationExtension;
|
||||||
use catalog::kvbackend::{CachedKvBackendBuilder, KvBackendCatalogManager, MetaKvBackend};
|
use catalog::kvbackend::{CachedKvBackendBuilder, KvBackendCatalogManagerBuilder, MetaKvBackend};
|
||||||
|
use catalog::process_manager::ProcessManager;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use client::client_manager::NodeClients;
|
use client::client_manager::NodeClients;
|
||||||
use common_base::Plugins;
|
use common_base::Plugins;
|
||||||
@@ -32,12 +33,13 @@ use common_meta::heartbeat::handler::HandlerGroupExecutor;
|
|||||||
use common_telemetry::info;
|
use common_telemetry::info;
|
||||||
use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
|
use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
|
||||||
use common_time::timezone::set_default_timezone;
|
use common_time::timezone::set_default_timezone;
|
||||||
use common_version::{short_version, version};
|
use common_version::{short_version, verbose_version};
|
||||||
use frontend::frontend::Frontend;
|
use frontend::frontend::Frontend;
|
||||||
use frontend::heartbeat::HeartbeatTask;
|
use frontend::heartbeat::HeartbeatTask;
|
||||||
use frontend::instance::builder::FrontendBuilder;
|
use frontend::instance::builder::FrontendBuilder;
|
||||||
use frontend::server::Services;
|
use frontend::server::Services;
|
||||||
use meta_client::{MetaClientOptions, MetaClientType};
|
use meta_client::{MetaClientOptions, MetaClientType};
|
||||||
|
use servers::addrs;
|
||||||
use servers::export_metrics::ExportMetricsTask;
|
use servers::export_metrics::ExportMetricsTask;
|
||||||
use servers::tls::{TlsMode, TlsOption};
|
use servers::tls::{TlsMode, TlsOption};
|
||||||
use snafu::{OptionExt, ResultExt};
|
use snafu::{OptionExt, ResultExt};
|
||||||
@@ -100,7 +102,7 @@ impl App for Instance {
|
|||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
subcmd: SubCommand,
|
pub subcmd: SubCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
@@ -114,7 +116,7 @@ impl Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
enum SubCommand {
|
pub enum SubCommand {
|
||||||
Start(StartCommand),
|
Start(StartCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +153,7 @@ pub struct StartCommand {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
postgres_addr: Option<String>,
|
postgres_addr: Option<String>,
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
config_file: Option<String>,
|
pub config_file: Option<String>,
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
influxdb_enable: Option<bool>,
|
influxdb_enable: Option<bool>,
|
||||||
#[clap(long, value_delimiter = ',', num_args = 1..)]
|
#[clap(long, value_delimiter = ',', num_args = 1..)]
|
||||||
@@ -167,7 +169,7 @@ pub struct StartCommand {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
disable_dashboard: Option<bool>,
|
disable_dashboard: Option<bool>,
|
||||||
#[clap(long, default_value = "GREPTIMEDB_FRONTEND")]
|
#[clap(long, default_value = "GREPTIMEDB_FRONTEND")]
|
||||||
env_prefix: String,
|
pub env_prefix: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StartCommand {
|
impl StartCommand {
|
||||||
@@ -280,7 +282,7 @@ impl StartCommand {
|
|||||||
opts.component.slow_query.as_ref(),
|
opts.component.slow_query.as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
log_versions(version(), short_version(), APP_NAME);
|
log_versions(verbose_version(), short_version(), APP_NAME);
|
||||||
create_resource_limit_metrics(APP_NAME);
|
create_resource_limit_metrics(APP_NAME);
|
||||||
|
|
||||||
info!("Frontend start command: {:#?}", self);
|
info!("Frontend start command: {:#?}", self);
|
||||||
@@ -311,6 +313,7 @@ impl StartCommand {
|
|||||||
MetaClientType::Frontend,
|
MetaClientType::Frontend,
|
||||||
meta_client_options,
|
meta_client_options,
|
||||||
Some(&plugins),
|
Some(&plugins),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context(error::MetaClientInitSnafu)?;
|
.context(error::MetaClientInitSnafu)?;
|
||||||
@@ -342,12 +345,25 @@ impl StartCommand {
|
|||||||
|
|
||||||
let information_extension =
|
let information_extension =
|
||||||
Arc::new(DistributedInformationExtension::new(meta_client.clone()));
|
Arc::new(DistributedInformationExtension::new(meta_client.clone()));
|
||||||
let catalog_manager = KvBackendCatalogManager::new(
|
|
||||||
|
let process_manager = Arc::new(ProcessManager::new(
|
||||||
|
addrs::resolve_addr(&opts.grpc.bind_addr, Some(&opts.grpc.server_addr)),
|
||||||
|
Some(meta_client.clone()),
|
||||||
|
));
|
||||||
|
|
||||||
|
let builder = KvBackendCatalogManagerBuilder::new(
|
||||||
information_extension,
|
information_extension,
|
||||||
cached_meta_backend.clone(),
|
cached_meta_backend.clone(),
|
||||||
layered_cache_registry.clone(),
|
layered_cache_registry.clone(),
|
||||||
None,
|
)
|
||||||
);
|
.with_process_manager(process_manager.clone());
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
let builder = if let Some(factories) = plugins.get() {
|
||||||
|
builder.with_extra_information_table_factories(factories)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
let catalog_manager = builder.build();
|
||||||
|
|
||||||
let executor = HandlerGroupExecutor::new(vec![
|
let executor = HandlerGroupExecutor::new(vec![
|
||||||
Arc::new(ParseMailboxMessageHandler),
|
Arc::new(ParseMailboxMessageHandler),
|
||||||
@@ -364,12 +380,16 @@ impl StartCommand {
|
|||||||
|
|
||||||
// frontend to datanode need not timeout.
|
// frontend to datanode need not timeout.
|
||||||
// Some queries are expected to take long time.
|
// Some queries are expected to take long time.
|
||||||
let channel_config = ChannelConfig {
|
let mut channel_config = ChannelConfig {
|
||||||
timeout: None,
|
timeout: None,
|
||||||
tcp_nodelay: opts.datanode.client.tcp_nodelay,
|
tcp_nodelay: opts.datanode.client.tcp_nodelay,
|
||||||
connect_timeout: Some(opts.datanode.client.connect_timeout),
|
connect_timeout: Some(opts.datanode.client.connect_timeout),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
if opts.grpc.flight_compression.transport_compression() {
|
||||||
|
channel_config.accept_compression = true;
|
||||||
|
channel_config.send_compression = true;
|
||||||
|
}
|
||||||
let client = NodeClients::new(channel_config);
|
let client = NodeClients::new(channel_config);
|
||||||
|
|
||||||
let instance = FrontendBuilder::new(
|
let instance = FrontendBuilder::new(
|
||||||
@@ -379,6 +399,7 @@ impl StartCommand {
|
|||||||
catalog_manager,
|
catalog_manager,
|
||||||
Arc::new(client),
|
Arc::new(client),
|
||||||
meta_client,
|
meta_client,
|
||||||
|
process_manager,
|
||||||
)
|
)
|
||||||
.with_plugin(plugins.clone())
|
.with_plugin(plugins.clone())
|
||||||
.with_local_cache_invalidator(layered_cache_registry)
|
.with_local_cache_invalidator(layered_cache_registry)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ pub trait App: Send {
|
|||||||
pub fn log_versions(version: &str, short_version: &str, app: &str) {
|
pub fn log_versions(version: &str, short_version: &str, app: &str) {
|
||||||
// Report app version as gauge.
|
// Report app version as gauge.
|
||||||
APP_VERSION
|
APP_VERSION
|
||||||
.with_label_values(&[env!("CARGO_PKG_VERSION"), short_version, app])
|
.with_label_values(&[common_version::version(), short_version, app])
|
||||||
.inc();
|
.inc();
|
||||||
|
|
||||||
// Log version and argument flags.
|
// Log version and argument flags.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use common_base::Plugins;
|
|||||||
use common_config::Configurable;
|
use common_config::Configurable;
|
||||||
use common_telemetry::info;
|
use common_telemetry::info;
|
||||||
use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
|
use common_telemetry::logging::{TracingOptions, DEFAULT_LOGGING_DIR};
|
||||||
use common_version::{short_version, version};
|
use common_version::{short_version, verbose_version};
|
||||||
use meta_srv::bootstrap::MetasrvInstance;
|
use meta_srv::bootstrap::MetasrvInstance;
|
||||||
use meta_srv::metasrv::BackendImpl;
|
use meta_srv::metasrv::BackendImpl;
|
||||||
use snafu::ResultExt;
|
use snafu::ResultExt;
|
||||||
@@ -54,6 +54,10 @@ impl Instance {
|
|||||||
pub fn get_inner(&self) -> &MetasrvInstance {
|
pub fn get_inner(&self) -> &MetasrvInstance {
|
||||||
&self.instance
|
&self.instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mut_inner(&mut self) -> &mut MetasrvInstance {
|
||||||
|
&mut self.instance
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -320,7 +324,7 @@ impl StartCommand {
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
log_versions(version(), short_version(), APP_NAME);
|
log_versions(verbose_version(), short_version(), APP_NAME);
|
||||||
create_resource_limit_metrics(APP_NAME);
|
create_resource_limit_metrics(APP_NAME);
|
||||||
|
|
||||||
info!("Metasrv start command: {:#?}", self);
|
info!("Metasrv start command: {:#?}", self);
|
||||||
@@ -336,12 +340,12 @@ impl StartCommand {
|
|||||||
.await
|
.await
|
||||||
.context(StartMetaServerSnafu)?;
|
.context(StartMetaServerSnafu)?;
|
||||||
|
|
||||||
let builder = meta_srv::bootstrap::metasrv_builder(&opts, plugins.clone(), None)
|
let builder = meta_srv::bootstrap::metasrv_builder(&opts, plugins, None)
|
||||||
.await
|
.await
|
||||||
.context(error::BuildMetaServerSnafu)?;
|
.context(error::BuildMetaServerSnafu)?;
|
||||||
let metasrv = builder.build().await.context(error::BuildMetaServerSnafu)?;
|
let metasrv = builder.build().await.context(error::BuildMetaServerSnafu)?;
|
||||||
|
|
||||||
let instance = MetasrvInstance::new(opts, plugins, metasrv)
|
let instance = MetasrvInstance::new(metasrv)
|
||||||
.await
|
.await
|
||||||
.context(error::BuildMetaServerSnafu)?;
|
.context(error::BuildMetaServerSnafu)?;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ use std::{fs, path};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
|
use cache::{build_fundamental_cache_registry, with_default_composite_cache_registry};
|
||||||
use catalog::information_schema::InformationExtension;
|
use catalog::information_schema::InformationExtension;
|
||||||
use catalog::kvbackend::KvBackendCatalogManager;
|
use catalog::kvbackend::KvBackendCatalogManagerBuilder;
|
||||||
|
use catalog::process_manager::ProcessManager;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use client::api::v1::meta::RegionRole;
|
use client::api::v1::meta::RegionRole;
|
||||||
use common_base::readable_size::ReadableSize;
|
use common_base::readable_size::ReadableSize;
|
||||||
@@ -29,20 +30,16 @@ use common_catalog::consts::{MIN_USER_FLOW_ID, MIN_USER_TABLE_ID};
|
|||||||
use common_config::{metadata_store_dir, Configurable, KvBackendConfig};
|
use common_config::{metadata_store_dir, Configurable, KvBackendConfig};
|
||||||
use common_error::ext::BoxedError;
|
use common_error::ext::BoxedError;
|
||||||
use common_meta::cache::LayeredCacheRegistryBuilder;
|
use common_meta::cache::LayeredCacheRegistryBuilder;
|
||||||
use common_meta::cache_invalidator::CacheInvalidatorRef;
|
|
||||||
use common_meta::cluster::{NodeInfo, NodeStatus};
|
use common_meta::cluster::{NodeInfo, NodeStatus};
|
||||||
use common_meta::datanode::RegionStat;
|
use common_meta::datanode::RegionStat;
|
||||||
use common_meta::ddl::flow_meta::{FlowMetadataAllocator, FlowMetadataAllocatorRef};
|
use common_meta::ddl::flow_meta::FlowMetadataAllocator;
|
||||||
use common_meta::ddl::table_meta::{TableMetadataAllocator, TableMetadataAllocatorRef};
|
use common_meta::ddl::table_meta::TableMetadataAllocator;
|
||||||
use common_meta::ddl::{DdlContext, NoopRegionFailureDetectorControl, ProcedureExecutorRef};
|
use common_meta::ddl::{DdlContext, NoopRegionFailureDetectorControl, ProcedureExecutorRef};
|
||||||
use common_meta::ddl_manager::DdlManager;
|
use common_meta::ddl_manager::DdlManager;
|
||||||
#[cfg(feature = "enterprise")]
|
|
||||||
use common_meta::ddl_manager::TriggerDdlManagerRef;
|
|
||||||
use common_meta::key::flow::flow_state::FlowStat;
|
use common_meta::key::flow::flow_state::FlowStat;
|
||||||
use common_meta::key::flow::{FlowMetadataManager, FlowMetadataManagerRef};
|
use common_meta::key::flow::FlowMetadataManager;
|
||||||
use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
|
use common_meta::key::{TableMetadataManager, TableMetadataManagerRef};
|
||||||
use common_meta::kv_backend::KvBackendRef;
|
use common_meta::kv_backend::KvBackendRef;
|
||||||
use common_meta::node_manager::NodeManagerRef;
|
|
||||||
use common_meta::peer::Peer;
|
use common_meta::peer::Peer;
|
||||||
use common_meta::region_keeper::MemoryRegionKeeper;
|
use common_meta::region_keeper::MemoryRegionKeeper;
|
||||||
use common_meta::region_registry::LeaderRegionRegistry;
|
use common_meta::region_registry::LeaderRegionRegistry;
|
||||||
@@ -54,7 +51,7 @@ use common_telemetry::logging::{
|
|||||||
LoggingOptions, SlowQueryOptions, TracingOptions, DEFAULT_LOGGING_DIR,
|
LoggingOptions, SlowQueryOptions, TracingOptions, DEFAULT_LOGGING_DIR,
|
||||||
};
|
};
|
||||||
use common_time::timezone::set_default_timezone;
|
use common_time::timezone::set_default_timezone;
|
||||||
use common_version::{short_version, version};
|
use common_version::{short_version, verbose_version};
|
||||||
use common_wal::config::DatanodeWalConfig;
|
use common_wal::config::DatanodeWalConfig;
|
||||||
use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig};
|
use datanode::config::{DatanodeOptions, ProcedureConfig, RegionEngineConfig, StorageConfig};
|
||||||
use datanode::datanode::{Datanode, DatanodeBuilder};
|
use datanode::datanode::{Datanode, DatanodeBuilder};
|
||||||
@@ -260,15 +257,34 @@ pub struct Instance {
|
|||||||
flownode: FlownodeInstance,
|
flownode: FlownodeInstance,
|
||||||
procedure_manager: ProcedureManagerRef,
|
procedure_manager: ProcedureManagerRef,
|
||||||
wal_options_allocator: WalOptionsAllocatorRef,
|
wal_options_allocator: WalOptionsAllocatorRef,
|
||||||
|
|
||||||
|
// The components of standalone, which make it easier to expand based
|
||||||
|
// on the components.
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
components: Components,
|
||||||
|
|
||||||
// Keep the logging guard to prevent the worker from being dropped.
|
// Keep the logging guard to prevent the worker from being dropped.
|
||||||
_guard: Vec<WorkerGuard>,
|
_guard: Vec<WorkerGuard>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub struct Components {
|
||||||
|
pub plugins: Plugins,
|
||||||
|
pub kv_backend: KvBackendRef,
|
||||||
|
pub frontend_client: Arc<FrontendClient>,
|
||||||
|
pub catalog_manager: catalog::CatalogManagerRef,
|
||||||
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
/// Find the socket addr of a server by its `name`.
|
/// Find the socket addr of a server by its `name`.
|
||||||
pub fn server_addr(&self, name: &str) -> Option<SocketAddr> {
|
pub fn server_addr(&self, name: &str) -> Option<SocketAddr> {
|
||||||
self.frontend.server_handlers().addr(name)
|
self.frontend.server_handlers().addr(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
pub fn components(&self) -> &Components {
|
||||||
|
&self.components
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@@ -469,7 +485,7 @@ impl StartCommand {
|
|||||||
opts.component.slow_query.as_ref(),
|
opts.component.slow_query.as_ref(),
|
||||||
);
|
);
|
||||||
|
|
||||||
log_versions(version(), short_version(), APP_NAME);
|
log_versions(verbose_version(), short_version(), APP_NAME);
|
||||||
create_resource_limit_metrics(APP_NAME);
|
create_resource_limit_metrics(APP_NAME);
|
||||||
|
|
||||||
info!("Standalone start command: {:#?}", self);
|
info!("Standalone start command: {:#?}", self);
|
||||||
@@ -526,12 +542,22 @@ impl StartCommand {
|
|||||||
datanode.region_server(),
|
datanode.region_server(),
|
||||||
procedure_manager.clone(),
|
procedure_manager.clone(),
|
||||||
));
|
));
|
||||||
let catalog_manager = KvBackendCatalogManager::new(
|
|
||||||
|
let process_manager = Arc::new(ProcessManager::new(opts.grpc.server_addr.clone(), None));
|
||||||
|
let builder = KvBackendCatalogManagerBuilder::new(
|
||||||
information_extension.clone(),
|
information_extension.clone(),
|
||||||
kv_backend.clone(),
|
kv_backend.clone(),
|
||||||
layered_cache_registry.clone(),
|
layered_cache_registry.clone(),
|
||||||
Some(procedure_manager.clone()),
|
)
|
||||||
);
|
.with_procedure_manager(procedure_manager.clone())
|
||||||
|
.with_process_manager(process_manager.clone());
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
let builder = if let Some(factories) = plugins.get() {
|
||||||
|
builder.with_extra_information_table_factories(factories)
|
||||||
|
} else {
|
||||||
|
builder
|
||||||
|
};
|
||||||
|
let catalog_manager = builder.build();
|
||||||
|
|
||||||
let table_metadata_manager =
|
let table_metadata_manager =
|
||||||
Self::create_table_metadata_manager(kv_backend.clone()).await?;
|
Self::create_table_metadata_manager(kv_backend.clone()).await?;
|
||||||
@@ -545,14 +571,15 @@ impl StartCommand {
|
|||||||
// for standalone not use grpc, but get a handler to frontend grpc client without
|
// for standalone not use grpc, but get a handler to frontend grpc client without
|
||||||
// actually make a connection
|
// actually make a connection
|
||||||
let (frontend_client, frontend_instance_handler) =
|
let (frontend_client, frontend_instance_handler) =
|
||||||
FrontendClient::from_empty_grpc_handler();
|
FrontendClient::from_empty_grpc_handler(opts.query.clone());
|
||||||
|
let frontend_client = Arc::new(frontend_client);
|
||||||
let flow_builder = FlownodeBuilder::new(
|
let flow_builder = FlownodeBuilder::new(
|
||||||
flownode_options,
|
flownode_options,
|
||||||
plugins.clone(),
|
plugins.clone(),
|
||||||
table_metadata_manager.clone(),
|
table_metadata_manager.clone(),
|
||||||
catalog_manager.clone(),
|
catalog_manager.clone(),
|
||||||
flow_metadata_manager.clone(),
|
flow_metadata_manager.clone(),
|
||||||
Arc::new(frontend_client.clone()),
|
frontend_client.clone(),
|
||||||
);
|
);
|
||||||
let flownode = flow_builder
|
let flownode = flow_builder
|
||||||
.build()
|
.build()
|
||||||
@@ -590,28 +617,36 @@ impl StartCommand {
|
|||||||
.await
|
.await
|
||||||
.context(error::BuildWalOptionsAllocatorSnafu)?;
|
.context(error::BuildWalOptionsAllocatorSnafu)?;
|
||||||
let wal_options_allocator = Arc::new(wal_options_allocator);
|
let wal_options_allocator = Arc::new(wal_options_allocator);
|
||||||
let table_meta_allocator = Arc::new(TableMetadataAllocator::new(
|
let table_metadata_allocator = Arc::new(TableMetadataAllocator::new(
|
||||||
table_id_sequence,
|
table_id_sequence,
|
||||||
wal_options_allocator.clone(),
|
wal_options_allocator.clone(),
|
||||||
));
|
));
|
||||||
let flow_meta_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
|
let flow_metadata_allocator = Arc::new(FlowMetadataAllocator::with_noop_peer_allocator(
|
||||||
flow_id_sequence,
|
flow_id_sequence,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let ddl_context = DdlContext {
|
||||||
|
node_manager: node_manager.clone(),
|
||||||
|
cache_invalidator: layered_cache_registry.clone(),
|
||||||
|
memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
|
||||||
|
leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
|
||||||
|
table_metadata_manager: table_metadata_manager.clone(),
|
||||||
|
table_metadata_allocator: table_metadata_allocator.clone(),
|
||||||
|
flow_metadata_manager: flow_metadata_manager.clone(),
|
||||||
|
flow_metadata_allocator: flow_metadata_allocator.clone(),
|
||||||
|
region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
|
||||||
|
};
|
||||||
|
let procedure_manager_c = procedure_manager.clone();
|
||||||
|
|
||||||
|
let ddl_manager = DdlManager::try_new(ddl_context, procedure_manager_c, true)
|
||||||
|
.context(error::InitDdlManagerSnafu)?;
|
||||||
#[cfg(feature = "enterprise")]
|
#[cfg(feature = "enterprise")]
|
||||||
let trigger_ddl_manager: Option<TriggerDdlManagerRef> = plugins.get();
|
let ddl_manager = {
|
||||||
let ddl_task_executor = Self::create_ddl_task_executor(
|
let trigger_ddl_manager: Option<common_meta::ddl_manager::TriggerDdlManagerRef> =
|
||||||
procedure_manager.clone(),
|
plugins.get();
|
||||||
node_manager.clone(),
|
ddl_manager.with_trigger_ddl_manager(trigger_ddl_manager)
|
||||||
layered_cache_registry.clone(),
|
};
|
||||||
table_metadata_manager,
|
let ddl_task_executor: ProcedureExecutorRef = Arc::new(ddl_manager);
|
||||||
table_meta_allocator,
|
|
||||||
flow_metadata_manager,
|
|
||||||
flow_meta_allocator,
|
|
||||||
#[cfg(feature = "enterprise")]
|
|
||||||
trigger_ddl_manager,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let fe_instance = FrontendBuilder::new(
|
let fe_instance = FrontendBuilder::new(
|
||||||
fe_opts.clone(),
|
fe_opts.clone(),
|
||||||
@@ -620,6 +655,7 @@ impl StartCommand {
|
|||||||
catalog_manager.clone(),
|
catalog_manager.clone(),
|
||||||
node_manager.clone(),
|
node_manager.clone(),
|
||||||
ddl_task_executor.clone(),
|
ddl_task_executor.clone(),
|
||||||
|
process_manager,
|
||||||
)
|
)
|
||||||
.with_plugin(plugins.clone())
|
.with_plugin(plugins.clone())
|
||||||
.try_build()
|
.try_build()
|
||||||
@@ -647,13 +683,13 @@ impl StartCommand {
|
|||||||
node_manager,
|
node_manager,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context(error::StartFlownodeSnafu)?;
|
.context(StartFlownodeSnafu)?;
|
||||||
flow_streaming_engine.set_frontend_invoker(invoker).await;
|
flow_streaming_engine.set_frontend_invoker(invoker).await;
|
||||||
|
|
||||||
let export_metrics_task = ExportMetricsTask::try_new(&opts.export_metrics, Some(&plugins))
|
let export_metrics_task = ExportMetricsTask::try_new(&opts.export_metrics, Some(&plugins))
|
||||||
.context(error::ServersSnafu)?;
|
.context(error::ServersSnafu)?;
|
||||||
|
|
||||||
let servers = Services::new(opts, fe_instance.clone(), plugins)
|
let servers = Services::new(opts, fe_instance.clone(), plugins.clone())
|
||||||
.build()
|
.build()
|
||||||
.context(error::StartFrontendSnafu)?;
|
.context(error::StartFrontendSnafu)?;
|
||||||
|
|
||||||
@@ -664,51 +700,26 @@ impl StartCommand {
|
|||||||
export_metrics_task,
|
export_metrics_task,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
let components = Components {
|
||||||
|
plugins,
|
||||||
|
kv_backend,
|
||||||
|
frontend_client,
|
||||||
|
catalog_manager,
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Instance {
|
Ok(Instance {
|
||||||
datanode,
|
datanode,
|
||||||
frontend,
|
frontend,
|
||||||
flownode,
|
flownode,
|
||||||
procedure_manager,
|
procedure_manager,
|
||||||
wal_options_allocator,
|
wal_options_allocator,
|
||||||
|
#[cfg(feature = "enterprise")]
|
||||||
|
components,
|
||||||
_guard: guard,
|
_guard: guard,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub async fn create_ddl_task_executor(
|
|
||||||
procedure_manager: ProcedureManagerRef,
|
|
||||||
node_manager: NodeManagerRef,
|
|
||||||
cache_invalidator: CacheInvalidatorRef,
|
|
||||||
table_metadata_manager: TableMetadataManagerRef,
|
|
||||||
table_metadata_allocator: TableMetadataAllocatorRef,
|
|
||||||
flow_metadata_manager: FlowMetadataManagerRef,
|
|
||||||
flow_metadata_allocator: FlowMetadataAllocatorRef,
|
|
||||||
#[cfg(feature = "enterprise")] trigger_ddl_manager: Option<TriggerDdlManagerRef>,
|
|
||||||
) -> Result<ProcedureExecutorRef> {
|
|
||||||
let procedure_executor: ProcedureExecutorRef = Arc::new(
|
|
||||||
DdlManager::try_new(
|
|
||||||
DdlContext {
|
|
||||||
node_manager,
|
|
||||||
cache_invalidator,
|
|
||||||
memory_region_keeper: Arc::new(MemoryRegionKeeper::default()),
|
|
||||||
leader_region_registry: Arc::new(LeaderRegionRegistry::default()),
|
|
||||||
table_metadata_manager,
|
|
||||||
table_metadata_allocator,
|
|
||||||
flow_metadata_manager,
|
|
||||||
flow_metadata_allocator,
|
|
||||||
region_failure_detector_controller: Arc::new(NoopRegionFailureDetectorControl),
|
|
||||||
},
|
|
||||||
procedure_manager,
|
|
||||||
true,
|
|
||||||
#[cfg(feature = "enterprise")]
|
|
||||||
trigger_ddl_manager,
|
|
||||||
)
|
|
||||||
.context(error::InitDdlManagerSnafu)?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(procedure_executor)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create_table_metadata_manager(
|
pub async fn create_table_metadata_manager(
|
||||||
kv_backend: KvBackendRef,
|
kv_backend: KvBackendRef,
|
||||||
) -> Result<TableMetadataManagerRef> {
|
) -> Result<TableMetadataManagerRef> {
|
||||||
@@ -844,7 +855,7 @@ mod tests {
|
|||||||
use common_config::ENV_VAR_SEP;
|
use common_config::ENV_VAR_SEP;
|
||||||
use common_test_util::temp_dir::create_named_temp_file;
|
use common_test_util::temp_dir::create_named_temp_file;
|
||||||
use common_wal::config::DatanodeWalConfig;
|
use common_wal::config::DatanodeWalConfig;
|
||||||
use datanode::config::{FileConfig, GcsConfig};
|
use object_store::config::{FileConfig, GcsConfig};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::options::GlobalOptions;
|
use crate::options::GlobalOptions;
|
||||||
@@ -963,15 +974,15 @@ mod tests {
|
|||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
&dn_opts.storage.store,
|
&dn_opts.storage.store,
|
||||||
datanode::config::ObjectStoreConfig::File(FileConfig { .. })
|
object_store::config::ObjectStoreConfig::File(FileConfig { .. })
|
||||||
));
|
));
|
||||||
assert_eq!(dn_opts.storage.providers.len(), 2);
|
assert_eq!(dn_opts.storage.providers.len(), 2);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
dn_opts.storage.providers[0],
|
dn_opts.storage.providers[0],
|
||||||
datanode::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
|
object_store::config::ObjectStoreConfig::Gcs(GcsConfig { .. })
|
||||||
));
|
));
|
||||||
match &dn_opts.storage.providers[1] {
|
match &dn_opts.storage.providers[1] {
|
||||||
datanode::config::ObjectStoreConfig::S3(s3_config) => {
|
object_store::config::ObjectStoreConfig::S3(s3_config) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"SecretBox<alloc::string::String>([REDACTED])".to_string(),
|
"SecretBox<alloc::string::String>([REDACTED])".to_string(),
|
||||||
format!("{:?}", s3_config.access_key_id)
|
format!("{:?}", s3_config.access_key_id)
|
||||||
|
|||||||
@@ -12,24 +12,25 @@
|
|||||||
// 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::path::Path;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use cmd::options::GreptimeOptions;
|
use cmd::options::GreptimeOptions;
|
||||||
use cmd::standalone::StandaloneOptions;
|
use cmd::standalone::StandaloneOptions;
|
||||||
use common_config::{Configurable, DEFAULT_DATA_HOME};
|
use common_config::{Configurable, DEFAULT_DATA_HOME};
|
||||||
use common_options::datanode::{ClientOptions, DatanodeClientOptions};
|
use common_options::datanode::{ClientOptions, DatanodeClientOptions};
|
||||||
use common_telemetry::logging::{LoggingOptions, DEFAULT_LOGGING_DIR, DEFAULT_OTLP_ENDPOINT};
|
use common_telemetry::logging::{LoggingOptions, DEFAULT_LOGGING_DIR, DEFAULT_OTLP_HTTP_ENDPOINT};
|
||||||
use common_wal::config::raft_engine::RaftEngineConfig;
|
use common_wal::config::raft_engine::RaftEngineConfig;
|
||||||
use common_wal::config::DatanodeWalConfig;
|
use common_wal::config::DatanodeWalConfig;
|
||||||
use datanode::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
|
use datanode::config::{DatanodeOptions, RegionEngineConfig, StorageConfig};
|
||||||
use file_engine::config::EngineConfig as FileEngineConfig;
|
use file_engine::config::EngineConfig as FileEngineConfig;
|
||||||
|
use flow::FlownodeOptions;
|
||||||
use frontend::frontend::FrontendOptions;
|
use frontend::frontend::FrontendOptions;
|
||||||
use meta_client::MetaClientOptions;
|
use meta_client::MetaClientOptions;
|
||||||
use meta_srv::metasrv::MetasrvOptions;
|
use meta_srv::metasrv::MetasrvOptions;
|
||||||
use meta_srv::selector::SelectorType;
|
use meta_srv::selector::SelectorType;
|
||||||
use metric_engine::config::EngineConfig as MetricEngineConfig;
|
use metric_engine::config::EngineConfig as MetricEngineConfig;
|
||||||
use mito2::config::MitoConfig;
|
use mito2::config::MitoConfig;
|
||||||
|
use query::options::QueryOptions;
|
||||||
use servers::export_metrics::ExportMetricsOption;
|
use servers::export_metrics::ExportMetricsOption;
|
||||||
use servers::grpc::GrpcOptions;
|
use servers::grpc::GrpcOptions;
|
||||||
use servers::http::HttpOptions;
|
use servers::http::HttpOptions;
|
||||||
@@ -58,12 +59,7 @@ fn test_load_datanode_example_config() {
|
|||||||
metadata_cache_tti: Duration::from_secs(300),
|
metadata_cache_tti: Duration::from_secs(300),
|
||||||
}),
|
}),
|
||||||
wal: DatanodeWalConfig::RaftEngine(RaftEngineConfig {
|
wal: DatanodeWalConfig::RaftEngine(RaftEngineConfig {
|
||||||
dir: Some(
|
dir: Some(format!("{}/{}", DEFAULT_DATA_HOME, WAL_DIR)),
|
||||||
Path::new(DEFAULT_DATA_HOME)
|
|
||||||
.join(WAL_DIR)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
sync_period: Some(Duration::from_secs(10)),
|
sync_period: Some(Duration::from_secs(10)),
|
||||||
recovery_parallelism: 2,
|
recovery_parallelism: 2,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -86,11 +82,8 @@ fn test_load_datanode_example_config() {
|
|||||||
],
|
],
|
||||||
logging: LoggingOptions {
|
logging: LoggingOptions {
|
||||||
level: Some("info".to_string()),
|
level: Some("info".to_string()),
|
||||||
dir: Path::new(DEFAULT_DATA_HOME)
|
dir: format!("{}/{}", DEFAULT_DATA_HOME, DEFAULT_LOGGING_DIR),
|
||||||
.join(DEFAULT_LOGGING_DIR)
|
otlp_endpoint: Some(DEFAULT_OTLP_HTTP_ENDPOINT.to_string()),
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
otlp_endpoint: Some(DEFAULT_OTLP_ENDPOINT.to_string()),
|
|
||||||
tracing_sample_ratio: Some(Default::default()),
|
tracing_sample_ratio: Some(Default::default()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -132,11 +125,8 @@ fn test_load_frontend_example_config() {
|
|||||||
}),
|
}),
|
||||||
logging: LoggingOptions {
|
logging: LoggingOptions {
|
||||||
level: Some("info".to_string()),
|
level: Some("info".to_string()),
|
||||||
dir: Path::new(DEFAULT_DATA_HOME)
|
dir: format!("{}/{}", DEFAULT_DATA_HOME, DEFAULT_LOGGING_DIR),
|
||||||
.join(DEFAULT_LOGGING_DIR)
|
otlp_endpoint: Some(DEFAULT_OTLP_HTTP_ENDPOINT.to_string()),
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
otlp_endpoint: Some(DEFAULT_OTLP_ENDPOINT.to_string()),
|
|
||||||
tracing_sample_ratio: Some(Default::default()),
|
tracing_sample_ratio: Some(Default::default()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -182,12 +172,9 @@ fn test_load_metasrv_example_config() {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
logging: LoggingOptions {
|
logging: LoggingOptions {
|
||||||
dir: Path::new(DEFAULT_DATA_HOME)
|
dir: format!("{}/{}", DEFAULT_DATA_HOME, DEFAULT_LOGGING_DIR),
|
||||||
.join(DEFAULT_LOGGING_DIR)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
level: Some("info".to_string()),
|
level: Some("info".to_string()),
|
||||||
otlp_endpoint: Some(DEFAULT_OTLP_ENDPOINT.to_string()),
|
otlp_endpoint: Some(DEFAULT_OTLP_HTTP_ENDPOINT.to_string()),
|
||||||
tracing_sample_ratio: Some(Default::default()),
|
tracing_sample_ratio: Some(Default::default()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -210,6 +197,57 @@ fn test_load_metasrv_example_config() {
|
|||||||
similar_asserts::assert_eq!(options, expected);
|
similar_asserts::assert_eq!(options, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_load_flownode_example_config() {
|
||||||
|
let example_config = common_test_util::find_workspace_path("config/flownode.example.toml");
|
||||||
|
let options =
|
||||||
|
GreptimeOptions::<FlownodeOptions>::load_layered_options(example_config.to_str(), "")
|
||||||
|
.unwrap();
|
||||||
|
let expected = GreptimeOptions::<FlownodeOptions> {
|
||||||
|
component: FlownodeOptions {
|
||||||
|
node_id: Some(14),
|
||||||
|
flow: Default::default(),
|
||||||
|
grpc: GrpcOptions {
|
||||||
|
bind_addr: "127.0.0.1:6800".to_string(),
|
||||||
|
server_addr: "127.0.0.1:6800".to_string(),
|
||||||
|
runtime_size: 2,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
logging: LoggingOptions {
|
||||||
|
dir: format!("{}/{}", DEFAULT_DATA_HOME, DEFAULT_LOGGING_DIR),
|
||||||
|
level: Some("info".to_string()),
|
||||||
|
otlp_endpoint: Some(DEFAULT_OTLP_HTTP_ENDPOINT.to_string()),
|
||||||
|
otlp_export_protocol: Some(common_telemetry::logging::OtlpExportProtocol::Http),
|
||||||
|
tracing_sample_ratio: Some(Default::default()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
tracing: Default::default(),
|
||||||
|
heartbeat: Default::default(),
|
||||||
|
// flownode deliberately use a slower query parallelism
|
||||||
|
// to avoid overwhelming the frontend with too many queries
|
||||||
|
query: QueryOptions { parallelism: 1 },
|
||||||
|
meta_client: Some(MetaClientOptions {
|
||||||
|
metasrv_addrs: vec!["127.0.0.1:3002".to_string()],
|
||||||
|
timeout: Duration::from_secs(3),
|
||||||
|
heartbeat_timeout: Duration::from_millis(500),
|
||||||
|
ddl_timeout: Duration::from_secs(10),
|
||||||
|
connect_timeout: Duration::from_secs(1),
|
||||||
|
tcp_nodelay: true,
|
||||||
|
metadata_cache_max_capacity: 100000,
|
||||||
|
metadata_cache_ttl: Duration::from_secs(600),
|
||||||
|
metadata_cache_tti: Duration::from_secs(300),
|
||||||
|
}),
|
||||||
|
http: HttpOptions {
|
||||||
|
addr: "127.0.0.1:4000".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
user_provider: None,
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
similar_asserts::assert_eq!(options, expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_standalone_example_config() {
|
fn test_load_standalone_example_config() {
|
||||||
let example_config = common_test_util::find_workspace_path("config/standalone.example.toml");
|
let example_config = common_test_util::find_workspace_path("config/standalone.example.toml");
|
||||||
@@ -220,12 +258,7 @@ fn test_load_standalone_example_config() {
|
|||||||
component: StandaloneOptions {
|
component: StandaloneOptions {
|
||||||
default_timezone: Some("UTC".to_string()),
|
default_timezone: Some("UTC".to_string()),
|
||||||
wal: DatanodeWalConfig::RaftEngine(RaftEngineConfig {
|
wal: DatanodeWalConfig::RaftEngine(RaftEngineConfig {
|
||||||
dir: Some(
|
dir: Some(format!("{}/{}", DEFAULT_DATA_HOME, WAL_DIR)),
|
||||||
Path::new(DEFAULT_DATA_HOME)
|
|
||||||
.join(WAL_DIR)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
sync_period: Some(Duration::from_secs(10)),
|
sync_period: Some(Duration::from_secs(10)),
|
||||||
recovery_parallelism: 2,
|
recovery_parallelism: 2,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -248,11 +281,8 @@ fn test_load_standalone_example_config() {
|
|||||||
},
|
},
|
||||||
logging: LoggingOptions {
|
logging: LoggingOptions {
|
||||||
level: Some("info".to_string()),
|
level: Some("info".to_string()),
|
||||||
dir: Path::new(DEFAULT_DATA_HOME)
|
dir: format!("{}/{}", DEFAULT_DATA_HOME, DEFAULT_LOGGING_DIR),
|
||||||
.join(DEFAULT_LOGGING_DIR)
|
otlp_endpoint: Some(DEFAULT_OTLP_HTTP_ENDPOINT.to_string()),
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
otlp_endpoint: Some(DEFAULT_OTLP_ENDPOINT.to_string()),
|
|
||||||
tracing_sample_ratio: Some(Default::default()),
|
tracing_sample_ratio: Some(Default::default()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|||||||
240
src/common/base/src/cancellation.rs
Normal file
240
src/common/base/src/cancellation.rs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! [CancellationHandle] is used to compose with manual implementation of [futures::future::Future]
|
||||||
|
//! or [futures::stream::Stream] to facilitate cancellation.
|
||||||
|
//! See example in [frontend::stream_wrapper::CancellableStreamWrapper] and [CancellableFuture].
|
||||||
|
|
||||||
|
use std::fmt::{Debug, Display, Formatter};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use futures::task::AtomicWaker;
|
||||||
|
use pin_project::pin_project;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CancellationHandle {
|
||||||
|
waker: AtomicWaker,
|
||||||
|
cancelled: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for CancellationHandle {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("CancellationHandle")
|
||||||
|
.field("cancelled", &self.is_cancelled())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CancellationHandle {
|
||||||
|
pub fn waker(&self) -> &AtomicWaker {
|
||||||
|
&self.waker
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancels a future or stream.
|
||||||
|
pub fn cancel(&self) {
|
||||||
|
if self
|
||||||
|
.cancelled
|
||||||
|
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
self.waker.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this handle cancelled.
|
||||||
|
pub fn is_cancelled(&self) -> bool {
|
||||||
|
self.cancelled.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pin_project]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CancellableFuture<T> {
|
||||||
|
#[pin]
|
||||||
|
fut: T,
|
||||||
|
handle: Arc<CancellationHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> CancellableFuture<T> {
|
||||||
|
pub fn new(fut: T, handle: Arc<CancellationHandle>) -> Self {
|
||||||
|
Self { fut, handle }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Future for CancellableFuture<T>
|
||||||
|
where
|
||||||
|
T: Future,
|
||||||
|
{
|
||||||
|
type Output = Result<T::Output, Cancelled>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.as_mut().project();
|
||||||
|
// Check if the task has been aborted
|
||||||
|
if this.handle.is_cancelled() {
|
||||||
|
return Poll::Ready(Err(Cancelled));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Poll::Ready(x) = this.fut.poll(cx) {
|
||||||
|
return Poll::Ready(Ok(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handle.waker().register(cx.waker());
|
||||||
|
if this.handle.is_cancelled() {
|
||||||
|
return Poll::Ready(Err(Cancelled));
|
||||||
|
}
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Cancelled;
|
||||||
|
|
||||||
|
impl Display for Cancelled {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Future has been cancelled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tokio::time::{sleep, timeout};
|
||||||
|
|
||||||
|
use crate::cancellation::{CancellableFuture, CancellationHandle, Cancelled};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellable_future_completes_normally() {
|
||||||
|
let handle = Arc::new(CancellationHandle::default());
|
||||||
|
let future = async { 42 };
|
||||||
|
let cancellable = CancellableFuture::new(future, handle);
|
||||||
|
|
||||||
|
let result = cancellable.await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellable_future_cancelled_before_start() {
|
||||||
|
let handle = Arc::new(CancellationHandle::default());
|
||||||
|
handle.cancel();
|
||||||
|
|
||||||
|
let future = async { 42 };
|
||||||
|
let cancellable = CancellableFuture::new(future, handle);
|
||||||
|
|
||||||
|
let result = cancellable.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), Cancelled));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellable_future_cancelled_during_execution() {
|
||||||
|
let handle = Arc::new(CancellationHandle::default());
|
||||||
|
let handle_clone = handle.clone();
|
||||||
|
|
||||||
|
// Create a future that sleeps for a long time
|
||||||
|
let future = async {
|
||||||
|
sleep(Duration::from_secs(10)).await;
|
||||||
|
42
|
||||||
|
};
|
||||||
|
let cancellable = CancellableFuture::new(future, handle);
|
||||||
|
|
||||||
|
// Cancel the future after a short delay
|
||||||
|
tokio::spawn(async move {
|
||||||
|
sleep(Duration::from_millis(50)).await;
|
||||||
|
handle_clone.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cancellable.await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert!(matches!(result.unwrap_err(), Cancelled));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellable_future_completes_before_cancellation() {
|
||||||
|
let handle = Arc::new(CancellationHandle::default());
|
||||||
|
let handle_clone = handle.clone();
|
||||||
|
|
||||||
|
// Create a future that completes quickly
|
||||||
|
let future = async {
|
||||||
|
sleep(Duration::from_millis(10)).await;
|
||||||
|
42
|
||||||
|
};
|
||||||
|
let cancellable = CancellableFuture::new(future, handle);
|
||||||
|
|
||||||
|
// Try to cancel after the future should have completed
|
||||||
|
tokio::spawn(async move {
|
||||||
|
sleep(Duration::from_millis(100)).await;
|
||||||
|
handle_clone.cancel();
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = cancellable.await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(result.unwrap(), 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellation_handle_is_cancelled() {
|
||||||
|
let handle = CancellationHandle::default();
|
||||||
|
assert!(!handle.is_cancelled());
|
||||||
|
|
||||||
|
handle.cancel();
|
||||||
|
assert!(handle.is_cancelled());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_multiple_cancellable_futures_with_same_handle() {
|
||||||
|
let handle = Arc::new(CancellationHandle::default());
|
||||||
|
|
||||||
|
let future1 = CancellableFuture::new(async { 1 }, handle.clone());
|
||||||
|
let future2 = CancellableFuture::new(async { 2 }, handle.clone());
|
||||||
|
|
||||||
|
// Cancel before starting
|
||||||
|
handle.cancel();
|
||||||
|
|
||||||
|
let (result1, result2) = tokio::join!(future1, future2);
|
||||||
|
|
||||||
|
assert!(result1.is_err());
|
||||||
|
assert!(result2.is_err());
|
||||||
|
assert!(matches!(result1.unwrap_err(), Cancelled));
|
||||||
|
assert!(matches!(result2.unwrap_err(), Cancelled));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancellable_future_with_timeout() {
|
||||||
|
let handle = Arc::new(CancellationHandle::default());
|
||||||
|
let future = async {
|
||||||
|
sleep(Duration::from_secs(1)).await;
|
||||||
|
42
|
||||||
|
};
|
||||||
|
let cancellable = CancellableFuture::new(future, handle.clone());
|
||||||
|
|
||||||
|
// Use timeout to ensure the test doesn't hang
|
||||||
|
let result = timeout(Duration::from_millis(100), cancellable).await;
|
||||||
|
|
||||||
|
// Should timeout because the future takes 1 second but we timeout after 100ms
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cancelled_display() {
|
||||||
|
let cancelled = Cancelled;
|
||||||
|
assert_eq!(format!("{}", cancelled), "Future has been cancelled");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
pub mod bit_vec;
|
pub mod bit_vec;
|
||||||
pub mod bytes;
|
pub mod bytes;
|
||||||
|
pub mod cancellation;
|
||||||
pub mod plugins;
|
pub mod plugins;
|
||||||
pub mod range_read;
|
pub mod range_read;
|
||||||
#[allow(clippy::all)]
|
#[allow(clippy::all)]
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ pub const INFORMATION_SCHEMA_ROUTINES_TABLE_ID: u32 = 21;
|
|||||||
pub const INFORMATION_SCHEMA_SCHEMA_PRIVILEGES_TABLE_ID: u32 = 22;
|
pub const INFORMATION_SCHEMA_SCHEMA_PRIVILEGES_TABLE_ID: u32 = 22;
|
||||||
/// id for information_schema.TABLE_PRIVILEGES
|
/// id for information_schema.TABLE_PRIVILEGES
|
||||||
pub const INFORMATION_SCHEMA_TABLE_PRIVILEGES_TABLE_ID: u32 = 23;
|
pub const INFORMATION_SCHEMA_TABLE_PRIVILEGES_TABLE_ID: u32 = 23;
|
||||||
/// id for information_schema.TRIGGERS
|
/// id for information_schema.TRIGGERS (for mysql)
|
||||||
pub const INFORMATION_SCHEMA_TRIGGERS_TABLE_ID: u32 = 24;
|
pub const INFORMATION_SCHEMA_TRIGGERS_TABLE_ID: u32 = 24;
|
||||||
/// id for information_schema.GLOBAL_STATUS
|
/// id for information_schema.GLOBAL_STATUS
|
||||||
pub const INFORMATION_SCHEMA_GLOBAL_STATUS_TABLE_ID: u32 = 25;
|
pub const INFORMATION_SCHEMA_GLOBAL_STATUS_TABLE_ID: u32 = 25;
|
||||||
@@ -102,6 +102,10 @@ pub const INFORMATION_SCHEMA_FLOW_TABLE_ID: u32 = 33;
|
|||||||
pub const INFORMATION_SCHEMA_PROCEDURE_INFO_TABLE_ID: u32 = 34;
|
pub const INFORMATION_SCHEMA_PROCEDURE_INFO_TABLE_ID: u32 = 34;
|
||||||
/// id for information_schema.region_statistics
|
/// id for information_schema.region_statistics
|
||||||
pub const INFORMATION_SCHEMA_REGION_STATISTICS_TABLE_ID: u32 = 35;
|
pub const INFORMATION_SCHEMA_REGION_STATISTICS_TABLE_ID: u32 = 35;
|
||||||
|
/// id for information_schema.process_list
|
||||||
|
pub const INFORMATION_SCHEMA_PROCESS_LIST_TABLE_ID: u32 = 36;
|
||||||
|
/// id for information_schema.trigger_list (for greptimedb trigger)
|
||||||
|
pub const INFORMATION_SCHEMA_TRIGGER_TABLE_ID: u32 = 37;
|
||||||
|
|
||||||
// ----- End of information_schema tables -----
|
// ----- End of information_schema tables -----
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ common-macro.workspace = true
|
|||||||
config.workspace = true
|
config.workspace = true
|
||||||
humantime-serde.workspace = true
|
humantime-serde.workspace = true
|
||||||
num_cpus.workspace = true
|
num_cpus.workspace = true
|
||||||
|
object-store.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_with.workspace = true
|
serde_with.workspace = true
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ mod tests {
|
|||||||
use common_telemetry::logging::LoggingOptions;
|
use common_telemetry::logging::LoggingOptions;
|
||||||
use common_test_util::temp_dir::create_named_temp_file;
|
use common_test_util::temp_dir::create_named_temp_file;
|
||||||
use common_wal::config::DatanodeWalConfig;
|
use common_wal::config::DatanodeWalConfig;
|
||||||
use datanode::config::{ObjectStoreConfig, StorageConfig};
|
use datanode::config::StorageConfig;
|
||||||
use meta_client::MetaClientOptions;
|
use meta_client::MetaClientOptions;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ mod tests {
|
|||||||
|
|
||||||
// Check the configs from environment variables.
|
// Check the configs from environment variables.
|
||||||
match &opts.storage.store {
|
match &opts.storage.store {
|
||||||
ObjectStoreConfig::S3(s3_config) => {
|
object_store::config::ObjectStoreConfig::S3(s3_config) => {
|
||||||
assert_eq!(s3_config.bucket, "mybucket".to_string());
|
assert_eq!(s3_config.bucket, "mybucket".to_string());
|
||||||
}
|
}
|
||||||
_ => panic!("unexpected store type"),
|
_ => panic!("unexpected store type"),
|
||||||
|
|||||||
@@ -119,6 +119,11 @@ pub enum StatusCode {
|
|||||||
FlowAlreadyExists = 8000,
|
FlowAlreadyExists = 8000,
|
||||||
FlowNotFound = 8001,
|
FlowNotFound = 8001,
|
||||||
// ====== End of flow related status code =====
|
// ====== End of flow related status code =====
|
||||||
|
|
||||||
|
// ====== Begin of trigger related status code =====
|
||||||
|
TriggerAlreadyExists = 9000,
|
||||||
|
TriggerNotFound = 9001,
|
||||||
|
// ====== End of trigger related status code =====
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusCode {
|
impl StatusCode {
|
||||||
@@ -155,6 +160,8 @@ impl StatusCode {
|
|||||||
| StatusCode::RegionNotFound
|
| StatusCode::RegionNotFound
|
||||||
| StatusCode::FlowAlreadyExists
|
| StatusCode::FlowAlreadyExists
|
||||||
| StatusCode::FlowNotFound
|
| StatusCode::FlowNotFound
|
||||||
|
| StatusCode::TriggerAlreadyExists
|
||||||
|
| StatusCode::TriggerNotFound
|
||||||
| StatusCode::RegionReadonly
|
| StatusCode::RegionReadonly
|
||||||
| StatusCode::TableColumnNotFound
|
| StatusCode::TableColumnNotFound
|
||||||
| StatusCode::TableColumnExists
|
| StatusCode::TableColumnExists
|
||||||
@@ -198,6 +205,8 @@ impl StatusCode {
|
|||||||
| StatusCode::PlanQuery
|
| StatusCode::PlanQuery
|
||||||
| StatusCode::FlowAlreadyExists
|
| StatusCode::FlowAlreadyExists
|
||||||
| StatusCode::FlowNotFound
|
| StatusCode::FlowNotFound
|
||||||
|
| StatusCode::TriggerAlreadyExists
|
||||||
|
| StatusCode::TriggerNotFound
|
||||||
| StatusCode::RegionNotReady
|
| StatusCode::RegionNotReady
|
||||||
| StatusCode::RegionBusy
|
| StatusCode::RegionBusy
|
||||||
| StatusCode::RegionReadonly
|
| StatusCode::RegionReadonly
|
||||||
@@ -230,6 +239,48 @@ impl fmt::Display for StatusCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! define_from_tonic_status {
|
||||||
|
($Error: ty, $Variant: ident) => {
|
||||||
|
impl From<tonic::Status> for $Error {
|
||||||
|
fn from(e: tonic::Status) -> Self {
|
||||||
|
use snafu::location;
|
||||||
|
|
||||||
|
fn metadata_value(e: &tonic::Status, key: &str) -> Option<String> {
|
||||||
|
e.metadata()
|
||||||
|
.get(key)
|
||||||
|
.and_then(|v| String::from_utf8(v.as_bytes().to_vec()).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_CODE)
|
||||||
|
.and_then(|s| {
|
||||||
|
if let Ok(code) = s.parse::<u32>() {
|
||||||
|
StatusCode::from_u32(code)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| match e.code() {
|
||||||
|
tonic::Code::Cancelled => StatusCode::Cancelled,
|
||||||
|
tonic::Code::DeadlineExceeded => StatusCode::DeadlineExceeded,
|
||||||
|
_ => StatusCode::Internal,
|
||||||
|
});
|
||||||
|
|
||||||
|
let msg = metadata_value(&e, $crate::GREPTIME_DB_HEADER_ERROR_MSG)
|
||||||
|
.unwrap_or_else(|| e.message().to_string());
|
||||||
|
|
||||||
|
// TODO(LFC): Make the error variant defined automatically.
|
||||||
|
Self::$Variant {
|
||||||
|
code,
|
||||||
|
msg,
|
||||||
|
tonic_code: e.code(),
|
||||||
|
location: location!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_into_tonic_status {
|
macro_rules! define_into_tonic_status {
|
||||||
($Error: ty) => {
|
($Error: ty) => {
|
||||||
@@ -281,12 +332,14 @@ pub fn status_to_tonic_code(status_code: StatusCode) -> Code {
|
|||||||
| StatusCode::TableColumnExists
|
| StatusCode::TableColumnExists
|
||||||
| StatusCode::RegionAlreadyExists
|
| StatusCode::RegionAlreadyExists
|
||||||
| StatusCode::DatabaseAlreadyExists
|
| StatusCode::DatabaseAlreadyExists
|
||||||
|
| StatusCode::TriggerAlreadyExists
|
||||||
| StatusCode::FlowAlreadyExists => Code::AlreadyExists,
|
| StatusCode::FlowAlreadyExists => Code::AlreadyExists,
|
||||||
StatusCode::TableNotFound
|
StatusCode::TableNotFound
|
||||||
| StatusCode::RegionNotFound
|
| StatusCode::RegionNotFound
|
||||||
| StatusCode::TableColumnNotFound
|
| StatusCode::TableColumnNotFound
|
||||||
| StatusCode::DatabaseNotFound
|
| StatusCode::DatabaseNotFound
|
||||||
| StatusCode::UserNotFound
|
| StatusCode::UserNotFound
|
||||||
|
| StatusCode::TriggerNotFound
|
||||||
| StatusCode::FlowNotFound => Code::NotFound,
|
| StatusCode::FlowNotFound => Code::NotFound,
|
||||||
StatusCode::TableUnavailable
|
StatusCode::TableUnavailable
|
||||||
| StatusCode::StorageUnavailable
|
| StatusCode::StorageUnavailable
|
||||||
@@ -304,15 +357,6 @@ pub fn status_to_tonic_code(status_code: StatusCode) -> Code {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts tonic [Code] to [StatusCode].
|
|
||||||
pub fn convert_tonic_code_to_status_code(code: Code) -> StatusCode {
|
|
||||||
match code {
|
|
||||||
Code::Cancelled => StatusCode::Cancelled,
|
|
||||||
Code::DeadlineExceeded => StatusCode::DeadlineExceeded,
|
|
||||||
_ => StatusCode::Internal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|||||||
@@ -7,5 +7,13 @@ license.workspace = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
common-error.workspace = true
|
common-error.workspace = true
|
||||||
|
common-grpc.workspace = true
|
||||||
common-macro.workspace = true
|
common-macro.workspace = true
|
||||||
|
common-meta.workspace = true
|
||||||
|
greptime-proto.workspace = true
|
||||||
|
meta-client.workspace = true
|
||||||
snafu.workspace = true
|
snafu.workspace = true
|
||||||
|
tonic.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio.workspace = true
|
||||||
|
|||||||
@@ -27,6 +27,35 @@ pub enum Error {
|
|||||||
location: Location,
|
location: Location,
|
||||||
source: BoxedError,
|
source: BoxedError,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to list nodes from metasrv"))]
|
||||||
|
Meta {
|
||||||
|
source: Box<meta_client::error::Error>,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to parse process id: {}", s))]
|
||||||
|
ParseProcessId {
|
||||||
|
s: String,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to invoke frontend service"))]
|
||||||
|
InvokeFrontend {
|
||||||
|
#[snafu(source)]
|
||||||
|
error: tonic::Status,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[snafu(display("Failed to invoke list process service"))]
|
||||||
|
CreateChannel {
|
||||||
|
source: common_grpc::error::Error,
|
||||||
|
#[snafu(implicit)]
|
||||||
|
location: Location,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -36,6 +65,10 @@ impl ErrorExt for Error {
|
|||||||
use Error::*;
|
use Error::*;
|
||||||
match self {
|
match self {
|
||||||
External { source, .. } => source.status_code(),
|
External { source, .. } => source.status_code(),
|
||||||
|
Meta { source, .. } => source.status_code(),
|
||||||
|
ParseProcessId { .. } => StatusCode::InvalidArguments,
|
||||||
|
InvokeFrontend { .. } => StatusCode::Unexpected,
|
||||||
|
CreateChannel { source, .. } => source.status_code(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,41 @@
|
|||||||
// 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::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use snafu::OptionExt;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod selector;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct DisplayProcessId {
|
||||||
|
pub server_addr: String,
|
||||||
|
pub id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DisplayProcessId {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}/{}", self.server_addr, self.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for DisplayProcessId {
|
||||||
|
type Error = error::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
let mut split = value.split('/');
|
||||||
|
let server_addr = split
|
||||||
|
.next()
|
||||||
|
.context(error::ParseProcessIdSnafu { s: value })?
|
||||||
|
.to_string();
|
||||||
|
let id = split
|
||||||
|
.next()
|
||||||
|
.context(error::ParseProcessIdSnafu { s: value })?;
|
||||||
|
let id = u32::from_str(id)
|
||||||
|
.ok()
|
||||||
|
.context(error::ParseProcessIdSnafu { s: value })?;
|
||||||
|
Ok(DisplayProcessId { server_addr, id })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
113
src/common/frontend/src/selector.rs
Normal file
113
src/common/frontend/src/selector.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
// 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::fmt::Debug;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
|
||||||
|
use common_meta::cluster::{ClusterInfo, NodeInfo, Role};
|
||||||
|
use greptime_proto::v1::frontend::{
|
||||||
|
frontend_client, KillProcessRequest, KillProcessResponse, ListProcessRequest,
|
||||||
|
ListProcessResponse,
|
||||||
|
};
|
||||||
|
use meta_client::MetaClientRef;
|
||||||
|
use snafu::ResultExt;
|
||||||
|
use tonic::Response;
|
||||||
|
|
||||||
|
use crate::error;
|
||||||
|
use crate::error::{MetaSnafu, Result};
|
||||||
|
|
||||||
|
pub type FrontendClientPtr = Box<dyn FrontendClient>;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait FrontendClient: Send + Debug {
|
||||||
|
async fn list_process(&mut self, req: ListProcessRequest) -> Result<ListProcessResponse>;
|
||||||
|
|
||||||
|
async fn kill_process(&mut self, req: KillProcessRequest) -> Result<KillProcessResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl FrontendClient for frontend_client::FrontendClient<tonic::transport::channel::Channel> {
|
||||||
|
async fn list_process(&mut self, req: ListProcessRequest) -> Result<ListProcessResponse> {
|
||||||
|
frontend_client::FrontendClient::<tonic::transport::channel::Channel>::list_process(
|
||||||
|
self, req,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context(error::InvokeFrontendSnafu)
|
||||||
|
.map(Response::into_inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn kill_process(&mut self, req: KillProcessRequest) -> Result<KillProcessResponse> {
|
||||||
|
frontend_client::FrontendClient::<tonic::transport::channel::Channel>::kill_process(
|
||||||
|
self, req,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context(error::InvokeFrontendSnafu)
|
||||||
|
.map(Response::into_inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait FrontendSelector {
|
||||||
|
async fn select<F>(&self, predicate: F) -> Result<Vec<FrontendClientPtr>>
|
||||||
|
where
|
||||||
|
F: Fn(&NodeInfo) -> bool + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MetaClientSelector {
|
||||||
|
meta_client: MetaClientRef,
|
||||||
|
channel_manager: ChannelManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl FrontendSelector for MetaClientSelector {
|
||||||
|
async fn select<F>(&self, predicate: F) -> Result<Vec<FrontendClientPtr>>
|
||||||
|
where
|
||||||
|
F: Fn(&NodeInfo) -> bool + Send,
|
||||||
|
{
|
||||||
|
let nodes = self
|
||||||
|
.meta_client
|
||||||
|
.list_nodes(Some(Role::Frontend))
|
||||||
|
.await
|
||||||
|
.map_err(Box::new)
|
||||||
|
.context(MetaSnafu)?;
|
||||||
|
|
||||||
|
nodes
|
||||||
|
.into_iter()
|
||||||
|
.filter(predicate)
|
||||||
|
.map(|node| {
|
||||||
|
let channel = self
|
||||||
|
.channel_manager
|
||||||
|
.get(node.peer.addr)
|
||||||
|
.context(error::CreateChannelSnafu)?;
|
||||||
|
let client = frontend_client::FrontendClient::new(channel);
|
||||||
|
Ok(Box::new(client) as FrontendClientPtr)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaClientSelector {
|
||||||
|
pub fn new(meta_client: MetaClientRef) -> Self {
|
||||||
|
let cfg = ChannelConfig::new()
|
||||||
|
.connect_timeout(Duration::from_secs(30))
|
||||||
|
.timeout(Duration::from_secs(30));
|
||||||
|
let channel_manager = ChannelManager::with_config(cfg);
|
||||||
|
Self {
|
||||||
|
meta_client,
|
||||||
|
channel_manager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,7 @@ common-version.workspace = true
|
|||||||
datafusion.workspace = true
|
datafusion.workspace = true
|
||||||
datafusion-common.workspace = true
|
datafusion-common.workspace = true
|
||||||
datafusion-expr.workspace = true
|
datafusion-expr.workspace = true
|
||||||
|
datafusion-functions-aggregate-common.workspace = true
|
||||||
datatypes.workspace = true
|
datatypes.workspace = true
|
||||||
derive_more = { version = "1", default-features = false, features = ["display"] }
|
derive_more = { version = "1", default-features = false, features = ["display"] }
|
||||||
geo = { version = "0.29", optional = true }
|
geo = { version = "0.29", optional = true }
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
pub mod approximate;
|
pub mod approximate;
|
||||||
|
pub mod count_hash;
|
||||||
#[cfg(feature = "geo")]
|
#[cfg(feature = "geo")]
|
||||||
pub mod geo;
|
pub mod geo;
|
||||||
pub mod vector;
|
pub mod vector;
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
|
|
||||||
use crate::function_registry::FunctionRegistry;
|
use crate::function_registry::FunctionRegistry;
|
||||||
|
|
||||||
pub(crate) mod hll;
|
pub mod hll;
|
||||||
mod uddsketch;
|
pub mod uddsketch;
|
||||||
|
|
||||||
pub(crate) struct ApproximateFunction;
|
pub(crate) struct ApproximateFunction;
|
||||||
|
|
||||||
|
|||||||
647
src/common/function/src/aggrs/count_hash.rs
Normal file
647
src/common/function/src/aggrs/count_hash.rs
Normal file
@@ -0,0 +1,647 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//! `CountHash` / `count_hash` is a hash-based approximate distinct count function.
|
||||||
|
//!
|
||||||
|
//! It is a variant of `CountDistinct` that uses a hash function to approximate the
|
||||||
|
//! distinct count.
|
||||||
|
//! It is designed to be more efficient than `CountDistinct` for large datasets,
|
||||||
|
//! but it is not as accurate, as the hash value may be collision.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use ahash::RandomState;
|
||||||
|
use datafusion_common::cast::as_list_array;
|
||||||
|
use datafusion_common::error::Result;
|
||||||
|
use datafusion_common::hash_utils::create_hashes;
|
||||||
|
use datafusion_common::utils::SingleRowListArrayBuilder;
|
||||||
|
use datafusion_common::{internal_err, not_impl_err, ScalarValue};
|
||||||
|
use datafusion_expr::function::{AccumulatorArgs, StateFieldsArgs};
|
||||||
|
use datafusion_expr::utils::{format_state_name, AggregateOrderSensitivity};
|
||||||
|
use datafusion_expr::{
|
||||||
|
Accumulator, AggregateUDF, AggregateUDFImpl, EmitTo, GroupsAccumulator, ReversedUDAF,
|
||||||
|
SetMonotonicity, Signature, TypeSignature, Volatility,
|
||||||
|
};
|
||||||
|
use datafusion_functions_aggregate_common::aggregate::groups_accumulator::nulls::filtered_null_mask;
|
||||||
|
use datatypes::arrow;
|
||||||
|
use datatypes::arrow::array::{
|
||||||
|
Array, ArrayRef, AsArray, BooleanArray, Int64Array, ListArray, UInt64Array,
|
||||||
|
};
|
||||||
|
use datatypes::arrow::buffer::{OffsetBuffer, ScalarBuffer};
|
||||||
|
use datatypes::arrow::datatypes::{DataType, Field};
|
||||||
|
|
||||||
|
use crate::function_registry::FunctionRegistry;
|
||||||
|
|
||||||
|
type HashValueType = u64;
|
||||||
|
|
||||||
|
// read from /dev/urandom 4047821dc6144e4b2abddf23ad4171126a52eeecd26eff2191cf673b965a7875
|
||||||
|
const RANDOM_SEED_0: u64 = 0x4047821dc6144e4b;
|
||||||
|
const RANDOM_SEED_1: u64 = 0x2abddf23ad417112;
|
||||||
|
const RANDOM_SEED_2: u64 = 0x6a52eeecd26eff21;
|
||||||
|
const RANDOM_SEED_3: u64 = 0x91cf673b965a7875;
|
||||||
|
|
||||||
|
impl CountHash {
|
||||||
|
pub fn register(registry: &FunctionRegistry) {
|
||||||
|
registry.register_aggr(CountHash::udf_impl());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn udf_impl() -> AggregateUDF {
|
||||||
|
AggregateUDF::new_from_impl(CountHash {
|
||||||
|
signature: Signature::one_of(
|
||||||
|
vec![TypeSignature::VariadicAny, TypeSignature::Nullary],
|
||||||
|
Volatility::Immutable,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CountHash {
|
||||||
|
signature: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AggregateUDFImpl for CountHash {
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"count_hash"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> &Signature {
|
||||||
|
&self.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
|
||||||
|
Ok(DataType::Int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_nullable(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_fields(&self, args: StateFieldsArgs) -> Result<Vec<Field>> {
|
||||||
|
Ok(vec![Field::new_list(
|
||||||
|
format_state_name(args.name, "count_hash"),
|
||||||
|
Field::new_list_field(DataType::UInt64, true),
|
||||||
|
// For count_hash accumulator, null list item stands for an
|
||||||
|
// empty value set (i.e., all NULL value so far for that group).
|
||||||
|
true,
|
||||||
|
)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accumulator(&self, acc_args: AccumulatorArgs) -> Result<Box<dyn Accumulator>> {
|
||||||
|
if acc_args.exprs.len() > 1 {
|
||||||
|
return not_impl_err!("count_hash with multiple arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(CountHashAccumulator {
|
||||||
|
values: HashSet::default(),
|
||||||
|
random_state: RandomState::with_seeds(
|
||||||
|
RANDOM_SEED_0,
|
||||||
|
RANDOM_SEED_1,
|
||||||
|
RANDOM_SEED_2,
|
||||||
|
RANDOM_SEED_3,
|
||||||
|
),
|
||||||
|
batch_hashes: vec![],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aliases(&self) -> &[String] {
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn groups_accumulator_supported(&self, _args: AccumulatorArgs) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_groups_accumulator(
|
||||||
|
&self,
|
||||||
|
args: AccumulatorArgs,
|
||||||
|
) -> Result<Box<dyn GroupsAccumulator>> {
|
||||||
|
if args.exprs.len() > 1 {
|
||||||
|
return not_impl_err!("count_hash with multiple arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Box::new(CountHashGroupAccumulator::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reverse_expr(&self) -> ReversedUDAF {
|
||||||
|
ReversedUDAF::Identical
|
||||||
|
}
|
||||||
|
|
||||||
|
fn order_sensitivity(&self) -> AggregateOrderSensitivity {
|
||||||
|
AggregateOrderSensitivity::Insensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_value(&self, _data_type: &DataType) -> Result<ScalarValue> {
|
||||||
|
Ok(ScalarValue::Int64(Some(0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_monotonicity(&self, _data_type: &DataType) -> SetMonotonicity {
|
||||||
|
SetMonotonicity::Increasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GroupsAccumulator for `count_hash` aggregate function
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CountHashGroupAccumulator {
|
||||||
|
/// One HashSet per group to track distinct values
|
||||||
|
distinct_sets: Vec<HashSet<HashValueType, RandomState>>,
|
||||||
|
random_state: RandomState,
|
||||||
|
batch_hashes: Vec<HashValueType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CountHashGroupAccumulator {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CountHashGroupAccumulator {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
distinct_sets: vec![],
|
||||||
|
random_state: RandomState::with_seeds(
|
||||||
|
RANDOM_SEED_0,
|
||||||
|
RANDOM_SEED_1,
|
||||||
|
RANDOM_SEED_2,
|
||||||
|
RANDOM_SEED_3,
|
||||||
|
),
|
||||||
|
batch_hashes: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_sets(&mut self, total_num_groups: usize) {
|
||||||
|
if self.distinct_sets.len() < total_num_groups {
|
||||||
|
self.distinct_sets
|
||||||
|
.resize_with(total_num_groups, HashSet::default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GroupsAccumulator for CountHashGroupAccumulator {
|
||||||
|
fn update_batch(
|
||||||
|
&mut self,
|
||||||
|
values: &[ArrayRef],
|
||||||
|
group_indices: &[usize],
|
||||||
|
opt_filter: Option<&BooleanArray>,
|
||||||
|
total_num_groups: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
assert_eq!(values.len(), 1, "count_hash expects a single argument");
|
||||||
|
self.ensure_sets(total_num_groups);
|
||||||
|
|
||||||
|
let array = &values[0];
|
||||||
|
self.batch_hashes.clear();
|
||||||
|
self.batch_hashes.resize(array.len(), 0);
|
||||||
|
let hashes = create_hashes(
|
||||||
|
&[ArrayRef::clone(array)],
|
||||||
|
&self.random_state,
|
||||||
|
&mut self.batch_hashes,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Use a pattern similar to accumulate_indices to process rows
|
||||||
|
// that are not null and pass the filter
|
||||||
|
let nulls = array.logical_nulls();
|
||||||
|
|
||||||
|
match (nulls.as_ref(), opt_filter) {
|
||||||
|
(None, None) => {
|
||||||
|
// No nulls, no filter - process all rows
|
||||||
|
for (row_idx, &group_idx) in group_indices.iter().enumerate() {
|
||||||
|
self.distinct_sets[group_idx].insert(hashes[row_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(nulls), None) => {
|
||||||
|
// Has nulls, no filter
|
||||||
|
for (row_idx, (&group_idx, is_valid)) in
|
||||||
|
group_indices.iter().zip(nulls.iter()).enumerate()
|
||||||
|
{
|
||||||
|
if is_valid {
|
||||||
|
self.distinct_sets[group_idx].insert(hashes[row_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, Some(filter)) => {
|
||||||
|
// No nulls, has filter
|
||||||
|
for (row_idx, (&group_idx, filter_value)) in
|
||||||
|
group_indices.iter().zip(filter.iter()).enumerate()
|
||||||
|
{
|
||||||
|
if let Some(true) = filter_value {
|
||||||
|
self.distinct_sets[group_idx].insert(hashes[row_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(nulls), Some(filter)) => {
|
||||||
|
// Has nulls and filter
|
||||||
|
let iter = filter
|
||||||
|
.iter()
|
||||||
|
.zip(group_indices.iter())
|
||||||
|
.zip(nulls.iter())
|
||||||
|
.enumerate();
|
||||||
|
|
||||||
|
for (row_idx, ((filter_value, &group_idx), is_valid)) in iter {
|
||||||
|
if is_valid && filter_value == Some(true) {
|
||||||
|
self.distinct_sets[group_idx].insert(hashes[row_idx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate(&mut self, emit_to: EmitTo) -> Result<ArrayRef> {
|
||||||
|
let distinct_sets: Vec<HashSet<u64, RandomState>> =
|
||||||
|
emit_to.take_needed(&mut self.distinct_sets);
|
||||||
|
|
||||||
|
let counts = distinct_sets
|
||||||
|
.iter()
|
||||||
|
.map(|set| set.len() as i64)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
Ok(Arc::new(Int64Array::from(counts)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_batch(
|
||||||
|
&mut self,
|
||||||
|
values: &[ArrayRef],
|
||||||
|
group_indices: &[usize],
|
||||||
|
_opt_filter: Option<&BooleanArray>,
|
||||||
|
total_num_groups: usize,
|
||||||
|
) -> Result<()> {
|
||||||
|
assert_eq!(
|
||||||
|
values.len(),
|
||||||
|
1,
|
||||||
|
"count_hash merge expects a single state array"
|
||||||
|
);
|
||||||
|
self.ensure_sets(total_num_groups);
|
||||||
|
|
||||||
|
let list_array = as_list_array(&values[0])?;
|
||||||
|
|
||||||
|
// For each group in the incoming batch
|
||||||
|
for (i, &group_idx) in group_indices.iter().enumerate() {
|
||||||
|
if i < list_array.len() {
|
||||||
|
let inner_array = list_array.value(i);
|
||||||
|
let inner_array = inner_array.as_any().downcast_ref::<UInt64Array>().unwrap();
|
||||||
|
// Add each value to our set for this group
|
||||||
|
for j in 0..inner_array.len() {
|
||||||
|
if !inner_array.is_null(j) {
|
||||||
|
self.distinct_sets[group_idx].insert(inner_array.value(j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&mut self, emit_to: EmitTo) -> Result<Vec<ArrayRef>> {
|
||||||
|
let distinct_sets: Vec<HashSet<u64, RandomState>> =
|
||||||
|
emit_to.take_needed(&mut self.distinct_sets);
|
||||||
|
|
||||||
|
let mut offsets = Vec::with_capacity(distinct_sets.len() + 1);
|
||||||
|
offsets.push(0);
|
||||||
|
let mut curr_len = 0i32;
|
||||||
|
|
||||||
|
let mut value_iter = distinct_sets
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|set| {
|
||||||
|
// build offset
|
||||||
|
curr_len += set.len() as i32;
|
||||||
|
offsets.push(curr_len);
|
||||||
|
// convert into iter
|
||||||
|
set.into_iter()
|
||||||
|
})
|
||||||
|
.peekable();
|
||||||
|
let data_array: ArrayRef = if value_iter.peek().is_none() {
|
||||||
|
arrow::array::new_empty_array(&DataType::UInt64) as _
|
||||||
|
} else {
|
||||||
|
Arc::new(UInt64Array::from_iter_values(value_iter))
|
||||||
|
};
|
||||||
|
let offset_buffer = OffsetBuffer::new(ScalarBuffer::from(offsets));
|
||||||
|
|
||||||
|
let list_array = ListArray::new(
|
||||||
|
Arc::new(Field::new_list_field(DataType::UInt64, true)),
|
||||||
|
offset_buffer,
|
||||||
|
data_array,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(vec![Arc::new(list_array) as _])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_state(
|
||||||
|
&self,
|
||||||
|
values: &[ArrayRef],
|
||||||
|
opt_filter: Option<&BooleanArray>,
|
||||||
|
) -> Result<Vec<ArrayRef>> {
|
||||||
|
// For a single hash value per row, create a list array with that value
|
||||||
|
assert_eq!(values.len(), 1, "count_hash expects a single argument");
|
||||||
|
let values = ArrayRef::clone(&values[0]);
|
||||||
|
|
||||||
|
let offsets = OffsetBuffer::new(ScalarBuffer::from_iter(0..values.len() as i32 + 1));
|
||||||
|
let nulls = filtered_null_mask(opt_filter, &values);
|
||||||
|
let list_array = ListArray::new(
|
||||||
|
Arc::new(Field::new_list_field(DataType::UInt64, true)),
|
||||||
|
offsets,
|
||||||
|
values,
|
||||||
|
nulls,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(vec![Arc::new(list_array)])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_convert_to_state(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
// Base size of the struct
|
||||||
|
let mut size = size_of::<Self>();
|
||||||
|
|
||||||
|
// Size of the vector holding the HashSets
|
||||||
|
size += size_of::<Vec<HashSet<HashValueType, RandomState>>>()
|
||||||
|
+ self.distinct_sets.capacity() * size_of::<HashSet<HashValueType, RandomState>>();
|
||||||
|
|
||||||
|
// Estimate HashSet contents size more efficiently
|
||||||
|
// Instead of iterating through all values which is expensive, use an approximation
|
||||||
|
for set in &self.distinct_sets {
|
||||||
|
// Base size of the HashSet
|
||||||
|
size += set.capacity() * size_of::<HashValueType>();
|
||||||
|
}
|
||||||
|
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CountHashAccumulator {
|
||||||
|
values: HashSet<HashValueType, RandomState>,
|
||||||
|
random_state: RandomState,
|
||||||
|
batch_hashes: Vec<HashValueType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CountHashAccumulator {
|
||||||
|
// calculating the size for fixed length values, taking first batch size *
|
||||||
|
// number of batches.
|
||||||
|
fn fixed_size(&self) -> usize {
|
||||||
|
size_of_val(self) + (size_of::<HashValueType>() * self.values.capacity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Accumulator for CountHashAccumulator {
|
||||||
|
/// Returns the distinct values seen so far as (one element) ListArray.
|
||||||
|
fn state(&mut self) -> Result<Vec<ScalarValue>> {
|
||||||
|
let values = self.values.iter().cloned().collect::<Vec<_>>();
|
||||||
|
let arr = Arc::new(UInt64Array::from(values)) as _;
|
||||||
|
let list_scalar = SingleRowListArrayBuilder::new(arr).build_list_scalar();
|
||||||
|
Ok(vec![list_scalar])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()> {
|
||||||
|
if values.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let arr = &values[0];
|
||||||
|
if arr.data_type() == &DataType::Null {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.batch_hashes.clear();
|
||||||
|
self.batch_hashes.resize(arr.len(), 0);
|
||||||
|
let hashes = create_hashes(
|
||||||
|
&[ArrayRef::clone(arr)],
|
||||||
|
&self.random_state,
|
||||||
|
&mut self.batch_hashes,
|
||||||
|
)?;
|
||||||
|
for hash in hashes.as_slice() {
|
||||||
|
self.values.insert(*hash);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges multiple sets of distinct values into the current set.
|
||||||
|
///
|
||||||
|
/// The input to this function is a `ListArray` with **multiple** rows,
|
||||||
|
/// where each row contains the values from a partial aggregate's phase (e.g.
|
||||||
|
/// the result of calling `Self::state` on multiple accumulators).
|
||||||
|
fn merge_batch(&mut self, states: &[ArrayRef]) -> Result<()> {
|
||||||
|
if states.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
assert_eq!(states.len(), 1, "array_agg states must be singleton!");
|
||||||
|
let array = &states[0];
|
||||||
|
let list_array = array.as_list::<i32>();
|
||||||
|
for inner_array in list_array.iter() {
|
||||||
|
let Some(inner_array) = inner_array else {
|
||||||
|
return internal_err!(
|
||||||
|
"Intermediate results of count_hash should always be non null"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
let hash_array = inner_array.as_any().downcast_ref::<UInt64Array>().unwrap();
|
||||||
|
for i in 0..hash_array.len() {
|
||||||
|
self.values.insert(hash_array.value(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate(&mut self) -> Result<ScalarValue> {
|
||||||
|
Ok(ScalarValue::Int64(Some(self.values.len() as i64)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> usize {
|
||||||
|
self.fixed_size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use datatypes::arrow::array::{Array, BooleanArray, Int32Array, Int64Array};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn create_test_accumulator() -> CountHashAccumulator {
|
||||||
|
CountHashAccumulator {
|
||||||
|
values: HashSet::default(),
|
||||||
|
random_state: RandomState::with_seeds(
|
||||||
|
RANDOM_SEED_0,
|
||||||
|
RANDOM_SEED_1,
|
||||||
|
RANDOM_SEED_2,
|
||||||
|
RANDOM_SEED_3,
|
||||||
|
),
|
||||||
|
batch_hashes: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_count_hash_accumulator() -> Result<()> {
|
||||||
|
let mut acc = create_test_accumulator();
|
||||||
|
|
||||||
|
// Test with some data
|
||||||
|
let array = Arc::new(Int32Array::from(vec![
|
||||||
|
Some(1),
|
||||||
|
Some(2),
|
||||||
|
Some(3),
|
||||||
|
Some(1),
|
||||||
|
Some(2),
|
||||||
|
None,
|
||||||
|
])) as ArrayRef;
|
||||||
|
acc.update_batch(&[array])?;
|
||||||
|
let result = acc.evaluate()?;
|
||||||
|
assert_eq!(result, ScalarValue::Int64(Some(4)));
|
||||||
|
|
||||||
|
// Test with empty data
|
||||||
|
let mut acc = create_test_accumulator();
|
||||||
|
let array = Arc::new(Int32Array::from(vec![] as Vec<Option<i32>>)) as ArrayRef;
|
||||||
|
acc.update_batch(&[array])?;
|
||||||
|
let result = acc.evaluate()?;
|
||||||
|
assert_eq!(result, ScalarValue::Int64(Some(0)));
|
||||||
|
|
||||||
|
// Test with only nulls
|
||||||
|
let mut acc = create_test_accumulator();
|
||||||
|
let array = Arc::new(Int32Array::from(vec![None, None, None])) as ArrayRef;
|
||||||
|
acc.update_batch(&[array])?;
|
||||||
|
let result = acc.evaluate()?;
|
||||||
|
assert_eq!(result, ScalarValue::Int64(Some(1)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_count_hash_accumulator_merge() -> Result<()> {
|
||||||
|
// Accumulator 1
|
||||||
|
let mut acc1 = create_test_accumulator();
|
||||||
|
let array1 = Arc::new(Int32Array::from(vec![Some(1), Some(2), Some(3)])) as ArrayRef;
|
||||||
|
acc1.update_batch(&[array1])?;
|
||||||
|
let state1 = acc1.state()?;
|
||||||
|
|
||||||
|
// Accumulator 2
|
||||||
|
let mut acc2 = create_test_accumulator();
|
||||||
|
let array2 = Arc::new(Int32Array::from(vec![Some(3), Some(4), Some(5)])) as ArrayRef;
|
||||||
|
acc2.update_batch(&[array2])?;
|
||||||
|
let state2 = acc2.state()?;
|
||||||
|
|
||||||
|
// Merge state1 and state2 into a new accumulator
|
||||||
|
let mut acc_merged = create_test_accumulator();
|
||||||
|
let state_array1 = state1[0].to_array()?;
|
||||||
|
let state_array2 = state2[0].to_array()?;
|
||||||
|
|
||||||
|
acc_merged.merge_batch(&[state_array1])?;
|
||||||
|
acc_merged.merge_batch(&[state_array2])?;
|
||||||
|
|
||||||
|
let result = acc_merged.evaluate()?;
|
||||||
|
// Distinct values are {1, 2, 3, 4, 5}, so count is 5
|
||||||
|
assert_eq!(result, ScalarValue::Int64(Some(5)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_test_group_accumulator() -> CountHashGroupAccumulator {
|
||||||
|
CountHashGroupAccumulator::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_count_hash_group_accumulator() -> Result<()> {
|
||||||
|
let mut acc = create_test_group_accumulator();
|
||||||
|
let values = Arc::new(Int32Array::from(vec![1, 2, 1, 3, 2, 4, 5])) as ArrayRef;
|
||||||
|
let group_indices = vec![0, 1, 0, 0, 1, 2, 0];
|
||||||
|
let total_num_groups = 3;
|
||||||
|
|
||||||
|
acc.update_batch(&[values], &group_indices, None, total_num_groups)?;
|
||||||
|
|
||||||
|
let result_array = acc.evaluate(EmitTo::All)?;
|
||||||
|
let result = result_array.as_any().downcast_ref::<Int64Array>().unwrap();
|
||||||
|
|
||||||
|
// Group 0: {1, 3, 5} -> 3
|
||||||
|
// Group 1: {2} -> 1
|
||||||
|
// Group 2: {4} -> 1
|
||||||
|
assert_eq!(result.value(0), 3);
|
||||||
|
assert_eq!(result.value(1), 1);
|
||||||
|
assert_eq!(result.value(2), 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_count_hash_group_accumulator_with_filter() -> Result<()> {
|
||||||
|
let mut acc = create_test_group_accumulator();
|
||||||
|
let values = Arc::new(Int32Array::from(vec![1, 2, 3, 4, 5, 6])) as ArrayRef;
|
||||||
|
let group_indices = vec![0, 0, 1, 1, 2, 2];
|
||||||
|
let filter = BooleanArray::from(vec![true, false, true, true, false, true]);
|
||||||
|
let total_num_groups = 3;
|
||||||
|
|
||||||
|
acc.update_batch(&[values], &group_indices, Some(&filter), total_num_groups)?;
|
||||||
|
|
||||||
|
let result_array = acc.evaluate(EmitTo::All)?;
|
||||||
|
let result = result_array.as_any().downcast_ref::<Int64Array>().unwrap();
|
||||||
|
|
||||||
|
// Group 0: {1} (2 is filtered out) -> 1
|
||||||
|
// Group 1: {3, 4} -> 2
|
||||||
|
// Group 2: {6} (5 is filtered out) -> 1
|
||||||
|
assert_eq!(result.value(0), 1);
|
||||||
|
assert_eq!(result.value(1), 2);
|
||||||
|
assert_eq!(result.value(2), 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_count_hash_group_accumulator_merge() -> Result<()> {
|
||||||
|
// Accumulator 1
|
||||||
|
let mut acc1 = create_test_group_accumulator();
|
||||||
|
let values1 = Arc::new(Int32Array::from(vec![1, 2, 3, 4])) as ArrayRef;
|
||||||
|
let group_indices1 = vec![0, 0, 1, 1];
|
||||||
|
acc1.update_batch(&[values1], &group_indices1, None, 2)?;
|
||||||
|
// acc1 state: group 0 -> {1, 2}, group 1 -> {3, 4}
|
||||||
|
let state1 = acc1.state(EmitTo::All)?;
|
||||||
|
|
||||||
|
// Accumulator 2
|
||||||
|
let mut acc2 = create_test_group_accumulator();
|
||||||
|
let values2 = Arc::new(Int32Array::from(vec![5, 6, 1, 3])) as ArrayRef;
|
||||||
|
// Merge into different group indices
|
||||||
|
let group_indices2 = vec![2, 2, 0, 1];
|
||||||
|
acc2.update_batch(&[values2], &group_indices2, None, 3)?;
|
||||||
|
// acc2 state: group 0 -> {1}, group 1 -> {3}, group 2 -> {5, 6}
|
||||||
|
|
||||||
|
// Merge state from acc1 into acc2
|
||||||
|
// We will merge acc1's group 0 into acc2's group 0
|
||||||
|
// and acc1's group 1 into acc2's group 2
|
||||||
|
let merge_group_indices = vec![0, 2];
|
||||||
|
acc2.merge_batch(&state1, &merge_group_indices, None, 3)?;
|
||||||
|
|
||||||
|
let result_array = acc2.evaluate(EmitTo::All)?;
|
||||||
|
let result = result_array.as_any().downcast_ref::<Int64Array>().unwrap();
|
||||||
|
|
||||||
|
// Final state of acc2:
|
||||||
|
// Group 0: {1} U {1, 2} -> {1, 2}, count = 2
|
||||||
|
// Group 1: {3}, count = 1
|
||||||
|
// Group 2: {5, 6} U {3, 4} -> {3, 4, 5, 6}, count = 4
|
||||||
|
assert_eq!(result.value(0), 2);
|
||||||
|
assert_eq!(result.value(1), 1);
|
||||||
|
assert_eq!(result.value(2), 4);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_size() {
|
||||||
|
let acc = create_test_group_accumulator();
|
||||||
|
// Just test it doesn't crash and returns a value.
|
||||||
|
assert!(acc.size() > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ use once_cell::sync::Lazy;
|
|||||||
|
|
||||||
use crate::admin::AdminFunction;
|
use crate::admin::AdminFunction;
|
||||||
use crate::aggrs::approximate::ApproximateFunction;
|
use crate::aggrs::approximate::ApproximateFunction;
|
||||||
|
use crate::aggrs::count_hash::CountHash;
|
||||||
use crate::aggrs::vector::VectorFunction as VectorAggrFunction;
|
use crate::aggrs::vector::VectorFunction as VectorAggrFunction;
|
||||||
use crate::function::{AsyncFunctionRef, Function, FunctionRef};
|
use crate::function::{AsyncFunctionRef, Function, FunctionRef};
|
||||||
use crate::function_factory::ScalarFunctionFactory;
|
use crate::function_factory::ScalarFunctionFactory;
|
||||||
@@ -144,6 +145,9 @@ pub static FUNCTION_REGISTRY: Lazy<Arc<FunctionRegistry>> = Lazy::new(|| {
|
|||||||
// Approximate functions
|
// Approximate functions
|
||||||
ApproximateFunction::register(&function_registry);
|
ApproximateFunction::register(&function_registry);
|
||||||
|
|
||||||
|
// CountHash function
|
||||||
|
CountHash::register(&function_registry);
|
||||||
|
|
||||||
Arc::new(function_registry)
|
Arc::new(function_registry)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
pub mod clamp;
|
pub mod clamp;
|
||||||
mod modulo;
|
mod modulo;
|
||||||
mod pow;
|
|
||||||
mod rate;
|
mod rate;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -26,7 +25,6 @@ use datafusion::error::DataFusionError;
|
|||||||
use datafusion::logical_expr::Volatility;
|
use datafusion::logical_expr::Volatility;
|
||||||
use datatypes::prelude::ConcreteDataType;
|
use datatypes::prelude::ConcreteDataType;
|
||||||
use datatypes::vectors::VectorRef;
|
use datatypes::vectors::VectorRef;
|
||||||
pub use pow::PowFunction;
|
|
||||||
pub use rate::RateFunction;
|
pub use rate::RateFunction;
|
||||||
use snafu::ResultExt;
|
use snafu::ResultExt;
|
||||||
|
|
||||||
@@ -39,7 +37,6 @@ pub(crate) struct MathFunction;
|
|||||||
impl MathFunction {
|
impl MathFunction {
|
||||||
pub fn register(registry: &FunctionRegistry) {
|
pub fn register(registry: &FunctionRegistry) {
|
||||||
registry.register_scalar(ModuloFunction);
|
registry.register_scalar(ModuloFunction);
|
||||||
registry.register_scalar(PowFunction);
|
|
||||||
registry.register_scalar(RateFunction);
|
registry.register_scalar(RateFunction);
|
||||||
registry.register_scalar(RangeFunction);
|
registry.register_scalar(RangeFunction);
|
||||||
registry.register_scalar(ClampFunction);
|
registry.register_scalar(ClampFunction);
|
||||||
|
|||||||
@@ -1,120 +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::fmt;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use common_query::error::Result;
|
|
||||||
use common_query::prelude::{Signature, Volatility};
|
|
||||||
use datatypes::data_type::DataType;
|
|
||||||
use datatypes::prelude::ConcreteDataType;
|
|
||||||
use datatypes::types::LogicalPrimitiveType;
|
|
||||||
use datatypes::vectors::VectorRef;
|
|
||||||
use datatypes::with_match_primitive_type_id;
|
|
||||||
use num::traits::Pow;
|
|
||||||
use num_traits::AsPrimitive;
|
|
||||||
|
|
||||||
use crate::function::{Function, FunctionContext};
|
|
||||||
use crate::scalars::expression::{scalar_binary_op, EvalContext};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct PowFunction;
|
|
||||||
|
|
||||||
impl Function for PowFunction {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"pow"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn return_type(&self, _input_types: &[ConcreteDataType]) -> Result<ConcreteDataType> {
|
|
||||||
Ok(ConcreteDataType::float64_datatype())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
|
||||||
Signature::uniform(2, ConcreteDataType::numerics(), Volatility::Immutable)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn eval(&self, _func_ctx: &FunctionContext, columns: &[VectorRef]) -> Result<VectorRef> {
|
|
||||||
with_match_primitive_type_id!(columns[0].data_type().logical_type_id(), |$S| {
|
|
||||||
with_match_primitive_type_id!(columns[1].data_type().logical_type_id(), |$T| {
|
|
||||||
let col = scalar_binary_op::<<$S as LogicalPrimitiveType>::Native, <$T as LogicalPrimitiveType>::Native, f64, _>(&columns[0], &columns[1], scalar_pow, &mut EvalContext::default())?;
|
|
||||||
Ok(Arc::new(col))
|
|
||||||
},{
|
|
||||||
unreachable!()
|
|
||||||
})
|
|
||||||
},{
|
|
||||||
unreachable!()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn scalar_pow<S, T>(value: Option<S>, base: Option<T>, _ctx: &mut EvalContext) -> Option<f64>
|
|
||||||
where
|
|
||||||
S: AsPrimitive<f64>,
|
|
||||||
T: AsPrimitive<f64>,
|
|
||||||
{
|
|
||||||
match (value, base) {
|
|
||||||
(Some(value), Some(base)) => Some(value.as_().pow(base.as_())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for PowFunction {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "POW")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use common_query::prelude::TypeSignature;
|
|
||||||
use datatypes::value::Value;
|
|
||||||
use datatypes::vectors::{Float32Vector, Int8Vector};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::function::FunctionContext;
|
|
||||||
#[test]
|
|
||||||
fn test_pow_function() {
|
|
||||||
let pow = PowFunction;
|
|
||||||
|
|
||||||
assert_eq!("pow", pow.name());
|
|
||||||
assert_eq!(
|
|
||||||
ConcreteDataType::float64_datatype(),
|
|
||||||
pow.return_type(&[]).unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(matches!(pow.signature(),
|
|
||||||
Signature {
|
|
||||||
type_signature: TypeSignature::Uniform(2, valid_types),
|
|
||||||
volatility: Volatility::Immutable
|
|
||||||
} if valid_types == ConcreteDataType::numerics()
|
|
||||||
));
|
|
||||||
|
|
||||||
let values = vec![1.0, 2.0, 3.0];
|
|
||||||
let bases = vec![0i8, -1i8, 3i8];
|
|
||||||
|
|
||||||
let args: Vec<VectorRef> = vec![
|
|
||||||
Arc::new(Float32Vector::from_vec(values.clone())),
|
|
||||||
Arc::new(Int8Vector::from_vec(bases.clone())),
|
|
||||||
];
|
|
||||||
|
|
||||||
let vector = pow.eval(&FunctionContext::default(), &args).unwrap();
|
|
||||||
assert_eq!(3, vector.len());
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
let p: f64 = (values[i] as f64).pow(bases[i] as f64);
|
|
||||||
assert!(matches!(vector.get(i), Value::Float64(v) if v == p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user