mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2025-12-25 23:49:58 +00:00
Compare commits
19 Commits
v0.1.0-alp
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a179481966 | ||
|
|
3ae7362f58 | ||
|
|
2e9c9f2176 | ||
|
|
89b942798c | ||
|
|
952e646e1d | ||
|
|
23f0320ffb | ||
|
|
49403012b5 | ||
|
|
b87d5334d1 | ||
|
|
fa4a74a408 | ||
|
|
e62b302fb2 | ||
|
|
6288fdb6bc | ||
|
|
cefdffff09 | ||
|
|
c3776ddd18 | ||
|
|
056d7cb911 | ||
|
|
16d1132733 | ||
|
|
37dc85a29e | ||
|
|
d08f8b87a6 | ||
|
|
64a706d6f0 | ||
|
|
cf4e876e51 |
2
.config/nextest.toml
Normal file
2
.config/nextest.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[profile.default]
|
||||
slow-timeout = { period = "60s", terminate-after = 3, grace-period = "30s" }
|
||||
39
.github/workflows/coverage.yml
vendored
39
.github/workflows/coverage.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- "main"
|
||||
- "develop"
|
||||
workflow_dispatch:
|
||||
|
||||
name: Code coverage
|
||||
|
||||
@@ -12,51 +13,37 @@ env:
|
||||
RUST_TOOLCHAIN: nightly-2022-07-14
|
||||
|
||||
jobs:
|
||||
grcov:
|
||||
coverage:
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache LLVM and Clang
|
||||
id: cache-llvm
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./llvm
|
||||
key: llvm
|
||||
- uses: actions/checkout@v3
|
||||
- uses: arduino/setup-protoc@v1
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
version: "14.0"
|
||||
cached: ${{ steps.cache-llvm.outputs.cache-hit }}
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
profile: minimal
|
||||
components: llvm-tools-preview
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.0.0
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cleanup disk
|
||||
uses: curoky/cleanup-disk-action@v2.0
|
||||
with:
|
||||
retain: 'rust,llvm'
|
||||
- name: Execute tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
retain: 'rust'
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@nextest
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@cargo-llvm-cov
|
||||
- name: Collect coverage data
|
||||
run: cargo llvm-cov nextest --workspace --lcov --output-path lcov.info
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
CARGO_INCREMENTAL: 0
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=unwind -Zpanic_abort_tests -Clink-arg=-fuse-ld=lld"
|
||||
GT_S3_BUCKET: ${{ secrets.S3_BUCKET }}
|
||||
GT_S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||
GT_S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
|
||||
UNITTEST_LOG_DIR: "__unittest_logs"
|
||||
- name: Gather coverage data
|
||||
id: coverage
|
||||
uses: actions-rs/grcov@v0.1
|
||||
- name: Codecov upload
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
|
||||
63
.github/workflows/develop.yml
vendored
63
.github/workflows/develop.yml
vendored
@@ -13,6 +13,7 @@ on:
|
||||
- '**.yml'
|
||||
- '.dockerignore'
|
||||
- 'docker/**'
|
||||
workflow_dispatch:
|
||||
|
||||
name: Continuous integration for developing
|
||||
|
||||
@@ -26,19 +27,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: arduino/setup-protoc@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.0.0
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --all-targets
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Run cargo check
|
||||
run: cargo check --workspace --all-targets
|
||||
|
||||
test:
|
||||
name: Test Suite
|
||||
@@ -46,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache LLVM and Clang
|
||||
id: cache-llvm
|
||||
uses: actions/cache@v3
|
||||
@@ -58,21 +55,19 @@ jobs:
|
||||
with:
|
||||
version: "14.0"
|
||||
cached: ${{ steps.cache-llvm.outputs.cache-hit }}
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.0.0
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cleanup disk
|
||||
uses: curoky/cleanup-disk-action@v2.0
|
||||
with:
|
||||
retain: 'rust,llvm'
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
- name: Install latest nextest release
|
||||
uses: taiki-e/install-action@nextest
|
||||
- name: Run tests
|
||||
run: cargo nextest run
|
||||
env:
|
||||
CARGO_BUILD_RUSTFLAGS: "-C link-arg=-fuse-ld=lld"
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -87,20 +82,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: arduino/setup-protoc@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
components: rustfmt
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.0.0
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Run cargo fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: Clippy
|
||||
@@ -108,17 +99,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: arduino/setup-protoc@v1
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
components: clippy
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.0.0
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings -D clippy::print_stdout -D clippy::print_stderr
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Run cargo clippy
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings -D clippy::print_stdout -D clippy::print_stderr
|
||||
|
||||
75
.github/workflows/release.yml
vendored
75
.github/workflows/release.yml
vendored
@@ -2,6 +2,7 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release
|
||||
|
||||
@@ -13,20 +14,20 @@ jobs:
|
||||
name: Build binary
|
||||
strategy:
|
||||
matrix:
|
||||
# The file format is greptime-<tag>.<os>-<arch>
|
||||
# The file format is greptime-<os>-<arch>
|
||||
include:
|
||||
- arch: x86_64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
file: greptime-${{ github.ref_name }}.linux-amd64
|
||||
file: greptime-linux-amd64
|
||||
- arch: aarch64-unknown-linux-gnu
|
||||
os: ubuntu-latest
|
||||
file: greptime-${{ github.ref_name }}.linux-arm64
|
||||
file: greptime-linux-arm64
|
||||
- arch: aarch64-apple-darwin
|
||||
os: macos-latest
|
||||
file: greptime-${{ github.ref_name }}.darwin-arm64
|
||||
file: greptime-darwin-arm64
|
||||
- arch: x86_64-apple-darwin
|
||||
os: macos-latest
|
||||
file: greptime-${{ github.ref_name }}.darwin-amd64
|
||||
file: greptime-darwin-amd64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
@@ -37,7 +38,6 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
./llvm
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
@@ -53,11 +53,6 @@ jobs:
|
||||
sudo cp protoc/bin/protoc /usr/local/bin/
|
||||
sudo cp -r protoc/include/google /usr/local/include/
|
||||
|
||||
- uses: KyleMayes/install-llvm-action@v1
|
||||
with:
|
||||
version: "14.0"
|
||||
cached: ${{ steps.cache.outputs.cache-hit }}
|
||||
|
||||
- name: Install Protoc for macos
|
||||
if: contains(matrix.arch, 'darwin')
|
||||
run: |
|
||||
@@ -69,37 +64,31 @@ jobs:
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install libssl-dev pkg-config g++-aarch64-linux-gnu gcc-aarch64-linux-gnu
|
||||
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Install rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
override: true
|
||||
target: ${{ matrix.arch }}
|
||||
targets: ${{ matrix.arch }}
|
||||
|
||||
- name: Output package versions
|
||||
run: protoc --version ; cargo version ; rustc --version ; gcc --version ; g++ --version
|
||||
|
||||
- name: Run cargo build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: ${{ matrix.opts }} --release --locked --target ${{ matrix.arch }}
|
||||
env:
|
||||
CARGO_BUILD_RUSTFLAGS: "-C link-arg=-fuse-ld=lld"
|
||||
run: cargo build ${{ matrix.opts }} --release --locked --target ${{ matrix.arch }}
|
||||
|
||||
- name: Calculate checksum and rename binary
|
||||
shell: bash
|
||||
run: |
|
||||
cd target/${{ matrix.arch }}/release
|
||||
cp greptime ${{ matrix.file }}
|
||||
echo $(shasum -a 256 greptime | cut -f1 -d' ') > ${{ matrix.file }}.sha256sum
|
||||
chmod +x greptime
|
||||
tar -zcvf ${{ matrix.file }}.tgz greptime
|
||||
echo $(shasum -a 256 ${{ matrix.file }}.tgz | cut -f1 -d' ') > ${{ matrix.file }}.sha256sum
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.file }}
|
||||
path: target/${{ matrix.arch }}/release/${{ matrix.file }}
|
||||
path: target/${{ matrix.arch }}/release/${{ matrix.file }}.tgz
|
||||
|
||||
- name: Upload checksum of artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -122,7 +111,7 @@ jobs:
|
||||
with:
|
||||
name: "Release ${{ github.ref_name }}"
|
||||
files: |
|
||||
**/greptime-${{ github.ref_name }}.*
|
||||
**/greptime-*
|
||||
|
||||
docker:
|
||||
name: Build docker image
|
||||
@@ -130,32 +119,31 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download amd64 binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: greptime-${{ github.ref_name }}.linux-amd64
|
||||
name: greptime-linux-amd64
|
||||
path: amd64
|
||||
|
||||
- name: Rename amd64 binary
|
||||
- name: Unzip the amd64 artifacts
|
||||
run: |
|
||||
mv amd64/greptime-${{ github.ref_name }}.linux-amd64 amd64/greptime
|
||||
cd amd64
|
||||
tar xvf greptime-linux-amd64.tgz
|
||||
rm greptime-linux-amd64.tgz
|
||||
|
||||
- name: Download arm64 binary
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: greptime-${{ github.ref_name }}.linux-arm64
|
||||
name: greptime-linux-arm64
|
||||
path: arm64
|
||||
|
||||
- name: Rename arm64 binary
|
||||
- name: Unzip the arm64 artifacts
|
||||
run: |
|
||||
mv arm64/greptime-${{ github.ref_name }}.linux-arm64 arm64/greptime
|
||||
|
||||
- name: Set file permissions
|
||||
shell: bash
|
||||
run: |
|
||||
chmod +x amd64/greptime arm64/greptime
|
||||
cd arm64
|
||||
tar xvf greptime-linux-arm64.tgz
|
||||
rm greptime-linux-arm64.tgz
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
@@ -170,6 +158,12 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Configure tag # If the release tag is v0.1.0, then the image version tag will be 0.1.0.
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
echo "VERSION=${VERSION:1}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
@@ -184,5 +178,6 @@ jobs:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
ghcr.io/greptimeteam/greptimedb:${{ github.ref_name }}
|
||||
greptime/greptimedb:${{ github.ref_name }}
|
||||
greptime/greptimedb:latest
|
||||
greptime/greptimedb:${{ env.VERSION }}
|
||||
ghcr.io/greptimeteam/greptimedb:${{ env.VERSION }}
|
||||
|
||||
@@ -10,7 +10,6 @@ To learn about the design of GreptimeDB, please refer to the [design docs](https
|
||||
- Make sure all unit tests are passed.
|
||||
- Make sure all clippy warnings are fixed (you can check it locally by running `cargo clippy --workspace --all-targets -- -D warnings -D clippy::print_stdout -D clippy::print_stderr`).
|
||||
|
||||
|
||||
#### `pre-commit` Hooks
|
||||
You could setup the [`pre-commit`](https://pre-commit.com/#plugins) hooks to run these checks on every commit automatically.
|
||||
|
||||
@@ -37,11 +36,10 @@ pre-commit installed at .git/hooks/pre-pus
|
||||
|
||||
now `pre-commit` will run automatically on `git commit`.
|
||||
|
||||
|
||||
### Title
|
||||
|
||||
The titles of pull requests should be prefixed with category name listed in [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0)
|
||||
like `feat`/`fix`/`doc`, with a concise summary of code change follows. DO NOT use last commit message as pull request title.
|
||||
The titles of pull requests should be prefixed with one of the change types listed in [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0)
|
||||
like `feat`/`fix`/`docs`, with a concise summary of code change follows. The following scope field is optional, you can fill it with the name of sub-crate if the pull request only changes one, or just leave it blank.
|
||||
|
||||
### Description
|
||||
|
||||
@@ -49,15 +47,10 @@ like `feat`/`fix`/`doc`, with a concise summary of code change follows. DO NOT u
|
||||
- But if it contains large code change, make sure to state the motivation/design details of this PR so that reviewers can understand what you're trying to do.
|
||||
- If the PR contains any breaking change or API change, make sure that is clearly listed in your description.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
All commit messages SHOULD adhere to the [Conventional Commits specification](https://conventionalcommits.org/).
|
||||
|
||||
## Getting help
|
||||
|
||||
There are many ways to get help when you're stuck. It is recommended to ask for help by opening an issue, with a detailed description
|
||||
of what you were trying to do and what went wrong. You can also reach for help in our Slack channel.
|
||||
|
||||
|
||||
## Bug report
|
||||
To report a bug or a security issue, you can [open a new GitHub issue](https://github.com/GrepTimeTeam/greptimedb/issues/new).
|
||||
|
||||
39
Cargo.lock
generated
39
Cargo.lock
generated
@@ -1121,6 +1121,22 @@ dependencies = [
|
||||
"tower",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "common-insert"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"api",
|
||||
"async-trait",
|
||||
"common-base",
|
||||
"common-error",
|
||||
"common-query",
|
||||
"common-telemetry",
|
||||
"common-time",
|
||||
"datatypes",
|
||||
"snafu",
|
||||
"table",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "common-query"
|
||||
version = "0.1.0"
|
||||
@@ -1635,6 +1651,7 @@ dependencies = [
|
||||
"common-catalog",
|
||||
"common-error",
|
||||
"common-grpc",
|
||||
"common-insert",
|
||||
"common-query",
|
||||
"common-recordbatch",
|
||||
"common-runtime",
|
||||
@@ -2000,6 +2017,7 @@ dependencies = [
|
||||
"catalog",
|
||||
"client",
|
||||
"common-base",
|
||||
"common-catalog",
|
||||
"common-error",
|
||||
"common-grpc",
|
||||
"common-query",
|
||||
@@ -2014,13 +2032,16 @@ dependencies = [
|
||||
"datatypes",
|
||||
"futures",
|
||||
"itertools",
|
||||
"meta-client",
|
||||
"openmetrics-parser",
|
||||
"prost 0.11.0",
|
||||
"query",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"servers",
|
||||
"snafu",
|
||||
"sql",
|
||||
"sqlparser",
|
||||
"store-api",
|
||||
"table",
|
||||
"tempdir",
|
||||
@@ -2940,6 +2961,7 @@ dependencies = [
|
||||
"api",
|
||||
"async-trait",
|
||||
"common-base",
|
||||
"common-catalog",
|
||||
"common-error",
|
||||
"common-grpc",
|
||||
"common-runtime",
|
||||
@@ -2951,6 +2973,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"lazy_static",
|
||||
"parking_lot",
|
||||
"prost 0.11.0",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -5068,6 +5091,7 @@ dependencies = [
|
||||
"tokio-stream",
|
||||
"tokio-test",
|
||||
"tonic",
|
||||
"tonic-reflection",
|
||||
"tower",
|
||||
"tower-http",
|
||||
]
|
||||
@@ -5968,6 +5992,21 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-reflection"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0455f730d540a1484bffc3c55c94100b18a662597b982c2e9073f2c55c602616"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost 0.11.0",
|
||||
"prost-types 0.11.1",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tonic",
|
||||
"tonic-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
|
||||
@@ -15,6 +15,7 @@ members = [
|
||||
"src/common/recordbatch",
|
||||
"src/common/runtime",
|
||||
"src/common/substrait",
|
||||
"src/common/insert",
|
||||
"src/common/telemetry",
|
||||
"src/common/time",
|
||||
"src/datanode",
|
||||
|
||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 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.
|
||||
@@ -2,7 +2,6 @@
|
||||
name = "benchmarks"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
arrow = "10"
|
||||
|
||||
63
scripts/install.sh
Executable file
63
scripts/install.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -ue
|
||||
|
||||
OS_TYPE=
|
||||
ARCH_TYPE=
|
||||
VERSION=${1:-latest}
|
||||
GITHUB_ORG=GreptimeTeam
|
||||
GITHUB_REPO=greptimedb
|
||||
BIN=greptimedb
|
||||
|
||||
get_os_type() {
|
||||
os_type="$(uname -s)"
|
||||
|
||||
case "$os_type" in
|
||||
Darwin)
|
||||
OS_TYPE=darwin
|
||||
;;
|
||||
Linux)
|
||||
OS_TYPE=linux
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown OS type: $os_type"
|
||||
exit 1
|
||||
esac
|
||||
}
|
||||
|
||||
get_arch_type() {
|
||||
arch_type="$(uname -m)"
|
||||
|
||||
case "$arch_type" in
|
||||
arm64)
|
||||
ARCH_TYPE=arm64
|
||||
;;
|
||||
aarch64)
|
||||
ARCH_TYPE=arm64
|
||||
;;
|
||||
x86_64)
|
||||
ARCH_TYPE=amd64
|
||||
;;
|
||||
amd64)
|
||||
ARCH_TYPE=amd64
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown CPU type: $arch_type"
|
||||
exit 1
|
||||
esac
|
||||
}
|
||||
|
||||
get_os_type
|
||||
get_arch_type
|
||||
|
||||
if [ -n "${OS_TYPE}" ] && [ -n "${ARCH_TYPE}" ]; then
|
||||
echo "Downloading ${BIN}, OS: ${OS_TYPE}, Arch: ${ARCH_TYPE}, Version: ${VERSION}"
|
||||
|
||||
if [ "${VERSION}" = "latest" ]; then
|
||||
wget "https://github.com/${GITHUB_ORG}/${GITHUB_REPO}/releases/latest/download/${BIN}-${OS_TYPE}-${ARCH_TYPE}.tgz"
|
||||
else
|
||||
wget "https://github.com/${GITHUB_ORG}/${GITHUB_REPO}/releases/download/${VERSION}/${BIN}-${OS_TYPE}-${ARCH_TYPE}.tgz"
|
||||
fi
|
||||
|
||||
tar xvf ${BIN}-${OS_TYPE}-${ARCH_TYPE}.tgz && rm ${BIN}-${OS_TYPE}-${ARCH_TYPE}.tgz && echo "Run '${BIN} --help' to get started"
|
||||
fi
|
||||
@@ -1,5 +1,9 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let default_out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
tonic_build::configure()
|
||||
.file_descriptor_set_path(default_out_dir.join("greptime_fd.bin"))
|
||||
.compile(
|
||||
&[
|
||||
"greptime/v1/insert.proto",
|
||||
|
||||
@@ -9,6 +9,6 @@ message InsertBatch {
|
||||
uint32 row_count = 2;
|
||||
}
|
||||
|
||||
message RegionId {
|
||||
uint64 id = 1;
|
||||
message RegionNumber {
|
||||
uint32 id = 1;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,9 @@ message RegionRoute {
|
||||
}
|
||||
|
||||
message Table {
|
||||
TableName table_name = 1;
|
||||
bytes table_schema = 2;
|
||||
uint64 id = 1;
|
||||
TableName table_name = 2;
|
||||
bytes table_schema = 3;
|
||||
}
|
||||
|
||||
message Region {
|
||||
@@ -80,3 +81,9 @@ message Partition {
|
||||
repeated bytes column_list = 1;
|
||||
repeated bytes value_list = 2;
|
||||
}
|
||||
|
||||
// This message is only for saving into store.
|
||||
message TableRouteValue {
|
||||
repeated Peer peers = 1;
|
||||
TableRoute table_route = 2;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub use prost::DecodeError;
|
||||
use prost::Message;
|
||||
|
||||
use crate::v1::codec::{InsertBatch, PhysicalPlanNode, RegionId, SelectResult};
|
||||
use crate::v1::codec::InsertBatch;
|
||||
use crate::v1::codec::PhysicalPlanNode;
|
||||
use crate::v1::codec::RegionNumber;
|
||||
use crate::v1::codec::SelectResult;
|
||||
use crate::v1::meta::TableRouteValue;
|
||||
|
||||
macro_rules! impl_convert_with_bytes {
|
||||
($data_type: ty) => {
|
||||
@@ -24,7 +28,8 @@ macro_rules! impl_convert_with_bytes {
|
||||
impl_convert_with_bytes!(InsertBatch);
|
||||
impl_convert_with_bytes!(SelectResult);
|
||||
impl_convert_with_bytes!(PhysicalPlanNode);
|
||||
impl_convert_with_bytes!(RegionId);
|
||||
impl_convert_with_bytes!(RegionNumber);
|
||||
impl_convert_with_bytes!(TableRouteValue);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -130,10 +135,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_convert_region_id() {
|
||||
let region_id = RegionId { id: 12 };
|
||||
let region_id = RegionNumber { id: 12 };
|
||||
|
||||
let bytes: Vec<u8> = region_id.into();
|
||||
let region_id: RegionId = bytes.deref().try_into().unwrap();
|
||||
let region_id: RegionNumber = bytes.deref().try_into().unwrap();
|
||||
|
||||
assert_eq!(12, region_id.id);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#![allow(clippy::derive_partial_eq_without_eq)]
|
||||
tonic::include_proto!("greptime.v1");
|
||||
|
||||
pub const GREPTIME_FD_SET: &[u8] = tonic::include_file_descriptor_set!("greptime_fd");
|
||||
|
||||
pub mod codec {
|
||||
tonic::include_proto!("greptime.v1.codec");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,47 @@
|
||||
tonic::include_proto!("greptime.v1.meta");
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
|
||||
pub const PROTOCOL_VERSION: u64 = 1;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PeerDict {
|
||||
peers: HashMap<Peer, usize>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl PeerDict {
|
||||
pub fn get_or_insert(&mut self, peer: Peer) -> usize {
|
||||
let index = self.peers.entry(peer).or_insert_with(|| {
|
||||
let v = self.index;
|
||||
self.index += 1;
|
||||
v
|
||||
});
|
||||
|
||||
*index
|
||||
}
|
||||
|
||||
pub fn into_peers(self) -> Vec<Peer> {
|
||||
let mut array = vec![Peer::default(); self.index];
|
||||
for (p, i) in self.peers {
|
||||
array[i] = p;
|
||||
}
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_hash_xor_eq)]
|
||||
impl Hash for Peer {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
self.addr.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Peer {}
|
||||
|
||||
impl RequestHeader {
|
||||
#[inline]
|
||||
pub fn new((cluster_id, member_id): (u64, u64)) -> Self {
|
||||
@@ -33,6 +73,21 @@ impl ResponseHeader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorCode {
|
||||
NoActiveDatanodes = 1,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
#[inline]
|
||||
pub fn no_active_datanodes() -> Self {
|
||||
Self {
|
||||
code: ErrorCode::NoActiveDatanodes as i32,
|
||||
err_msg: "No active datanodes".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! gen_set_header {
|
||||
($req: ty) => {
|
||||
impl $req {
|
||||
@@ -52,3 +107,59 @@ gen_set_header!(PutRequest);
|
||||
gen_set_header!(BatchPutRequest);
|
||||
gen_set_header!(CompareAndPutRequest);
|
||||
gen_set_header!(DeleteRangeRequest);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::vec;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_peer_dict() {
|
||||
let mut dict = PeerDict::default();
|
||||
|
||||
dict.get_or_insert(Peer {
|
||||
id: 1,
|
||||
addr: "111".to_string(),
|
||||
});
|
||||
dict.get_or_insert(Peer {
|
||||
id: 2,
|
||||
addr: "222".to_string(),
|
||||
});
|
||||
dict.get_or_insert(Peer {
|
||||
id: 1,
|
||||
addr: "111".to_string(),
|
||||
});
|
||||
dict.get_or_insert(Peer {
|
||||
id: 1,
|
||||
addr: "111".to_string(),
|
||||
});
|
||||
dict.get_or_insert(Peer {
|
||||
id: 1,
|
||||
addr: "111".to_string(),
|
||||
});
|
||||
dict.get_or_insert(Peer {
|
||||
id: 1,
|
||||
addr: "111".to_string(),
|
||||
});
|
||||
dict.get_or_insert(Peer {
|
||||
id: 2,
|
||||
addr: "222".to_string(),
|
||||
});
|
||||
|
||||
assert_eq!(2, dict.index);
|
||||
assert_eq!(
|
||||
vec![
|
||||
Peer {
|
||||
id: 1,
|
||||
addr: "111".to_string(),
|
||||
},
|
||||
Peer {
|
||||
id: 2,
|
||||
addr: "222".to_string(),
|
||||
}
|
||||
],
|
||||
dict.into_peers()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,13 +48,19 @@ pub enum Error {
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid system catalog entry type: {:?}", entry_type))]
|
||||
InvalidEntryType { entry_type: Option<u8> },
|
||||
InvalidEntryType {
|
||||
entry_type: Option<u8>,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid system catalog key: {:?}", key))]
|
||||
InvalidKey { key: Option<String> },
|
||||
InvalidKey {
|
||||
key: Option<String>,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Catalog value is not present"))]
|
||||
EmptyValue,
|
||||
EmptyValue { backtrace: Backtrace },
|
||||
|
||||
#[snafu(display("Failed to deserialize value, source: {}", source))]
|
||||
ValueDeserialize {
|
||||
@@ -63,10 +69,16 @@ pub enum Error {
|
||||
},
|
||||
|
||||
#[snafu(display("Cannot find catalog by name: {}", catalog_name))]
|
||||
CatalogNotFound { catalog_name: String },
|
||||
CatalogNotFound {
|
||||
catalog_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Cannot find schema, schema info: {}", schema_info))]
|
||||
SchemaNotFound { schema_info: String },
|
||||
SchemaNotFound {
|
||||
schema_info: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Table {} already exists", table))]
|
||||
TableExists { table: String, backtrace: Backtrace },
|
||||
@@ -85,7 +97,10 @@ pub enum Error {
|
||||
},
|
||||
|
||||
#[snafu(display("Table not found while opening table, table info: {}", table_info))]
|
||||
TableNotFound { table_info: String },
|
||||
TableNotFound {
|
||||
table_info: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to read system catalog table records"))]
|
||||
ReadSystemCatalog {
|
||||
@@ -155,6 +170,25 @@ pub enum Error {
|
||||
|
||||
#[snafu(display("Failed to parse table id from metasrv, data: {:?}", data))]
|
||||
ParseTableId { data: String, backtrace: Backtrace },
|
||||
|
||||
#[snafu(display("Failed to deserialize partition rule from string: {:?}", data))]
|
||||
DeserializePartitionRule {
|
||||
data: String,
|
||||
source: serde_json::error::Error,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid table schema in catalog, source: {:?}", source))]
|
||||
InvalidSchemaInCatalog {
|
||||
#[snafu(backtrace)]
|
||||
source: datatypes::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Catalog internal error: {}", source))]
|
||||
Internal {
|
||||
#[snafu(backtrace)]
|
||||
source: BoxedError,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -171,7 +205,7 @@ impl ErrorExt for Error {
|
||||
| Error::CatalogStateInconsistent { .. } => StatusCode::Unexpected,
|
||||
|
||||
Error::SystemCatalog { .. }
|
||||
| Error::EmptyValue
|
||||
| Error::EmptyValue { .. }
|
||||
| Error::ValueDeserialize { .. }
|
||||
| Error::Io { .. } => StatusCode::StorageUnavailable,
|
||||
|
||||
@@ -194,6 +228,9 @@ impl ErrorExt for Error {
|
||||
Error::BumpTableId { .. } | Error::ParseTableId { .. } => {
|
||||
StatusCode::StorageUnavailable
|
||||
}
|
||||
Error::DeserializePartitionRule { .. } => StatusCode::Unexpected,
|
||||
Error::InvalidSchemaInCatalog { .. } => StatusCode::Unexpected,
|
||||
Error::Internal { source, .. } => source.status_code(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +270,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
StatusCode::Unexpected,
|
||||
Error::InvalidKey { key: None }.status_code()
|
||||
InvalidKeySnafu { key: None }.build().status_code()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -274,7 +311,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
StatusCode::StorageUnavailable,
|
||||
Error::EmptyValue.status_code()
|
||||
EmptyValueSnafu {}.build().status_code()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,5 @@ pub mod memory;
|
||||
|
||||
pub use manager::LocalCatalogManager;
|
||||
pub use memory::{
|
||||
new_memory_catalog_list, MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider,
|
||||
new_memory_catalog_list, MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider,
|
||||
};
|
||||
|
||||
@@ -6,12 +6,11 @@ use common_catalog::consts::{
|
||||
DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, INFORMATION_SCHEMA_NAME, MIN_USER_TABLE_ID,
|
||||
SYSTEM_CATALOG_NAME, SYSTEM_CATALOG_TABLE_NAME,
|
||||
};
|
||||
use common_recordbatch::RecordBatch;
|
||||
use common_recordbatch::{RecordBatch, SendableRecordBatchStream};
|
||||
use common_telemetry::info;
|
||||
use datatypes::prelude::ScalarVector;
|
||||
use datatypes::vectors::{BinaryVector, UInt8Vector};
|
||||
use futures_util::lock::Mutex;
|
||||
use futures_util::StreamExt;
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
use table::engine::{EngineContext, TableEngineRef};
|
||||
use table::metadata::TableId;
|
||||
@@ -19,13 +18,12 @@ use table::requests::OpenTableRequest;
|
||||
use table::table::numbers::NumbersTable;
|
||||
use table::TableRef;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::error::{
|
||||
CatalogNotFoundSnafu, IllegalManagerStateSnafu, OpenTableSnafu, ReadSystemCatalogSnafu,
|
||||
SchemaNotFoundSnafu, SystemCatalogSnafu, SystemCatalogTypeMismatchSnafu, TableExistsSnafu,
|
||||
TableNotFoundSnafu,
|
||||
CatalogNotFoundSnafu, IllegalManagerStateSnafu, OpenTableSnafu, SchemaNotFoundSnafu,
|
||||
SystemCatalogSnafu, SystemCatalogTypeMismatchSnafu, TableExistsSnafu, TableNotFoundSnafu,
|
||||
};
|
||||
use crate::local::memory::{MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use crate::error::{ReadSystemCatalogSnafu, Result};
|
||||
use crate::local::memory::{MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use crate::system::{
|
||||
decode_system_catalog, Entry, SystemCatalogTable, TableEntry, ENTRY_TYPE_INDEX, KEY_INDEX,
|
||||
VALUE_INDEX,
|
||||
@@ -40,7 +38,7 @@ use crate::{
|
||||
/// A `CatalogManager` consists of a system catalog and a bunch of user catalogs.
|
||||
pub struct LocalCatalogManager {
|
||||
system: Arc<SystemCatalog>,
|
||||
catalogs: Arc<MemoryCatalogList>,
|
||||
catalogs: Arc<MemoryCatalogManager>,
|
||||
engine: TableEngineRef,
|
||||
next_table_id: AtomicU32,
|
||||
init_lock: Mutex<bool>,
|
||||
@@ -70,17 +68,10 @@ impl LocalCatalogManager {
|
||||
/// Scan all entries from system catalog table
|
||||
pub async fn init(&self) -> Result<()> {
|
||||
self.init_system_catalog()?;
|
||||
let mut system_records = self.system.information_schema.system.records().await?;
|
||||
let mut max_table_id = 0;
|
||||
while let Some(records) = system_records
|
||||
.next()
|
||||
.await
|
||||
.transpose()
|
||||
.context(ReadSystemCatalogSnafu)?
|
||||
{
|
||||
let table_id = self.handle_system_catalog_entries(records).await?;
|
||||
max_table_id = max_table_id.max(table_id);
|
||||
}
|
||||
let system_records = self.system.information_schema.system.records().await?;
|
||||
let entries = self.collect_system_catalog_entries(system_records).await?;
|
||||
let max_table_id = self.handle_system_catalog_entries(entries).await?;
|
||||
|
||||
info!(
|
||||
"All system catalog entries processed, max table id: {}",
|
||||
max_table_id
|
||||
@@ -110,7 +101,6 @@ impl LocalCatalogManager {
|
||||
let default_schema = Arc::new(MemorySchemaProvider::new());
|
||||
|
||||
// Add numbers table for test
|
||||
// TODO(hl): remove this registration
|
||||
let table = Arc::new(NumbersTable::default());
|
||||
default_schema.register_table("numbers".to_string(), table)?;
|
||||
|
||||
@@ -120,47 +110,65 @@ impl LocalCatalogManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Processes records from system catalog table and returns the max table id persisted
|
||||
/// in system catalog table.
|
||||
async fn handle_system_catalog_entries(&self, records: RecordBatch) -> Result<TableId> {
|
||||
/// Collect stream of system catalog entries to `Vec<Entry>`
|
||||
async fn collect_system_catalog_entries(
|
||||
&self,
|
||||
stream: SendableRecordBatchStream,
|
||||
) -> Result<Vec<Entry>> {
|
||||
let record_batch = common_recordbatch::util::collect(stream)
|
||||
.await
|
||||
.context(ReadSystemCatalogSnafu)?;
|
||||
let rbs = record_batch
|
||||
.into_iter()
|
||||
.map(Self::record_batch_to_entry)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(rbs.into_iter().flat_map(Vec::into_iter).collect::<_>())
|
||||
}
|
||||
|
||||
/// Convert `RecordBatch` to a vector of `Entry`.
|
||||
fn record_batch_to_entry(rb: RecordBatch) -> Result<Vec<Entry>> {
|
||||
ensure!(
|
||||
records.df_recordbatch.columns().len() >= 6,
|
||||
rb.df_recordbatch.columns().len() >= 6,
|
||||
SystemCatalogSnafu {
|
||||
msg: format!(
|
||||
"Length mismatch: {}",
|
||||
records.df_recordbatch.columns().len()
|
||||
)
|
||||
msg: format!("Length mismatch: {}", rb.df_recordbatch.columns().len())
|
||||
}
|
||||
);
|
||||
|
||||
let entry_type = UInt8Vector::try_from_arrow_array(&records.df_recordbatch.columns()[0])
|
||||
let entry_type = UInt8Vector::try_from_arrow_array(&rb.df_recordbatch.columns()[0])
|
||||
.with_context(|_| SystemCatalogTypeMismatchSnafu {
|
||||
data_type: records.df_recordbatch.columns()[ENTRY_TYPE_INDEX]
|
||||
data_type: rb.df_recordbatch.columns()[ENTRY_TYPE_INDEX]
|
||||
.data_type()
|
||||
.clone(),
|
||||
})?;
|
||||
|
||||
let key = BinaryVector::try_from_arrow_array(&records.df_recordbatch.columns()[1])
|
||||
let key = BinaryVector::try_from_arrow_array(&rb.df_recordbatch.columns()[1])
|
||||
.with_context(|_| SystemCatalogTypeMismatchSnafu {
|
||||
data_type: records.df_recordbatch.columns()[KEY_INDEX]
|
||||
.data_type()
|
||||
.clone(),
|
||||
data_type: rb.df_recordbatch.columns()[KEY_INDEX].data_type().clone(),
|
||||
})?;
|
||||
|
||||
let value = BinaryVector::try_from_arrow_array(&records.df_recordbatch.columns()[3])
|
||||
let value = BinaryVector::try_from_arrow_array(&rb.df_recordbatch.columns()[3])
|
||||
.with_context(|_| SystemCatalogTypeMismatchSnafu {
|
||||
data_type: records.df_recordbatch.columns()[VALUE_INDEX]
|
||||
.data_type()
|
||||
.clone(),
|
||||
data_type: rb.df_recordbatch.columns()[VALUE_INDEX].data_type().clone(),
|
||||
})?;
|
||||
|
||||
let mut max_table_id = 0;
|
||||
let mut res = Vec::with_capacity(rb.num_rows());
|
||||
for ((t, k), v) in entry_type
|
||||
.iter_data()
|
||||
.zip(key.iter_data())
|
||||
.zip(value.iter_data())
|
||||
{
|
||||
let entry = decode_system_catalog(t, k, v)?;
|
||||
res.push(entry);
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Processes records from system catalog table and returns the max table id persisted
|
||||
/// in system catalog table.
|
||||
async fn handle_system_catalog_entries(&self, entries: Vec<Entry>) -> Result<TableId> {
|
||||
let entries = Self::sort_entries(entries);
|
||||
let mut max_table_id = 0;
|
||||
for entry in entries {
|
||||
match entry {
|
||||
Entry::Catalog(c) => {
|
||||
self.catalogs.register_catalog_if_absent(
|
||||
@@ -192,6 +200,13 @@ impl LocalCatalogManager {
|
||||
Ok(max_table_id)
|
||||
}
|
||||
|
||||
/// Sort catalog entries to ensure catalog entries comes first, then schema entries,
|
||||
/// and table entries is the last.
|
||||
fn sort_entries(mut entries: Vec<Entry>) -> Vec<Entry> {
|
||||
entries.sort();
|
||||
entries
|
||||
}
|
||||
|
||||
async fn open_and_register_table(&self, t: &TableEntry) -> Result<()> {
|
||||
let catalog = self
|
||||
.catalogs
|
||||
@@ -351,3 +366,50 @@ impl CatalogManager for LocalCatalogManager {
|
||||
schema.table(table_name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use super::*;
|
||||
use crate::system::{CatalogEntry, SchemaEntry};
|
||||
|
||||
#[test]
|
||||
fn test_sort_entry() {
|
||||
let vec = vec![
|
||||
Entry::Table(TableEntry {
|
||||
catalog_name: "C1".to_string(),
|
||||
schema_name: "S1".to_string(),
|
||||
table_name: "T1".to_string(),
|
||||
table_id: 1,
|
||||
}),
|
||||
Entry::Catalog(CatalogEntry {
|
||||
catalog_name: "C2".to_string(),
|
||||
}),
|
||||
Entry::Schema(SchemaEntry {
|
||||
catalog_name: "C1".to_string(),
|
||||
schema_name: "S1".to_string(),
|
||||
}),
|
||||
Entry::Schema(SchemaEntry {
|
||||
catalog_name: "C2".to_string(),
|
||||
schema_name: "S2".to_string(),
|
||||
}),
|
||||
Entry::Catalog(CatalogEntry {
|
||||
catalog_name: "".to_string(),
|
||||
}),
|
||||
Entry::Table(TableEntry {
|
||||
catalog_name: "C1".to_string(),
|
||||
schema_name: "S1".to_string(),
|
||||
table_name: "T2".to_string(),
|
||||
table_id: 2,
|
||||
}),
|
||||
];
|
||||
let res = LocalCatalogManager::sort_entries(vec);
|
||||
assert_matches!(res[0], Entry::Catalog(..));
|
||||
assert_matches!(res[1], Entry::Catalog(..));
|
||||
assert_matches!(res[2], Entry::Schema(..));
|
||||
assert_matches!(res[3], Entry::Schema(..));
|
||||
assert_matches!(res[4], Entry::Table(..));
|
||||
assert_matches!(res[5], Entry::Table(..));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,94 @@
|
||||
use std::any::Any;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use common_catalog::consts::MIN_USER_TABLE_ID;
|
||||
use snafu::OptionExt;
|
||||
use table::metadata::TableId;
|
||||
use table::TableRef;
|
||||
|
||||
use crate::error::{Result, TableExistsSnafu};
|
||||
use crate::error::{CatalogNotFoundSnafu, Result, SchemaNotFoundSnafu, TableExistsSnafu};
|
||||
use crate::schema::SchemaProvider;
|
||||
use crate::{CatalogList, CatalogProvider, CatalogProviderRef, SchemaProviderRef};
|
||||
use crate::{
|
||||
CatalogList, CatalogManager, CatalogProvider, CatalogProviderRef, RegisterSystemTableRequest,
|
||||
RegisterTableRequest, SchemaProviderRef,
|
||||
};
|
||||
|
||||
/// Simple in-memory list of catalogs
|
||||
#[derive(Default)]
|
||||
pub struct MemoryCatalogList {
|
||||
pub struct MemoryCatalogManager {
|
||||
/// Collection of catalogs containing schemas and ultimately Tables
|
||||
pub catalogs: RwLock<HashMap<String, CatalogProviderRef>>,
|
||||
pub table_id: AtomicU32,
|
||||
}
|
||||
|
||||
impl MemoryCatalogList {
|
||||
impl Default for MemoryCatalogManager {
|
||||
fn default() -> Self {
|
||||
let manager = Self {
|
||||
table_id: AtomicU32::new(MIN_USER_TABLE_ID),
|
||||
catalogs: Default::default(),
|
||||
};
|
||||
let default_catalog = Arc::new(MemoryCatalogProvider::new());
|
||||
manager
|
||||
.register_catalog("greptime".to_string(), default_catalog.clone())
|
||||
.unwrap();
|
||||
default_catalog
|
||||
.register_schema("public".to_string(), Arc::new(MemorySchemaProvider::new()))
|
||||
.unwrap();
|
||||
manager
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl CatalogManager for MemoryCatalogManager {
|
||||
async fn start(&self) -> Result<()> {
|
||||
self.table_id.store(MIN_USER_TABLE_ID, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_table_id(&self) -> Result<TableId> {
|
||||
Ok(self.table_id.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
async fn register_table(&self, request: RegisterTableRequest) -> Result<usize> {
|
||||
let catalogs = self.catalogs.write().unwrap();
|
||||
let catalog = catalogs
|
||||
.get(&request.catalog)
|
||||
.context(CatalogNotFoundSnafu {
|
||||
catalog_name: &request.catalog,
|
||||
})?
|
||||
.clone();
|
||||
let schema = catalog
|
||||
.schema(&request.schema)?
|
||||
.with_context(|| SchemaNotFoundSnafu {
|
||||
schema_info: format!("{}.{}", &request.catalog, &request.schema),
|
||||
})?;
|
||||
schema
|
||||
.register_table(request.table_name, request.table)
|
||||
.map(|v| if v.is_some() { 0 } else { 1 })
|
||||
}
|
||||
|
||||
async fn register_system_table(&self, _request: RegisterSystemTableRequest) -> Result<()> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn table(&self, catalog: &str, schema: &str, table_name: &str) -> Result<Option<TableRef>> {
|
||||
let c = self.catalogs.read().unwrap();
|
||||
let catalog = if let Some(c) = c.get(catalog) {
|
||||
c.clone()
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
match catalog.schema(schema)? {
|
||||
None => Ok(None),
|
||||
Some(s) => s.table(table_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemoryCatalogManager {
|
||||
/// Registers a catalog and return `None` if no catalog with the same name was already
|
||||
/// registered, or `Some` with the previously registered catalog.
|
||||
pub fn register_catalog_if_absent(
|
||||
@@ -37,7 +108,7 @@ impl MemoryCatalogList {
|
||||
}
|
||||
}
|
||||
|
||||
impl CatalogList for MemoryCatalogList {
|
||||
impl CatalogList for MemoryCatalogManager {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@@ -162,8 +233,8 @@ impl SchemaProvider for MemorySchemaProvider {
|
||||
}
|
||||
|
||||
/// Create a memory catalog list contains a numbers table for test
|
||||
pub fn new_memory_catalog_list() -> Result<Arc<MemoryCatalogList>> {
|
||||
Ok(Arc::new(MemoryCatalogList::default()))
|
||||
pub fn new_memory_catalog_list() -> Result<Arc<MemoryCatalogManager>> {
|
||||
Ok(Arc::new(MemoryCatalogManager::default()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -178,23 +249,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_new_memory_catalog_list() {
|
||||
let catalog_list = new_memory_catalog_list().unwrap();
|
||||
let default_catalog = catalog_list.catalog(DEFAULT_CATALOG_NAME).unwrap().unwrap();
|
||||
|
||||
assert!(catalog_list
|
||||
.catalog(DEFAULT_CATALOG_NAME)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
let default_catalog = Arc::new(MemoryCatalogProvider::default());
|
||||
catalog_list
|
||||
.register_catalog(DEFAULT_CATALOG_NAME.to_string(), default_catalog.clone())
|
||||
.unwrap();
|
||||
|
||||
assert!(default_catalog
|
||||
let default_schema = default_catalog
|
||||
.schema(DEFAULT_SCHEMA_NAME)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
let default_schema = Arc::new(MemorySchemaProvider::default());
|
||||
default_catalog
|
||||
.register_schema(DEFAULT_SCHEMA_NAME.to_string(), default_schema.clone())
|
||||
.unwrap();
|
||||
|
||||
default_schema
|
||||
@@ -203,7 +262,6 @@ mod tests {
|
||||
|
||||
let table = default_schema.table("numbers").unwrap();
|
||||
assert!(table.is_some());
|
||||
|
||||
assert!(default_schema.table("not_exists").unwrap().is_none());
|
||||
}
|
||||
|
||||
@@ -229,7 +287,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn test_register_if_absent() {
|
||||
let list = MemoryCatalogList::default();
|
||||
let list = MemoryCatalogManager::default();
|
||||
assert!(list
|
||||
.register_catalog_if_absent(
|
||||
"test_catalog".to_string(),
|
||||
@@ -241,6 +299,8 @@ mod tests {
|
||||
Arc::new(MemoryCatalogProvider::new()),
|
||||
)
|
||||
.unwrap();
|
||||
list.as_any().downcast_ref::<MemoryCatalogList>().unwrap();
|
||||
list.as_any()
|
||||
.downcast_ref::<MemoryCatalogManager>()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,25 +306,25 @@ impl TryFrom<u8> for EntryType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub enum Entry {
|
||||
Catalog(CatalogEntry),
|
||||
Schema(SchemaEntry),
|
||||
Table(TableEntry),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct CatalogEntry {
|
||||
pub catalog_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct SchemaEntry {
|
||||
pub catalog_name: String,
|
||||
pub schema_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd)]
|
||||
pub struct TableEntry {
|
||||
pub catalog_name: String,
|
||||
pub schema_name: String,
|
||||
|
||||
@@ -312,6 +312,7 @@ fn build_schema_for_tables() -> Schema {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_query::physical_plan::RuntimeEnv;
|
||||
use datatypes::arrow::array::Utf8Array;
|
||||
use datatypes::arrow::datatypes::DataType;
|
||||
@@ -319,32 +320,30 @@ mod tests {
|
||||
use table::table::numbers::NumbersTable;
|
||||
|
||||
use super::*;
|
||||
use crate::local::memory::{
|
||||
new_memory_catalog_list, MemoryCatalogProvider, MemorySchemaProvider,
|
||||
};
|
||||
use crate::local::memory::new_memory_catalog_list;
|
||||
use crate::CatalogList;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_tables() {
|
||||
let catalog_list = new_memory_catalog_list().unwrap();
|
||||
let catalog_provider = Arc::new(MemoryCatalogProvider::default());
|
||||
let schema = Arc::new(MemorySchemaProvider::new());
|
||||
let schema = catalog_list
|
||||
.catalog(DEFAULT_CATALOG_NAME)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.schema(DEFAULT_SCHEMA_NAME)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
schema
|
||||
.register_table("test_table".to_string(), Arc::new(NumbersTable::default()))
|
||||
.unwrap();
|
||||
catalog_provider
|
||||
.register_schema("test_schema".to_string(), schema)
|
||||
.unwrap();
|
||||
catalog_list
|
||||
.register_catalog("test_catalog".to_string(), catalog_provider)
|
||||
.unwrap();
|
||||
let tables = Tables::new(catalog_list, "test_engine".to_string());
|
||||
|
||||
let tables = Tables::new(catalog_list, "test_engine".to_string());
|
||||
let tables_stream = tables.scan(&None, &[], None).await.unwrap();
|
||||
let mut tables_stream = tables_stream
|
||||
.execute(0, Arc::new(RuntimeEnv::default()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if let Some(t) = tables_stream.next().await {
|
||||
let batch = t.unwrap().df_recordbatch;
|
||||
assert_eq!(1, batch.num_rows());
|
||||
@@ -354,7 +353,7 @@ mod tests {
|
||||
assert_eq!(&DataType::Utf8, batch.column(2).data_type());
|
||||
assert_eq!(&DataType::Utf8, batch.column(3).data_type());
|
||||
assert_eq!(
|
||||
"test_catalog",
|
||||
"greptime",
|
||||
batch
|
||||
.column(0)
|
||||
.as_any()
|
||||
@@ -364,7 +363,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"test_schema",
|
||||
"public",
|
||||
batch
|
||||
.column(1)
|
||||
.as_any()
|
||||
|
||||
@@ -12,6 +12,7 @@ use crate::error::{Result, TypeMismatchSnafu};
|
||||
|
||||
type ColumnName = String;
|
||||
|
||||
// TODO(fys): will remove in the future.
|
||||
#[derive(Default)]
|
||||
pub struct LinesWriter {
|
||||
column_name_index: HashMap<ColumnName, usize>,
|
||||
@@ -208,7 +209,7 @@ impl LinesWriter {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_ms_ts(p: Precision, ts: i64) -> i64 {
|
||||
pub fn to_ms_ts(p: Precision, ts: i64) -> i64 {
|
||||
match p {
|
||||
Precision::NANOSECOND => ts / 1_000_000,
|
||||
Precision::MICROSECOND => ts / 1000,
|
||||
|
||||
18
src/common/insert/Cargo.toml
Normal file
18
src/common/insert/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "common-insert"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
api = { path = "../../api" }
|
||||
async-trait = "0.1"
|
||||
common-base = { path = "../base" }
|
||||
common-error = { path = "../error" }
|
||||
common-telemetry = { path = "../telemetry" }
|
||||
common-time = { path = "../time" }
|
||||
common-query = { path = "../query" }
|
||||
datatypes = { path = "../../datatypes" }
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
table = { path = "../../table" }
|
||||
72
src/common/insert/src/error.rs
Normal file
72
src/common/insert/src/error.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use std::any::Any;
|
||||
|
||||
use api::DecodeError;
|
||||
use common_error::ext::ErrorExt;
|
||||
use common_error::prelude::{Snafu, StatusCode};
|
||||
use snafu::{Backtrace, ErrorCompat};
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[snafu(display("Column {} not found in table {}", column_name, table_name))]
|
||||
ColumnNotFound {
|
||||
column_name: String,
|
||||
table_name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to convert bytes to insert batch, source: {}", source))]
|
||||
DecodeInsert { source: DecodeError },
|
||||
|
||||
#[snafu(display("Illegal insert data"))]
|
||||
IllegalInsertData,
|
||||
|
||||
#[snafu(display("Column datatype error, source: {}", source))]
|
||||
ColumnDataType {
|
||||
#[snafu(backtrace)]
|
||||
source: api::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to create schema when creating table, source: {}", source))]
|
||||
CreateSchema {
|
||||
#[snafu(backtrace)]
|
||||
source: datatypes::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Duplicated timestamp column in gRPC requests, exists {}, duplicated: {}",
|
||||
exists,
|
||||
duplicated
|
||||
))]
|
||||
DuplicatedTimestampColumn {
|
||||
exists: String,
|
||||
duplicated: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Missing timestamp column in request"))]
|
||||
MissingTimestampColumn { backtrace: Backtrace },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl ErrorExt for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Error::ColumnNotFound { .. } => StatusCode::TableColumnNotFound,
|
||||
Error::DecodeInsert { .. } | Error::IllegalInsertData { .. } => {
|
||||
StatusCode::InvalidArguments
|
||||
}
|
||||
Error::ColumnDataType { .. } => StatusCode::Internal,
|
||||
Error::CreateSchema { .. }
|
||||
| Error::DuplicatedTimestampColumn { .. }
|
||||
| Error::MissingTimestampColumn { .. } => StatusCode::InvalidArguments,
|
||||
}
|
||||
}
|
||||
fn backtrace_opt(&self) -> Option<&Backtrace> {
|
||||
ErrorCompat::backtrace(self)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,17 @@ use table::{
|
||||
Table,
|
||||
};
|
||||
|
||||
use crate::error::{self, ColumnNotFoundSnafu, DecodeInsertSnafu, IllegalInsertDataSnafu, Result};
|
||||
use crate::error::{
|
||||
ColumnDataTypeSnafu, ColumnNotFoundSnafu, CreateSchemaSnafu, DecodeInsertSnafu,
|
||||
DuplicatedTimestampColumnSnafu, IllegalInsertDataSnafu, MissingTimestampColumnSnafu, Result,
|
||||
};
|
||||
|
||||
const TAG_SEMANTIC_TYPE: i32 = SemanticType::Tag as i32;
|
||||
const TIMESTAMP_SEMANTIC_TYPE: i32 = SemanticType::Timestamp as i32;
|
||||
|
||||
#[inline]
|
||||
fn build_column_schema(column_name: &str, datatype: i32, nullable: bool) -> Result<ColumnSchema> {
|
||||
let datatype_wrapper =
|
||||
ColumnDataTypeWrapper::try_new(datatype).context(error::ColumnDataTypeSnafu)?;
|
||||
let datatype_wrapper = ColumnDataTypeWrapper::try_new(datatype).context(ColumnDataTypeSnafu)?;
|
||||
|
||||
Ok(ColumnSchema::new(
|
||||
column_name,
|
||||
@@ -127,7 +129,7 @@ pub fn build_create_table_request(
|
||||
TIMESTAMP_SEMANTIC_TYPE => {
|
||||
ensure!(
|
||||
timestamp_index == usize::MAX,
|
||||
error::DuplicatedTimestampColumnSnafu {
|
||||
DuplicatedTimestampColumnSnafu {
|
||||
exists: &columns[timestamp_index].column_name,
|
||||
duplicated: column_name,
|
||||
}
|
||||
@@ -145,17 +147,14 @@ pub fn build_create_table_request(
|
||||
}
|
||||
}
|
||||
|
||||
ensure!(
|
||||
timestamp_index != usize::MAX,
|
||||
error::MissingTimestampColumnSnafu
|
||||
);
|
||||
ensure!(timestamp_index != usize::MAX, MissingTimestampColumnSnafu);
|
||||
|
||||
let schema = Arc::new(
|
||||
SchemaBuilder::try_from(column_schemas)
|
||||
.unwrap()
|
||||
.timestamp_index(Some(timestamp_index))
|
||||
.build()
|
||||
.context(error::CreateSchemaSnafu)?,
|
||||
.context(CreateSchemaSnafu)?,
|
||||
);
|
||||
|
||||
return Ok(CreateTableRequest {
|
||||
@@ -172,7 +171,7 @@ pub fn build_create_table_request(
|
||||
});
|
||||
}
|
||||
|
||||
error::IllegalInsertDataSnafu.fail()
|
||||
IllegalInsertDataSnafu.fail()
|
||||
}
|
||||
|
||||
pub fn insertion_expr_to_request(
|
||||
@@ -357,7 +356,8 @@ fn is_null(null_mask: &BitVec, idx: usize) -> Option<bool> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{any::Any, sync::Arc};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::{
|
||||
codec::InsertBatch,
|
||||
6
src/common/insert/src/lib.rs
Normal file
6
src/common/insert/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod error;
|
||||
mod insert;
|
||||
pub use insert::{
|
||||
build_alter_table_request, build_create_table_request, find_new_columns, insert_batches,
|
||||
insertion_expr_to_request,
|
||||
};
|
||||
@@ -22,6 +22,7 @@ common-recordbatch = { path = "../common/recordbatch" }
|
||||
common-runtime = { path = "../common/runtime" }
|
||||
common-telemetry = { path = "../common/telemetry" }
|
||||
common-time = { path = "../common/time" }
|
||||
common-insert = { path = "../common/insert" }
|
||||
datafusion = { git = "https://github.com/apache/arrow-datafusion.git", branch = "arrow2", features = [
|
||||
"simd",
|
||||
] }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::any::Any;
|
||||
|
||||
use api::serde::DecodeError;
|
||||
use common_error::prelude::*;
|
||||
use datatypes::arrow::error::ArrowError;
|
||||
use storage::error::Error as StorageError;
|
||||
@@ -73,9 +72,6 @@ pub enum Error {
|
||||
#[snafu(display("Missing required field in protobuf, field: {}", field))]
|
||||
MissingField { field: String, backtrace: Backtrace },
|
||||
|
||||
#[snafu(display("Missing timestamp column in request"))]
|
||||
MissingTimestampColumn { backtrace: Backtrace },
|
||||
|
||||
#[snafu(display(
|
||||
"Columns and values number mismatch, columns: {}, values: {}",
|
||||
columns,
|
||||
@@ -92,15 +88,10 @@ pub enum Error {
|
||||
#[snafu(display("Failed to insert value to table: {}, source: {}", table_name, source))]
|
||||
Insert {
|
||||
table_name: String,
|
||||
#[snafu(backtrace)]
|
||||
source: TableError,
|
||||
},
|
||||
|
||||
#[snafu(display("Illegal insert data"))]
|
||||
IllegalInsertData,
|
||||
|
||||
#[snafu(display("Failed to convert bytes to insert batch, source: {}", source))]
|
||||
DecodeInsert { source: DecodeError },
|
||||
|
||||
#[snafu(display("Failed to start server, source: {}", source))]
|
||||
StartServer {
|
||||
#[snafu(backtrace)]
|
||||
@@ -257,17 +248,6 @@ pub enum Error {
|
||||
source: datatypes::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Duplicated timestamp column in gRPC requests, exists {}, duplicated: {}",
|
||||
exists,
|
||||
duplicated
|
||||
))]
|
||||
DuplicatedTimestampColumn {
|
||||
exists: String,
|
||||
duplicated: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to access catalog, source: {}", source))]
|
||||
Catalog {
|
||||
#[snafu(backtrace)]
|
||||
@@ -285,6 +265,15 @@ pub enum Error {
|
||||
#[snafu(backtrace)]
|
||||
source: meta_client::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to insert data, source: {}", source))]
|
||||
InsertData {
|
||||
#[snafu(backtrace)]
|
||||
source: common_insert::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Insert batch is empty"))]
|
||||
EmptyInsertBatch,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -316,18 +305,14 @@ impl ErrorExt for Error {
|
||||
| Error::ConvertSchema { source, .. } => source.status_code(),
|
||||
|
||||
Error::ColumnValuesNumberMismatch { .. }
|
||||
| Error::IllegalInsertData { .. }
|
||||
| Error::DecodeInsert { .. }
|
||||
| Error::InvalidSql { .. }
|
||||
| Error::KeyColumnNotFound { .. }
|
||||
| Error::InvalidPrimaryKey { .. }
|
||||
| Error::MissingField { .. }
|
||||
| Error::MissingTimestampColumn { .. }
|
||||
| Error::CatalogNotFound { .. }
|
||||
| Error::SchemaNotFound { .. }
|
||||
| Error::ConstraintNotSupported { .. }
|
||||
| Error::ParseTimestamp { .. }
|
||||
| Error::DuplicatedTimestampColumn { .. } => StatusCode::InvalidArguments,
|
||||
| Error::ParseTimestamp { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
// TODO(yingwen): Further categorize http error.
|
||||
Error::StartServer { .. }
|
||||
@@ -353,6 +338,8 @@ impl ErrorExt for Error {
|
||||
|
||||
Error::ArrowComputation { .. } => StatusCode::Unexpected,
|
||||
Error::MetaClientInit { source, .. } => source.status_code(),
|
||||
Error::InsertData { source, .. } => source.status_code(),
|
||||
Error::EmptyInsertBatch => StatusCode::InvalidArguments,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use api::v1::codec::RegionId;
|
||||
use api::v1::codec::RegionNumber;
|
||||
use api::v1::{
|
||||
admin_expr, codec::InsertBatch, insert_expr, object_expr, select_expr, AdminExpr, AdminResult,
|
||||
ObjectExpr, ObjectResult, SelectExpr,
|
||||
@@ -17,12 +17,11 @@ use substrait::{DFLogicalSubstraitConvertor, SubstraitPlan};
|
||||
use table::requests::AddColumnRequest;
|
||||
|
||||
use crate::error::{
|
||||
self, CatalogSnafu, DecodeLogicalPlanSnafu, ExecuteSqlSnafu, InsertSnafu, Result,
|
||||
TableNotFoundSnafu, UnsupportedExprSnafu,
|
||||
CatalogSnafu, DecodeLogicalPlanSnafu, EmptyInsertBatchSnafu, ExecuteSqlSnafu, InsertDataSnafu,
|
||||
InsertSnafu, Result, TableNotFoundSnafu, UnsupportedExprSnafu,
|
||||
};
|
||||
use crate::instance::Instance;
|
||||
use crate::server::grpc::handler::{build_err_result, ObjectResultBuilder};
|
||||
use crate::server::grpc::insert::{self, insertion_expr_to_request};
|
||||
use crate::server::grpc::plan::PhysicalPlanner;
|
||||
use crate::server::grpc::select::to_object_result;
|
||||
use crate::sql::SqlRequest;
|
||||
@@ -38,7 +37,7 @@ impl Instance {
|
||||
.map(|req| req.column_schema.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let alter_request = insert::build_alter_table_request(table_name, add_columns);
|
||||
let alter_request = common_insert::build_alter_table_request(table_name, add_columns);
|
||||
|
||||
debug!(
|
||||
"Adding new columns: {:?} to table: {}",
|
||||
@@ -70,13 +69,14 @@ impl Instance {
|
||||
.next_table_id()
|
||||
.await
|
||||
.context(CatalogSnafu)?;
|
||||
let create_table_request = insert::build_create_table_request(
|
||||
let create_table_request = common_insert::build_create_table_request(
|
||||
catalog_name,
|
||||
schema_name,
|
||||
table_id,
|
||||
table_name,
|
||||
insert_batches,
|
||||
)?;
|
||||
)
|
||||
.context(InsertDataSnafu)?;
|
||||
|
||||
info!(
|
||||
"Try to create table: {} automatically with request: {:?}",
|
||||
@@ -111,12 +111,16 @@ impl Instance {
|
||||
.expect("default schema must exist")
|
||||
.unwrap();
|
||||
|
||||
let insert_batches = insert::insert_batches(values.values)?;
|
||||
ensure!(!insert_batches.is_empty(), error::IllegalInsertDataSnafu);
|
||||
let insert_batches =
|
||||
common_insert::insert_batches(values.values).context(InsertDataSnafu)?;
|
||||
|
||||
ensure!(!insert_batches.is_empty(), EmptyInsertBatchSnafu);
|
||||
|
||||
let table = if let Some(table) = schema_provider.table(table_name).context(CatalogSnafu)? {
|
||||
let schema = table.schema();
|
||||
if let Some(add_columns) = insert::find_new_columns(&schema, &insert_batches)? {
|
||||
if let Some(add_columns) = common_insert::find_new_columns(&schema, &insert_batches)
|
||||
.context(InsertDataSnafu)?
|
||||
{
|
||||
self.add_new_columns_to_table(table_name, add_columns)
|
||||
.await?;
|
||||
}
|
||||
@@ -137,7 +141,9 @@ impl Instance {
|
||||
.context(TableNotFoundSnafu { table_name })?
|
||||
};
|
||||
|
||||
let insert = insertion_expr_to_request(table_name, insert_batches, table.clone())?;
|
||||
let insert =
|
||||
common_insert::insertion_expr_to_request(table_name, insert_batches, table.clone())
|
||||
.context(InsertDataSnafu)?;
|
||||
|
||||
let affected_rows = table
|
||||
.insert(insert)
|
||||
@@ -209,13 +215,13 @@ impl GrpcQueryHandler for Instance {
|
||||
})?;
|
||||
|
||||
// TODO(fys): _region_id is for later use.
|
||||
let _region_id: Option<RegionId> = insert_expr
|
||||
let _region_id: Option<RegionNumber> = insert_expr
|
||||
.options
|
||||
.get("region_id")
|
||||
.map(|id| {
|
||||
id.deref()
|
||||
.try_into()
|
||||
.context(servers::error::DecodeRegionIdSnafu)
|
||||
.context(servers::error::DecodeRegionNumberSnafu)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod ddl;
|
||||
pub(crate) mod handler;
|
||||
pub(crate) mod insert;
|
||||
pub(crate) mod plan;
|
||||
pub mod select;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use arrow::array::{Int64Array, UInt64Array};
|
||||
use common_query::Output;
|
||||
use common_recordbatch::util;
|
||||
use datafusion::arrow_print;
|
||||
use datafusion_common::record_batch::RecordBatch as DfRecordBatch;
|
||||
use datatypes::arrow_array::StringArray;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
|
||||
@@ -240,12 +242,24 @@ pub async fn test_create_table_illegal_timestamp_type() {
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_output_stream(output: Output, expected: Vec<&str>) {
|
||||
match output {
|
||||
Output::Stream(stream) => {
|
||||
let recordbatches = util::collect(stream).await.unwrap();
|
||||
let recordbatch = recordbatches
|
||||
.into_iter()
|
||||
.map(|r| r.df_recordbatch)
|
||||
.collect::<Vec<DfRecordBatch>>();
|
||||
let pretty_print = arrow_print::write(&recordbatch);
|
||||
let pretty_print = pretty_print.lines().collect::<Vec<&str>>();
|
||||
assert_eq!(pretty_print, expected);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_alter_table() {
|
||||
use datafusion::arrow_print;
|
||||
use datafusion_common::record_batch::RecordBatch as DfRecordBatch;
|
||||
// TODO(LFC) Use real Mito engine when we can alter its region schema,
|
||||
// and delete the `new_mock` method.
|
||||
let instance = Instance::new_mock().await.unwrap();
|
||||
instance.start().await.unwrap();
|
||||
|
||||
@@ -278,26 +292,69 @@ async fn test_alter_table() {
|
||||
assert!(matches!(output, Output::AffectedRows(1)));
|
||||
|
||||
let output = instance.execute_sql("select * from demo").await.unwrap();
|
||||
match output {
|
||||
Output::Stream(stream) => {
|
||||
let recordbatches = util::collect(stream).await.unwrap();
|
||||
let recordbatch = recordbatches
|
||||
.into_iter()
|
||||
.map(|r| r.df_recordbatch)
|
||||
.collect::<Vec<DfRecordBatch>>();
|
||||
let pretty_print = arrow_print::write(&recordbatch);
|
||||
let pretty_print = pretty_print.lines().collect::<Vec<&str>>();
|
||||
let expected = vec![
|
||||
"+-------+-----+--------+---------------------+--------+",
|
||||
"| host | cpu | memory | ts | my_tag |",
|
||||
"+-------+-----+--------+---------------------+--------+",
|
||||
"| host1 | 1.1 | 100 | 1970-01-01 00:00:01 | |",
|
||||
"| host2 | 2.2 | 200 | 1970-01-01 00:00:02 | hello |",
|
||||
"| host3 | 3.3 | 300 | 1970-01-01 00:00:03 | |",
|
||||
"+-------+-----+--------+---------------------+--------+",
|
||||
];
|
||||
assert_eq!(pretty_print, expected);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let expected = vec![
|
||||
"+-------+-----+--------+---------------------+--------+",
|
||||
"| host | cpu | memory | ts | my_tag |",
|
||||
"+-------+-----+--------+---------------------+--------+",
|
||||
"| host1 | 1.1 | 100 | 1970-01-01 00:00:01 | |",
|
||||
"| host2 | 2.2 | 200 | 1970-01-01 00:00:02 | hello |",
|
||||
"| host3 | 3.3 | 300 | 1970-01-01 00:00:03 | |",
|
||||
"+-------+-----+--------+---------------------+--------+",
|
||||
];
|
||||
check_output_stream(output, expected).await;
|
||||
}
|
||||
|
||||
async fn test_insert_with_default_value_for_type(type_name: &str) {
|
||||
let (opts, _guard) = test_util::create_tmp_dir_and_datanode_opts("execute_create");
|
||||
let instance = Instance::with_mock_meta_client(&opts).await.unwrap();
|
||||
instance.start().await.unwrap();
|
||||
|
||||
let create_sql = format!(
|
||||
r#"create table test_table(
|
||||
host string,
|
||||
ts {} DEFAULT CURRENT_TIMESTAMP,
|
||||
cpu double default 0,
|
||||
TIME INDEX (ts),
|
||||
PRIMARY KEY(host)
|
||||
) engine=mito with(regions=1);"#,
|
||||
type_name
|
||||
);
|
||||
let output = instance.execute_sql(&create_sql).await.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(1)));
|
||||
|
||||
// Insert with ts.
|
||||
instance
|
||||
.execute_sql("insert into test_table(host, cpu, ts) values ('host1', 1.1, 1000)")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(1)));
|
||||
|
||||
// Insert without ts, so it should be filled by default value.
|
||||
let output = instance
|
||||
.execute_sql("insert into test_table(host, cpu) values ('host2', 2.2)")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(1)));
|
||||
|
||||
let output = instance
|
||||
.execute_sql("select host, cpu from test_table")
|
||||
.await
|
||||
.unwrap();
|
||||
let expected = vec![
|
||||
"+-------+-----+",
|
||||
"| host | cpu |",
|
||||
"+-------+-----+",
|
||||
"| host1 | 1.1 |",
|
||||
"| host2 | 2.2 |",
|
||||
"+-------+-----+",
|
||||
];
|
||||
check_output_stream(output, expected).await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_insert_with_default_value() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
|
||||
test_insert_with_default_value_for_type("timestamp").await;
|
||||
test_insert_with_default_value_for_type("bigint").await;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use arrow::array::{
|
||||
self, Array, BinaryArray as ArrowBinaryArray, MutableBinaryArray as ArrowMutableBinaryArray,
|
||||
MutableUtf8Array, PrimitiveArray, Utf8Array,
|
||||
self, Array, BinaryArray as ArrowBinaryArray, ListArray,
|
||||
MutableBinaryArray as ArrowMutableBinaryArray, MutableUtf8Array, PrimitiveArray, Utf8Array,
|
||||
};
|
||||
use arrow::datatypes::DataType as ArrowDataType;
|
||||
use common_time::timestamp::Timestamp;
|
||||
@@ -8,7 +8,7 @@ use snafu::OptionExt;
|
||||
|
||||
use crate::error::{ConversionSnafu, Result};
|
||||
use crate::prelude::ConcreteDataType;
|
||||
use crate::value::Value;
|
||||
use crate::value::{ListValue, Value};
|
||||
|
||||
pub type BinaryArray = ArrowBinaryArray<i64>;
|
||||
pub type MutableBinaryArray = ArrowMutableBinaryArray<i64>;
|
||||
@@ -69,7 +69,14 @@ pub fn arrow_array_get(array: &dyn Array, idx: usize) -> Result<Value> {
|
||||
};
|
||||
Value::Timestamp(Timestamp::new(value, unit))
|
||||
}
|
||||
// TODO(sunng87): List
|
||||
ArrowDataType::List(_) => {
|
||||
let array = cast_array!(array, ListArray::<i32>).value(idx);
|
||||
let inner_datatype = ConcreteDataType::try_from(array.data_type())?;
|
||||
let values = (0..array.len())
|
||||
.map(|i| arrow_array_get(&*array, i))
|
||||
.collect::<Result<Vec<Value>>>()?;
|
||||
Value::List(ListValue::new(Some(Box::new(values)), inner_datatype))
|
||||
}
|
||||
_ => unimplemented!("Arrow array datatype: {:?}", array.data_type()),
|
||||
};
|
||||
|
||||
@@ -80,6 +87,7 @@ pub fn arrow_array_get(array: &dyn Array, idx: usize) -> Result<Value> {
|
||||
mod test {
|
||||
use arrow::array::Int64Array as ArrowI64Array;
|
||||
use arrow::array::*;
|
||||
use arrow::array::{MutableListArray, MutablePrimitiveArray, TryExtend};
|
||||
use arrow::buffer::Buffer;
|
||||
use arrow::datatypes::{DataType, TimeUnit as ArrowTimeUnit};
|
||||
use common_time::timestamp::{TimeUnit, Timestamp};
|
||||
@@ -164,5 +172,40 @@ mod test {
|
||||
Value::Timestamp(Timestamp::new(1, TimeUnit::Nanosecond)),
|
||||
arrow_array_get(&array4, 0).unwrap()
|
||||
);
|
||||
|
||||
// test list array
|
||||
let data = vec![
|
||||
Some(vec![Some(1i32), Some(2), Some(3)]),
|
||||
None,
|
||||
Some(vec![Some(4), None, Some(6)]),
|
||||
];
|
||||
|
||||
let mut arrow_array = MutableListArray::<i32, MutablePrimitiveArray<i32>>::new();
|
||||
arrow_array.try_extend(data).unwrap();
|
||||
let arrow_array: ListArray<i32> = arrow_array.into();
|
||||
|
||||
let v0 = arrow_array_get(&arrow_array, 0).unwrap();
|
||||
match v0 {
|
||||
Value::List(list) => {
|
||||
assert!(matches!(list.datatype(), ConcreteDataType::Int32(_)));
|
||||
let items = list.items().as_ref().unwrap();
|
||||
assert_eq!(
|
||||
**items,
|
||||
vec![Value::Int32(1), Value::Int32(2), Value::Int32(3)]
|
||||
);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
assert_eq!(Value::Null, arrow_array_get(&arrow_array, 1).unwrap());
|
||||
let v2 = arrow_array_get(&arrow_array, 2).unwrap();
|
||||
match v2 {
|
||||
Value::List(list) => {
|
||||
assert!(matches!(list.datatype(), ConcreteDataType::Int32(_)));
|
||||
let items = list.items().as_ref().unwrap();
|
||||
assert_eq!(**items, vec![Value::Int32(4), Value::Null, Value::Int32(6)]);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_time::{util, Timestamp};
|
||||
use common_time::util;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, ResultExt};
|
||||
|
||||
use crate::data_type::{ConcreteDataType, DataType};
|
||||
use crate::error::{self, Result};
|
||||
use crate::scalars::ScalarVector;
|
||||
use crate::value::Value;
|
||||
use crate::vectors::{ConstantVector, TimestampVector, VectorRef};
|
||||
use crate::vectors::{Int64Vector, TimestampVector, VectorRef};
|
||||
|
||||
const CURRENT_TIMESTAMP: &str = "current_timestamp()";
|
||||
|
||||
@@ -107,15 +106,7 @@ impl ColumnDefaultConstraint {
|
||||
match &expr[..] {
|
||||
// TODO(dennis): we only supports current_timestamp right now,
|
||||
// it's better to use a expression framework in future.
|
||||
CURRENT_TIMESTAMP => {
|
||||
// TODO(yingwen): We should coerce the type to the physical type of
|
||||
// input `data_type`.
|
||||
let vector =
|
||||
Arc::new(TimestampVector::from_slice(&[Timestamp::from_millis(
|
||||
util::current_time_millis(),
|
||||
)]));
|
||||
Ok(Arc::new(ConstantVector::new(vector, num_rows)))
|
||||
}
|
||||
CURRENT_TIMESTAMP => create_current_timestamp_vector(data_type, num_rows),
|
||||
_ => error::UnsupportedDefaultExprSnafu { expr }.fail(),
|
||||
}
|
||||
}
|
||||
@@ -143,9 +134,31 @@ impl ColumnDefaultConstraint {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_current_timestamp_vector(
|
||||
data_type: &ConcreteDataType,
|
||||
num_rows: usize,
|
||||
) -> Result<VectorRef> {
|
||||
match data_type {
|
||||
ConcreteDataType::Timestamp(_) => Ok(Arc::new(TimestampVector::from_values(
|
||||
std::iter::repeat(util::current_time_millis()).take(num_rows),
|
||||
))),
|
||||
ConcreteDataType::Int64(_) => Ok(Arc::new(Int64Vector::from_values(
|
||||
std::iter::repeat(util::current_time_millis()).take(num_rows),
|
||||
))),
|
||||
_ => error::DefaultValueTypeSnafu {
|
||||
reason: format!(
|
||||
"Not support to assign current timestamp to {:?} type",
|
||||
data_type
|
||||
),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
use crate::vectors::Int32Vector;
|
||||
|
||||
#[test]
|
||||
@@ -224,6 +237,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_create_default_vector_by_func() {
|
||||
let constraint = ColumnDefaultConstraint::Function(CURRENT_TIMESTAMP.to_string());
|
||||
// Timestamp type.
|
||||
let data_type = ConcreteDataType::timestamp_millis_datatype();
|
||||
let v = constraint
|
||||
.create_default_vector(&data_type, false, 4)
|
||||
@@ -235,10 +249,32 @@ mod tests {
|
||||
v.get(0)
|
||||
);
|
||||
|
||||
// Int64 type.
|
||||
let data_type = ConcreteDataType::int64_datatype();
|
||||
let v = constraint
|
||||
.create_default_vector(&data_type, false, 4)
|
||||
.unwrap();
|
||||
assert_eq!(4, v.len());
|
||||
assert!(
|
||||
matches!(v.get(0), Value::Int64(_)),
|
||||
"v {:?} is not timestamp",
|
||||
v.get(0)
|
||||
);
|
||||
|
||||
let constraint = ColumnDefaultConstraint::Function("no".to_string());
|
||||
let data_type = ConcreteDataType::timestamp_millis_datatype();
|
||||
constraint
|
||||
.create_default_vector(&data_type, false, 4)
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_by_func_and_invalid_type() {
|
||||
let constraint = ColumnDefaultConstraint::Function(CURRENT_TIMESTAMP.to_string());
|
||||
let data_type = ConcreteDataType::boolean_datatype();
|
||||
let err = constraint
|
||||
.create_default_vector(&data_type, false, 4)
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, Error::DefaultValueType { .. }), "{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ common-error = { path = "../common/error" }
|
||||
common-grpc = { path = "../common/grpc" }
|
||||
common-query = { path = "../common/query" }
|
||||
common-recordbatch = { path = "../common/recordbatch" }
|
||||
common-catalog = { path = "../common/catalog" }
|
||||
common-runtime = { path = "../common/runtime" }
|
||||
common-telemetry = { path = "../common/telemetry" }
|
||||
common-time = { path = "../common/time" }
|
||||
@@ -21,17 +22,21 @@ datafusion = { git = "https://github.com/apache/arrow-datafusion.git", branch =
|
||||
datafusion-common = { git = "https://github.com/apache/arrow-datafusion.git", branch = "arrow2" }
|
||||
datafusion-expr = { git = "https://github.com/apache/arrow-datafusion.git", branch = "arrow2" }
|
||||
datatypes = { path = "../datatypes" }
|
||||
futures = "0.3"
|
||||
itertools = "0.10"
|
||||
openmetrics-parser = "0.4"
|
||||
prost = "0.11"
|
||||
query = { path = "../query" }
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
sqlparser = "0.15"
|
||||
servers = { path = "../servers" }
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
sql = { path = "../sql" }
|
||||
store-api = { path = "../store-api" }
|
||||
table = { path = "../table" }
|
||||
tokio = { version = "1.18", features = ["full"] }
|
||||
meta-client = {path = "../meta-client"}
|
||||
|
||||
[dependencies.arrow]
|
||||
package = "arrow2"
|
||||
|
||||
270
src/frontend/src/catalog.rs
Normal file
270
src/frontend/src/catalog.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use std::any::Any;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use catalog::error::{
|
||||
DeserializePartitionRuleSnafu, InvalidCatalogValueSnafu, InvalidSchemaInCatalogSnafu,
|
||||
};
|
||||
use catalog::remote::{Kv, KvBackendRef};
|
||||
use catalog::{
|
||||
CatalogList, CatalogProvider, CatalogProviderRef, SchemaProvider, SchemaProviderRef,
|
||||
};
|
||||
use common_catalog::{CatalogKey, SchemaKey, TableGlobalKey, TableGlobalValue};
|
||||
use common_error::ext::BoxedError;
|
||||
use futures::StreamExt;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use table::TableRef;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::error::DatanodeNotAvailableSnafu;
|
||||
use crate::mock::{DatanodeId, DatanodeInstance};
|
||||
use crate::partitioning::range::RangePartitionRule;
|
||||
use crate::table::DistTable;
|
||||
|
||||
pub type DatanodeInstances = HashMap<DatanodeId, DatanodeInstance>;
|
||||
|
||||
pub struct FrontendCatalogManager {
|
||||
backend: KvBackendRef,
|
||||
datanode_instances: Arc<RwLock<DatanodeInstances>>,
|
||||
}
|
||||
|
||||
impl FrontendCatalogManager {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(backend: KvBackendRef, datanode_instances: Arc<RwLock<DatanodeInstances>>) -> Self {
|
||||
Self {
|
||||
backend,
|
||||
datanode_instances,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CatalogList for FrontendCatalogManager {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn register_catalog(
|
||||
&self,
|
||||
_name: String,
|
||||
_catalog: CatalogProviderRef,
|
||||
) -> catalog::error::Result<Option<CatalogProviderRef>> {
|
||||
unimplemented!("Frontend catalog list does not support register catalog")
|
||||
}
|
||||
|
||||
fn catalog_names(&self) -> catalog::error::Result<Vec<String>> {
|
||||
let backend = self.backend.clone();
|
||||
let res = std::thread::spawn(|| {
|
||||
common_runtime::block_on_read(async move {
|
||||
let key = common_catalog::build_catalog_prefix();
|
||||
let mut iter = backend.range(key.as_bytes());
|
||||
let mut res = HashSet::new();
|
||||
|
||||
while let Some(r) = iter.next().await {
|
||||
let Kv(k, _) = r?;
|
||||
let key = CatalogKey::parse(String::from_utf8_lossy(&k))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
res.insert(key.catalog_name);
|
||||
}
|
||||
Ok(res.into_iter().collect())
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
res
|
||||
}
|
||||
|
||||
fn catalog(&self, name: &str) -> catalog::error::Result<Option<CatalogProviderRef>> {
|
||||
let all_catalogs = self.catalog_names()?;
|
||||
if all_catalogs.contains(&name.to_string()) {
|
||||
Ok(Some(Arc::new(FrontendCatalogProvider {
|
||||
catalog_name: name.to_string(),
|
||||
backend: self.backend.clone(),
|
||||
datanode_instances: self.datanode_instances.clone(),
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrontendCatalogProvider {
|
||||
catalog_name: String,
|
||||
backend: KvBackendRef,
|
||||
datanode_instances: Arc<RwLock<DatanodeInstances>>,
|
||||
}
|
||||
|
||||
impl CatalogProvider for FrontendCatalogProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema_names(&self) -> catalog::error::Result<Vec<String>> {
|
||||
let backend = self.backend.clone();
|
||||
let catalog_name = self.catalog_name.clone();
|
||||
let res = std::thread::spawn(|| {
|
||||
common_runtime::block_on_read(async move {
|
||||
let key = common_catalog::build_schema_prefix(&catalog_name);
|
||||
let mut iter = backend.range(key.as_bytes());
|
||||
let mut res = HashSet::new();
|
||||
|
||||
while let Some(r) = iter.next().await {
|
||||
let Kv(k, _) = r?;
|
||||
let key = SchemaKey::parse(String::from_utf8_lossy(&k))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
res.insert(key.schema_name);
|
||||
}
|
||||
Ok(res.into_iter().collect())
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
res
|
||||
}
|
||||
|
||||
fn register_schema(
|
||||
&self,
|
||||
_name: String,
|
||||
_schema: SchemaProviderRef,
|
||||
) -> catalog::error::Result<Option<SchemaProviderRef>> {
|
||||
unimplemented!("Frontend catalog provider does not support register schema")
|
||||
}
|
||||
|
||||
fn schema(&self, name: &str) -> catalog::error::Result<Option<SchemaProviderRef>> {
|
||||
let all_schemas = self.schema_names()?;
|
||||
if all_schemas.contains(&name.to_string()) {
|
||||
Ok(Some(Arc::new(FrontendSchemaProvider {
|
||||
catalog_name: self.catalog_name.clone(),
|
||||
schema_name: name.to_string(),
|
||||
backend: self.backend.clone(),
|
||||
datanode_instances: self.datanode_instances.clone(),
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrontendSchemaProvider {
|
||||
catalog_name: String,
|
||||
schema_name: String,
|
||||
backend: KvBackendRef,
|
||||
datanode_instances: Arc<RwLock<DatanodeInstances>>,
|
||||
}
|
||||
|
||||
impl SchemaProvider for FrontendSchemaProvider {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn table_names(&self) -> catalog::error::Result<Vec<String>> {
|
||||
let backend = self.backend.clone();
|
||||
let catalog_name = self.catalog_name.clone();
|
||||
let schema_name = self.schema_name.clone();
|
||||
|
||||
std::thread::spawn(|| {
|
||||
common_runtime::block_on_read(async move {
|
||||
let key = common_catalog::build_table_global_prefix(catalog_name, schema_name);
|
||||
let mut iter = backend.range(key.as_bytes());
|
||||
let mut res = HashSet::new();
|
||||
|
||||
while let Some(r) = iter.next().await {
|
||||
let Kv(k, _) = r?;
|
||||
let key = TableGlobalKey::parse(String::from_utf8_lossy(&k))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
res.insert(key.table_name);
|
||||
}
|
||||
Ok(res.into_iter().collect())
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn table(&self, name: &str) -> catalog::error::Result<Option<TableRef>> {
|
||||
let table_global_key = TableGlobalKey {
|
||||
catalog_name: self.catalog_name.clone(),
|
||||
schema_name: self.schema_name.clone(),
|
||||
table_name: name.to_string(),
|
||||
};
|
||||
|
||||
let instances = self.datanode_instances.clone();
|
||||
let backend = self.backend.clone();
|
||||
let table_name = name.to_string();
|
||||
let result: Result<Option<TableRef>, catalog::error::Error> = std::thread::spawn(|| {
|
||||
common_runtime::block_on_read(async move {
|
||||
let mut datanode_instances = HashMap::new();
|
||||
let res = match backend.get(table_global_key.to_string().as_bytes()).await? {
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
Some(r) => r,
|
||||
};
|
||||
|
||||
let mut region_to_datanode_map = HashMap::new();
|
||||
|
||||
let val = TableGlobalValue::parse(String::from_utf8_lossy(&res.1))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
let node_id: DatanodeId = val.node_id;
|
||||
|
||||
// TODO(hl): We need to deserialize string to PartitionRule trait object
|
||||
let partition_rule: Arc<RangePartitionRule> =
|
||||
Arc::new(serde_json::from_str(&val.partition_rules).context(
|
||||
DeserializePartitionRuleSnafu {
|
||||
data: &val.partition_rules,
|
||||
},
|
||||
)?);
|
||||
|
||||
for (node_id, region_ids) in val.regions_id_map {
|
||||
for region_id in region_ids {
|
||||
region_to_datanode_map.insert(region_id, node_id);
|
||||
}
|
||||
}
|
||||
|
||||
datanode_instances.insert(
|
||||
node_id,
|
||||
instances
|
||||
.read()
|
||||
.await
|
||||
.get(&node_id)
|
||||
.context(DatanodeNotAvailableSnafu { node_id })
|
||||
.map_err(BoxedError::new)
|
||||
.context(catalog::error::InternalSnafu)?
|
||||
.clone(),
|
||||
);
|
||||
|
||||
let table = Arc::new(DistTable {
|
||||
table_name: table_name.clone(),
|
||||
schema: Arc::new(
|
||||
val.meta
|
||||
.schema
|
||||
.try_into()
|
||||
.context(InvalidSchemaInCatalogSnafu)?,
|
||||
),
|
||||
partition_rule,
|
||||
region_dist_map: region_to_datanode_map,
|
||||
datanode_instances,
|
||||
});
|
||||
Ok(Some(table as _))
|
||||
})
|
||||
})
|
||||
.join()
|
||||
.unwrap();
|
||||
result
|
||||
}
|
||||
|
||||
fn register_table(
|
||||
&self,
|
||||
_name: String,
|
||||
_table: TableRef,
|
||||
) -> catalog::error::Result<Option<TableRef>> {
|
||||
unimplemented!("Frontend schema provider does not support register table")
|
||||
}
|
||||
|
||||
fn deregister_table(&self, _name: &str) -> catalog::error::Result<Option<TableRef>> {
|
||||
unimplemented!("Frontend schema provider does not support deregister table")
|
||||
}
|
||||
|
||||
fn table_exist(&self, name: &str) -> catalog::error::Result<bool> {
|
||||
Ok(self.table_names()?.contains(&name.to_string()))
|
||||
}
|
||||
}
|
||||
@@ -142,11 +142,50 @@ pub enum Error {
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Table not found: {}", table_name))]
|
||||
TableNotFound {
|
||||
table_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Column {} not found in table {}", column_name, table_name))]
|
||||
ColumnNotFound {
|
||||
column_name: String,
|
||||
table_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Columns and values number mismatch, columns: {}, values: {}",
|
||||
columns,
|
||||
values,
|
||||
))]
|
||||
ColumnValuesNumberMismatch {
|
||||
columns: usize,
|
||||
values: usize,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to join task, source: {}", source))]
|
||||
JoinTask {
|
||||
source: common_runtime::JoinError,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed access catalog: {}", source))]
|
||||
Catalog {
|
||||
#[snafu(backtrace)]
|
||||
source: catalog::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to parse catalog entry: {}", source))]
|
||||
ParseCatalogEntry {
|
||||
#[snafu(backtrace)]
|
||||
source: common_catalog::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Cannot find datanode by id: {}", node_id))]
|
||||
DatanodeNotAvailable { node_id: u64, backtrace: Backtrace },
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -161,6 +200,7 @@ impl ErrorExt for Error {
|
||||
| Error::FindRegions { .. }
|
||||
| Error::InvalidInsertRequest { .. }
|
||||
| Error::FindPartitionColumn { .. }
|
||||
| Error::ColumnValuesNumberMismatch { .. }
|
||||
| Error::RegionKeysSize { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
Error::RuntimeResource { source, .. } => source.status_code(),
|
||||
@@ -182,7 +222,14 @@ impl ErrorExt for Error {
|
||||
StatusCode::Unexpected
|
||||
}
|
||||
Error::ExecOpentsdbPut { .. } => StatusCode::Internal,
|
||||
|
||||
Error::TableNotFound { .. } => StatusCode::TableNotFound,
|
||||
Error::ColumnNotFound { .. } => StatusCode::TableColumnNotFound,
|
||||
|
||||
Error::JoinTask { .. } => StatusCode::Unexpected,
|
||||
Error::Catalog { source, .. } => source.status_code(),
|
||||
Error::ParseCatalogEntry { source, .. } => source.status_code(),
|
||||
Error::DatanodeNotAvailable { .. } => StatusCode::StorageUnavailable,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#![feature(assert_matches)]
|
||||
|
||||
mod catalog;
|
||||
pub mod error;
|
||||
pub mod frontend;
|
||||
pub mod grpc;
|
||||
@@ -13,6 +14,7 @@ pub mod postgres;
|
||||
pub mod prometheus;
|
||||
mod server;
|
||||
pub mod spliter;
|
||||
mod sql;
|
||||
mod table;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -21,7 +21,7 @@ use table::table::adapter::DfTableProviderAdapter;
|
||||
pub(crate) type DatanodeId = u64;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DatanodeInstance {
|
||||
pub struct DatanodeInstance {
|
||||
pub(crate) datanode_id: DatanodeId,
|
||||
catalog_manager: CatalogManagerRef,
|
||||
db: Database,
|
||||
@@ -37,12 +37,12 @@ impl DatanodeInstance {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn new(
|
||||
datanode_id: DatanodeId,
|
||||
catalog_manager: CatalogManagerRef,
|
||||
catalog_list: CatalogManagerRef,
|
||||
db: Database,
|
||||
) -> Self {
|
||||
Self {
|
||||
datanode_id,
|
||||
catalog_manager,
|
||||
catalog_manager: catalog_list,
|
||||
db,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::sync::Arc;
|
||||
|
||||
pub use datafusion_expr::Operator;
|
||||
use datatypes::prelude::Value;
|
||||
use store_api::storage::RegionId;
|
||||
use store_api::storage::RegionNumber;
|
||||
|
||||
pub(crate) type PartitionRuleRef<E> = Arc<dyn PartitionRule<Error = E>>;
|
||||
|
||||
@@ -17,9 +17,9 @@ pub trait PartitionRule: Sync + Send {
|
||||
|
||||
// TODO(LFC): Unify `find_region` and `find_regions` methods when distributed read and write features are both merged into develop.
|
||||
// Or find better names since one is mainly for writes and the other is for reads.
|
||||
fn find_region(&self, values: &[Value]) -> Result<RegionId, Self::Error>;
|
||||
fn find_region(&self, values: &[Value]) -> Result<RegionNumber, Self::Error>;
|
||||
|
||||
fn find_regions(&self, exprs: &[PartitionExpr]) -> Result<Vec<RegionId>, Self::Error>;
|
||||
fn find_regions(&self, exprs: &[PartitionExpr]) -> Result<Vec<RegionNumber>, Self::Error>;
|
||||
}
|
||||
|
||||
/// The right bound(exclusive) of partition range.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use datafusion_expr::Operator;
|
||||
use datatypes::value::Value;
|
||||
use snafu::ensure;
|
||||
use store_api::storage::RegionNumber;
|
||||
|
||||
use crate::error::{self, Error};
|
||||
use crate::partitioning::{PartitionBound, PartitionExpr, PartitionRule, RegionId};
|
||||
use crate::partitioning::{PartitionBound, PartitionExpr, PartitionRule};
|
||||
|
||||
/// A [RangeColumnsPartitionRule] is very similar to [RangePartitionRule] except that it allows
|
||||
/// partitioning by multiple columns.
|
||||
@@ -32,7 +33,7 @@ use crate::partitioning::{PartitionBound, PartitionExpr, PartitionRule, RegionId
|
||||
struct RangeColumnsPartitionRule {
|
||||
column_list: Vec<String>,
|
||||
value_lists: Vec<Vec<PartitionBound>>,
|
||||
regions: Vec<RegionId>,
|
||||
regions: Vec<RegionNumber>,
|
||||
|
||||
// TODO(LFC): Implement finding regions by all partitioning columns, not by the first one only.
|
||||
// Singled out the first partitioning column's bounds for finding regions by range.
|
||||
@@ -54,7 +55,7 @@ struct RangeColumnsPartitionRule {
|
||||
//
|
||||
// The following two fields are acted as caches, so we don't need to recalculate them every time.
|
||||
first_column_bounds: Vec<PartitionBound>,
|
||||
first_column_regions: Vec<Vec<RegionId>>,
|
||||
first_column_regions: Vec<Vec<RegionNumber>>,
|
||||
}
|
||||
|
||||
impl RangeColumnsPartitionRule {
|
||||
@@ -65,7 +66,7 @@ impl RangeColumnsPartitionRule {
|
||||
fn new(
|
||||
column_list: Vec<String>,
|
||||
value_lists: Vec<Vec<PartitionBound>>,
|
||||
regions: Vec<RegionId>,
|
||||
regions: Vec<RegionNumber>,
|
||||
) -> Self {
|
||||
// An example range columns partition rule to calculate the first column bounds and regions:
|
||||
// SQL:
|
||||
@@ -87,7 +88,7 @@ impl RangeColumnsPartitionRule {
|
||||
|
||||
let mut distinct_bounds = Vec::<PartitionBound>::new();
|
||||
distinct_bounds.push(first_column_bounds[0].clone());
|
||||
let mut first_column_regions = Vec::<Vec<RegionId>>::new();
|
||||
let mut first_column_regions = Vec::<Vec<RegionNumber>>::new();
|
||||
first_column_regions.push(vec![regions[0]]);
|
||||
|
||||
for i in 1..first_column_bounds.len() {
|
||||
@@ -116,7 +117,7 @@ impl PartitionRule for RangeColumnsPartitionRule {
|
||||
self.column_list.clone()
|
||||
}
|
||||
|
||||
fn find_region(&self, values: &[Value]) -> Result<RegionId, Self::Error> {
|
||||
fn find_region(&self, values: &[Value]) -> Result<RegionNumber, Self::Error> {
|
||||
ensure!(
|
||||
values.len() == self.column_list.len(),
|
||||
error::RegionKeysSizeSnafu {
|
||||
@@ -137,7 +138,7 @@ impl PartitionRule for RangeColumnsPartitionRule {
|
||||
})
|
||||
}
|
||||
|
||||
fn find_regions(&self, exprs: &[PartitionExpr]) -> Result<Vec<RegionId>, Self::Error> {
|
||||
fn find_regions(&self, exprs: &[PartitionExpr]) -> Result<Vec<RegionNumber>, Self::Error> {
|
||||
let regions = if exprs.iter().all(|x| self.column_list.contains(&x.column)) {
|
||||
let PartitionExpr {
|
||||
column: _,
|
||||
@@ -173,7 +174,7 @@ impl PartitionRule for RangeColumnsPartitionRule {
|
||||
.iter()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect::<Vec<RegionId>>()
|
||||
.collect::<Vec<RegionNumber>>()
|
||||
} else {
|
||||
self.regions.clone()
|
||||
};
|
||||
@@ -224,7 +225,7 @@ mod tests {
|
||||
vec![1, 2, 3, 4, 5, 6],
|
||||
);
|
||||
|
||||
let test = |op: Operator, value: &str, expected_regions: Vec<u64>| {
|
||||
let test = |op: Operator, value: &str, expected_regions: Vec<RegionNumber>| {
|
||||
let exprs = vec![
|
||||
// Intentionally fix column b's partition expr to "b < 1". If we support finding
|
||||
// regions by both columns("a" and "b") in the future, some test cases should fail.
|
||||
@@ -242,7 +243,7 @@ mod tests {
|
||||
let regions = rule.find_regions(&exprs).unwrap();
|
||||
assert_eq!(
|
||||
regions,
|
||||
expected_regions.into_iter().collect::<Vec<RegionId>>()
|
||||
expected_regions.into_iter().collect::<Vec<RegionNumber>>()
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use datatypes::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::OptionExt;
|
||||
use store_api::storage::RegionNumber;
|
||||
|
||||
use crate::error::{self, Error};
|
||||
use crate::partitioning::{Operator, PartitionExpr, PartitionRule, RegionId};
|
||||
use crate::partitioning::{Operator, PartitionExpr, PartitionRule};
|
||||
|
||||
/// [RangePartitionRule] manages the distribution of partitions partitioning by some column's value
|
||||
/// range. It's generated from create table request, using MySQL's syntax:
|
||||
@@ -41,13 +43,14 @@ use crate::partitioning::{Operator, PartitionExpr, PartitionRule, RegionId};
|
||||
///
|
||||
// TODO(LFC): Further clarify "partition" and "region".
|
||||
// Could be creating an extra layer between partition and region.
|
||||
pub(crate) struct RangePartitionRule {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RangePartitionRule {
|
||||
column_name: String,
|
||||
// Does not store the last "MAXVALUE" bound; because in this way our binary search in finding
|
||||
// partitions are easier (besides, it's hard to represent "MAXVALUE" in our `Value`).
|
||||
// Then the length of `bounds` is one less than `regions`.
|
||||
bounds: Vec<Value>,
|
||||
regions: Vec<RegionId>,
|
||||
regions: Vec<RegionNumber>,
|
||||
}
|
||||
|
||||
impl RangePartitionRule {
|
||||
@@ -56,7 +59,7 @@ impl RangePartitionRule {
|
||||
pub(crate) fn new(
|
||||
column_name: impl Into<String>,
|
||||
bounds: Vec<Value>,
|
||||
regions: Vec<RegionId>,
|
||||
regions: Vec<RegionNumber>,
|
||||
) -> Self {
|
||||
Self {
|
||||
column_name: column_name.into(),
|
||||
@@ -69,7 +72,7 @@ impl RangePartitionRule {
|
||||
&self.column_name
|
||||
}
|
||||
|
||||
fn all_regions(&self) -> &Vec<RegionId> {
|
||||
fn all_regions(&self) -> &Vec<RegionNumber> {
|
||||
&self.regions
|
||||
}
|
||||
}
|
||||
@@ -81,11 +84,11 @@ impl PartitionRule for RangePartitionRule {
|
||||
vec![self.column_name().to_string()]
|
||||
}
|
||||
|
||||
fn find_region(&self, _values: &[Value]) -> Result<RegionId, Self::Error> {
|
||||
fn find_region(&self, _values: &[Value]) -> Result<RegionNumber, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn find_regions(&self, exprs: &[PartitionExpr]) -> Result<Vec<RegionId>, Self::Error> {
|
||||
fn find_regions(&self, exprs: &[PartitionExpr]) -> Result<Vec<RegionNumber>, Self::Error> {
|
||||
if exprs.is_empty() {
|
||||
return Ok(self.regions.clone());
|
||||
}
|
||||
@@ -152,18 +155,19 @@ mod test {
|
||||
regions: vec![1, 2, 3, 4],
|
||||
};
|
||||
|
||||
let test = |column: &str, op: Operator, value: &str, expected_regions: Vec<u64>| {
|
||||
let expr = PartitionExpr {
|
||||
column: column.to_string(),
|
||||
op,
|
||||
value: value.into(),
|
||||
let test =
|
||||
|column: &str, op: Operator, value: &str, expected_regions: Vec<RegionNumber>| {
|
||||
let expr = PartitionExpr {
|
||||
column: column.to_string(),
|
||||
op,
|
||||
value: value.into(),
|
||||
};
|
||||
let regions = rule.find_regions(&[expr]).unwrap();
|
||||
assert_eq!(
|
||||
regions,
|
||||
expected_regions.into_iter().collect::<Vec<RegionNumber>>()
|
||||
);
|
||||
};
|
||||
let regions = rule.find_regions(&[expr]).unwrap();
|
||||
assert_eq!(
|
||||
regions,
|
||||
expected_regions.into_iter().collect::<Vec<RegionId>>()
|
||||
);
|
||||
};
|
||||
|
||||
test("a", Operator::NotEq, "hz", vec![1, 2, 3, 4]);
|
||||
test("a", Operator::NotEq, "what", vec![1, 2, 3, 4]);
|
||||
|
||||
@@ -5,7 +5,7 @@ use datatypes::vectors::VectorBuilder;
|
||||
use datatypes::vectors::VectorRef;
|
||||
use snafu::ensure;
|
||||
use snafu::OptionExt;
|
||||
use store_api::storage::RegionId;
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use crate::error::Error;
|
||||
@@ -15,7 +15,7 @@ use crate::error::InvalidInsertRequestSnafu;
|
||||
use crate::error::Result;
|
||||
use crate::partitioning::PartitionRuleRef;
|
||||
|
||||
pub type DistInsertRequest = HashMap<RegionId, InsertRequest>;
|
||||
pub type DistInsertRequest = HashMap<RegionNumber, InsertRequest>;
|
||||
|
||||
pub struct WriteSpliter {
|
||||
partition_rule: PartitionRuleRef<Error>,
|
||||
@@ -41,11 +41,11 @@ impl WriteSpliter {
|
||||
fn split_partitioning_values(
|
||||
&self,
|
||||
values: &[VectorRef],
|
||||
) -> Result<HashMap<RegionId, Vec<usize>>> {
|
||||
) -> Result<HashMap<RegionNumber, Vec<usize>>> {
|
||||
if values.is_empty() {
|
||||
return Ok(HashMap::default());
|
||||
}
|
||||
let mut region_map: HashMap<RegionId, Vec<usize>> = HashMap::new();
|
||||
let mut region_map: HashMap<RegionNumber, Vec<usize>> = HashMap::new();
|
||||
let row_count = values[0].len();
|
||||
for idx in 0..row_count {
|
||||
let region_id = match self
|
||||
@@ -113,9 +113,9 @@ fn partition_values(partition_columns: &[VectorRef], idx: usize) -> Vec<Value> {
|
||||
|
||||
fn partition_insert_request(
|
||||
insert: &InsertRequest,
|
||||
region_map: HashMap<RegionId, Vec<usize>>,
|
||||
region_map: HashMap<RegionNumber, Vec<usize>>,
|
||||
) -> DistInsertRequest {
|
||||
let mut dist_insert: HashMap<RegionId, HashMap<&str, VectorBuilder>> =
|
||||
let mut dist_insert: HashMap<RegionNumber, HashMap<&str, VectorBuilder>> =
|
||||
HashMap::with_capacity(region_map.len());
|
||||
|
||||
let column_count = insert.columns_values.len();
|
||||
@@ -162,10 +162,12 @@ mod tests {
|
||||
value::Value,
|
||||
vectors::VectorBuilder,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use super::{
|
||||
check_req, find_partitioning_values, partition_insert_request, partition_values, RegionId,
|
||||
check_req, find_partitioning_values, partition_insert_request, partition_values,
|
||||
WriteSpliter,
|
||||
};
|
||||
use crate::{
|
||||
@@ -242,13 +244,13 @@ mod tests {
|
||||
#[test]
|
||||
fn test_partition_insert_request() {
|
||||
let insert = mock_insert_request();
|
||||
let mut region_map: HashMap<RegionId, Vec<usize>> = HashMap::with_capacity(2);
|
||||
let mut region_map: HashMap<RegionNumber, Vec<usize>> = HashMap::with_capacity(2);
|
||||
region_map.insert(1, vec![2, 0]);
|
||||
region_map.insert(2, vec![1]);
|
||||
|
||||
let dist_insert = partition_insert_request(&insert, region_map);
|
||||
|
||||
let r1_insert = dist_insert.get(&1_u64).unwrap();
|
||||
let r1_insert = dist_insert.get(&1_u32).unwrap();
|
||||
assert_eq!("demo", r1_insert.table_name);
|
||||
let expected: Value = 3_i16.into();
|
||||
assert_eq!(expected, r1_insert.columns_values.get("id").unwrap().get(0));
|
||||
@@ -283,7 +285,7 @@ mod tests {
|
||||
.get(1)
|
||||
);
|
||||
|
||||
let r2_insert = dist_insert.get(&2_u64).unwrap();
|
||||
let r2_insert = dist_insert.get(&2_u32).unwrap();
|
||||
assert_eq!("demo", r2_insert.table_name);
|
||||
let expected: Value = 2_i16.into();
|
||||
assert_eq!(expected, r2_insert.columns_values.get("id").unwrap().get(0));
|
||||
@@ -401,6 +403,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct MockPartitionRule;
|
||||
|
||||
// PARTITION BY LIST COLUMNS(id) (
|
||||
@@ -414,7 +417,7 @@ mod tests {
|
||||
vec!["id".to_string()]
|
||||
}
|
||||
|
||||
fn find_region(&self, values: &[Value]) -> Result<RegionId, Self::Error> {
|
||||
fn find_region(&self, values: &[Value]) -> Result<RegionNumber, Self::Error> {
|
||||
let val = values.get(0).unwrap().to_owned();
|
||||
let id_1: Value = 1_i16.into();
|
||||
let id_2: Value = 2_i16.into();
|
||||
@@ -428,7 +431,7 @@ mod tests {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn find_regions(&self, _: &[PartitionExpr]) -> Result<Vec<RegionId>, Self::Error> {
|
||||
fn find_regions(&self, _: &[PartitionExpr]) -> Result<Vec<RegionNumber>, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
102
src/frontend/src/sql.rs
Normal file
102
src/frontend/src/sql.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use catalog::SchemaProviderRef;
|
||||
use common_error::snafu::ensure;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::vectors::VectorBuilder;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use sql::ast::Value as SqlValue;
|
||||
use sql::statements;
|
||||
use sql::statements::insert::Insert;
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
|
||||
// TODO(fys): Extract the common logic in datanode and frontend in the future.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn insert_to_request(
|
||||
schema_provider: &SchemaProviderRef,
|
||||
stmt: Insert,
|
||||
) -> Result<InsertRequest> {
|
||||
let columns = stmt.columns();
|
||||
let values = stmt.values().context(error::ParseSqlSnafu)?;
|
||||
let table_name = stmt.table_name();
|
||||
|
||||
let table = schema_provider
|
||||
.table(&table_name)
|
||||
.context(error::CatalogSnafu)?
|
||||
.context(error::TableNotFoundSnafu {
|
||||
table_name: &table_name,
|
||||
})?;
|
||||
let schema = table.schema();
|
||||
let columns_num = if columns.is_empty() {
|
||||
schema.column_schemas().len()
|
||||
} else {
|
||||
columns.len()
|
||||
};
|
||||
let rows_num = values.len();
|
||||
|
||||
let mut columns_builders: Vec<(&String, &ConcreteDataType, VectorBuilder)> =
|
||||
Vec::with_capacity(columns_num);
|
||||
|
||||
if columns.is_empty() {
|
||||
for column_schema in schema.column_schemas() {
|
||||
let data_type = &column_schema.data_type;
|
||||
columns_builders.push((
|
||||
&column_schema.name,
|
||||
data_type,
|
||||
VectorBuilder::with_capacity(data_type.clone(), rows_num),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
for column_name in columns {
|
||||
let column_schema = schema.column_schema_by_name(column_name).with_context(|| {
|
||||
error::ColumnNotFoundSnafu {
|
||||
table_name: &table_name,
|
||||
column_name: column_name.to_string(),
|
||||
}
|
||||
})?;
|
||||
let data_type = &column_schema.data_type;
|
||||
columns_builders.push((
|
||||
column_name,
|
||||
data_type,
|
||||
VectorBuilder::with_capacity(data_type.clone(), rows_num),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for row in values {
|
||||
ensure!(
|
||||
row.len() == columns_num,
|
||||
error::ColumnValuesNumberMismatchSnafu {
|
||||
columns: columns_num,
|
||||
values: row.len(),
|
||||
}
|
||||
);
|
||||
|
||||
for (sql_val, (column_name, data_type, builder)) in
|
||||
row.iter().zip(columns_builders.iter_mut())
|
||||
{
|
||||
add_row_to_vector(column_name, data_type, sql_val, builder)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(InsertRequest {
|
||||
table_name,
|
||||
columns_values: columns_builders
|
||||
.into_iter()
|
||||
.map(|(c, _, mut b)| (c.to_owned(), b.finish()))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn add_row_to_vector(
|
||||
column_name: &str,
|
||||
data_type: &ConcreteDataType,
|
||||
sql_val: &SqlValue,
|
||||
builder: &mut VectorBuilder,
|
||||
) -> Result<()> {
|
||||
let value = statements::sql_value_to_value(column_name, data_type, sql_val)
|
||||
.context(error::ParseSqlSnafu)?;
|
||||
builder.push(&value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -14,7 +14,7 @@ use datafusion::logical_plan::Expr as DfExpr;
|
||||
use datafusion::physical_plan::Partitioning;
|
||||
use datatypes::schema::{ColumnSchema, Schema, SchemaRef};
|
||||
use snafu::prelude::*;
|
||||
use store_api::storage::RegionId;
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::error::Error as TableError;
|
||||
use table::metadata::{FilterPushDownType, TableInfoRef};
|
||||
use table::requests::InsertRequest;
|
||||
@@ -26,12 +26,12 @@ use crate::mock::{DatanodeId, DatanodeInstance, TableScanPlan};
|
||||
use crate::partitioning::{Operator, PartitionExpr, PartitionRuleRef};
|
||||
use crate::spliter::WriteSpliter;
|
||||
|
||||
struct DistTable {
|
||||
table_name: String,
|
||||
schema: SchemaRef,
|
||||
partition_rule: PartitionRuleRef<Error>,
|
||||
region_dist_map: HashMap<RegionId, DatanodeId>,
|
||||
datanode_instances: HashMap<DatanodeId, DatanodeInstance>,
|
||||
pub struct DistTable {
|
||||
pub table_name: String,
|
||||
pub schema: SchemaRef,
|
||||
pub partition_rule: PartitionRuleRef<Error>,
|
||||
pub region_dist_map: HashMap<RegionNumber, DatanodeId>,
|
||||
pub datanode_instances: HashMap<DatanodeId, DatanodeInstance>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -104,7 +104,7 @@ impl Table for DistTable {
|
||||
|
||||
impl DistTable {
|
||||
// TODO(LFC): Finding regions now seems less efficient, should be further looked into.
|
||||
fn find_regions(&self, filters: &[Expr]) -> Result<Vec<RegionId>> {
|
||||
fn find_regions(&self, filters: &[Expr]) -> Result<Vec<RegionNumber>> {
|
||||
let regions = if let Some((first, rest)) = filters.split_first() {
|
||||
let mut target = self.find_regions0(first)?;
|
||||
for filter in rest {
|
||||
@@ -119,7 +119,7 @@ impl DistTable {
|
||||
break;
|
||||
}
|
||||
}
|
||||
target.into_iter().collect::<Vec<RegionId>>()
|
||||
target.into_iter().collect::<Vec<_>>()
|
||||
} else {
|
||||
self.partition_rule.find_regions(&[])?
|
||||
};
|
||||
@@ -136,7 +136,7 @@ impl DistTable {
|
||||
// - BETWEEN and IN (maybe more)
|
||||
// - expr with arithmetic like "a + 1 < 10" (should have been optimized in logic plan?)
|
||||
// - not comparison or neither "AND" nor "OR" operations, for example, "a LIKE x"
|
||||
fn find_regions0(&self, filter: &Expr) -> Result<HashSet<RegionId>> {
|
||||
fn find_regions0(&self, filter: &Expr) -> Result<HashSet<RegionNumber>> {
|
||||
let expr = filter.df_expr();
|
||||
match expr {
|
||||
DfExpr::BinaryExpr { left, op, right } if is_compare_op(op) => {
|
||||
@@ -156,7 +156,7 @@ impl DistTable {
|
||||
.partition_rule
|
||||
.find_regions(&[PartitionExpr::new(column, op, value)])?
|
||||
.into_iter()
|
||||
.collect::<HashSet<RegionId>>());
|
||||
.collect::<HashSet<RegionNumber>>());
|
||||
}
|
||||
}
|
||||
DfExpr::BinaryExpr { left, op, right }
|
||||
@@ -168,11 +168,11 @@ impl DistTable {
|
||||
Operator::And => left_regions
|
||||
.intersection(&right_regions)
|
||||
.cloned()
|
||||
.collect::<HashSet<RegionId>>(),
|
||||
.collect::<HashSet<RegionNumber>>(),
|
||||
Operator::Or => left_regions
|
||||
.union(&right_regions)
|
||||
.cloned()
|
||||
.collect::<HashSet<RegionId>>(),
|
||||
.collect::<HashSet<RegionNumber>>(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
return Ok(regions);
|
||||
@@ -185,10 +185,13 @@ impl DistTable {
|
||||
.partition_rule
|
||||
.find_regions(&[])?
|
||||
.into_iter()
|
||||
.collect::<HashSet<RegionId>>())
|
||||
.collect::<HashSet<RegionNumber>>())
|
||||
}
|
||||
|
||||
fn find_datanodes(&self, regions: Vec<RegionId>) -> Result<HashMap<DatanodeId, Vec<RegionId>>> {
|
||||
fn find_datanodes(
|
||||
&self,
|
||||
regions: Vec<RegionNumber>,
|
||||
) -> Result<HashMap<DatanodeId, Vec<RegionNumber>>> {
|
||||
let mut datanodes = HashMap::new();
|
||||
for region in regions.iter() {
|
||||
let datanode = *self
|
||||
@@ -344,6 +347,7 @@ mod test {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_dist_table_scan() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
let table = Arc::new(new_dist_table().await);
|
||||
|
||||
// should scan all regions
|
||||
@@ -434,7 +438,7 @@ mod test {
|
||||
let partition_rule = RangePartitionRule::new(
|
||||
"a",
|
||||
vec![10_i32.into(), 20_i32.into(), 50_i32.into()],
|
||||
vec![1_u64, 2, 3, 4],
|
||||
vec![1_u32, 2, 3, 4],
|
||||
);
|
||||
|
||||
let table1 = new_memtable(schema.clone(), (0..5).collect::<Vec<i32>>());
|
||||
@@ -458,7 +462,7 @@ mod test {
|
||||
table_name: "dist_numbers".to_string(),
|
||||
schema,
|
||||
partition_rule: Arc::new(partition_rule),
|
||||
region_dist_map: HashMap::from([(1_u64, 1), (2_u64, 2), (3_u64, 3), (4_u64, 4)]),
|
||||
region_dist_map: HashMap::from([(1_u32, 1), (2_u32, 2), (3_u32, 3), (4_u32, 4)]),
|
||||
datanode_instances,
|
||||
}
|
||||
}
|
||||
@@ -491,20 +495,21 @@ mod test {
|
||||
|
||||
let instance = Arc::new(Instance::with_mock_meta_client(&opts).await.unwrap());
|
||||
instance.start().await.unwrap();
|
||||
|
||||
let catalog_manager = instance.catalog_manager().clone();
|
||||
let client = crate::tests::create_datanode_client(instance).await;
|
||||
|
||||
let table_name = table.table_name().to_string();
|
||||
catalog_manager
|
||||
.register_table(RegisterTableRequest {
|
||||
catalog: "greptime".to_string(),
|
||||
schema: "public".to_string(),
|
||||
table_name: table.table_name().to_string(),
|
||||
table_name: table_name.clone(),
|
||||
table_id: 1234,
|
||||
table: Arc::new(table),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let client = crate::tests::create_datanode_client(instance).await;
|
||||
DatanodeInstance::new(
|
||||
datanode_id,
|
||||
catalog_manager,
|
||||
@@ -516,7 +521,7 @@ mod test {
|
||||
async fn test_find_regions() {
|
||||
let table = new_dist_table().await;
|
||||
|
||||
let test = |filters: Vec<Expr>, expect_regions: Vec<u64>| {
|
||||
let test = |filters: Vec<Expr>, expect_regions: Vec<RegionNumber>| {
|
||||
let mut regions = table.find_regions(filters.as_slice()).unwrap();
|
||||
regions.sort();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use client::ObjectResult;
|
||||
use snafu::ensure;
|
||||
use snafu::OptionExt;
|
||||
use snafu::ResultExt;
|
||||
use store_api::storage::RegionId;
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use super::DistTable;
|
||||
@@ -21,7 +21,7 @@ use crate::error::Result;
|
||||
impl DistTable {
|
||||
pub async fn dist_insert(
|
||||
&self,
|
||||
inserts: HashMap<RegionId, InsertRequest>,
|
||||
inserts: HashMap<RegionNumber, InsertRequest>,
|
||||
) -> Result<ObjectResult> {
|
||||
let mut joins = Vec::with_capacity(inserts.len());
|
||||
|
||||
@@ -66,7 +66,7 @@ impl DistTable {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_insert_expr(region_id: RegionId, insert: InsertRequest) -> Result<InsertExpr> {
|
||||
fn to_insert_expr(region_id: RegionNumber, insert: InsertRequest) -> Result<InsertExpr> {
|
||||
let mut row_count = None;
|
||||
|
||||
let columns = insert
|
||||
@@ -109,7 +109,7 @@ fn to_insert_expr(region_id: RegionId, insert: InsertRequest) -> Result<InsertEx
|
||||
options.insert(
|
||||
// TODO(fys): Temporarily hard code here
|
||||
"region_id".to_string(),
|
||||
codec::RegionId { id: region_id }.into(),
|
||||
codec::RegionNumber { id: region_id }.into(),
|
||||
);
|
||||
|
||||
Ok(InsertExpr {
|
||||
@@ -196,7 +196,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let bytes = insert_expr.options.get("region_id").unwrap();
|
||||
let region_id: codec::RegionId = bytes.deref().try_into().unwrap();
|
||||
let region_id: codec::RegionNumber = bytes.deref().try_into().unwrap();
|
||||
assert_eq!(12, region_id.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,13 @@ pub struct LogFile {
|
||||
append_buffer_size: usize,
|
||||
}
|
||||
|
||||
impl Drop for LogFile {
|
||||
fn drop(&mut self) {
|
||||
self.state.stopped.store(true, Ordering::Relaxed);
|
||||
info!("Stopping log file {}", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
impl LogFile {
|
||||
/// Opens a file in path with given log config.
|
||||
pub async fn open(path: impl Into<String>, config: &LogConfig) -> Result<Self> {
|
||||
@@ -826,4 +833,24 @@ mod tests {
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_shutdown() {
|
||||
logging::init_default_ut_logging();
|
||||
let config = LogConfig::default();
|
||||
let dir = TempDir::new("greptimedb-store-test").unwrap();
|
||||
let path_buf = dir.path().join("0010.log");
|
||||
let path = path_buf.to_str().unwrap().to_string();
|
||||
File::create(path.as_str()).unwrap();
|
||||
|
||||
let mut file = LogFile::open(path.clone(), &config)
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("Failed to open file: {}", path));
|
||||
|
||||
let state = file.state.clone();
|
||||
file.start().await.unwrap();
|
||||
drop(file);
|
||||
|
||||
assert!(state.stopped.load(Ordering::Relaxed));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,14 +372,14 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ask_leader() {
|
||||
let client = mocks::mock_client_with_noopstore().await;
|
||||
let client = mocks::mock_client_with_memstore().await;
|
||||
let res = client.ask_leader().await;
|
||||
assert!(res.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_heartbeat() {
|
||||
let client = mocks::mock_client_with_noopstore().await;
|
||||
let client = mocks::mock_client_with_memstore().await;
|
||||
let (sender, mut receiver) = client.heartbeat().await.unwrap();
|
||||
// send heartbeats
|
||||
tokio::spawn(async move {
|
||||
@@ -428,9 +428,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_route() {
|
||||
async fn test_route() {
|
||||
let selector = Arc::new(MockSelector {});
|
||||
let client = mocks::mock_client_with_selector(selector).await;
|
||||
let client = mocks::mock_client_with_memorystore_and_selector(selector).await;
|
||||
|
||||
let p1 = Partition {
|
||||
column_list: vec![b"col_1".to_vec(), b"col_2".to_vec()],
|
||||
value_list: vec![b"k1".to_vec(), b"k2".to_vec()],
|
||||
@@ -440,29 +441,17 @@ mod tests {
|
||||
value_list: vec![b"Max1".to_vec(), b"Max2".to_vec()],
|
||||
};
|
||||
let table_name = TableName::new("test_catalog", "test_schema", "test_table");
|
||||
let req = CreateRequest::new(table_name)
|
||||
let req = CreateRequest::new(table_name.clone())
|
||||
.add_partition(p1)
|
||||
.add_partition(p2);
|
||||
|
||||
let res = client.create_route(req).await.unwrap();
|
||||
assert_eq!(1, res.table_routes.len());
|
||||
|
||||
let table_routes = res.table_routes;
|
||||
assert_eq!(1, table_routes.len());
|
||||
let table_route = table_routes.get(0).unwrap();
|
||||
let table = &table_route.table;
|
||||
let table_name = &table.table_name;
|
||||
assert_eq!("test_catalog", table_name.catalog_name);
|
||||
assert_eq!("test_schema", table_name.schema_name);
|
||||
assert_eq!("test_table", table_name.table_name);
|
||||
|
||||
let region_routes = &table_route.region_routes;
|
||||
assert_eq!(2, region_routes.len());
|
||||
let r0 = region_routes.get(0).unwrap();
|
||||
let r1 = region_routes.get(1).unwrap();
|
||||
assert_eq!(0, r0.region.as_ref().unwrap().id);
|
||||
assert_eq!(1, r1.region.as_ref().unwrap().id);
|
||||
assert_eq!("peer0", r0.leader_peer.as_ref().unwrap().addr);
|
||||
assert_eq!("peer1", r1.leader_peer.as_ref().unwrap().addr);
|
||||
let req = RouteRequest::new().add_table_name(table_name);
|
||||
let res = client.route(req).await.unwrap();
|
||||
// empty table_routes since no TableGlobalValue is stored by datanode
|
||||
assert!(res.table_routes.is_empty());
|
||||
}
|
||||
|
||||
async fn gen_data(client: &MetaClient) {
|
||||
|
||||
@@ -5,11 +5,6 @@ use meta_srv::mocks::MockInfo;
|
||||
use crate::client::MetaClient;
|
||||
use crate::client::MetaClientBuilder;
|
||||
|
||||
pub async fn mock_client_with_noopstore() -> MetaClient {
|
||||
let mock_info = server_mock::mock_with_noopstore().await;
|
||||
mock_client_by(mock_info).await
|
||||
}
|
||||
|
||||
pub async fn mock_client_with_memstore() -> MetaClient {
|
||||
let mock_info = server_mock::mock_with_memstore().await;
|
||||
mock_client_by(mock_info).await
|
||||
@@ -21,8 +16,8 @@ pub async fn mock_client_with_etcdstore(addr: &str) -> MetaClient {
|
||||
mock_client_by(mock_info).await
|
||||
}
|
||||
|
||||
pub async fn mock_client_with_selector(selector: SelectorRef) -> MetaClient {
|
||||
let mock_info = server_mock::mock_with_selector(selector).await;
|
||||
pub async fn mock_client_with_memorystore_and_selector(selector: SelectorRef) -> MetaClient {
|
||||
let mock_info = server_mock::mock_with_memstore_and_selector(selector).await;
|
||||
mock_client_by(mock_info).await
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ pub struct TableRoute {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Table {
|
||||
pub id: u64,
|
||||
pub table_name: TableName,
|
||||
pub table_schema: Vec<u8>,
|
||||
}
|
||||
@@ -149,6 +150,7 @@ impl TryFrom<PbTable> for Table {
|
||||
})?
|
||||
.into();
|
||||
Ok(Self {
|
||||
id: t.id,
|
||||
table_name,
|
||||
table_schema: t.table_schema,
|
||||
})
|
||||
@@ -296,6 +298,7 @@ mod tests {
|
||||
],
|
||||
table_routes: vec![PbTableRoute {
|
||||
table: Some(PbTable {
|
||||
id: 1,
|
||||
table_name: Some(PbTableName {
|
||||
catalog_name: "c1".to_string(),
|
||||
schema_name: "s1".to_string(),
|
||||
@@ -324,6 +327,7 @@ mod tests {
|
||||
assert_eq!(1, table_routes.len());
|
||||
let table_route = table_routes.remove(0);
|
||||
let table = table_route.table;
|
||||
assert_eq!(1, table.id);
|
||||
assert_eq!("c1", table.table_name.catalog_name);
|
||||
assert_eq!("s1", table.table_name.schema_name);
|
||||
assert_eq!("t1", table.table_name.table_name);
|
||||
|
||||
@@ -10,6 +10,7 @@ mock = []
|
||||
api = { path = "../api" }
|
||||
async-trait = "0.1"
|
||||
common-base = { path = "../common/base" }
|
||||
common-catalog = { path = "../common/catalog" }
|
||||
common-error = { path = "../common/error" }
|
||||
common-grpc = { path = "../common/grpc" }
|
||||
common-runtime = { path = "../common/runtime" }
|
||||
@@ -21,6 +22,7 @@ h2 = "0.3"
|
||||
http-body = "0.4"
|
||||
lazy_static = "1.4"
|
||||
parking_lot = "0.12"
|
||||
prost = "0.11"
|
||||
regex = "1.6"
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -79,6 +79,33 @@ pub enum Error {
|
||||
err_msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Cannot parse catalog value, source: {}", source))]
|
||||
InvalidCatalogValue {
|
||||
#[snafu(backtrace)]
|
||||
source: common_catalog::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Unexcepted sequence value: {}", err_msg))]
|
||||
UnexceptedSequenceValue {
|
||||
err_msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to decode table route, source: {}", source))]
|
||||
DecodeTableRoute {
|
||||
source: prost::DecodeError,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Table route not found: {}", key))]
|
||||
TableRouteNotFound { key: String, backtrace: Backtrace },
|
||||
|
||||
#[snafu(display("Failed to get sequence: {}", err_msg))]
|
||||
NextSequence {
|
||||
err_msg: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -106,15 +133,19 @@ impl ErrorExt for Error {
|
||||
| Error::TcpBind { .. }
|
||||
| Error::SerializeToJson { .. }
|
||||
| Error::DeserializeFromJson { .. }
|
||||
| Error::DecodeTableRoute { .. }
|
||||
| Error::StartGrpc { .. } => StatusCode::Internal,
|
||||
Error::EmptyKey { .. }
|
||||
| Error::EmptyTableName { .. }
|
||||
| Error::InvalidLeaseKey { .. }
|
||||
| Error::ParseNum { .. }
|
||||
| Error::InvalidArguments { .. } => StatusCode::InvalidArguments,
|
||||
Error::LeaseKeyFromUtf8 { .. } | Error::InvalidTxnResult { .. } => {
|
||||
StatusCode::Unexpected
|
||||
}
|
||||
Error::LeaseKeyFromUtf8 { .. }
|
||||
| Error::UnexceptedSequenceValue { .. }
|
||||
| Error::TableRouteNotFound { .. }
|
||||
| Error::NextSequence { .. }
|
||||
| Error::InvalidTxnResult { .. } => StatusCode::Unexpected,
|
||||
Error::InvalidCatalogValue { source, .. } => source.status_code(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ impl HeartbeatHandler for DatanodeLeaseHandler {
|
||||
node_addr: peer.addr.clone(),
|
||||
};
|
||||
|
||||
info!("Receive a heartbeat from datanode: {:?}, {:?}", key, value);
|
||||
info!("Receive a heartbeat: {:?}, {:?}", key, value);
|
||||
|
||||
let key = key.try_into()?;
|
||||
let value = value.try_into()?;
|
||||
@@ -41,8 +41,7 @@ impl HeartbeatHandler for DatanodeLeaseHandler {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let kv_store = ctx.kv_store();
|
||||
let _ = kv_store.put(put).await?;
|
||||
let _ = ctx.kv_store().put(put).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -35,11 +35,12 @@ mod tests {
|
||||
use api::v1::meta::{HeartbeatResponse, RequestHeader};
|
||||
|
||||
use super::*;
|
||||
use crate::{handler::Context, service::store::noop::NoopKvStore};
|
||||
use crate::handler::Context;
|
||||
use crate::service::store::memory::MemStore;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_heartbeat_resp_header() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let ctx = Context {
|
||||
server_addr: "0.0.0.0:0000".to_string(),
|
||||
kv_store,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use api::v1::meta::TableName;
|
||||
use common_catalog::TableGlobalKey;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
@@ -12,6 +14,8 @@ use crate::error;
|
||||
use crate::error::Result;
|
||||
|
||||
pub(crate) const DN_LEASE_PREFIX: &str = "__meta_dnlease";
|
||||
pub(crate) const SEQ_PREFIX: &str = "__meta_seq";
|
||||
pub(crate) const TABLE_ROUTE_PREFIX: &str = "__meta_table_route";
|
||||
|
||||
lazy_static! {
|
||||
static ref DATANODE_KEY_PATTERN: Regex =
|
||||
@@ -108,6 +112,44 @@ impl TryFrom<LeaseValue> for Vec<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableRouteKey<'a> {
|
||||
pub table_id: u64,
|
||||
pub catalog_name: &'a str,
|
||||
pub schema_name: &'a str,
|
||||
pub table_name: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> TableRouteKey<'a> {
|
||||
pub fn with_table_name(table_id: u64, t: &'a TableName) -> Self {
|
||||
Self {
|
||||
table_id,
|
||||
catalog_name: &t.catalog_name,
|
||||
schema_name: &t.schema_name,
|
||||
table_name: &t.table_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_table_global_key(table_id: u64, t: &'a TableGlobalKey) -> Self {
|
||||
Self {
|
||||
table_id,
|
||||
catalog_name: &t.catalog_name,
|
||||
schema_name: &t.schema_name,
|
||||
table_name: &t.table_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> String {
|
||||
format!(
|
||||
"{}-{}-{}-{}",
|
||||
TABLE_ROUTE_PREFIX, self.catalog_name, self.schema_name, self.table_name
|
||||
)
|
||||
}
|
||||
|
||||
pub fn key(&self) -> String {
|
||||
format!("{}-{}", self.prefix(), self.table_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use api::v1::meta::RangeRequest;
|
||||
use api::v1::meta::RangeResponse;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::keys::LeaseKey;
|
||||
@@ -10,7 +9,7 @@ use crate::util;
|
||||
|
||||
pub async fn alive_datanodes<P>(
|
||||
cluster_id: u64,
|
||||
kv_store: KvStoreRef,
|
||||
kv_store: &KvStoreRef,
|
||||
predicate: P,
|
||||
) -> Result<Vec<(LeaseKey, LeaseValue)>>
|
||||
where
|
||||
@@ -26,7 +25,7 @@ where
|
||||
|
||||
let res = kv_store.range(req).await?;
|
||||
|
||||
let RangeResponse { kvs, .. } = res;
|
||||
let kvs = res.kvs;
|
||||
let mut lease_kvs = vec![];
|
||||
for kv in kvs {
|
||||
let lease_key: LeaseKey = kv.key.try_into()?;
|
||||
|
||||
@@ -3,11 +3,12 @@ pub mod bootstrap;
|
||||
pub mod error;
|
||||
pub mod handler;
|
||||
mod keys;
|
||||
pub mod lease;
|
||||
mod lease;
|
||||
pub mod metasrv;
|
||||
#[cfg(feature = "mock")]
|
||||
pub mod mocks;
|
||||
pub mod selector;
|
||||
mod sequence;
|
||||
pub mod service;
|
||||
mod util;
|
||||
|
||||
|
||||
@@ -9,8 +9,12 @@ use crate::handler::response_header::ResponseHeaderHandler;
|
||||
use crate::handler::HeartbeatHandlerGroup;
|
||||
use crate::selector::lease_based::LeaseBasedSelector;
|
||||
use crate::selector::Selector;
|
||||
use crate::sequence::Sequence;
|
||||
use crate::sequence::SequenceRef;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
|
||||
pub const TABLE_ID_SEQ: &str = "table_id";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MetaSrvOptions {
|
||||
pub bind_addr: String,
|
||||
@@ -24,7 +28,7 @@ impl Default for MetaSrvOptions {
|
||||
Self {
|
||||
bind_addr: "0.0.0.0:3002".to_string(),
|
||||
server_addr: "0.0.0.0:3002".to_string(),
|
||||
store_addr: "0.0.0.0:2380".to_string(),
|
||||
store_addr: "0.0.0.0:2379".to_string(),
|
||||
datanode_lease_secs: 15,
|
||||
}
|
||||
}
|
||||
@@ -42,6 +46,7 @@ pub type SelectorRef = Arc<dyn Selector<Context = Context, Output = Vec<Peer>>>;
|
||||
pub struct MetaSrv {
|
||||
options: MetaSrvOptions,
|
||||
kv_store: KvStoreRef,
|
||||
table_id_sequence: SequenceRef,
|
||||
selector: SelectorRef,
|
||||
handler_group: HeartbeatHandlerGroup,
|
||||
}
|
||||
@@ -52,6 +57,7 @@ impl MetaSrv {
|
||||
kv_store: KvStoreRef,
|
||||
selector: Option<SelectorRef>,
|
||||
) -> Self {
|
||||
let table_id_sequence = Arc::new(Sequence::new(TABLE_ID_SEQ, 10, kv_store.clone()));
|
||||
let selector = selector.unwrap_or_else(|| Arc::new(LeaseBasedSelector {}));
|
||||
let handler_group = HeartbeatHandlerGroup::default();
|
||||
handler_group.add_handler(ResponseHeaderHandler).await;
|
||||
@@ -60,6 +66,7 @@ impl MetaSrv {
|
||||
Self {
|
||||
options,
|
||||
kv_store,
|
||||
table_id_sequence,
|
||||
selector,
|
||||
handler_group,
|
||||
}
|
||||
@@ -75,6 +82,11 @@ impl MetaSrv {
|
||||
self.kv_store.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn table_id_sequence(&self) -> SequenceRef {
|
||||
self.table_id_sequence.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn selector(&self) -> SelectorRef {
|
||||
self.selector.clone()
|
||||
|
||||
@@ -13,18 +13,12 @@ use crate::metasrv::SelectorRef;
|
||||
use crate::service::store::etcd::EtcdStore;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::service::store::memory::MemStore;
|
||||
use crate::service::store::noop::NoopKvStore;
|
||||
|
||||
pub struct MockInfo {
|
||||
pub server_addr: String,
|
||||
pub channel_manager: ChannelManager,
|
||||
}
|
||||
|
||||
pub async fn mock_with_noopstore() -> MockInfo {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
mock(Default::default(), kv_store, None).await
|
||||
}
|
||||
|
||||
pub async fn mock_with_memstore() -> MockInfo {
|
||||
let kv_store = Arc::new(MemStore::default());
|
||||
mock(Default::default(), kv_store, None).await
|
||||
@@ -35,8 +29,8 @@ pub async fn mock_with_etcdstore(addr: &str) -> MockInfo {
|
||||
mock(Default::default(), kv_store, None).await
|
||||
}
|
||||
|
||||
pub async fn mock_with_selector(selector: SelectorRef) -> MockInfo {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
pub async fn mock_with_memstore_and_selector(selector: SelectorRef) -> MockInfo {
|
||||
let kv_store = Arc::new(MemStore::default());
|
||||
mock(Default::default(), kv_store, Some(selector)).await
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ impl Selector for LeaseBasedSelector {
|
||||
let lease_filter = |_: &LeaseKey, v: &LeaseValue| {
|
||||
time_util::current_time_millis() - v.timestamp_millis < ctx.datanode_lease_secs
|
||||
};
|
||||
let mut lease_kvs = lease::alive_datanodes(ns, ctx.kv_store.clone(), lease_filter).await?;
|
||||
let mut lease_kvs = lease::alive_datanodes(ns, &ctx.kv_store, lease_filter).await?;
|
||||
// TODO(jiachun): At the moment we are just pushing the latest to the forefront,
|
||||
// and it is better to use load-based strategies in the future.
|
||||
lease_kvs.sort_by(|a, b| b.1.timestamp_millis.cmp(&a.1.timestamp_millis));
|
||||
|
||||
197
src/meta-srv/src/sequence.rs
Normal file
197
src/meta-srv/src/sequence.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::CompareAndPutRequest;
|
||||
use snafu::ensure;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::keys;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
|
||||
pub type SequenceRef = Arc<Sequence>;
|
||||
|
||||
pub struct Sequence {
|
||||
inner: Mutex<Inner>,
|
||||
}
|
||||
|
||||
impl Sequence {
|
||||
pub fn new(name: impl AsRef<str>, step: u64, generator: KvStoreRef) -> Self {
|
||||
let name = format!("{}-{}", keys::SEQ_PREFIX, name.as_ref());
|
||||
let step = step.max(1);
|
||||
Self {
|
||||
inner: Mutex::new(Inner {
|
||||
name,
|
||||
generator,
|
||||
next: 0,
|
||||
step,
|
||||
range: None,
|
||||
force_quit: 1024,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn next(&self) -> Result<u64> {
|
||||
let mut inner = self.inner.lock().await;
|
||||
inner.next().await
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
name: String,
|
||||
generator: KvStoreRef,
|
||||
// The next available sequences(if it is in the range,
|
||||
// otherwise it need to fetch from generator again).
|
||||
next: u64,
|
||||
// Fetch several sequences at once: [start, start + step).
|
||||
step: u64,
|
||||
// The range of available sequences for the local cache.
|
||||
range: Option<Range<u64>>,
|
||||
// Used to avoid dead loops.
|
||||
force_quit: usize,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
/// 1. returns the `next` value directly if it is in the `range` (local cache)
|
||||
/// 2. fetch(CAS) next `range` from the `generator`
|
||||
/// 3. jump to step 1
|
||||
pub async fn next(&mut self) -> Result<u64> {
|
||||
for _ in 0..self.force_quit {
|
||||
match &self.range {
|
||||
Some(range) => {
|
||||
if range.contains(&self.next) {
|
||||
let res = Ok(self.next);
|
||||
self.next += 1;
|
||||
return res;
|
||||
}
|
||||
self.range = None;
|
||||
}
|
||||
None => {
|
||||
let range = self.next_range().await?;
|
||||
self.next = range.start;
|
||||
self.range = Some(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error::NextSequenceSnafu {
|
||||
err_msg: format!("{}.next()", &self.name),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
|
||||
pub async fn next_range(&self) -> Result<Range<u64>> {
|
||||
let key = self.name.as_bytes();
|
||||
let mut start = self.next;
|
||||
for _ in 0..self.force_quit {
|
||||
let expect = if start == 0 {
|
||||
vec![]
|
||||
} else {
|
||||
u64::to_le_bytes(start).to_vec()
|
||||
};
|
||||
let value = u64::to_le_bytes(start + self.step);
|
||||
|
||||
let req = CompareAndPutRequest {
|
||||
key: key.to_vec(),
|
||||
expect,
|
||||
value: value.to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let res = self.generator.compare_and_put(req).await?;
|
||||
|
||||
if !res.success {
|
||||
if let Some(kv) = res.prev_kv {
|
||||
let value = kv.value;
|
||||
ensure!(
|
||||
value.len() == std::mem::size_of::<u64>(),
|
||||
error::UnexceptedSequenceValueSnafu {
|
||||
err_msg: format!("key={}, unexpected value={:?}", self.name, value)
|
||||
}
|
||||
);
|
||||
start = u64::from_le_bytes(value.try_into().unwrap());
|
||||
} else {
|
||||
start = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(Range {
|
||||
start,
|
||||
end: start + self.step,
|
||||
});
|
||||
}
|
||||
|
||||
error::NextSequenceSnafu {
|
||||
err_msg: format!("{}.next_range()", &self.name),
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::service::store::{kv::KvStore, memory::MemStore};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sequence() {
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let seq = Sequence::new("test_seq", 10, kv_store);
|
||||
|
||||
for i in 0..100 {
|
||||
assert_eq!(i, seq.next().await.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sequence_fouce_quit() {
|
||||
struct Noop;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvStore for Noop {
|
||||
async fn range(
|
||||
&self,
|
||||
_: api::v1::meta::RangeRequest,
|
||||
) -> Result<api::v1::meta::RangeResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn put(
|
||||
&self,
|
||||
_: api::v1::meta::PutRequest,
|
||||
) -> Result<api::v1::meta::PutResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn batch_put(
|
||||
&self,
|
||||
_: api::v1::meta::BatchPutRequest,
|
||||
) -> Result<api::v1::meta::BatchPutResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn compare_and_put(
|
||||
&self,
|
||||
_: CompareAndPutRequest,
|
||||
) -> Result<api::v1::meta::CompareAndPutResponse> {
|
||||
Ok(api::v1::meta::CompareAndPutResponse::default())
|
||||
}
|
||||
|
||||
async fn delete_range(
|
||||
&self,
|
||||
_: api::v1::meta::DeleteRangeRequest,
|
||||
) -> Result<api::v1::meta::DeleteRangeResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
let kv_store = Arc::new(Noop {});
|
||||
let seq = Sequence::new("test_seq", 10, kv_store);
|
||||
|
||||
let next = seq.next().await;
|
||||
assert!(next.is_err());
|
||||
}
|
||||
}
|
||||
@@ -142,11 +142,11 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::metasrv::MetaSrvOptions;
|
||||
use crate::service::store::noop::NoopKvStore;
|
||||
use crate::service::store::memory::MemStore;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_ask_leader() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
|
||||
let req = AskLeaderRequest {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
use api::v1::meta::router_server;
|
||||
use api::v1::meta::CreateRequest;
|
||||
use api::v1::meta::Error;
|
||||
use api::v1::meta::PeerDict;
|
||||
use api::v1::meta::PutRequest;
|
||||
use api::v1::meta::RangeRequest;
|
||||
use api::v1::meta::Region;
|
||||
use api::v1::meta::RegionRoute;
|
||||
use api::v1::meta::ResponseHeader;
|
||||
@@ -7,22 +11,31 @@ use api::v1::meta::RouteRequest;
|
||||
use api::v1::meta::RouteResponse;
|
||||
use api::v1::meta::Table;
|
||||
use api::v1::meta::TableRoute;
|
||||
use api::v1::meta::TableRouteValue;
|
||||
use common_catalog::TableGlobalKey;
|
||||
use common_catalog::TableGlobalValue;
|
||||
use common_telemetry::warn;
|
||||
use snafu::OptionExt;
|
||||
use snafu::ResultExt;
|
||||
use tonic::Request;
|
||||
use tonic::Response;
|
||||
|
||||
use super::store::kv::KvStoreRef;
|
||||
use super::GrpcResult;
|
||||
use crate::error;
|
||||
use crate::error::Result;
|
||||
use crate::keys::TableRouteKey;
|
||||
use crate::metasrv::Context;
|
||||
use crate::metasrv::MetaSrv;
|
||||
use crate::metasrv::SelectorRef;
|
||||
use crate::sequence::SequenceRef;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl router_server::Router for MetaSrv {
|
||||
async fn route(&self, req: Request<RouteRequest>) -> GrpcResult<RouteResponse> {
|
||||
let req = req.into_inner();
|
||||
let res = handle_route(req).await?;
|
||||
let ctx = self.new_ctx();
|
||||
let res = handle_route(req, ctx).await?;
|
||||
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
@@ -31,20 +44,68 @@ impl router_server::Router for MetaSrv {
|
||||
let req = req.into_inner();
|
||||
let ctx = self.new_ctx();
|
||||
let selector = self.selector();
|
||||
let res = handle_create(req, ctx, selector).await?;
|
||||
let table_id_sequence = self.table_id_sequence();
|
||||
let res = handle_create(req, ctx, selector, table_id_sequence).await?;
|
||||
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_route(_req: RouteRequest) -> Result<RouteResponse> {
|
||||
todo!()
|
||||
async fn handle_route(req: RouteRequest, ctx: Context) -> Result<RouteResponse> {
|
||||
let RouteRequest {
|
||||
header,
|
||||
table_names,
|
||||
} = req;
|
||||
let cluster_id = header.as_ref().map_or(0, |h| h.cluster_id);
|
||||
let table_global_keys = table_names.into_iter().map(|t| TableGlobalKey {
|
||||
catalog_name: t.catalog_name,
|
||||
schema_name: t.schema_name,
|
||||
table_name: t.table_name,
|
||||
});
|
||||
let tables = fetch_tables(&ctx.kv_store, table_global_keys).await?;
|
||||
|
||||
let mut peer_dict = PeerDict::default();
|
||||
let mut table_routes = vec![];
|
||||
for (tg, tr) in tables {
|
||||
let TableRouteValue {
|
||||
peers,
|
||||
mut table_route,
|
||||
} = tr;
|
||||
if let Some(ref mut table_route) = table_route {
|
||||
for rr in &mut table_route.region_routes {
|
||||
if let Some(peer) = peers.get(rr.leader_peer_index as usize) {
|
||||
rr.leader_peer_index = peer_dict.get_or_insert(peer.clone()) as u64;
|
||||
}
|
||||
for index in &mut rr.follower_peer_indexes {
|
||||
if let Some(peer) = peers.get(*index as usize) {
|
||||
*index = peer_dict.get_or_insert(peer.clone()) as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref mut table) = table_route.table {
|
||||
table.table_schema = tg.as_bytes().context(error::InvalidCatalogValueSnafu)?;
|
||||
}
|
||||
}
|
||||
if let Some(table_route) = table_route {
|
||||
table_routes.push(table_route)
|
||||
}
|
||||
}
|
||||
let peers = peer_dict.into_peers();
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(RouteResponse {
|
||||
header,
|
||||
peers,
|
||||
table_routes,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_create(
|
||||
req: CreateRequest,
|
||||
ctx: Context,
|
||||
selector: SelectorRef,
|
||||
table_id_sequence: SequenceRef,
|
||||
) -> Result<RouteResponse> {
|
||||
let CreateRequest {
|
||||
header,
|
||||
@@ -55,22 +116,37 @@ async fn handle_create(
|
||||
let cluster_id = header.as_ref().map_or(0, |h| h.cluster_id);
|
||||
|
||||
let peers = selector.select(cluster_id, &ctx).await?;
|
||||
if peers.is_empty() {
|
||||
let header = Some(ResponseHeader::failed(
|
||||
cluster_id,
|
||||
Error::no_active_datanodes(),
|
||||
));
|
||||
return Ok(RouteResponse {
|
||||
header,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
let id = table_id_sequence.next().await?;
|
||||
let table_route_key = TableRouteKey::with_table_name(id, &table_name)
|
||||
.key()
|
||||
.into_bytes();
|
||||
|
||||
let table = Table {
|
||||
id,
|
||||
table_name: Some(table_name),
|
||||
..Default::default()
|
||||
};
|
||||
let region_num = partitions.len();
|
||||
let mut region_routes = Vec::with_capacity(region_num);
|
||||
for i in 0..region_num {
|
||||
let mut region_routes = Vec::with_capacity(partitions.len());
|
||||
for (i, partition) in partitions.into_iter().enumerate() {
|
||||
let region = Region {
|
||||
id: i as u64,
|
||||
partition: Some(partition),
|
||||
..Default::default()
|
||||
};
|
||||
let region_route = RegionRoute {
|
||||
region: Some(region),
|
||||
leader_peer_index: (i % peers.len()) as u64,
|
||||
follower_peer_indexes: vec![(i % peers.len()) as u64],
|
||||
follower_peer_indexes: vec![], // follower_peers is not supported at the moment
|
||||
};
|
||||
region_routes.push(region_route);
|
||||
}
|
||||
@@ -79,6 +155,13 @@ async fn handle_create(
|
||||
region_routes,
|
||||
};
|
||||
|
||||
// save table route data into meta store
|
||||
let table_route_value = TableRouteValue {
|
||||
peers: peers.clone(),
|
||||
table_route: Some(table_route.clone()),
|
||||
};
|
||||
put_into_store(&ctx.kv_store, table_route_key, table_route_value).await?;
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(RouteResponse {
|
||||
header,
|
||||
@@ -87,112 +170,89 @@ async fn handle_create(
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::router_server::Router;
|
||||
use api::v1::meta::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use super::*;
|
||||
use crate::metasrv::MetaSrvOptions;
|
||||
use crate::selector::{Namespace, Selector};
|
||||
use crate::service::store::noop::NoopKvStore;
|
||||
|
||||
#[should_panic]
|
||||
#[tokio::test]
|
||||
async fn test_handle_route() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
|
||||
let req = RouteRequest {
|
||||
header: Some(RequestHeader::new((1, 1))),
|
||||
table_names: vec![
|
||||
TableName {
|
||||
catalog_name: "catalog1".to_string(),
|
||||
schema_name: "schema1".to_string(),
|
||||
table_name: "table1".to_string(),
|
||||
},
|
||||
TableName {
|
||||
catalog_name: "catalog1".to_string(),
|
||||
schema_name: "schema1".to_string(),
|
||||
table_name: "table2".to_string(),
|
||||
},
|
||||
TableName {
|
||||
catalog_name: "catalog1".to_string(),
|
||||
schema_name: "schema1".to_string(),
|
||||
table_name: "table3".to_string(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let _res = meta_srv.route(req.into_request()).await.unwrap();
|
||||
}
|
||||
|
||||
struct MockSelector;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Selector for MockSelector {
|
||||
type Context = Context;
|
||||
type Output = Vec<Peer>;
|
||||
|
||||
async fn select(&self, _ns: Namespace, _ctx: &Self::Context) -> Result<Self::Output> {
|
||||
Ok(vec![
|
||||
Peer {
|
||||
id: 0,
|
||||
addr: "127.0.0.1:3000".to_string(),
|
||||
},
|
||||
Peer {
|
||||
id: 1,
|
||||
addr: "127.0.0.1:3001".to_string(),
|
||||
},
|
||||
])
|
||||
async fn fetch_tables(
|
||||
kv_store: &KvStoreRef,
|
||||
keys: impl Iterator<Item = TableGlobalKey>,
|
||||
) -> Result<Vec<(TableGlobalValue, TableRouteValue)>> {
|
||||
let mut tables = vec![];
|
||||
// Maybe we can optimize the for loop in the future, but in general,
|
||||
// there won't be many keys, in fact, there is usually just one.
|
||||
for tk in keys {
|
||||
let tv = get_table_global_value(kv_store, &tk).await?;
|
||||
if tv.is_none() {
|
||||
warn!("Table global value is absent: {}", tk);
|
||||
continue;
|
||||
}
|
||||
let tv = tv.unwrap();
|
||||
|
||||
let table_id = tv.id as u64;
|
||||
let tr_key = TableRouteKey::with_table_global_key(table_id, &tk);
|
||||
let tr = get_table_route_value(kv_store, &tr_key).await?;
|
||||
|
||||
tables.push((tv, tr));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_create() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let table_name = TableName {
|
||||
catalog_name: "test_catalog".to_string(),
|
||||
schema_name: "test_db".to_string(),
|
||||
table_name: "table1".to_string(),
|
||||
};
|
||||
let p0 = Partition {
|
||||
column_list: vec![b"col1".to_vec(), b"col2".to_vec()],
|
||||
value_list: vec![b"v1".to_vec(), b"v2".to_vec()],
|
||||
};
|
||||
let p1 = Partition {
|
||||
column_list: vec![b"col1".to_vec(), b"col2".to_vec()],
|
||||
value_list: vec![b"v11".to_vec(), b"v22".to_vec()],
|
||||
};
|
||||
let req = CreateRequest {
|
||||
header: Some(RequestHeader::new((1, 1))),
|
||||
table_name: Some(table_name),
|
||||
partitions: vec![p0, p1],
|
||||
};
|
||||
let ctx = Context {
|
||||
datanode_lease_secs: 10,
|
||||
kv_store,
|
||||
};
|
||||
let selector = Arc::new(MockSelector {});
|
||||
let res = handle_create(req, ctx, selector).await.unwrap();
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
Peer {
|
||||
id: 0,
|
||||
addr: "127.0.0.1:3000".to_string(),
|
||||
},
|
||||
Peer {
|
||||
id: 1,
|
||||
addr: "127.0.0.1:3001".to_string(),
|
||||
},
|
||||
],
|
||||
res.peers
|
||||
);
|
||||
assert_eq!(1, res.table_routes.len());
|
||||
assert_eq!(2, res.table_routes.get(0).unwrap().region_routes.len());
|
||||
async fn get_table_route_value(
|
||||
kv_store: &KvStoreRef,
|
||||
key: &TableRouteKey<'_>,
|
||||
) -> Result<TableRouteValue> {
|
||||
let tr = get_from_store(kv_store, key.key().into_bytes())
|
||||
.await?
|
||||
.context(error::TableRouteNotFoundSnafu { key: key.key() })?;
|
||||
let tr: TableRouteValue = tr
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.context(error::DecodeTableRouteSnafu)?;
|
||||
|
||||
Ok(tr)
|
||||
}
|
||||
|
||||
async fn get_table_global_value(
|
||||
kv_store: &KvStoreRef,
|
||||
key: &TableGlobalKey,
|
||||
) -> Result<Option<TableGlobalValue>> {
|
||||
let tg_key = format!("{}", key).into_bytes();
|
||||
let tv = get_from_store(kv_store, tg_key).await?;
|
||||
match tv {
|
||||
Some(tv) => {
|
||||
let tv = TableGlobalValue::parse(&String::from_utf8_lossy(&tv))
|
||||
.context(error::InvalidCatalogValueSnafu)?;
|
||||
Ok(Some(tv))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn put_into_store(
|
||||
kv_store: &KvStoreRef,
|
||||
key: impl Into<Vec<u8>>,
|
||||
value: impl Into<Vec<u8>>,
|
||||
) -> Result<()> {
|
||||
let key = key.into();
|
||||
let value = value.into();
|
||||
let put_req = PutRequest {
|
||||
key,
|
||||
value,
|
||||
..Default::default()
|
||||
};
|
||||
let _ = kv_store.put(put_req).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_from_store(kv_store: &KvStoreRef, key: Vec<u8>) -> Result<Option<Vec<u8>>> {
|
||||
let req = RangeRequest {
|
||||
key,
|
||||
..Default::default()
|
||||
};
|
||||
let res = kv_store.range(req).await?;
|
||||
let mut kvs = res.kvs;
|
||||
if kvs.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(kvs.pop().unwrap().value))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pub mod etcd;
|
||||
pub mod kv;
|
||||
pub mod memory;
|
||||
pub mod noop;
|
||||
|
||||
use api::v1::meta::store_server;
|
||||
use api::v1::meta::BatchPutRequest;
|
||||
@@ -74,11 +73,11 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::metasrv::MetaSrvOptions;
|
||||
use crate::service::store::noop::NoopKvStore;
|
||||
use crate::service::store::memory::MemStore;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_range() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
let req = RangeRequest::default();
|
||||
let res = meta_srv.range(req.into_request()).await;
|
||||
@@ -88,7 +87,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
let req = PutRequest::default();
|
||||
let res = meta_srv.put(req.into_request()).await;
|
||||
@@ -98,7 +97,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_batch_put() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
let req = BatchPutRequest::default();
|
||||
let res = meta_srv.batch_put(req.into_request()).await;
|
||||
@@ -108,7 +107,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_compare_and_put() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
let req = CompareAndPutRequest::default();
|
||||
let res = meta_srv.compare_and_put(req.into_request()).await;
|
||||
@@ -118,7 +117,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_range() {
|
||||
let kv_store = Arc::new(NoopKvStore {});
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let meta_srv = MetaSrv::new(MetaSrvOptions::default(), kv_store, None).await;
|
||||
let req = DeleteRangeRequest::default();
|
||||
let res = meta_srv.delete_range(req.into_request()).await;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
@@ -146,19 +146,26 @@ impl KvStore for MemStore {
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let prev_val = memory.get(&key);
|
||||
let (success, prev_kv) = match memory.entry(key) {
|
||||
Entry::Vacant(e) => {
|
||||
let success = expect.is_empty();
|
||||
if success {
|
||||
e.insert(value);
|
||||
}
|
||||
(success, None)
|
||||
}
|
||||
Entry::Occupied(mut e) => {
|
||||
let key = e.key().clone();
|
||||
let prev_val = e.get().clone();
|
||||
let success = prev_val == expect;
|
||||
if success {
|
||||
e.insert(value);
|
||||
}
|
||||
(success, Some((key, prev_val)))
|
||||
}
|
||||
};
|
||||
|
||||
let success = prev_val
|
||||
.map(|v| expect.cmp(v) == Ordering::Equal)
|
||||
.unwrap_or(false | expect.is_empty());
|
||||
let prev_kv = prev_val.map(|v| KeyValue {
|
||||
key: key.clone(),
|
||||
value: v.clone(),
|
||||
});
|
||||
|
||||
if success {
|
||||
memory.insert(key, value);
|
||||
}
|
||||
let prev_kv = prev_kv.map(|(key, value)| KeyValue { key, value });
|
||||
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
use api::v1::meta::BatchPutRequest;
|
||||
use api::v1::meta::BatchPutResponse;
|
||||
use api::v1::meta::CompareAndPutRequest;
|
||||
use api::v1::meta::CompareAndPutResponse;
|
||||
use api::v1::meta::DeleteRangeRequest;
|
||||
use api::v1::meta::DeleteRangeResponse;
|
||||
use api::v1::meta::PutRequest;
|
||||
use api::v1::meta::PutResponse;
|
||||
use api::v1::meta::RangeRequest;
|
||||
use api::v1::meta::RangeResponse;
|
||||
|
||||
use super::kv::KvStore;
|
||||
use crate::error::Result;
|
||||
|
||||
/// A noop kv_store which only for test
|
||||
// TODO(jiachun): Add a test feature
|
||||
#[derive(Clone)]
|
||||
pub struct NoopKvStore;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvStore for NoopKvStore {
|
||||
async fn range(&self, _req: RangeRequest) -> Result<RangeResponse> {
|
||||
Ok(RangeResponse::default())
|
||||
}
|
||||
|
||||
async fn put(&self, _req: PutRequest) -> Result<PutResponse> {
|
||||
Ok(PutResponse::default())
|
||||
}
|
||||
|
||||
async fn batch_put(&self, _req: BatchPutRequest) -> Result<BatchPutResponse> {
|
||||
Ok(BatchPutResponse::default())
|
||||
}
|
||||
|
||||
async fn compare_and_put(&self, _req: CompareAndPutRequest) -> Result<CompareAndPutResponse> {
|
||||
Ok(CompareAndPutResponse::default())
|
||||
}
|
||||
|
||||
async fn delete_range(&self, _req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
|
||||
Ok(DeleteRangeResponse::default())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use catalog::local::{MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::local::{MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::{CatalogList, CatalogProvider, SchemaProvider};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_query::Output;
|
||||
@@ -18,7 +18,7 @@ use table::test_util::MemTable;
|
||||
pub fn create_query_engine() -> Arc<dyn QueryEngine> {
|
||||
let schema_provider = Arc::new(MemorySchemaProvider::new());
|
||||
let catalog_provider = Arc::new(MemoryCatalogProvider::new());
|
||||
let catalog_list = Arc::new(MemoryCatalogList::default());
|
||||
let catalog_list = Arc::new(MemoryCatalogManager::default());
|
||||
|
||||
let mut column_schemas = vec![];
|
||||
let mut columns = vec![];
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use catalog::local::{MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::local::{MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::{CatalogList, CatalogProvider, SchemaProvider};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_function::scalars::aggregate::AggregateFunctionMeta;
|
||||
@@ -242,7 +242,7 @@ fn new_query_engine_factory(table: MemTable) -> QueryEngineFactory {
|
||||
|
||||
let schema_provider = Arc::new(MemorySchemaProvider::new());
|
||||
let catalog_provider = Arc::new(MemoryCatalogProvider::new());
|
||||
let catalog_list = Arc::new(MemoryCatalogList::default());
|
||||
let catalog_list = Arc::new(MemoryCatalogManager::default());
|
||||
|
||||
schema_provider.register_table(table_name, table).unwrap();
|
||||
catalog_provider
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
mod function;
|
||||
use catalog::local::{MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::local::{MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::{CatalogList, CatalogProvider, SchemaProvider};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_query::Output;
|
||||
@@ -114,7 +114,7 @@ fn create_correctness_engine() -> Arc<dyn QueryEngine> {
|
||||
// create engine
|
||||
let schema_provider = Arc::new(MemorySchemaProvider::new());
|
||||
let catalog_provider = Arc::new(MemoryCatalogProvider::new());
|
||||
let catalog_list = Arc::new(MemoryCatalogList::default());
|
||||
let catalog_list = Arc::new(MemoryCatalogManager::default());
|
||||
|
||||
let mut column_schemas = vec![];
|
||||
let mut columns = vec![];
|
||||
|
||||
@@ -3,7 +3,7 @@ mod pow;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow::array::UInt32Array;
|
||||
use catalog::local::{MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::local::{MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::{CatalogList, CatalogProvider, SchemaProvider};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_query::prelude::{create_udf, make_scalar_function, Volatility};
|
||||
@@ -150,7 +150,7 @@ async fn test_udf() -> Result<()> {
|
||||
fn create_query_engine() -> Arc<dyn QueryEngine> {
|
||||
let schema_provider = Arc::new(MemorySchemaProvider::new());
|
||||
let catalog_provider = Arc::new(MemoryCatalogProvider::new());
|
||||
let catalog_list = Arc::new(MemoryCatalogList::default());
|
||||
let catalog_list = Arc::new(MemoryCatalogManager::default());
|
||||
|
||||
// create table with primitives, and all columns' length are even
|
||||
let mut column_schemas = vec![];
|
||||
|
||||
@@ -15,7 +15,7 @@ use datatypes::vectors::Helper as HelperVec;
|
||||
use rustpython_vm::builtins::PyList;
|
||||
use rustpython_vm::pymodule;
|
||||
use rustpython_vm::{
|
||||
builtins::{PyBaseExceptionRef, PyBool, PyFloat, PyInt},
|
||||
builtins::{PyBaseExceptionRef, PyBool, PyFloat, PyInt, PyStr},
|
||||
AsObject, PyObjectRef, PyPayload, PyResult, VirtualMachine,
|
||||
};
|
||||
|
||||
@@ -50,14 +50,15 @@ fn collect_diff_types_string(values: &[ScalarValue], ty: &DataType) -> String {
|
||||
///
|
||||
/// supported scalar are(leftside is python data type, right side is rust type):
|
||||
///
|
||||
/// | Python | Rust |
|
||||
/// | ------ | ---- |
|
||||
/// | integer| i64 |
|
||||
/// | float | f64 |
|
||||
/// | bool | bool |
|
||||
/// | vector | array|
|
||||
/// | Python | Rust |
|
||||
/// | ------ | ------ |
|
||||
/// | integer| i64 |
|
||||
/// | float | f64 |
|
||||
/// | str | String |
|
||||
/// | bool | bool |
|
||||
/// | vector | array |
|
||||
/// | list | `ScalarValue::List` |
|
||||
fn try_into_columnar_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<DFColValue> {
|
||||
pub fn try_into_columnar_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<DFColValue> {
|
||||
if is_instance::<PyVector>(&obj, vm) {
|
||||
let ret = obj
|
||||
.payload::<PyVector>()
|
||||
@@ -67,6 +68,9 @@ fn try_into_columnar_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<DF
|
||||
// Note that a `PyBool` is also a `PyInt`, so check if it is a bool first to get a more precise type
|
||||
let ret = obj.try_into_value::<bool>(vm)?;
|
||||
Ok(DFColValue::Scalar(ScalarValue::Boolean(Some(ret))))
|
||||
} else if is_instance::<PyStr>(&obj, vm) {
|
||||
let ret = obj.try_into_value::<String>(vm)?;
|
||||
Ok(DFColValue::Scalar(ScalarValue::Utf8(Some(ret))))
|
||||
} else if is_instance::<PyInt>(&obj, vm) {
|
||||
let ret = obj.try_into_value::<i64>(vm)?;
|
||||
Ok(DFColValue::Scalar(ScalarValue::Int64(Some(ret))))
|
||||
@@ -92,10 +96,10 @@ fn try_into_columnar_value(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<DF
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
if ret.is_empty() {
|
||||
//TODO(dennis): empty list, we set type as f64.
|
||||
// TODO(dennis): empty list, we set type as null.
|
||||
return Ok(DFColValue::Scalar(ScalarValue::List(
|
||||
None,
|
||||
Box::new(DataType::Float64),
|
||||
Box::new(DataType::Null),
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This is the file for UDF&UDAF binding from datafusion,
|
||||
// This is the file for UDF&UDAF binding from datafusion,
|
||||
// including most test for those function(except ApproxMedian which datafusion didn't implement)
|
||||
// check src/script/builtins/test.rs::run_builtin_fn_testcases() for more information
|
||||
[
|
||||
@@ -632,7 +632,7 @@ variance_pop(values)"#,
|
||||
ty: Float64
|
||||
))
|
||||
),
|
||||
|
||||
|
||||
|
||||
// GrepTime's own UDF
|
||||
TestCase(
|
||||
@@ -782,7 +782,7 @@ sin(num)"#,
|
||||
script: r#"
|
||||
from greptime import *
|
||||
sin(num)"#,
|
||||
expect: Err("Can't cast object of type str into vector or scalar")
|
||||
expect: Err("TypeError: Can't cast type Utf8 to Float64")
|
||||
),
|
||||
TestCase(
|
||||
input: {},
|
||||
|
||||
@@ -13,7 +13,7 @@ use datatypes::arrow::compute::cast::CastOptions;
|
||||
use datatypes::arrow::datatypes::{DataType, Field, Schema as ArrowSchema};
|
||||
use datatypes::schema::Schema;
|
||||
use datatypes::vectors::Helper;
|
||||
use datatypes::vectors::{BooleanVector, Vector, VectorRef};
|
||||
use datatypes::vectors::{BooleanVector, StringVector, Vector, VectorRef};
|
||||
use rustpython_bytecode::CodeObject;
|
||||
use rustpython_vm as vm;
|
||||
use rustpython_vm::{class::PyClassImpl, AsObject};
|
||||
@@ -186,10 +186,11 @@ fn try_into_py_vector(fetch_args: Vec<ArrayRef>) -> Result<Vec<PyVector>> {
|
||||
DataType::Int16 => try_into_vector::<i16>(arg)?,
|
||||
DataType::Int32 => try_into_vector::<i32>(arg)?,
|
||||
DataType::Int64 => try_into_vector::<i64>(arg)?,
|
||||
DataType::Utf8 => {
|
||||
Arc::new(StringVector::try_from_arrow_array(arg).context(TypeCastSnafu)?) as _
|
||||
}
|
||||
DataType::Boolean => {
|
||||
let v: VectorRef =
|
||||
Arc::new(BooleanVector::try_from_arrow_array(arg).context(TypeCastSnafu)?);
|
||||
v
|
||||
Arc::new(BooleanVector::try_from_arrow_array(arg).context(TypeCastSnafu)?) as _
|
||||
}
|
||||
_ => {
|
||||
return ret_other_error_with(format!(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use common_error::prelude::{ErrorCompat, ErrorExt, StatusCode};
|
||||
use console::{style, Style};
|
||||
use datafusion::error::DataFusionError;
|
||||
use datatypes::arrow::error::ArrowError;
|
||||
use datatypes::error::Error as DataTypeError;
|
||||
use query::error::Error as QueryError;
|
||||
@@ -50,6 +51,12 @@ pub enum Error {
|
||||
source: ArrowError,
|
||||
},
|
||||
|
||||
#[snafu(display("DataFusion error: {}", source))]
|
||||
DataFusion {
|
||||
backtrace: Backtrace,
|
||||
source: DataFusionError,
|
||||
},
|
||||
|
||||
/// errors in coprocessors' parse check for types and etc.
|
||||
#[snafu(display("Coprocessor error: {} {}.", reason,
|
||||
if let Some(loc) = loc{
|
||||
@@ -93,9 +100,10 @@ impl From<QueryError> for Error {
|
||||
impl ErrorExt for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Error::Arrow { .. } | Error::PyRuntime { .. } | Error::Other { .. } => {
|
||||
StatusCode::Internal
|
||||
}
|
||||
Error::DataFusion { .. }
|
||||
| Error::Arrow { .. }
|
||||
| Error::PyRuntime { .. }
|
||||
| Error::Other { .. } => StatusCode::Internal,
|
||||
|
||||
Error::RecordBatch { source } => source.status_code(),
|
||||
Error::DatabaseQuery { source } => source.status_code(),
|
||||
|
||||
@@ -362,6 +362,39 @@ def a(cpu: vector[f32], mem: vector[f64])->(vector[f64|None],
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
name: "constant_list",
|
||||
code: r#"
|
||||
@copr(args=["cpu", "mem"], returns=["what"])
|
||||
def a(cpu: vector[f32], mem: vector[f64]):
|
||||
return ["apple" ,"banana", "cherry"]
|
||||
"#,
|
||||
predicate: ExecIsOk(
|
||||
fields: [
|
||||
(
|
||||
datatype: Some(Utf8),
|
||||
is_nullable: false,
|
||||
),
|
||||
],
|
||||
columns: [
|
||||
(
|
||||
ty: Utf8,
|
||||
len: 3
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
name: "constant_list_different_type",
|
||||
code: r#"
|
||||
@copr(args=["cpu", "mem"], returns=["what"])
|
||||
def a(cpu: vector[f32], mem: vector[f64]):
|
||||
return ["apple" ,3, "cherry"]
|
||||
"#,
|
||||
predicate: ExecIsErr(
|
||||
reason: "All elements in a list should be same type to cast to Datafusion list!",
|
||||
)
|
||||
),
|
||||
(
|
||||
// expect 4 vector ,found 5
|
||||
name: "ret_nums_wrong",
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use datafusion::arrow::array::{ArrayRef, BooleanArray, PrimitiveArray};
|
||||
use rustpython_vm::builtins::{PyBool, PyFloat, PyInt};
|
||||
use datafusion::arrow::array::{ArrayRef, BooleanArray, NullArray, PrimitiveArray, Utf8Array};
|
||||
use datafusion_common::ScalarValue;
|
||||
use datafusion_expr::ColumnarValue as DFColValue;
|
||||
use datatypes::arrow::datatypes::DataType;
|
||||
use rustpython_vm::builtins::{PyBool, PyFloat, PyInt, PyList, PyStr};
|
||||
use rustpython_vm::{builtins::PyBaseExceptionRef, PyObjectRef, PyPayload, PyRef, VirtualMachine};
|
||||
use snafu::OptionExt;
|
||||
use snafu::ResultExt;
|
||||
use snafu::{Backtrace, GenerateImplicitData};
|
||||
|
||||
use crate::python::builtins::try_into_columnar_value;
|
||||
use crate::python::error;
|
||||
use crate::python::error::ret_other_error_with;
|
||||
use crate::python::PyVector;
|
||||
@@ -39,6 +44,7 @@ pub fn py_vec_obj_to_array(
|
||||
vm: &VirtualMachine,
|
||||
col_len: usize,
|
||||
) -> Result<ArrayRef, error::Error> {
|
||||
// It's ugly, but we can't find a better way right now.
|
||||
if is_instance::<PyVector>(obj, vm) {
|
||||
let pyv = obj.payload::<PyVector>().with_context(|| {
|
||||
ret_other_error_with(format!("can't cast obj {:?} to PyVector", obj))
|
||||
@@ -66,6 +72,30 @@ pub fn py_vec_obj_to_array(
|
||||
|
||||
let ret = BooleanArray::from_iter(std::iter::repeat(Some(val)).take(col_len));
|
||||
Ok(Arc::new(ret) as _)
|
||||
} else if is_instance::<PyStr>(obj, vm) {
|
||||
let val = obj
|
||||
.to_owned()
|
||||
.try_into_value::<String>(vm)
|
||||
.map_err(|e| format_py_error(e, vm))?;
|
||||
|
||||
let ret = Utf8Array::<i32>::from_iter(std::iter::repeat(Some(val)).take(col_len));
|
||||
Ok(Arc::new(ret) as _)
|
||||
} else if is_instance::<PyList>(obj, vm) {
|
||||
let columnar_value =
|
||||
try_into_columnar_value(obj.clone(), vm).map_err(|e| format_py_error(e, vm))?;
|
||||
|
||||
match columnar_value {
|
||||
DFColValue::Scalar(ScalarValue::List(scalars, _datatype)) => match scalars {
|
||||
Some(scalars) => {
|
||||
let array = ScalarValue::iter_to_array(scalars.into_iter())
|
||||
.context(error::DataFusionSnafu)?;
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
None => Ok(Arc::new(NullArray::new(DataType::Null, 0))),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
ret_other_error_with(format!("Expect a vector or a constant, found {:?}", obj)).fail()
|
||||
}
|
||||
|
||||
@@ -37,9 +37,11 @@ serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
snap = "1"
|
||||
table = { path = "../table" }
|
||||
tokio = { version = "1.20", features = ["full"] }
|
||||
tokio-stream = { version = "0.1", features = ["net"] }
|
||||
tonic = "0.8"
|
||||
tonic-reflection = "0.5"
|
||||
tower = { version = "0.4", features = ["full"] }
|
||||
tower-http = { version = "0.3", features = ["full"] }
|
||||
|
||||
|
||||
@@ -154,7 +154,13 @@ pub enum Error {
|
||||
InvalidPromRemoteReadQueryResult { msg: String, backtrace: Backtrace },
|
||||
|
||||
#[snafu(display("Failed to decode region id, source: {}", source))]
|
||||
DecodeRegionId { source: api::DecodeError },
|
||||
DecodeRegionNumber { source: api::DecodeError },
|
||||
|
||||
#[snafu(display("Failed to build gRPC reflection service, source: {}", source))]
|
||||
GrpcReflectionService {
|
||||
source: tonic_reflection::server::Error,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -172,7 +178,8 @@ impl ErrorExt for Error {
|
||||
| StartGrpc { .. }
|
||||
| AlreadyStarted { .. }
|
||||
| InvalidPromRemoteReadQueryResult { .. }
|
||||
| TcpBind { .. } => StatusCode::Internal,
|
||||
| TcpBind { .. }
|
||||
| GrpcReflectionService { .. } => StatusCode::Internal,
|
||||
|
||||
InsertScript { source, .. }
|
||||
| ExecuteScript { source, .. }
|
||||
@@ -189,7 +196,7 @@ impl ErrorExt for Error {
|
||||
| DecodePromRemoteRequest { .. }
|
||||
| DecompressPromRemoteRequest { .. }
|
||||
| InvalidPromRemoteRequest { .. }
|
||||
| DecodeRegionId { .. }
|
||||
| DecodeRegionNumber { .. }
|
||||
| TimePrecision { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
InfluxdbLinesWrite { source, .. } => source.status_code(),
|
||||
|
||||
@@ -16,7 +16,7 @@ use tokio::sync::Mutex;
|
||||
use tokio_stream::wrappers::TcpListenerStream;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::error::{AlreadyStartedSnafu, Result, StartGrpcSnafu, TcpBindSnafu};
|
||||
use crate::error::{self, AlreadyStartedSnafu, Result, StartGrpcSnafu, TcpBindSnafu};
|
||||
use crate::grpc::handler::BatchHandler;
|
||||
use crate::query_handler::{GrpcAdminHandlerRef, GrpcQueryHandlerRef};
|
||||
use crate::server::Server;
|
||||
@@ -104,9 +104,16 @@ impl Server for GrpcServer {
|
||||
(listener, addr)
|
||||
};
|
||||
|
||||
let reflection_service = tonic_reflection::server::Builder::configure()
|
||||
.register_encoded_file_descriptor_set(api::v1::GREPTIME_FD_SET)
|
||||
.with_service_name("greptime.v1.Greptime")
|
||||
.build()
|
||||
.context(error::GrpcReflectionServiceSnafu)?;
|
||||
|
||||
// Would block to serve requests.
|
||||
tonic::transport::Server::builder()
|
||||
.add_service(self.create_service())
|
||||
.add_service(reflection_service)
|
||||
.serve_with_incoming_shutdown(TcpListenerStream::new(listener), rx.map(drop))
|
||||
.await
|
||||
.context(StartGrpcSnafu)?;
|
||||
|
||||
@@ -7,8 +7,10 @@ use api::v1::{
|
||||
use common_grpc::writer::{LinesWriter, Precision};
|
||||
use influxdb_line_protocol::{parse_lines, FieldValue};
|
||||
use snafu::ResultExt;
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use crate::error::{Error, InfluxdbLineProtocolSnafu, InfluxdbLinesWriteSnafu};
|
||||
use crate::line_writer::LineWriter;
|
||||
|
||||
pub const INFLUXDB_TIMESTAMP_COLUMN_NAME: &str = "ts";
|
||||
pub const DEFAULT_TIME_PRECISION: Precision = Precision::NANOSECOND;
|
||||
@@ -20,6 +22,60 @@ pub struct InfluxdbRequest {
|
||||
|
||||
type TableName = String;
|
||||
|
||||
impl TryFrom<&InfluxdbRequest> for Vec<InsertRequest> {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &InfluxdbRequest) -> Result<Self, Self::Error> {
|
||||
let lines = parse_lines(&value.lines)
|
||||
.collect::<influxdb_line_protocol::Result<Vec<_>>>()
|
||||
.context(InfluxdbLineProtocolSnafu)?;
|
||||
let line_len = lines.len();
|
||||
let mut writers: HashMap<TableName, LineWriter> = HashMap::new();
|
||||
|
||||
for line in lines {
|
||||
let table_name = line.series.measurement;
|
||||
let writer = writers
|
||||
.entry(table_name.to_string())
|
||||
.or_insert_with(|| LineWriter::with_lines(table_name, line_len));
|
||||
|
||||
let tags = line.series.tag_set;
|
||||
if let Some(tags) = tags {
|
||||
for (k, v) in tags {
|
||||
writer.write_tag(k.as_str(), v.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
let fields = line.field_set;
|
||||
for (k, v) in fields {
|
||||
let column_name = k.as_str();
|
||||
match v {
|
||||
FieldValue::I64(value) => writer.write_i64(column_name, value),
|
||||
FieldValue::U64(value) => writer.write_u64(column_name, value),
|
||||
FieldValue::F64(value) => writer.write_f64(column_name, value),
|
||||
FieldValue::String(value) => writer.write_string(column_name, value.as_str()),
|
||||
FieldValue::Boolean(value) => writer.write_bool(column_name, value),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(timestamp) = line.timestamp {
|
||||
let precision = if let Some(val) = &value.precision {
|
||||
*val
|
||||
} else {
|
||||
DEFAULT_TIME_PRECISION
|
||||
};
|
||||
writer.write_ts(INFLUXDB_TIMESTAMP_COLUMN_NAME, (timestamp, precision));
|
||||
}
|
||||
|
||||
writer.commit();
|
||||
}
|
||||
Ok(writers
|
||||
.into_iter()
|
||||
.map(|(_, writer)| writer.finish())
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(fys): will remove in the future.
|
||||
impl TryFrom<&InfluxdbRequest> for Vec<InsertExpr> {
|
||||
type Error = Error;
|
||||
|
||||
@@ -106,7 +162,7 @@ impl TryFrom<&InfluxdbRequest> for Vec<InsertExpr> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Deref;
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use api::v1::{
|
||||
codec::InsertBatch,
|
||||
@@ -115,6 +171,9 @@ mod tests {
|
||||
Column, ColumnDataType, InsertExpr,
|
||||
};
|
||||
use common_base::BitVec;
|
||||
use common_time::{timestamp::TimeUnit, Timestamp};
|
||||
use datatypes::{value::Value, vectors::Vector};
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use crate::influxdb::InfluxdbRequest;
|
||||
|
||||
@@ -124,6 +183,30 @@ mod tests {
|
||||
monitor1,host=host1 cpu=66.6,memory=1024 1663840496100023100
|
||||
monitor1,host=host2 memory=1027 1663840496400340001
|
||||
monitor2,host=host3 cpu=66.5 1663840496100023102
|
||||
monitor2,host=host4 cpu=66.3,memory=1029 1663840496400340003";
|
||||
|
||||
let influxdb_req = &InfluxdbRequest {
|
||||
precision: None,
|
||||
lines: lines.to_string(),
|
||||
};
|
||||
let insert_reqs: Vec<InsertRequest> = influxdb_req.try_into().unwrap();
|
||||
|
||||
for insert_req in insert_reqs {
|
||||
match &insert_req.table_name[..] {
|
||||
"monitor1" => assert_table_1(&insert_req),
|
||||
"monitor2" => assert_table_2(&insert_req),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(fys): will remove in the future.
|
||||
#[test]
|
||||
fn test_convert_influxdb_lines_1() {
|
||||
let lines = r"
|
||||
monitor1,host=host1 cpu=66.6,memory=1024 1663840496100023100
|
||||
monitor1,host=host2 memory=1027 1663840496400340001
|
||||
monitor2,host=host3 cpu=66.5 1663840496100023102
|
||||
monitor2,host=host4 cpu=66.3,memory=1029 1663840496400340003";
|
||||
|
||||
let influxdb_req = &InfluxdbRequest {
|
||||
@@ -150,6 +233,77 @@ monitor2,host=host4 cpu=66.3,memory=1029 1663840496400340003";
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_table_1(insert_req: &InsertRequest) {
|
||||
let table_name = &insert_req.table_name;
|
||||
assert_eq!("monitor1", table_name);
|
||||
|
||||
let columns = &insert_req.columns_values;
|
||||
|
||||
let host = columns.get("host").unwrap();
|
||||
let expected: Vec<Value> = vec!["host1".into(), "host2".into()];
|
||||
assert_vector(&expected, host);
|
||||
|
||||
let cpu = columns.get("cpu").unwrap();
|
||||
let expected: Vec<Value> = vec![66.6.into(), Value::Null];
|
||||
assert_vector(&expected, cpu);
|
||||
|
||||
let memory = columns.get("memory").unwrap();
|
||||
let expected: Vec<Value> = vec![1024.0.into(), 1027.0.into()];
|
||||
assert_vector(&expected, memory);
|
||||
|
||||
let ts = columns.get("ts").unwrap();
|
||||
let expected: Vec<Value> = vec![
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(
|
||||
1663840496100,
|
||||
TimeUnit::Millisecond,
|
||||
)),
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(
|
||||
1663840496400,
|
||||
TimeUnit::Millisecond,
|
||||
)),
|
||||
];
|
||||
assert_vector(&expected, ts);
|
||||
}
|
||||
|
||||
fn assert_table_2(insert_req: &InsertRequest) {
|
||||
let table_name = &insert_req.table_name;
|
||||
assert_eq!("monitor2", table_name);
|
||||
|
||||
let columns = &insert_req.columns_values;
|
||||
|
||||
let host = columns.get("host").unwrap();
|
||||
let expected: Vec<Value> = vec!["host3".into(), "host4".into()];
|
||||
assert_vector(&expected, host);
|
||||
|
||||
let cpu = columns.get("cpu").unwrap();
|
||||
let expected: Vec<Value> = vec![66.5.into(), 66.3.into()];
|
||||
assert_vector(&expected, cpu);
|
||||
|
||||
let memory = columns.get("memory").unwrap();
|
||||
let expected: Vec<Value> = vec![Value::Null, 1029.0.into()];
|
||||
assert_vector(&expected, memory);
|
||||
|
||||
let ts = columns.get("ts").unwrap();
|
||||
let expected: Vec<Value> = vec![
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(
|
||||
1663840496100,
|
||||
TimeUnit::Millisecond,
|
||||
)),
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(
|
||||
1663840496400,
|
||||
TimeUnit::Millisecond,
|
||||
)),
|
||||
];
|
||||
assert_vector(&expected, ts);
|
||||
}
|
||||
|
||||
fn assert_vector(expected: &[Value], vector: &Arc<dyn Vector>) {
|
||||
for (idx, expected) in expected.iter().enumerate() {
|
||||
let val = vector.get(idx);
|
||||
assert_eq!(*expected, val);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_monitor_1(insert_batch: &InsertBatch) {
|
||||
let columns = &insert_batch.columns;
|
||||
assert_eq!(4, columns.len());
|
||||
|
||||
@@ -5,6 +5,7 @@ pub mod error;
|
||||
pub mod grpc;
|
||||
pub mod http;
|
||||
pub mod influxdb;
|
||||
pub mod line_writer;
|
||||
pub mod mysql;
|
||||
pub mod opentsdb;
|
||||
pub mod postgres;
|
||||
|
||||
202
src/servers/src/line_writer.rs
Normal file
202
src/servers/src/line_writer.rs
Normal file
@@ -0,0 +1,202 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use common_grpc::writer::{to_ms_ts, Precision};
|
||||
use common_time::{timestamp::TimeUnit::Millisecond, Timestamp};
|
||||
use datatypes::{
|
||||
prelude::ConcreteDataType,
|
||||
types::TimestampType,
|
||||
value::Value,
|
||||
vectors::{VectorBuilder, VectorRef},
|
||||
};
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
type ColumnLen = usize;
|
||||
type ColumnName = String;
|
||||
|
||||
pub struct LineWriter {
|
||||
table_name: String,
|
||||
expected_rows: usize,
|
||||
current_rows: usize,
|
||||
columns_builders: HashMap<ColumnName, (VectorBuilder, ColumnLen)>,
|
||||
}
|
||||
|
||||
impl LineWriter {
|
||||
pub fn with_lines(table_name: impl Into<String>, lines: usize) -> Self {
|
||||
Self {
|
||||
table_name: table_name.into(),
|
||||
expected_rows: lines,
|
||||
current_rows: 0,
|
||||
columns_builders: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_ts(&mut self, column_name: &str, value: (i64, Precision)) {
|
||||
let (val, precision) = value;
|
||||
let datatype = ConcreteDataType::Timestamp(TimestampType { unit: Millisecond });
|
||||
let ts_val = Value::Timestamp(Timestamp::new(to_ms_ts(precision, val), Millisecond));
|
||||
self.write(column_name, datatype, ts_val);
|
||||
}
|
||||
|
||||
pub fn write_tag(&mut self, column_name: &str, value: &str) {
|
||||
self.write(
|
||||
column_name,
|
||||
ConcreteDataType::string_datatype(),
|
||||
Value::String(value.into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn write_u64(&mut self, column_name: &str, value: u64) {
|
||||
self.write(
|
||||
column_name,
|
||||
ConcreteDataType::uint64_datatype(),
|
||||
Value::UInt64(value),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn write_i64(&mut self, column_name: &str, value: i64) {
|
||||
self.write(
|
||||
column_name,
|
||||
ConcreteDataType::int64_datatype(),
|
||||
Value::Int64(value),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn write_f64(&mut self, column_name: &str, value: f64) {
|
||||
self.write(
|
||||
column_name,
|
||||
ConcreteDataType::float64_datatype(),
|
||||
Value::Float64(value.into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn write_string(&mut self, column_name: &str, value: &str) {
|
||||
self.write(
|
||||
column_name,
|
||||
ConcreteDataType::string_datatype(),
|
||||
Value::String(value.into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn write_bool(&mut self, column_name: &str, value: bool) {
|
||||
self.write(
|
||||
column_name,
|
||||
ConcreteDataType::boolean_datatype(),
|
||||
Value::Boolean(value),
|
||||
);
|
||||
}
|
||||
|
||||
fn write(&mut self, column_name: &str, datatype: ConcreteDataType, value: Value) {
|
||||
let or_insert = || {
|
||||
let rows = self.current_rows;
|
||||
let mut builder = VectorBuilder::with_capacity(datatype, self.expected_rows);
|
||||
(0..rows).into_iter().for_each(|_| builder.push_null());
|
||||
(builder, rows)
|
||||
};
|
||||
let (builder, column_len) = self
|
||||
.columns_builders
|
||||
.entry(column_name.to_string())
|
||||
.or_insert_with(or_insert);
|
||||
|
||||
builder.push(&value);
|
||||
*column_len += 1;
|
||||
}
|
||||
|
||||
pub fn commit(&mut self) {
|
||||
self.current_rows += 1;
|
||||
self.columns_builders
|
||||
.values_mut()
|
||||
.into_iter()
|
||||
.for_each(|(builder, len)| {
|
||||
if self.current_rows > *len {
|
||||
builder.push(&Value::Null)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn finish(self) -> InsertRequest {
|
||||
let columns_values: HashMap<ColumnName, VectorRef> = self
|
||||
.columns_builders
|
||||
.into_iter()
|
||||
.map(|(column_name, (mut builder, _))| (column_name, builder.finish()))
|
||||
.collect();
|
||||
InsertRequest {
|
||||
table_name: self.table_name,
|
||||
columns_values,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_time::Timestamp;
|
||||
use datatypes::{value::Value, vectors::Vector};
|
||||
|
||||
use crate::line_writer::{LineWriter, Precision};
|
||||
|
||||
#[test]
|
||||
fn test_writer() {
|
||||
let mut writer = LineWriter::with_lines("demo".to_string(), 4);
|
||||
writer.write_ts("ts", (1665893727685, Precision::MILLISECOND));
|
||||
writer.write_tag("host", "host-1");
|
||||
writer.write_i64("memory", 10_i64);
|
||||
writer.commit();
|
||||
|
||||
writer.write_ts("ts", (1665893727686, Precision::MILLISECOND));
|
||||
writer.write_tag("host", "host-2");
|
||||
writer.write_tag("region", "region-2");
|
||||
writer.write_i64("memory", 9_i64);
|
||||
writer.commit();
|
||||
|
||||
writer.write_ts("ts", (1665893727689, Precision::MILLISECOND));
|
||||
writer.write_tag("host", "host-3");
|
||||
writer.write_tag("region", "region-3");
|
||||
writer.write_i64("cpu", 19_i64);
|
||||
writer.commit();
|
||||
|
||||
let insert_request = writer.finish();
|
||||
|
||||
assert_eq!("demo", insert_request.table_name);
|
||||
|
||||
let columns = insert_request.columns_values;
|
||||
assert_eq!(5, columns.len());
|
||||
assert!(columns.contains_key("ts"));
|
||||
assert!(columns.contains_key("host"));
|
||||
assert!(columns.contains_key("memory"));
|
||||
assert!(columns.contains_key("region"));
|
||||
assert!(columns.contains_key("cpu"));
|
||||
|
||||
let ts = columns.get("ts").unwrap();
|
||||
let host = columns.get("host").unwrap();
|
||||
let memory = columns.get("memory").unwrap();
|
||||
let region = columns.get("region").unwrap();
|
||||
let cpu = columns.get("cpu").unwrap();
|
||||
|
||||
let expected: Vec<Value> = vec![
|
||||
Value::Timestamp(Timestamp::from_millis(1665893727685_i64)),
|
||||
Value::Timestamp(Timestamp::from_millis(1665893727686_i64)),
|
||||
Value::Timestamp(Timestamp::from_millis(1665893727689_i64)),
|
||||
];
|
||||
assert_vector(&expected, ts);
|
||||
|
||||
let expected: Vec<Value> = vec!["host-1".into(), "host-2".into(), "host-3".into()];
|
||||
assert_vector(&expected, host);
|
||||
|
||||
let expected: Vec<Value> = vec![10_i64.into(), 9_i64.into(), Value::Null];
|
||||
assert_vector(&expected, memory);
|
||||
|
||||
let expected: Vec<Value> = vec![Value::Null, "region-2".into(), "region-3".into()];
|
||||
assert_vector(&expected, region);
|
||||
|
||||
let expected: Vec<Value> = vec![Value::Null, Value::Null, 19_i64.into()];
|
||||
assert_vector(&expected, cpu);
|
||||
}
|
||||
|
||||
fn assert_vector(expected: &[Value], vector: &Arc<dyn Vector>) {
|
||||
for (idx, expected) in expected.iter().enumerate() {
|
||||
let val = vector.get(idx);
|
||||
assert_eq!(*expected, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,11 @@ use std::collections::HashMap;
|
||||
|
||||
use api::v1::codec::InsertBatch;
|
||||
use api::v1::{column, column::SemanticType, insert_expr, Column, ColumnDataType, InsertExpr};
|
||||
use common_grpc::writer::Precision;
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::line_writer::LineWriter;
|
||||
|
||||
pub const OPENTSDB_TIMESTAMP_COLUMN_NAME: &str = "greptime_timestamp";
|
||||
pub const OPENTSDB_VALUE_COLUMN_NAME: &str = "greptime_value";
|
||||
@@ -112,6 +115,23 @@ impl DataPoint {
|
||||
self.value
|
||||
}
|
||||
|
||||
pub fn as_insert_request(&self) -> InsertRequest {
|
||||
let mut line_writer = LineWriter::with_lines(self.metric.clone(), 1);
|
||||
line_writer.write_ts(
|
||||
OPENTSDB_TIMESTAMP_COLUMN_NAME,
|
||||
(self.ts_millis(), Precision::MILLISECOND),
|
||||
);
|
||||
|
||||
line_writer.write_f64(OPENTSDB_VALUE_COLUMN_NAME, self.value);
|
||||
|
||||
for (tagk, tagv) in self.tags.iter() {
|
||||
line_writer.write_tag(tagk, tagv);
|
||||
}
|
||||
line_writer.commit();
|
||||
line_writer.finish()
|
||||
}
|
||||
|
||||
// TODO(fys): will remove in the future.
|
||||
pub fn as_grpc_insert(&self) -> InsertExpr {
|
||||
let mut columns = Vec::with_capacity(2 + self.tags.len());
|
||||
|
||||
@@ -180,6 +200,12 @@ impl DataPoint {
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_time::{timestamp::TimeUnit, Timestamp};
|
||||
use datatypes::value::Value;
|
||||
use datatypes::vectors::Vector;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -239,6 +265,43 @@ mod test {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_insert_request() {
|
||||
let data_point = DataPoint {
|
||||
metric: "my_metric_1".to_string(),
|
||||
ts_millis: 1000,
|
||||
value: 1.0,
|
||||
tags: vec![
|
||||
("tagk1".to_string(), "tagv1".to_string()),
|
||||
("tagk2".to_string(), "tagv2".to_string()),
|
||||
],
|
||||
};
|
||||
let insert_request = data_point.as_insert_request();
|
||||
assert_eq!("my_metric_1", insert_request.table_name);
|
||||
let columns = insert_request.columns_values;
|
||||
assert_eq!(4, columns.len());
|
||||
let ts = columns.get(OPENTSDB_TIMESTAMP_COLUMN_NAME).unwrap();
|
||||
let expected = vec![datatypes::prelude::Value::Timestamp(Timestamp::new(
|
||||
1000,
|
||||
TimeUnit::Millisecond,
|
||||
))];
|
||||
assert_vector(&expected, ts);
|
||||
let val = columns.get(OPENTSDB_VALUE_COLUMN_NAME).unwrap();
|
||||
assert_vector(&[1.0.into()], val);
|
||||
let tagk1 = columns.get("tagk1").unwrap();
|
||||
assert_vector(&["tagv1".into()], tagk1);
|
||||
let tagk2 = columns.get("tagk2").unwrap();
|
||||
assert_vector(&["tagv2".into()], tagk2);
|
||||
}
|
||||
|
||||
fn assert_vector(expected: &[Value], vector: &Arc<dyn Vector>) {
|
||||
for (idx, expected) in expected.iter().enumerate() {
|
||||
let val = vector.get(idx);
|
||||
assert_eq!(*expected, val);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(fys): will remove in the future.
|
||||
#[test]
|
||||
fn test_as_grpc_insert() {
|
||||
let data_point = DataPoint {
|
||||
|
||||
@@ -11,11 +11,14 @@ use api::v1::{
|
||||
codec::SelectResult, column, column::SemanticType, insert_expr, Column, ColumnDataType,
|
||||
InsertExpr,
|
||||
};
|
||||
use common_grpc::writer::Precision::MILLISECOND;
|
||||
use openmetrics_parser::{MetricsExposition, PrometheusType, PrometheusValue};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use snap::raw::{Decoder, Encoder};
|
||||
use table::requests::InsertRequest;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::line_writer::LineWriter;
|
||||
|
||||
const TIMESTAMP_COLUMN_NAME: &str = "greptime_timestamp";
|
||||
const VALUE_COLUMN_NAME: &str = "greptime_value";
|
||||
@@ -275,6 +278,55 @@ pub fn select_result_to_timeseries(
|
||||
Ok(timeseries_map.into_values().collect())
|
||||
}
|
||||
|
||||
/// Cast a remote write request into InsertRequest
|
||||
pub fn write_request_to_insert_reqs(mut request: WriteRequest) -> Result<Vec<InsertRequest>> {
|
||||
let timeseries = std::mem::take(&mut request.timeseries);
|
||||
|
||||
timeseries
|
||||
.into_iter()
|
||||
.map(timeseries_to_insert_request)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn timeseries_to_insert_request(mut timeseries: TimeSeries) -> Result<InsertRequest> {
|
||||
// TODO(dennis): save exemplars into a column
|
||||
let labels = std::mem::take(&mut timeseries.labels);
|
||||
let samples = std::mem::take(&mut timeseries.samples);
|
||||
|
||||
let mut table_name = None;
|
||||
for label in &labels {
|
||||
// The metric name is a special label
|
||||
if label.name == METRIC_NAME_LABEL {
|
||||
table_name = Some(&label.value);
|
||||
}
|
||||
}
|
||||
let table_name = table_name.context(error::InvalidPromRemoteRequestSnafu {
|
||||
msg: "missing '__name__' label in timeseries",
|
||||
})?;
|
||||
|
||||
let row_count = samples.len();
|
||||
let mut line_writer = LineWriter::with_lines(table_name, row_count);
|
||||
|
||||
for sample in samples {
|
||||
let ts_millis = sample.timestamp;
|
||||
let val = sample.value;
|
||||
|
||||
line_writer.write_ts(TIMESTAMP_COLUMN_NAME, (ts_millis, MILLISECOND));
|
||||
line_writer.write_f64(VALUE_COLUMN_NAME, val);
|
||||
|
||||
labels
|
||||
.iter()
|
||||
.filter(|label| label.name != METRIC_NAME_LABEL)
|
||||
.for_each(|label| {
|
||||
line_writer.write_tag(&label.name, &label.value);
|
||||
});
|
||||
|
||||
line_writer.commit();
|
||||
}
|
||||
Ok(line_writer.finish())
|
||||
}
|
||||
|
||||
// TODO(fys): it will remove in the future.
|
||||
/// Cast a remote write request into gRPC's InsertExpr.
|
||||
pub fn write_request_to_insert_exprs(mut request: WriteRequest) -> Result<Vec<InsertExpr>> {
|
||||
let timeseries = std::mem::take(&mut request.timeseries);
|
||||
@@ -285,6 +337,7 @@ pub fn write_request_to_insert_exprs(mut request: WriteRequest) -> Result<Vec<In
|
||||
.collect()
|
||||
}
|
||||
|
||||
// TODO(fys): it will remove in the future.
|
||||
fn timeseries_to_insert_expr(mut timeseries: TimeSeries) -> Result<InsertExpr> {
|
||||
// TODO(dennis): save exemplars into a column
|
||||
let labels = std::mem::take(&mut timeseries.labels);
|
||||
@@ -439,7 +492,12 @@ pub fn mock_timeseries() -> Vec<TimeSeries> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::prometheus::remote::LabelMatcher;
|
||||
use common_time::timestamp::TimeUnit;
|
||||
use common_time::Timestamp;
|
||||
use datatypes::{value::Value, vectors::Vector};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -499,6 +557,91 @@ mod tests {
|
||||
assert_eq!("select * from test where greptime_timestamp>=1000 AND greptime_timestamp<=2000 AND job~'*prom*' AND instance!='localhost' order by greptime_timestamp", sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_request_to_insert_reqs() {
|
||||
let write_request = WriteRequest {
|
||||
timeseries: mock_timeseries(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let reqs = write_request_to_insert_reqs(write_request).unwrap();
|
||||
|
||||
assert_eq!(3, reqs.len());
|
||||
|
||||
let req1 = reqs.get(0).unwrap();
|
||||
assert_eq!("metric1", req1.table_name);
|
||||
|
||||
let columns = &req1.columns_values;
|
||||
let job = columns.get("job").unwrap();
|
||||
let expected: Vec<Value> = vec!["spark".into(), "spark".into()];
|
||||
assert_vector(&expected, job);
|
||||
|
||||
let ts = columns.get(TIMESTAMP_COLUMN_NAME).unwrap();
|
||||
let expected: Vec<Value> = vec![
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(1000, TimeUnit::Millisecond)),
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(2000, TimeUnit::Millisecond)),
|
||||
];
|
||||
assert_vector(&expected, ts);
|
||||
|
||||
let val = columns.get(VALUE_COLUMN_NAME).unwrap();
|
||||
let expected: Vec<Value> = vec![1.0_f64.into(), 2.0_f64.into()];
|
||||
assert_vector(&expected, val);
|
||||
|
||||
let req2 = reqs.get(1).unwrap();
|
||||
assert_eq!("metric2", req2.table_name);
|
||||
|
||||
let columns = &req2.columns_values;
|
||||
let instance = columns.get("instance").unwrap();
|
||||
let expected: Vec<Value> = vec!["test_host1".into(), "test_host1".into()];
|
||||
assert_vector(&expected, instance);
|
||||
|
||||
let idc = columns.get("idc").unwrap();
|
||||
let expected: Vec<Value> = vec!["z001".into(), "z001".into()];
|
||||
assert_vector(&expected, idc);
|
||||
|
||||
let ts = columns.get(TIMESTAMP_COLUMN_NAME).unwrap();
|
||||
let expected: Vec<Value> = vec![
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(1000, TimeUnit::Millisecond)),
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(2000, TimeUnit::Millisecond)),
|
||||
];
|
||||
assert_vector(&expected, ts);
|
||||
|
||||
let val = columns.get(VALUE_COLUMN_NAME).unwrap();
|
||||
let expected: Vec<Value> = vec![3.0_f64.into(), 4.0_f64.into()];
|
||||
assert_vector(&expected, val);
|
||||
|
||||
let req3 = reqs.get(2).unwrap();
|
||||
assert_eq!("metric3", req3.table_name);
|
||||
|
||||
let columns = &req3.columns_values;
|
||||
let idc = columns.get("idc").unwrap();
|
||||
let expected: Vec<Value> = vec!["z002".into(), "z002".into(), "z002".into()];
|
||||
assert_vector(&expected, idc);
|
||||
|
||||
let app = columns.get("app").unwrap();
|
||||
let expected: Vec<Value> = vec!["biz".into(), "biz".into(), "biz".into()];
|
||||
assert_vector(&expected, app);
|
||||
|
||||
let ts = columns.get(TIMESTAMP_COLUMN_NAME).unwrap();
|
||||
let expected: Vec<Value> = vec![
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(1000, TimeUnit::Millisecond)),
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(2000, TimeUnit::Millisecond)),
|
||||
datatypes::prelude::Value::Timestamp(Timestamp::new(3000, TimeUnit::Millisecond)),
|
||||
];
|
||||
assert_vector(&expected, ts);
|
||||
|
||||
let val = columns.get(VALUE_COLUMN_NAME).unwrap();
|
||||
let expected: Vec<Value> = vec![5.0_f64.into(), 6.0_f64.into(), 7.0_f64.into()];
|
||||
assert_vector(&expected, val);
|
||||
}
|
||||
|
||||
fn assert_vector(expected: &[Value], vector: &Arc<dyn Vector>) {
|
||||
for (idx, expected) in expected.iter().enumerate() {
|
||||
let val = vector.get(idx);
|
||||
assert_eq!(*expected, val);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_request_to_insert_exprs() {
|
||||
let write_request = WriteRequest {
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use catalog::local::{MemoryCatalogList, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::local::{MemoryCatalogManager, MemoryCatalogProvider, MemorySchemaProvider};
|
||||
use catalog::{CatalogList, CatalogProvider, SchemaProvider};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_query::Output;
|
||||
@@ -70,7 +70,7 @@ fn create_testing_sql_query_handler(table: MemTable) -> SqlQueryHandlerRef {
|
||||
|
||||
let schema_provider = Arc::new(MemorySchemaProvider::new());
|
||||
let catalog_provider = Arc::new(MemoryCatalogProvider::new());
|
||||
let catalog_list = Arc::new(MemoryCatalogList::default());
|
||||
let catalog_list = Arc::new(MemoryCatalogManager::default());
|
||||
schema_provider.register_table(table_name, table).unwrap();
|
||||
catalog_provider
|
||||
.register_schema(DEFAULT_SCHEMA_NAME.to_string(), schema_provider)
|
||||
|
||||
@@ -10,6 +10,8 @@ pub type ColumnFamilyId = u32;
|
||||
/// Id of the region.
|
||||
pub type RegionId = u64;
|
||||
|
||||
pub type RegionNumber = u32;
|
||||
|
||||
/// A [ColumnDescriptor] contains information to create a column.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Builder)]
|
||||
#[builder(pattern = "owned", build_fn(validate = "Self::validate"))]
|
||||
|
||||
@@ -19,6 +19,7 @@ use table::Result as TableResult;
|
||||
use table::{
|
||||
metadata::{TableId, TableInfoBuilder, TableMetaBuilder, TableType, TableVersion},
|
||||
table::TableRef,
|
||||
Table,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -740,10 +741,6 @@ mod tests {
|
||||
let (_engine, table_engine, table, _object_store, _dir) =
|
||||
test_util::setup_mock_engine_and_table().await;
|
||||
|
||||
let table = table
|
||||
.as_any()
|
||||
.downcast_ref::<MitoTable<MockRegion>>()
|
||||
.unwrap();
|
||||
let table_info = table.table_info();
|
||||
|
||||
let request = CreateTableRequest {
|
||||
@@ -760,14 +757,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let created_table = table_engine.create_table(&ctx, request).await.unwrap();
|
||||
assert_eq!(
|
||||
table_info,
|
||||
created_table
|
||||
.as_any()
|
||||
.downcast_ref::<MitoTable<MockRegion>>()
|
||||
.unwrap()
|
||||
.table_info()
|
||||
);
|
||||
assert_eq!(table_info, created_table.table_info());
|
||||
|
||||
// test create_if_not_exists=false
|
||||
let request = CreateTableRequest {
|
||||
@@ -826,10 +816,6 @@ mod tests {
|
||||
.unwrap();
|
||||
assert_eq!(table.schema(), reopened.schema());
|
||||
|
||||
let table = table
|
||||
.as_any()
|
||||
.downcast_ref::<MitoTable<MockRegion>>()
|
||||
.unwrap();
|
||||
let reopened = reopened
|
||||
.as_any()
|
||||
.downcast_ref::<MitoTable<MockRegion>>()
|
||||
@@ -850,30 +836,92 @@ mod tests {
|
||||
assert_eq!(18446744069414584330, region_id(u32::MAX, 10));
|
||||
}
|
||||
|
||||
fn new_add_columns_req(new_tag: &ColumnSchema, new_field: &ColumnSchema) -> AlterTableRequest {
|
||||
AlterTableRequest {
|
||||
catalog_name: None,
|
||||
schema_name: None,
|
||||
table_name: TABLE_NAME.to_string(),
|
||||
alter_kind: AlterKind::AddColumns {
|
||||
columns: vec![
|
||||
AddColumnRequest {
|
||||
column_schema: new_tag.clone(),
|
||||
is_key: true,
|
||||
},
|
||||
AddColumnRequest {
|
||||
column_schema: new_field.clone(),
|
||||
is_key: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_alter_table_add_column() {
|
||||
let (_engine, table_engine, table, _object_store, _dir) =
|
||||
test_util::setup_mock_engine_and_table().await;
|
||||
|
||||
let table = table
|
||||
.as_any()
|
||||
.downcast_ref::<MitoTable<MockRegion>>()
|
||||
.unwrap();
|
||||
let table_info = table.table_info();
|
||||
let old_info = (*table_info).clone();
|
||||
let old_info = table.table_info();
|
||||
let old_meta = &old_info.meta;
|
||||
let old_schema = &old_meta.schema;
|
||||
|
||||
let new_column = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
|
||||
let new_tag = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
|
||||
let new_field = ColumnSchema::new("my_field", ConcreteDataType::string_datatype(), true);
|
||||
let req = new_add_columns_req(&new_tag, &new_field);
|
||||
let table = table_engine
|
||||
.alter_table(&EngineContext::default(), req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let new_info = table.table_info();
|
||||
let new_meta = &new_info.meta;
|
||||
let new_schema = &new_meta.schema;
|
||||
|
||||
assert_eq!(&[0, 4], &new_meta.primary_key_indices[..]);
|
||||
assert_eq!(&[1, 2, 3, 5], &new_meta.value_indices[..]);
|
||||
assert_eq!(new_schema.num_columns(), old_schema.num_columns() + 2);
|
||||
assert_eq!(
|
||||
&new_schema.column_schemas()[..old_schema.num_columns()],
|
||||
old_schema.column_schemas()
|
||||
);
|
||||
assert_eq!(
|
||||
&new_schema.column_schemas()[old_schema.num_columns()],
|
||||
&new_tag
|
||||
);
|
||||
assert_eq!(
|
||||
&new_schema.column_schemas()[old_schema.num_columns() + 1],
|
||||
&new_field
|
||||
);
|
||||
assert_eq!(new_schema.timestamp_column(), old_schema.timestamp_column());
|
||||
assert_eq!(new_schema.version(), old_schema.version() + 1);
|
||||
assert_eq!(new_meta.next_column_id, old_meta.next_column_id + 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_alter_table_remove_column() {
|
||||
let (_engine, table_engine, _table, _object_store, _dir) =
|
||||
test_util::setup_mock_engine_and_table().await;
|
||||
|
||||
// Add two columns to the table first.
|
||||
let new_tag = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
|
||||
let new_field = ColumnSchema::new("my_field", ConcreteDataType::string_datatype(), true);
|
||||
let req = new_add_columns_req(&new_tag, &new_field);
|
||||
let table = table_engine
|
||||
.alter_table(&EngineContext::default(), req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let old_info = table.table_info();
|
||||
let old_meta = &old_info.meta;
|
||||
let old_schema = &old_meta.schema;
|
||||
|
||||
// Then remove memory and my_field from the table.
|
||||
let req = AlterTableRequest {
|
||||
catalog_name: None,
|
||||
schema_name: None,
|
||||
table_name: TABLE_NAME.to_string(),
|
||||
alter_kind: AlterKind::AddColumns {
|
||||
columns: vec![AddColumnRequest {
|
||||
column_schema: new_column.clone(),
|
||||
is_key: false,
|
||||
}],
|
||||
alter_kind: AlterKind::RemoveColumns {
|
||||
names: vec![String::from("memory"), String::from("my_field")],
|
||||
},
|
||||
};
|
||||
let table = table_engine
|
||||
@@ -881,21 +929,20 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let table = table
|
||||
.as_any()
|
||||
.downcast_ref::<MitoTable<MockRegion>>()
|
||||
.unwrap();
|
||||
let new_info = table.table_info();
|
||||
let new_meta = &new_info.meta;
|
||||
let new_schema = &new_meta.schema;
|
||||
|
||||
assert_eq!(new_schema.num_columns(), old_schema.num_columns() + 1);
|
||||
assert_eq!(
|
||||
new_schema.column_schemas().split_last().unwrap(),
|
||||
(&new_column, old_schema.column_schemas())
|
||||
);
|
||||
assert_eq!(new_schema.num_columns(), old_schema.num_columns() - 2);
|
||||
let remaining_names: Vec<String> = new_schema
|
||||
.column_schemas()
|
||||
.iter()
|
||||
.map(|column_schema| column_schema.name.clone())
|
||||
.collect();
|
||||
assert_eq!(&["host", "cpu", "ts", "my_tag"], &remaining_names[..]);
|
||||
assert_eq!(&[0, 3], &new_meta.primary_key_indices[..]);
|
||||
assert_eq!(&[1, 2], &new_meta.value_indices[..]);
|
||||
assert_eq!(new_schema.timestamp_column(), old_schema.timestamp_column());
|
||||
assert_eq!(new_schema.version(), old_schema.version() + 1);
|
||||
assert_eq!(new_meta.next_column_id, old_meta.next_column_id + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +136,6 @@ pub enum Error {
|
||||
table_name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Column {} already exists in table {}", column_name, table_name))]
|
||||
ColumnExists {
|
||||
backtrace: Backtrace,
|
||||
column_name: String,
|
||||
table_name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Columns {} not exist in table {}", column_names.join(","), table_name))]
|
||||
ColumnsNotExist {
|
||||
backtrace: Backtrace,
|
||||
@@ -150,13 +143,6 @@ pub enum Error {
|
||||
table_name: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to build schema, msg: {}, source: {}", msg, source))]
|
||||
SchemaBuild {
|
||||
#[snafu(backtrace)]
|
||||
source: datatypes::error::Error,
|
||||
msg: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to alter table {}, source: {}", table_name, source))]
|
||||
AlterTable {
|
||||
table_name: String,
|
||||
@@ -206,8 +192,6 @@ impl ErrorExt for Error {
|
||||
|
||||
AlterTable { source, .. } => source.status_code(),
|
||||
|
||||
SchemaBuild { source, .. } => source.status_code(),
|
||||
|
||||
BuildRowKeyDescriptor { .. }
|
||||
| BuildColumnDescriptor { .. }
|
||||
| BuildColumnFamilyDescriptor { .. }
|
||||
@@ -220,8 +204,6 @@ impl ErrorExt for Error {
|
||||
| UnsupportedDefaultConstraint { .. }
|
||||
| TableNotFound { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
ColumnExists { .. } => StatusCode::TableColumnExists,
|
||||
|
||||
ColumnsNotExist { .. } => StatusCode::TableColumnNotFound,
|
||||
|
||||
TableInfoNotFound { .. } | ConvertRaw { .. } => StatusCode::Unexpected,
|
||||
|
||||
@@ -12,7 +12,7 @@ use common_query::physical_plan::PhysicalPlanRef;
|
||||
use common_recordbatch::error::{Error as RecordBatchError, Result as RecordBatchResult};
|
||||
use common_recordbatch::{RecordBatch, RecordBatchStream};
|
||||
use common_telemetry::logging;
|
||||
use datatypes::schema::{ColumnSchema, SchemaBuilder};
|
||||
use datatypes::schema::ColumnSchema;
|
||||
use datatypes::vectors::VectorRef;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::Stream;
|
||||
@@ -20,23 +20,22 @@ use object_store::ObjectStore;
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
use store_api::manifest::{self, Manifest, ManifestVersion, MetaActionIterator};
|
||||
use store_api::storage::{
|
||||
AddColumn, AlterOperation, AlterRequest, ChunkReader, ColumnDescriptorBuilder, PutOperation,
|
||||
ReadContext, Region, RegionMeta, ScanRequest, SchemaRef, Snapshot, WriteContext, WriteRequest,
|
||||
AddColumn, AlterOperation, AlterRequest, ChunkReader, PutOperation, ReadContext, Region,
|
||||
RegionMeta, ScanRequest, SchemaRef, Snapshot, WriteContext, WriteRequest,
|
||||
};
|
||||
use table::error::{Error as TableError, MissingColumnSnafu, Result as TableResult};
|
||||
use table::metadata::{FilterPushDownType, TableInfoRef, TableMetaBuilder};
|
||||
use table::metadata::{FilterPushDownType, TableInfoRef};
|
||||
use table::requests::{AddColumnRequest, AlterKind, AlterTableRequest, InsertRequest};
|
||||
use table::table::scan::SimpleTableScan;
|
||||
use table::{
|
||||
metadata::{RawTableInfo, TableInfo, TableType},
|
||||
metadata::{RawTableInfo, TableInfo, TableMeta, TableType},
|
||||
table::Table,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::error::{
|
||||
self, ColumnsNotExistSnafu, ProjectedColumnNotFoundSnafu, Result, ScanTableManifestSnafu,
|
||||
SchemaBuildSnafu, TableInfoNotFoundSnafu, UnsupportedDefaultConstraintSnafu,
|
||||
UpdateTableManifestSnafu,
|
||||
TableInfoNotFoundSnafu, UnsupportedDefaultConstraintSnafu, UpdateTableManifestSnafu,
|
||||
};
|
||||
use crate::manifest::action::*;
|
||||
use crate::manifest::TableManifest;
|
||||
@@ -186,100 +185,26 @@ impl<R: Region> Table for MitoTable<R> {
|
||||
Ok(Arc::new(SimpleTableScan::new(stream)))
|
||||
}
|
||||
|
||||
// Alter table changes the schemas of the table. The altering happens as cloning a new schema,
|
||||
// change the new one, and swap the old. Though we can change the schema in place, considering
|
||||
// the complex interwinding of inner data representation of schema, I think it's safer to
|
||||
// change it like this to avoid partial inconsistent during the altering. For example, schema's
|
||||
// `name_to_index` field must changed with `column_schemas` synchronously. If we add or remove
|
||||
// columns from `column_schemas` *and then* update the `name_to_index`, there's a slightly time
|
||||
// window of an inconsistency of the two field, which might bring some hard to trace down
|
||||
// concurrency related bugs or failures. (Of course we could introduce some guards like readwrite
|
||||
// lock to protect the consistency of schema altering, but that would hurt the performance of
|
||||
// schema reads, and the reads are the dominant operation of schema. At last, altering is
|
||||
// performed far lesser frequent.)
|
||||
/// Alter table changes the schemas of the table.
|
||||
async fn alter(&self, req: AlterTableRequest) -> TableResult<()> {
|
||||
let _lock = self.alter_lock.lock().await;
|
||||
|
||||
let table_info = self.table_info();
|
||||
let table_name = &table_info.name;
|
||||
let table_meta = &table_info.meta;
|
||||
|
||||
let (alter_op, table_schema, new_columns_num) = match &req.alter_kind {
|
||||
AlterKind::AddColumns {
|
||||
columns: new_columns,
|
||||
} => {
|
||||
let columns = new_columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, add_column)| {
|
||||
let new_column = &add_column.column_schema;
|
||||
|
||||
let desc = ColumnDescriptorBuilder::new(
|
||||
table_meta.next_column_id + i as u32,
|
||||
&new_column.name,
|
||||
new_column.data_type.clone(),
|
||||
)
|
||||
.is_nullable(new_column.is_nullable())
|
||||
.default_constraint(new_column.default_constraint().cloned())
|
||||
.build()
|
||||
.context(error::BuildColumnDescriptorSnafu {
|
||||
table_name,
|
||||
column_name: &new_column.name,
|
||||
})?;
|
||||
|
||||
Ok(AddColumn {
|
||||
desc,
|
||||
is_key: add_column.is_key,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let alter_op = AlterOperation::AddColumns { columns };
|
||||
|
||||
// TODO(yingwen): [alter] Better way to alter the schema struct. In fact the column order
|
||||
// in table schema could differ from the region schema, so we could just push this column
|
||||
// to the back of the schema (as last column).
|
||||
let table_schema = build_table_schema_with_new_columns(
|
||||
table_name,
|
||||
&table_meta.schema,
|
||||
new_columns,
|
||||
)?;
|
||||
|
||||
(alter_op, table_schema, new_columns.len() as u32)
|
||||
}
|
||||
// TODO(dennis): supports removing columns etc.
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let new_meta = TableMetaBuilder::default()
|
||||
.schema(table_schema.clone())
|
||||
.engine(&table_meta.engine)
|
||||
.next_column_id(table_meta.next_column_id + new_columns_num) // Bump next column id.
|
||||
.primary_key_indices(table_meta.primary_key_indices.clone())
|
||||
let mut new_meta = table_meta
|
||||
.builder_with_alter_kind(table_name, &req.alter_kind)?
|
||||
.build()
|
||||
.context(error::BuildTableMetaSnafu { table_name })?;
|
||||
|
||||
let alter_op = create_alter_operation(table_name, &req.alter_kind, &mut new_meta)?;
|
||||
|
||||
let mut new_info = TableInfo::clone(&*table_info);
|
||||
// Increase version of the table.
|
||||
new_info.ident.version = table_info.ident.version + 1;
|
||||
new_info.meta = new_meta;
|
||||
|
||||
// FIXME(yingwen): [alter] Alter the region, now this is a temporary implementation.
|
||||
let region = self.region();
|
||||
let region_meta = region.in_memory_metadata();
|
||||
let alter_req = AlterRequest {
|
||||
operation: alter_op,
|
||||
version: region_meta.version(),
|
||||
};
|
||||
|
||||
logging::debug!(
|
||||
"start altering region {} of table {}, with request {:?}",
|
||||
region.name(),
|
||||
table_name,
|
||||
alter_req,
|
||||
);
|
||||
region.alter(alter_req).await.map_err(TableError::new)?;
|
||||
|
||||
// then alter table info
|
||||
// Persist the alteration to the manifest.
|
||||
logging::debug!(
|
||||
"start updating the manifest of table {} with new table info {:?}",
|
||||
table_name,
|
||||
@@ -295,13 +220,27 @@ impl<R: Region> Table for MitoTable<R> {
|
||||
.context(UpdateTableManifestSnafu {
|
||||
table_name: &self.table_info().name,
|
||||
})?;
|
||||
|
||||
// TODO(yingwen): Error handling. Maybe the region need to provide a method to
|
||||
// validate the request first.
|
||||
let region = self.region();
|
||||
let region_meta = region.in_memory_metadata();
|
||||
let alter_req = AlterRequest {
|
||||
operation: alter_op,
|
||||
version: region_meta.version(),
|
||||
};
|
||||
// Alter the region.
|
||||
logging::debug!(
|
||||
"start altering region {} of table {}, with request {:?}",
|
||||
region.name(),
|
||||
table_name,
|
||||
alter_req,
|
||||
);
|
||||
region.alter(alter_req).await.map_err(TableError::new)?;
|
||||
|
||||
// Update in memory metadata of the table.
|
||||
self.set_table_info(new_info);
|
||||
|
||||
// TODO(LFC): Think of a way to properly handle the metadata integrity between region and table.
|
||||
// Currently there are no "transactions" to alter the metadata of region and table together,
|
||||
// they are altered in sequence. That means there might be cases where the metadata of region
|
||||
// is altered while the table's is not. Then the metadata integrity between region and
|
||||
// table cannot be hold.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -310,47 +249,6 @@ impl<R: Region> Table for MitoTable<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_table_schema_with_new_columns(
|
||||
table_name: &str,
|
||||
table_schema: &SchemaRef,
|
||||
new_columns: &[AddColumnRequest],
|
||||
) -> Result<SchemaRef> {
|
||||
let mut columns = table_schema.column_schemas().to_vec();
|
||||
|
||||
for add_column in new_columns {
|
||||
let new_column = &add_column.column_schema;
|
||||
if table_schema
|
||||
.column_schema_by_name(&new_column.name)
|
||||
.is_some()
|
||||
{
|
||||
return error::ColumnExistsSnafu {
|
||||
column_name: &new_column.name,
|
||||
table_name,
|
||||
}
|
||||
.fail()?;
|
||||
}
|
||||
columns.push(new_column.clone());
|
||||
}
|
||||
|
||||
// Right now we are not support adding the column
|
||||
// before or after some column, so just clone a new schema like this.
|
||||
// TODO(LFC): support adding column before or after some column
|
||||
let mut builder = SchemaBuilder::try_from_columns(columns)
|
||||
.context(SchemaBuildSnafu {
|
||||
msg: "Failed to convert column schemas into table schema",
|
||||
})?
|
||||
.timestamp_index(table_schema.timestamp_index())
|
||||
.version(table_schema.version() + 1);
|
||||
|
||||
for (k, v) in table_schema.arrow_schema().metadata.iter() {
|
||||
builder = builder.add_metadata(k, v);
|
||||
}
|
||||
let new_schema = Arc::new(builder.build().with_context(|_| error::SchemaBuildSnafu {
|
||||
msg: format!("cannot add new columns {:?}", new_columns),
|
||||
})?);
|
||||
Ok(new_schema)
|
||||
}
|
||||
|
||||
struct ChunkStream {
|
||||
schema: SchemaRef,
|
||||
stream: Pin<Box<dyn Stream<Item = RecordBatchResult<RecordBatch>> + Send>>,
|
||||
@@ -449,7 +347,7 @@ impl<R: Region> MitoTable<R> {
|
||||
) -> Result<MitoTable<R>> {
|
||||
let manifest = TableManifest::new(&table_manifest_dir(table_name), object_store);
|
||||
|
||||
// TODO(dennis): save manifest version into catalog?
|
||||
// TODO(dennis): save manifest version into catalog?
|
||||
let _manifest_version = manifest
|
||||
.update(TableMetaActionList::with_action(TableMetaAction::Change(
|
||||
Box::new(TableChange {
|
||||
@@ -543,11 +441,6 @@ impl<R: Region> MitoTable<R> {
|
||||
&self.region
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn table_info(&self) -> TableInfoRef {
|
||||
Arc::clone(&self.table_info.load())
|
||||
}
|
||||
|
||||
pub fn set_table_info(&self, table_info: TableInfo) {
|
||||
self.table_info.swap(Arc::new(table_info));
|
||||
}
|
||||
@@ -563,57 +456,50 @@ impl<R: Region> MitoTable<R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create [`AlterOperation`] according to given `alter_kind`.
|
||||
fn create_alter_operation(
|
||||
table_name: &str,
|
||||
alter_kind: &AlterKind,
|
||||
table_meta: &mut TableMeta,
|
||||
) -> TableResult<AlterOperation> {
|
||||
match alter_kind {
|
||||
AlterKind::AddColumns { columns } => {
|
||||
create_add_columns_operation(table_name, columns, table_meta)
|
||||
}
|
||||
AlterKind::RemoveColumns { names } => Ok(AlterOperation::DropColumns {
|
||||
names: names.to_vec(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_add_columns_operation(
|
||||
table_name: &str,
|
||||
requests: &[AddColumnRequest],
|
||||
table_meta: &mut TableMeta,
|
||||
) -> TableResult<AlterOperation> {
|
||||
let columns = requests
|
||||
.iter()
|
||||
.map(|request| {
|
||||
let new_column = &request.column_schema;
|
||||
let desc = table_meta.alloc_new_column(table_name, new_column)?;
|
||||
|
||||
Ok(AddColumn {
|
||||
desc,
|
||||
is_key: request.is_key,
|
||||
})
|
||||
})
|
||||
.collect::<TableResult<Vec<_>>>()?;
|
||||
|
||||
Ok(AlterOperation::AddColumns { columns })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
|
||||
use super::*;
|
||||
use crate::table::test_util;
|
||||
|
||||
#[test]
|
||||
fn test_table_manifest_dir() {
|
||||
assert_eq!("demo/manifest/", table_manifest_dir("demo"));
|
||||
assert_eq!("numbers/manifest/", table_manifest_dir("numbers"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_table_schema_with_new_column() {
|
||||
let table_info = test_util::build_test_table_info();
|
||||
let table_name = &table_info.name;
|
||||
let table_meta = &table_info.meta;
|
||||
let table_schema = &table_meta.schema;
|
||||
|
||||
let new_column = ColumnSchema::new("host", ConcreteDataType::string_datatype(), true);
|
||||
|
||||
let new_columns = vec![AddColumnRequest {
|
||||
column_schema: new_column,
|
||||
is_key: false,
|
||||
}];
|
||||
let result = build_table_schema_with_new_columns(table_name, table_schema, &new_columns);
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("Column host already exists in table demo"));
|
||||
|
||||
let new_column = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
|
||||
let new_columns = vec![AddColumnRequest {
|
||||
column_schema: new_column.clone(),
|
||||
is_key: false,
|
||||
}];
|
||||
let new_schema =
|
||||
build_table_schema_with_new_columns(table_name, table_schema, &new_columns).unwrap();
|
||||
|
||||
assert_eq!(new_schema.num_columns(), table_schema.num_columns() + 1);
|
||||
assert_eq!(
|
||||
new_schema.column_schemas().split_last().unwrap(),
|
||||
(&new_column, table_schema.column_schemas())
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
new_schema.timestamp_column(),
|
||||
table_schema.timestamp_column()
|
||||
);
|
||||
assert_eq!(new_schema.version(), table_schema.version() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,8 @@ pub fn build_test_table_info() -> TableInfo {
|
||||
.schema(Arc::new(schema_for_test()))
|
||||
.engine(MITO_ENGINE)
|
||||
.next_column_id(1)
|
||||
.primary_key_indices(vec![0, 1])
|
||||
// host is primary key column.
|
||||
.primary_key_indices(vec![0])
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
@@ -71,6 +72,21 @@ pub async fn new_test_object_store(prefix: &str) -> (TempDir, ObjectStore) {
|
||||
(dir, ObjectStore::new(accessor))
|
||||
}
|
||||
|
||||
fn new_create_request(schema: SchemaRef) -> CreateTableRequest {
|
||||
CreateTableRequest {
|
||||
id: 1,
|
||||
catalog_name: "greptime".to_string(),
|
||||
schema_name: "public".to_string(),
|
||||
table_name: TABLE_NAME.to_string(),
|
||||
desc: Some("a test table".to_string()),
|
||||
schema,
|
||||
region_numbers: vec![0],
|
||||
create_if_not_exists: true,
|
||||
primary_key_indices: vec![0],
|
||||
table_options: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_test_engine_and_table() -> (
|
||||
MitoEngine<EngineImpl<NoopLogStore>>,
|
||||
TableRef,
|
||||
@@ -93,18 +109,7 @@ pub async fn setup_test_engine_and_table() -> (
|
||||
let table = table_engine
|
||||
.create_table(
|
||||
&EngineContext::default(),
|
||||
CreateTableRequest {
|
||||
id: 1,
|
||||
catalog_name: "greptime".to_string(),
|
||||
schema_name: "public".to_string(),
|
||||
table_name: TABLE_NAME.to_string(),
|
||||
desc: Some("a test table".to_string()),
|
||||
schema: schema.clone(),
|
||||
create_if_not_exists: true,
|
||||
primary_key_indices: Vec::default(),
|
||||
table_options: HashMap::new(),
|
||||
region_numbers: vec![0],
|
||||
},
|
||||
new_create_request(schema.clone()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -124,21 +129,7 @@ pub async fn setup_mock_engine_and_table(
|
||||
|
||||
let schema = Arc::new(schema_for_test());
|
||||
let table = table_engine
|
||||
.create_table(
|
||||
&EngineContext::default(),
|
||||
CreateTableRequest {
|
||||
id: 1,
|
||||
catalog_name: "greptime".to_string(),
|
||||
schema_name: "public".to_string(),
|
||||
table_name: TABLE_NAME.to_string(),
|
||||
desc: None,
|
||||
schema: schema.clone(),
|
||||
create_if_not_exists: true,
|
||||
primary_key_indices: Vec::default(),
|
||||
table_options: HashMap::new(),
|
||||
region_numbers: vec![0],
|
||||
},
|
||||
)
|
||||
.create_table(&EngineContext::default(), new_create_request(schema))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -51,6 +51,51 @@ pub enum InnerError {
|
||||
#[snafu(backtrace)]
|
||||
source: BoxedError,
|
||||
},
|
||||
|
||||
#[snafu(display("Column {} already exists in table {}", column_name, table_name))]
|
||||
ColumnExists {
|
||||
column_name: String,
|
||||
table_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to build schema, msg: {}, source: {}", msg, source))]
|
||||
SchemaBuild {
|
||||
#[snafu(backtrace)]
|
||||
source: datatypes::error::Error,
|
||||
msg: String,
|
||||
},
|
||||
|
||||
#[snafu(display("Column {} not exists in table {}", column_name, table_name))]
|
||||
ColumnNotExists {
|
||||
column_name: String,
|
||||
table_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Not allowed to remove index column {} from table {}",
|
||||
column_name,
|
||||
table_name
|
||||
))]
|
||||
RemoveColumnInIndex {
|
||||
column_name: String,
|
||||
table_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to build column descriptor for table: {}, column: {}, source: {}",
|
||||
table_name,
|
||||
column_name,
|
||||
source,
|
||||
))]
|
||||
BuildColumnDescriptor {
|
||||
source: store_api::storage::ColumnDescriptorBuilderError,
|
||||
table_name: String,
|
||||
column_name: String,
|
||||
backtrace: Backtrace,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorExt for InnerError {
|
||||
@@ -60,8 +105,13 @@ impl ErrorExt for InnerError {
|
||||
| InnerError::PollStream { .. }
|
||||
| InnerError::SchemaConversion { .. }
|
||||
| InnerError::TableProjection { .. } => StatusCode::EngineExecuteQuery,
|
||||
InnerError::MissingColumn { .. } => StatusCode::InvalidArguments,
|
||||
InnerError::MissingColumn { .. }
|
||||
| InnerError::RemoveColumnInIndex { .. }
|
||||
| InnerError::BuildColumnDescriptor { .. } => StatusCode::InvalidArguments,
|
||||
InnerError::TablesRecordBatch { .. } => StatusCode::Unexpected,
|
||||
InnerError::ColumnExists { .. } => StatusCode::TableColumnExists,
|
||||
InnerError::SchemaBuild { source, .. } => source.status_code(),
|
||||
InnerError::ColumnNotExists { .. } => StatusCode::TableColumnNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use datatypes::error::{Error as ConvertError, Result as ConvertResult};
|
||||
use datatypes::schema::{RawSchema, Schema, SchemaRef};
|
||||
use datatypes::schema::{ColumnSchema, RawSchema, Schema, SchemaBuilder, SchemaRef};
|
||||
use derive_builder::Builder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use store_api::storage::ColumnId;
|
||||
use snafu::{ensure, ResultExt};
|
||||
use store_api::storage::{ColumnDescriptor, ColumnDescriptorBuilder, ColumnId};
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::requests::{AddColumnRequest, AlterKind};
|
||||
|
||||
pub type TableId = u32;
|
||||
pub type TableVersion = u64;
|
||||
@@ -42,7 +46,10 @@ pub enum TableType {
|
||||
/// Identifier of the table.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Default)]
|
||||
pub struct TableIdent {
|
||||
/// Unique id of this table.
|
||||
pub table_id: TableId,
|
||||
/// Version of the table, bumped when metadata (such as schema) of the table
|
||||
/// being changed.
|
||||
pub version: TableVersion,
|
||||
}
|
||||
|
||||
@@ -50,6 +57,8 @@ pub struct TableIdent {
|
||||
#[builder(pattern = "mutable")]
|
||||
pub struct TableMeta {
|
||||
pub schema: SchemaRef,
|
||||
/// The indices of columns in primary key. Note that the index of timestamp column
|
||||
/// is not included in these indices.
|
||||
pub primary_key_indices: Vec<usize>,
|
||||
#[builder(default = "self.default_value_indices()?")]
|
||||
pub value_indices: Vec<usize>,
|
||||
@@ -69,7 +78,7 @@ pub struct TableMeta {
|
||||
}
|
||||
|
||||
impl TableMetaBuilder {
|
||||
fn default_value_indices(&self) -> Result<Vec<usize>, String> {
|
||||
fn default_value_indices(&self) -> std::result::Result<Vec<usize>, String> {
|
||||
match (&self.primary_key_indices, &self.schema) {
|
||||
(Some(v), Some(schema)) => {
|
||||
let column_schemas = schema.column_schemas();
|
||||
@@ -96,13 +105,230 @@ impl TableMeta {
|
||||
.iter()
|
||||
.map(|idx| &columns_schemas[*idx].name)
|
||||
}
|
||||
|
||||
/// Returns the new [TableMetaBuilder] after applying given `alter_kind`.
|
||||
///
|
||||
/// The returned builder would derive the next column id of this meta.
|
||||
pub fn builder_with_alter_kind(
|
||||
&self,
|
||||
table_name: &str,
|
||||
alter_kind: &AlterKind,
|
||||
) -> Result<TableMetaBuilder> {
|
||||
match alter_kind {
|
||||
AlterKind::AddColumns { columns } => self.add_columns(table_name, columns),
|
||||
AlterKind::RemoveColumns { names } => self.remove_columns(table_name, names),
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate a new column for the table.
|
||||
///
|
||||
/// This method would bump the `next_column_id` of the meta.
|
||||
pub fn alloc_new_column(
|
||||
&mut self,
|
||||
table_name: &str,
|
||||
new_column: &ColumnSchema,
|
||||
) -> Result<ColumnDescriptor> {
|
||||
let desc = ColumnDescriptorBuilder::new(
|
||||
self.next_column_id as ColumnId,
|
||||
&new_column.name,
|
||||
new_column.data_type.clone(),
|
||||
)
|
||||
.is_nullable(new_column.is_nullable())
|
||||
.default_constraint(new_column.default_constraint().cloned())
|
||||
.build()
|
||||
.context(error::BuildColumnDescriptorSnafu {
|
||||
table_name,
|
||||
column_name: &new_column.name,
|
||||
})?;
|
||||
|
||||
// Bump next column id.
|
||||
self.next_column_id += 1;
|
||||
|
||||
Ok(desc)
|
||||
}
|
||||
|
||||
fn new_meta_builder(&self) -> TableMetaBuilder {
|
||||
let mut builder = TableMetaBuilder::default();
|
||||
builder
|
||||
.engine(&self.engine)
|
||||
.engine_options(self.engine_options.clone())
|
||||
.options(self.options.clone())
|
||||
.created_on(self.created_on)
|
||||
.next_column_id(self.next_column_id);
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
fn add_columns(
|
||||
&self,
|
||||
table_name: &str,
|
||||
requests: &[AddColumnRequest],
|
||||
) -> Result<TableMetaBuilder> {
|
||||
let table_schema = &self.schema;
|
||||
let mut meta_builder = self.new_meta_builder();
|
||||
|
||||
// Check whether columns to add are already existing.
|
||||
for request in requests {
|
||||
let column_name = &request.column_schema.name;
|
||||
ensure!(
|
||||
table_schema.column_schema_by_name(column_name).is_none(),
|
||||
error::ColumnExistsSnafu {
|
||||
column_name,
|
||||
table_name,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Collect names of columns to add for error message.
|
||||
let mut column_names = Vec::with_capacity(requests.len());
|
||||
let mut primary_key_indices = self.primary_key_indices.clone();
|
||||
let mut columns = Vec::with_capacity(table_schema.num_columns() + requests.len());
|
||||
columns.extend_from_slice(table_schema.column_schemas());
|
||||
// Append new columns to the end of column list.
|
||||
for request in requests {
|
||||
column_names.push(request.column_schema.name.clone());
|
||||
if request.is_key {
|
||||
// If a key column is added, we also need to store its index in primary_key_indices.
|
||||
primary_key_indices.push(columns.len());
|
||||
}
|
||||
columns.push(request.column_schema.clone());
|
||||
}
|
||||
|
||||
let mut builder = SchemaBuilder::try_from(columns)
|
||||
.with_context(|_| error::SchemaBuildSnafu {
|
||||
msg: format!(
|
||||
"Failed to convert column schemas into schema for table {}",
|
||||
table_name
|
||||
),
|
||||
})?
|
||||
.timestamp_index(table_schema.timestamp_index())
|
||||
// Also bump the schema version.
|
||||
.version(table_schema.version() + 1);
|
||||
for (k, v) in table_schema.metadata().iter() {
|
||||
builder = builder.add_metadata(k, v);
|
||||
}
|
||||
let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
|
||||
msg: format!(
|
||||
"Table {} cannot add new columns {:?}",
|
||||
table_name, column_names
|
||||
),
|
||||
})?;
|
||||
|
||||
// value_indices would be generated automatically.
|
||||
meta_builder
|
||||
.schema(Arc::new(new_schema))
|
||||
.primary_key_indices(primary_key_indices);
|
||||
|
||||
Ok(meta_builder)
|
||||
}
|
||||
|
||||
fn remove_columns(
|
||||
&self,
|
||||
table_name: &str,
|
||||
column_names: &[String],
|
||||
) -> Result<TableMetaBuilder> {
|
||||
let table_schema = &self.schema;
|
||||
let column_names: HashSet<_> = column_names.iter().collect();
|
||||
let mut meta_builder = self.new_meta_builder();
|
||||
|
||||
let timestamp_index = table_schema.timestamp_index();
|
||||
// Check whether columns are existing and not in primary key index.
|
||||
for column_name in &column_names {
|
||||
if let Some(index) = table_schema.column_index_by_name(column_name) {
|
||||
// This is a linear search, but since there won't be too much columns, the performance should
|
||||
// be acceptable.
|
||||
ensure!(
|
||||
!self.primary_key_indices.contains(&index),
|
||||
error::RemoveColumnInIndexSnafu {
|
||||
column_name: *column_name,
|
||||
table_name,
|
||||
}
|
||||
);
|
||||
|
||||
if let Some(ts_index) = timestamp_index {
|
||||
// Not allowed to remove column in timestamp index.
|
||||
ensure!(
|
||||
index != ts_index,
|
||||
error::RemoveColumnInIndexSnafu {
|
||||
column_name: table_schema.column_name_by_index(ts_index),
|
||||
table_name,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return error::ColumnNotExistsSnafu {
|
||||
column_name: *column_name,
|
||||
table_name,
|
||||
}
|
||||
.fail()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Collect columns after removal.
|
||||
let columns: Vec<_> = table_schema
|
||||
.column_schemas()
|
||||
.iter()
|
||||
.filter(|column_schema| !column_names.contains(&column_schema.name))
|
||||
.cloned()
|
||||
.collect();
|
||||
// Find the index of the timestamp column.
|
||||
let timestamp_column = table_schema.timestamp_column();
|
||||
let timestamp_index = columns.iter().enumerate().find_map(|(idx, column_schema)| {
|
||||
let is_timestamp = timestamp_column
|
||||
.map(|c| c.name == column_schema.name)
|
||||
.unwrap_or(false);
|
||||
if is_timestamp {
|
||||
Some(idx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let mut builder = SchemaBuilder::try_from_columns(columns)
|
||||
.with_context(|_| error::SchemaBuildSnafu {
|
||||
msg: format!(
|
||||
"Failed to convert column schemas into schema for table {}",
|
||||
table_name
|
||||
),
|
||||
})?
|
||||
// Need to use the newly computed timestamp index.
|
||||
.timestamp_index(timestamp_index)
|
||||
// Also bump the schema version.
|
||||
.version(table_schema.version() + 1);
|
||||
for (k, v) in table_schema.metadata().iter() {
|
||||
builder = builder.add_metadata(k, v);
|
||||
}
|
||||
let new_schema = builder.build().with_context(|_| error::SchemaBuildSnafu {
|
||||
msg: format!(
|
||||
"Table {} cannot add remove columns {:?}",
|
||||
table_name, column_names
|
||||
),
|
||||
})?;
|
||||
|
||||
// Rebuild the indices of primary key columns.
|
||||
let primary_key_indices = self
|
||||
.primary_key_indices
|
||||
.iter()
|
||||
.map(|idx| table_schema.column_name_by_index(*idx))
|
||||
// This unwrap is safe since we don't allow removing a primary key column.
|
||||
.map(|name| new_schema.column_index_by_name(name).unwrap())
|
||||
.collect();
|
||||
|
||||
meta_builder
|
||||
.schema(Arc::new(new_schema))
|
||||
.primary_key_indices(primary_key_indices);
|
||||
|
||||
Ok(meta_builder)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Builder)]
|
||||
#[builder(pattern = "owned")]
|
||||
pub struct TableInfo {
|
||||
/// Id and version of the table.
|
||||
#[builder(default, setter(into))]
|
||||
pub ident: TableIdent,
|
||||
/// Name of the table.
|
||||
#[builder(setter(into))]
|
||||
pub name: String,
|
||||
/// Comment of the table.
|
||||
@@ -248,6 +474,7 @@ impl TryFrom<RawTableInfo> for TableInfo {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use common_error::prelude::*;
|
||||
use datatypes::data_type::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnSchema, Schema, SchemaBuilder};
|
||||
|
||||
@@ -257,6 +484,7 @@ mod tests {
|
||||
let column_schemas = vec![
|
||||
ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
|
||||
ColumnSchema::new("ts", ConcreteDataType::timestamp_millis_datatype(), false),
|
||||
ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true),
|
||||
];
|
||||
SchemaBuilder::try_from(column_schemas)
|
||||
.unwrap()
|
||||
@@ -271,10 +499,9 @@ mod tests {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema)
|
||||
.primary_key_indices(vec![1])
|
||||
.value_indices(vec![0])
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(2)
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
let info = TableInfoBuilder::default()
|
||||
@@ -290,4 +517,236 @@ mod tests {
|
||||
|
||||
assert_eq!(info, info_new);
|
||||
}
|
||||
|
||||
fn add_columns_to_meta(meta: &TableMeta) -> TableMeta {
|
||||
let new_tag = ColumnSchema::new("my_tag", ConcreteDataType::string_datatype(), true);
|
||||
let new_field = ColumnSchema::new("my_field", ConcreteDataType::string_datatype(), true);
|
||||
let alter_kind = AlterKind::AddColumns {
|
||||
columns: vec![
|
||||
AddColumnRequest {
|
||||
column_schema: new_tag,
|
||||
is_key: true,
|
||||
},
|
||||
AddColumnRequest {
|
||||
column_schema: new_field,
|
||||
is_key: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let builder = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.unwrap();
|
||||
builder.build().unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_columns() {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema)
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let new_meta = add_columns_to_meta(&meta);
|
||||
|
||||
let names: Vec<String> = new_meta
|
||||
.schema
|
||||
.column_schemas()
|
||||
.iter()
|
||||
.map(|column_schema| column_schema.name.clone())
|
||||
.collect();
|
||||
assert_eq!(&["col1", "ts", "col2", "my_tag", "my_field"], &names[..]);
|
||||
assert_eq!(&[0, 3], &new_meta.primary_key_indices[..]);
|
||||
assert_eq!(&[1, 2, 4], &new_meta.value_indices[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_columns() {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema.clone())
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
// Add more columns so we have enough candidate columns to remove.
|
||||
let meta = add_columns_to_meta(&meta);
|
||||
|
||||
let alter_kind = AlterKind::RemoveColumns {
|
||||
names: vec![String::from("col2"), String::from("my_field")],
|
||||
};
|
||||
let new_meta = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let names: Vec<String> = new_meta
|
||||
.schema
|
||||
.column_schemas()
|
||||
.iter()
|
||||
.map(|column_schema| column_schema.name.clone())
|
||||
.collect();
|
||||
assert_eq!(&["col1", "ts", "my_tag"], &names[..]);
|
||||
assert_eq!(&[0, 2], &new_meta.primary_key_indices[..]);
|
||||
assert_eq!(&[1], &new_meta.value_indices[..]);
|
||||
assert_eq!(
|
||||
schema.timestamp_column(),
|
||||
new_meta.schema.timestamp_column()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_multiple_columns_before_timestamp() {
|
||||
let column_schemas = vec![
|
||||
ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true),
|
||||
ColumnSchema::new("col2", ConcreteDataType::int32_datatype(), true),
|
||||
ColumnSchema::new("col3", ConcreteDataType::int32_datatype(), true),
|
||||
ColumnSchema::new("ts", ConcreteDataType::timestamp_millis_datatype(), false),
|
||||
];
|
||||
let schema = Arc::new(
|
||||
SchemaBuilder::try_from(column_schemas)
|
||||
.unwrap()
|
||||
.timestamp_index(Some(3))
|
||||
.version(123)
|
||||
.build()
|
||||
.unwrap(),
|
||||
);
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema.clone())
|
||||
.primary_key_indices(vec![1])
|
||||
.engine("engine")
|
||||
.next_column_id(4)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Remove columns in reverse order to test whether timestamp index is valid.
|
||||
let alter_kind = AlterKind::RemoveColumns {
|
||||
names: vec![String::from("col3"), String::from("col1")],
|
||||
};
|
||||
let new_meta = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.unwrap()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let names: Vec<String> = new_meta
|
||||
.schema
|
||||
.column_schemas()
|
||||
.iter()
|
||||
.map(|column_schema| column_schema.name.clone())
|
||||
.collect();
|
||||
assert_eq!(&["col2", "ts"], &names[..]);
|
||||
assert_eq!(&[0], &new_meta.primary_key_indices[..]);
|
||||
assert_eq!(&[1], &new_meta.value_indices[..]);
|
||||
assert_eq!(
|
||||
schema.timestamp_column(),
|
||||
new_meta.schema.timestamp_column()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_existing_column() {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema)
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let alter_kind = AlterKind::AddColumns {
|
||||
columns: vec![AddColumnRequest {
|
||||
column_schema: ColumnSchema::new("col1", ConcreteDataType::string_datatype(), true),
|
||||
is_key: false,
|
||||
}],
|
||||
};
|
||||
|
||||
let err = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(StatusCode::TableColumnExists, err.status_code());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_unknown_column() {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema)
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let alter_kind = AlterKind::RemoveColumns {
|
||||
names: vec![String::from("unknown")],
|
||||
};
|
||||
|
||||
let err = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(StatusCode::TableColumnNotFound, err.status_code());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_key_column() {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let meta = TableMetaBuilder::default()
|
||||
.schema(schema)
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Remove column in primary key.
|
||||
let alter_kind = AlterKind::RemoveColumns {
|
||||
names: vec![String::from("col1")],
|
||||
};
|
||||
|
||||
let err = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(StatusCode::InvalidArguments, err.status_code());
|
||||
|
||||
// Remove timestamp column.
|
||||
let alter_kind = AlterKind::RemoveColumns {
|
||||
names: vec![String::from("ts")],
|
||||
};
|
||||
|
||||
let err = meta
|
||||
.builder_with_alter_kind("my_table", &alter_kind)
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(StatusCode::InvalidArguments, err.status_code());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_alloc_new_column() {
|
||||
let schema = Arc::new(new_test_schema());
|
||||
let mut meta = TableMetaBuilder::default()
|
||||
.schema(schema)
|
||||
.primary_key_indices(vec![0])
|
||||
.engine("engine")
|
||||
.next_column_id(3)
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(3, meta.next_column_id);
|
||||
|
||||
let column_schema = ColumnSchema::new("col1", ConcreteDataType::int32_datatype(), true);
|
||||
let desc = meta.alloc_new_column("test_table", &column_schema).unwrap();
|
||||
|
||||
assert_eq!(4, meta.next_column_id);
|
||||
assert_eq!(column_schema.name, desc.name);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user