mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-08 22:32:55 +00:00
Compare commits
26 Commits
v0.3.2
...
v0.4.0-nig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b31fad5d52 | ||
|
|
00181885cc | ||
|
|
195dfdc5d3 | ||
|
|
f20b5695b8 | ||
|
|
f731193ddc | ||
|
|
963e468286 | ||
|
|
f19498f73e | ||
|
|
4cc42e2ba6 | ||
|
|
cd5afc8cb7 | ||
|
|
6dd24f4dc4 | ||
|
|
55500b7711 | ||
|
|
64acfd3802 | ||
|
|
ad165c1c64 | ||
|
|
8dcb12e317 | ||
|
|
03e30652c8 | ||
|
|
61c793796c | ||
|
|
dc085442d7 | ||
|
|
9153191819 | ||
|
|
979400ac58 | ||
|
|
28748edb0d | ||
|
|
66e5ed5483 | ||
|
|
af2fb2acbd | ||
|
|
eb2654b89a | ||
|
|
3d0d082c56 | ||
|
|
4073fceea5 | ||
|
|
8a00424468 |
@@ -14,4 +14,8 @@ GT_AZBLOB_CONTAINER=AZBLOB container
|
||||
GT_AZBLOB_ACCOUNT_NAME=AZBLOB account name
|
||||
GT_AZBLOB_ACCOUNT_KEY=AZBLOB account key
|
||||
GT_AZBLOB_ENDPOINT=AZBLOB endpoint
|
||||
|
||||
# Settings for gcs test
|
||||
GT_GCS_BUCKET = GCS bucket
|
||||
GT_GCS_SCOPE = GCS scope
|
||||
GT_GCS_CREDENTIAL_PATH = GCS credential path
|
||||
GT_GCS_ENDPOINT = GCS end point
|
||||
|
||||
14
.github/workflows/docs.yml
vendored
14
.github/workflows/docs.yml
vendored
@@ -27,6 +27,13 @@ name: CI
|
||||
# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
|
||||
jobs:
|
||||
typos:
|
||||
name: Spell Check with Typos
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: crate-ci/typos@v1.13.10
|
||||
|
||||
check:
|
||||
name: Check
|
||||
if: github.event.pull_request.draft == false
|
||||
@@ -53,3 +60,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No action required"'
|
||||
|
||||
sqlness:
|
||||
name: Sqlness Test
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No action required"'
|
||||
|
||||
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@@ -136,7 +136,7 @@ jobs:
|
||||
|
||||
- name: Upload to S3
|
||||
run: |
|
||||
aws s3 sync target/${{ matrix.arch }}/${{ env.CARGO_PROFILE }} s3://${{ secrets.GREPTIMEDB_RELEASE_BUCKET_NAME }}/releases/${TAG}
|
||||
aws s3 cp target/${{ matrix.arch }}/${{ env.CARGO_PROFILE }} s3://${{ secrets.GREPTIMEDB_RELEASE_BUCKET_NAME }}/releases/${TAG} --recursive --exclude "*" --include "*.tgz"
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_CN_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_CN_SECRET_ACCESS_KEY }}
|
||||
@@ -312,7 +312,7 @@ jobs:
|
||||
|
||||
- name: Upload to S3
|
||||
run: |
|
||||
aws s3 sync target/${{ matrix.arch }}/${{ env.CARGO_PROFILE }} s3://${{ secrets.GREPTIMEDB_RELEASE_BUCKET_NAME }}/releases/${TAG}
|
||||
aws s3 cp target/${{ matrix.arch }}/${{ env.CARGO_PROFILE }} s3://${{ secrets.GREPTIMEDB_RELEASE_BUCKET_NAME }}/releases/${TAG} --recursive --exclude "*" --include "*.tgz"
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_CN_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_CN_SECRET_ACCESS_KEY }}
|
||||
@@ -459,7 +459,8 @@ jobs:
|
||||
name: "${{ github.ref_name }}"
|
||||
prerelease: ${{ env.prerelease }}
|
||||
makeLatest: ${{ env.makeLatest }}
|
||||
generateReleaseNotes: true
|
||||
generateReleaseNotes: false
|
||||
allowUpdates: true
|
||||
artifacts: |
|
||||
**/greptime-*
|
||||
|
||||
@@ -482,7 +483,7 @@ jobs:
|
||||
- name: Login to alibaba cloud container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: registry.cn-hangzhou.aliyuncs.com
|
||||
registry: greptime-registry.cn-hangzhou.cr.aliyuncs.com
|
||||
username: ${{ secrets.ALICLOUD_USERNAME }}
|
||||
password: ${{ secrets.ALICLOUD_PASSWORD }}
|
||||
|
||||
@@ -504,6 +505,6 @@ jobs:
|
||||
- name: Push image to alibaba cloud container registry # Use 'docker buildx imagetools create' to create a new image base on source image.
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag registry.cn-hangzhou.aliyuncs.com/greptime/greptimedb:latest \
|
||||
--tag registry.cn-hangzhou.aliyuncs.com/greptime/greptimedb:${{ env.IMAGE_TAG }} \
|
||||
--tag greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb:latest \
|
||||
--tag greptime-registry.cn-hangzhou.cr.aliyuncs.com/greptime/greptimedb:${{ env.IMAGE_TAG }} \
|
||||
greptime/greptimedb:${{ env.IMAGE_TAG }}
|
||||
|
||||
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -1812,8 +1812,11 @@ dependencies = [
|
||||
"common-telemetry",
|
||||
"common-time",
|
||||
"datatypes",
|
||||
"etcd-client",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"prost",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snafu",
|
||||
@@ -4109,7 +4112,7 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
[[package]]
|
||||
name = "greptime-proto"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/WenyXu/greptime-proto.git?rev=1eda4691a5d2c8ffc463d48ca2317905ba7e4b2d#1eda4691a5d2c8ffc463d48ca2317905ba7e4b2d"
|
||||
source = "git+https://github.com/GreptimeTeam/greptime-proto.git?rev=917ead6274b4dccbaf33c59a7360646ba2f285a9#917ead6274b4dccbaf33c59a7360646ba2f285a9"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"serde",
|
||||
@@ -6001,8 +6004,9 @@ checksum = "978aa494585d3ca4ad74929863093e87cac9790d81fe7aba2b3dc2890643a0fc"
|
||||
|
||||
[[package]]
|
||||
name = "orc-rust"
|
||||
version = "0.2.3"
|
||||
source = "git+https://github.com/WenyXu/orc-rs.git?rev=0319acd32456e403c20f135cc012441a76852605#0319acd32456e403c20f135cc012441a76852605"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01773d11a950f7418e691899bd2917e393972188b961d381ee3ce1880ad02e32"
|
||||
dependencies = [
|
||||
"arrow",
|
||||
"bytes",
|
||||
|
||||
@@ -71,14 +71,17 @@ datafusion-optimizer = { git = "https://github.com/waynexia/arrow-datafusion.git
|
||||
datafusion-physical-expr = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "63e52dde9e44cac4b1f6c6e6b6bf6368ba3bd323" }
|
||||
datafusion-sql = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "63e52dde9e44cac4b1f6c6e6b6bf6368ba3bd323" }
|
||||
datafusion-substrait = { git = "https://github.com/waynexia/arrow-datafusion.git", rev = "63e52dde9e44cac4b1f6c6e6b6bf6368ba3bd323" }
|
||||
etcd-client = "0.11"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
greptime-proto = { git = "https://github.com/WenyXu/greptime-proto.git", rev = "1eda4691a5d2c8ffc463d48ca2317905ba7e4b2d" }
|
||||
greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "917ead6274b4dccbaf33c59a7360646ba2f285a9" }
|
||||
itertools = "0.10"
|
||||
lazy_static = "1.4"
|
||||
parquet = "40.0"
|
||||
paste = "1.0"
|
||||
prost = "0.11"
|
||||
rand = "0.8"
|
||||
regex = "1.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
|
||||
@@ -30,7 +30,9 @@ RUN cargo build --release
|
||||
# TODO(zyy17): Maybe should use the more secure container image.
|
||||
FROM ubuntu:22.04 as base
|
||||
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get \
|
||||
-y install ca-certificates \
|
||||
curl
|
||||
|
||||
WORKDIR /greptime
|
||||
COPY --from=builder /greptimedb/target/release/greptime /greptime/bin/
|
||||
|
||||
@@ -49,7 +49,9 @@ RUN export PYO3_CROSS_LIB_DIR=$PY_INSTALL_PATH/lib && \
|
||||
# Exporting the binary to the clean image
|
||||
FROM ubuntu:22.04 as base
|
||||
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get -y install ca-certificates
|
||||
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get \
|
||||
-y install ca-certificates \
|
||||
curl
|
||||
|
||||
WORKDIR /greptime
|
||||
COPY --from=builder /greptimedb/target/aarch64-unknown-linux-gnu/release/greptime /greptime/bin/
|
||||
|
||||
@@ -4,7 +4,8 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
ca-certificates \
|
||||
python3.10 \
|
||||
python3.10-dev \
|
||||
python3-pip
|
||||
python3-pip \
|
||||
curl
|
||||
|
||||
COPY requirements.txt /etc/greptime/requirements.txt
|
||||
|
||||
|
||||
39
docs/benchmarks/tsbs/v0.3.2.md
Normal file
39
docs/benchmarks/tsbs/v0.3.2.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# TSBS benchmark - v0.3.2
|
||||
|
||||
## Environment
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| CPU | AMD Ryzen 7 7735HS (8 core 3.2GHz) |
|
||||
| Memory | 32GB |
|
||||
| Disk | SOLIDIGM SSDPFKNU010TZ |
|
||||
| OS | Ubuntu 22.04.2 LTS |
|
||||
|
||||
|
||||
## Write performance
|
||||
|
||||
| Write buffer size | Ingest rate(rows/s) |
|
||||
| --- | --- |
|
||||
| 512M | 139583.04 |
|
||||
| 32M | 279250.52 |
|
||||
|
||||
|
||||
## Query performance
|
||||
|
||||
| Query type | v0.3.2 write buffer 32M (ms) | v0.3.2 write buffer 512M (ms) | v0.3.1 write buffer 32M (ms) |
|
||||
| --- | --- | --- | --- |
|
||||
| cpu-max-all-1 | 921.12 | 241.23 | 553.63 |
|
||||
| cpu-max-all-8 | 2657.66 | 502.78 | 3308.41 |
|
||||
| double-groupby-1 | 28238.85 | 27367.42 | 52148.22 |
|
||||
| double-groupby-5 | 33094.65 | 32421.89 | 56762.37 |
|
||||
| double-groupby-all | 38565.89 | 38635.52 | 59596.80 |
|
||||
| groupby-orderby-limit | 23321.60 | 22423.55 | 53983.23 |
|
||||
| high-cpu-1 | 1167.04 | 254.15 | 832.41 |
|
||||
| high-cpu-all | 32814.08 | 29906.94 | 62853.12 |
|
||||
| lastpoint | 192045.05 | 153575.42 | NA |
|
||||
| single-groupby-1-1-1 | 63.97 | 87.35 | 92.66 |
|
||||
| single-groupby-1-1-12 | 666.24 | 326.98 | 781.50 |
|
||||
| single-groupby-1-8-1 | 225.29 | 137.97 |281.95 |
|
||||
| single-groupby-5-1-1 | 70.40 | 81.64 | 86.15 |
|
||||
| single-groupby-5-1-12 | 722.75 | 356.01 | 805.18 |
|
||||
| single-groupby-5-8-1 | 285.60 | 115.88 | 326.29 |
|
||||
303
docs/rfcs/2023-07-06-table-engine-refactor.md
Normal file
303
docs/rfcs/2023-07-06-table-engine-refactor.md
Normal file
@@ -0,0 +1,303 @@
|
||||
---
|
||||
Feature Name: table-engine-refactor
|
||||
Tracking Issue: https://github.com/GreptimeTeam/greptimedb/issues/1869
|
||||
Date: 2023-07-06
|
||||
Author: "Yingwen <realevenyag@gmail.com>"
|
||||
---
|
||||
|
||||
Refactor Table Engine
|
||||
----------------------
|
||||
|
||||
# Summary
|
||||
Refactor table engines to address several historical tech debts.
|
||||
|
||||
# Motivation
|
||||
Both `Frontend` and `Datanode` have to deal with multiple regions in a table. This results in code duplication and additional burden to the `Datanode`.
|
||||
|
||||
Before:
|
||||
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
subgraph Frontend["Frontend"]
|
||||
subgraph MyTable
|
||||
A("region 0, 2 -> Datanode0")
|
||||
B("region 1, 3 -> Datanode1")
|
||||
end
|
||||
end
|
||||
|
||||
MyTable --> MetaSrv
|
||||
MetaSrv --> ETCD
|
||||
|
||||
MyTable-->TableEngine0
|
||||
MyTable-->TableEngine1
|
||||
|
||||
subgraph Datanode0
|
||||
Procedure0("procedure")
|
||||
TableEngine0("table engine")
|
||||
region0
|
||||
region2
|
||||
mytable0("my_table")
|
||||
|
||||
Procedure0-->mytable0
|
||||
TableEngine0-->mytable0
|
||||
mytable0-->region0
|
||||
mytable0-->region2
|
||||
end
|
||||
|
||||
|
||||
subgraph Datanode1
|
||||
Procedure1("procedure")
|
||||
TableEngine1("table engine")
|
||||
region1
|
||||
region3
|
||||
mytable1("my_table")
|
||||
|
||||
Procedure1-->mytable1
|
||||
TableEngine1-->mytable1
|
||||
mytable1-->region1
|
||||
mytable1-->region3
|
||||
end
|
||||
|
||||
|
||||
subgraph manifest["table manifest"]
|
||||
M0("my_table")
|
||||
M1("regions: [0, 1, 2, 3]")
|
||||
end
|
||||
|
||||
mytable1-->manifest
|
||||
mytable0-->manifest
|
||||
|
||||
RegionManifest0("region manifest 0")
|
||||
RegionManifest1("region manifest 1")
|
||||
RegionManifest2("region manifest 2")
|
||||
RegionManifest3("region manifest 3")
|
||||
region0-->RegionManifest0
|
||||
region1-->RegionManifest1
|
||||
region2-->RegionManifest2
|
||||
region3-->RegionManifest3
|
||||
```
|
||||
|
||||
`Datanodes` can update the same manifest file for a table as regions are assigned to different nodes in the cluster. We also have to run procedures on `Datanode` to ensure the table manifest is consistent with region manifests. "Table" in a `Datanode` is a subset of the table's regions. The `Datanode` is much closer to `RegionServer` in `HBase` which only deals with regions.
|
||||
|
||||
In cluster mode, we store table metadata in etcd and table manifest. The table manifest becomes redundant. We can remove the table manifest if we refactor the table engines to region engines that only care about regions. What's more, we don't need to run those procedures on `Datanode`.
|
||||
|
||||
After:
|
||||
```mermaid
|
||||
graph TB
|
||||
|
||||
subgraph Frontend["Frontend"]
|
||||
direction LR
|
||||
subgraph MyTable
|
||||
A("region 0, 2 -> Datanode0")
|
||||
B("region 1, 3 -> Datanode1")
|
||||
end
|
||||
end
|
||||
|
||||
MyTable --> MetaSrv
|
||||
MetaSrv --> ETCD
|
||||
|
||||
MyTable-->RegionEngine
|
||||
MyTable-->RegionEngine1
|
||||
|
||||
subgraph Datanode0
|
||||
RegionEngine("region engine")
|
||||
region0
|
||||
region2
|
||||
RegionEngine-->region0
|
||||
RegionEngine-->region2
|
||||
end
|
||||
|
||||
|
||||
subgraph Datanode1
|
||||
RegionEngine1("region engine")
|
||||
region1
|
||||
region3
|
||||
RegionEngine1-->region1
|
||||
RegionEngine1-->region3
|
||||
end
|
||||
|
||||
RegionManifest0("region manifest 0")
|
||||
RegionManifest1("region manifest 1")
|
||||
RegionManifest2("region manifest 2")
|
||||
RegionManifest3("region manifest 3")
|
||||
region0-->RegionManifest0
|
||||
region1-->RegionManifest1
|
||||
region2-->RegionManifest2
|
||||
region3-->RegionManifest3
|
||||
```
|
||||
This RFC proposes to refactor table engines into region engines as a first step to make the `Datanode` acts like a `RegionServer`.
|
||||
|
||||
|
||||
# Details
|
||||
## Overview
|
||||
|
||||
We plan to refactor the `TableEngine` trait into `RegionEngine` gradually. This RFC focuses on the `mito` engine as it is the default table engine and the most complicated engine.
|
||||
|
||||
Currently, we built `MitoEngine` upon `StorageEngine` that manages regions of the `mito` engine. Since `MitoEngine` becomes a region engine, we could combine `StorageEngine` with `MitoEngine` to simplify our code structure.
|
||||
|
||||
The chart below shows the overall architecture of the `MitoEngine`.
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class MitoEngine~LogStore~ {
|
||||
-WorkerGroup workers
|
||||
}
|
||||
class MitoRegion {
|
||||
+VersionControlRef version_control
|
||||
-RegionId region_id
|
||||
-String manifest_dir
|
||||
-AtomicI64 last_flush_millis
|
||||
+region_id() RegionId
|
||||
+scan() ChunkReaderImpl
|
||||
}
|
||||
class RegionMap {
|
||||
-HashMap<RegionId, MitoRegionRef> regions
|
||||
}
|
||||
class ChunkReaderImpl
|
||||
|
||||
class WorkerGroup {
|
||||
-Vec~RegionWorker~ workers
|
||||
}
|
||||
class RegionWorker {
|
||||
-RegionMap regions
|
||||
-Sender sender
|
||||
-JoinHandle handle
|
||||
}
|
||||
class RegionWorkerThread~LogStore~ {
|
||||
-RegionMap regions
|
||||
-Receiver receiver
|
||||
-Wal~LogStore~ wal
|
||||
-ObjectStore object_store
|
||||
-MemtableBuilderRef memtable_builder
|
||||
-FlushSchedulerRef~LogStore~ flush_scheduler
|
||||
-FlushStrategy flush_strategy
|
||||
-CompactionSchedulerRef~LogStore~ compaction_scheduler
|
||||
-FilePurgerRef file_purger
|
||||
}
|
||||
class Wal~LogStore~ {
|
||||
-LogStore log_store
|
||||
}
|
||||
class MitoConfig
|
||||
|
||||
MitoEngine~LogStore~ o-- MitoConfig
|
||||
MitoEngine~LogStore~ o-- MitoRegion
|
||||
MitoEngine~LogStore~ o-- WorkerGroup
|
||||
MitoRegion o-- VersionControl
|
||||
MitoRegion -- ChunkReaderImpl
|
||||
WorkerGroup o-- RegionWorker
|
||||
RegionWorker o-- RegionMap
|
||||
RegionWorker -- RegionWorkerThread~LogStore~
|
||||
RegionWorkerThread~LogStore~ o-- RegionMap
|
||||
RegionWorkerThread~LogStore~ o-- Wal~LogStore~
|
||||
```
|
||||
|
||||
We replace the `RegionWriter` with `RegionWorker` to process write requests and DDL requests.
|
||||
|
||||
|
||||
## Metadata
|
||||
We also merge region's metadata with table's metadata. It should make metadata much easier to maintain.
|
||||
```mermaid
|
||||
classDiagram
|
||||
class VersionControl {
|
||||
-CowCell~Version~ version
|
||||
-AtomicU64 committed_sequence
|
||||
}
|
||||
class Version {
|
||||
-RegionMetadataRef metadata
|
||||
-MemtableVersionRef memtables
|
||||
-LevelMetasRef ssts
|
||||
-SequenceNumber flushed_sequence
|
||||
-ManifestVersion manifest_version
|
||||
}
|
||||
class MemtableVersion {
|
||||
-MemtableRef mutable
|
||||
-Vec~MemtableRef~ immutables
|
||||
+mutable_memtable() MemtableRef
|
||||
+immutable_memtables() &[MemtableRef]
|
||||
+freeze_mutable(MemtableRef new_mutable) MemtableVersion
|
||||
}
|
||||
class LevelMetas {
|
||||
-LevelMetaVec levels
|
||||
-AccessLayerRef sst_layer
|
||||
-FilePurgerRef file_purger
|
||||
-Option~i64~ compaction_time_window
|
||||
}
|
||||
class LevelMeta {
|
||||
-Level level
|
||||
-HashMap<FileId, FileHandle> files
|
||||
}
|
||||
class FileHandle {
|
||||
-FileMeta meta
|
||||
-bool compacting
|
||||
-AtomicBool deleted
|
||||
-AccessLayerRef sst_layer
|
||||
-FilePurgerRef file_purger
|
||||
}
|
||||
class FileMeta {
|
||||
+RegionId region_id
|
||||
+FileId file_id
|
||||
+Option<Timestamp, Timestamp> time_range
|
||||
+Level level
|
||||
+u64 file_size
|
||||
}
|
||||
|
||||
VersionControl o-- Version
|
||||
Version o-- RegionMetadata
|
||||
Version o-- MemtableVersion
|
||||
Version o-- LevelMetas
|
||||
LevelMetas o-- LevelMeta
|
||||
LevelMeta o-- FileHandle
|
||||
FileHandle o-- FileMeta
|
||||
|
||||
class RegionMetadata {
|
||||
+RegionId region_id
|
||||
+VersionNumber version
|
||||
+SchemaRef table_schema
|
||||
+Vec~usize~ primary_key_indices
|
||||
+Vec~usize~ value_indices
|
||||
+ColumnId next_column_id
|
||||
+TableOptions region_options
|
||||
+DateTime~Utc~ created_on
|
||||
+RegionSchemaRef region_schema
|
||||
}
|
||||
class RegionSchema {
|
||||
-SchemaRef user_schema
|
||||
-StoreSchemaRef store_schema
|
||||
-ColumnsMetadataRef columns
|
||||
}
|
||||
class Schema
|
||||
class StoreSchema {
|
||||
-Vec~ColumnMetadata~ columns
|
||||
-SchemaRef schema
|
||||
-usize row_key_end
|
||||
-usize user_column_end
|
||||
}
|
||||
class ColumnsMetadata {
|
||||
-Vec~ColumnMetadata~ columns
|
||||
-HashMap<String, usize> name_to_col_index
|
||||
-usize row_key_end
|
||||
-usize timestamp_key_index
|
||||
-usize user_column_end
|
||||
}
|
||||
class ColumnMetadata
|
||||
|
||||
RegionMetadata o-- RegionSchema
|
||||
RegionMetadata o-- Schema
|
||||
RegionSchema o-- StoreSchema
|
||||
RegionSchema o-- Schema
|
||||
RegionSchema o-- ColumnsMetadata
|
||||
StoreSchema o-- ColumnsMetadata
|
||||
StoreSchema o-- Schema
|
||||
StoreSchema o-- ColumnMetadata
|
||||
ColumnsMetadata o-- ColumnMetadata
|
||||
```
|
||||
|
||||
# Drawback
|
||||
This is a breaking change.
|
||||
|
||||
# Future Work
|
||||
- Rename `TableEngine` to `RegionEngine`
|
||||
- Simplify schema relationship in the `mito` engine
|
||||
- Refactor the `Datanode` into a `RegionServer`.
|
||||
@@ -30,6 +30,7 @@ pub const TABLE_GLOBAL_KEY_PREFIX: &str = "__tg";
|
||||
pub const TABLE_REGIONAL_KEY_PREFIX: &str = "__tr";
|
||||
|
||||
const ALPHANUMERICS_NAME_PATTERN: &str = "[a-zA-Z_][a-zA-Z0-9_]*";
|
||||
const TABLE_NAME_PATTERN: &str = "[a-zA-Z_:][a-zA-Z0-9_:]*";
|
||||
|
||||
lazy_static! {
|
||||
static ref CATALOG_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
@@ -47,14 +48,14 @@ lazy_static! {
|
||||
|
||||
lazy_static! {
|
||||
static ref TABLE_GLOBAL_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{TABLE_GLOBAL_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})$"
|
||||
"^{TABLE_GLOBAL_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-({TABLE_NAME_PATTERN})$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TABLE_REGIONAL_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{TABLE_REGIONAL_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-([0-9]+)$"
|
||||
"^{TABLE_REGIONAL_KEY_PREFIX}-({ALPHANUMERICS_NAME_PATTERN})-({ALPHANUMERICS_NAME_PATTERN})-({TABLE_NAME_PATTERN})-([0-9]+)$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
@@ -394,4 +395,26 @@ mod tests {
|
||||
let s = r#"{"node_id":1,"regions_id_map":{"1":[0]},"table_info":{"ident":{"table_id":1098,"version":1},"name":"container_cpu_limit","desc":"Created on insertion","catalog_name":"greptime","schema_name":"dd","meta":{"schema":{"column_schemas":[{"name":"container_id","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"container_name","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"docker_image","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"host","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"image_name","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"image_tag","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"interval","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"runtime","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"short_image","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"type","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"dd_value","data_type":{"Float64":{}},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}},{"name":"ts","data_type":{"Timestamp":{"Millisecond":null}},"is_nullable":false,"is_time_index":true,"default_constraint":null,"metadata":{"greptime:time_index":"true"}},{"name":"git.repository_url","data_type":{"String":null},"is_nullable":true,"is_time_index":false,"default_constraint":null,"metadata":{}}],"timestamp_index":11,"version":1},"primary_key_indices":[0,1,2,3,4,5,6,7,8,9,12],"value_indices":[10,11],"engine":"mito","next_column_id":12,"region_numbers":[],"engine_options":{},"options":{},"created_on":"1970-01-01T00:00:00Z"},"table_type":"Base"}}"#;
|
||||
let _ = TableGlobalValue::parse(s).unwrap();
|
||||
}
|
||||
|
||||
fn test_valid_table_patterns(table_name: &str) {
|
||||
assert_eq!(
|
||||
table_name,
|
||||
TableGlobalKey::parse(format!("__tg-catalog-schema-{}", table_name))
|
||||
.unwrap()
|
||||
.table_name
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
table_name,
|
||||
TableRegionalKey::parse(format!("__tr-catalog-schema-{}-0", table_name))
|
||||
.unwrap()
|
||||
.table_name
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_table_name_pattern() {
|
||||
test_valid_table_patterns("cpu:metrics");
|
||||
test_valid_table_patterns(":cpu:metrics");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use futures_util::StreamExt;
|
||||
use snafu::ResultExt;
|
||||
use store_api::storage::ScanRequest;
|
||||
use table::error::{SchemaConversionSnafu, TablesRecordBatchSnafu};
|
||||
use table::metadata::TableType;
|
||||
use table::{Result as TableResult, Table, TableRef};
|
||||
|
||||
use self::columns::InformationSchemaColumns;
|
||||
@@ -102,6 +103,10 @@ impl Table for InformationTable {
|
||||
unreachable!("Should not call table_info() of InformationTable directly")
|
||||
}
|
||||
|
||||
fn table_type(&self) -> table::metadata::TableType {
|
||||
TableType::View
|
||||
}
|
||||
|
||||
async fn scan_to_stream(&self, request: ScanRequest) -> TableResult<SendableRecordBatchStream> {
|
||||
let projection = request.projection;
|
||||
let projected_schema = if let Some(projection) = &projection {
|
||||
|
||||
@@ -59,6 +59,9 @@ pub trait CatalogManager: Send + Sync {
|
||||
/// This method will/should fail if catalog not exist
|
||||
async fn register_schema(&self, request: RegisterSchemaRequest) -> Result<bool>;
|
||||
|
||||
/// Deregisters a database within given catalog/schema to catalog manager
|
||||
async fn deregister_schema(&self, request: DeregisterSchemaRequest) -> Result<bool>;
|
||||
|
||||
/// Registers a table within given catalog/schema to catalog manager,
|
||||
/// returns whether the table registered.
|
||||
///
|
||||
@@ -149,6 +152,12 @@ pub struct DeregisterTableRequest {
|
||||
pub table_name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeregisterSchemaRequest {
|
||||
pub catalog: String,
|
||||
pub schema: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RegisterSchemaRequest {
|
||||
pub catalog: String,
|
||||
|
||||
@@ -41,7 +41,7 @@ use crate::error::{
|
||||
self, CatalogNotFoundSnafu, IllegalManagerStateSnafu, OpenTableSnafu, ReadSystemCatalogSnafu,
|
||||
Result, SchemaExistsSnafu, SchemaNotFoundSnafu, SystemCatalogSnafu,
|
||||
SystemCatalogTypeMismatchSnafu, TableEngineNotFoundSnafu, TableExistsSnafu, TableNotExistSnafu,
|
||||
TableNotFoundSnafu,
|
||||
TableNotFoundSnafu, UnimplementedSnafu,
|
||||
};
|
||||
use crate::information_schema::InformationSchemaProvider;
|
||||
use crate::local::memory::MemoryCatalogManager;
|
||||
@@ -51,8 +51,9 @@ use crate::system::{
|
||||
};
|
||||
use crate::tables::SystemCatalog;
|
||||
use crate::{
|
||||
handle_system_table_request, CatalogManager, CatalogManagerRef, DeregisterTableRequest,
|
||||
RegisterSchemaRequest, RegisterSystemTableRequest, RegisterTableRequest, RenameTableRequest,
|
||||
handle_system_table_request, CatalogManager, CatalogManagerRef, DeregisterSchemaRequest,
|
||||
DeregisterTableRequest, RegisterSchemaRequest, RegisterSystemTableRequest,
|
||||
RegisterTableRequest, RenameTableRequest,
|
||||
};
|
||||
|
||||
/// A `CatalogManager` consists of a system catalog and a bunch of user catalogs.
|
||||
@@ -516,6 +517,13 @@ impl CatalogManager for LocalCatalogManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn deregister_schema(&self, _request: DeregisterSchemaRequest) -> Result<bool> {
|
||||
UnimplementedSnafu {
|
||||
operation: "deregister schema",
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
|
||||
async fn register_system_table(&self, request: RegisterSystemTableRequest) -> Result<()> {
|
||||
self.check_state().await?;
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ use crate::error::{
|
||||
CatalogNotFoundSnafu, Result, SchemaNotFoundSnafu, TableExistsSnafu, TableNotFoundSnafu,
|
||||
};
|
||||
use crate::{
|
||||
CatalogManager, DeregisterTableRequest, RegisterSchemaRequest, RegisterSystemTableRequest,
|
||||
RegisterTableRequest, RenameTableRequest,
|
||||
CatalogManager, DeregisterSchemaRequest, DeregisterTableRequest, RegisterSchemaRequest,
|
||||
RegisterSystemTableRequest, RegisterTableRequest, RenameTableRequest,
|
||||
};
|
||||
|
||||
type SchemaEntries = HashMap<String, HashMap<String, TableRef>>;
|
||||
@@ -150,6 +150,34 @@ impl CatalogManager for MemoryCatalogManager {
|
||||
Ok(registered)
|
||||
}
|
||||
|
||||
async fn deregister_schema(&self, request: DeregisterSchemaRequest) -> Result<bool> {
|
||||
let mut catalogs = self.catalogs.write().unwrap();
|
||||
let schemas = catalogs
|
||||
.get_mut(&request.catalog)
|
||||
.with_context(|| CatalogNotFoundSnafu {
|
||||
catalog_name: &request.catalog,
|
||||
})?;
|
||||
let table_count = schemas
|
||||
.remove(&request.schema)
|
||||
.with_context(|| SchemaNotFoundSnafu {
|
||||
catalog: &request.catalog,
|
||||
schema: &request.schema,
|
||||
})?
|
||||
.len();
|
||||
decrement_gauge!(
|
||||
crate::metrics::METRIC_CATALOG_MANAGER_TABLE_COUNT,
|
||||
table_count as f64,
|
||||
&[crate::metrics::db_label(&request.catalog, &request.schema)],
|
||||
);
|
||||
|
||||
decrement_gauge!(
|
||||
crate::metrics::METRIC_CATALOG_MANAGER_SCHEMA_COUNT,
|
||||
1.0,
|
||||
&[crate::metrics::db_label(&request.catalog, &request.schema)],
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn register_system_table(&self, _request: RegisterSystemTableRequest) -> Result<()> {
|
||||
// TODO(ruihang): support register system table request
|
||||
Ok(())
|
||||
@@ -517,4 +545,42 @@ mod tests {
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_catalog_deregister_schema() {
|
||||
let catalog = MemoryCatalogManager::default();
|
||||
|
||||
// Registers a catalog, a schema, and a table.
|
||||
let catalog_name = "foo_catalog".to_string();
|
||||
let schema_name = "foo_schema".to_string();
|
||||
let table_name = "foo_table".to_string();
|
||||
let schema = RegisterSchemaRequest {
|
||||
catalog: catalog_name.clone(),
|
||||
schema: schema_name.clone(),
|
||||
};
|
||||
let table = RegisterTableRequest {
|
||||
catalog: catalog_name.clone(),
|
||||
schema: schema_name.clone(),
|
||||
table_name,
|
||||
table_id: 0,
|
||||
table: Arc::new(NumbersTable::default()),
|
||||
};
|
||||
catalog
|
||||
.register_catalog(catalog_name.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
catalog.register_schema(schema).await.unwrap();
|
||||
catalog.register_table(table).await.unwrap();
|
||||
|
||||
let request = DeregisterSchemaRequest {
|
||||
catalog: catalog_name.clone(),
|
||||
schema: schema_name.clone(),
|
||||
};
|
||||
|
||||
assert!(catalog.deregister_schema(request).await.unwrap());
|
||||
assert!(!catalog
|
||||
.schema_exist(&catalog_name, &schema_name)
|
||||
.await
|
||||
.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,76 +30,3 @@ pub trait KvCacheInvalidator: Send + Sync {
|
||||
}
|
||||
|
||||
pub type KvCacheInvalidatorRef = Arc<dyn KvCacheInvalidator>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::any::Any;
|
||||
|
||||
use async_stream::stream;
|
||||
use common_meta::kv_backend::{Kv, KvBackend, ValueIter};
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
struct MockKvBackend {}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvBackend for MockKvBackend {
|
||||
type Error = Error;
|
||||
|
||||
fn range<'a, 'b>(&'a self, _key: &[u8]) -> ValueIter<'b, Error>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
Box::pin(stream!({
|
||||
for i in 0..3 {
|
||||
yield Ok(Kv(
|
||||
i.to_string().as_bytes().to_vec(),
|
||||
i.to_string().as_bytes().to_vec(),
|
||||
))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async fn set(&self, _key: &[u8], _val: &[u8]) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn compare_and_set(
|
||||
&self,
|
||||
_key: &[u8],
|
||||
_expect: &[u8],
|
||||
_val: &[u8],
|
||||
) -> Result<Result<(), Option<Vec<u8>>>, Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn delete_range(&self, _key: &[u8], _end: &[u8]) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn move_value(&self, _from_key: &[u8], _to_key: &[u8]) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get() {
|
||||
let backend = MockKvBackend {};
|
||||
|
||||
let result = backend.get(0.to_string().as_bytes()).await;
|
||||
assert_eq!(0.to_string().as_bytes(), result.unwrap().unwrap().0);
|
||||
|
||||
let result = backend.get(1.to_string().as_bytes()).await;
|
||||
assert_eq!(1.to_string().as_bytes(), result.unwrap().unwrap().0);
|
||||
|
||||
let result = backend.get(2.to_string().as_bytes()).await;
|
||||
assert_eq!(2.to_string().as_bytes(), result.unwrap().unwrap().0);
|
||||
|
||||
let result = backend.get(3.to_string().as_bytes()).await;
|
||||
assert!(result.unwrap().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,18 @@ use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_stream::stream;
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_meta::error::Error::{CacheNotGet, GetKvCache};
|
||||
use common_meta::error::{CacheNotGetSnafu, Error, MetaSrvSnafu, Result};
|
||||
use common_meta::kv_backend::{Kv, KvBackend, KvBackendRef, ValueIter};
|
||||
use common_meta::kv_backend::{KvBackend, KvBackendRef, TxnService};
|
||||
use common_meta::rpc::store::{
|
||||
CompareAndPutRequest, DeleteRangeRequest, MoveValueRequest, PutRequest, RangeRequest,
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
use common_telemetry::{info, timer};
|
||||
use common_meta::rpc::KeyValue;
|
||||
use common_telemetry::timer;
|
||||
use meta_client::client::MetaClient;
|
||||
use moka::future::{Cache, CacheBuilder};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
@@ -37,24 +40,29 @@ const CACHE_MAX_CAPACITY: u64 = 10000;
|
||||
const CACHE_TTL_SECOND: u64 = 10 * 60;
|
||||
const CACHE_TTI_SECOND: u64 = 5 * 60;
|
||||
|
||||
pub type CacheBackendRef = Arc<Cache<Vec<u8>, Kv>>;
|
||||
pub type CacheBackendRef = Arc<Cache<Vec<u8>, KeyValue>>;
|
||||
|
||||
pub struct CachedMetaKvBackend {
|
||||
kv_backend: KvBackendRef,
|
||||
cache: CacheBackendRef,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl TxnService for CachedMetaKvBackend {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvBackend for CachedMetaKvBackend {
|
||||
type Error = Error;
|
||||
|
||||
fn range<'a, 'b>(&'a self, key: &[u8]) -> ValueIter<'b, Error>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
self.kv_backend.range(key)
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
async fn get(&self, key: &[u8]) -> Result<Option<Kv>> {
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
|
||||
self.kv_backend.range(req).await
|
||||
}
|
||||
|
||||
async fn get(&self, key: &[u8]) -> Result<Option<KeyValue>> {
|
||||
let _timer = timer!(METRIC_CATALOG_KV_GET);
|
||||
|
||||
let init = async {
|
||||
@@ -81,8 +89,10 @@ impl KvBackend for CachedMetaKvBackend {
|
||||
})
|
||||
}
|
||||
|
||||
async fn set(&self, key: &[u8], val: &[u8]) -> Result<()> {
|
||||
let ret = self.kv_backend.set(key, val).await;
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse> {
|
||||
let key = &req.key.clone();
|
||||
|
||||
let ret = self.kv_backend.put(req).await;
|
||||
|
||||
if ret.is_ok() {
|
||||
self.invalidate_key(key).await;
|
||||
@@ -91,8 +101,72 @@ impl KvBackend for CachedMetaKvBackend {
|
||||
ret
|
||||
}
|
||||
|
||||
async fn delete(&self, key: &[u8]) -> Result<()> {
|
||||
let ret = self.kv_backend.delete_range(key, &[]).await;
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse> {
|
||||
let keys = req
|
||||
.kvs
|
||||
.iter()
|
||||
.map(|kv| kv.key().to_vec())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let resp = self.kv_backend.batch_put(req).await;
|
||||
|
||||
if resp.is_ok() {
|
||||
for key in keys {
|
||||
self.invalidate_key(&key).await;
|
||||
}
|
||||
}
|
||||
|
||||
resp
|
||||
}
|
||||
|
||||
async fn delete_range(&self, mut req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
|
||||
let prev_kv = req.prev_kv;
|
||||
|
||||
req.prev_kv = true;
|
||||
let resp = self.kv_backend.delete_range(req).await;
|
||||
match resp {
|
||||
Ok(mut resp) => {
|
||||
for prev_kv in resp.prev_kvs.iter() {
|
||||
self.invalidate_key(prev_kv.key()).await;
|
||||
}
|
||||
|
||||
if !prev_kv {
|
||||
resp.prev_kvs = vec![];
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn batch_delete(&self, mut req: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
|
||||
let prev_kv = req.prev_kv;
|
||||
|
||||
req.prev_kv = true;
|
||||
let resp = self.kv_backend.batch_delete(req).await;
|
||||
match resp {
|
||||
Ok(mut resp) => {
|
||||
for prev_kv in resp.prev_kvs.iter() {
|
||||
self.invalidate_key(prev_kv.key()).await;
|
||||
}
|
||||
|
||||
if !prev_kv {
|
||||
resp.prev_kvs = vec![];
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse> {
|
||||
self.kv_backend.batch_get(req).await
|
||||
}
|
||||
|
||||
async fn compare_and_put(&self, req: CompareAndPutRequest) -> Result<CompareAndPutResponse> {
|
||||
let key = &req.key.clone();
|
||||
|
||||
let ret = self.kv_backend.compare_and_put(req).await;
|
||||
|
||||
if ret.is_ok() {
|
||||
self.invalidate_key(key).await;
|
||||
@@ -101,28 +175,11 @@ impl KvBackend for CachedMetaKvBackend {
|
||||
ret
|
||||
}
|
||||
|
||||
async fn delete_range(&self, _key: &[u8], _end: &[u8]) -> Result<()> {
|
||||
// TODO(fys): implement it
|
||||
unimplemented!()
|
||||
}
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse> {
|
||||
let from_key = &req.from_key.clone();
|
||||
let to_key = &req.to_key.clone();
|
||||
|
||||
async fn compare_and_set(
|
||||
&self,
|
||||
key: &[u8],
|
||||
expect: &[u8],
|
||||
val: &[u8],
|
||||
) -> Result<std::result::Result<(), Option<Vec<u8>>>> {
|
||||
let ret = self.kv_backend.compare_and_set(key, expect, val).await;
|
||||
|
||||
if ret.is_ok() {
|
||||
self.invalidate_key(key).await;
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
async fn move_value(&self, from_key: &[u8], to_key: &[u8]) -> Result<()> {
|
||||
let ret = self.kv_backend.move_value(from_key, to_key).await;
|
||||
let ret = self.kv_backend.move_value(req).await;
|
||||
|
||||
if ret.is_ok() {
|
||||
self.invalidate_key(from_key).await;
|
||||
@@ -146,15 +203,8 @@ impl KvCacheInvalidator for CachedMetaKvBackend {
|
||||
|
||||
impl CachedMetaKvBackend {
|
||||
pub fn new(client: Arc<MetaClient>) -> Self {
|
||||
let cache = Arc::new(
|
||||
CacheBuilder::new(CACHE_MAX_CAPACITY)
|
||||
.time_to_live(Duration::from_secs(CACHE_TTL_SECOND))
|
||||
.time_to_idle(Duration::from_secs(CACHE_TTI_SECOND))
|
||||
.build(),
|
||||
);
|
||||
let kv_backend = Arc::new(MetaKvBackend { client });
|
||||
|
||||
Self { kv_backend, cache }
|
||||
Self::wrap(kv_backend)
|
||||
}
|
||||
|
||||
pub fn wrap(kv_backend: KvBackendRef) -> Self {
|
||||
@@ -165,7 +215,12 @@ impl CachedMetaKvBackend {
|
||||
.build(),
|
||||
);
|
||||
|
||||
Self { kv_backend, cache }
|
||||
let name = format!("CachedKvBackend({})", kv_backend.name());
|
||||
Self {
|
||||
kv_backend,
|
||||
cache,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cache(&self) -> &CacheBackendRef {
|
||||
@@ -178,108 +233,97 @@ pub struct MetaKvBackend {
|
||||
pub client: Arc<MetaClient>,
|
||||
}
|
||||
|
||||
impl TxnService for MetaKvBackend {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
/// Implement `KvBackend` trait for `MetaKvBackend` instead of opendal's `Accessor` since
|
||||
/// `MetaClient`'s range method can return both keys and values, which can reduce IO overhead
|
||||
/// comparing to `Accessor`'s list and get method.
|
||||
#[async_trait::async_trait]
|
||||
impl KvBackend for MetaKvBackend {
|
||||
type Error = Error;
|
||||
|
||||
fn range<'a, 'b>(&'a self, key: &[u8]) -> ValueIter<'b, Error>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
let key = key.to_vec();
|
||||
Box::pin(stream!({
|
||||
let mut resp = self
|
||||
.client
|
||||
.range(RangeRequest::new().with_prefix(key))
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)?;
|
||||
let kvs = resp.take_kvs();
|
||||
for mut kv in kvs.into_iter() {
|
||||
yield Ok(Kv(kv.take_key(), kv.take_value()))
|
||||
}
|
||||
}))
|
||||
fn name(&self) -> &str {
|
||||
"MetaKvBackend"
|
||||
}
|
||||
|
||||
async fn get(&self, key: &[u8]) -> Result<Option<Kv>> {
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
|
||||
self.client
|
||||
.range(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn get(&self, key: &[u8]) -> Result<Option<KeyValue>> {
|
||||
let mut response = self
|
||||
.client
|
||||
.range(RangeRequest::new().with_key(key))
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)?;
|
||||
Ok(response
|
||||
.take_kvs()
|
||||
.get_mut(0)
|
||||
.map(|kv| Kv(kv.take_key(), kv.take_value())))
|
||||
Ok(response.take_kvs().get_mut(0).map(|kv| KeyValue {
|
||||
key: kv.take_key(),
|
||||
value: kv.take_value(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn set(&self, key: &[u8], val: &[u8]) -> Result<()> {
|
||||
let req = PutRequest::new()
|
||||
.with_key(key.to_vec())
|
||||
.with_value(val.to_vec());
|
||||
let _ = self
|
||||
.client
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse> {
|
||||
self.client
|
||||
.batch_put(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse> {
|
||||
self.client
|
||||
.put(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)?;
|
||||
Ok(())
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn delete_range(&self, key: &[u8], end: &[u8]) -> Result<()> {
|
||||
let req = DeleteRangeRequest::new().with_range(key.to_vec(), end.to_vec());
|
||||
let resp = self
|
||||
.client
|
||||
async fn delete_range(&self, req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
|
||||
self.client
|
||||
.delete_range(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)?;
|
||||
info!(
|
||||
"Delete range, key: {}, end: {}, deleted: {}",
|
||||
String::from_utf8_lossy(key),
|
||||
String::from_utf8_lossy(end),
|
||||
resp.deleted()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn compare_and_set(
|
||||
async fn batch_delete(&self, req: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
|
||||
self.client
|
||||
.batch_delete(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse> {
|
||||
self.client
|
||||
.batch_get(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn compare_and_put(
|
||||
&self,
|
||||
key: &[u8],
|
||||
expect: &[u8],
|
||||
val: &[u8],
|
||||
) -> Result<std::result::Result<(), Option<Vec<u8>>>> {
|
||||
let request = CompareAndPutRequest::new()
|
||||
.with_key(key.to_vec())
|
||||
.with_expect(expect.to_vec())
|
||||
.with_value(val.to_vec());
|
||||
let mut response = self
|
||||
.client
|
||||
request: CompareAndPutRequest,
|
||||
) -> Result<CompareAndPutResponse> {
|
||||
self.client
|
||||
.compare_and_put(request)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)?;
|
||||
if response.is_success() {
|
||||
Ok(Ok(()))
|
||||
} else {
|
||||
Ok(Err(response.take_prev_kv().map(|v| v.value().to_vec())))
|
||||
}
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
async fn move_value(&self, from_key: &[u8], to_key: &[u8]) -> Result<()> {
|
||||
let req = MoveValueRequest::new(from_key, to_key);
|
||||
let _ = self
|
||||
.client
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse> {
|
||||
self.client
|
||||
.move_value(req)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(MetaSrvSnafu)?;
|
||||
Ok(())
|
||||
.context(MetaSrvSnafu)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
|
||||
@@ -14,17 +14,15 @@
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashSet;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_stream::stream;
|
||||
use async_trait::async_trait;
|
||||
use common_catalog::consts::{MAX_SYS_TABLE_ID, MITO_ENGINE};
|
||||
use common_meta::ident::TableIdent;
|
||||
use common_meta::kv_backend::{Kv, KvBackendRef};
|
||||
use common_meta::kv_backend::KvBackendRef;
|
||||
use common_meta::rpc::store::{PutRequest, RangeRequest};
|
||||
use common_meta::rpc::KeyValue;
|
||||
use common_telemetry::{debug, error, info, warn};
|
||||
use futures::Stream;
|
||||
use futures_util::{StreamExt, TryStreamExt};
|
||||
use metrics::{decrement_gauge, increment_gauge};
|
||||
use snafu::ResultExt;
|
||||
use table::engine::manager::TableEngineManagerRef;
|
||||
@@ -36,7 +34,7 @@ use tokio::sync::Mutex;
|
||||
use crate::error::{
|
||||
CatalogNotFoundSnafu, CreateTableSnafu, InvalidCatalogValueSnafu, OpenTableSnafu,
|
||||
ParallelOpenTableSnafu, Result, SchemaNotFoundSnafu, TableEngineNotFoundSnafu,
|
||||
TableMetadataManagerSnafu,
|
||||
TableMetadataManagerSnafu, UnimplementedSnafu,
|
||||
};
|
||||
use crate::helper::{
|
||||
build_catalog_prefix, build_schema_prefix, build_table_global_prefix,
|
||||
@@ -45,8 +43,8 @@ use crate::helper::{
|
||||
};
|
||||
use crate::remote::region_alive_keeper::RegionAliveKeepers;
|
||||
use crate::{
|
||||
handle_system_table_request, CatalogManager, DeregisterTableRequest, RegisterSchemaRequest,
|
||||
RegisterSystemTableRequest, RegisterTableRequest, RenameTableRequest,
|
||||
handle_system_table_request, CatalogManager, DeregisterSchemaRequest, DeregisterTableRequest,
|
||||
RegisterSchemaRequest, RegisterSystemTableRequest, RegisterTableRequest, RenameTableRequest,
|
||||
};
|
||||
|
||||
/// Catalog manager based on metasrv.
|
||||
@@ -74,35 +72,39 @@ impl RemoteCatalogManager {
|
||||
}
|
||||
}
|
||||
|
||||
async fn iter_remote_catalogs(
|
||||
&self,
|
||||
) -> Pin<Box<dyn Stream<Item = Result<CatalogKey>> + Send + '_>> {
|
||||
async fn iter_remote_catalogs(&self) -> Result<Vec<CatalogKey>> {
|
||||
let catalog_range_prefix = build_catalog_prefix();
|
||||
let mut catalogs = self.backend.range(catalog_range_prefix.as_bytes());
|
||||
Box::pin(stream!({
|
||||
while let Some(r) = catalogs.next().await {
|
||||
let Kv(k, _) = r.context(TableMetadataManagerSnafu)?;
|
||||
if !k.starts_with(catalog_range_prefix.as_bytes()) {
|
||||
debug!("Ignoring non-catalog key: {}", String::from_utf8_lossy(&k));
|
||||
continue;
|
||||
}
|
||||
let req = RangeRequest::new().with_prefix(catalog_range_prefix.as_bytes());
|
||||
|
||||
let catalog_key = String::from_utf8_lossy(&k);
|
||||
if let Ok(key) = CatalogKey::parse(&catalog_key) {
|
||||
yield Ok(key)
|
||||
} else {
|
||||
error!("Invalid catalog key: {:?}", catalog_key);
|
||||
let kvs = self
|
||||
.backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
|
||||
let catalogs = kvs
|
||||
.into_iter()
|
||||
.filter_map(|kv| {
|
||||
let catalog_key = String::from_utf8_lossy(kv.key());
|
||||
|
||||
match CatalogKey::parse(&catalog_key) {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
error!(e; "Ignore invalid catalog key {:?}", catalog_key);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
Ok(catalogs)
|
||||
}
|
||||
|
||||
/// Fetch catalogs/schemas/tables from remote catalog manager along with max table id allocated.
|
||||
async fn initiate_catalogs(&self) -> Result<()> {
|
||||
let mut catalogs = self.iter_remote_catalogs().await;
|
||||
let catalogs = self.iter_remote_catalogs().await?;
|
||||
let mut joins = Vec::new();
|
||||
while let Some(r) = catalogs.next().await {
|
||||
let CatalogKey { catalog_name, .. } = r?;
|
||||
for CatalogKey { catalog_name } in catalogs {
|
||||
info!("Fetch catalog from metasrv: {}", catalog_name);
|
||||
|
||||
let node_id = self.node_id;
|
||||
@@ -128,13 +130,11 @@ impl RemoteCatalogManager {
|
||||
schema_name: schema_name.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
let req = PutRequest::new()
|
||||
.with_key(schema_key.as_bytes())
|
||||
.with_value(SchemaValue.as_bytes().context(InvalidCatalogValueSnafu)?);
|
||||
self.backend
|
||||
.set(
|
||||
schema_key.as_bytes(),
|
||||
&SchemaValue {}
|
||||
.as_bytes()
|
||||
.context(InvalidCatalogValueSnafu)?,
|
||||
)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
info!("Created schema '{schema_key}'");
|
||||
@@ -143,13 +143,11 @@ impl RemoteCatalogManager {
|
||||
catalog_name: catalog_name.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
let req = PutRequest::new()
|
||||
.with_key(catalog_key.as_bytes())
|
||||
.with_value(CatalogValue.as_bytes().context(InvalidCatalogValueSnafu)?);
|
||||
self.backend
|
||||
.set(
|
||||
catalog_key.as_bytes(),
|
||||
&CatalogValue {}
|
||||
.as_bytes()
|
||||
.context(InvalidCatalogValueSnafu)?,
|
||||
)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
info!("Created catalog '{catalog_key}");
|
||||
@@ -174,9 +172,7 @@ impl RemoteCatalogManager {
|
||||
schema_name: String,
|
||||
) -> Result<u32> {
|
||||
info!("initializing tables in {}.{}", catalog_name, schema_name);
|
||||
let tables = iter_remote_tables(node_id, &backend, &catalog_name, &schema_name).await;
|
||||
|
||||
let kvs = tables.try_collect::<Vec<_>>().await?;
|
||||
let kvs = iter_remote_tables(node_id, &backend, &catalog_name, &schema_name).await?;
|
||||
let table_num = kvs.len();
|
||||
let joins = kvs
|
||||
.into_iter()
|
||||
@@ -253,15 +249,14 @@ impl RemoteCatalogManager {
|
||||
engine_manager: TableEngineManagerRef,
|
||||
catalog_name: String,
|
||||
) -> Result<()> {
|
||||
let mut schemas = iter_remote_schemas(&backend, &catalog_name).await;
|
||||
let mut joins = Vec::new();
|
||||
while let Some(r) = schemas.next().await {
|
||||
let SchemaKey {
|
||||
catalog_name,
|
||||
schema_name,
|
||||
..
|
||||
} = r?;
|
||||
let schemas = iter_remote_schemas(&backend, &catalog_name).await?;
|
||||
|
||||
let mut joins = Vec::new();
|
||||
for SchemaKey {
|
||||
catalog_name,
|
||||
schema_name,
|
||||
} in schemas
|
||||
{
|
||||
info!(
|
||||
"Fetch schema from metasrv: {}.{}",
|
||||
&catalog_name, &schema_name
|
||||
@@ -314,11 +309,11 @@ impl RemoteCatalogManager {
|
||||
let table_key = self
|
||||
.build_regional_table_key(catalog_name, schema_name, table_name)
|
||||
.to_string();
|
||||
let req = PutRequest::new()
|
||||
.with_key(table_key.as_bytes())
|
||||
.with_value(table_value.as_bytes().context(InvalidCatalogValueSnafu)?);
|
||||
self.backend
|
||||
.set(
|
||||
table_key.as_bytes(),
|
||||
&table_value.as_bytes().context(InvalidCatalogValueSnafu)?,
|
||||
)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
debug!(
|
||||
@@ -349,7 +344,7 @@ impl RemoteCatalogManager {
|
||||
.get(table_key.as_bytes())
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.map(|Kv(_, v)| {
|
||||
.map(|KeyValue { key: _, value: v }| {
|
||||
let TableRegionalValue {
|
||||
table_id,
|
||||
engine_name,
|
||||
@@ -367,7 +362,7 @@ impl RemoteCatalogManager {
|
||||
};
|
||||
|
||||
self.backend
|
||||
.delete(table_key.as_bytes())
|
||||
.delete(table_key.as_bytes(), false)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
debug!(
|
||||
@@ -430,23 +425,30 @@ impl RemoteCatalogManager {
|
||||
async fn iter_remote_schemas<'a>(
|
||||
backend: &'a KvBackendRef,
|
||||
catalog_name: &'a str,
|
||||
) -> Pin<Box<dyn Stream<Item = Result<SchemaKey>> + Send + 'a>> {
|
||||
) -> Result<Vec<SchemaKey>> {
|
||||
let schema_prefix = build_schema_prefix(catalog_name);
|
||||
let mut schemas = backend.range(schema_prefix.as_bytes());
|
||||
let req = RangeRequest::new().with_prefix(schema_prefix.as_bytes());
|
||||
|
||||
Box::pin(stream!({
|
||||
while let Some(r) = schemas.next().await {
|
||||
let Kv(k, _) = r.context(TableMetadataManagerSnafu)?;
|
||||
if !k.starts_with(schema_prefix.as_bytes()) {
|
||||
debug!("Ignoring non-schema key: {}", String::from_utf8_lossy(&k));
|
||||
continue;
|
||||
let kvs = backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
|
||||
let schemas = kvs
|
||||
.into_iter()
|
||||
.filter_map(|kv| {
|
||||
let schema_key = String::from_utf8_lossy(kv.key());
|
||||
match SchemaKey::parse(&schema_key) {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
warn!("Ignore invalid schema key {:?}: {e}", schema_key);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
let schema_key =
|
||||
SchemaKey::parse(&String::from_utf8_lossy(&k)).context(InvalidCatalogValueSnafu)?;
|
||||
yield Ok(schema_key)
|
||||
}
|
||||
}))
|
||||
})
|
||||
.collect();
|
||||
Ok(schemas)
|
||||
}
|
||||
|
||||
/// Iterate over all table entries on metasrv
|
||||
@@ -455,35 +457,42 @@ async fn iter_remote_tables<'a>(
|
||||
backend: &'a KvBackendRef,
|
||||
catalog_name: &'a str,
|
||||
schema_name: &'a str,
|
||||
) -> Pin<Box<dyn Stream<Item = Result<(TableGlobalKey, TableGlobalValue)>> + Send + 'a>> {
|
||||
) -> Result<Vec<(TableGlobalKey, TableGlobalValue)>> {
|
||||
let table_prefix = build_table_global_prefix(catalog_name, schema_name);
|
||||
let mut tables = backend.range(table_prefix.as_bytes());
|
||||
Box::pin(stream!({
|
||||
while let Some(r) = tables.next().await {
|
||||
let Kv(k, v) = r.context(TableMetadataManagerSnafu)?;
|
||||
if !k.starts_with(table_prefix.as_bytes()) {
|
||||
debug!("Ignoring non-table prefix: {}", String::from_utf8_lossy(&k));
|
||||
continue;
|
||||
}
|
||||
let table_key = TableGlobalKey::parse(&String::from_utf8_lossy(&k))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
let table_value = TableGlobalValue::from_bytes(&v).context(InvalidCatalogValueSnafu)?;
|
||||
let req = RangeRequest::new().with_prefix(table_prefix.as_bytes());
|
||||
|
||||
info!(
|
||||
"Found catalog table entry, key: {}, value: {:?}",
|
||||
table_key, table_value
|
||||
);
|
||||
// metasrv has allocated region ids to current datanode
|
||||
if table_value
|
||||
.regions_id_map
|
||||
.get(&node_id)
|
||||
.map(|v| !v.is_empty())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
yield Ok((table_key, table_value))
|
||||
}
|
||||
let kvs = backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
|
||||
let mut tables = Vec::with_capacity(kvs.len());
|
||||
for kv in kvs {
|
||||
let tgk = &String::from_utf8_lossy(kv.key());
|
||||
let Ok(table_key) = TableGlobalKey::parse(tgk) else {
|
||||
warn!("Ignore invalid table global key {:?}", tgk);
|
||||
continue;
|
||||
};
|
||||
|
||||
let Ok(table_value) = TableGlobalValue::from_bytes(kv.value()) else {
|
||||
warn!("Ignore invalid table global value {:?}", String::from_utf8_lossy(kv.value()));
|
||||
continue;
|
||||
};
|
||||
|
||||
info!("Found catalog table entry, key: {table_key}, value: {table_value:?}");
|
||||
|
||||
// metasrv has allocated region ids to current datanode
|
||||
if table_value
|
||||
.regions_id_map
|
||||
.get(&node_id)
|
||||
.map(|v| !v.is_empty())
|
||||
.unwrap_or(false)
|
||||
{
|
||||
tables.push((table_key, table_value))
|
||||
}
|
||||
}))
|
||||
}
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
async fn print_regional_key_debug_info(
|
||||
@@ -500,7 +509,10 @@ async fn print_regional_key_debug_info(
|
||||
.to_string();
|
||||
|
||||
match backend.get(regional_key.as_bytes()).await {
|
||||
Ok(Some(Kv(_, values_bytes))) => {
|
||||
Ok(Some(KeyValue {
|
||||
key: _,
|
||||
value: values_bytes,
|
||||
})) => {
|
||||
debug!(
|
||||
"Node id: {}, TableRegionalKey: {}, value: {},",
|
||||
node_id,
|
||||
@@ -702,13 +714,11 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
let catalog_name = request.catalog;
|
||||
let schema_name = request.schema;
|
||||
let key = self.build_schema_key(catalog_name, schema_name).to_string();
|
||||
let req = PutRequest::new()
|
||||
.with_key(key.as_bytes())
|
||||
.with_value(SchemaValue.as_bytes().context(InvalidCatalogValueSnafu)?);
|
||||
self.backend
|
||||
.set(
|
||||
key.as_bytes(),
|
||||
&SchemaValue {}
|
||||
.as_bytes()
|
||||
.context(InvalidCatalogValueSnafu)?,
|
||||
)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
|
||||
@@ -716,6 +726,13 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
async fn deregister_schema(&self, _request: DeregisterSchemaRequest) -> Result<bool> {
|
||||
UnimplementedSnafu {
|
||||
operation: "deregister schema",
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
|
||||
async fn rename_table(&self, request: RenameTableRequest) -> Result<bool> {
|
||||
let catalog_name = request.catalog.clone();
|
||||
let schema_name = request.schema.clone();
|
||||
@@ -729,21 +746,27 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
node_id: self.node_id,
|
||||
}
|
||||
.to_string();
|
||||
let Some(Kv(_, value_bytes)) = self.backend.get(old_table_key.as_bytes()).await.context(TableMetadataManagerSnafu)? else {
|
||||
return Ok(false)
|
||||
};
|
||||
let Some(KeyValue{ key: _, value }) = self.backend
|
||||
.get(old_table_key.as_bytes())
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)? else {
|
||||
return Ok(false)
|
||||
};
|
||||
let new_table_key = TableRegionalKey {
|
||||
catalog_name: request.catalog.clone(),
|
||||
schema_name: request.schema.clone(),
|
||||
table_name: request.new_table_name,
|
||||
node_id: self.node_id,
|
||||
};
|
||||
let req = PutRequest::new()
|
||||
.with_key(new_table_key.to_string().as_bytes())
|
||||
.with_value(value);
|
||||
self.backend
|
||||
.set(new_table_key.to_string().as_bytes(), &value_bytes)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
self.backend
|
||||
.delete(old_table_key.to_string().as_bytes())
|
||||
.delete(old_table_key.to_string().as_bytes(), false)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
Ok(true)
|
||||
@@ -796,7 +819,7 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
.get(key.as_bytes())
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.map(|Kv(_, v)| {
|
||||
.map(|KeyValue { key: _, value: v }| {
|
||||
let TableRegionalValue {
|
||||
table_id,
|
||||
engine_name,
|
||||
@@ -863,16 +886,19 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
}
|
||||
|
||||
async fn catalog_names(&self) -> Result<Vec<String>> {
|
||||
let mut stream = self.backend.range(CATALOG_KEY_PREFIX.as_bytes());
|
||||
let req = RangeRequest::new().with_prefix(CATALOG_KEY_PREFIX.as_bytes());
|
||||
let kvs = self
|
||||
.backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
let mut catalogs = HashSet::new();
|
||||
|
||||
while let Some(catalog) = stream.next().await {
|
||||
if let Ok(catalog) = catalog {
|
||||
let catalog_key = String::from_utf8_lossy(&catalog.0);
|
||||
|
||||
if let Ok(key) = CatalogKey::parse(&catalog_key) {
|
||||
let _ = catalogs.insert(key.catalog_name);
|
||||
}
|
||||
for catalog in kvs {
|
||||
let catalog_key = String::from_utf8_lossy(catalog.key());
|
||||
if let Ok(key) = CatalogKey::parse(&catalog_key) {
|
||||
let _ = catalogs.insert(key.catalog_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -880,18 +906,19 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
}
|
||||
|
||||
async fn schema_names(&self, catalog_name: &str) -> Result<Vec<String>> {
|
||||
let mut stream = self
|
||||
let req = RangeRequest::new().with_prefix(build_schema_prefix(catalog_name).as_bytes());
|
||||
let kvs = self
|
||||
.backend
|
||||
.range(build_schema_prefix(catalog_name).as_bytes());
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
let mut schemas = HashSet::new();
|
||||
|
||||
while let Some(schema) = stream.next().await {
|
||||
if let Ok(schema) = schema {
|
||||
let schema_key = String::from_utf8_lossy(&schema.0);
|
||||
|
||||
if let Ok(key) = SchemaKey::parse(&schema_key) {
|
||||
let _ = schemas.insert(key.schema_name);
|
||||
}
|
||||
for schema in kvs {
|
||||
let schema_key = String::from_utf8_lossy(schema.key());
|
||||
if let Ok(key) = SchemaKey::parse(&schema_key) {
|
||||
let _ = schemas.insert(key.schema_name);
|
||||
}
|
||||
}
|
||||
Ok(schemas.into_iter().collect())
|
||||
@@ -901,18 +928,20 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
self.check_catalog_schema_exist(catalog_name, schema_name)
|
||||
.await?;
|
||||
|
||||
let mut stream = self
|
||||
let req = RangeRequest::new()
|
||||
.with_prefix(build_table_regional_prefix(catalog_name, schema_name).as_bytes());
|
||||
let kvs = self
|
||||
.backend
|
||||
.range(build_table_regional_prefix(catalog_name, schema_name).as_bytes());
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
let mut tables = HashSet::new();
|
||||
|
||||
while let Some(table) = stream.next().await {
|
||||
if let Ok(table) = table {
|
||||
let table_key = String::from_utf8_lossy(&table.0);
|
||||
|
||||
if let Ok(key) = TableRegionalKey::parse(&table_key) {
|
||||
let _ = tables.insert(key.table_name);
|
||||
}
|
||||
for table in kvs {
|
||||
let table_key = String::from_utf8_lossy(table.key());
|
||||
if let Ok(key) = TableRegionalKey::parse(&table_key) {
|
||||
let _ = tables.insert(key.table_name);
|
||||
}
|
||||
}
|
||||
Ok(tables.into_iter().collect())
|
||||
@@ -921,13 +950,11 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
async fn register_catalog(&self, name: String) -> Result<bool> {
|
||||
let key = CatalogKey { catalog_name: name }.to_string();
|
||||
// TODO(hl): use compare_and_swap to prevent concurrent update
|
||||
let req = PutRequest::new()
|
||||
.with_key(key.as_bytes())
|
||||
.with_value(CatalogValue.as_bytes().context(InvalidCatalogValueSnafu)?);
|
||||
self.backend
|
||||
.set(
|
||||
key.as_bytes(),
|
||||
&CatalogValue {}
|
||||
.as_bytes()
|
||||
.context(InvalidCatalogValueSnafu)?,
|
||||
)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
increment_gauge!(crate::metrics::METRIC_CATALOG_MANAGER_CATALOG_COUNT, 1.0);
|
||||
|
||||
@@ -21,6 +21,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use catalog::error::Error;
|
||||
use catalog::helper::{CatalogKey, CatalogValue, SchemaKey, SchemaValue};
|
||||
use catalog::remote::mock::MockTableEngine;
|
||||
use catalog::remote::region_alive_keeper::RegionAliveKeepers;
|
||||
@@ -30,8 +31,8 @@ mod tests {
|
||||
use common_meta::ident::TableIdent;
|
||||
use common_meta::kv_backend::memory::MemoryKvBackend;
|
||||
use common_meta::kv_backend::KvBackend;
|
||||
use common_meta::rpc::store::{CompareAndPutRequest, PutRequest, RangeRequest};
|
||||
use datatypes::schema::RawSchema;
|
||||
use futures_util::StreamExt;
|
||||
use table::engine::manager::{MemoryTableEngineManager, TableEngineManagerRef};
|
||||
use table::engine::{EngineContext, TableEngineRef};
|
||||
use table::requests::CreateTableRequest;
|
||||
@@ -52,38 +53,35 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_backend() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
let backend = MemoryKvBackend::default();
|
||||
let backend = MemoryKvBackend::<Error>::default();
|
||||
|
||||
let default_catalog_key = CatalogKey {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
|
||||
backend
|
||||
.set(
|
||||
default_catalog_key.as_bytes(),
|
||||
&CatalogValue {}.as_bytes().unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let req = PutRequest::new()
|
||||
.with_key(default_catalog_key.as_bytes())
|
||||
.with_value(CatalogValue.as_bytes().unwrap());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
let schema_key = SchemaKey {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
backend
|
||||
.set(schema_key.as_bytes(), &SchemaValue {}.as_bytes().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let req = PutRequest::new()
|
||||
.with_key(schema_key.as_bytes())
|
||||
.with_value(SchemaValue.as_bytes().unwrap());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
let mut iter = backend.range("__c-".as_bytes());
|
||||
let mut res = HashSet::new();
|
||||
while let Some(r) = iter.next().await {
|
||||
let kv = r.unwrap();
|
||||
let _ = res.insert(String::from_utf8_lossy(&kv.0).to_string());
|
||||
}
|
||||
let req = RangeRequest::new().with_prefix(b"__c-".to_vec());
|
||||
let res = backend
|
||||
.range(req)
|
||||
.await
|
||||
.unwrap()
|
||||
.kvs
|
||||
.into_iter()
|
||||
.map(|kv| String::from_utf8_lossy(kv.key()).to_string());
|
||||
assert_eq!(
|
||||
vec!["__c-greptime".to_string()],
|
||||
res.into_iter().collect::<Vec<_>>()
|
||||
@@ -98,36 +96,32 @@ mod tests {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
|
||||
backend
|
||||
.set(
|
||||
default_catalog_key.as_bytes(),
|
||||
&CatalogValue {}.as_bytes().unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let req = PutRequest::new()
|
||||
.with_key(default_catalog_key.as_bytes())
|
||||
.with_value(CatalogValue.as_bytes().unwrap());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
let ret = backend.get(b"__c-greptime").await.unwrap();
|
||||
let _ = ret.unwrap();
|
||||
|
||||
let _ = backend
|
||||
.compare_and_set(
|
||||
b"__c-greptime",
|
||||
&CatalogValue {}.as_bytes().unwrap(),
|
||||
b"123",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let req = CompareAndPutRequest::new()
|
||||
.with_key(b"__c-greptime".to_vec())
|
||||
.with_expect(CatalogValue.as_bytes().unwrap())
|
||||
.with_value(b"123".to_vec());
|
||||
let _ = backend.compare_and_put(req).await.unwrap();
|
||||
|
||||
let ret = backend.get(b"__c-greptime").await.unwrap();
|
||||
assert_eq!(&b"123"[..], &(ret.as_ref().unwrap().1));
|
||||
assert_eq!(b"123", ret.as_ref().unwrap().value.as_slice());
|
||||
|
||||
let _ = backend.set(b"__c-greptime", b"1234").await;
|
||||
let req = PutRequest::new()
|
||||
.with_key(b"__c-greptime".to_vec())
|
||||
.with_value(b"1234".to_vec());
|
||||
let _ = backend.put(req).await;
|
||||
|
||||
let ret = backend.get(b"__c-greptime").await.unwrap();
|
||||
assert_eq!(&b"1234"[..], &(ret.as_ref().unwrap().1));
|
||||
assert_eq!(b"1234", ret.unwrap().value.as_slice());
|
||||
|
||||
backend.delete(b"__c-greptime").await.unwrap();
|
||||
backend.delete(b"__c-greptime", false).await.unwrap();
|
||||
|
||||
let ret = backend.get(b"__c-greptime").await.unwrap();
|
||||
assert!(ret.is_none());
|
||||
@@ -135,8 +129,16 @@ mod tests {
|
||||
|
||||
async fn prepare_components(node_id: u64) -> TestingComponents {
|
||||
let backend = Arc::new(MemoryKvBackend::default());
|
||||
backend.set(b"__c-greptime", b"").await.unwrap();
|
||||
backend.set(b"__s-greptime-public", b"").await.unwrap();
|
||||
|
||||
let req = PutRequest::new()
|
||||
.with_key(b"__c-greptime".to_vec())
|
||||
.with_value(b"".to_vec());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
let req = PutRequest::new()
|
||||
.with_key(b"__s-greptime-public".to_vec())
|
||||
.with_value(b"".to_vec());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
let cached_backend = Arc::new(CachedMetaKvBackend::wrap(backend));
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
mod client;
|
||||
pub mod client_manager;
|
||||
mod database;
|
||||
mod error;
|
||||
pub mod error;
|
||||
pub mod load_balance;
|
||||
mod metrics;
|
||||
mod stream_insert;
|
||||
|
||||
@@ -273,6 +273,7 @@ mod tests {
|
||||
ObjectStoreConfig::S3 { .. } => unreachable!(),
|
||||
ObjectStoreConfig::Oss { .. } => unreachable!(),
|
||||
ObjectStoreConfig::Azblob { .. } => unreachable!(),
|
||||
ObjectStoreConfig::Gcs { .. } => unreachable!(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -24,7 +24,7 @@ datafusion.workspace = true
|
||||
derive_builder = "0.12"
|
||||
futures.workspace = true
|
||||
object-store = { path = "../../object-store" }
|
||||
orc-rust = { git = "https://github.com/WenyXu/orc-rs.git", rev = "0319acd32456e403c20f135cc012441a76852605" }
|
||||
orc-rust = "0.2"
|
||||
regex = "1.7"
|
||||
snafu.workspace = true
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -20,7 +20,7 @@ use snafu::ResultExt;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
|
||||
const ENDPOINT_URL: &str = "endpoint_url";
|
||||
const ENDPOINT: &str = "endpoint";
|
||||
const ACCESS_KEY_ID: &str = "access_key_id";
|
||||
const SECRET_ACCESS_KEY: &str = "secret_access_key";
|
||||
const SESSION_TOKEN: &str = "session_token";
|
||||
@@ -36,7 +36,7 @@ pub fn build_s3_backend(
|
||||
|
||||
let _ = builder.root(path).bucket(host);
|
||||
|
||||
if let Some(endpoint) = connection.get(ENDPOINT_URL) {
|
||||
if let Some(endpoint) = connection.get(ENDPOINT) {
|
||||
let _ = builder.endpoint(endpoint);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,6 +240,7 @@ mod tests {
|
||||
location: None,
|
||||
}],
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let alter_request = alter_expr_to_request(1, expr).unwrap();
|
||||
@@ -297,6 +298,7 @@ mod tests {
|
||||
},
|
||||
],
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let alter_request = alter_expr_to_request(1, expr).unwrap();
|
||||
@@ -345,6 +347,7 @@ mod tests {
|
||||
name: "mem_usage".to_string(),
|
||||
}],
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let alter_request = alter_expr_to_request(1, expr).unwrap();
|
||||
|
||||
@@ -13,8 +13,11 @@ common-error = { path = "../error" }
|
||||
common-runtime = { path = "../runtime" }
|
||||
common-telemetry = { path = "../telemetry" }
|
||||
common-time = { path = "../time" }
|
||||
etcd-client.workspace = true
|
||||
futures.workspace = true
|
||||
lazy_static.workspace = true
|
||||
prost.workspace = true
|
||||
regex.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
snafu.workspace = true
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
use common_error::prelude::*;
|
||||
use serde_json::error::Error as JsonError;
|
||||
use snafu::Location;
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::metadata::TableId;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
@@ -56,6 +58,9 @@ pub enum Error {
|
||||
#[snafu(display("Invalid protobuf message, err: {}", err_msg))]
|
||||
InvalidProtoMsg { err_msg: String, location: Location },
|
||||
|
||||
#[snafu(display("Concurrent modify regions placement: {err_msg}"))]
|
||||
ConcurrentModifyRegionsPlacement { err_msg: String, location: Location },
|
||||
|
||||
#[snafu(display("Invalid table metadata, err: {}", err_msg))]
|
||||
InvalidTableMetadata { err_msg: String, location: Location },
|
||||
|
||||
@@ -70,6 +75,22 @@ pub enum Error {
|
||||
source: BoxedError,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Etcd txn error: {err_msg}"))]
|
||||
EtcdTxnOpResponse { err_msg: String, location: Location },
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to move region {} in table {}, err: {}",
|
||||
region,
|
||||
table_id,
|
||||
err_msg
|
||||
))]
|
||||
MoveRegion {
|
||||
table_id: TableId,
|
||||
region: RegionNumber,
|
||||
err_msg: String,
|
||||
location: Location,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -78,14 +99,18 @@ impl ErrorExt for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
use Error::*;
|
||||
match self {
|
||||
IllegalServerState { .. } => StatusCode::Internal,
|
||||
IllegalServerState { .. } | EtcdTxnOpResponse { .. } => StatusCode::Internal,
|
||||
|
||||
SerdeJson { .. }
|
||||
| RouteInfoCorrupted { .. }
|
||||
| InvalidProtoMsg { .. }
|
||||
| InvalidTableMetadata { .. } => StatusCode::Unexpected,
|
||||
| InvalidTableMetadata { .. }
|
||||
| MoveRegion { .. } => StatusCode::Unexpected,
|
||||
|
||||
SendMessage { .. } | GetKvCache { .. } | CacheNotGet { .. } => StatusCode::Internal,
|
||||
SendMessage { .. }
|
||||
| GetKvCache { .. }
|
||||
| CacheNotGet { .. }
|
||||
| ConcurrentModifyRegionsPlacement { .. } => StatusCode::Internal,
|
||||
|
||||
EncodeJson { .. } | DecodeJson { .. } | PayloadNotExist { .. } => {
|
||||
StatusCode::Unexpected
|
||||
|
||||
@@ -15,12 +15,22 @@
|
||||
//! This mod defines all the keys used in the metadata store (Metasrv).
|
||||
//! Specifically, there are these kinds of keys:
|
||||
//!
|
||||
//! 1. Table info key: `__table_info/{table_id}`
|
||||
//! 1. Datanode table key: `__dn_table/{datanode_id}/{table_id}`
|
||||
//! - The value is a [DatanodeTableValue] struct; it contains `table_id` and the regions that
|
||||
//! belong to this Datanode.
|
||||
//! - This key is primary used in the startup of Datanode, to let Datanode know which tables
|
||||
//! and regions it should open.
|
||||
//!
|
||||
//! 2. Table info key: `__table_info/{table_id}`
|
||||
//! - The value is a [TableInfoValue] struct; it contains the whole table info (like column
|
||||
//! schemas).
|
||||
//! - This key is mainly used in constructing the table in Datanode and Frontend.
|
||||
//!
|
||||
//! 2. Table region key: `__table_region/{table_id}`
|
||||
//! 3. Table name key: `__table_name/{catalog_name}/{schema_name}/{table_name}`
|
||||
//! - The value is a [TableNameValue] struct; it contains the table id.
|
||||
//! - Used in the table name to table id lookup.
|
||||
//!
|
||||
//! 4. Table region key: `__table_region/{table_id}`
|
||||
//! - The value is a [TableRegionValue] struct; it contains the region distribution of the
|
||||
//! table in the Datanodes.
|
||||
//!
|
||||
@@ -31,14 +41,20 @@
|
||||
//! table metadata manager: [TableMetadataManager]. It contains all the managers defined above.
|
||||
//! It's recommended to just use this manager only.
|
||||
|
||||
pub mod datanode_table;
|
||||
pub mod table_info;
|
||||
pub mod table_name;
|
||||
pub mod table_region;
|
||||
mod table_route;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use datanode_table::{DatanodeTableManager, DatanodeTableValue};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use snafu::ResultExt;
|
||||
use table_info::{TableInfoManager, TableInfoValue};
|
||||
use table_name::{TableNameManager, TableNameValue};
|
||||
use table_region::{TableRegionManager, TableRegionValue};
|
||||
|
||||
use crate::error::{InvalidTableMetadataSnafu, Result, SerdeJsonSnafu};
|
||||
@@ -47,9 +63,25 @@ use crate::kv_backend::KvBackendRef;
|
||||
|
||||
pub const REMOVED_PREFIX: &str = "__removed";
|
||||
|
||||
const TABLE_NAME_PATTERN: &str = "[a-zA-Z_:][a-zA-Z0-9_:]*";
|
||||
|
||||
const DATANODE_TABLE_KEY_PREFIX: &str = "__dn_table";
|
||||
const TABLE_INFO_KEY_PREFIX: &str = "__table_info";
|
||||
const TABLE_NAME_KEY_PREFIX: &str = "__table_name";
|
||||
const TABLE_REGION_KEY_PREFIX: &str = "__table_region";
|
||||
|
||||
lazy_static! {
|
||||
static ref DATANODE_TABLE_KEY_PATTERN: Regex =
|
||||
Regex::new(&format!("^{DATANODE_TABLE_KEY_PREFIX}/([0-9])/([0-9])$")).unwrap();
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref TABLE_NAME_KEY_PATTERN: Regex = Regex::new(&format!(
|
||||
"^{TABLE_NAME_KEY_PREFIX}/({TABLE_NAME_PATTERN})/({TABLE_NAME_PATTERN})/({TABLE_NAME_PATTERN})$"
|
||||
))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn to_removed_key(key: &str) -> String {
|
||||
format!("{REMOVED_PREFIX}-{key}")
|
||||
}
|
||||
@@ -61,18 +93,26 @@ pub trait TableMetaKey {
|
||||
pub type TableMetadataManagerRef = Arc<TableMetadataManager>;
|
||||
|
||||
pub struct TableMetadataManager {
|
||||
table_name_manager: TableNameManager,
|
||||
table_info_manager: TableInfoManager,
|
||||
table_region_manager: TableRegionManager,
|
||||
datanode_table_manager: DatanodeTableManager,
|
||||
}
|
||||
|
||||
impl TableMetadataManager {
|
||||
pub fn new(kv_backend: KvBackendRef) -> Self {
|
||||
TableMetadataManager {
|
||||
table_name_manager: TableNameManager::new(kv_backend.clone()),
|
||||
table_info_manager: TableInfoManager::new(kv_backend.clone()),
|
||||
table_region_manager: TableRegionManager::new(kv_backend),
|
||||
table_region_manager: TableRegionManager::new(kv_backend.clone()),
|
||||
datanode_table_manager: DatanodeTableManager::new(kv_backend),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_name_manager(&self) -> &TableNameManager {
|
||||
&self.table_name_manager
|
||||
}
|
||||
|
||||
pub fn table_info_manager(&self) -> &TableInfoManager {
|
||||
&self.table_info_manager
|
||||
}
|
||||
@@ -80,6 +120,10 @@ impl TableMetadataManager {
|
||||
pub fn table_region_manager(&self) -> &TableRegionManager {
|
||||
&self.table_region_manager
|
||||
}
|
||||
|
||||
pub fn datanode_table_manager(&self) -> &DatanodeTableManager {
|
||||
&self.datanode_table_manager
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_table_meta_value {
|
||||
@@ -104,8 +148,10 @@ macro_rules! impl_table_meta_value {
|
||||
}
|
||||
|
||||
impl_table_meta_value! {
|
||||
TableNameValue,
|
||||
TableInfoValue,
|
||||
TableRegionValue
|
||||
TableRegionValue,
|
||||
DatanodeTableValue
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
440
src/common/meta/src/key/datanode_table.rs
Normal file
440
src/common/meta/src/key/datanode_table.rs
Normal file
@@ -0,0 +1,440 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, OptionExt};
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use super::{DATANODE_TABLE_KEY_PATTERN, DATANODE_TABLE_KEY_PREFIX};
|
||||
use crate::error::{
|
||||
ConcurrentModifyRegionsPlacementSnafu, InvalidTableMetadataSnafu, MoveRegionSnafu, Result,
|
||||
};
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest, RangeRequest};
|
||||
use crate::DatanodeId;
|
||||
|
||||
struct DatanodeTableKey {
|
||||
datanode_id: DatanodeId,
|
||||
table_id: TableId,
|
||||
}
|
||||
|
||||
impl DatanodeTableKey {
|
||||
fn new(datanode_id: DatanodeId, table_id: TableId) -> Self {
|
||||
Self {
|
||||
datanode_id,
|
||||
table_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn prefix(datanode_id: DatanodeId) -> String {
|
||||
format!("{}/{datanode_id}", DATANODE_TABLE_KEY_PREFIX)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn strip_table_id(raw_key: &[u8]) -> Result<TableId> {
|
||||
let key = String::from_utf8(raw_key.to_vec()).map_err(|e| {
|
||||
InvalidTableMetadataSnafu {
|
||||
err_msg: format!(
|
||||
"DatanodeTableKey '{}' is not a valid UTF8 string: {e}",
|
||||
String::from_utf8_lossy(raw_key)
|
||||
),
|
||||
}
|
||||
.build()
|
||||
})?;
|
||||
let captures =
|
||||
DATANODE_TABLE_KEY_PATTERN
|
||||
.captures(&key)
|
||||
.context(InvalidTableMetadataSnafu {
|
||||
err_msg: format!("Invalid DatanodeTableKey '{key}'"),
|
||||
})?;
|
||||
// Safety: pass the regex check above
|
||||
let table_id = captures[2].parse::<TableId>().unwrap();
|
||||
Ok(table_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableMetaKey for DatanodeTableKey {
|
||||
fn as_raw_key(&self) -> Vec<u8> {
|
||||
format!("{}/{}", Self::prefix(self.datanode_id), self.table_id).into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DatanodeTableValue {
|
||||
pub table_id: TableId,
|
||||
pub regions: Vec<RegionNumber>,
|
||||
version: u64,
|
||||
}
|
||||
|
||||
impl DatanodeTableValue {
|
||||
fn new(table_id: TableId, regions: Vec<RegionNumber>) -> Self {
|
||||
Self {
|
||||
table_id,
|
||||
regions,
|
||||
version: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DatanodeTableManager {
|
||||
kv_backend: KvBackendRef,
|
||||
}
|
||||
|
||||
impl DatanodeTableManager {
|
||||
pub fn new(kv_backend: KvBackendRef) -> Self {
|
||||
Self { kv_backend }
|
||||
}
|
||||
|
||||
async fn get(&self, key: &DatanodeTableKey) -> Result<Option<DatanodeTableValue>> {
|
||||
self.kv_backend
|
||||
.get(&key.as_raw_key())
|
||||
.await?
|
||||
.map(|kv| DatanodeTableValue::try_from_raw_value(kv.value))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
&self,
|
||||
datanode_id: DatanodeId,
|
||||
table_id: TableId,
|
||||
regions: Vec<RegionNumber>,
|
||||
) -> Result<()> {
|
||||
let key = DatanodeTableKey::new(datanode_id, table_id).as_raw_key();
|
||||
let val = DatanodeTableValue::new(table_id, regions).try_as_raw_value()?;
|
||||
let req = CompareAndPutRequest::new().with_key(key).with_value(val);
|
||||
|
||||
let resp = self.kv_backend.compare_and_put(req).await?;
|
||||
if !resp.success {
|
||||
let curr = resp.prev_kv.map_or_else(
|
||||
|| "empty".to_string(),
|
||||
|kv| {
|
||||
DatanodeTableValue::try_from_raw_value(kv.value).map_or_else(
|
||||
|e| format!("Invalid DatanodeTableValue for Datanode {datanode_id}: {e}"),
|
||||
|v| format!("{v:?}"),
|
||||
)
|
||||
},
|
||||
);
|
||||
return ConcurrentModifyRegionsPlacementSnafu {
|
||||
err_msg: format!("Datanode {datanode_id} already existed {curr}"),
|
||||
}
|
||||
.fail();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove(&self, datanode_id: DatanodeId, table_id: TableId) -> Result<()> {
|
||||
let key = DatanodeTableKey::new(datanode_id, table_id);
|
||||
let removed_key = to_removed_key(&String::from_utf8_lossy(&key.as_raw_key()));
|
||||
let req = MoveValueRequest::new(key.as_raw_key(), removed_key.as_bytes());
|
||||
let _ = self.kv_backend.move_value(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_region(
|
||||
&self,
|
||||
from_datanode: DatanodeId,
|
||||
to_datanode: DatanodeId,
|
||||
table_id: TableId,
|
||||
region: RegionNumber,
|
||||
) -> Result<bool> {
|
||||
let from_key = DatanodeTableKey::new(from_datanode, table_id);
|
||||
let mut from_value = self.get(&from_key).await?.context(MoveRegionSnafu {
|
||||
table_id,
|
||||
region,
|
||||
err_msg: format!("DatanodeTableKey not found for Datanode {from_datanode}"),
|
||||
})?;
|
||||
|
||||
ensure!(
|
||||
from_value.regions.contains(®ion),
|
||||
MoveRegionSnafu {
|
||||
table_id,
|
||||
region,
|
||||
err_msg: format!("target region not found in Datanode {from_datanode}"),
|
||||
}
|
||||
);
|
||||
|
||||
let to_key = DatanodeTableKey::new(to_datanode, table_id);
|
||||
let to_value = self.get(&to_key).await?;
|
||||
|
||||
if let Some(v) = to_value.as_ref() {
|
||||
ensure!(
|
||||
!v.regions.contains(®ion),
|
||||
MoveRegionSnafu {
|
||||
table_id,
|
||||
region,
|
||||
err_msg: format!("target region already existed in Datanode {to_datanode}"),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let compares = vec![
|
||||
Compare::with_value(
|
||||
from_key.as_raw_key(),
|
||||
CompareOp::Equal,
|
||||
from_value.try_as_raw_value()?,
|
||||
),
|
||||
Compare::new(
|
||||
to_key.as_raw_key(),
|
||||
CompareOp::Equal,
|
||||
to_value
|
||||
.as_ref()
|
||||
.map(|x| x.try_as_raw_value())
|
||||
.transpose()?,
|
||||
),
|
||||
];
|
||||
|
||||
let mut operations = Vec::with_capacity(2);
|
||||
|
||||
from_value.regions.retain(|x| *x != region);
|
||||
if from_value.regions.is_empty() {
|
||||
operations.push(TxnOp::Delete(from_key.as_raw_key()));
|
||||
} else {
|
||||
from_value.version += 1;
|
||||
operations.push(TxnOp::Put(
|
||||
from_key.as_raw_key(),
|
||||
from_value.try_as_raw_value()?,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(mut v) = to_value {
|
||||
v.regions.push(region);
|
||||
v.version += 1;
|
||||
operations.push(TxnOp::Put(to_key.as_raw_key(), v.try_as_raw_value()?));
|
||||
} else {
|
||||
let v = DatanodeTableValue::new(table_id, vec![region]);
|
||||
operations.push(TxnOp::Put(to_key.as_raw_key(), v.try_as_raw_value()?));
|
||||
}
|
||||
|
||||
let txn = Txn::new().when(compares).and_then(operations);
|
||||
let resp = self.kv_backend.txn(txn).await?;
|
||||
Ok(resp.succeeded)
|
||||
}
|
||||
|
||||
pub async fn tables(&self, datanode_id: DatanodeId) -> Result<Vec<DatanodeTableValue>> {
|
||||
let prefix = DatanodeTableKey::prefix(datanode_id);
|
||||
let req = RangeRequest::new().with_prefix(prefix.as_bytes());
|
||||
let resp = self.kv_backend.range(req).await?;
|
||||
let table_ids = resp
|
||||
.kvs
|
||||
.into_iter()
|
||||
.map(|kv| DatanodeTableValue::try_from_raw_value(kv.value))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(table_ids)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::kv_backend::memory::MemoryKvBackend;
|
||||
use crate::kv_backend::KvBackend;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_move_region() {
|
||||
let manager = DatanodeTableManager::new(Arc::new(MemoryKvBackend::default()));
|
||||
|
||||
let result = manager.move_region(1, 2, 1, 1).await;
|
||||
assert!(result.unwrap_err().to_string().contains(
|
||||
"Failed to move region 1 in table 1, err: DatanodeTableKey not found for Datanode 1"
|
||||
));
|
||||
|
||||
assert!(manager.create(1, 1, vec![1, 2, 3]).await.is_ok());
|
||||
let result = manager.move_region(1, 2, 1, 100).await;
|
||||
assert!(result.unwrap_err().to_string().contains(
|
||||
"Failed to move region 100 in table 1, err: target region not found in Datanode 1"
|
||||
));
|
||||
|
||||
// Move region 1 from datanode 1 to datanode 2.
|
||||
// Note that the DatanodeTableValue is not existed for datanode 2 now.
|
||||
assert!(manager.move_region(1, 2, 1, 1).await.unwrap());
|
||||
let value = manager
|
||||
.get(&DatanodeTableKey::new(1, 1))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![2, 3],
|
||||
version: 1,
|
||||
}
|
||||
);
|
||||
let value = manager
|
||||
.get(&DatanodeTableKey::new(2, 1))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![1],
|
||||
version: 0,
|
||||
}
|
||||
);
|
||||
|
||||
// Move region 2 from datanode 1 to datanode 2.
|
||||
assert!(manager.move_region(1, 2, 1, 2).await.is_ok());
|
||||
let value = manager
|
||||
.get(&DatanodeTableKey::new(1, 1))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![3],
|
||||
version: 2,
|
||||
}
|
||||
);
|
||||
let value = manager
|
||||
.get(&DatanodeTableKey::new(2, 1))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![1, 2],
|
||||
version: 1,
|
||||
}
|
||||
);
|
||||
|
||||
// Move region 3 (the last region) from datanode 1 to datanode 2.
|
||||
assert!(manager.move_region(1, 2, 1, 3).await.is_ok());
|
||||
let value = manager.get(&DatanodeTableKey::new(1, 1)).await.unwrap();
|
||||
assert!(value.is_none());
|
||||
let value = manager
|
||||
.get(&DatanodeTableKey::new(2, 1))
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![1, 2, 3],
|
||||
version: 2,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_datanode_table_value_manager() {
|
||||
let backend = Arc::new(MemoryKvBackend::default());
|
||||
let manager = DatanodeTableManager::new(backend.clone());
|
||||
|
||||
assert!(manager.create(1, 1, vec![1, 2, 3]).await.is_ok());
|
||||
assert!(manager.create(1, 2, vec![4, 5, 6]).await.is_ok());
|
||||
assert!(manager.create(2, 1, vec![4, 5, 6]).await.is_ok());
|
||||
assert!(manager.create(2, 2, vec![1, 2, 3]).await.is_ok());
|
||||
|
||||
let err_msg = manager
|
||||
.create(1, 1, vec![4, 5, 6])
|
||||
.await
|
||||
.unwrap_err()
|
||||
.to_string();
|
||||
assert!(err_msg.contains("Concurrent modify regions placement: Datanode 1 already existed DatanodeTableValue { table_id: 1, regions: [1, 2, 3], version: 0 }"));
|
||||
|
||||
let to_be_removed_key = DatanodeTableKey::new(2, 1);
|
||||
let expected_value = DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![4, 5, 6],
|
||||
version: 0,
|
||||
};
|
||||
let value = manager.get(&to_be_removed_key).await.unwrap().unwrap();
|
||||
assert_eq!(value, expected_value);
|
||||
|
||||
assert!(manager.remove(2, 1).await.is_ok());
|
||||
assert!(manager.get(&to_be_removed_key).await.unwrap().is_none());
|
||||
let kv = backend
|
||||
.get(b"__removed-__dn_table/2/1")
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(b"__removed-__dn_table/2/1", kv.key());
|
||||
let value = DatanodeTableValue::try_from_raw_value(kv.value).unwrap();
|
||||
assert_eq!(value, expected_value);
|
||||
|
||||
let values = manager.tables(1).await.unwrap();
|
||||
assert_eq!(values.len(), 2);
|
||||
assert_eq!(
|
||||
values[0],
|
||||
DatanodeTableValue {
|
||||
table_id: 1,
|
||||
regions: vec![1, 2, 3],
|
||||
version: 0,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
values[1],
|
||||
DatanodeTableValue {
|
||||
table_id: 2,
|
||||
regions: vec![4, 5, 6],
|
||||
version: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde() {
|
||||
let key = DatanodeTableKey {
|
||||
datanode_id: 1,
|
||||
table_id: 2,
|
||||
};
|
||||
let raw_key = key.as_raw_key();
|
||||
assert_eq!(raw_key, b"__dn_table/1/2");
|
||||
|
||||
let value = DatanodeTableValue {
|
||||
table_id: 42,
|
||||
regions: vec![1, 2, 3],
|
||||
version: 1,
|
||||
};
|
||||
let literal = br#"{"table_id":42,"regions":[1,2,3],"version":1}"#;
|
||||
|
||||
let raw_value = value.try_as_raw_value().unwrap();
|
||||
assert_eq!(raw_value, literal);
|
||||
|
||||
let actual = DatanodeTableValue::try_from_raw_value(literal.to_vec()).unwrap();
|
||||
assert_eq!(actual, value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_table_id() {
|
||||
fn test_err(raw_key: &[u8]) {
|
||||
let result = DatanodeTableKey::strip_table_id(raw_key);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
test_err(b"");
|
||||
test_err(vec![0u8, 159, 146, 150].as_slice()); // invalid UTF8 string
|
||||
test_err(b"invalid_prefix/1/2");
|
||||
test_err(b"__dn_table/");
|
||||
test_err(b"__dn_table/invalid_len_1");
|
||||
test_err(b"__dn_table/invalid_len_3/1/2");
|
||||
test_err(b"__dn_table/invalid_node_id/2");
|
||||
test_err(b"__dn_table/1/invalid_table_id");
|
||||
|
||||
let table_id = DatanodeTableKey::strip_table_id(b"__dn_table/1/2").unwrap();
|
||||
assert_eq!(table_id, 2);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ use super::TABLE_INFO_KEY_PREFIX;
|
||||
use crate::error::Result;
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest};
|
||||
|
||||
pub struct TableInfoKey {
|
||||
table_id: TableId,
|
||||
@@ -57,11 +58,18 @@ impl TableInfoManager {
|
||||
self.kv_backend
|
||||
.get(&raw_key)
|
||||
.await?
|
||||
.map(|x| TableInfoValue::try_from_raw_value(x.1))
|
||||
.map(|x| TableInfoValue::try_from_raw_value(x.value))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn compare_and_set(
|
||||
/// Compare and put value of key. `expect` is the expected value, if backend's current value associated
|
||||
/// with key is the same as `expect`, the value will be updated to `val`.
|
||||
///
|
||||
/// - If the compare-and-set operation successfully updated value, this method will return an `Ok(Ok())`
|
||||
/// - If associated value is not the same as `expect`, no value will be updated and an `Ok(Err(Vec<u8>))`
|
||||
/// will be returned, the `Err(Vec<u8>)` indicates the current associated value of key.
|
||||
/// - If any error happens during operation, an `Err(Error)` will be returned.
|
||||
pub async fn compare_and_put(
|
||||
&self,
|
||||
table_id: TableId,
|
||||
expect: Option<TableInfoValue>,
|
||||
@@ -82,17 +90,24 @@ impl TableInfoManager {
|
||||
};
|
||||
let raw_value = value.try_as_raw_value()?;
|
||||
|
||||
self.kv_backend
|
||||
.compare_and_set(&raw_key, &expect, &raw_value)
|
||||
.await
|
||||
let req = CompareAndPutRequest::new()
|
||||
.with_key(raw_key)
|
||||
.with_expect(expect)
|
||||
.with_value(raw_value);
|
||||
let resp = self.kv_backend.compare_and_put(req).await?;
|
||||
Ok(if resp.success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(resp.prev_kv.map(|x| x.value))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remove(&self, table_id: TableId) -> Result<()> {
|
||||
let key = TableInfoKey::new(table_id);
|
||||
let removed_key = to_removed_key(&String::from_utf8_lossy(key.as_raw_key().as_slice()));
|
||||
self.kv_backend
|
||||
.move_value(&key.as_raw_key(), removed_key.as_bytes())
|
||||
.await
|
||||
let key = TableInfoKey::new(table_id).as_raw_key();
|
||||
let removed_key = to_removed_key(&String::from_utf8_lossy(&key));
|
||||
let req = MoveValueRequest::new(key, removed_key.as_bytes());
|
||||
self.kv_backend.move_value(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +122,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::kv_backend::memory::MemoryKvBackend;
|
||||
use crate::kv_backend::KvBackend;
|
||||
use crate::rpc::store::PutRequest;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_table_info_manager() {
|
||||
@@ -120,7 +136,8 @@ mod tests {
|
||||
}
|
||||
.try_as_raw_value()
|
||||
.unwrap();
|
||||
backend.set(&key, &val).await.unwrap();
|
||||
let req = PutRequest::new().with_key(key).with_value(val);
|
||||
backend.put(req).await.unwrap();
|
||||
}
|
||||
|
||||
let manager = TableInfoManager::new(backend.clone());
|
||||
@@ -137,7 +154,7 @@ mod tests {
|
||||
|
||||
let table_info = new_table_info(4);
|
||||
let result = manager
|
||||
.compare_and_set(4, None, table_info.clone())
|
||||
.compare_and_put(4, None, table_info.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_ok());
|
||||
@@ -145,7 +162,7 @@ mod tests {
|
||||
// test cas failed, the new table info is not set
|
||||
let new_table_info = new_table_info(4);
|
||||
let result = manager
|
||||
.compare_and_set(4, None, new_table_info.clone())
|
||||
.compare_and_put(4, None, new_table_info.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let actual = TableInfoValue::try_from_raw_value(result.unwrap_err().unwrap()).unwrap();
|
||||
@@ -159,7 +176,7 @@ mod tests {
|
||||
|
||||
// test cas success
|
||||
let result = manager
|
||||
.compare_and_set(4, Some(actual), new_table_info.clone())
|
||||
.compare_and_put(4, Some(actual), new_table_info.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_ok());
|
||||
@@ -171,8 +188,8 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(b"__removed-__table_info/4", kv.0.as_slice());
|
||||
let value = TableInfoValue::try_from_raw_value(kv.1).unwrap();
|
||||
assert_eq!(b"__removed-__table_info/4", kv.key.as_slice());
|
||||
let value = TableInfoValue::try_from_raw_value(kv.value).unwrap();
|
||||
assert_eq!(value.table_info, new_table_info);
|
||||
assert_eq!(value.version, 1);
|
||||
}
|
||||
|
||||
233
src/common/meta/src/key/table_name.rs
Normal file
233
src/common/meta/src/key/table_name.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::OptionExt;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use super::{TABLE_NAME_KEY_PATTERN, TABLE_NAME_KEY_PREFIX};
|
||||
use crate::error::{InvalidTableMetadataSnafu, Result};
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::memory::MemoryKvBackend;
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest, RangeRequest};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TableNameKey<'a> {
|
||||
pub catalog: &'a str,
|
||||
pub schema: &'a str,
|
||||
pub table: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> TableNameKey<'a> {
|
||||
pub fn new(catalog: &'a str, schema: &'a str, table: &'a str) -> Self {
|
||||
Self {
|
||||
catalog,
|
||||
schema,
|
||||
table,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefix_to_table(catalog: &str, schema: &str) -> String {
|
||||
format!("{}/{}/{}", TABLE_NAME_KEY_PREFIX, catalog, schema)
|
||||
}
|
||||
|
||||
fn strip_table_name(raw_key: &[u8]) -> Result<String> {
|
||||
let key = String::from_utf8(raw_key.to_vec()).map_err(|e| {
|
||||
InvalidTableMetadataSnafu {
|
||||
err_msg: format!(
|
||||
"TableNameKey '{}' is not a valid UTF8 string: {e}",
|
||||
String::from_utf8_lossy(raw_key)
|
||||
),
|
||||
}
|
||||
.build()
|
||||
})?;
|
||||
let captures =
|
||||
TABLE_NAME_KEY_PATTERN
|
||||
.captures(&key)
|
||||
.context(InvalidTableMetadataSnafu {
|
||||
err_msg: format!("Invalid TableNameKey '{key}'"),
|
||||
})?;
|
||||
// Safety: pass the regex check above
|
||||
Ok(captures[3].to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl TableMetaKey for TableNameKey<'_> {
|
||||
fn as_raw_key(&self) -> Vec<u8> {
|
||||
format!(
|
||||
"{}/{}",
|
||||
Self::prefix_to_table(self.catalog, self.schema),
|
||||
self.table
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TableNameValue {
|
||||
table_id: TableId,
|
||||
}
|
||||
|
||||
impl TableNameValue {
|
||||
fn new(table_id: TableId) -> Self {
|
||||
Self { table_id }
|
||||
}
|
||||
|
||||
pub fn table_id(&self) -> TableId {
|
||||
self.table_id
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TableNameManager {
|
||||
kv_backend: KvBackendRef,
|
||||
}
|
||||
|
||||
impl Default for TableNameManager {
|
||||
fn default() -> Self {
|
||||
Self::new(Arc::new(MemoryKvBackend::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TableNameManager {
|
||||
pub fn new(kv_backend: KvBackendRef) -> Self {
|
||||
Self { kv_backend }
|
||||
}
|
||||
|
||||
pub async fn create(&self, key: &TableNameKey<'_>, table_id: TableId) -> Result<bool> {
|
||||
let raw_key = key.as_raw_key();
|
||||
let value = TableNameValue::new(table_id);
|
||||
let raw_value = value.try_as_raw_value()?;
|
||||
let req = CompareAndPutRequest::new()
|
||||
.with_key(raw_key)
|
||||
.with_value(raw_value);
|
||||
let result = self.kv_backend.compare_and_put(req).await?;
|
||||
Ok(result.success)
|
||||
}
|
||||
|
||||
pub async fn get(&self, key: &TableNameKey<'_>) -> Result<Option<TableNameValue>> {
|
||||
let raw_key = key.as_raw_key();
|
||||
self.kv_backend
|
||||
.get(&raw_key)
|
||||
.await?
|
||||
.map(|x| TableNameValue::try_from_raw_value(x.value))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn tables(&self, catalog: &str, schema: &str) -> Result<Vec<String>> {
|
||||
let key = TableNameKey::prefix_to_table(catalog, schema).into_bytes();
|
||||
let req = RangeRequest::new().with_prefix(key);
|
||||
let resp = self.kv_backend.range(req).await?;
|
||||
let table_names = resp
|
||||
.kvs
|
||||
.into_iter()
|
||||
.map(|kv| TableNameKey::strip_table_name(kv.key()))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(table_names)
|
||||
}
|
||||
|
||||
pub async fn remove(&self, key: &TableNameKey<'_>) -> Result<()> {
|
||||
let raw_key = key.as_raw_key();
|
||||
let removed_key = to_removed_key(&String::from_utf8_lossy(&raw_key));
|
||||
let req = MoveValueRequest::new(raw_key, removed_key.as_bytes());
|
||||
let _ = self.kv_backend.move_value(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::kv_backend::memory::MemoryKvBackend;
|
||||
use crate::kv_backend::KvBackend;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_table_name_manager() {
|
||||
let backend = Arc::new(MemoryKvBackend::default());
|
||||
let manager = TableNameManager::new(backend.clone());
|
||||
|
||||
for i in 1..=3 {
|
||||
let table_name = format!("table_{}", i);
|
||||
let key = TableNameKey::new("my_catalog", "my_schema", &table_name);
|
||||
assert!(manager.create(&key, i).await.unwrap());
|
||||
}
|
||||
|
||||
let key = TableNameKey::new("my_catalog", "my_schema", "my_table");
|
||||
assert!(manager.create(&key, 99).await.unwrap());
|
||||
assert!(!manager.create(&key, 99).await.unwrap());
|
||||
|
||||
let value = manager.get(&key).await.unwrap().unwrap();
|
||||
assert_eq!(value.table_id(), 99);
|
||||
let not_existed = TableNameKey::new("x", "y", "z");
|
||||
assert!(manager.get(¬_existed).await.unwrap().is_none());
|
||||
|
||||
assert!(manager.remove(&key).await.is_ok());
|
||||
let kv = backend
|
||||
.get(b"__removed-__table_name/my_catalog/my_schema/my_table")
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let value = TableNameValue::try_from_raw_value(kv.value).unwrap();
|
||||
assert_eq!(value.table_id(), 99);
|
||||
|
||||
let tables = manager.tables("my_catalog", "my_schema").await.unwrap();
|
||||
assert_eq!(tables.len(), 3);
|
||||
assert_eq!(tables, vec!["table_1", "table_2", "table_3"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strip_table_name() {
|
||||
fn test_err(raw_key: &[u8]) {
|
||||
assert!(TableNameKey::strip_table_name(raw_key).is_err());
|
||||
}
|
||||
|
||||
test_err(b"");
|
||||
test_err(vec![0u8, 159, 146, 150].as_slice()); // invalid UTF8 string
|
||||
test_err(b"invalid_prefix/my_catalog/my_schema/my_table");
|
||||
test_err(b"__table_name/");
|
||||
test_err(b"__table_name/invalid_len_1");
|
||||
test_err(b"__table_name/invalid_len_2/x");
|
||||
test_err(b"__table_name/invalid_len_4/x/y/z");
|
||||
test_err(b"__table_name/000_invalid_catalog/y/z");
|
||||
test_err(b"__table_name/x/000_invalid_schema/z");
|
||||
test_err(b"__table_name/x/y/000_invalid_table");
|
||||
|
||||
let table_name =
|
||||
TableNameKey::strip_table_name(b"__table_name/my_catalog/my_schema/my_table").unwrap();
|
||||
assert_eq!(table_name, "my_table");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serde() {
|
||||
let key = TableNameKey::new("my_catalog", "my_schema", "my_table");
|
||||
let raw_key = key.as_raw_key();
|
||||
assert_eq!(
|
||||
b"__table_name/my_catalog/my_schema/my_table",
|
||||
raw_key.as_slice()
|
||||
);
|
||||
|
||||
let value = TableNameValue::new(1);
|
||||
let literal = br#"{"table_id":1}"#;
|
||||
|
||||
assert_eq!(value.try_as_raw_value().unwrap(), literal);
|
||||
assert_eq!(
|
||||
TableNameValue::try_from_raw_value(literal.to_vec()).unwrap(),
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use super::TABLE_REGION_KEY_PREFIX;
|
||||
use crate::error::Result;
|
||||
use crate::key::{to_removed_key, TableMetaKey};
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::{CompareAndPutRequest, MoveValueRequest};
|
||||
use crate::DatanodeId;
|
||||
|
||||
pub type RegionDistribution = BTreeMap<DatanodeId, Vec<RegionNumber>>;
|
||||
@@ -63,11 +64,18 @@ impl TableRegionManager {
|
||||
self.kv_backend
|
||||
.get(&raw_key)
|
||||
.await?
|
||||
.map(|x| TableRegionValue::try_from_raw_value(x.1))
|
||||
.map(|x| TableRegionValue::try_from_raw_value(x.value))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn compare_and_set(
|
||||
/// Compare and put value of key. `expect` is the expected value, if backend's current value associated
|
||||
/// with key is the same as `expect`, the value will be updated to `val`.
|
||||
///
|
||||
/// - If the compare-and-set operation successfully updated value, this method will return an `Ok(Ok())`
|
||||
/// - If associated value is not the same as `expect`, no value will be updated and an `Ok(Err(Vec<u8>))`
|
||||
/// will be returned, the `Err(Vec<u8>)` indicates the current associated value of key.
|
||||
/// - If any error happens during operation, an `Err(Error)` will be returned.
|
||||
pub async fn compare_and_put(
|
||||
&self,
|
||||
table_id: TableId,
|
||||
expect: Option<TableRegionValue>,
|
||||
@@ -88,17 +96,24 @@ impl TableRegionManager {
|
||||
};
|
||||
let raw_value = value.try_as_raw_value()?;
|
||||
|
||||
self.kv_backend
|
||||
.compare_and_set(&raw_key, &expect, &raw_value)
|
||||
.await
|
||||
let req = CompareAndPutRequest::new()
|
||||
.with_key(raw_key)
|
||||
.with_expect(expect)
|
||||
.with_value(raw_value);
|
||||
let resp = self.kv_backend.compare_and_put(req).await?;
|
||||
Ok(if resp.success {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(resp.prev_kv.map(|x| x.value))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn remove(&self, table_id: TableId) -> Result<()> {
|
||||
let key = TableRegionKey::new(table_id);
|
||||
let remove_key = to_removed_key(&String::from_utf8_lossy(key.as_raw_key().as_slice()));
|
||||
self.kv_backend
|
||||
.move_value(&key.as_raw_key(), remove_key.as_bytes())
|
||||
.await
|
||||
let key = TableRegionKey::new(table_id).as_raw_key();
|
||||
let remove_key = to_removed_key(&String::from_utf8_lossy(&key));
|
||||
let req = MoveValueRequest::new(key, remove_key.as_bytes());
|
||||
self.kv_backend.move_value(req).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +133,7 @@ mod tests {
|
||||
let region_distribution =
|
||||
RegionDistribution::from([(1, vec![1, 2, 3]), (2, vec![4, 5, 6])]);
|
||||
let result = manager
|
||||
.compare_and_set(1, None, region_distribution.clone())
|
||||
.compare_and_put(1, None, region_distribution.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_ok());
|
||||
@@ -126,7 +141,7 @@ mod tests {
|
||||
let new_region_distribution =
|
||||
RegionDistribution::from([(1, vec![4, 5, 6]), (2, vec![1, 2, 3])]);
|
||||
let curr = manager
|
||||
.compare_and_set(1, None, new_region_distribution.clone())
|
||||
.compare_and_put(1, None, new_region_distribution.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap_err()
|
||||
@@ -141,7 +156,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert!(manager
|
||||
.compare_and_set(1, Some(curr), new_region_distribution.clone())
|
||||
.compare_and_put(1, Some(curr), new_region_distribution.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.is_ok());
|
||||
@@ -163,8 +178,8 @@ mod tests {
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(b"__removed-__table_region/1", kv.0.as_slice());
|
||||
let value = TableRegionValue::try_from_raw_value(kv.1).unwrap();
|
||||
assert_eq!(b"__removed-__table_region/1", kv.key.as_slice());
|
||||
let value = TableRegionValue::try_from_raw_value(kv.value).unwrap();
|
||||
assert_eq!(value.region_distribution, new_region_distribution);
|
||||
assert_eq!(value.version, 1);
|
||||
}
|
||||
|
||||
@@ -12,21 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use api::v1::meta::TableName;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use crate::key::to_removed_key;
|
||||
|
||||
pub const TABLE_ROUTE_PREFIX: &str = "__meta_table_route";
|
||||
|
||||
pub struct TableRouteKey<'a> {
|
||||
pub table_id: u64,
|
||||
pub table_id: TableId,
|
||||
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 {
|
||||
pub fn with_table_name(table_id: TableId, t: &'a TableName) -> Self {
|
||||
Self {
|
||||
table_id,
|
||||
catalog_name: &t.catalog_name,
|
||||
@@ -42,12 +45,14 @@ impl<'a> TableRouteKey<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn key(&self) -> String {
|
||||
format!("{}-{}", self.prefix(), self.table_id)
|
||||
}
|
||||
|
||||
pub fn removed_key(&self) -> String {
|
||||
to_removed_key(&self.key())
|
||||
to_removed_key(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for TableRouteKey<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}-{}", self.prefix(), self.table_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +74,7 @@ mod tests {
|
||||
let prefix = key.prefix();
|
||||
assert_eq!("__meta_table_route-greptime-public-demo", prefix);
|
||||
|
||||
let key_string = key.key();
|
||||
let key_string = key.to_string();
|
||||
assert_eq!("__meta_table_route-greptime-public-demo-123", key_string);
|
||||
|
||||
let removed = key.removed_key();
|
||||
|
||||
@@ -13,68 +13,84 @@
|
||||
// limitations under the License.
|
||||
|
||||
pub mod memory;
|
||||
pub mod txn;
|
||||
|
||||
use std::any::Any;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common_error::ext::ErrorExt;
|
||||
use futures::{Stream, StreamExt};
|
||||
pub use txn::TxnService;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::rpc::store::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
use crate::rpc::KeyValue;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Kv(pub Vec<u8>, pub Vec<u8>);
|
||||
|
||||
pub type ValueIter<'a, E> = Pin<Box<dyn Stream<Item = Result<Kv, E>> + Send + 'a>>;
|
||||
|
||||
pub type KvBackendRef = Arc<dyn KvBackend<Error = Error>>;
|
||||
pub type KvBackendRef = Arc<dyn KvBackend<Error = Error> + Send + Sync>;
|
||||
|
||||
#[async_trait]
|
||||
pub trait KvBackend: Send + Sync {
|
||||
type Error: ErrorExt;
|
||||
pub trait KvBackend: TxnService
|
||||
where
|
||||
Self::Error: ErrorExt,
|
||||
{
|
||||
fn name(&self) -> &str;
|
||||
|
||||
fn range<'a, 'b>(&'a self, key: &[u8]) -> ValueIter<'b, Self::Error>
|
||||
where
|
||||
'a: 'b;
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse, Self::Error>;
|
||||
|
||||
async fn set(&self, key: &[u8], val: &[u8]) -> Result<(), Self::Error>;
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse, Self::Error>;
|
||||
|
||||
/// Compare and set value of key. `expect` is the expected value, if backend's current value associated
|
||||
/// with key is the same as `expect`, the value will be updated to `val`.
|
||||
///
|
||||
/// - If the compare-and-set operation successfully updated value, this method will return an `Ok(Ok())`
|
||||
/// - If associated value is not the same as `expect`, no value will be updated and an `Ok(Err(Vec<u8>))`
|
||||
/// will be returned, the `Err(Vec<u8>)` indicates the current associated value of key.
|
||||
/// - If any error happens during operation, an `Err(Error)` will be returned.
|
||||
async fn compare_and_set(
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse, Self::Error>;
|
||||
|
||||
async fn compare_and_put(
|
||||
&self,
|
||||
key: &[u8],
|
||||
expect: &[u8],
|
||||
val: &[u8],
|
||||
) -> Result<Result<(), Option<Vec<u8>>>, Self::Error>;
|
||||
req: CompareAndPutRequest,
|
||||
) -> Result<CompareAndPutResponse, Self::Error>;
|
||||
|
||||
async fn delete_range(&self, key: &[u8], end: &[u8]) -> Result<(), Self::Error>;
|
||||
async fn delete_range(
|
||||
&self,
|
||||
req: DeleteRangeRequest,
|
||||
) -> Result<DeleteRangeResponse, Self::Error>;
|
||||
|
||||
async fn delete(&self, key: &[u8]) -> Result<(), Self::Error> {
|
||||
self.delete_range(key, &[]).await
|
||||
async fn delete(&self, key: &[u8], prev_kv: bool) -> Result<Option<KeyValue>, Self::Error> {
|
||||
let mut req = DeleteRangeRequest::new().with_key(key.to_vec());
|
||||
if prev_kv {
|
||||
req = req.with_prev_kv();
|
||||
}
|
||||
|
||||
let resp = self.delete_range(req).await?;
|
||||
|
||||
if prev_kv {
|
||||
Ok(resp.prev_kvs.into_iter().next())
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn batch_delete(
|
||||
&self,
|
||||
req: BatchDeleteRequest,
|
||||
) -> Result<BatchDeleteResponse, Self::Error>;
|
||||
|
||||
/// Default get is implemented based on `range` method.
|
||||
async fn get(&self, key: &[u8]) -> Result<Option<Kv>, Self::Error> {
|
||||
let mut iter = self.range(key);
|
||||
while let Some(r) = iter.next().await {
|
||||
let kv = r?;
|
||||
if kv.0 == key {
|
||||
return Ok(Some(kv));
|
||||
}
|
||||
}
|
||||
return Ok(None);
|
||||
async fn get(&self, key: &[u8]) -> Result<Option<KeyValue>, Self::Error> {
|
||||
let req = RangeRequest::new().with_key(key.to_vec());
|
||||
let mut resp = self.range(req).await?;
|
||||
Ok(if resp.kvs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(resp.kvs.remove(0))
|
||||
})
|
||||
}
|
||||
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse, Self::Error>;
|
||||
|
||||
/// MoveValue atomically renames the key to the given updated key.
|
||||
async fn move_value(&self, from_key: &[u8], to_key: &[u8]) -> Result<(), Self::Error>;
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse, Self::Error>;
|
||||
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
@@ -16,20 +16,32 @@ use std::any::Any;
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Range;
|
||||
use std::sync::RwLock;
|
||||
|
||||
use async_stream::stream;
|
||||
use async_trait::async_trait;
|
||||
use common_error::prelude::ErrorExt;
|
||||
use common_telemetry::timer;
|
||||
use serde::Serializer;
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::kv_backend::{Kv, KvBackend, ValueIter};
|
||||
use crate::kv_backend::txn::{Txn, TxnOp, TxnOpResponse, TxnRequest, TxnResponse};
|
||||
use crate::kv_backend::{KvBackend, TxnService};
|
||||
use crate::metrics::METRIC_META_TXN_REQUEST;
|
||||
use crate::rpc::store::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
use crate::rpc::KeyValue;
|
||||
|
||||
pub struct MemoryKvBackend {
|
||||
pub struct MemoryKvBackend<T> {
|
||||
kvs: RwLock<BTreeMap<Vec<u8>, Vec<u8>>>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl Display for MemoryKvBackend {
|
||||
impl<T> Display for MemoryKvBackend<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let kvs = self.kvs.read().unwrap();
|
||||
for (k, v) in kvs.iter() {
|
||||
@@ -42,93 +54,239 @@ impl Display for MemoryKvBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MemoryKvBackend {
|
||||
impl<T> Default for MemoryKvBackend<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kvs: RwLock::new(BTreeMap::new()),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> MemoryKvBackend<T> {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn clear(&self) {
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
kvs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl KvBackend for MemoryKvBackend {
|
||||
type Error = Error;
|
||||
impl<T: ErrorExt + Send + Sync + 'static> KvBackend for MemoryKvBackend<T> {
|
||||
fn name(&self) -> &str {
|
||||
"Memory"
|
||||
}
|
||||
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse, Self::Error> {
|
||||
let RangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
limit,
|
||||
keys_only,
|
||||
} = req;
|
||||
|
||||
fn range<'a, 'b>(&'a self, prefix: &[u8]) -> ValueIter<'b, Error>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
let kvs = self.kvs.read().unwrap();
|
||||
let kvs = kvs.clone();
|
||||
|
||||
let prefix = prefix.to_vec();
|
||||
Box::pin(stream!({
|
||||
for (k, v) in kvs.range(prefix.clone()..) {
|
||||
if !k.starts_with(&prefix) {
|
||||
break;
|
||||
let iter: Box<dyn Iterator<Item = (&Vec<u8>, &Vec<u8>)>> = if range_end.is_empty() {
|
||||
Box::new(kvs.get_key_value(&key).into_iter())
|
||||
} else {
|
||||
Box::new(kvs.range(key..range_end))
|
||||
};
|
||||
let mut kvs = iter
|
||||
.map(|(k, v)| {
|
||||
let key = k.clone();
|
||||
let value = if keys_only { vec![] } else { v.clone() };
|
||||
KeyValue { key, value }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let more = if limit > 0 && kvs.len() > limit as usize {
|
||||
kvs.truncate(limit as usize);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Ok(RangeResponse { kvs, more })
|
||||
}
|
||||
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse, Self::Error> {
|
||||
let PutRequest {
|
||||
key,
|
||||
value,
|
||||
prev_kv,
|
||||
} = req;
|
||||
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
|
||||
let prev_kv = if prev_kv {
|
||||
kvs.insert(key.clone(), value)
|
||||
.map(|value| KeyValue { key, value })
|
||||
} else {
|
||||
kvs.insert(key, value);
|
||||
None
|
||||
};
|
||||
|
||||
Ok(PutResponse { prev_kv })
|
||||
}
|
||||
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse, Self::Error> {
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
|
||||
let mut prev_kvs = if req.prev_kv {
|
||||
Vec::with_capacity(req.kvs.len())
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
for kv in req.kvs {
|
||||
if req.prev_kv {
|
||||
if let Some(value) = kvs.insert(kv.key.clone(), kv.value) {
|
||||
prev_kvs.push(KeyValue { key: kv.key, value });
|
||||
}
|
||||
yield Ok(Kv(k.clone(), v.clone()));
|
||||
} else {
|
||||
kvs.insert(kv.key, kv.value);
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
Ok(BatchPutResponse { prev_kvs })
|
||||
}
|
||||
|
||||
async fn set(&self, key: &[u8], val: &[u8]) -> Result<(), Error> {
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
let _ = kvs.insert(key.to_vec(), val.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn compare_and_set(
|
||||
async fn compare_and_put(
|
||||
&self,
|
||||
key: &[u8],
|
||||
expect: &[u8],
|
||||
val: &[u8],
|
||||
) -> Result<Result<(), Option<Vec<u8>>>, Error> {
|
||||
let key = key.to_vec();
|
||||
let val = val.to_vec();
|
||||
req: CompareAndPutRequest,
|
||||
) -> Result<CompareAndPutResponse, Self::Error> {
|
||||
let CompareAndPutRequest { key, expect, value } = req;
|
||||
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
|
||||
let existed = kvs.entry(key);
|
||||
Ok(match existed {
|
||||
let (success, prev_kv) = match existed {
|
||||
Entry::Vacant(e) => {
|
||||
if expect.is_empty() {
|
||||
let _ = e.insert(val);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(None)
|
||||
let expected = expect.is_empty();
|
||||
if expected {
|
||||
let _ = e.insert(value);
|
||||
}
|
||||
(expected, None)
|
||||
}
|
||||
Entry::Occupied(mut existed) => {
|
||||
if existed.get() == expect {
|
||||
let _ = existed.insert(val);
|
||||
Ok(())
|
||||
let expected = existed.get() == &expect;
|
||||
let prev_kv = if expected {
|
||||
let _ = existed.insert(value);
|
||||
None
|
||||
} else {
|
||||
Err(Some(existed.get().clone()))
|
||||
}
|
||||
Some(KeyValue {
|
||||
key: existed.key().clone(),
|
||||
value: existed.get().clone(),
|
||||
})
|
||||
};
|
||||
(expected, prev_kv)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(CompareAndPutResponse { success, prev_kv })
|
||||
}
|
||||
|
||||
async fn delete_range(
|
||||
&self,
|
||||
req: DeleteRangeRequest,
|
||||
) -> Result<DeleteRangeResponse, Self::Error> {
|
||||
let DeleteRangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
prev_kv,
|
||||
} = req;
|
||||
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
|
||||
let prev_kvs = if range_end.is_empty() {
|
||||
kvs.remove(&key)
|
||||
.into_iter()
|
||||
.map(|value| KeyValue {
|
||||
key: key.clone(),
|
||||
value,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
let range = Range {
|
||||
start: key,
|
||||
end: range_end,
|
||||
};
|
||||
kvs.drain_filter(|key, _| range.contains(key))
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
Ok(DeleteRangeResponse {
|
||||
deleted: prev_kvs.len() as i64,
|
||||
prev_kvs: if prev_kv { prev_kvs } else { vec![] },
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_range(&self, key: &[u8], end: &[u8]) -> Result<(), Error> {
|
||||
async fn batch_delete(
|
||||
&self,
|
||||
req: BatchDeleteRequest,
|
||||
) -> Result<BatchDeleteResponse, Self::Error> {
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
if end.is_empty() {
|
||||
let _ = kvs.remove(key);
|
||||
} else {
|
||||
let start = key.to_vec();
|
||||
let end = end.to_vec();
|
||||
let range = start..end;
|
||||
|
||||
kvs.retain(|k, _| !range.contains(k));
|
||||
let mut prev_kvs = if req.prev_kv {
|
||||
Vec::with_capacity(req.keys.len())
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
for key in req.keys {
|
||||
if req.prev_kv {
|
||||
if let Some(value) = kvs.remove(&key) {
|
||||
prev_kvs.push(KeyValue { key, value });
|
||||
}
|
||||
} else {
|
||||
kvs.remove(&key);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Ok(BatchDeleteResponse { prev_kvs })
|
||||
}
|
||||
|
||||
async fn move_value(&self, from_key: &[u8], to_key: &[u8]) -> Result<(), Error> {
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse, Self::Error> {
|
||||
let kvs = self.kvs.read().unwrap();
|
||||
|
||||
let kvs = req
|
||||
.keys
|
||||
.into_iter()
|
||||
.filter_map(|key| {
|
||||
kvs.get_key_value(&key).map(|(k, v)| KeyValue {
|
||||
key: k.clone(),
|
||||
value: v.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(BatchGetResponse { kvs })
|
||||
}
|
||||
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse, Self::Error> {
|
||||
let MoveValueRequest { from_key, to_key } = req;
|
||||
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
if let Some(v) = kvs.remove(from_key) {
|
||||
let _ = kvs.insert(to_key.to_vec(), v);
|
||||
}
|
||||
Ok(())
|
||||
|
||||
let kv = if let Some(v) = kvs.remove(&from_key) {
|
||||
kvs.insert(to_key, v.clone());
|
||||
Some(KeyValue {
|
||||
key: from_key,
|
||||
value: v,
|
||||
})
|
||||
} else {
|
||||
kvs.get(&to_key).map(|v| KeyValue {
|
||||
key: to_key,
|
||||
value: v.clone(),
|
||||
})
|
||||
};
|
||||
|
||||
Ok(MoveValueResponse(kv))
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
@@ -136,62 +294,383 @@ impl KvBackend for MemoryKvBackend {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use futures::TryStreamExt;
|
||||
#[async_trait]
|
||||
impl<T: ErrorExt + Send + Sync> TxnService for MemoryKvBackend<T> {
|
||||
type Error = T;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_memory_kv_backend() {
|
||||
let backend = MemoryKvBackend::default();
|
||||
|
||||
for i in 1..10 {
|
||||
let key = format!("key{}", i);
|
||||
let val = format!("val{}", i);
|
||||
assert!(backend.set(key.as_bytes(), val.as_bytes()).await.is_ok());
|
||||
}
|
||||
|
||||
let result = backend
|
||||
.compare_and_set(b"hello", b"what", b"world")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.unwrap_err().is_none());
|
||||
|
||||
let result = backend
|
||||
.compare_and_set(b"hello", b"", b"world")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = backend
|
||||
.compare_and_set(b"hello", b"world", b"greptime")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(result.is_ok());
|
||||
|
||||
let result = backend
|
||||
.compare_and_set(b"hello", b"world", b"what")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result.unwrap_err().unwrap(), b"greptime");
|
||||
|
||||
assert!(backend.delete_range(b"key1", &[]).await.is_ok());
|
||||
assert!(backend.delete_range(b"key3", b"key9").await.is_ok());
|
||||
|
||||
assert!(backend.move_value(b"key9", b"key10").await.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
backend.to_string(),
|
||||
r#"hello -> greptime
|
||||
key10 -> val9
|
||||
key2 -> val2
|
||||
"#
|
||||
async fn txn(&self, txn: Txn) -> Result<TxnResponse, Self::Error> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_TXN_REQUEST,
|
||||
&[("target", "memory"), ("op", "txn")]
|
||||
);
|
||||
|
||||
let range = backend.range(b"key").try_collect::<Vec<_>>().await.unwrap();
|
||||
assert_eq!(range.len(), 2);
|
||||
assert_eq!(range[0], Kv(b"key10".to_vec(), b"val9".to_vec()));
|
||||
assert_eq!(range[1], Kv(b"key2".to_vec(), b"val2".to_vec()));
|
||||
let TxnRequest {
|
||||
compare,
|
||||
success,
|
||||
failure,
|
||||
} = txn.into();
|
||||
|
||||
let mut kvs = self.kvs.write().unwrap();
|
||||
|
||||
let succeeded = compare
|
||||
.iter()
|
||||
.all(|x| x.compare_with_value(kvs.get(&x.key)));
|
||||
|
||||
let do_txn = |txn_op| match txn_op {
|
||||
TxnOp::Put(key, value) => {
|
||||
let prev_value = kvs.insert(key.clone(), value);
|
||||
let prev_kv = prev_value.map(|value| KeyValue { key, value });
|
||||
TxnOpResponse::ResponsePut(PutResponse { prev_kv })
|
||||
}
|
||||
|
||||
TxnOp::Get(key) => {
|
||||
let value = kvs.get(&key);
|
||||
let kvs = value
|
||||
.into_iter()
|
||||
.map(|value| KeyValue {
|
||||
key: key.clone(),
|
||||
value: value.clone(),
|
||||
})
|
||||
.collect();
|
||||
TxnOpResponse::ResponseGet(RangeResponse { kvs, more: false })
|
||||
}
|
||||
|
||||
TxnOp::Delete(key) => {
|
||||
let prev_value = kvs.remove(&key);
|
||||
let deleted = prev_value.as_ref().map(|x| x.len()).unwrap_or(0) as i64;
|
||||
|
||||
let prev_kvs = prev_value
|
||||
.into_iter()
|
||||
.map(|value| KeyValue {
|
||||
key: key.clone(),
|
||||
value,
|
||||
})
|
||||
.collect();
|
||||
TxnOpResponse::ResponseDelete(DeleteRangeResponse { deleted, prev_kvs })
|
||||
}
|
||||
};
|
||||
|
||||
let responses: Vec<_> = if succeeded { success } else { failure }
|
||||
.into_iter()
|
||||
.map(do_txn)
|
||||
.collect();
|
||||
|
||||
Ok(TxnResponse {
|
||||
succeeded,
|
||||
responses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
use crate::kv_backend::KvBackend;
|
||||
use crate::rpc::store::{BatchGetRequest, BatchPutRequest};
|
||||
use crate::rpc::KeyValue;
|
||||
use crate::util;
|
||||
|
||||
async fn mock_mem_store_with_data() -> MemoryKvBackend<Error> {
|
||||
let kv_store = MemoryKvBackend::<Error>::new();
|
||||
let kvs = mock_kvs();
|
||||
|
||||
assert!(kv_store
|
||||
.batch_put(BatchPutRequest {
|
||||
kvs,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
assert!(kv_store
|
||||
.put(PutRequest {
|
||||
key: b"key11".to_vec(),
|
||||
value: b"val11".to_vec(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
kv_store
|
||||
}
|
||||
|
||||
fn mock_kvs() -> Vec<KeyValue> {
|
||||
vec![
|
||||
KeyValue {
|
||||
key: b"key1".to_vec(),
|
||||
value: b"val1".to_vec(),
|
||||
},
|
||||
KeyValue {
|
||||
key: b"key2".to_vec(),
|
||||
value: b"val2".to_vec(),
|
||||
},
|
||||
KeyValue {
|
||||
key: b"key3".to_vec(),
|
||||
value: b"val3".to_vec(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let resp = kv_store
|
||||
.put(PutRequest {
|
||||
key: b"key11".to_vec(),
|
||||
value: b"val12".to_vec(),
|
||||
prev_kv: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resp.prev_kv.is_none());
|
||||
|
||||
let resp = kv_store
|
||||
.put(PutRequest {
|
||||
key: b"key11".to_vec(),
|
||||
value: b"val13".to_vec(),
|
||||
prev_kv: true,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let prev_kv = resp.prev_kv.unwrap();
|
||||
assert_eq!(b"key11", prev_kv.key());
|
||||
assert_eq!(b"val12", prev_kv.value());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_range() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let key = b"key1".to_vec();
|
||||
let range_end = util::get_prefix_end_key(b"key1");
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key: key.clone(),
|
||||
range_end: range_end.clone(),
|
||||
limit: 0,
|
||||
keys_only: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, resp.kvs.len());
|
||||
assert_eq!(b"key1", resp.kvs[0].key());
|
||||
assert_eq!(b"val1", resp.kvs[0].value());
|
||||
assert_eq!(b"key11", resp.kvs[1].key());
|
||||
assert_eq!(b"val11", resp.kvs[1].value());
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key: key.clone(),
|
||||
range_end: range_end.clone(),
|
||||
limit: 0,
|
||||
keys_only: true,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, resp.kvs.len());
|
||||
assert_eq!(b"key1", resp.kvs[0].key());
|
||||
assert_eq!(b"", resp.kvs[0].value());
|
||||
assert_eq!(b"key11", resp.kvs[1].key());
|
||||
assert_eq!(b"", resp.kvs[1].value());
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key: key.clone(),
|
||||
limit: 0,
|
||||
keys_only: false,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, resp.kvs.len());
|
||||
assert_eq!(b"key1", resp.kvs[0].key());
|
||||
assert_eq!(b"val1", resp.kvs[0].value());
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
limit: 1,
|
||||
keys_only: false,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, resp.kvs.len());
|
||||
assert_eq!(b"key1", resp.kvs[0].key());
|
||||
assert_eq!(b"val1", resp.kvs[0].value());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_batch_get() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let keys = vec![];
|
||||
let resp = kv_store.batch_get(BatchGetRequest { keys }).await.unwrap();
|
||||
|
||||
assert!(resp.kvs.is_empty());
|
||||
|
||||
let keys = vec![b"key10".to_vec()];
|
||||
let resp = kv_store.batch_get(BatchGetRequest { keys }).await.unwrap();
|
||||
|
||||
assert!(resp.kvs.is_empty());
|
||||
|
||||
let keys = vec![b"key1".to_vec(), b"key3".to_vec(), b"key4".to_vec()];
|
||||
let resp = kv_store.batch_get(BatchGetRequest { keys }).await.unwrap();
|
||||
|
||||
assert_eq!(2, resp.kvs.len());
|
||||
assert_eq!(b"key1", resp.kvs[0].key());
|
||||
assert_eq!(b"val1", resp.kvs[0].value());
|
||||
assert_eq!(b"key3", resp.kvs[1].key());
|
||||
assert_eq!(b"val3", resp.kvs[1].value());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_compare_and_put() {
|
||||
let kv_store = Arc::new(MemoryKvBackend::<Error>::new());
|
||||
let success = Arc::new(AtomicU8::new(0));
|
||||
|
||||
let mut joins = vec![];
|
||||
for _ in 0..20 {
|
||||
let kv_store_clone = kv_store.clone();
|
||||
let success_clone = success.clone();
|
||||
let join = tokio::spawn(async move {
|
||||
let req = CompareAndPutRequest {
|
||||
key: b"key".to_vec(),
|
||||
expect: vec![],
|
||||
value: b"val_new".to_vec(),
|
||||
};
|
||||
let resp = kv_store_clone.compare_and_put(req).await.unwrap();
|
||||
if resp.success {
|
||||
success_clone.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
joins.push(join);
|
||||
}
|
||||
|
||||
for join in joins {
|
||||
join.await.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(1, success.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_range() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let req = DeleteRangeRequest {
|
||||
key: b"key3".to_vec(),
|
||||
range_end: vec![],
|
||||
prev_kv: true,
|
||||
};
|
||||
|
||||
let resp = kv_store.delete_range(req).await.unwrap();
|
||||
assert_eq!(1, resp.prev_kvs.len());
|
||||
assert_eq!(b"key3", resp.prev_kvs[0].key());
|
||||
assert_eq!(b"val3", resp.prev_kvs[0].value());
|
||||
|
||||
let resp = kv_store.get(b"key3").await.unwrap();
|
||||
assert!(resp.is_none());
|
||||
|
||||
let req = DeleteRangeRequest {
|
||||
key: b"key2".to_vec(),
|
||||
range_end: vec![],
|
||||
prev_kv: false,
|
||||
};
|
||||
|
||||
let resp = kv_store.delete_range(req).await.unwrap();
|
||||
assert!(resp.prev_kvs.is_empty());
|
||||
|
||||
let resp = kv_store.get(b"key2").await.unwrap();
|
||||
assert!(resp.is_none());
|
||||
|
||||
let key = b"key1".to_vec();
|
||||
let range_end = util::get_prefix_end_key(b"key1");
|
||||
|
||||
let req = DeleteRangeRequest {
|
||||
key: key.clone(),
|
||||
range_end: range_end.clone(),
|
||||
prev_kv: true,
|
||||
};
|
||||
let resp = kv_store.delete_range(req).await.unwrap();
|
||||
assert_eq!(2, resp.prev_kvs.len());
|
||||
|
||||
let req = RangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
..Default::default()
|
||||
};
|
||||
let resp = kv_store.range(req).await.unwrap();
|
||||
assert!(resp.kvs.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_move_value() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let req = MoveValueRequest {
|
||||
from_key: b"key1".to_vec(),
|
||||
to_key: b"key111".to_vec(),
|
||||
};
|
||||
|
||||
let resp = kv_store.move_value(req).await.unwrap();
|
||||
assert_eq!(b"key1", resp.0.as_ref().unwrap().key());
|
||||
assert_eq!(b"val1", resp.0.as_ref().unwrap().value());
|
||||
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let req = MoveValueRequest {
|
||||
from_key: b"notexistkey".to_vec(),
|
||||
to_key: b"key222".to_vec(),
|
||||
};
|
||||
|
||||
let resp = kv_store.move_value(req).await.unwrap();
|
||||
assert!(resp.0.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_batch_delete() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
assert!(kv_store.get(b"key1").await.unwrap().is_some());
|
||||
assert!(kv_store.get(b"key100").await.unwrap().is_none());
|
||||
|
||||
let req = BatchDeleteRequest {
|
||||
keys: vec![b"key1".to_vec(), b"key100".to_vec()],
|
||||
prev_kv: true,
|
||||
};
|
||||
let resp = kv_store.batch_delete(req).await.unwrap();
|
||||
assert_eq!(1, resp.prev_kvs.len());
|
||||
assert_eq!(
|
||||
vec![KeyValue {
|
||||
key: b"key1".to_vec(),
|
||||
value: b"val1".to_vec()
|
||||
}],
|
||||
resp.prev_kvs
|
||||
);
|
||||
assert!(kv_store.get(b"key1").await.unwrap().is_none());
|
||||
|
||||
assert!(kv_store.get(b"key2").await.unwrap().is_some());
|
||||
assert!(kv_store.get(b"key3").await.unwrap().is_some());
|
||||
|
||||
let req = BatchDeleteRequest {
|
||||
keys: vec![b"key2".to_vec(), b"key3".to_vec()],
|
||||
prev_kv: false,
|
||||
};
|
||||
let resp = kv_store.batch_delete(req).await.unwrap();
|
||||
assert!(resp.prev_kvs.is_empty());
|
||||
|
||||
assert!(kv_store.get(b"key2").await.unwrap().is_none());
|
||||
assert!(kv_store.get(b"key3").await.unwrap().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,17 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{DeleteRangeResponse, PutResponse, RangeResponse};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
mod etcd;
|
||||
|
||||
use common_error::prelude::ErrorExt;
|
||||
|
||||
use crate::rpc::store::{DeleteRangeResponse, PutResponse, RangeResponse};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait TxnService: Sync + Send {
|
||||
async fn txn(&self, _txn: Txn) -> Result<TxnResponse> {
|
||||
type Error: ErrorExt;
|
||||
|
||||
async fn txn(&self, _txn: Txn) -> Result<TxnResponse, Self::Error> {
|
||||
unimplemented!("txn is not implemented")
|
||||
}
|
||||
}
|
||||
@@ -169,11 +171,14 @@ impl From<Txn> for TxnRequest {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use api::v1::meta::{KeyValue, PutRequest};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::error::Error;
|
||||
use crate::kv_backend::memory::MemoryKvBackend;
|
||||
use crate::kv_backend::KvBackendRef;
|
||||
use crate::rpc::store::PutRequest;
|
||||
use crate::rpc::KeyValue;
|
||||
|
||||
#[test]
|
||||
fn test_compare() {
|
||||
@@ -301,7 +306,7 @@ mod tests {
|
||||
async fn test_txn_compare_equal() {
|
||||
let kv_store = create_kv_store().await;
|
||||
let key = vec![101u8];
|
||||
let _ = kv_store.delete(key.clone(), false).await.unwrap();
|
||||
kv_store.delete(&key, false).await.unwrap();
|
||||
|
||||
let txn = Txn::new()
|
||||
.when(vec![Compare::with_not_exist_value(
|
||||
@@ -332,7 +337,7 @@ mod tests {
|
||||
async fn test_txn_compare_greater() {
|
||||
let kv_store = create_kv_store().await;
|
||||
let key = vec![102u8];
|
||||
let _ = kv_store.delete(key.clone(), false).await.unwrap();
|
||||
kv_store.delete(&key, false).await.unwrap();
|
||||
|
||||
let txn = Txn::new()
|
||||
.when(vec![Compare::with_not_exist_value(
|
||||
@@ -361,10 +366,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
res,
|
||||
TxnOpResponse::ResponseGet(RangeResponse {
|
||||
header: None,
|
||||
kvs: vec![KeyValue {
|
||||
key,
|
||||
value: vec![1],
|
||||
value: vec![1]
|
||||
}],
|
||||
more: false,
|
||||
})
|
||||
@@ -375,7 +379,7 @@ mod tests {
|
||||
async fn test_txn_compare_less() {
|
||||
let kv_store = create_kv_store().await;
|
||||
let key = vec![103u8];
|
||||
let _ = kv_store.delete(vec![3], false).await.unwrap();
|
||||
kv_store.delete(&[3], false).await.unwrap();
|
||||
|
||||
let txn = Txn::new()
|
||||
.when(vec![Compare::with_not_exist_value(
|
||||
@@ -404,10 +408,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
res,
|
||||
TxnOpResponse::ResponseGet(RangeResponse {
|
||||
header: None,
|
||||
kvs: vec![KeyValue {
|
||||
key,
|
||||
value: vec![2],
|
||||
value: vec![2]
|
||||
}],
|
||||
more: false,
|
||||
})
|
||||
@@ -418,7 +421,7 @@ mod tests {
|
||||
async fn test_txn_compare_not_equal() {
|
||||
let kv_store = create_kv_store().await;
|
||||
let key = vec![104u8];
|
||||
let _ = kv_store.delete(key.clone(), false).await.unwrap();
|
||||
kv_store.delete(&key, false).await.unwrap();
|
||||
|
||||
let txn = Txn::new()
|
||||
.when(vec![Compare::with_not_exist_value(
|
||||
@@ -447,18 +450,17 @@ mod tests {
|
||||
assert_eq!(
|
||||
res,
|
||||
TxnOpResponse::ResponseGet(RangeResponse {
|
||||
header: None,
|
||||
kvs: vec![KeyValue {
|
||||
key,
|
||||
value: vec![1],
|
||||
value: vec![1]
|
||||
}],
|
||||
more: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async fn create_kv_store() -> KvStoreRef {
|
||||
std::sync::Arc::new(crate::service::store::memory::MemStore::new())
|
||||
async fn create_kv_store() -> KvBackendRef {
|
||||
Arc::new(MemoryKvBackend::<Error>::new())
|
||||
// TODO(jiachun): Add a feature to test against etcd in github CI
|
||||
//
|
||||
// The same test can be run against etcd by uncommenting the following line
|
||||
@@ -12,15 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{DeleteRangeResponse, PutResponse, RangeResponse};
|
||||
use etcd_client::{
|
||||
Compare as EtcdCompare, CompareOp as EtcdCompareOp, Txn as EtcdTxn, TxnOp as EtcdTxnOp,
|
||||
TxnOpResponse as EtcdTxnOpResponse, TxnResponse as EtcdTxnResponse,
|
||||
};
|
||||
|
||||
use super::{Compare, CompareOp, Txn, TxnOp, TxnOpResponse, TxnResponse};
|
||||
use crate::error::{self, Result};
|
||||
use crate::service::store::etcd_util::KvPair;
|
||||
use crate::service::store::txn::{Compare, CompareOp, Txn, TxnOp, TxnOpResponse, TxnResponse};
|
||||
use crate::rpc::store::{DeleteRangeResponse, PutResponse, RangeResponse};
|
||||
|
||||
impl From<Txn> for EtcdTxn {
|
||||
fn from(txn: Txn) -> Self {
|
||||
@@ -88,31 +87,25 @@ impl TryFrom<EtcdTxnOpResponse> for TxnOpResponse {
|
||||
fn try_from(op_resp: EtcdTxnOpResponse) -> Result<Self> {
|
||||
match op_resp {
|
||||
EtcdTxnOpResponse::Put(res) => {
|
||||
let prev_kv = res.prev_key().map(KvPair::from_etcd_kv);
|
||||
let put_res = PutResponse {
|
||||
prev_kv,
|
||||
..Default::default()
|
||||
};
|
||||
let prev_kv = res.prev_key().cloned().map(Into::into);
|
||||
let put_res = PutResponse { prev_kv };
|
||||
Ok(TxnOpResponse::ResponsePut(put_res))
|
||||
}
|
||||
EtcdTxnOpResponse::Get(res) => {
|
||||
let kvs = res.kvs().iter().map(KvPair::from_etcd_kv).collect();
|
||||
let range_res = RangeResponse {
|
||||
kvs,
|
||||
..Default::default()
|
||||
};
|
||||
let kvs = res.kvs().iter().cloned().map(Into::into).collect();
|
||||
let range_res = RangeResponse { kvs, more: false };
|
||||
Ok(TxnOpResponse::ResponseGet(range_res))
|
||||
}
|
||||
EtcdTxnOpResponse::Delete(res) => {
|
||||
let prev_kvs = res
|
||||
.prev_kvs()
|
||||
.iter()
|
||||
.map(KvPair::from_etcd_kv)
|
||||
.cloned()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
let delete_res = DeleteRangeResponse {
|
||||
prev_kvs,
|
||||
deleted: res.deleted(),
|
||||
..Default::default()
|
||||
};
|
||||
Ok(TxnOpResponse::ResponseDelete(delete_res))
|
||||
}
|
||||
@@ -12,15 +12,19 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![feature(btree_drain_filter)]
|
||||
|
||||
pub mod error;
|
||||
pub mod heartbeat;
|
||||
pub mod ident;
|
||||
pub mod instruction;
|
||||
pub mod key;
|
||||
pub mod kv_backend;
|
||||
pub mod metrics;
|
||||
pub mod peer;
|
||||
pub mod rpc;
|
||||
pub mod table_name;
|
||||
pub mod util;
|
||||
|
||||
pub type ClusterId = u64;
|
||||
pub type DatanodeId = u64;
|
||||
|
||||
15
src/common/meta/src/metrics.rs
Normal file
15
src/common/meta/src/metrics.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub const METRIC_META_TXN_REQUEST: &str = "meta.txn_request";
|
||||
@@ -18,17 +18,14 @@ pub mod router;
|
||||
pub mod store;
|
||||
pub mod util;
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use api::v1::meta::{KeyValue as PbKeyValue, ResponseHeader as PbResponseHeader};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ResponseHeader(PbResponseHeader);
|
||||
|
||||
impl ResponseHeader {
|
||||
#[inline]
|
||||
pub(crate) fn new(header: PbResponseHeader) -> Self {
|
||||
Self(header)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn protocol_version(&self) -> u64 {
|
||||
self.0.protocol_version
|
||||
@@ -56,33 +53,83 @@ impl ResponseHeader {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyValue(PbKeyValue);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct KeyValue {
|
||||
pub key: Vec<u8>,
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<KeyValue> for PbKeyValue {
|
||||
fn from(kv: KeyValue) -> Self {
|
||||
Self {
|
||||
key: kv.key,
|
||||
value: kv.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<etcd_client::KeyValue> for KeyValue {
|
||||
fn from(kv: etcd_client::KeyValue) -> Self {
|
||||
Self {
|
||||
key: kv.key().to_vec(),
|
||||
value: kv.value().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyValue> for (Vec<u8>, Vec<u8>) {
|
||||
fn from(kv: KeyValue) -> Self {
|
||||
(kv.key, kv.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Vec<u8>, Vec<u8>)> for KeyValue {
|
||||
fn from(kv: (Vec<u8>, Vec<u8>)) -> Self {
|
||||
Self {
|
||||
key: kv.0,
|
||||
value: kv.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KeyValue {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"({}, {})",
|
||||
String::from_utf8_lossy(&self.key),
|
||||
String::from_utf8_lossy(&self.value)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyValue {
|
||||
#[inline]
|
||||
pub(crate) fn new(kv: PbKeyValue) -> Self {
|
||||
Self(kv)
|
||||
pub fn new(kv: PbKeyValue) -> Self {
|
||||
Self {
|
||||
key: kv.key,
|
||||
value: kv.value,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn key(&self) -> &[u8] {
|
||||
&self.0.key
|
||||
&self.key
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_key(&mut self) -> Vec<u8> {
|
||||
std::mem::take(&mut self.0.key)
|
||||
std::mem::take(&mut self.key)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn value(&self) -> &[u8] {
|
||||
&self.0.value
|
||||
&self.value
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_value(&mut self) -> Vec<u8> {
|
||||
std::mem::take(&mut self.0.value)
|
||||
std::mem::take(&mut self.value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +150,7 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let header = ResponseHeader::new(pb_header);
|
||||
let header = ResponseHeader(pb_header);
|
||||
assert_eq!(101, header.protocol_version());
|
||||
assert_eq!(1, header.cluster_id());
|
||||
assert_eq!(100, header.error_code());
|
||||
|
||||
@@ -16,11 +16,11 @@ use std::result;
|
||||
|
||||
use api::v1::meta::submit_ddl_task_request::Task;
|
||||
use api::v1::meta::{
|
||||
CreateTableTask as PbCreateTableTask, Partition,
|
||||
CreateTableTask as PbCreateTableTask, DropTableTask as PbDropTableTask, Partition,
|
||||
SubmitDdlTaskRequest as PbSubmitDdlTaskRequest,
|
||||
SubmitDdlTaskResponse as PbSubmitDdlTaskResponse,
|
||||
};
|
||||
use api::v1::CreateTableExpr;
|
||||
use api::v1::{CreateTableExpr, DropTableExpr};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
@@ -33,6 +33,7 @@ use crate::table_name::TableName;
|
||||
#[derive(Debug)]
|
||||
pub enum DdlTask {
|
||||
CreateTable(CreateTableTask),
|
||||
DropTable(DropTableTask),
|
||||
}
|
||||
|
||||
impl DdlTask {
|
||||
@@ -52,6 +53,8 @@ impl TryFrom<Task> for DdlTask {
|
||||
Task::CreateTableTask(create_table) => {
|
||||
Ok(DdlTask::CreateTable(create_table.try_into()?))
|
||||
}
|
||||
Task::DropTableTask(drop_table) => Ok(DdlTask::DropTable(drop_table.try_into()?)),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +73,15 @@ impl TryFrom<SubmitDdlTaskRequest> for PbSubmitDdlTaskRequest {
|
||||
create_table: Some(task.create_table),
|
||||
partitions: task.partitions,
|
||||
}),
|
||||
|
||||
DdlTask::DropTable(task) => Task::DropTableTask(PbDropTableTask {
|
||||
drop_table: Some(DropTableExpr {
|
||||
catalog_name: task.catalog,
|
||||
schema_name: task.schema,
|
||||
table_name: task.table,
|
||||
table_id: Some(api::v1::TableId { id: task.table_id }),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
Ok(Self {
|
||||
header: None,
|
||||
@@ -97,6 +109,54 @@ impl TryFrom<PbSubmitDdlTaskResponse> for SubmitDdlTaskResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DropTableTask {
|
||||
pub catalog: String,
|
||||
pub schema: String,
|
||||
pub table: String,
|
||||
pub table_id: TableId,
|
||||
}
|
||||
|
||||
impl DropTableTask {
|
||||
pub fn table_ref(&self) -> TableReference {
|
||||
TableReference {
|
||||
catalog: &self.catalog,
|
||||
schema: &self.schema,
|
||||
table: &self.table,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn table_name(&self) -> TableName {
|
||||
TableName {
|
||||
catalog_name: self.catalog.to_string(),
|
||||
schema_name: self.schema.to_string(),
|
||||
table_name: self.table.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PbDropTableTask> for DropTableTask {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(pb: PbDropTableTask) -> Result<Self> {
|
||||
let drop_table = pb.drop_table.context(error::InvalidProtoMsgSnafu {
|
||||
err_msg: "expected drop table",
|
||||
})?;
|
||||
|
||||
Ok(Self {
|
||||
catalog: drop_table.catalog_name,
|
||||
schema: drop_table.schema_name,
|
||||
table: drop_table.table_name,
|
||||
table_id: drop_table
|
||||
.table_id
|
||||
.context(error::InvalidProtoMsgSnafu {
|
||||
err_msg: "expected table_id",
|
||||
})?
|
||||
.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CreateTableTask {
|
||||
pub create_table: CreateTableExpr,
|
||||
|
||||
@@ -148,7 +148,7 @@ impl TableRoute {
|
||||
pub fn new(table: Table, region_routes: Vec<RegionRoute>) -> Self {
|
||||
let region_leaders = region_routes
|
||||
.iter()
|
||||
.map(|x| (x.region.id as RegionNumber, x.leader_peer.clone()))
|
||||
.map(|x| (x.region.id.region_number(), x.leader_peer.clone()))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
Self {
|
||||
@@ -265,13 +265,13 @@ impl TableRoute {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_leader_regions(&self, datanode: &Peer) -> Vec<u32> {
|
||||
pub fn find_leader_regions(&self, datanode: &Peer) -> Vec<RegionNumber> {
|
||||
self.region_routes
|
||||
.iter()
|
||||
.filter_map(|x| {
|
||||
if let Some(peer) = &x.leader_peer {
|
||||
if peer == datanode {
|
||||
return Some(x.region.id as u32);
|
||||
return Some(x.region.id.region_number());
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -340,7 +340,7 @@ pub struct Region {
|
||||
impl From<PbRegion> for Region {
|
||||
fn from(r: PbRegion) -> Self {
|
||||
Self {
|
||||
id: r.id,
|
||||
id: r.id.into(),
|
||||
name: r.name,
|
||||
partition: r.partition.map(Into::into),
|
||||
attrs: r.attrs,
|
||||
@@ -351,7 +351,7 @@ impl From<PbRegion> for Region {
|
||||
impl From<Region> for PbRegion {
|
||||
fn from(region: Region) -> Self {
|
||||
Self {
|
||||
id: region.id,
|
||||
id: region.id.into(),
|
||||
name: region.name,
|
||||
partition: region.partition.map(Into::into),
|
||||
attrs: region.attrs,
|
||||
@@ -668,7 +668,7 @@ mod tests {
|
||||
region_routes: vec![
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 1,
|
||||
id: 1.into(),
|
||||
name: "r1".to_string(),
|
||||
partition: None,
|
||||
attrs: HashMap::new(),
|
||||
@@ -678,7 +678,7 @@ mod tests {
|
||||
},
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 2,
|
||||
id: 2.into(),
|
||||
name: "r2".to_string(),
|
||||
partition: None,
|
||||
attrs: HashMap::new(),
|
||||
|
||||
@@ -12,21 +12,23 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use api::v1::meta::{
|
||||
BatchDeleteRequest as PbBatchDeleteRequest, BatchDeleteResponse as PbBatchDeleteResponse,
|
||||
BatchGetRequest as PbBatchGetRequest, BatchGetResponse as PbBatchGetResponse,
|
||||
BatchPutRequest as PbBatchPutRequest, BatchPutResponse as PbBatchPutResponse,
|
||||
CompareAndPutRequest as PbCompareAndPutRequest,
|
||||
CompareAndPutResponse as PbCompareAndPutResponse, DeleteRangeRequest as PbDeleteRangeRequest,
|
||||
DeleteRangeResponse as PbDeleteRangeResponse, KeyValue as PbKeyValue,
|
||||
MoveValueRequest as PbMoveValueRequest, MoveValueResponse as PbMoveValueResponse,
|
||||
PutRequest as PbPutRequest, PutResponse as PbPutResponse, RangeRequest as PbRangeRequest,
|
||||
RangeResponse as PbRangeResponse,
|
||||
DeleteRangeResponse as PbDeleteRangeResponse, MoveValueRequest as PbMoveValueRequest,
|
||||
MoveValueResponse as PbMoveValueResponse, PutRequest as PbPutRequest,
|
||||
PutResponse as PbPutResponse, RangeRequest as PbRangeRequest, RangeResponse as PbRangeResponse,
|
||||
ResponseHeader as PbResponseHeader,
|
||||
};
|
||||
|
||||
use crate::error;
|
||||
use crate::error::Result;
|
||||
use crate::rpc::{util, KeyValue, ResponseHeader};
|
||||
use crate::rpc::{util, KeyValue};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct RangeRequest {
|
||||
@@ -59,6 +61,30 @@ impl From<RangeRequest> for PbRangeRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbRangeRequest> for RangeRequest {
|
||||
fn from(value: PbRangeRequest) -> Self {
|
||||
Self {
|
||||
key: value.key,
|
||||
range_end: value.range_end,
|
||||
limit: value.limit,
|
||||
keys_only: value.keys_only,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RangeRequest {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"RangeRequest{{key: '{}', range_end: '{}', limit: {}, keys_only: {}}}",
|
||||
String::from_utf8_lossy(&self.key),
|
||||
String::from_utf8_lossy(&self.range_end),
|
||||
self.limit,
|
||||
self.keys_only
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl RangeRequest {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
@@ -119,8 +145,26 @@ impl RangeRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RangeResponse(PbRangeResponse);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RangeResponse {
|
||||
pub kvs: Vec<KeyValue>,
|
||||
pub more: bool,
|
||||
}
|
||||
|
||||
impl Display for RangeResponse {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"RangeResponse{{kvs: [{}], more: {}}}",
|
||||
self.kvs
|
||||
.iter()
|
||||
.map(|kv| kv.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
self.more
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PbRangeResponse> for RangeResponse {
|
||||
type Error = error::Error;
|
||||
@@ -128,29 +172,25 @@ impl TryFrom<PbRangeResponse> for RangeResponse {
|
||||
fn try_from(pb: PbRangeResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self {
|
||||
kvs: pb.kvs.into_iter().map(KeyValue::new).collect(),
|
||||
more: pb.more,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl RangeResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbRangeResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbRangeResponse {
|
||||
PbRangeResponse {
|
||||
header: Some(header),
|
||||
kvs: self.kvs.into_iter().map(Into::into).collect(),
|
||||
more: self.more,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_kvs(&mut self) -> Vec<KeyValue> {
|
||||
self.0.kvs.drain(..).map(KeyValue::new).collect()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn more(&self) -> bool {
|
||||
self.0.more
|
||||
self.kvs.drain(..).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +217,16 @@ impl From<PutRequest> for PbPutRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbPutRequest> for PutRequest {
|
||||
fn from(value: PbPutRequest) -> Self {
|
||||
Self {
|
||||
key: value.key,
|
||||
value: value.value,
|
||||
prev_kv: value.prev_kv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PutRequest {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
@@ -211,8 +261,10 @@ impl PutRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PutResponse(PbPutResponse);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PutResponse {
|
||||
pub prev_kv: Option<KeyValue>,
|
||||
}
|
||||
|
||||
impl TryFrom<PbPutResponse> for PutResponse {
|
||||
type Error = error::Error;
|
||||
@@ -220,27 +272,22 @@ impl TryFrom<PbPutResponse> for PutResponse {
|
||||
fn try_from(pb: PbPutResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self {
|
||||
prev_kv: pb.prev_kv.map(KeyValue::new),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PutResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbPutResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_prev_kv(&mut self) -> Option<KeyValue> {
|
||||
self.0.prev_kv.take().map(KeyValue::new)
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbPutResponse {
|
||||
PbPutResponse {
|
||||
header: Some(header),
|
||||
prev_kv: self.prev_kv.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BatchGetRequest {
|
||||
pub keys: Vec<Vec<u8>>,
|
||||
}
|
||||
@@ -254,6 +301,12 @@ impl From<BatchGetRequest> for PbBatchGetRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbBatchGetRequest> for BatchGetRequest {
|
||||
fn from(value: PbBatchGetRequest) -> Self {
|
||||
Self { keys: value.keys }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BatchGetRequest {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -274,7 +327,23 @@ impl BatchGetRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BatchGetResponse(PbBatchGetResponse);
|
||||
pub struct BatchGetResponse {
|
||||
pub kvs: Vec<KeyValue>,
|
||||
}
|
||||
|
||||
impl Display for BatchGetResponse {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[{}]",
|
||||
self.kvs
|
||||
.iter()
|
||||
.map(|kv| kv.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PbBatchGetResponse> for BatchGetResponse {
|
||||
type Error = error::Error;
|
||||
@@ -282,30 +351,24 @@ impl TryFrom<PbBatchGetResponse> for BatchGetResponse {
|
||||
fn try_from(pb: PbBatchGetResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self(pb))
|
||||
Ok(Self {
|
||||
kvs: pb.kvs.into_iter().map(KeyValue::new).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchGetResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbBatchGetResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_kvs(&mut self) -> Vec<KeyValue> {
|
||||
self.0.kvs.drain(..).map(KeyValue::new).collect()
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbBatchGetResponse {
|
||||
PbBatchGetResponse {
|
||||
header: Some(header),
|
||||
kvs: self.kvs.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct BatchPutRequest {
|
||||
pub kvs: Vec<PbKeyValue>,
|
||||
pub kvs: Vec<KeyValue>,
|
||||
/// If prev_kv is set, gets the previous key-value pairs before changing it.
|
||||
/// The previous key-value pairs will be returned in the batch put response.
|
||||
pub prev_kv: bool,
|
||||
@@ -315,12 +378,21 @@ impl From<BatchPutRequest> for PbBatchPutRequest {
|
||||
fn from(req: BatchPutRequest) -> Self {
|
||||
Self {
|
||||
header: None,
|
||||
kvs: req.kvs,
|
||||
kvs: req.kvs.into_iter().map(Into::into).collect(),
|
||||
prev_kv: req.prev_kv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbBatchPutRequest> for BatchPutRequest {
|
||||
fn from(value: PbBatchPutRequest) -> Self {
|
||||
Self {
|
||||
kvs: value.kvs.into_iter().map(KeyValue::new).collect(),
|
||||
prev_kv: value.prev_kv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchPutRequest {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
@@ -332,7 +404,7 @@ impl BatchPutRequest {
|
||||
|
||||
#[inline]
|
||||
pub fn add_kv(mut self, key: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self {
|
||||
self.kvs.push(PbKeyValue {
|
||||
self.kvs.push(KeyValue {
|
||||
key: key.into(),
|
||||
value: value.into(),
|
||||
});
|
||||
@@ -349,7 +421,9 @@ impl BatchPutRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BatchPutResponse(PbBatchPutResponse);
|
||||
pub struct BatchPutResponse {
|
||||
pub prev_kvs: Vec<KeyValue>,
|
||||
}
|
||||
|
||||
impl TryFrom<PbBatchPutResponse> for BatchPutResponse {
|
||||
type Error = error::Error;
|
||||
@@ -357,24 +431,23 @@ impl TryFrom<PbBatchPutResponse> for BatchPutResponse {
|
||||
fn try_from(pb: PbBatchPutResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self {
|
||||
prev_kvs: pb.prev_kvs.into_iter().map(KeyValue::new).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchPutResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbBatchPutResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbBatchPutResponse {
|
||||
PbBatchPutResponse {
|
||||
header: Some(header),
|
||||
prev_kvs: self.prev_kvs.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_prev_kvs(&mut self) -> Vec<KeyValue> {
|
||||
self.0.prev_kvs.drain(..).map(KeyValue::new).collect()
|
||||
self.prev_kvs.drain(..).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,6 +469,15 @@ impl From<BatchDeleteRequest> for PbBatchDeleteRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbBatchDeleteRequest> for BatchDeleteRequest {
|
||||
fn from(value: PbBatchDeleteRequest) -> Self {
|
||||
Self {
|
||||
keys: value.keys,
|
||||
prev_kv: value.prev_kv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchDeleteRequest {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
@@ -421,7 +503,9 @@ impl BatchDeleteRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BatchDeleteResponse(PbBatchDeleteResponse);
|
||||
pub struct BatchDeleteResponse {
|
||||
pub prev_kvs: Vec<KeyValue>,
|
||||
}
|
||||
|
||||
impl TryFrom<PbBatchDeleteResponse> for BatchDeleteResponse {
|
||||
type Error = error::Error;
|
||||
@@ -429,24 +513,18 @@ impl TryFrom<PbBatchDeleteResponse> for BatchDeleteResponse {
|
||||
fn try_from(pb: PbBatchDeleteResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self {
|
||||
prev_kvs: pb.prev_kvs.into_iter().map(KeyValue::new).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BatchDeleteResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbBatchDeleteResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_prev_kvs(&mut self) -> Vec<KeyValue> {
|
||||
self.0.prev_kvs.drain(..).map(KeyValue::new).collect()
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbBatchDeleteResponse {
|
||||
PbBatchDeleteResponse {
|
||||
header: Some(header),
|
||||
prev_kvs: self.prev_kvs.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,6 +549,16 @@ impl From<CompareAndPutRequest> for PbCompareAndPutRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbCompareAndPutRequest> for CompareAndPutRequest {
|
||||
fn from(value: PbCompareAndPutRequest) -> Self {
|
||||
Self {
|
||||
key: value.key,
|
||||
expect: value.expect,
|
||||
value: value.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompareAndPutRequest {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
@@ -504,8 +592,11 @@ impl CompareAndPutRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CompareAndPutResponse(PbCompareAndPutResponse);
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CompareAndPutResponse {
|
||||
pub success: bool,
|
||||
pub prev_kv: Option<KeyValue>,
|
||||
}
|
||||
|
||||
impl TryFrom<PbCompareAndPutResponse> for CompareAndPutResponse {
|
||||
type Error = error::Error;
|
||||
@@ -513,29 +604,30 @@ impl TryFrom<PbCompareAndPutResponse> for CompareAndPutResponse {
|
||||
fn try_from(pb: PbCompareAndPutResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self {
|
||||
success: pb.success,
|
||||
prev_kv: pb.prev_kv.map(KeyValue::new),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CompareAndPutResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbCompareAndPutResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbCompareAndPutResponse {
|
||||
PbCompareAndPutResponse {
|
||||
header: Some(header),
|
||||
success: self.success,
|
||||
prev_kv: self.prev_kv.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_success(&self) -> bool {
|
||||
self.0.success
|
||||
self.success
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_prev_kv(&mut self) -> Option<KeyValue> {
|
||||
self.0.prev_kv.take().map(KeyValue::new)
|
||||
self.prev_kv.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,6 +663,16 @@ impl From<DeleteRangeRequest> for PbDeleteRangeRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbDeleteRangeRequest> for DeleteRangeRequest {
|
||||
fn from(value: PbDeleteRangeRequest) -> Self {
|
||||
Self {
|
||||
key: value.key,
|
||||
range_end: value.range_end,
|
||||
prev_kv: value.prev_kv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DeleteRangeRequest {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
@@ -624,8 +726,11 @@ impl DeleteRangeRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeleteRangeResponse(PbDeleteRangeResponse);
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DeleteRangeResponse {
|
||||
pub deleted: i64,
|
||||
pub prev_kvs: Vec<KeyValue>,
|
||||
}
|
||||
|
||||
impl TryFrom<PbDeleteRangeResponse> for DeleteRangeResponse {
|
||||
type Error = error::Error;
|
||||
@@ -633,29 +738,30 @@ impl TryFrom<PbDeleteRangeResponse> for DeleteRangeResponse {
|
||||
fn try_from(pb: PbDeleteRangeResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self {
|
||||
deleted: pb.deleted,
|
||||
prev_kvs: pb.prev_kvs.into_iter().map(KeyValue::new).collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DeleteRangeResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbDeleteRangeResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbDeleteRangeResponse {
|
||||
PbDeleteRangeResponse {
|
||||
header: Some(header),
|
||||
deleted: self.deleted,
|
||||
prev_kvs: self.prev_kvs.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn deleted(&self) -> i64 {
|
||||
self.0.deleted
|
||||
self.deleted
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_prev_kvs(&mut self) -> Vec<KeyValue> {
|
||||
self.0.prev_kvs.drain(..).map(KeyValue::new).collect()
|
||||
self.prev_kvs.drain(..).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -678,6 +784,15 @@ impl From<MoveValueRequest> for PbMoveValueRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PbMoveValueRequest> for MoveValueRequest {
|
||||
fn from(value: PbMoveValueRequest) -> Self {
|
||||
Self {
|
||||
from_key: value.from_key,
|
||||
to_key: value.to_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveValueRequest {
|
||||
#[inline]
|
||||
pub fn new(from_key: impl Into<Vec<u8>>, to_key: impl Into<Vec<u8>>) -> Self {
|
||||
@@ -689,7 +804,7 @@ impl MoveValueRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MoveValueResponse(PbMoveValueResponse);
|
||||
pub struct MoveValueResponse(pub Option<KeyValue>);
|
||||
|
||||
impl TryFrom<PbMoveValueResponse> for MoveValueResponse {
|
||||
type Error = error::Error;
|
||||
@@ -697,24 +812,21 @@ impl TryFrom<PbMoveValueResponse> for MoveValueResponse {
|
||||
fn try_from(pb: PbMoveValueResponse) -> Result<Self> {
|
||||
util::check_response_header(pb.header.as_ref())?;
|
||||
|
||||
Ok(Self::new(pb))
|
||||
Ok(Self(pb.kv.map(KeyValue::new)))
|
||||
}
|
||||
}
|
||||
|
||||
impl MoveValueResponse {
|
||||
#[inline]
|
||||
pub fn new(res: PbMoveValueResponse) -> Self {
|
||||
Self(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_header(&mut self) -> Option<ResponseHeader> {
|
||||
self.0.header.take().map(ResponseHeader::new)
|
||||
pub fn to_proto_resp(self, header: PbResponseHeader) -> PbMoveValueResponse {
|
||||
PbMoveValueResponse {
|
||||
header: Some(header),
|
||||
kv: self.0.map(Into::into),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_kv(&mut self) -> Option<KeyValue> {
|
||||
self.0.kv.take().map(KeyValue::new)
|
||||
self.0.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,9 +896,8 @@ mod tests {
|
||||
more: true,
|
||||
};
|
||||
|
||||
let mut res = RangeResponse::new(pb_res);
|
||||
assert!(res.take_header().is_none());
|
||||
assert!(res.more());
|
||||
let mut res: RangeResponse = pb_res.try_into().unwrap();
|
||||
assert!(res.more);
|
||||
let mut kvs = res.take_kvs();
|
||||
let kv0 = kvs.get_mut(0).unwrap();
|
||||
assert_eq!(b"k1".to_vec(), kv0.key().to_vec());
|
||||
@@ -826,9 +937,8 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let mut res = PutResponse::new(pb_res);
|
||||
assert!(res.take_header().is_none());
|
||||
let mut kv = res.take_prev_kv().unwrap();
|
||||
let res: PutResponse = pb_res.try_into().unwrap();
|
||||
let mut kv = res.prev_kv.unwrap();
|
||||
assert_eq!(b"k1".to_vec(), kv.key().to_vec());
|
||||
assert_eq!(b"k1".to_vec(), kv.take_key());
|
||||
assert_eq!(b"v1".to_vec(), kv.value().to_vec());
|
||||
@@ -859,12 +969,8 @@ mod tests {
|
||||
value: b"test_value1".to_vec(),
|
||||
}],
|
||||
};
|
||||
let mut res = BatchGetResponse::new(pb_res);
|
||||
|
||||
assert!(res.take_header().is_none());
|
||||
|
||||
let kvs = res.take_kvs();
|
||||
|
||||
let res: BatchGetResponse = pb_res.try_into().unwrap();
|
||||
let kvs = res.kvs;
|
||||
assert_eq!(b"test_key1".as_slice(), kvs[0].key());
|
||||
assert_eq!(b"test_value1".as_slice(), kvs[0].value());
|
||||
}
|
||||
@@ -898,8 +1004,7 @@ mod tests {
|
||||
}],
|
||||
};
|
||||
|
||||
let mut res = BatchPutResponse::new(pb_res);
|
||||
assert!(res.take_header().is_none());
|
||||
let mut res: BatchPutResponse = pb_res.try_into().unwrap();
|
||||
let kvs = res.take_prev_kvs();
|
||||
assert_eq!(b"k1".to_vec(), kvs[0].key().to_vec());
|
||||
assert_eq!(b"v1".to_vec(), kvs[0].value().to_vec());
|
||||
@@ -931,9 +1036,8 @@ mod tests {
|
||||
}],
|
||||
};
|
||||
|
||||
let mut res = BatchDeleteResponse::new(pb_res);
|
||||
assert!(res.take_header().is_none());
|
||||
let kvs = res.take_prev_kvs();
|
||||
let res: BatchDeleteResponse = pb_res.try_into().unwrap();
|
||||
let kvs = res.prev_kvs;
|
||||
assert_eq!(b"k1".to_vec(), kvs[0].key().to_vec());
|
||||
assert_eq!(b"v1".to_vec(), kvs[0].value().to_vec());
|
||||
}
|
||||
@@ -969,8 +1073,7 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let mut res = CompareAndPutResponse::new(pb_res);
|
||||
assert!(res.take_header().is_none());
|
||||
let mut res: CompareAndPutResponse = pb_res.try_into().unwrap();
|
||||
let mut kv = res.take_prev_kv().unwrap();
|
||||
assert_eq!(b"k1".to_vec(), kv.key().to_vec());
|
||||
assert_eq!(b"k1".to_vec(), kv.take_key());
|
||||
@@ -1026,7 +1129,6 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut res: DeleteRangeResponse = pb_res.try_into().unwrap();
|
||||
assert!(res.take_header().is_none());
|
||||
assert_eq!(2, res.deleted());
|
||||
let mut kvs = res.take_prev_kvs();
|
||||
let kv0 = kvs.get_mut(0).unwrap();
|
||||
@@ -1064,8 +1166,7 @@ mod tests {
|
||||
}),
|
||||
};
|
||||
|
||||
let mut res = MoveValueResponse::new(pb_res);
|
||||
assert!(res.take_header().is_none());
|
||||
let mut res: MoveValueResponse = pb_res.try_into().unwrap();
|
||||
let mut kv = res.take_kv().unwrap();
|
||||
assert_eq!(b"k1".to_vec(), kv.key().to_vec());
|
||||
assert_eq!(b"k1".to_vec(), kv.take_key());
|
||||
|
||||
@@ -16,7 +16,6 @@ use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use datafusion::catalog::catalog::CatalogList;
|
||||
use datafusion::execution::context::SessionState;
|
||||
use datafusion::execution::runtime_env::RuntimeEnv;
|
||||
@@ -43,9 +42,10 @@ impl SubstraitPlan for DFLogicalSubstraitConvertor {
|
||||
&self,
|
||||
message: B,
|
||||
catalog_list: Arc<dyn CatalogList>,
|
||||
catalog: &str,
|
||||
schema: &str,
|
||||
) -> Result<Self::Plan, Self::Error> {
|
||||
let state_config = SessionConfig::new()
|
||||
.with_default_catalog_and_schema(DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME);
|
||||
let state_config = SessionConfig::new().with_default_catalog_and_schema(catalog, schema);
|
||||
let state = SessionState::with_config_rt(state_config, Arc::new(RuntimeEnv::default()));
|
||||
let mut context = SessionContext::with_state(state);
|
||||
context.register_catalog_list(catalog_list);
|
||||
|
||||
@@ -36,6 +36,8 @@ pub trait SubstraitPlan {
|
||||
&self,
|
||||
message: B,
|
||||
catalog_list: Arc<dyn CatalogList>,
|
||||
catalog: &str,
|
||||
schema: &str,
|
||||
) -> Result<Self::Plan, Self::Error>;
|
||||
|
||||
fn encode(&self, plan: Self::Plan) -> Result<Bytes, Self::Error>;
|
||||
|
||||
@@ -52,6 +52,7 @@ pub enum ObjectStoreConfig {
|
||||
S3(S3Config),
|
||||
Oss(OssConfig),
|
||||
Azblob(AzblobConfig),
|
||||
Gcs(GcsConfig),
|
||||
}
|
||||
|
||||
/// Storage engine config
|
||||
@@ -122,6 +123,19 @@ pub struct AzblobConfig {
|
||||
pub cache_capacity: Option<ReadableSize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct GcsConfig {
|
||||
pub root: String,
|
||||
pub bucket: String,
|
||||
pub scope: String,
|
||||
#[serde(skip_serializing)]
|
||||
pub credential_path: SecretString,
|
||||
pub endpoint: String,
|
||||
pub cache_path: Option<String>,
|
||||
pub cache_capacity: Option<ReadableSize>,
|
||||
}
|
||||
|
||||
impl Default for S3Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -166,6 +180,20 @@ impl Default for AzblobConfig {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GcsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
root: String::default(),
|
||||
bucket: String::default(),
|
||||
scope: String::default(),
|
||||
credential_path: SecretString::from(String::default()),
|
||||
endpoint: String::default(),
|
||||
cache_path: Option::default(),
|
||||
cache_capacity: Option::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ObjectStoreConfig {
|
||||
fn default() -> Self {
|
||||
ObjectStoreConfig::File(FileConfig {
|
||||
|
||||
@@ -74,7 +74,12 @@ impl Instance {
|
||||
.await?;
|
||||
|
||||
let logical_plan = DFLogicalSubstraitConvertor
|
||||
.decode(plan_bytes.as_slice(), Arc::new(catalog_list) as Arc<_>)
|
||||
.decode(
|
||||
plan_bytes.as_slice(),
|
||||
Arc::new(catalog_list) as Arc<_>,
|
||||
&ctx.current_catalog(),
|
||||
&ctx.current_schema(),
|
||||
)
|
||||
.await
|
||||
.context(DecodeLogicalPlanSnafu)?;
|
||||
|
||||
@@ -408,6 +413,7 @@ mod test {
|
||||
},
|
||||
],
|
||||
})),
|
||||
..Default::default()
|
||||
})),
|
||||
});
|
||||
let output = instance.do_query(query, QueryContext::arc()).await.unwrap();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
mod azblob;
|
||||
mod fs;
|
||||
mod gcs;
|
||||
mod oss;
|
||||
mod s3;
|
||||
|
||||
@@ -40,6 +41,7 @@ pub(crate) async fn new_object_store(store_config: &ObjectStoreConfig) -> Result
|
||||
ObjectStoreConfig::Azblob(azblob_config) => {
|
||||
azblob::new_azblob_object_store(azblob_config).await
|
||||
}
|
||||
ObjectStoreConfig::Gcs(gcs_config) => gcs::new_gcs_object_store(gcs_config).await,
|
||||
}?;
|
||||
|
||||
// Enable retry layer and cache layer for non-fs object storages
|
||||
@@ -88,6 +90,13 @@ async fn create_object_store_with_cache(
|
||||
.unwrap_or(DEFAULT_OBJECT_STORE_CACHE_SIZE);
|
||||
(path, capacity)
|
||||
}
|
||||
ObjectStoreConfig::Gcs(gcs_config) => {
|
||||
let path = gcs_config.cache_path.as_ref();
|
||||
let capacity = gcs_config
|
||||
.cache_capacity
|
||||
.unwrap_or(DEFAULT_OBJECT_STORE_CACHE_SIZE);
|
||||
(path, capacity)
|
||||
}
|
||||
_ => (None, ReadableSize(0)),
|
||||
};
|
||||
|
||||
|
||||
42
src/datanode/src/store/gcs.rs
Normal file
42
src/datanode/src/store/gcs.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use common_telemetry::logging::info;
|
||||
use object_store::services::Gcs as GCSBuilder;
|
||||
use object_store::{util, ObjectStore};
|
||||
use secrecy::ExposeSecret;
|
||||
use snafu::prelude::*;
|
||||
|
||||
use crate::datanode::GcsConfig;
|
||||
use crate::error::{self, Result};
|
||||
|
||||
pub(crate) async fn new_gcs_object_store(gcs_config: &GcsConfig) -> Result<ObjectStore> {
|
||||
let root = util::normalize_dir(&gcs_config.root);
|
||||
info!(
|
||||
"The gcs storage bucket is: {}, root is: {}",
|
||||
gcs_config.bucket, &root
|
||||
);
|
||||
|
||||
let mut builder = GCSBuilder::default();
|
||||
builder
|
||||
.root(&root)
|
||||
.bucket(&gcs_config.bucket)
|
||||
.scope(&gcs_config.scope)
|
||||
.credential_path(gcs_config.credential_path.expose_secret())
|
||||
.endpoint(&gcs_config.endpoint);
|
||||
|
||||
Ok(ObjectStore::new(builder)
|
||||
.context(error::InitBackendSnafu)?
|
||||
.finish())
|
||||
}
|
||||
@@ -18,7 +18,6 @@ use common_datasource::file_format::csv::{CsvConfigBuilder, CsvFormat, CsvOpener
|
||||
use common_datasource::file_format::json::{JsonFormat, JsonOpener};
|
||||
use common_datasource::file_format::parquet::{DefaultParquetFileReaderFactory, ParquetFormat};
|
||||
use common_datasource::file_format::Format;
|
||||
use common_query::physical_plan::{PhysicalPlanAdapter, PhysicalPlanRef};
|
||||
use common_query::prelude::Expr;
|
||||
use common_query::DfPhysicalPlan;
|
||||
use common_recordbatch::adapter::RecordBatchStreamAdapter;
|
||||
@@ -33,10 +32,9 @@ use datafusion::physical_plan::file_format::{FileOpener, FileScanConfig, FileStr
|
||||
use datafusion::physical_plan::metrics::ExecutionPlanMetricsSet;
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datatypes::arrow::datatypes::Schema as ArrowSchema;
|
||||
use datatypes::schema::{Schema, SchemaRef};
|
||||
use datatypes::schema::SchemaRef;
|
||||
use object_store::ObjectStore;
|
||||
use snafu::ResultExt;
|
||||
use table::table::scan::StreamScanAdapter;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
|
||||
@@ -87,17 +85,6 @@ fn build_json_opener(
|
||||
))
|
||||
}
|
||||
|
||||
fn build_scan_plan<T: FileOpener + Send + 'static>(
|
||||
opener: T,
|
||||
file_schema: Arc<ArrowSchema>,
|
||||
files: &[String],
|
||||
projection: Option<&Vec<usize>>,
|
||||
limit: Option<usize>,
|
||||
) -> Result<PhysicalPlanRef> {
|
||||
let adapter = build_record_batch_stream(opener, file_schema, files, projection, limit)?;
|
||||
Ok(Arc::new(StreamScanAdapter::new(adapter)))
|
||||
}
|
||||
|
||||
fn build_record_batch_stream<T: FileOpener + Send + 'static>(
|
||||
opener: T,
|
||||
file_schema: Arc<ArrowSchema>,
|
||||
@@ -130,22 +117,6 @@ fn build_record_batch_stream<T: FileOpener + Send + 'static>(
|
||||
Ok(Box::pin(adapter))
|
||||
}
|
||||
|
||||
fn new_csv_scan_plan(
|
||||
_ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
format: &CsvFormat,
|
||||
) -> Result<PhysicalPlanRef> {
|
||||
let file_schema = config.file_schema.arrow_schema().clone();
|
||||
let opener = build_csv_opener(file_schema.clone(), config, format)?;
|
||||
build_scan_plan(
|
||||
opener,
|
||||
file_schema,
|
||||
config.files,
|
||||
config.projection,
|
||||
config.limit,
|
||||
)
|
||||
}
|
||||
|
||||
fn new_csv_stream(
|
||||
_ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
@@ -162,22 +133,6 @@ fn new_csv_stream(
|
||||
)
|
||||
}
|
||||
|
||||
fn new_json_scan_plan(
|
||||
_ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
format: &JsonFormat,
|
||||
) -> Result<PhysicalPlanRef> {
|
||||
let file_schema = config.file_schema.arrow_schema().clone();
|
||||
let opener = build_json_opener(file_schema.clone(), config, format)?;
|
||||
build_scan_plan(
|
||||
opener,
|
||||
file_schema,
|
||||
config.files,
|
||||
config.projection,
|
||||
config.limit,
|
||||
)
|
||||
}
|
||||
|
||||
fn new_json_stream(
|
||||
_ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
@@ -194,76 +149,6 @@ fn new_json_stream(
|
||||
)
|
||||
}
|
||||
|
||||
fn new_parquet_scan_plan(
|
||||
_ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
_format: &ParquetFormat,
|
||||
) -> Result<PhysicalPlanRef> {
|
||||
let file_schema = config.file_schema.arrow_schema().clone();
|
||||
let ScanPlanConfig {
|
||||
files,
|
||||
projection,
|
||||
limit,
|
||||
filters,
|
||||
store,
|
||||
..
|
||||
} = config;
|
||||
|
||||
let scan_config = FileScanConfig {
|
||||
object_store_url: ObjectStoreUrl::parse("empty://").unwrap(), // won't be used
|
||||
file_schema: file_schema.clone(),
|
||||
file_groups: vec![files
|
||||
.iter()
|
||||
.map(|filename| PartitionedFile::new(filename.to_string(), 0))
|
||||
.collect::<Vec<_>>()],
|
||||
statistics: Default::default(),
|
||||
projection: projection.cloned(),
|
||||
limit: *limit,
|
||||
table_partition_cols: vec![],
|
||||
output_ordering: None,
|
||||
infinite_source: false,
|
||||
};
|
||||
|
||||
let filters = filters
|
||||
.iter()
|
||||
.map(|f| f.df_expr().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let filters = if let Some(expr) = conjunction(filters) {
|
||||
let df_schema = file_schema
|
||||
.clone()
|
||||
.to_dfschema_ref()
|
||||
.context(error::ParquetScanPlanSnafu)?;
|
||||
|
||||
let filters = create_physical_expr(&expr, &df_schema, &file_schema, &ExecutionProps::new())
|
||||
.context(error::ParquetScanPlanSnafu)?;
|
||||
Some(filters)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let exec = ParquetExec::new(scan_config, filters, None).with_parquet_file_reader_factory(
|
||||
Arc::new(DefaultParquetFileReaderFactory::new(store.clone())),
|
||||
);
|
||||
|
||||
let projected_schema = if let Some(projection) = config.projection {
|
||||
Arc::new(
|
||||
file_schema
|
||||
.project(projection)
|
||||
.context(error::ProjectSchemaSnafu)?,
|
||||
)
|
||||
} else {
|
||||
file_schema
|
||||
};
|
||||
|
||||
let schema = Schema::try_from(projected_schema).context(error::ConvertSchemaSnafu)?;
|
||||
|
||||
Ok(Arc::new(PhysicalPlanAdapter::new(
|
||||
Arc::new(schema),
|
||||
Arc::new(exec),
|
||||
)))
|
||||
}
|
||||
|
||||
fn new_parquet_stream_with_exec_plan(
|
||||
_ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
@@ -338,19 +223,6 @@ pub struct ScanPlanConfig<'a> {
|
||||
pub store: ObjectStore,
|
||||
}
|
||||
|
||||
pub fn create_physical_plan(
|
||||
format: &Format,
|
||||
ctx: &CreateScanPlanContext,
|
||||
config: &ScanPlanConfig,
|
||||
) -> Result<PhysicalPlanRef> {
|
||||
match format {
|
||||
Format::Csv(format) => new_csv_scan_plan(ctx, config, format),
|
||||
Format::Json(format) => new_json_scan_plan(ctx, config, format),
|
||||
Format::Parquet(format) => new_parquet_scan_plan(ctx, config, format),
|
||||
_ => error::UnsupportedFormatSnafu { format: *format }.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_stream(
|
||||
format: &Format,
|
||||
ctx: &CreateScanPlanContext,
|
||||
|
||||
@@ -28,17 +28,17 @@ use catalog::helper::{
|
||||
use catalog::information_schema::InformationSchemaProvider;
|
||||
use catalog::remote::KvCacheInvalidatorRef;
|
||||
use catalog::{
|
||||
CatalogManager, DeregisterTableRequest, RegisterSchemaRequest, RegisterSystemTableRequest,
|
||||
RegisterTableRequest, RenameTableRequest,
|
||||
CatalogManager, DeregisterSchemaRequest, DeregisterTableRequest, RegisterSchemaRequest,
|
||||
RegisterSystemTableRequest, RegisterTableRequest, RenameTableRequest,
|
||||
};
|
||||
use client::client_manager::DatanodeClients;
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, INFORMATION_SCHEMA_NAME};
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_meta::kv_backend::{Kv, KvBackendRef};
|
||||
use common_meta::kv_backend::KvBackendRef;
|
||||
use common_meta::rpc::store::RangeRequest;
|
||||
use common_meta::rpc::KeyValue;
|
||||
use common_meta::table_name::TableName;
|
||||
use common_telemetry::warn;
|
||||
use futures::StreamExt;
|
||||
use futures_util::TryStreamExt;
|
||||
use partition::manager::PartitionRuleManagerRef;
|
||||
use snafu::prelude::*;
|
||||
use table::table::numbers::NumbersTable;
|
||||
@@ -130,7 +130,7 @@ impl CatalogManager for FrontendCatalogManager {
|
||||
}
|
||||
|
||||
async fn register_catalog(&self, _name: String) -> CatalogResult<bool> {
|
||||
unimplemented!("FrontendCatalogManager does not support register catalog")
|
||||
unimplemented!("FrontendCatalogManager does not support registering catalog")
|
||||
}
|
||||
|
||||
// TODO(LFC): Handle the table caching in (de)register_table.
|
||||
@@ -151,7 +151,14 @@ impl CatalogManager for FrontendCatalogManager {
|
||||
&self,
|
||||
_request: RegisterSchemaRequest,
|
||||
) -> catalog::error::Result<bool> {
|
||||
unimplemented!("FrontendCatalogManager does not support register schema")
|
||||
unimplemented!("FrontendCatalogManager does not support registering schema")
|
||||
}
|
||||
|
||||
async fn deregister_schema(
|
||||
&self,
|
||||
_request: DeregisterSchemaRequest,
|
||||
) -> catalog_err::Result<bool> {
|
||||
unimplemented!("FrontendCatalogManager does not support deregistering schema")
|
||||
}
|
||||
|
||||
async fn rename_table(&self, _request: RenameTableRequest) -> catalog_err::Result<bool> {
|
||||
@@ -252,10 +259,17 @@ impl CatalogManager for FrontendCatalogManager {
|
||||
|
||||
async fn catalog_names(&self) -> CatalogResult<Vec<String>> {
|
||||
let key = build_catalog_prefix();
|
||||
let mut iter = self.backend.range(key.as_bytes());
|
||||
let req = RangeRequest::new().with_prefix(key.as_bytes());
|
||||
|
||||
let kvs = self
|
||||
.backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
|
||||
let mut res = HashSet::new();
|
||||
while let Some(r) = iter.next().await {
|
||||
let Kv(k, _) = r.context(TableMetadataManagerSnafu)?;
|
||||
for KeyValue { key: k, value: _ } in kvs {
|
||||
let catalog_key = String::from_utf8_lossy(&k);
|
||||
if let Ok(key) = CatalogKey::parse(catalog_key.as_ref()) {
|
||||
let _ = res.insert(key.catalog_name);
|
||||
@@ -268,10 +282,17 @@ impl CatalogManager for FrontendCatalogManager {
|
||||
|
||||
async fn schema_names(&self, catalog: &str) -> CatalogResult<Vec<String>> {
|
||||
let key = build_schema_prefix(catalog);
|
||||
let mut iter = self.backend.range(key.as_bytes());
|
||||
let req = RangeRequest::new().with_prefix(key.as_bytes());
|
||||
|
||||
let kvs = self
|
||||
.backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs;
|
||||
|
||||
let mut res = HashSet::new();
|
||||
while let Some(r) = iter.next().await {
|
||||
let Kv(k, _) = r.context(TableMetadataManagerSnafu)?;
|
||||
for KeyValue { key: k, value: _ } in kvs {
|
||||
let key =
|
||||
SchemaKey::parse(String::from_utf8_lossy(&k)).context(InvalidCatalogValueSnafu)?;
|
||||
let _ = res.insert(key.schema_name);
|
||||
@@ -285,16 +306,23 @@ impl CatalogManager for FrontendCatalogManager {
|
||||
tables.push("numbers".to_string());
|
||||
}
|
||||
let key = build_table_global_prefix(catalog, schema);
|
||||
let iter = self.backend.range(key.as_bytes());
|
||||
let req = RangeRequest::new().with_prefix(key.as_bytes());
|
||||
|
||||
let iter = self
|
||||
.backend
|
||||
.range(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?
|
||||
.kvs
|
||||
.into_iter();
|
||||
|
||||
let result = iter
|
||||
.map(|r| {
|
||||
let Kv(k, _) = r.context(TableMetadataManagerSnafu)?;
|
||||
.map(|KeyValue { key: k, value: _ }| {
|
||||
let key = TableGlobalKey::parse(String::from_utf8_lossy(&k))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
Ok(key.table_name)
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
.collect::<CatalogResult<Vec<_>>>()?;
|
||||
tables.extend(result);
|
||||
Ok(tables)
|
||||
}
|
||||
@@ -377,7 +405,7 @@ impl CatalogManager for FrontendCatalogManager {
|
||||
let Some(kv) = self.backend().get(table_global_key.to_string().as_bytes()).await.context(TableMetadataManagerSnafu)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let v = TableGlobalValue::from_bytes(kv.1).context(InvalidCatalogValueSnafu)?;
|
||||
let v = TableGlobalValue::from_bytes(kv.value).context(InvalidCatalogValueSnafu)?;
|
||||
let table_info = Arc::new(
|
||||
v.table_info
|
||||
.try_into()
|
||||
|
||||
@@ -25,6 +25,12 @@ use store_api::storage::RegionNumber;
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[snafu(display("Execute the operation timeout, source: {}", source))]
|
||||
Timeout {
|
||||
location: Location,
|
||||
source: tokio::time::error::Elapsed,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to handle heartbeat response, source: {}", source))]
|
||||
HandleHeartbeatResponse {
|
||||
location: Location,
|
||||
@@ -633,7 +639,8 @@ impl ErrorExt for Error {
|
||||
| Error::FindRegionRoute { .. }
|
||||
| Error::BuildDfLogicalPlan { .. }
|
||||
| Error::BuildTableMeta { .. }
|
||||
| Error::VectorToGrpcColumn { .. } => StatusCode::Internal,
|
||||
| Error::VectorToGrpcColumn { .. }
|
||||
| Error::Timeout { .. } => StatusCode::Internal,
|
||||
|
||||
Error::IncompleteGrpcResult { .. }
|
||||
| Error::ContextValueNotFound { .. }
|
||||
|
||||
@@ -320,6 +320,7 @@ pub(crate) fn to_alter_expr(
|
||||
schema_name,
|
||||
table_name,
|
||||
kind: Some(kind),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,7 @@ impl Instance {
|
||||
.enable_router()
|
||||
.enable_store()
|
||||
.enable_heartbeat()
|
||||
.enable_ddl()
|
||||
.channel_manager(channel_manager)
|
||||
.build();
|
||||
meta_client
|
||||
@@ -401,6 +402,7 @@ impl Instance {
|
||||
schema_name: ctx.current_schema(),
|
||||
table_name: table_name.to_string(),
|
||||
kind: Some(Kind::AddColumns(add_columns)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
self.grpc_query_handler
|
||||
|
||||
@@ -16,6 +16,7 @@ pub(crate) mod inserter;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use api::helper::ColumnDataTypeWrapper;
|
||||
use api::v1::ddl_request::Expr as DdlExpr;
|
||||
@@ -33,14 +34,14 @@ use client::Database;
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_catalog::format_full_table_name;
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_meta::rpc::ddl::{DdlTask, SubmitDdlTaskRequest, SubmitDdlTaskResponse};
|
||||
use common_meta::rpc::router::{
|
||||
CreateRequest as MetaCreateRequest, DeleteRequest as MetaDeleteRequest,
|
||||
Partition as MetaPartition, RouteRequest, RouteResponse,
|
||||
DeleteRequest as MetaDeleteRequest, Partition as MetaPartition, RouteRequest,
|
||||
};
|
||||
use common_meta::rpc::store::CompareAndPutRequest;
|
||||
use common_meta::table_name::TableName;
|
||||
use common_query::Output;
|
||||
use common_telemetry::{debug, info, warn};
|
||||
use common_telemetry::{debug, info};
|
||||
use datanode::instance::sql::table_idents_to_full_name;
|
||||
use datanode::sql::SqlHandler;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
@@ -58,11 +59,12 @@ use sql::statements::create::{PartitionEntry, Partitions};
|
||||
use sql::statements::statement::Statement;
|
||||
use sql::statements::{self, sql_value_to_value};
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::engine::{self, TableReference};
|
||||
use table::engine::TableReference;
|
||||
use table::metadata::{RawTableInfo, RawTableMeta, TableIdent, TableType};
|
||||
use table::requests::TableOptions;
|
||||
use table::table::AlterContext;
|
||||
use table::TableRef;
|
||||
use tokio::time::timeout;
|
||||
|
||||
use crate::catalog::FrontendCatalogManager;
|
||||
use crate::error::{
|
||||
@@ -98,17 +100,6 @@ impl DistInstance {
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_table(&self, table_name: &TableName) -> Result<Option<TableRef>> {
|
||||
self.catalog_manager
|
||||
.table(
|
||||
&table_name.catalog_name,
|
||||
&table_name.schema_name,
|
||||
&table_name.table_name,
|
||||
)
|
||||
.await
|
||||
.context(CatalogSnafu)
|
||||
}
|
||||
|
||||
pub async fn create_table(
|
||||
&self,
|
||||
create_table: &mut CreateTableExpr,
|
||||
@@ -121,56 +112,14 @@ impl DistInstance {
|
||||
&create_table.table_name,
|
||||
);
|
||||
|
||||
if let Some(table) = self.find_table(&table_name).await? {
|
||||
return if create_table.create_if_not_exists {
|
||||
Ok(table)
|
||||
} else {
|
||||
TableAlreadyExistSnafu {
|
||||
table: table_name.to_string(),
|
||||
}
|
||||
.fail()
|
||||
};
|
||||
}
|
||||
|
||||
let mut table_info = create_table_info(create_table)?;
|
||||
|
||||
let response = self
|
||||
.create_table_in_meta(create_table, partitions, &table_info)
|
||||
.await;
|
||||
let response = match response {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
return if let Some(table) = self.find_table(&table_name).await? {
|
||||
warn!("Table '{table_name}' is created concurrently by other Frontend nodes!");
|
||||
Ok(table)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
};
|
||||
let resp = self
|
||||
.create_table_procedure(create_table, partitions, table_info.clone())
|
||||
.await?;
|
||||
|
||||
let table_routes = response.table_routes;
|
||||
ensure!(
|
||||
table_routes.len() == 1,
|
||||
error::CreateTableRouteSnafu {
|
||||
table_name: create_table.table_name.to_string()
|
||||
}
|
||||
);
|
||||
let table_route = table_routes.first().unwrap();
|
||||
info!(
|
||||
"Creating distributed table {table_name} with table routes: {}",
|
||||
serde_json::to_string_pretty(table_route)
|
||||
.unwrap_or_else(|_| format!("{table_route:#?}"))
|
||||
);
|
||||
let region_routes = &table_route.region_routes;
|
||||
ensure!(
|
||||
!region_routes.is_empty(),
|
||||
error::FindRegionRouteSnafu {
|
||||
table_name: create_table.table_name.to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let table_id = table_route.table.id as u32;
|
||||
let table_id = resp.table_id;
|
||||
info!("Successfully created distributed table '{table_name}' with table id {table_id}");
|
||||
table_info.ident.table_id = table_id;
|
||||
let table_info = Arc::new(table_info.try_into().context(error::CreateTableInfoSnafu)?);
|
||||
|
||||
@@ -199,26 +148,6 @@ impl DistInstance {
|
||||
}
|
||||
);
|
||||
|
||||
for datanode in table_route.find_leaders() {
|
||||
let client = self.datanode_clients.get_client(&datanode).await;
|
||||
let client = Database::new(&table_name.catalog_name, &table_name.schema_name, client);
|
||||
|
||||
let regions = table_route.find_leader_regions(&datanode);
|
||||
let mut create_expr_for_region = create_table.clone();
|
||||
create_expr_for_region.region_numbers = regions;
|
||||
|
||||
debug!(
|
||||
"Creating table {:?} on Datanode {:?} with regions {:?}",
|
||||
create_table, datanode, create_expr_for_region.region_numbers,
|
||||
);
|
||||
|
||||
let _timer = common_telemetry::timer!(crate::metrics::DIST_CREATE_TABLE_IN_DATANODE);
|
||||
let _ = client
|
||||
.create(create_expr_for_region)
|
||||
.await
|
||||
.context(RequestDatanodeSnafu)?;
|
||||
}
|
||||
|
||||
// Since the table information created on meta does not go through KvBackend, so we
|
||||
// manually invalidate the cache here.
|
||||
//
|
||||
@@ -270,6 +199,7 @@ impl DistInstance {
|
||||
catalog_name: table_name.catalog_name.clone(),
|
||||
schema_name: table_name.schema_name.clone(),
|
||||
table_name: table_name.table_name.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
for table_route in route_response.table_routes.iter() {
|
||||
for datanode in table_route.find_leaders() {
|
||||
@@ -330,12 +260,13 @@ impl DistInstance {
|
||||
schema_name: table_name.schema_name.clone(),
|
||||
table_name: table_name.table_name.clone(),
|
||||
region_number,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
for table_route in &route_response.table_routes {
|
||||
let should_send_rpc = table_route.region_routes.iter().any(|route| {
|
||||
if let Some(n) = region_number {
|
||||
n == engine::region_number(route.region.id)
|
||||
n == route.region.id.region_number()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@@ -552,33 +483,27 @@ impl DistInstance {
|
||||
Ok(Output::AffectedRows(0))
|
||||
}
|
||||
|
||||
async fn create_table_in_meta(
|
||||
async fn create_table_procedure(
|
||||
&self,
|
||||
create_table: &CreateTableExpr,
|
||||
partitions: Option<Partitions>,
|
||||
table_info: &RawTableInfo,
|
||||
) -> Result<RouteResponse> {
|
||||
let _timer = common_telemetry::timer!(crate::metrics::DIST_CREATE_TABLE_IN_META);
|
||||
let mut catalog_name = create_table.catalog_name.clone();
|
||||
if catalog_name.is_empty() {
|
||||
catalog_name = DEFAULT_CATALOG_NAME.to_string();
|
||||
}
|
||||
let mut schema_name = create_table.schema_name.clone();
|
||||
if schema_name.is_empty() {
|
||||
schema_name = DEFAULT_SCHEMA_NAME.to_string();
|
||||
}
|
||||
let table_name = TableName::new(catalog_name, schema_name, create_table.table_name.clone());
|
||||
|
||||
table_info: RawTableInfo,
|
||||
) -> Result<SubmitDdlTaskResponse> {
|
||||
let partitions = parse_partitions(create_table, partitions)?;
|
||||
let request = MetaCreateRequest {
|
||||
table_name,
|
||||
partitions,
|
||||
table_info,
|
||||
let partitions = partitions.into_iter().map(Into::into).collect();
|
||||
|
||||
let request = SubmitDdlTaskRequest {
|
||||
task: DdlTask::new_create_table(create_table.clone(), partitions, table_info),
|
||||
};
|
||||
self.meta_client
|
||||
.create_route(request)
|
||||
.await
|
||||
.context(RequestMetaSnafu)
|
||||
|
||||
timeout(
|
||||
// TODO(weny): makes timeout configurable.
|
||||
Duration::from_secs(10),
|
||||
self.meta_client.submit_ddl_task(request),
|
||||
)
|
||||
.await
|
||||
.context(error::TimeoutSnafu)?
|
||||
.context(error::RequestMetaSnafu)
|
||||
}
|
||||
|
||||
async fn handle_dist_insert(
|
||||
@@ -694,7 +619,7 @@ fn create_partitions_stmt(partitions: Vec<PartitionInfo>) -> Result<Option<Parti
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
// Generated the partition name from id
|
||||
let name = &format!("r{}", info.id);
|
||||
let name = &format!("r{}", info.id.as_u64());
|
||||
let bounds = info.partition.partition_bounds();
|
||||
let value_list = bounds
|
||||
.iter()
|
||||
|
||||
@@ -183,6 +183,7 @@ mod tests {
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_meta::kv_backend::memory::MemoryKvBackend;
|
||||
use common_meta::kv_backend::{KvBackend, KvBackendRef};
|
||||
use common_meta::rpc::store::PutRequest;
|
||||
use datatypes::prelude::{ConcreteDataType, VectorRef};
|
||||
use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema, Schema};
|
||||
use datatypes::vectors::Int32Vector;
|
||||
@@ -199,26 +200,20 @@ mod tests {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
backend
|
||||
.set(
|
||||
default_catalog.as_bytes(),
|
||||
CatalogValue.as_bytes().unwrap().as_slice(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let req = PutRequest::new()
|
||||
.with_key(default_catalog.as_bytes())
|
||||
.with_value(CatalogValue.as_bytes().unwrap());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
let default_schema = SchemaKey {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
}
|
||||
.to_string();
|
||||
backend
|
||||
.set(
|
||||
default_schema.as_bytes(),
|
||||
SchemaValue.as_bytes().unwrap().as_slice(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let req = PutRequest::new()
|
||||
.with_key(default_schema.as_bytes())
|
||||
.with_value(SchemaValue.as_bytes().unwrap());
|
||||
backend.put(req).await.unwrap();
|
||||
|
||||
backend
|
||||
}
|
||||
@@ -257,13 +252,10 @@ mod tests {
|
||||
table_info: table_info.into(),
|
||||
};
|
||||
|
||||
backend
|
||||
.set(
|
||||
table_global_key.to_string().as_bytes(),
|
||||
table_global_value.as_bytes().unwrap().as_slice(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let req = PutRequest::new()
|
||||
.with_key(table_global_key.to_string().as_bytes())
|
||||
.with_value(table_global_value.as_bytes().unwrap());
|
||||
backend.put(req).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -20,8 +20,6 @@ pub(crate) const METRIC_RUN_SCRIPT_ELAPSED: &str = "frontend.run_script_elapsed"
|
||||
/// frontend metrics
|
||||
/// Metrics for creating table in dist mode.
|
||||
pub const DIST_CREATE_TABLE: &str = "frontend.dist.create_table";
|
||||
pub const DIST_CREATE_TABLE_IN_META: &str = "frontend.dist.create_table.update_meta";
|
||||
pub const DIST_CREATE_TABLE_IN_DATANODE: &str = "frontend.dist.create_table.invoke_datanode";
|
||||
pub const DIST_INGEST_ROW_COUNT: &str = "frontend.dist.ingest_rows";
|
||||
|
||||
/// The samples count of Prometheus remote write.
|
||||
|
||||
@@ -23,6 +23,7 @@ use catalog::helper::{TableGlobalKey, TableGlobalValue};
|
||||
use client::Database;
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_meta::key::TableRouteKey;
|
||||
use common_meta::rpc::store::{MoveValueRequest, PutRequest};
|
||||
use common_meta::table_name::TableName;
|
||||
use common_query::error::Result as QueryResult;
|
||||
use common_query::logical_plan::Expr;
|
||||
@@ -260,7 +261,7 @@ impl DistTable {
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
Ok(if let Some(raw) = raw {
|
||||
Some(TableGlobalValue::from_bytes(raw.1).context(error::CatalogEntrySerdeSnafu)?)
|
||||
Some(TableGlobalValue::from_bytes(raw.value).context(error::CatalogEntrySerdeSnafu)?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
@@ -272,19 +273,26 @@ impl DistTable {
|
||||
value: TableGlobalValue,
|
||||
) -> Result<()> {
|
||||
let value = value.as_bytes().context(error::CatalogEntrySerdeSnafu)?;
|
||||
self.catalog_manager
|
||||
let req = PutRequest::new()
|
||||
.with_key(key.to_string().as_bytes())
|
||||
.with_value(value);
|
||||
let _ = self
|
||||
.catalog_manager
|
||||
.backend()
|
||||
.set(key.to_string().as_bytes(), &value)
|
||||
.put(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_table_global_value(&self, key: TableGlobalKey) -> Result<()> {
|
||||
self.catalog_manager
|
||||
let _ = self
|
||||
.catalog_manager
|
||||
.backend()
|
||||
.delete(key.to_string().as_bytes())
|
||||
.delete(key.to_string().as_bytes(), false)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn move_table_route_value(
|
||||
@@ -296,24 +304,25 @@ impl DistTable {
|
||||
new_table_name: &str,
|
||||
) -> Result<()> {
|
||||
let old_key = TableRouteKey {
|
||||
table_id: table_id.into(),
|
||||
table_id,
|
||||
catalog_name,
|
||||
schema_name,
|
||||
table_name: old_table_name,
|
||||
}
|
||||
.key();
|
||||
.to_string();
|
||||
|
||||
let new_key = TableRouteKey {
|
||||
table_id: table_id.into(),
|
||||
table_id,
|
||||
catalog_name,
|
||||
schema_name,
|
||||
table_name: new_table_name,
|
||||
}
|
||||
.key();
|
||||
.to_string();
|
||||
|
||||
let req = MoveValueRequest::new(old_key.as_bytes(), new_key.as_bytes());
|
||||
self.catalog_manager
|
||||
.backend()
|
||||
.move_value(old_key.as_bytes(), new_key.as_bytes())
|
||||
.move_value(req)
|
||||
.await
|
||||
.context(TableMetadataManagerSnafu)?;
|
||||
|
||||
@@ -622,7 +631,7 @@ pub(crate) mod test {
|
||||
vec![
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 3,
|
||||
id: 3.into(),
|
||||
name: "r1".to_string(),
|
||||
partition: Some(
|
||||
PartitionDef::new(
|
||||
@@ -639,7 +648,7 @@ pub(crate) mod test {
|
||||
},
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 2,
|
||||
id: 2.into(),
|
||||
name: "r2".to_string(),
|
||||
partition: Some(
|
||||
PartitionDef::new(
|
||||
@@ -656,7 +665,7 @@ pub(crate) mod test {
|
||||
},
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 1,
|
||||
id: 1.into(),
|
||||
name: "r3".to_string(),
|
||||
partition: Some(
|
||||
PartitionDef::new(
|
||||
@@ -691,7 +700,7 @@ pub(crate) mod test {
|
||||
vec![
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 1,
|
||||
id: 1.into(),
|
||||
name: "r1".to_string(),
|
||||
partition: Some(
|
||||
PartitionDef::new(
|
||||
@@ -711,7 +720,7 @@ pub(crate) mod test {
|
||||
},
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 2,
|
||||
id: 2.into(),
|
||||
name: "r2".to_string(),
|
||||
partition: Some(
|
||||
PartitionDef::new(
|
||||
@@ -731,7 +740,7 @@ pub(crate) mod test {
|
||||
},
|
||||
RegionRoute {
|
||||
region: Region {
|
||||
id: 3,
|
||||
id: 3.into(),
|
||||
name: "r3".to_string(),
|
||||
partition: Some(
|
||||
PartitionDef::new(
|
||||
|
||||
@@ -12,7 +12,7 @@ common-error = { path = "../common/error" }
|
||||
common-grpc = { path = "../common/grpc" }
|
||||
common-telemetry = { path = "../common/telemetry" }
|
||||
common-meta = { path = "../common/meta" }
|
||||
etcd-client = "0.11"
|
||||
etcd-client.workspace = true
|
||||
rand.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
|
||||
@@ -21,6 +21,7 @@ mod store;
|
||||
|
||||
use api::v1::meta::Role;
|
||||
use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
|
||||
use common_meta::rpc::ddl::{SubmitDdlTaskRequest, SubmitDdlTaskResponse};
|
||||
use common_meta::rpc::lock::{LockRequest, LockResponse, UnlockRequest};
|
||||
use common_meta::rpc::router::{CreateRequest, DeleteRequest, RouteRequest, RouteResponse};
|
||||
use common_meta::rpc::store::{
|
||||
@@ -185,11 +186,14 @@ impl MetaClient {
|
||||
client.start(urls.clone()).await?;
|
||||
info!("Store client started");
|
||||
}
|
||||
|
||||
if let Some(client) = &mut self.lock {
|
||||
client.start(urls).await?;
|
||||
client.start(urls.clone()).await?;
|
||||
info!("Lock client started");
|
||||
}
|
||||
if let Some(client) = &mut self.ddl {
|
||||
client.start(urls).await?;
|
||||
info!("Ddl client started");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -348,6 +352,20 @@ impl MetaClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn submit_ddl_task(
|
||||
&self,
|
||||
req: SubmitDdlTaskRequest,
|
||||
) -> Result<SubmitDdlTaskResponse> {
|
||||
let res = self
|
||||
.ddl_client()?
|
||||
.submit_ddl_task(req.try_into().context(error::ConvertMetaRequestSnafu)?)
|
||||
.await?
|
||||
.try_into()
|
||||
.context(error::ConvertMetaResponseSnafu)?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn heartbeat_client(&self) -> Result<HeartbeatClient> {
|
||||
self.heartbeat.clone().context(error::NotStartedSnafu {
|
||||
@@ -376,6 +394,13 @@ impl MetaClient {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn ddl_client(&self) -> Result<DdlClient> {
|
||||
self.ddl
|
||||
.clone()
|
||||
.context(error::NotStartedSnafu { name: "ddl_client" })
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn channel_config(&self) -> &ChannelConfig {
|
||||
self.channel_manager.config()
|
||||
@@ -739,7 +764,7 @@ mod tests {
|
||||
.with_key(tc.key("key"))
|
||||
.with_value(b"value".to_vec());
|
||||
let res = tc.client.put(req).await;
|
||||
assert!(res.unwrap().take_prev_kv().is_none());
|
||||
assert!(res.unwrap().prev_kv.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -752,14 +777,14 @@ mod tests {
|
||||
.with_value(b"value".to_vec())
|
||||
.with_prev_kv();
|
||||
let res = tc.client.put(req).await;
|
||||
assert!(res.unwrap().take_prev_kv().is_none());
|
||||
assert!(res.unwrap().prev_kv.is_none());
|
||||
|
||||
let req = PutRequest::new()
|
||||
.with_key(key.as_slice())
|
||||
.with_value(b"value1".to_vec())
|
||||
.with_prev_kv();
|
||||
let res = tc.client.put(req).await;
|
||||
let mut kv = res.unwrap().take_prev_kv().unwrap();
|
||||
let mut kv = res.unwrap().prev_kv.unwrap();
|
||||
assert_eq!(key, kv.take_key());
|
||||
assert_eq!(b"value".to_vec(), kv.take_value());
|
||||
}
|
||||
@@ -794,16 +819,14 @@ mod tests {
|
||||
for i in 0..256 {
|
||||
req = req.add_key(tc.key(&format!("key-{}", i)));
|
||||
}
|
||||
let mut res = tc.client.batch_get(req).await.unwrap();
|
||||
|
||||
assert_eq!(10, res.take_kvs().len());
|
||||
let res = tc.client.batch_get(req).await.unwrap();
|
||||
assert_eq!(10, res.kvs.len());
|
||||
|
||||
let req = BatchGetRequest::default()
|
||||
.add_key(tc.key("key-1"))
|
||||
.add_key(tc.key("key-999"));
|
||||
let mut res = tc.client.batch_get(req).await.unwrap();
|
||||
|
||||
assert_eq!(1, res.take_kvs().len());
|
||||
let res = tc.client.batch_get(req).await.unwrap();
|
||||
assert_eq!(1, res.kvs.len());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -867,7 +890,9 @@ mod tests {
|
||||
let res = tc.client.compare_and_put(req).await;
|
||||
let mut res = res.unwrap();
|
||||
assert!(res.is_success());
|
||||
assert_eq!(b"value".to_vec(), res.take_prev_kv().unwrap().take_value());
|
||||
|
||||
// If compare-and-put is success, previous value doesn't need to be returned.
|
||||
assert!(res.take_prev_kv().is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -27,14 +27,10 @@ use crate::error;
|
||||
use crate::error::Result;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
// TODO(weny): removes this in following PRs.
|
||||
#[allow(unused)]
|
||||
pub struct Client {
|
||||
inner: Arc<RwLock<Inner>>,
|
||||
}
|
||||
|
||||
// TODO(weny): removes this in following PRs.
|
||||
#[allow(dead_code)]
|
||||
impl Client {
|
||||
pub fn new(id: Id, role: Role, channel_manager: ChannelManager) -> Self {
|
||||
let inner = Arc::new(RwLock::new(Inner {
|
||||
|
||||
@@ -25,7 +25,7 @@ common-telemetry = { path = "../common/telemetry" }
|
||||
common-time = { path = "../common/time" }
|
||||
dashmap = "5.4"
|
||||
derive_builder = "0.12"
|
||||
etcd-client = "0.11"
|
||||
etcd-client.workspace = true
|
||||
futures.workspace = true
|
||||
h2 = "0.3"
|
||||
http-body = "0.4"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{DeleteRangeRequest, PutRequest, RangeRequest};
|
||||
use common_meta::rpc::store::{DeleteRangeRequest, PutRequest, RangeRequest};
|
||||
use meta_srv::service::store::etcd::EtcdStore;
|
||||
use tracing::{event, subscriber, Level};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
@@ -31,7 +31,6 @@ async fn run() {
|
||||
key: b"key1".to_vec(),
|
||||
value: b"value1".to_vec(),
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
let res = kv_store.put(put_req).await;
|
||||
event!(Level::INFO, "put result: {:#?}", res);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::cluster_server::ClusterServer;
|
||||
use api::v1::meta::ddl_task_server::DdlTaskServer;
|
||||
use api::v1::meta::heartbeat_server::HeartbeatServer;
|
||||
use api::v1::meta::lock_server::LockServer;
|
||||
use api::v1::meta::router_server::RouterServer;
|
||||
@@ -147,6 +148,7 @@ pub fn router(meta_srv: MetaSrv) -> Router {
|
||||
.add_service(StoreServer::new(meta_srv.clone()))
|
||||
.add_service(ClusterServer::new(meta_srv.clone()))
|
||||
.add_service(LockServer::new(meta_srv.clone()))
|
||||
.add_service(DdlTaskServer::new(meta_srv.clone()))
|
||||
.add_service(admin::make_admin_service(meta_srv))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,18 +18,22 @@ use std::time::Duration;
|
||||
|
||||
use api::v1::meta::cluster_client::ClusterClient;
|
||||
use api::v1::meta::{
|
||||
BatchGetRequest, BatchGetResponse, KeyValue, RangeRequest, RangeResponse, ResponseHeader,
|
||||
BatchGetRequest as PbBatchGetRequest, BatchGetResponse as PbBatchGetResponse,
|
||||
RangeRequest as PbRangeRequest, RangeResponse as PbRangeResponse, ResponseHeader,
|
||||
};
|
||||
use common_grpc::channel_manager::ChannelManager;
|
||||
use common_meta::rpc::store::{BatchGetRequest, RangeRequest};
|
||||
use common_meta::rpc::KeyValue;
|
||||
use common_meta::util;
|
||||
use common_telemetry::warn;
|
||||
use derive_builder::Builder;
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
|
||||
use crate::error;
|
||||
use crate::error::{match_for_io_error, Result};
|
||||
use crate::keys::{StatKey, StatValue, DN_STAT_PREFIX};
|
||||
use crate::metasrv::ElectionRef;
|
||||
use crate::service::store::kv::ResettableKvStoreRef;
|
||||
use crate::{error, util};
|
||||
|
||||
pub type MetaPeerClientRef = Arc<MetaPeerClient>;
|
||||
|
||||
@@ -112,13 +116,13 @@ impl MetaPeerClient {
|
||||
.get(&leader_addr)
|
||||
.context(error::CreateChannelSnafu)?;
|
||||
|
||||
let request = tonic::Request::new(RangeRequest {
|
||||
let request = tonic::Request::new(PbRangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let response: RangeResponse = ClusterClient::new(channel)
|
||||
let response: PbRangeResponse = ClusterClient::new(channel)
|
||||
.range(request)
|
||||
.await
|
||||
.context(error::RangeSnafu)?
|
||||
@@ -126,16 +130,13 @@ impl MetaPeerClient {
|
||||
|
||||
check_resp_header(&response.header, Context { addr: &leader_addr })?;
|
||||
|
||||
Ok(response.kvs)
|
||||
Ok(response.kvs.into_iter().map(KeyValue::new).collect())
|
||||
}
|
||||
|
||||
// Get kv information from the leader's in_mem kv store
|
||||
pub async fn batch_get(&self, keys: Vec<Vec<u8>>) -> Result<Vec<KeyValue>> {
|
||||
if self.is_leader() {
|
||||
let request = BatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
};
|
||||
let request = BatchGetRequest { keys };
|
||||
|
||||
return self.in_memory.batch_get(request).await.map(|resp| resp.kvs);
|
||||
}
|
||||
@@ -175,12 +176,12 @@ impl MetaPeerClient {
|
||||
.get(&leader_addr)
|
||||
.context(error::CreateChannelSnafu)?;
|
||||
|
||||
let request = tonic::Request::new(BatchGetRequest {
|
||||
let request = tonic::Request::new(PbBatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let response: BatchGetResponse = ClusterClient::new(channel)
|
||||
let response: PbBatchGetResponse = ClusterClient::new(channel)
|
||||
.batch_get(request)
|
||||
.await
|
||||
.context(error::BatchGetSnafu)?
|
||||
@@ -188,7 +189,7 @@ impl MetaPeerClient {
|
||||
|
||||
check_resp_header(&response.header, Context { addr: &leader_addr })?;
|
||||
|
||||
Ok(response.kvs)
|
||||
Ok(response.kvs.into_iter().map(KeyValue::new).collect())
|
||||
}
|
||||
|
||||
// Check if the meta node is a leader node.
|
||||
@@ -240,7 +241,8 @@ fn need_retry(error: &error::Error) -> bool {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use api::v1::meta::{Error, ErrorCode, KeyValue, ResponseHeader};
|
||||
use api::v1::meta::{Error, ErrorCode, ResponseHeader};
|
||||
use common_meta::rpc::KeyValue;
|
||||
|
||||
use super::{check_resp_header, to_stat_kv_map, Context};
|
||||
use crate::error;
|
||||
|
||||
@@ -15,13 +15,15 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use client::client_manager::DatanodeClients;
|
||||
use common_meta::rpc::ddl::CreateTableTask;
|
||||
use common_meta::rpc::ddl::{CreateTableTask, DropTableTask};
|
||||
use common_meta::rpc::router::TableRoute;
|
||||
use common_procedure::{watcher, ProcedureId, ProcedureManagerRef, ProcedureWithId};
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::procedure::create_table::CreateTableProcedure;
|
||||
use crate::procedure::drop_table::DropTableProcedure;
|
||||
use crate::service::mailbox::MailboxRef;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
|
||||
pub type DdlManagerRef = Arc<DdlManager>;
|
||||
@@ -30,14 +32,16 @@ pub struct DdlManager {
|
||||
procedure_manager: ProcedureManagerRef,
|
||||
kv_store: KvStoreRef,
|
||||
datanode_clients: Arc<DatanodeClients>,
|
||||
mailbox: MailboxRef,
|
||||
server_addr: String,
|
||||
}
|
||||
|
||||
// TODO(weny): removes in following PRs.
|
||||
#[allow(unused)]
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct DdlContext {
|
||||
pub(crate) kv_store: KvStoreRef,
|
||||
pub(crate) datanode_clients: Arc<DatanodeClients>,
|
||||
pub(crate) mailbox: MailboxRef,
|
||||
pub(crate) server_addr: String,
|
||||
}
|
||||
|
||||
impl DdlManager {
|
||||
@@ -45,11 +49,15 @@ impl DdlManager {
|
||||
procedure_manager: ProcedureManagerRef,
|
||||
kv_store: KvStoreRef,
|
||||
datanode_clients: Arc<DatanodeClients>,
|
||||
mailbox: MailboxRef,
|
||||
server_addr: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
procedure_manager,
|
||||
kv_store,
|
||||
datanode_clients,
|
||||
mailbox,
|
||||
server_addr,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +65,8 @@ impl DdlManager {
|
||||
DdlContext {
|
||||
kv_store: self.kv_store.clone(),
|
||||
datanode_clients: self.datanode_clients.clone(),
|
||||
mailbox: self.mailbox.clone(),
|
||||
server_addr: self.server_addr.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +102,21 @@ impl DdlManager {
|
||||
self.submit_procedure(procedure_with_id).await
|
||||
}
|
||||
|
||||
pub async fn submit_drop_table_task(
|
||||
&self,
|
||||
cluster_id: u64,
|
||||
drop_table_task: DropTableTask,
|
||||
table_route: TableRoute,
|
||||
) -> Result<ProcedureId> {
|
||||
let context = self.create_context();
|
||||
|
||||
let procedure = DropTableProcedure::new(cluster_id, drop_table_task, table_route, context);
|
||||
|
||||
let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure));
|
||||
|
||||
self.submit_procedure(procedure_with_id).await
|
||||
}
|
||||
|
||||
async fn submit_procedure(&self, procedure_with_id: ProcedureWithId) -> Result<ProcedureId> {
|
||||
let procedure_id = procedure_with_id.id;
|
||||
|
||||
|
||||
@@ -14,15 +14,21 @@
|
||||
|
||||
use common_error::prelude::*;
|
||||
use common_meta::peer::Peer;
|
||||
use common_runtime::JoinError;
|
||||
use snafu::Location;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
use tokio::sync::oneshot::error::TryRecvError;
|
||||
use tonic::codegen::http;
|
||||
use tonic::Code;
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[snafu(display("Failed to join a future: {}", source))]
|
||||
Join {
|
||||
location: Location,
|
||||
source: JoinError,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to execute transaction: {}", msg))]
|
||||
Txn { location: Location, msg: String },
|
||||
|
||||
@@ -37,12 +43,6 @@ pub enum Error {
|
||||
found: u64,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to receive status, source: {}", source,))]
|
||||
TryReceiveStatus {
|
||||
location: Location,
|
||||
source: TryRecvError,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to request Datanode, expected: {}, but only {} available",
|
||||
expected,
|
||||
@@ -409,8 +409,11 @@ pub enum Error {
|
||||
source: common_meta::error::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Etcd txn got an error: {err_msg}"))]
|
||||
EtcdTxnOpResponse { err_msg: String, location: Location },
|
||||
#[snafu(display("Failed to convert Etcd txn object: {source}"))]
|
||||
ConvertEtcdTxnObject {
|
||||
source: common_meta::error::Error,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
// this error is used for custom error mapping
|
||||
// please do not delete it
|
||||
@@ -467,7 +470,7 @@ impl ErrorExt for Error {
|
||||
| Error::StartGrpc { .. }
|
||||
| Error::Combine { .. }
|
||||
| Error::NoEnoughAvailableDatanode { .. }
|
||||
| Error::TryReceiveStatus { .. } => StatusCode::Internal,
|
||||
| Error::Join { .. } => StatusCode::Internal,
|
||||
Error::EmptyKey { .. }
|
||||
| Error::MissingRequiredParameter { .. }
|
||||
| Error::MissingRequestHeader { .. }
|
||||
@@ -490,7 +493,6 @@ impl ErrorExt for Error {
|
||||
| Error::InvalidTxnResult { .. }
|
||||
| Error::InvalidUtf8Value { .. }
|
||||
| Error::UnexpectedInstructionReply { .. }
|
||||
| Error::EtcdTxnOpResponse { .. }
|
||||
| Error::Unexpected { .. }
|
||||
| Error::Txn { .. }
|
||||
| Error::TableIdChanged { .. } => StatusCode::Unexpected,
|
||||
@@ -507,9 +509,11 @@ impl ErrorExt for Error {
|
||||
Error::RegionFailoverCandidatesNotFound { .. } => StatusCode::RuntimeResourcesExhausted,
|
||||
|
||||
Error::RegisterProcedureLoader { source, .. } => source.status_code(),
|
||||
Error::TableRouteConversion { source, .. } | Error::ConvertProtoData { source, .. } => {
|
||||
source.status_code()
|
||||
}
|
||||
|
||||
Error::TableRouteConversion { source, .. }
|
||||
| Error::ConvertProtoData { source, .. }
|
||||
| Error::ConvertEtcdTxnObject { source, .. } => source.status_code(),
|
||||
|
||||
Error::Other { source, .. } => source.status_code(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use async_trait::async_trait;
|
||||
use common_catalog::consts::MITO_ENGINE;
|
||||
use common_meta::ident::TableIdent;
|
||||
use common_meta::RegionIdent;
|
||||
use table::engine::table_id;
|
||||
use store_api::storage::RegionId;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::handler::failure_handler::runner::{FailureDetectControl, FailureDetectRunner};
|
||||
@@ -92,7 +92,7 @@ impl HeartbeatHandler for RegionFailureHandler {
|
||||
catalog: x.catalog.clone(),
|
||||
schema: x.schema.clone(),
|
||||
table: x.table.clone(),
|
||||
table_id: table_id(x.id),
|
||||
table_id: RegionId::from(x.id).table_id(),
|
||||
// TODO(#1583): Use the actual table engine.
|
||||
engine: MITO_ENGINE.to_string(),
|
||||
},
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{HeartbeatRequest, PutRequest, Role};
|
||||
use api::v1::meta::{HeartbeatRequest, Role};
|
||||
use common_meta::rpc::store::PutRequest;
|
||||
use common_telemetry::{trace, warn};
|
||||
use common_time::util as time_util;
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use api::v1::meta::{HeartbeatRequest, PutRequest, Role};
|
||||
use api::v1::meta::{HeartbeatRequest, Role};
|
||||
use common_meta::rpc::store::PutRequest;
|
||||
use common_telemetry::warn;
|
||||
use dashmap::DashMap;
|
||||
|
||||
@@ -147,7 +148,6 @@ mod tests {
|
||||
use crate::keys::StatKey;
|
||||
use crate::sequence::Sequence;
|
||||
use crate::service::store::cached_kv::LeaderCachedKvStore;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::memory::MemStore;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -184,7 +184,8 @@ mod tests {
|
||||
cluster_id: 3,
|
||||
node_id: 101,
|
||||
};
|
||||
let res = ctx.in_memory.get(key.try_into().unwrap()).await.unwrap();
|
||||
let key: Vec<u8> = key.try_into().unwrap();
|
||||
let res = ctx.in_memory.get(&key).await.unwrap();
|
||||
let kv = res.unwrap();
|
||||
let key: StatKey = kv.key.clone().try_into().unwrap();
|
||||
assert_eq!(3, key.cluster_id);
|
||||
@@ -195,7 +196,9 @@ mod tests {
|
||||
assert_eq!(Some(1), val.stats[0].region_num);
|
||||
|
||||
handle_request_many_times(ctx.clone(), &handler, 10).await;
|
||||
let res = ctx.in_memory.get(key.try_into().unwrap()).await.unwrap();
|
||||
|
||||
let key: Vec<u8> = key.try_into().unwrap();
|
||||
let res = ctx.in_memory.get(&key).await.unwrap();
|
||||
let kv = res.unwrap();
|
||||
let val: StatValue = kv.value.try_into().unwrap();
|
||||
// refresh every 10 stats
|
||||
|
||||
@@ -20,7 +20,7 @@ use async_trait::async_trait;
|
||||
use catalog::helper::TableGlobalKey;
|
||||
use common_meta::ident::TableIdent;
|
||||
use common_meta::ClusterId;
|
||||
use store_api::storage::RegionNumber;
|
||||
use store_api::storage::{RegionId, RegionNumber};
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::handler::{HeartbeatAccumulator, HeartbeatHandler};
|
||||
@@ -102,7 +102,7 @@ impl HeartbeatHandler for RegionLeaseHandler {
|
||||
datanode_regions
|
||||
.entry(key)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(table::engine::region_number(x.id));
|
||||
.push(RegionId::from(x.id).region_number());
|
||||
});
|
||||
|
||||
// TODO(LFC): Retrieve table global values from some cache here.
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use common_meta::util;
|
||||
use common_time::util as time_util;
|
||||
|
||||
use crate::cluster::MetaPeerClientRef;
|
||||
use crate::error::Result;
|
||||
use crate::keys::{LeaseKey, LeaseValue, DN_LEASE_PREFIX};
|
||||
use crate::util;
|
||||
|
||||
pub async fn alive_datanodes(
|
||||
cluster_id: u64,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#![feature(async_closure)]
|
||||
#![feature(btree_drain_filter)]
|
||||
#![feature(result_flattening)]
|
||||
|
||||
pub mod bootstrap;
|
||||
pub mod cluster;
|
||||
@@ -39,6 +40,4 @@ pub mod table_routes;
|
||||
#[cfg(test)]
|
||||
mod test_util;
|
||||
|
||||
pub mod util;
|
||||
|
||||
pub use crate::error::Result;
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::CompareAndPutRequest;
|
||||
use async_trait::async_trait;
|
||||
use catalog::helper::{CatalogKey, CatalogValue, SchemaKey, SchemaValue};
|
||||
use common_meta::rpc::store::CompareAndPutRequest;
|
||||
use common_telemetry::{info, timer};
|
||||
use metrics::increment_counter;
|
||||
use snafu::{ensure, ResultExt};
|
||||
@@ -80,7 +80,6 @@ impl MetadataService for DefaultMetadataService {
|
||||
value: CatalogValue {}
|
||||
.as_bytes()
|
||||
.context(error::InvalidCatalogValueSnafu)?,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let resp = kv_store.compare_and_put(req).await?;
|
||||
@@ -96,7 +95,6 @@ impl MetadataService for DefaultMetadataService {
|
||||
value: SchemaValue {}
|
||||
.as_bytes()
|
||||
.context(error::InvalidCatalogValueSnafu)?,
|
||||
..Default::default()
|
||||
};
|
||||
let resp = kv_store.compare_and_put(req).await?;
|
||||
|
||||
@@ -124,7 +122,6 @@ mod tests {
|
||||
use catalog::helper::{CatalogKey, SchemaKey};
|
||||
|
||||
use super::{DefaultMetadataService, MetadataService};
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::service::store::memory::MemStore;
|
||||
|
||||
@@ -156,11 +153,9 @@ mod tests {
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
let result = kv_store.get(key.clone()).await.unwrap();
|
||||
|
||||
let result = kv_store.get(&key).await.unwrap();
|
||||
let kv = result.unwrap();
|
||||
|
||||
assert_eq!(key, kv.key);
|
||||
assert_eq!(key, kv.key());
|
||||
|
||||
let key: Vec<u8> = SchemaKey {
|
||||
catalog_name: "catalog".to_string(),
|
||||
@@ -169,10 +164,8 @@ mod tests {
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
let result = kv_store.get(key.clone()).await.unwrap();
|
||||
|
||||
let result = kv_store.get(&key).await.unwrap();
|
||||
let kv = result.unwrap();
|
||||
|
||||
assert_eq!(key, kv.key);
|
||||
assert_eq!(key, kv.key());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ pub struct MetaSrvBuilder {
|
||||
meta_peer_client: Option<MetaPeerClientRef>,
|
||||
lock: Option<DistLockRef>,
|
||||
metadata_service: Option<MetadataServiceRef>,
|
||||
datanode_clients: Option<Arc<DatanodeClients>>,
|
||||
}
|
||||
|
||||
impl MetaSrvBuilder {
|
||||
@@ -67,6 +68,7 @@ impl MetaSrvBuilder {
|
||||
options: None,
|
||||
lock: None,
|
||||
metadata_service: None,
|
||||
datanode_clients: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +117,11 @@ impl MetaSrvBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn datanode_clients(mut self, clients: Arc<DatanodeClients>) -> Self {
|
||||
self.datanode_clients = Some(clients);
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn build(self) -> Result<MetaSrv> {
|
||||
let started = Arc::new(AtomicBool::new(false));
|
||||
|
||||
@@ -128,6 +135,7 @@ impl MetaSrvBuilder {
|
||||
handler_group,
|
||||
lock,
|
||||
metadata_service,
|
||||
datanode_clients,
|
||||
} = self;
|
||||
|
||||
let options = options.unwrap_or_default();
|
||||
@@ -158,6 +166,17 @@ impl MetaSrvBuilder {
|
||||
.unwrap_or_else(|| Arc::new(DefaultMetadataService::new(kv_store.clone())));
|
||||
let lock = lock.unwrap_or_else(|| Arc::new(MemLock::default()));
|
||||
|
||||
// TODO(weny): considers to modify the default config of procedure manager
|
||||
let ddl_manager = Arc::new(DdlManager::new(
|
||||
procedure_manager.clone(),
|
||||
kv_store.clone(),
|
||||
datanode_clients.unwrap_or_else(|| Arc::new(DatanodeClients::default())),
|
||||
mailbox.clone(),
|
||||
options.server_addr.clone(),
|
||||
));
|
||||
|
||||
let _ = ddl_manager.try_start();
|
||||
|
||||
let handler_group = match handler_group {
|
||||
Some(handler_group) => handler_group,
|
||||
None => {
|
||||
@@ -212,15 +231,6 @@ impl MetaSrvBuilder {
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(weny): considers to modify the default config of procedure manager
|
||||
let ddl_manager = Arc::new(DdlManager::new(
|
||||
procedure_manager.clone(),
|
||||
kv_store.clone(),
|
||||
Arc::new(DatanodeClients::default()),
|
||||
));
|
||||
|
||||
let _ = ddl_manager.try_start();
|
||||
|
||||
Ok(MetaSrv {
|
||||
started,
|
||||
options,
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
pub(crate) const METRIC_META_CREATE_CATALOG: &str = "meta.create_catalog";
|
||||
pub(crate) const METRIC_META_CREATE_SCHEMA: &str = "meta.create_schema";
|
||||
pub(crate) const METRIC_META_KV_REQUEST: &str = "meta.kv_request";
|
||||
pub(crate) const METRIC_META_TXN_REQUEST: &str = "meta.txn_request";
|
||||
pub(crate) const METRIC_META_ROUTE_REQUEST: &str = "meta.route_request";
|
||||
pub(crate) const METRIC_META_HEARTBEAT_CONNECTION_NUM: &str = "meta.heartbeat_connection_num";
|
||||
pub(crate) const METRIC_META_HANDLER_EXECUTE: &str = "meta.handler_execute";
|
||||
|
||||
pub(crate) const METRIC_META_CREATE_TABLE_PROCEDURE_CREATE_META: &str =
|
||||
"meta.procedure.create_table.create_meta";
|
||||
pub(crate) const METRIC_META_CREATE_TABLE_PROCEDURE_CREATE_TABLE: &str =
|
||||
"meta.procedure.create_table.create_table";
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use api::v1::meta::ddl_task_server::DdlTaskServer;
|
||||
use api::v1::meta::heartbeat_server::HeartbeatServer;
|
||||
use api::v1::meta::router_server::RouterServer;
|
||||
use api::v1::meta::store_server::StoreServer;
|
||||
use client::client_manager::DatanodeClients;
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
|
||||
use tower::service_fn;
|
||||
@@ -38,23 +40,24 @@ pub struct MockInfo {
|
||||
|
||||
pub async fn mock_with_memstore() -> MockInfo {
|
||||
let kv_store = Arc::new(MemStore::default());
|
||||
mock(Default::default(), kv_store, None).await
|
||||
mock(Default::default(), kv_store, None, None).await
|
||||
}
|
||||
|
||||
pub async fn mock_with_etcdstore(addr: &str) -> MockInfo {
|
||||
let kv_store = EtcdStore::with_endpoints([addr]).await.unwrap();
|
||||
mock(Default::default(), kv_store, None).await
|
||||
mock(Default::default(), kv_store, None, None).await
|
||||
}
|
||||
|
||||
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
|
||||
mock(Default::default(), kv_store, Some(selector), None).await
|
||||
}
|
||||
|
||||
pub async fn mock(
|
||||
opts: MetaSrvOptions,
|
||||
kv_store: KvStoreRef,
|
||||
selector: Option<SelectorRef>,
|
||||
datanode_clients: Option<Arc<DatanodeClients>>,
|
||||
) -> MockInfo {
|
||||
let server_addr = opts.server_addr.clone();
|
||||
|
||||
@@ -72,6 +75,11 @@ pub async fn mock(
|
||||
None => builder,
|
||||
};
|
||||
|
||||
let builder = match datanode_clients {
|
||||
Some(clients) => builder.datanode_clients(clients),
|
||||
None => builder,
|
||||
};
|
||||
|
||||
let meta_srv = builder.build().await.unwrap();
|
||||
meta_srv.try_start().await.unwrap();
|
||||
|
||||
@@ -82,6 +90,7 @@ pub async fn mock(
|
||||
.add_service(HeartbeatServer::new(service.clone()))
|
||||
.add_service(RouterServer::new(service.clone()))
|
||||
.add_service(StoreServer::new(service.clone()))
|
||||
.add_service(DdlTaskServer::new(service.clone()))
|
||||
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(server)]))
|
||||
.await
|
||||
});
|
||||
|
||||
@@ -13,5 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
pub mod create_table;
|
||||
pub mod drop_table;
|
||||
pub mod region_failover;
|
||||
pub(crate) mod state_store;
|
||||
mod utils;
|
||||
|
||||
@@ -19,33 +19,29 @@ use client::Database;
|
||||
use common_error::ext::ErrorExt;
|
||||
use common_error::status_code::StatusCode;
|
||||
use common_meta::key::TableRouteKey;
|
||||
use common_meta::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
|
||||
use common_meta::rpc::ddl::CreateTableTask;
|
||||
use common_meta::rpc::router::TableRoute;
|
||||
use common_meta::table_name::TableName;
|
||||
use common_procedure::error::{FromJsonSnafu, Result as ProcedureResult, ToJsonSnafu};
|
||||
use common_procedure::{
|
||||
Context as ProcedureContext, Error as ProcedureError, LockKey, Procedure, Status,
|
||||
};
|
||||
use common_procedure::{Context as ProcedureContext, LockKey, Procedure, Status};
|
||||
use futures::future::join_all;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, ResultExt};
|
||||
use table::engine::TableReference;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use super::utils::{handle_request_datanode_error, handle_retry_error};
|
||||
use crate::ddl::DdlContext;
|
||||
use crate::error::{self, Result};
|
||||
use crate::service::router::create_table_global_value;
|
||||
use crate::service::store::txn::{Compare, CompareOp, Txn, TxnOp};
|
||||
use crate::table_routes::get_table_global_value;
|
||||
|
||||
// TODO(weny): removes in following PRs.
|
||||
#[allow(unused)]
|
||||
pub struct CreateTableProcedure {
|
||||
context: DdlContext,
|
||||
creator: TableCreator,
|
||||
}
|
||||
|
||||
// TODO(weny): removes in following PRs.
|
||||
#[allow(dead_code)]
|
||||
impl CreateTableProcedure {
|
||||
pub(crate) const TYPE_NAME: &'static str = "metasrv-procedure::CreateTable";
|
||||
|
||||
@@ -105,12 +101,15 @@ impl CreateTableProcedure {
|
||||
|
||||
/// registers the `TableRouteValue`,`TableGlobalValue`
|
||||
async fn register_metadata(&self) -> Result<()> {
|
||||
let _timer = common_telemetry::timer!(
|
||||
crate::metrics::METRIC_META_CREATE_TABLE_PROCEDURE_CREATE_META
|
||||
);
|
||||
let table_name = self.table_name();
|
||||
|
||||
let table_id = self.creator.data.table_route.table.id;
|
||||
let table_id = self.creator.data.table_route.table.id as TableId;
|
||||
|
||||
let table_route_key = TableRouteKey::with_table_name(table_id, &table_name.clone().into())
|
||||
.key()
|
||||
.to_string()
|
||||
.into_bytes();
|
||||
|
||||
let table_global_key = TableGlobalKey {
|
||||
@@ -195,8 +194,10 @@ impl CreateTableProcedure {
|
||||
}
|
||||
|
||||
async fn on_datanode_create_table(&mut self) -> Result<Status> {
|
||||
let _timer = common_telemetry::timer!(
|
||||
crate::metrics::METRIC_META_CREATE_TABLE_PROCEDURE_CREATE_TABLE
|
||||
);
|
||||
let table_route = &self.creator.data.table_route;
|
||||
|
||||
let table_name = self.table_name();
|
||||
let clients = self.context.datanode_clients.clone();
|
||||
let leaders = table_route.find_leaders();
|
||||
@@ -209,16 +210,14 @@ impl CreateTableProcedure {
|
||||
let regions = table_route.find_leader_regions(&datanode);
|
||||
let mut create_expr_for_region = self.creator.data.task.create_table.clone();
|
||||
create_expr_for_region.region_numbers = regions;
|
||||
create_expr_for_region.table_id = Some(api::v1::TableId {
|
||||
id: table_route.table.id as u32,
|
||||
});
|
||||
|
||||
joins.push(common_runtime::spawn_bg(async move {
|
||||
if let Err(err) = client
|
||||
.create(create_expr_for_region)
|
||||
.await
|
||||
.context(error::RequestDatanodeSnafu { peer: datanode })
|
||||
{
|
||||
// TODO(weny): add tests for `TableAlreadyExists`
|
||||
if let Err(err) = client.create(create_expr_for_region).await {
|
||||
if err.status_code() != StatusCode::TableAlreadyExists {
|
||||
return Err(err);
|
||||
return Err(handle_request_datanode_error(datanode)(err));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -228,17 +227,7 @@ impl CreateTableProcedure {
|
||||
let _ = join_all(joins)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|result| {
|
||||
result.map_err(|err| {
|
||||
error::RetryLaterSnafu {
|
||||
reason: format!(
|
||||
"Failed to execute create table on datanode, source: {}",
|
||||
err
|
||||
),
|
||||
}
|
||||
.build()
|
||||
})
|
||||
})
|
||||
.map(|e| e.context(error::JoinSnafu))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
self.creator.data.state = CreateTableState::CreateMetadata;
|
||||
@@ -254,22 +243,12 @@ impl Procedure for CreateTableProcedure {
|
||||
}
|
||||
|
||||
async fn execute(&mut self, _ctx: &ProcedureContext) -> ProcedureResult<Status> {
|
||||
let error_handler = |e| {
|
||||
if matches!(e, error::Error::RetryLater { .. }) {
|
||||
ProcedureError::retry_later(e)
|
||||
} else {
|
||||
ProcedureError::external(e)
|
||||
}
|
||||
};
|
||||
match self.creator.data.state {
|
||||
CreateTableState::Prepare => self.on_prepare().await.map_err(error_handler),
|
||||
CreateTableState::DatanodeCreateTable => {
|
||||
self.on_datanode_create_table().await.map_err(error_handler)
|
||||
}
|
||||
CreateTableState::CreateMetadata => {
|
||||
self.on_create_metadata().await.map_err(error_handler)
|
||||
}
|
||||
CreateTableState::Prepare => self.on_prepare().await,
|
||||
CreateTableState::DatanodeCreateTable => self.on_datanode_create_table().await,
|
||||
CreateTableState::CreateMetadata => self.on_create_metadata().await,
|
||||
}
|
||||
.map_err(handle_retry_error)
|
||||
}
|
||||
|
||||
fn dump(&self) -> ProcedureResult<String> {
|
||||
@@ -305,7 +284,7 @@ impl TableCreator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
enum CreateTableState {
|
||||
/// Prepares to create the table
|
||||
Prepare,
|
||||
|
||||
264
src/meta-srv/src/procedure/drop_table.rs
Normal file
264
src/meta-srv/src/procedure/drop_table.rs
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::MailboxMessage;
|
||||
use api::v1::{DropTableExpr, TableId};
|
||||
use async_trait::async_trait;
|
||||
use client::Database;
|
||||
use common_catalog::consts::MITO_ENGINE;
|
||||
use common_error::ext::ErrorExt;
|
||||
use common_error::status_code::StatusCode;
|
||||
use common_meta::ident::TableIdent;
|
||||
use common_meta::instruction::Instruction;
|
||||
use common_meta::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
|
||||
use common_meta::rpc::ddl::DropTableTask;
|
||||
use common_meta::rpc::router::TableRoute;
|
||||
use common_meta::table_name::TableName;
|
||||
use common_procedure::error::{FromJsonSnafu, ToJsonSnafu};
|
||||
use common_procedure::{
|
||||
Context as ProcedureContext, LockKey, Procedure, Result as ProcedureResult, Status,
|
||||
};
|
||||
use common_telemetry::debug;
|
||||
use futures::future::join_all;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, ResultExt};
|
||||
use table::engine::TableReference;
|
||||
|
||||
use super::utils::{build_table_metadata_key, handle_retry_error};
|
||||
use crate::ddl::DdlContext;
|
||||
use crate::error;
|
||||
use crate::error::Result;
|
||||
use crate::procedure::utils::{build_table_route_value, handle_request_datanode_error};
|
||||
use crate::service::mailbox::BroadcastChannel;
|
||||
use crate::table_routes::fetch_table;
|
||||
pub struct DropTableProcedure {
|
||||
context: DdlContext,
|
||||
data: DropTableData,
|
||||
}
|
||||
|
||||
// TODO(weny): removes in following PRs.
|
||||
#[allow(unused)]
|
||||
impl DropTableProcedure {
|
||||
pub(crate) const TYPE_NAME: &'static str = "metasrv-procedure::DropTable";
|
||||
|
||||
pub(crate) fn new(
|
||||
cluster_id: u64,
|
||||
task: DropTableTask,
|
||||
table_route: TableRoute,
|
||||
context: DdlContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
data: DropTableData::new(cluster_id, task, table_route),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_json(json: &str, context: DdlContext) -> ProcedureResult<Self> {
|
||||
let data = serde_json::from_str(json).context(FromJsonSnafu)?;
|
||||
Ok(Self { context, data })
|
||||
}
|
||||
|
||||
/// Removes the table metadata.
|
||||
async fn on_remove_metadata(&mut self) -> Result<Status> {
|
||||
let table_ref = self.data.table_ref();
|
||||
|
||||
// If metadata not exists (might have already been removed).
|
||||
if fetch_table(&self.context.kv_store, table_ref)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
self.data.state = DropTableState::InvalidateTableCache;
|
||||
|
||||
return Ok(Status::executing(true));
|
||||
}
|
||||
|
||||
let table_ref = self.data.table_ref();
|
||||
let table_id = self.data.task.table_id;
|
||||
|
||||
let (table_global_key, table_route_key) = build_table_metadata_key(table_ref, table_id);
|
||||
let table_route_value = build_table_route_value(self.data.table_route.clone())?;
|
||||
|
||||
// To protect the potential resource leak issues.
|
||||
// We must compare the table route value, before deleting.
|
||||
let txn = Txn::new()
|
||||
.when(vec![Compare::with_value(
|
||||
table_route_key.to_string().into_bytes(),
|
||||
CompareOp::Equal,
|
||||
table_route_value.into(),
|
||||
)])
|
||||
.and_then(vec![
|
||||
TxnOp::Delete(table_route_key.to_string().into_bytes()),
|
||||
TxnOp::Delete(table_global_key.to_string().into_bytes()),
|
||||
]);
|
||||
let resp = self.context.kv_store.txn(txn).await?;
|
||||
|
||||
ensure!(
|
||||
resp.succeeded,
|
||||
error::TxnSnafu {
|
||||
msg: "table_route_value changed"
|
||||
}
|
||||
);
|
||||
|
||||
self.data.state = DropTableState::InvalidateTableCache;
|
||||
|
||||
Ok(Status::executing(true))
|
||||
}
|
||||
|
||||
/// Broadcasts invalidate table cache instruction.
|
||||
async fn on_broadcast(&mut self) -> Result<Status> {
|
||||
let table_name = self.data.table_name();
|
||||
|
||||
let table_ident = TableIdent {
|
||||
catalog: table_name.catalog_name,
|
||||
schema: table_name.schema_name,
|
||||
table: table_name.table_name,
|
||||
table_id: self.data.task.table_id,
|
||||
// TODO(weny): retrieves the engine from the upper.
|
||||
engine: MITO_ENGINE.to_string(),
|
||||
};
|
||||
let instruction = Instruction::InvalidateTableCache(table_ident);
|
||||
|
||||
let msg = &MailboxMessage::json_message(
|
||||
"Invalidate Table Cache by dropping table procedure",
|
||||
&format!("Metasrv@{}", self.context.server_addr),
|
||||
"Frontend broadcast",
|
||||
common_time::util::current_time_millis(),
|
||||
&instruction,
|
||||
)
|
||||
.with_context(|_| error::SerializeToJsonSnafu {
|
||||
input: instruction.to_string(),
|
||||
})?;
|
||||
|
||||
self.context
|
||||
.mailbox
|
||||
.broadcast(&BroadcastChannel::Frontend, msg)
|
||||
.await?;
|
||||
|
||||
self.data.state = DropTableState::DatanodeDropTable;
|
||||
|
||||
Ok(Status::executing(true))
|
||||
}
|
||||
|
||||
/// Executes drop table instruction on datanode.
|
||||
async fn on_datanode_drop_table(&mut self) -> Result<Status> {
|
||||
let table_route = &self.data.table_route;
|
||||
|
||||
let table_ref = self.data.table_ref();
|
||||
let table_id = self.data.task.table_id;
|
||||
|
||||
let clients = self.context.datanode_clients.clone();
|
||||
let leaders = table_route.find_leaders();
|
||||
let mut joins = Vec::with_capacity(leaders.len());
|
||||
|
||||
let expr = DropTableExpr {
|
||||
catalog_name: table_ref.catalog.to_string(),
|
||||
schema_name: table_ref.schema.to_string(),
|
||||
table_name: table_ref.table.to_string(),
|
||||
table_id: Some(TableId { id: table_id }),
|
||||
};
|
||||
|
||||
for datanode in leaders {
|
||||
debug!("Dropping table {table_ref} on Datanode {datanode:?}");
|
||||
|
||||
let client = clients.get_client(&datanode).await;
|
||||
let client = Database::new(table_ref.catalog, table_ref.schema, client);
|
||||
let expr = expr.clone();
|
||||
joins.push(common_runtime::spawn_bg(async move {
|
||||
if let Err(err) = client.drop_table(expr).await {
|
||||
// TODO(weny): add tests for `TableNotFound`
|
||||
if err.status_code() != StatusCode::TableNotFound {
|
||||
return Err(handle_request_datanode_error(datanode)(err));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
let _ = join_all(joins)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|e| e.context(error::JoinSnafu).flatten())
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(Status::Done)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Procedure for DropTableProcedure {
|
||||
fn type_name(&self) -> &str {
|
||||
Self::TYPE_NAME
|
||||
}
|
||||
|
||||
async fn execute(&mut self, _ctx: &ProcedureContext) -> ProcedureResult<Status> {
|
||||
match self.data.state {
|
||||
DropTableState::RemoveMetadata => self.on_remove_metadata().await,
|
||||
DropTableState::InvalidateTableCache => self.on_broadcast().await,
|
||||
DropTableState::DatanodeDropTable => self.on_datanode_drop_table().await,
|
||||
}
|
||||
.map_err(handle_retry_error)
|
||||
}
|
||||
|
||||
fn dump(&self) -> ProcedureResult<String> {
|
||||
serde_json::to_string(&self.data).context(ToJsonSnafu)
|
||||
}
|
||||
|
||||
fn lock_key(&self) -> LockKey {
|
||||
let table_ref = &self.data.table_ref();
|
||||
let key = common_catalog::format_full_table_name(
|
||||
table_ref.catalog,
|
||||
table_ref.schema,
|
||||
table_ref.table,
|
||||
);
|
||||
|
||||
LockKey::single(key)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct DropTableData {
|
||||
state: DropTableState,
|
||||
cluster_id: u64,
|
||||
task: DropTableTask,
|
||||
table_route: TableRoute,
|
||||
}
|
||||
|
||||
impl DropTableData {
|
||||
pub fn new(cluster_id: u64, task: DropTableTask, table_route: TableRoute) -> Self {
|
||||
Self {
|
||||
state: DropTableState::RemoveMetadata,
|
||||
cluster_id,
|
||||
task,
|
||||
table_route,
|
||||
}
|
||||
}
|
||||
|
||||
fn table_ref(&self) -> TableReference {
|
||||
self.task.table_ref()
|
||||
}
|
||||
|
||||
fn table_name(&self) -> TableName {
|
||||
self.task.table_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum DropTableState {
|
||||
/// Removes metadata
|
||||
RemoveMetadata,
|
||||
/// Invalidates Table Cache
|
||||
InvalidateTableCache,
|
||||
/// Datanode drops the table
|
||||
DatanodeDropTable,
|
||||
}
|
||||
@@ -45,7 +45,6 @@ use crate::error::{Error, RegisterProcedureLoaderSnafu, Result};
|
||||
use crate::lock::DistLockRef;
|
||||
use crate::metasrv::{SelectorContext, SelectorRef};
|
||||
use crate::service::mailbox::MailboxRef;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
|
||||
const OPEN_REGION_MESSAGE_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const CLOSE_REGION_MESSAGE_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
@@ -208,7 +207,7 @@ impl RegionFailoverManager {
|
||||
let table_global_value = self
|
||||
.selector_ctx
|
||||
.kv_store
|
||||
.get(table_global_key.to_raw_key())
|
||||
.get(&table_global_key.to_raw_key())
|
||||
.await?;
|
||||
Ok(table_global_value.is_some())
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ impl UpdateRegionMetadata {
|
||||
let table_route = value
|
||||
.table_route
|
||||
.with_context(|| CorruptedTableRouteSnafu {
|
||||
key: key.key(),
|
||||
key: key.to_string(),
|
||||
reason: "'table_route' is empty",
|
||||
})?;
|
||||
let mut table_route = TableRoute::try_from_raw(&value.peers, table_route)
|
||||
@@ -177,7 +177,7 @@ fn pretty_log_table_route_change(
|
||||
info!(
|
||||
"Updating region routes in table route value (key = '{}') to [{}]. \
|
||||
Failed region {} was on Datanode {}.",
|
||||
key.key(),
|
||||
key.to_string(),
|
||||
region_routes.join(", "),
|
||||
failed_region.region_number,
|
||||
failed_region.datanode_id,
|
||||
@@ -329,7 +329,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let key = TableRouteKey {
|
||||
table_id: failed_region.table_ident.table_id as u64,
|
||||
table_id: failed_region.table_ident.table_id,
|
||||
catalog_name: &failed_region.table_ident.catalog,
|
||||
schema_name: &failed_region.table_ident.schema,
|
||||
table_name: &failed_region.table_ident.table,
|
||||
@@ -465,7 +465,7 @@ mod tests {
|
||||
let catalog_name = failed_region_1.table_ident.catalog.clone();
|
||||
let schema_name = failed_region_1.table_ident.schema.clone();
|
||||
let table_name = failed_region_1.table_ident.table.clone();
|
||||
let table_id = failed_region_1.table_ident.table_id as u64;
|
||||
let table_id = failed_region_1.table_ident.table_id;
|
||||
|
||||
let _ = futures::future::join_all(vec![
|
||||
tokio::spawn(async move {
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{BatchDeleteRequest, PutRequest, RangeRequest};
|
||||
use async_stream::try_stream;
|
||||
use async_trait::async_trait;
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_meta::rpc::store::{BatchDeleteRequest, PutRequest, RangeRequest};
|
||||
use common_meta::util;
|
||||
use common_procedure::error::{
|
||||
CorruptedDataSnafu, DeleteStatesSnafu, ListStateSnafu, PutStateSnafu,
|
||||
};
|
||||
@@ -24,7 +25,6 @@ use common_procedure::Result;
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::util;
|
||||
|
||||
const PROCEDURE_PREFIX: &str = "/__procedure__/";
|
||||
|
||||
|
||||
81
src/meta-srv/src/procedure/utils.rs
Normal file
81
src/meta-srv/src/procedure/utils.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::TableRouteValue;
|
||||
use catalog::helper::TableGlobalKey;
|
||||
use common_meta::key::TableRouteKey;
|
||||
use common_meta::peer::Peer;
|
||||
use common_meta::rpc::router::TableRoute;
|
||||
use common_procedure::error::Error as ProcedureError;
|
||||
use snafu::{location, Location, ResultExt};
|
||||
use table::engine::TableReference;
|
||||
use table::metadata::TableId;
|
||||
|
||||
use crate::error::{self, Error, Result};
|
||||
|
||||
pub fn build_table_route_value(table_route: TableRoute) -> Result<TableRouteValue> {
|
||||
let (peers, table_route) = table_route
|
||||
.try_into_raw()
|
||||
.context(error::ConvertProtoDataSnafu)?;
|
||||
|
||||
Ok(TableRouteValue {
|
||||
peers,
|
||||
table_route: Some(table_route),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build_table_metadata_key(
|
||||
table_ref: TableReference<'_>,
|
||||
table_id: TableId,
|
||||
) -> (TableGlobalKey, TableRouteKey) {
|
||||
let table_route_key = TableRouteKey {
|
||||
table_id,
|
||||
catalog_name: table_ref.catalog,
|
||||
schema_name: table_ref.schema,
|
||||
table_name: table_ref.schema,
|
||||
};
|
||||
|
||||
let table_global_key = TableGlobalKey {
|
||||
catalog_name: table_ref.catalog.to_string(),
|
||||
schema_name: table_ref.schema.to_string(),
|
||||
table_name: table_ref.table.to_string(),
|
||||
};
|
||||
|
||||
(table_global_key, table_route_key)
|
||||
}
|
||||
|
||||
pub fn handle_request_datanode_error(datanode: Peer) -> impl FnOnce(client::error::Error) -> Error {
|
||||
move |err| {
|
||||
if matches!(err, client::error::Error::FlightGet { .. }) {
|
||||
error::RetryLaterSnafu {
|
||||
reason: format!("Failed to execute operation on datanode, source: {}", err),
|
||||
}
|
||||
.build()
|
||||
} else {
|
||||
error::Error::RequestDatanode {
|
||||
location: location!(),
|
||||
peer: datanode,
|
||||
source: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_retry_error(e: Error) -> ProcedureError {
|
||||
if matches!(e, error::Error::RetryLater { .. }) {
|
||||
ProcedureError::retry_later(e)
|
||||
} else {
|
||||
ProcedureError::external(e)
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::CompareAndPutRequest;
|
||||
use common_meta::rpc::store::CompareAndPutRequest;
|
||||
use snafu::{ensure, OptionExt};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -120,7 +120,6 @@ impl Inner {
|
||||
key: key.to_vec(),
|
||||
expect,
|
||||
value: value.to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let res = self.generator.compare_and_put(req).await?;
|
||||
@@ -156,16 +155,20 @@ impl Inner {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::{
|
||||
use common_meta::kv_backend::{KvBackend, TxnService};
|
||||
use common_meta::rpc::store::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse,
|
||||
BatchPutRequest, BatchPutResponse, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::service::store::kv::KvStore;
|
||||
use crate::error::Error;
|
||||
use crate::service::store::memory::MemStore;
|
||||
use crate::service::store::txn::TxnService;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_sequence() {
|
||||
@@ -200,28 +203,25 @@ mod tests {
|
||||
async fn test_sequence_force_quit() {
|
||||
struct Noop;
|
||||
|
||||
impl TxnService for Noop {}
|
||||
impl TxnService for Noop {
|
||||
type Error = Error;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvStore for Noop {
|
||||
async fn range(
|
||||
&self,
|
||||
_: api::v1::meta::RangeRequest,
|
||||
) -> Result<api::v1::meta::RangeResponse> {
|
||||
impl KvBackend for Noop {
|
||||
fn name(&self) -> &str {
|
||||
"Noop"
|
||||
}
|
||||
|
||||
async fn range(&self, _: RangeRequest) -> Result<RangeResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn put(
|
||||
&self,
|
||||
_: api::v1::meta::PutRequest,
|
||||
) -> Result<api::v1::meta::PutResponse> {
|
||||
async fn put(&self, _: PutRequest) -> Result<PutResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn batch_put(
|
||||
&self,
|
||||
_: api::v1::meta::BatchPutRequest,
|
||||
) -> Result<api::v1::meta::BatchPutResponse> {
|
||||
async fn batch_put(&self, _: BatchPutRequest) -> Result<BatchPutResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
@@ -232,27 +232,25 @@ mod tests {
|
||||
async fn compare_and_put(
|
||||
&self,
|
||||
_: CompareAndPutRequest,
|
||||
) -> Result<api::v1::meta::CompareAndPutResponse> {
|
||||
Ok(api::v1::meta::CompareAndPutResponse::default())
|
||||
) -> Result<CompareAndPutResponse> {
|
||||
Ok(CompareAndPutResponse::default())
|
||||
}
|
||||
|
||||
async fn delete_range(
|
||||
&self,
|
||||
_: api::v1::meta::DeleteRangeRequest,
|
||||
) -> Result<api::v1::meta::DeleteRangeResponse> {
|
||||
async fn delete_range(&self, _: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn move_value(
|
||||
&self,
|
||||
_: api::v1::meta::MoveValueRequest,
|
||||
) -> Result<api::v1::meta::MoveValueResponse> {
|
||||
async fn move_value(&self, _: MoveValueRequest) -> Result<MoveValueResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
async fn batch_delete(&self, _: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
let kv_store = Arc::new(Noop {});
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api::v1::meta::{RangeRequest, RangeResponse};
|
||||
use catalog::helper::{
|
||||
build_catalog_prefix, build_schema_prefix, build_table_global_prefix, TABLE_GLOBAL_KEY_PREFIX,
|
||||
};
|
||||
use common_meta::rpc::store::{RangeRequest, RangeResponse};
|
||||
use common_meta::util;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use tonic::codegen::http;
|
||||
|
||||
use crate::error;
|
||||
use crate::error::Result;
|
||||
use crate::service::admin::HttpHandler;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::{error, util};
|
||||
|
||||
pub struct CatalogsHandler {
|
||||
pub kv_store: KvStoreRef,
|
||||
@@ -104,7 +104,7 @@ impl HttpHandler for TableHandler {
|
||||
})?;
|
||||
let table_key = format!("{TABLE_GLOBAL_KEY_PREFIX}-{table_name}");
|
||||
|
||||
let response = self.kv_store.get(table_key.into_bytes()).await?;
|
||||
let response = self.kv_store.get(table_key.as_bytes()).await?;
|
||||
let mut value: String = "Not found result".to_string();
|
||||
if let Some(key_value) = response {
|
||||
value = String::from_utf8(key_value.value).context(error::InvalidUtf8ValueSnafu)?;
|
||||
@@ -161,11 +161,11 @@ async fn get_keys_by_prefix(key_prefix: String, kv_store: &KvStoreRef) -> Result
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::PutRequest;
|
||||
use catalog::helper::{
|
||||
build_catalog_prefix, build_schema_prefix, build_table_global_prefix, CatalogKey,
|
||||
SchemaKey, TableGlobalKey,
|
||||
};
|
||||
use common_meta::rpc::store::PutRequest;
|
||||
|
||||
use crate::service::admin::meta::get_keys_by_prefix;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
@@ -184,7 +184,7 @@ mod tests {
|
||||
.put(PutRequest {
|
||||
key: catalog.to_string().as_bytes().to_vec(),
|
||||
value: "".as_bytes().to_vec(),
|
||||
..Default::default()
|
||||
prev_kv: false,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
@@ -197,7 +197,7 @@ mod tests {
|
||||
.put(PutRequest {
|
||||
key: schema.to_string().as_bytes().to_vec(),
|
||||
value: "".as_bytes().to_vec(),
|
||||
..Default::default()
|
||||
prev_kv: false,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
@@ -216,7 +216,7 @@ mod tests {
|
||||
.put(PutRequest {
|
||||
key: table1.to_string().as_bytes().to_vec(),
|
||||
value: "".as_bytes().to_vec(),
|
||||
..Default::default()
|
||||
prev_kv: false,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
@@ -224,7 +224,7 @@ mod tests {
|
||||
.put(PutRequest {
|
||||
key: table2.to_string().as_bytes().to_vec(),
|
||||
value: "".as_bytes().to_vec(),
|
||||
..Default::default()
|
||||
prev_kv: false,
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
@@ -14,16 +14,18 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api::v1::meta::{RangeRequest, RangeResponse, TableRouteValue};
|
||||
use api::v1::meta::TableRouteValue;
|
||||
use common_meta::key::TABLE_ROUTE_PREFIX;
|
||||
use common_meta::rpc::store::{RangeRequest, RangeResponse};
|
||||
use common_meta::util;
|
||||
use prost::Message;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use tonic::codegen::http;
|
||||
|
||||
use super::HttpHandler;
|
||||
use crate::error;
|
||||
use crate::error::Result;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::{error, util};
|
||||
|
||||
pub struct RouteHandler {
|
||||
pub kv_store: KvStoreRef,
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{
|
||||
cluster_server, BatchGetRequest, BatchGetResponse, Error, RangeRequest, RangeResponse,
|
||||
ResponseHeader,
|
||||
cluster_server, BatchGetRequest as PbBatchGetRequest, BatchGetResponse as PbBatchGetResponse,
|
||||
Error, RangeRequest as PbRangeRequest, RangeResponse as PbRangeResponse, ResponseHeader,
|
||||
};
|
||||
use common_telemetry::warn;
|
||||
use tonic::{Request, Response};
|
||||
@@ -24,10 +24,10 @@ use crate::service::GrpcResult;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl cluster_server::Cluster for MetaSrv {
|
||||
async fn batch_get(&self, req: Request<BatchGetRequest>) -> GrpcResult<BatchGetResponse> {
|
||||
async fn batch_get(&self, req: Request<PbBatchGetRequest>) -> GrpcResult<PbBatchGetResponse> {
|
||||
if !self.is_leader() {
|
||||
let is_not_leader = ResponseHeader::failed(0, Error::is_not_leader());
|
||||
let resp = BatchGetResponse {
|
||||
let resp = PbBatchGetResponse {
|
||||
header: Some(is_not_leader),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -36,22 +36,17 @@ impl cluster_server::Cluster for MetaSrv {
|
||||
return Ok(Response::new(resp));
|
||||
}
|
||||
|
||||
let kvs = self.in_memory().batch_get(req.into_inner()).await?.kvs;
|
||||
let req = req.into_inner().into();
|
||||
let resp = self.in_memory().batch_get(req).await?;
|
||||
|
||||
let success = ResponseHeader::success(0);
|
||||
|
||||
let get_resp = BatchGetResponse {
|
||||
kvs,
|
||||
header: Some(success),
|
||||
};
|
||||
|
||||
Ok(Response::new(get_resp))
|
||||
let resp = resp.to_proto_resp(ResponseHeader::success(0));
|
||||
Ok(Response::new(resp))
|
||||
}
|
||||
|
||||
async fn range(&self, req: Request<RangeRequest>) -> GrpcResult<RangeResponse> {
|
||||
async fn range(&self, req: Request<PbRangeRequest>) -> GrpcResult<PbRangeResponse> {
|
||||
if !self.is_leader() {
|
||||
let is_not_leader = ResponseHeader::failed(0, Error::is_not_leader());
|
||||
let resp = RangeResponse {
|
||||
let resp = PbRangeResponse {
|
||||
header: Some(is_not_leader),
|
||||
..Default::default()
|
||||
};
|
||||
@@ -60,10 +55,11 @@ impl cluster_server::Cluster for MetaSrv {
|
||||
return Ok(Response::new(resp));
|
||||
}
|
||||
|
||||
let req = req.into_inner();
|
||||
let req = req.into_inner().into();
|
||||
let res = self.in_memory().range(req).await?;
|
||||
|
||||
Ok(Response::new(res))
|
||||
let resp = res.to_proto_resp(ResponseHeader::success(0));
|
||||
Ok(Response::new(resp))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ use api::v1::meta::{
|
||||
Table, TableRoute,
|
||||
};
|
||||
use api::v1::TableId;
|
||||
use common_meta::rpc::ddl::{CreateTableTask, DdlTask};
|
||||
use common_meta::key::TableRouteKey;
|
||||
use common_meta::rpc::ddl::{CreateTableTask, DdlTask, DropTableTask};
|
||||
use common_meta::rpc::router;
|
||||
use common_meta::table_name::TableName;
|
||||
use common_telemetry::{info, warn};
|
||||
@@ -25,11 +26,13 @@ use snafu::{OptionExt, ResultExt};
|
||||
use table::metadata::RawTableInfo;
|
||||
use tonic::{Request, Response};
|
||||
|
||||
use super::store::kv::KvStoreRef;
|
||||
use super::GrpcResult;
|
||||
use crate::ddl::DdlManagerRef;
|
||||
use crate::error::{self, Result};
|
||||
use crate::metasrv::{MetaSrv, SelectorContext, SelectorRef};
|
||||
use crate::sequence::SequenceRef;
|
||||
use crate::table_routes::get_table_route_value;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ddl_task_server::DdlTask for MetaSrv {
|
||||
@@ -67,6 +70,15 @@ impl ddl_task_server::DdlTask for MetaSrv {
|
||||
)
|
||||
.await?
|
||||
}
|
||||
DdlTask::DropTable(drop_table_task) => {
|
||||
handle_drop_table_task(
|
||||
header.cluster_id,
|
||||
drop_table_task,
|
||||
self.kv_store().clone(),
|
||||
self.ddl_manager().clone(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(resp))
|
||||
@@ -116,7 +128,7 @@ async fn handle_create_table_task(
|
||||
.submit_create_table_task(cluster_id, create_table_task, table_route)
|
||||
.await?;
|
||||
|
||||
info!("Table: {table_id} created via procedure_id {id:?}");
|
||||
info!("Table: {table_id} is dropped via procedure_id {id:?}");
|
||||
|
||||
Ok(SubmitDdlTaskResponse {
|
||||
key: id.to_string().into(),
|
||||
@@ -185,3 +197,42 @@ async fn handle_create_table_route(
|
||||
|
||||
router::TableRoute::try_from_raw(&peers, table_route).context(error::TableRouteConversionSnafu)
|
||||
}
|
||||
|
||||
async fn handle_drop_table_task(
|
||||
cluster_id: u64,
|
||||
drop_table_task: DropTableTask,
|
||||
kv_store: KvStoreRef,
|
||||
ddl_manager: DdlManagerRef,
|
||||
) -> Result<SubmitDdlTaskResponse> {
|
||||
let table_id = drop_table_task.table_id;
|
||||
|
||||
let table_route_key = TableRouteKey {
|
||||
table_id,
|
||||
catalog_name: &drop_table_task.catalog,
|
||||
schema_name: &drop_table_task.schema,
|
||||
table_name: &drop_table_task.table,
|
||||
};
|
||||
|
||||
let table_route_value = get_table_route_value(&kv_store, &table_route_key).await?;
|
||||
|
||||
let table_route = router::TableRoute::try_from_raw(
|
||||
&table_route_value.peers,
|
||||
table_route_value
|
||||
.table_route
|
||||
.context(error::UnexpectedSnafu {
|
||||
violated: "expected table_route",
|
||||
})?,
|
||||
)
|
||||
.context(error::TableRouteConversionSnafu)?;
|
||||
|
||||
let id = ddl_manager
|
||||
.submit_drop_table_task(cluster_id, drop_table_task, table_route)
|
||||
.await?;
|
||||
|
||||
info!("Table: {table_id} created via procedure_id {id:?}");
|
||||
|
||||
Ok(SubmitDdlTaskResponse {
|
||||
key: id.to_string().into(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api::v1::meta::{
|
||||
router_server, BatchPutRequest, CreateRequest, DeleteRequest, Error, KeyValue, Peer, PeerDict,
|
||||
Region, RegionRoute, ResponseHeader, RouteRequest, RouteResponse, Table, TableRoute,
|
||||
TableRouteValue,
|
||||
router_server, CreateRequest, DeleteRequest, Error, Peer, PeerDict, Region, RegionRoute,
|
||||
ResponseHeader, RouteRequest, RouteResponse, Table, TableRoute, TableRouteValue,
|
||||
};
|
||||
use catalog::helper::{TableGlobalKey, TableGlobalValue};
|
||||
use common_meta::key::TableRouteKey;
|
||||
use common_meta::rpc::store::BatchPutRequest;
|
||||
use common_meta::rpc::KeyValue;
|
||||
use common_meta::table_name::TableName;
|
||||
use common_telemetry::{timer, warn};
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
@@ -33,12 +34,10 @@ use crate::lock::{keys, DistLockGuard};
|
||||
use crate::metasrv::{Context, MetaSrv, SelectorContext, SelectorRef};
|
||||
use crate::metrics::METRIC_META_ROUTE_REQUEST;
|
||||
use crate::sequence::SequenceRef;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::service::GrpcResult;
|
||||
use crate::table_routes::{
|
||||
get_table_global_value, get_table_route_value, remove_table_global_value,
|
||||
remove_table_route_value,
|
||||
fetch_tables, get_table_global_value, remove_table_global_value, remove_table_route_value,
|
||||
table_route_key,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -67,7 +66,7 @@ impl router_server::Router for MetaSrv {
|
||||
.to_string()
|
||||
.into_bytes();
|
||||
ensure!(
|
||||
self.kv_store().get(table_global_key).await?.is_none(),
|
||||
self.kv_store().get(&table_global_key).await?.is_none(),
|
||||
error::TableAlreadyExistsSnafu {
|
||||
table_name: table_name.to_string(),
|
||||
}
|
||||
@@ -181,8 +180,8 @@ async fn handle_create(
|
||||
let id = table_id_sequence.next().await?;
|
||||
table_info.ident.table_id = id as u32;
|
||||
|
||||
let table_route_key = TableRouteKey::with_table_name(id, &table_name)
|
||||
.key()
|
||||
let table_route_key = TableRouteKey::with_table_name(id as _, &table_name)
|
||||
.to_string()
|
||||
.into_bytes();
|
||||
|
||||
let table = Table {
|
||||
@@ -239,7 +238,6 @@ async fn handle_create(
|
||||
},
|
||||
],
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let resp = ctx.kv_store.batch_put(req).await?;
|
||||
@@ -337,7 +335,7 @@ async fn handle_delete(req: DeleteRequest, ctx: Context) -> Result<RouteResponse
|
||||
|
||||
let _ = remove_table_global_value(&ctx.kv_store, &tgk).await?;
|
||||
|
||||
let trk = table_route_key(tgv.table_id() as u64, &tgk);
|
||||
let trk = table_route_key(tgv.table_id(), &tgk);
|
||||
let (_, trv) = remove_table_route_value(&ctx.kv_store, &trk).await?;
|
||||
let (peers, table_routes) = fill_table_routes(vec![(tgv, trv)])?;
|
||||
|
||||
@@ -382,36 +380,3 @@ pub(crate) fn fill_table_routes(
|
||||
|
||||
Ok((peer_dict.into_peers(), table_routes))
|
||||
}
|
||||
|
||||
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 tgk in keys {
|
||||
let tgv = get_table_global_value(kv_store, &tgk).await?;
|
||||
if tgv.is_none() {
|
||||
warn!("Table global value is absent: {}", tgk);
|
||||
continue;
|
||||
}
|
||||
let tgv = tgv.unwrap();
|
||||
|
||||
let trk = table_route_key(tgv.table_id() as u64, &tgk);
|
||||
let trv = get_table_route_value(kv_store, &trk).await?;
|
||||
|
||||
tables.push((tgv, trv));
|
||||
}
|
||||
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
pub(crate) fn table_route_key(table_id: u64, t: &TableGlobalKey) -> TableRouteKey<'_> {
|
||||
TableRouteKey {
|
||||
table_id,
|
||||
catalog_name: &t.catalog_name,
|
||||
schema_name: &t.schema_name,
|
||||
table_name: &t.table_name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,85 +15,252 @@
|
||||
pub mod cached_kv;
|
||||
pub mod etcd;
|
||||
pub(crate) mod etcd_util;
|
||||
pub mod ext;
|
||||
pub mod kv;
|
||||
pub mod memory;
|
||||
pub mod txn;
|
||||
|
||||
use api::v1::meta::{
|
||||
store_server, BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse,
|
||||
BatchPutRequest, BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse,
|
||||
DeleteRangeRequest, DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest,
|
||||
PutResponse, RangeRequest, RangeResponse,
|
||||
store_server, BatchDeleteRequest as PbBatchDeleteRequest,
|
||||
BatchDeleteResponse as PbBatchDeleteResponse, BatchGetRequest as PbBatchGetRequest,
|
||||
BatchGetResponse as PbBatchGetResponse, BatchPutRequest as PbBatchPutRequest,
|
||||
BatchPutResponse as PbBatchPutResponse, CompareAndPutRequest as PbCompareAndPutRequest,
|
||||
CompareAndPutResponse as PbCompareAndPutResponse, DeleteRangeRequest as PbDeleteRangeRequest,
|
||||
DeleteRangeResponse as PbDeleteRangeResponse, MoveValueRequest as PbMoveValueRequest,
|
||||
MoveValueResponse as PbMoveValueResponse, PutRequest as PbPutRequest,
|
||||
PutResponse as PbPutResponse, RangeRequest as PbRangeRequest, RangeResponse as PbRangeResponse,
|
||||
ResponseHeader,
|
||||
};
|
||||
use common_meta::rpc::store::{
|
||||
BatchDeleteRequest, BatchGetRequest, BatchPutRequest, CompareAndPutRequest, DeleteRangeRequest,
|
||||
MoveValueRequest, PutRequest, RangeRequest,
|
||||
};
|
||||
use common_telemetry::timer;
|
||||
use snafu::OptionExt;
|
||||
use tonic::{Request, Response};
|
||||
|
||||
use crate::error::MissingRequestHeaderSnafu;
|
||||
use crate::metasrv::MetaSrv;
|
||||
use crate::metrics::METRIC_META_KV_REQUEST;
|
||||
use crate::service::GrpcResult;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl store_server::Store for MetaSrv {
|
||||
async fn range(&self, req: Request<RangeRequest>) -> GrpcResult<RangeResponse> {
|
||||
async fn range(&self, req: Request<PbRangeRequest>) -> GrpcResult<PbRangeResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "range".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: RangeRequest = req.into();
|
||||
|
||||
let res = self.kv_store().range(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn put(&self, req: Request<PutRequest>) -> GrpcResult<PutResponse> {
|
||||
async fn put(&self, req: Request<PbPutRequest>) -> GrpcResult<PbPutResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "put".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: PutRequest = req.into();
|
||||
|
||||
let res = self.kv_store().put(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn batch_get(&self, req: Request<BatchGetRequest>) -> GrpcResult<BatchGetResponse> {
|
||||
async fn batch_get(&self, req: Request<PbBatchGetRequest>) -> GrpcResult<PbBatchGetResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "batch_get".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: BatchGetRequest = req.into();
|
||||
|
||||
let res = self.kv_store().batch_get(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn batch_put(&self, req: Request<BatchPutRequest>) -> GrpcResult<BatchPutResponse> {
|
||||
async fn batch_put(&self, req: Request<PbBatchPutRequest>) -> GrpcResult<PbBatchPutResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "batch_pub".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: BatchPutRequest = req.into();
|
||||
|
||||
let res = self.kv_store().batch_put(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn batch_delete(
|
||||
&self,
|
||||
req: Request<BatchDeleteRequest>,
|
||||
) -> GrpcResult<BatchDeleteResponse> {
|
||||
req: Request<PbBatchDeleteRequest>,
|
||||
) -> GrpcResult<PbBatchDeleteResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "batch_delete".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: BatchDeleteRequest = req.into();
|
||||
|
||||
let res = self.kv_store().batch_delete(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn compare_and_put(
|
||||
&self,
|
||||
req: Request<CompareAndPutRequest>,
|
||||
) -> GrpcResult<CompareAndPutResponse> {
|
||||
req: Request<PbCompareAndPutRequest>,
|
||||
) -> GrpcResult<PbCompareAndPutResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "compare_and_put".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: CompareAndPutRequest = req.into();
|
||||
|
||||
let res = self.kv_store().compare_and_put(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn delete_range(
|
||||
&self,
|
||||
req: Request<DeleteRangeRequest>,
|
||||
) -> GrpcResult<DeleteRangeResponse> {
|
||||
req: Request<PbDeleteRangeRequest>,
|
||||
) -> GrpcResult<PbDeleteRangeResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "delete_range".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: DeleteRangeRequest = req.into();
|
||||
|
||||
let res = self.kv_store().delete_range(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn move_value(&self, req: Request<MoveValueRequest>) -> GrpcResult<MoveValueResponse> {
|
||||
async fn move_value(
|
||||
&self,
|
||||
req: Request<PbMoveValueRequest>,
|
||||
) -> GrpcResult<PbMoveValueResponse> {
|
||||
let req = req.into_inner();
|
||||
|
||||
let cluster_id = req
|
||||
.header
|
||||
.as_ref()
|
||||
.context(MissingRequestHeaderSnafu)?
|
||||
.cluster_id;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", self.kv_store().name().to_string()),
|
||||
("op", "move_value".to_string()),
|
||||
("cluster_id", cluster_id.to_string()),
|
||||
]
|
||||
);
|
||||
|
||||
let req: MoveValueRequest = req.into();
|
||||
|
||||
let res = self.kv_store().move_value(req).await?;
|
||||
|
||||
let res = res.to_proto_resp(ResponseHeader::success(cluster_id));
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
}
|
||||
@@ -122,7 +289,8 @@ mod tests {
|
||||
async fn test_range() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = RangeRequest::default();
|
||||
let mut req = RangeRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.range(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -132,7 +300,8 @@ mod tests {
|
||||
async fn test_put() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = PutRequest::default();
|
||||
let mut req = PutRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.put(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -142,7 +311,8 @@ mod tests {
|
||||
async fn test_batch_get() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = BatchGetRequest::default();
|
||||
let mut req = BatchGetRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.batch_get(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -152,7 +322,8 @@ mod tests {
|
||||
async fn test_batch_put() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = BatchPutRequest::default();
|
||||
let mut req = BatchPutRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.batch_put(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -162,7 +333,8 @@ mod tests {
|
||||
async fn test_batch_delete() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = BatchDeleteRequest::default();
|
||||
let mut req = BatchDeleteRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.batch_delete(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -172,7 +344,8 @@ mod tests {
|
||||
async fn test_compare_and_put() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = CompareAndPutRequest::default();
|
||||
let mut req = CompareAndPutRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.compare_and_put(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -182,7 +355,8 @@ mod tests {
|
||||
async fn test_delete_range() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = DeleteRangeRequest::default();
|
||||
let mut req = DeleteRangeRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.delete_range(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
@@ -192,7 +366,8 @@ mod tests {
|
||||
async fn test_move_value() {
|
||||
let meta_srv = new_meta_srv().await;
|
||||
|
||||
let req = MoveValueRequest::default();
|
||||
let mut req = MoveValueRequest::default();
|
||||
req.set_header((1, 1), Role::Datanode);
|
||||
let res = meta_srv.move_value(req.into_request()).await;
|
||||
|
||||
let _ = res.unwrap();
|
||||
|
||||
@@ -12,22 +12,24 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::{
|
||||
use common_meta::kv_backend::txn::{Txn, TxnOp, TxnRequest, TxnResponse};
|
||||
use common_meta::kv_backend::{KvBackend, TxnService};
|
||||
use common_meta::rpc::store::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, KeyValue, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
use common_meta::rpc::KeyValue;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::{KvStore, KvStoreRef, ResettableKvStore, ResettableKvStoreRef};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::service::store::kv::{KvStoreRef, ResettableKvStore, ResettableKvStoreRef};
|
||||
use crate::service::store::memory::MemStore;
|
||||
use crate::service::store::txn::{Txn, TxnOp, TxnRequest, TxnResponse, TxnService};
|
||||
|
||||
pub type CheckLeaderRef = Arc<dyn CheckLeader>;
|
||||
|
||||
@@ -57,15 +59,18 @@ pub struct LeaderCachedKvStore {
|
||||
store: KvStoreRef,
|
||||
cache: ResettableKvStoreRef,
|
||||
version: AtomicUsize,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl LeaderCachedKvStore {
|
||||
pub fn new(check_leader: CheckLeaderRef, store: KvStoreRef) -> Self {
|
||||
let name = format!("LeaderCached({})", store.name());
|
||||
Self {
|
||||
check_leader,
|
||||
store,
|
||||
cache: Arc::new(MemStore::new()),
|
||||
version: AtomicUsize::new(0),
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +87,7 @@ impl LeaderCachedKvStore {
|
||||
|
||||
#[inline]
|
||||
async fn invalid_key(&self, key: Vec<u8>) -> Result<()> {
|
||||
let _ = self.cache.delete(key, false).await?;
|
||||
let _ = self.cache.delete(&key, false).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -110,7 +115,11 @@ impl LeaderCachedKvStore {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvStore for LeaderCachedKvStore {
|
||||
impl KvBackend for LeaderCachedKvStore {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
|
||||
if !self.is_leader() {
|
||||
return self.store.range(req).await;
|
||||
@@ -186,16 +195,13 @@ impl KvStore for LeaderCachedKvStore {
|
||||
.filter(|key| !hit_keys.contains(*key))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let remote_req = BatchGetRequest {
|
||||
keys: missed_keys,
|
||||
..Default::default()
|
||||
};
|
||||
let remote_req = BatchGetRequest { keys: missed_keys };
|
||||
|
||||
let ver = self.get_version();
|
||||
|
||||
let remote_res = self.store.batch_get(remote_req).await?;
|
||||
let put_req = BatchPutRequest {
|
||||
kvs: remote_res.kvs.clone(),
|
||||
kvs: remote_res.kvs.clone().into_iter().map(Into::into).collect(),
|
||||
..Default::default()
|
||||
};
|
||||
let _ = self.cache.batch_put(put_req).await?;
|
||||
@@ -204,7 +210,7 @@ impl KvStore for LeaderCachedKvStore {
|
||||
let keys = remote_res
|
||||
.kvs
|
||||
.iter()
|
||||
.map(|kv| kv.key.clone())
|
||||
.map(|kv| kv.key().to_vec())
|
||||
.collect::<Vec<_>>();
|
||||
self.invalid_keys(keys).await?;
|
||||
}
|
||||
@@ -291,10 +297,16 @@ impl KvStore for LeaderCachedKvStore {
|
||||
self.invalid_keys(vec![from_key, to_key]).await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TxnService for LeaderCachedKvStore {
|
||||
type Error = Error;
|
||||
|
||||
async fn txn(&self, txn: Txn) -> Result<TxnResponse> {
|
||||
if !self.is_leader() {
|
||||
return self.store.txn(txn).await;
|
||||
@@ -338,7 +350,7 @@ impl ResettableKvStore for LeaderCachedKvStore {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use api::v1::meta::KeyValue;
|
||||
use common_meta::rpc::KeyValue;
|
||||
|
||||
use super::*;
|
||||
use crate::service::store::memory::MemStore;
|
||||
@@ -364,23 +376,19 @@ mod tests {
|
||||
};
|
||||
let _ = inner_store.put(put_req).await.unwrap();
|
||||
|
||||
let cached_value = inner_cache.get(key.clone()).await.unwrap();
|
||||
let cached_value = inner_cache.get(&key).await.unwrap();
|
||||
assert!(cached_value.is_none());
|
||||
|
||||
let cached_value = cached_store.get(key.clone()).await.unwrap().unwrap();
|
||||
assert_eq!(cached_value.value, value);
|
||||
let cached_value = cached_store.get(&key).await.unwrap().unwrap();
|
||||
assert_eq!(cached_value.value(), value);
|
||||
|
||||
let cached_value = inner_cache.get(key.clone()).await.unwrap().unwrap();
|
||||
assert_eq!(cached_value.value, value);
|
||||
let cached_value = inner_cache.get(&key).await.unwrap().unwrap();
|
||||
assert_eq!(cached_value.value(), value);
|
||||
|
||||
let res = cached_store
|
||||
.delete(key.clone(), true)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(res.value, value);
|
||||
let res = cached_store.delete(&key, true).await.unwrap().unwrap();
|
||||
assert_eq!(res.value(), value);
|
||||
|
||||
let cached_value = inner_cache.get(key.clone()).await.unwrap();
|
||||
let cached_value = inner_cache.get(&key).await.unwrap();
|
||||
assert!(cached_value.is_none());
|
||||
}
|
||||
|
||||
@@ -409,10 +417,7 @@ mod tests {
|
||||
.map(|i| format!("test_key_{}", i).into_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let batch_get_req = BatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
};
|
||||
let batch_get_req = BatchGetRequest { keys };
|
||||
|
||||
let cached_values = inner_cache.batch_get(batch_get_req.clone()).await.unwrap();
|
||||
assert!(cached_values.kvs.is_empty());
|
||||
@@ -451,10 +456,7 @@ mod tests {
|
||||
let keys = (1..5)
|
||||
.map(|i| format!("test_key_{}", i).into_bytes())
|
||||
.collect::<Vec<_>>();
|
||||
let batch_get_req = BatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
};
|
||||
let batch_get_req = BatchGetRequest { keys };
|
||||
let cached_values = inner_cache.batch_get(batch_get_req.clone()).await.unwrap();
|
||||
assert_eq!(cached_values.kvs.len(), 4);
|
||||
|
||||
|
||||
@@ -12,15 +12,20 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::{
|
||||
use common_error::prelude::*;
|
||||
use common_meta::kv_backend::txn::{Txn as KvTxn, TxnResponse as KvTxnResponse};
|
||||
use common_meta::kv_backend::{KvBackend, TxnService};
|
||||
use common_meta::metrics::METRIC_META_TXN_REQUEST;
|
||||
use common_meta::rpc::store::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, KeyValue, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse, ResponseHeader,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
use common_error::prelude::*;
|
||||
use common_meta::rpc::KeyValue;
|
||||
use common_telemetry::{timer, warn};
|
||||
use etcd_client::{
|
||||
Client, Compare, CompareOp, DeleteOptions, GetOptions, PutOptions, Txn, TxnOp, TxnOpResponse,
|
||||
@@ -28,11 +33,9 @@ use etcd_client::{
|
||||
};
|
||||
|
||||
use crate::error;
|
||||
use crate::error::Result;
|
||||
use crate::metrics::{METRIC_META_KV_REQUEST, METRIC_META_TXN_REQUEST};
|
||||
use crate::error::{ConvertEtcdTxnObjectSnafu, Error, Result};
|
||||
use crate::service::store::etcd_util::KvPair;
|
||||
use crate::service::store::kv::{KvStore, KvStoreRef};
|
||||
use crate::service::store::txn::TxnService;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
|
||||
// Maximum number of operations permitted in a transaction.
|
||||
// The etcd default configuration's `--max-txn-ops` is 128.
|
||||
@@ -89,22 +92,13 @@ impl EtcdStore {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvStore for EtcdStore {
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
|
||||
let Get {
|
||||
cluster_id,
|
||||
key,
|
||||
options,
|
||||
} = req.try_into()?;
|
||||
impl KvBackend for EtcdStore {
|
||||
fn name(&self) -> &str {
|
||||
"Etcd"
|
||||
}
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "range".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
|
||||
let Get { key, options } = req.try_into()?;
|
||||
|
||||
let res = self
|
||||
.client
|
||||
@@ -119,9 +113,7 @@ impl KvStore for EtcdStore {
|
||||
.map(KvPair::from_etcd_kv)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(RangeResponse {
|
||||
header,
|
||||
kvs,
|
||||
more: res.more(),
|
||||
})
|
||||
@@ -129,21 +121,11 @@ impl KvStore for EtcdStore {
|
||||
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse> {
|
||||
let Put {
|
||||
cluster_id,
|
||||
key,
|
||||
value,
|
||||
options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "put".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
|
||||
let res = self
|
||||
.client
|
||||
.kv_client()
|
||||
@@ -152,26 +134,11 @@ impl KvStore for EtcdStore {
|
||||
.context(error::EtcdFailedSnafu)?;
|
||||
|
||||
let prev_kv = res.prev_key().map(KvPair::from_etcd_kv);
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(PutResponse { header, prev_kv })
|
||||
Ok(PutResponse { prev_kv })
|
||||
}
|
||||
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse> {
|
||||
let BatchGet {
|
||||
cluster_id,
|
||||
keys,
|
||||
options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "batch_get".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
let BatchGet { keys, options } = req.try_into()?;
|
||||
|
||||
let get_ops: Vec<_> = keys
|
||||
.into_iter()
|
||||
@@ -192,25 +159,11 @@ impl KvStore for EtcdStore {
|
||||
}
|
||||
}
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(BatchGetResponse { header, kvs })
|
||||
Ok(BatchGetResponse { kvs })
|
||||
}
|
||||
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse> {
|
||||
let BatchPut {
|
||||
cluster_id,
|
||||
kvs,
|
||||
options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "batch_put".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
let BatchPut { kvs, options } = req.try_into()?;
|
||||
|
||||
let put_ops = kvs
|
||||
.into_iter()
|
||||
@@ -233,25 +186,11 @@ impl KvStore for EtcdStore {
|
||||
}
|
||||
}
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(BatchPutResponse { header, prev_kvs })
|
||||
Ok(BatchPutResponse { prev_kvs })
|
||||
}
|
||||
|
||||
async fn batch_delete(&self, req: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
|
||||
let BatchDelete {
|
||||
cluster_id,
|
||||
keys,
|
||||
options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "batch_delete".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
let BatchDelete { keys, options } = req.try_into()?;
|
||||
|
||||
let mut prev_kvs = Vec::with_capacity(keys.len());
|
||||
|
||||
@@ -275,28 +214,17 @@ impl KvStore for EtcdStore {
|
||||
}
|
||||
}
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(BatchDeleteResponse { header, prev_kvs })
|
||||
Ok(BatchDeleteResponse { prev_kvs })
|
||||
}
|
||||
|
||||
async fn compare_and_put(&self, req: CompareAndPutRequest) -> Result<CompareAndPutResponse> {
|
||||
let CompareAndPut {
|
||||
cluster_id,
|
||||
key,
|
||||
expect,
|
||||
value,
|
||||
put_options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "compare_and_put".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
|
||||
let compare = if expect.is_empty() {
|
||||
// create if absent
|
||||
// revision 0 means key was not exist
|
||||
@@ -333,29 +261,11 @@ impl KvStore for EtcdStore {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(CompareAndPutResponse {
|
||||
header,
|
||||
success,
|
||||
prev_kv,
|
||||
})
|
||||
Ok(CompareAndPutResponse { success, prev_kv })
|
||||
}
|
||||
|
||||
async fn delete_range(&self, req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
|
||||
let Delete {
|
||||
cluster_id,
|
||||
key,
|
||||
options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "delete_range".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
let Delete { key, options } = req.try_into()?;
|
||||
|
||||
let res = self
|
||||
.client
|
||||
@@ -370,9 +280,7 @@ impl KvStore for EtcdStore {
|
||||
.map(KvPair::from_etcd_kv)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(DeleteRangeResponse {
|
||||
header,
|
||||
deleted: res.deleted(),
|
||||
prev_kvs,
|
||||
})
|
||||
@@ -380,24 +288,13 @@ impl KvStore for EtcdStore {
|
||||
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse> {
|
||||
let MoveValue {
|
||||
cluster_id,
|
||||
from_key,
|
||||
to_key,
|
||||
delete_options,
|
||||
} = req.try_into()?;
|
||||
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[
|
||||
("target", "etcd".to_string()),
|
||||
("op", "move_value".to_string()),
|
||||
("cluster_id", cluster_id.to_string())
|
||||
]
|
||||
);
|
||||
|
||||
let mut client = self.client.kv_client();
|
||||
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
// TODO(jiachun): Maybe it's better to let the users control it in the request
|
||||
const MAX_RETRIES: usize = 8;
|
||||
for _ in 0..MAX_RETRIES {
|
||||
@@ -442,16 +339,14 @@ impl KvStore for EtcdStore {
|
||||
for op_res in txn_res.op_responses() {
|
||||
match op_res {
|
||||
TxnOpResponse::Get(res) => {
|
||||
return Ok(MoveValueResponse {
|
||||
header,
|
||||
kv: res.kvs().first().map(KvPair::from_etcd_kv),
|
||||
});
|
||||
return Ok(MoveValueResponse(
|
||||
res.kvs().first().map(KvPair::from_etcd_kv),
|
||||
));
|
||||
}
|
||||
TxnOpResponse::Delete(res) => {
|
||||
return Ok(MoveValueResponse {
|
||||
header,
|
||||
kv: res.prev_kvs().first().map(KvPair::from_etcd_kv),
|
||||
});
|
||||
return Ok(MoveValueResponse(
|
||||
res.prev_kvs().first().map(KvPair::from_etcd_kv),
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -463,14 +358,17 @@ impl KvStore for EtcdStore {
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TxnService for EtcdStore {
|
||||
async fn txn(
|
||||
&self,
|
||||
txn: crate::service::store::txn::Txn,
|
||||
) -> Result<crate::service::store::txn::TxnResponse> {
|
||||
type Error = Error;
|
||||
|
||||
async fn txn(&self, txn: KvTxn) -> Result<KvTxnResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_TXN_REQUEST,
|
||||
&[("target", "etcd".to_string()), ("op", "txn".to_string()),]
|
||||
@@ -483,12 +381,11 @@ impl TxnService for EtcdStore {
|
||||
.txn(etcd_txn)
|
||||
.await
|
||||
.context(error::EtcdFailedSnafu)?;
|
||||
txn_res.try_into()
|
||||
txn_res.try_into().context(ConvertEtcdTxnObjectSnafu)
|
||||
}
|
||||
}
|
||||
|
||||
struct Get {
|
||||
cluster_id: u64,
|
||||
key: Vec<u8>,
|
||||
options: Option<GetOptions>,
|
||||
}
|
||||
@@ -498,7 +395,6 @@ impl TryFrom<RangeRequest> for Get {
|
||||
|
||||
fn try_from(req: RangeRequest) -> Result<Self> {
|
||||
let RangeRequest {
|
||||
header,
|
||||
key,
|
||||
range_end,
|
||||
limit,
|
||||
@@ -519,7 +415,6 @@ impl TryFrom<RangeRequest> for Get {
|
||||
}
|
||||
|
||||
Ok(Get {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
key,
|
||||
options: Some(options),
|
||||
})
|
||||
@@ -527,7 +422,6 @@ impl TryFrom<RangeRequest> for Get {
|
||||
}
|
||||
|
||||
struct Put {
|
||||
cluster_id: u64,
|
||||
key: Vec<u8>,
|
||||
value: Vec<u8>,
|
||||
options: Option<PutOptions>,
|
||||
@@ -538,7 +432,6 @@ impl TryFrom<PutRequest> for Put {
|
||||
|
||||
fn try_from(req: PutRequest) -> Result<Self> {
|
||||
let PutRequest {
|
||||
header,
|
||||
key,
|
||||
value,
|
||||
prev_kv,
|
||||
@@ -550,7 +443,6 @@ impl TryFrom<PutRequest> for Put {
|
||||
}
|
||||
|
||||
Ok(Put {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
key,
|
||||
value,
|
||||
options: Some(options),
|
||||
@@ -559,7 +451,6 @@ impl TryFrom<PutRequest> for Put {
|
||||
}
|
||||
|
||||
struct BatchGet {
|
||||
cluster_id: u64,
|
||||
keys: Vec<Vec<u8>>,
|
||||
options: Option<GetOptions>,
|
||||
}
|
||||
@@ -568,12 +459,11 @@ impl TryFrom<BatchGetRequest> for BatchGet {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(req: BatchGetRequest) -> Result<Self> {
|
||||
let BatchGetRequest { header, keys } = req;
|
||||
let BatchGetRequest { keys } = req;
|
||||
|
||||
let options = GetOptions::default();
|
||||
|
||||
Ok(BatchGet {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
keys,
|
||||
options: Some(options),
|
||||
})
|
||||
@@ -581,7 +471,6 @@ impl TryFrom<BatchGetRequest> for BatchGet {
|
||||
}
|
||||
|
||||
struct BatchPut {
|
||||
cluster_id: u64,
|
||||
kvs: Vec<KeyValue>,
|
||||
options: Option<PutOptions>,
|
||||
}
|
||||
@@ -590,11 +479,7 @@ impl TryFrom<BatchPutRequest> for BatchPut {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(req: BatchPutRequest) -> Result<Self> {
|
||||
let BatchPutRequest {
|
||||
header,
|
||||
kvs,
|
||||
prev_kv,
|
||||
} = req;
|
||||
let BatchPutRequest { kvs, prev_kv } = req;
|
||||
|
||||
let mut options = PutOptions::default();
|
||||
if prev_kv {
|
||||
@@ -602,7 +487,6 @@ impl TryFrom<BatchPutRequest> for BatchPut {
|
||||
}
|
||||
|
||||
Ok(BatchPut {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
kvs,
|
||||
options: Some(options),
|
||||
})
|
||||
@@ -610,7 +494,6 @@ impl TryFrom<BatchPutRequest> for BatchPut {
|
||||
}
|
||||
|
||||
struct BatchDelete {
|
||||
cluster_id: u64,
|
||||
keys: Vec<Vec<u8>>,
|
||||
options: Option<DeleteOptions>,
|
||||
}
|
||||
@@ -619,11 +502,7 @@ impl TryFrom<BatchDeleteRequest> for BatchDelete {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(req: BatchDeleteRequest) -> Result<Self> {
|
||||
let BatchDeleteRequest {
|
||||
header,
|
||||
keys,
|
||||
prev_kv,
|
||||
} = req;
|
||||
let BatchDeleteRequest { keys, prev_kv } = req;
|
||||
|
||||
let mut options = DeleteOptions::default();
|
||||
if prev_kv {
|
||||
@@ -631,7 +510,6 @@ impl TryFrom<BatchDeleteRequest> for BatchDelete {
|
||||
}
|
||||
|
||||
Ok(BatchDelete {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
keys,
|
||||
options: Some(options),
|
||||
})
|
||||
@@ -639,7 +517,6 @@ impl TryFrom<BatchDeleteRequest> for BatchDelete {
|
||||
}
|
||||
|
||||
struct CompareAndPut {
|
||||
cluster_id: u64,
|
||||
key: Vec<u8>,
|
||||
expect: Vec<u8>,
|
||||
value: Vec<u8>,
|
||||
@@ -650,15 +527,9 @@ impl TryFrom<CompareAndPutRequest> for CompareAndPut {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(req: CompareAndPutRequest) -> Result<Self> {
|
||||
let CompareAndPutRequest {
|
||||
header,
|
||||
key,
|
||||
expect,
|
||||
value,
|
||||
} = req;
|
||||
let CompareAndPutRequest { key, expect, value } = req;
|
||||
|
||||
Ok(CompareAndPut {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
key,
|
||||
expect,
|
||||
value,
|
||||
@@ -668,7 +539,6 @@ impl TryFrom<CompareAndPutRequest> for CompareAndPut {
|
||||
}
|
||||
|
||||
struct Delete {
|
||||
cluster_id: u64,
|
||||
key: Vec<u8>,
|
||||
options: Option<DeleteOptions>,
|
||||
}
|
||||
@@ -678,7 +548,6 @@ impl TryFrom<DeleteRangeRequest> for Delete {
|
||||
|
||||
fn try_from(req: DeleteRangeRequest) -> Result<Self> {
|
||||
let DeleteRangeRequest {
|
||||
header,
|
||||
key,
|
||||
range_end,
|
||||
prev_kv,
|
||||
@@ -695,7 +564,6 @@ impl TryFrom<DeleteRangeRequest> for Delete {
|
||||
}
|
||||
|
||||
Ok(Delete {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
key,
|
||||
options: Some(options),
|
||||
})
|
||||
@@ -703,7 +571,6 @@ impl TryFrom<DeleteRangeRequest> for Delete {
|
||||
}
|
||||
|
||||
struct MoveValue {
|
||||
cluster_id: u64,
|
||||
from_key: Vec<u8>,
|
||||
to_key: Vec<u8>,
|
||||
delete_options: Option<DeleteOptions>,
|
||||
@@ -713,14 +580,9 @@ impl TryFrom<MoveValueRequest> for MoveValue {
|
||||
type Error = error::Error;
|
||||
|
||||
fn try_from(req: MoveValueRequest) -> Result<Self> {
|
||||
let MoveValueRequest {
|
||||
header,
|
||||
from_key,
|
||||
to_key,
|
||||
} = req;
|
||||
let MoveValueRequest { from_key, to_key } = req;
|
||||
|
||||
Ok(MoveValue {
|
||||
cluster_id: header.map_or(0, |h| h.cluster_id),
|
||||
from_key,
|
||||
to_key,
|
||||
delete_options: Some(DeleteOptions::default().with_prev_key()),
|
||||
@@ -739,7 +601,6 @@ mod tests {
|
||||
range_end: b"test_range_end".to_vec(),
|
||||
limit: 64,
|
||||
keys_only: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let get: Get = req.try_into().unwrap();
|
||||
@@ -754,7 +615,6 @@ mod tests {
|
||||
key: b"test_key".to_vec(),
|
||||
value: b"test_value".to_vec(),
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let put: Put = req.try_into().unwrap();
|
||||
@@ -768,7 +628,6 @@ mod tests {
|
||||
fn test_parse_batch_get() {
|
||||
let req = BatchGetRequest {
|
||||
keys: vec![b"k1".to_vec(), b"k2".to_vec(), b"k3".to_vec()],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let batch_get: BatchGet = req.try_into().unwrap();
|
||||
@@ -787,13 +646,13 @@ mod tests {
|
||||
value: b"test_value".to_vec(),
|
||||
}],
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let batch_put: BatchPut = req.try_into().unwrap();
|
||||
|
||||
assert_eq!(b"test_key".to_vec(), batch_put.kvs.get(0).unwrap().key);
|
||||
assert_eq!(b"test_value".to_vec(), batch_put.kvs.get(0).unwrap().value);
|
||||
let kv = batch_put.kvs.get(0).unwrap();
|
||||
assert_eq!(b"test_key", kv.key());
|
||||
assert_eq!(b"test_value", kv.value());
|
||||
let _ = batch_put.options.unwrap();
|
||||
}
|
||||
|
||||
@@ -802,7 +661,6 @@ mod tests {
|
||||
let req = BatchDeleteRequest {
|
||||
keys: vec![b"k1".to_vec(), b"k2".to_vec(), b"k3".to_vec()],
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let batch_delete: BatchDelete = req.try_into().unwrap();
|
||||
@@ -820,7 +678,6 @@ mod tests {
|
||||
key: b"test_key".to_vec(),
|
||||
expect: b"test_expect".to_vec(),
|
||||
value: b"test_value".to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let compare_and_put: CompareAndPut = req.try_into().unwrap();
|
||||
@@ -837,7 +694,6 @@ mod tests {
|
||||
key: b"test_key".to_vec(),
|
||||
range_end: b"test_range_end".to_vec(),
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let delete: Delete = req.try_into().unwrap();
|
||||
@@ -851,7 +707,6 @@ mod tests {
|
||||
let req = MoveValueRequest {
|
||||
from_key: b"test_from_key".to_vec(),
|
||||
to_key: b"test_to_key".to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let move_value: MoveValue = req.try_into().unwrap();
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::KeyValue;
|
||||
use common_meta::rpc::KeyValue;
|
||||
|
||||
pub struct KvPair<'a>(&'a etcd_client::KeyValue);
|
||||
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{DeleteRangeRequest, KeyValue, RangeRequest};
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::service::store::kv::KvStore;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait KvStoreExt {
|
||||
/// Get the value by the given key.
|
||||
async fn get(&self, key: Vec<u8>) -> Result<Option<KeyValue>>;
|
||||
|
||||
/// Check if a key exists, it does not return the value.
|
||||
async fn exists(&self, key: Vec<u8>) -> Result<bool>;
|
||||
|
||||
/// Delete the value by the given key. If prev_kv is true,
|
||||
/// the previous key-value pairs will be returned.
|
||||
async fn delete(&self, key: Vec<u8>, prev_kv: bool) -> Result<Option<KeyValue>>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<T> KvStoreExt for T
|
||||
where
|
||||
T: KvStore + ?Sized,
|
||||
{
|
||||
async fn get(&self, key: Vec<u8>) -> Result<Option<KeyValue>> {
|
||||
let req = RangeRequest {
|
||||
key,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut kvs = self.range(req).await?.kvs;
|
||||
|
||||
Ok(kvs.pop())
|
||||
}
|
||||
|
||||
async fn exists(&self, key: Vec<u8>) -> Result<bool> {
|
||||
let req = RangeRequest {
|
||||
key,
|
||||
keys_only: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let kvs = self.range(req).await?.kvs;
|
||||
|
||||
Ok(!kvs.is_empty())
|
||||
}
|
||||
|
||||
async fn delete(&self, key: Vec<u8>, prev_kv: bool) -> Result<Option<KeyValue>> {
|
||||
let req = DeleteRangeRequest {
|
||||
key,
|
||||
prev_kv,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut prev_kvs = self.delete_range(req).await?.prev_kvs;
|
||||
|
||||
Ok(prev_kvs.pop())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::PutRequest;
|
||||
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
use crate::service::store::memory::MemStore;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get() {
|
||||
let mut in_mem = Arc::new(MemStore::new()) as KvStoreRef;
|
||||
|
||||
put_stats_to_store(&mut in_mem).await;
|
||||
|
||||
let kv = in_mem
|
||||
.get("test_key1".as_bytes().to_vec())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!("test_key1".as_bytes(), kv.key);
|
||||
assert_eq!("test_val1".as_bytes(), kv.value);
|
||||
|
||||
let kv = in_mem
|
||||
.get("test_key2".as_bytes().to_vec())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!("test_key2".as_bytes(), kv.key);
|
||||
assert_eq!("test_val2".as_bytes(), kv.value);
|
||||
|
||||
let may_kv = in_mem.get("test_key3".as_bytes().to_vec()).await.unwrap();
|
||||
|
||||
assert!(may_kv.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_exists() {
|
||||
let mut in_mem = Arc::new(MemStore::new()) as KvStoreRef;
|
||||
|
||||
put_stats_to_store(&mut in_mem).await;
|
||||
|
||||
assert!(in_mem
|
||||
.exists("test_key1".as_bytes().to_vec())
|
||||
.await
|
||||
.unwrap());
|
||||
assert!(in_mem
|
||||
.exists("test_key2".as_bytes().to_vec())
|
||||
.await
|
||||
.unwrap());
|
||||
assert!(!in_mem
|
||||
.exists("test_key3".as_bytes().to_vec())
|
||||
.await
|
||||
.unwrap());
|
||||
assert!(!in_mem.exists("test_key".as_bytes().to_vec()).await.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete() {
|
||||
let mut in_mem = Arc::new(MemStore::new()) as KvStoreRef;
|
||||
|
||||
let mut prev_kv = in_mem
|
||||
.delete("test_key1".as_bytes().to_vec(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(prev_kv.is_none());
|
||||
|
||||
put_stats_to_store(&mut in_mem).await;
|
||||
|
||||
assert!(in_mem
|
||||
.exists("test_key1".as_bytes().to_vec())
|
||||
.await
|
||||
.unwrap());
|
||||
|
||||
prev_kv = in_mem
|
||||
.delete("test_key1".as_bytes().to_vec(), true)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!("test_key1".as_bytes(), prev_kv.unwrap().key);
|
||||
}
|
||||
|
||||
async fn put_stats_to_store(store: &mut KvStoreRef) {
|
||||
assert!(store
|
||||
.put(PutRequest {
|
||||
key: "test_key1".as_bytes().to_vec(),
|
||||
value: "test_val1".as_bytes().to_vec(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
assert!(store
|
||||
.put(PutRequest {
|
||||
key: "test_key2".as_bytes().to_vec(),
|
||||
value: "test_val2".as_bytes().to_vec(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
@@ -14,38 +14,13 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse,
|
||||
};
|
||||
use common_meta::kv_backend::KvBackend;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::service::store::txn::TxnService;
|
||||
use crate::error::Error;
|
||||
|
||||
pub type KvStoreRef = Arc<dyn KvStore>;
|
||||
pub type KvStoreRef = Arc<dyn KvBackend<Error = Error>>;
|
||||
pub type ResettableKvStoreRef = Arc<dyn ResettableKvStore>;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait KvStore: TxnService {
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse>;
|
||||
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse>;
|
||||
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse>;
|
||||
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse>;
|
||||
|
||||
async fn batch_delete(&self, req: BatchDeleteRequest) -> Result<BatchDeleteResponse>;
|
||||
|
||||
async fn compare_and_put(&self, req: CompareAndPutRequest) -> Result<CompareAndPutResponse>;
|
||||
|
||||
async fn delete_range(&self, req: DeleteRangeRequest) -> Result<DeleteRangeResponse>;
|
||||
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse>;
|
||||
}
|
||||
|
||||
pub trait ResettableKvStore: KvStore {
|
||||
pub trait ResettableKvStore: KvBackend<Error = Error> {
|
||||
fn reset(&self);
|
||||
}
|
||||
|
||||
@@ -12,697 +12,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::btree_map::Entry;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::Range;
|
||||
use common_meta::kv_backend::memory::MemoryKvBackend;
|
||||
|
||||
use api::v1::meta::{
|
||||
BatchDeleteRequest, BatchDeleteResponse, BatchGetRequest, BatchGetResponse, BatchPutRequest,
|
||||
BatchPutResponse, CompareAndPutRequest, CompareAndPutResponse, DeleteRangeRequest,
|
||||
DeleteRangeResponse, KeyValue, MoveValueRequest, MoveValueResponse, PutRequest, PutResponse,
|
||||
RangeRequest, RangeResponse, ResponseHeader,
|
||||
};
|
||||
use common_telemetry::timer;
|
||||
use parking_lot::RwLock;
|
||||
use crate::error::Error;
|
||||
use crate::service::store::kv::ResettableKvStore;
|
||||
|
||||
use super::ext::KvStoreExt;
|
||||
use crate::error::Result;
|
||||
use crate::metrics::{METRIC_META_KV_REQUEST, METRIC_META_TXN_REQUEST};
|
||||
use crate::service::store::kv::{KvStore, ResettableKvStore};
|
||||
use crate::service::store::txn::{Txn, TxnOp, TxnOpResponse, TxnRequest, TxnResponse, TxnService};
|
||||
|
||||
pub struct MemStore {
|
||||
inner: RwLock<BTreeMap<Vec<u8>, Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Default for MemStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl MemStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
inner: RwLock::new(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub type MemStore = MemoryKvBackend<Error>;
|
||||
|
||||
impl ResettableKvStore for MemStore {
|
||||
fn reset(&self) {
|
||||
self.inner.write().clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl KvStore for MemStore {
|
||||
async fn range(&self, req: RangeRequest) -> Result<RangeResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "range"),]
|
||||
);
|
||||
|
||||
let RangeRequest {
|
||||
header,
|
||||
key,
|
||||
range_end,
|
||||
limit,
|
||||
keys_only,
|
||||
} = req;
|
||||
|
||||
let memory = self.inner.read();
|
||||
|
||||
let mut kvs = if range_end.is_empty() {
|
||||
memory.get_key_value(&key).map_or(vec![], |(k, v)| {
|
||||
vec![KeyValue {
|
||||
key: k.clone(),
|
||||
value: if keys_only { vec![] } else { v.clone() },
|
||||
}]
|
||||
})
|
||||
} else {
|
||||
let range = Range {
|
||||
start: key,
|
||||
end: range_end,
|
||||
};
|
||||
memory
|
||||
.range(range)
|
||||
.map(|kv| KeyValue {
|
||||
key: kv.0.clone(),
|
||||
value: if keys_only { vec![] } else { kv.1.clone() },
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let more = if limit > 0 {
|
||||
kvs.truncate(limit as usize);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(RangeResponse { header, kvs, more })
|
||||
}
|
||||
|
||||
async fn put(&self, req: PutRequest) -> Result<PutResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "put"),]
|
||||
);
|
||||
|
||||
let PutRequest {
|
||||
header,
|
||||
key,
|
||||
value,
|
||||
prev_kv,
|
||||
} = req;
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let prev_value = memory.insert(key.clone(), value);
|
||||
let prev_kv = if prev_kv {
|
||||
prev_value.map(|value| KeyValue { key, value })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(PutResponse { header, prev_kv })
|
||||
}
|
||||
|
||||
async fn batch_get(&self, req: BatchGetRequest) -> Result<BatchGetResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "batch_get"),]
|
||||
);
|
||||
|
||||
let BatchGetRequest { header, keys } = req;
|
||||
|
||||
let mut kvs = Vec::with_capacity(keys.len());
|
||||
for key in keys {
|
||||
if let Some(kv) = self.get(key).await? {
|
||||
kvs.push(kv);
|
||||
}
|
||||
}
|
||||
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(BatchGetResponse { header, kvs })
|
||||
}
|
||||
|
||||
async fn batch_put(&self, req: BatchPutRequest) -> Result<BatchPutResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "batch_put"),]
|
||||
);
|
||||
|
||||
let BatchPutRequest {
|
||||
header,
|
||||
kvs,
|
||||
prev_kv,
|
||||
} = req;
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let prev_kvs = if prev_kv {
|
||||
kvs.into_iter()
|
||||
.map(|kv| (kv.key.clone(), memory.insert(kv.key, kv.value)))
|
||||
.filter(|(_, v)| v.is_some())
|
||||
.map(|(key, value)| KeyValue {
|
||||
key,
|
||||
value: value.unwrap(),
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
for kv in kvs.into_iter() {
|
||||
let _ = memory.insert(kv.key, kv.value);
|
||||
}
|
||||
vec![]
|
||||
};
|
||||
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(BatchPutResponse { header, prev_kvs })
|
||||
}
|
||||
|
||||
async fn batch_delete(&self, req: BatchDeleteRequest) -> Result<BatchDeleteResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "batch_delete"),]
|
||||
);
|
||||
|
||||
let BatchDeleteRequest {
|
||||
header,
|
||||
keys,
|
||||
prev_kv,
|
||||
} = req;
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let prev_kvs = if prev_kv {
|
||||
keys.into_iter()
|
||||
.filter_map(|key| memory.remove(&key).map(|value| KeyValue { key, value }))
|
||||
.collect()
|
||||
} else {
|
||||
for key in keys.into_iter() {
|
||||
let _ = memory.remove(&key);
|
||||
}
|
||||
vec![]
|
||||
};
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(BatchDeleteResponse { header, prev_kvs })
|
||||
}
|
||||
|
||||
async fn compare_and_put(&self, req: CompareAndPutRequest) -> Result<CompareAndPutResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "compare_and_put"),]
|
||||
);
|
||||
|
||||
let CompareAndPutRequest {
|
||||
header,
|
||||
key,
|
||||
expect,
|
||||
value,
|
||||
} = req;
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let (success, prev_kv) = match memory.entry(key) {
|
||||
Entry::Vacant(e) => {
|
||||
let success = expect.is_empty();
|
||||
if success {
|
||||
let _ = 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 {
|
||||
let _ = e.insert(value);
|
||||
}
|
||||
(success, Some((key, prev_val)))
|
||||
}
|
||||
};
|
||||
|
||||
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));
|
||||
Ok(CompareAndPutResponse {
|
||||
header,
|
||||
success,
|
||||
prev_kv,
|
||||
})
|
||||
}
|
||||
|
||||
async fn delete_range(&self, req: DeleteRangeRequest) -> Result<DeleteRangeResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "deleteL_range"),]
|
||||
);
|
||||
|
||||
let DeleteRangeRequest {
|
||||
header,
|
||||
key,
|
||||
range_end,
|
||||
prev_kv,
|
||||
} = req;
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let prev_kvs = if range_end.is_empty() {
|
||||
let prev_val = memory.remove(&key);
|
||||
prev_val.map_or(vec![], |value| vec![KeyValue { key, value }])
|
||||
} else {
|
||||
let range = Range {
|
||||
start: key,
|
||||
end: range_end,
|
||||
};
|
||||
memory
|
||||
.drain_filter(|key, _| range.contains(key))
|
||||
.map(|(key, value)| KeyValue { key, value })
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let cluster_id = header.map_or(0, |h| h.cluster_id);
|
||||
let header = Some(ResponseHeader::success(cluster_id));
|
||||
Ok(DeleteRangeResponse {
|
||||
header,
|
||||
deleted: prev_kvs.len() as i64,
|
||||
prev_kvs: if prev_kv {
|
||||
prev_kvs
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async fn move_value(&self, req: MoveValueRequest) -> Result<MoveValueResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_KV_REQUEST,
|
||||
&[("target", "memory"), ("op", "move_value"),]
|
||||
);
|
||||
|
||||
let MoveValueRequest {
|
||||
header,
|
||||
from_key,
|
||||
to_key,
|
||||
} = req;
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let kv = match memory.remove(&from_key) {
|
||||
Some(v) => {
|
||||
let _ = memory.insert(to_key, v.clone());
|
||||
Some((from_key, v))
|
||||
}
|
||||
None => memory.get(&to_key).map(|v| (to_key, v.clone())),
|
||||
};
|
||||
|
||||
let kv = 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));
|
||||
Ok(MoveValueResponse { header, kv })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl TxnService for MemStore {
|
||||
async fn txn(&self, txn: Txn) -> Result<TxnResponse> {
|
||||
let _timer = timer!(
|
||||
METRIC_META_TXN_REQUEST,
|
||||
&[("target", "memory".to_string()), ("op", "txn".to_string()),]
|
||||
);
|
||||
|
||||
let TxnRequest {
|
||||
compare,
|
||||
success,
|
||||
failure,
|
||||
} = txn.into();
|
||||
|
||||
let mut memory = self.inner.write();
|
||||
|
||||
let succeeded = compare
|
||||
.iter()
|
||||
.all(|x| x.compare_with_value(memory.get(&x.key)));
|
||||
|
||||
let do_txn = |txn_op| match txn_op {
|
||||
TxnOp::Put(key, value) => {
|
||||
let prev_value = memory.insert(key.clone(), value);
|
||||
let prev_kv = prev_value.map(|value| KeyValue { key, value });
|
||||
let put_res = PutResponse {
|
||||
prev_kv,
|
||||
..Default::default()
|
||||
};
|
||||
TxnOpResponse::ResponsePut(put_res)
|
||||
}
|
||||
TxnOp::Get(key) => {
|
||||
let value = memory.get(&key);
|
||||
let kv = value.map(|value| KeyValue {
|
||||
key,
|
||||
value: value.clone(),
|
||||
});
|
||||
let get_res = RangeResponse {
|
||||
kvs: kv.map(|kv| vec![kv]).unwrap_or(vec![]),
|
||||
..Default::default()
|
||||
};
|
||||
TxnOpResponse::ResponseGet(get_res)
|
||||
}
|
||||
TxnOp::Delete(key) => {
|
||||
let prev_value = memory.remove(&key);
|
||||
let prev_kv = prev_value.map(|value| KeyValue { key, value });
|
||||
let delete_res = DeleteRangeResponse {
|
||||
prev_kvs: prev_kv.map(|kv| vec![kv]).unwrap_or(vec![]),
|
||||
..Default::default()
|
||||
};
|
||||
TxnOpResponse::ResponseDelete(delete_res)
|
||||
}
|
||||
};
|
||||
|
||||
let responses: Vec<_> = if succeeded {
|
||||
success.into_iter().map(do_txn).collect()
|
||||
} else {
|
||||
failure.into_iter().map(do_txn).collect()
|
||||
};
|
||||
|
||||
Ok(TxnResponse {
|
||||
succeeded,
|
||||
responses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use api::v1::meta::{
|
||||
BatchGetRequest, BatchPutRequest, CompareAndPutRequest, DeleteRangeRequest, KeyValue,
|
||||
MoveValueRequest, PutRequest, RangeRequest,
|
||||
};
|
||||
|
||||
use super::MemStore;
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStore;
|
||||
use crate::util;
|
||||
|
||||
async fn mock_mem_store_with_data() -> MemStore {
|
||||
let kv_store = MemStore::new();
|
||||
let kvs = mock_kvs();
|
||||
|
||||
assert!(kv_store
|
||||
.batch_put(BatchPutRequest {
|
||||
kvs,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
assert!(kv_store
|
||||
.put(PutRequest {
|
||||
key: b"key11".to_vec(),
|
||||
value: b"val11".to_vec(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.is_ok());
|
||||
|
||||
kv_store
|
||||
}
|
||||
|
||||
fn mock_kvs() -> Vec<KeyValue> {
|
||||
vec![
|
||||
KeyValue {
|
||||
key: b"key1".to_vec(),
|
||||
value: b"val1".to_vec(),
|
||||
},
|
||||
KeyValue {
|
||||
key: b"key2".to_vec(),
|
||||
value: b"val2".to_vec(),
|
||||
},
|
||||
KeyValue {
|
||||
key: b"key3".to_vec(),
|
||||
value: b"val3".to_vec(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_put() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let resp = kv_store
|
||||
.put(PutRequest {
|
||||
key: b"key11".to_vec(),
|
||||
value: b"val12".to_vec(),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(resp.prev_kv.is_none());
|
||||
|
||||
let resp = kv_store
|
||||
.put(PutRequest {
|
||||
key: b"key11".to_vec(),
|
||||
value: b"val13".to_vec(),
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(b"key11".as_slice(), resp.prev_kv.as_ref().unwrap().key);
|
||||
assert_eq!(b"val12".as_slice(), resp.prev_kv.as_ref().unwrap().value);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_range() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let key = b"key1".to_vec();
|
||||
let range_end = util::get_prefix_end_key(b"key1");
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key: key.clone(),
|
||||
range_end: range_end.clone(),
|
||||
limit: 0,
|
||||
keys_only: false,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, resp.kvs.len());
|
||||
assert_eq!(b"key1".as_slice(), resp.kvs[0].key);
|
||||
assert_eq!(b"val1".as_slice(), resp.kvs[0].value);
|
||||
assert_eq!(b"key11".as_slice(), resp.kvs[1].key);
|
||||
assert_eq!(b"val11".as_slice(), resp.kvs[1].value);
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key: key.clone(),
|
||||
range_end: range_end.clone(),
|
||||
limit: 0,
|
||||
keys_only: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, resp.kvs.len());
|
||||
assert_eq!(b"key1".as_slice(), resp.kvs[0].key);
|
||||
assert_eq!(b"".as_slice(), resp.kvs[0].value);
|
||||
assert_eq!(b"key11".as_slice(), resp.kvs[1].key);
|
||||
assert_eq!(b"".as_slice(), resp.kvs[1].value);
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key: key.clone(),
|
||||
limit: 0,
|
||||
keys_only: false,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, resp.kvs.len());
|
||||
assert_eq!(b"key1".as_slice(), resp.kvs[0].key);
|
||||
assert_eq!(b"val1".as_slice(), resp.kvs[0].value);
|
||||
|
||||
let resp = kv_store
|
||||
.range(RangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
limit: 1,
|
||||
keys_only: false,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(1, resp.kvs.len());
|
||||
assert_eq!(b"key1".as_slice(), resp.kvs[0].key);
|
||||
assert_eq!(b"val1".as_slice(), resp.kvs[0].value);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_batch_get() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let keys = vec![];
|
||||
let batch_resp = kv_store
|
||||
.batch_get(BatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(batch_resp.kvs.is_empty());
|
||||
|
||||
let keys = vec![b"key10".to_vec()];
|
||||
let batch_resp = kv_store
|
||||
.batch_get(BatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(batch_resp.kvs.is_empty());
|
||||
|
||||
let keys = vec![b"key1".to_vec(), b"key3".to_vec(), b"key4".to_vec()];
|
||||
let batch_resp = kv_store
|
||||
.batch_get(BatchGetRequest {
|
||||
keys,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(2, batch_resp.kvs.len());
|
||||
assert_eq!(b"key1".as_slice(), batch_resp.kvs[0].key);
|
||||
assert_eq!(b"val1".as_slice(), batch_resp.kvs[0].value);
|
||||
assert_eq!(b"key3".as_slice(), batch_resp.kvs[1].key);
|
||||
assert_eq!(b"val3".as_slice(), batch_resp.kvs[1].value);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_compare_and_put() {
|
||||
let kv_store = Arc::new(MemStore::new());
|
||||
let success = Arc::new(AtomicU8::new(0));
|
||||
|
||||
let mut joins = vec![];
|
||||
for _ in 0..20 {
|
||||
let kv_store_clone = kv_store.clone();
|
||||
let success_clone = success.clone();
|
||||
let join = tokio::spawn(async move {
|
||||
let req = CompareAndPutRequest {
|
||||
key: b"key".to_vec(),
|
||||
expect: vec![],
|
||||
value: b"val_new".to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
let resp = kv_store_clone.compare_and_put(req).await.unwrap();
|
||||
if resp.success {
|
||||
let _ = success_clone.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
});
|
||||
joins.push(join);
|
||||
}
|
||||
|
||||
for join in joins {
|
||||
join.await.unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(1, success.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_range() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let req = DeleteRangeRequest {
|
||||
key: b"key3".to_vec(),
|
||||
range_end: vec![],
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let resp = kv_store.delete_range(req).await.unwrap();
|
||||
assert_eq!(1, resp.prev_kvs.len());
|
||||
assert_eq!(b"key3".as_slice(), resp.prev_kvs[0].key);
|
||||
assert_eq!(b"val3".as_slice(), resp.prev_kvs[0].value);
|
||||
|
||||
let get_resp = kv_store.get(b"key3".to_vec()).await.unwrap();
|
||||
assert!(get_resp.is_none());
|
||||
|
||||
let req = DeleteRangeRequest {
|
||||
key: b"key2".to_vec(),
|
||||
range_end: vec![],
|
||||
prev_kv: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let resp = kv_store.delete_range(req).await.unwrap();
|
||||
assert!(resp.prev_kvs.is_empty());
|
||||
|
||||
let get_resp = kv_store.get(b"key2".to_vec()).await.unwrap();
|
||||
assert!(get_resp.is_none());
|
||||
|
||||
let key = b"key1".to_vec();
|
||||
let range_end = util::get_prefix_end_key(b"key1");
|
||||
|
||||
let req = DeleteRangeRequest {
|
||||
key: key.clone(),
|
||||
range_end: range_end.clone(),
|
||||
prev_kv: true,
|
||||
..Default::default()
|
||||
};
|
||||
let resp = kv_store.delete_range(req).await.unwrap();
|
||||
assert_eq!(2, resp.prev_kvs.len());
|
||||
|
||||
let req = RangeRequest {
|
||||
key,
|
||||
range_end,
|
||||
..Default::default()
|
||||
};
|
||||
let resp = kv_store.range(req).await.unwrap();
|
||||
assert!(resp.kvs.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_move_value() {
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let req = MoveValueRequest {
|
||||
from_key: b"key1".to_vec(),
|
||||
to_key: b"key111".to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let resp = kv_store.move_value(req).await.unwrap();
|
||||
assert_eq!(b"key1".as_slice(), resp.kv.as_ref().unwrap().key);
|
||||
assert_eq!(b"val1".as_slice(), resp.kv.as_ref().unwrap().value);
|
||||
|
||||
let kv_store = mock_mem_store_with_data().await;
|
||||
|
||||
let req = MoveValueRequest {
|
||||
from_key: b"notexistkey".to_vec(),
|
||||
to_key: b"key222".to_vec(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let resp = kv_store.move_value(req).await.unwrap();
|
||||
assert!(resp.kv.is_none());
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,25 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api::v1::meta::{MoveValueRequest, PutRequest, TableRouteValue};
|
||||
use api::v1::meta::TableRouteValue;
|
||||
use catalog::helper::{TableGlobalKey, TableGlobalValue};
|
||||
use common_meta::key::TableRouteKey;
|
||||
use common_meta::rpc::store::{BatchGetRequest, BatchGetResponse};
|
||||
use common_meta::rpc::store::{BatchGetRequest, MoveValueRequest, PutRequest};
|
||||
use common_telemetry::warn;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use table::engine::TableReference;
|
||||
|
||||
use crate::error::{
|
||||
ConvertProtoDataSnafu, DecodeTableRouteSnafu, InvalidCatalogValueSnafu, Result,
|
||||
TableNotFoundSnafu, TableRouteNotFoundSnafu,
|
||||
DecodeTableRouteSnafu, InvalidCatalogValueSnafu, Result, TableNotFoundSnafu,
|
||||
TableRouteNotFoundSnafu,
|
||||
};
|
||||
use crate::service::store::ext::KvStoreExt;
|
||||
use crate::service::store::kv::KvStoreRef;
|
||||
|
||||
pub async fn get_table_global_value(
|
||||
kv_store: &KvStoreRef,
|
||||
key: &TableGlobalKey,
|
||||
) -> Result<Option<TableGlobalValue>> {
|
||||
let kv = kv_store.get(key.to_raw_key()).await?;
|
||||
let kv = kv_store.get(&key.to_raw_key()).await?;
|
||||
kv.map(|kv| TableGlobalValue::from_bytes(kv.value).context(InvalidCatalogValueSnafu))
|
||||
.transpose()
|
||||
}
|
||||
@@ -43,13 +44,8 @@ pub(crate) async fn batch_get_table_global_value(
|
||||
let req = BatchGetRequest {
|
||||
keys: keys.iter().map(|x| x.to_raw_key()).collect::<Vec<_>>(),
|
||||
};
|
||||
let mut resp: BatchGetResponse = kv_store
|
||||
.batch_get(req.into())
|
||||
.await?
|
||||
.try_into()
|
||||
.context(ConvertProtoDataSnafu)?;
|
||||
let kvs = kv_store.batch_get(req).await?.kvs;
|
||||
|
||||
let kvs = resp.take_kvs();
|
||||
let mut result = HashMap::with_capacity(kvs.len());
|
||||
for kv in kvs {
|
||||
let key = TableGlobalKey::try_from_raw_key(kv.key()).context(InvalidCatalogValueSnafu)?;
|
||||
@@ -71,7 +67,6 @@ pub(crate) async fn put_table_global_value(
|
||||
value: &TableGlobalValue,
|
||||
) -> Result<()> {
|
||||
let req = PutRequest {
|
||||
header: None,
|
||||
key: key.to_raw_key(),
|
||||
value: value.as_bytes().context(InvalidCatalogValueSnafu)?,
|
||||
prev_kv: false,
|
||||
@@ -99,13 +94,12 @@ pub(crate) async fn get_table_route_value(
|
||||
key: &TableRouteKey<'_>,
|
||||
) -> Result<TableRouteValue> {
|
||||
let kv = kv_store
|
||||
.get(key.key().into_bytes())
|
||||
.get(key.to_string().as_bytes())
|
||||
.await?
|
||||
.with_context(|| TableRouteNotFoundSnafu { key: key.key() })?;
|
||||
kv.value
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.context(DecodeTableRouteSnafu)
|
||||
.with_context(|| TableRouteNotFoundSnafu {
|
||||
key: key.to_string(),
|
||||
})?;
|
||||
kv.value().try_into().context(DecodeTableRouteSnafu)
|
||||
}
|
||||
|
||||
pub(crate) async fn put_table_route_value(
|
||||
@@ -114,8 +108,7 @@ pub(crate) async fn put_table_route_value(
|
||||
value: TableRouteValue,
|
||||
) -> Result<()> {
|
||||
let req = PutRequest {
|
||||
header: None,
|
||||
key: key.key().into_bytes(),
|
||||
key: key.to_string().into_bytes(),
|
||||
value: value.into(),
|
||||
prev_kv: false,
|
||||
};
|
||||
@@ -127,11 +120,13 @@ pub(crate) async fn remove_table_route_value(
|
||||
kv_store: &KvStoreRef,
|
||||
key: &TableRouteKey<'_>,
|
||||
) -> Result<(Vec<u8>, TableRouteValue)> {
|
||||
let from_key = key.key().into_bytes();
|
||||
let from_key = key.to_string().into_bytes();
|
||||
let to_key = key.removed_key().into_bytes();
|
||||
let v = move_value(kv_store, from_key, to_key)
|
||||
.await?
|
||||
.context(TableRouteNotFoundSnafu { key: key.key() })?;
|
||||
.context(TableRouteNotFoundSnafu {
|
||||
key: key.to_string(),
|
||||
})?;
|
||||
let trv: TableRouteValue = v.1.as_slice().try_into().context(DecodeTableRouteSnafu)?;
|
||||
|
||||
Ok((v.0, trv))
|
||||
@@ -144,14 +139,65 @@ async fn move_value(
|
||||
) -> Result<Option<(Vec<u8>, Vec<u8>)>> {
|
||||
let from_key = from_key.into();
|
||||
let to_key = to_key.into();
|
||||
let move_req = MoveValueRequest {
|
||||
from_key,
|
||||
to_key,
|
||||
..Default::default()
|
||||
};
|
||||
let move_req = MoveValueRequest { from_key, to_key };
|
||||
let res = kv_store.move_value(move_req).await?;
|
||||
|
||||
Ok(res.kv.map(|kv| (kv.key, kv.value)))
|
||||
Ok(res.0.map(Into::into))
|
||||
}
|
||||
|
||||
pub(crate) fn table_route_key(table_id: u32, t: &TableGlobalKey) -> TableRouteKey<'_> {
|
||||
TableRouteKey {
|
||||
table_id,
|
||||
catalog_name: &t.catalog_name,
|
||||
schema_name: &t.schema_name,
|
||||
table_name: &t.table_name,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_table(
|
||||
kv_store: &KvStoreRef,
|
||||
table_ref: TableReference<'_>,
|
||||
) -> Result<Option<(TableGlobalValue, TableRouteValue)>> {
|
||||
let tgk = TableGlobalKey {
|
||||
catalog_name: table_ref.catalog.to_string(),
|
||||
schema_name: table_ref.schema.to_string(),
|
||||
table_name: table_ref.table.to_string(),
|
||||
};
|
||||
|
||||
let tgv = get_table_global_value(kv_store, &tgk).await?;
|
||||
|
||||
if let Some(tgv) = tgv {
|
||||
let trk = table_route_key(tgv.table_id(), &tgk);
|
||||
let trv = get_table_route_value(kv_store, &trk).await?;
|
||||
|
||||
return Ok(Some((tgv, trv)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub(crate) 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 tgk in keys {
|
||||
let tgv = get_table_global_value(kv_store, &tgk).await?;
|
||||
if tgv.is_none() {
|
||||
warn!("Table global value is absent: {}", tgk);
|
||||
continue;
|
||||
}
|
||||
let tgv = tgv.unwrap();
|
||||
|
||||
let trk = table_route_key(tgv.table_id(), &tgk);
|
||||
let trv = get_table_route_value(kv_store, &trk).await?;
|
||||
|
||||
tables.push((tgv, trv));
|
||||
}
|
||||
|
||||
Ok(tables)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -25,9 +25,9 @@ use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, ResultExt};
|
||||
use store_api::storage::{
|
||||
ColumnId, CompactionStrategy, CreateOptions, EngineContext, OpenOptions,
|
||||
RegionDescriptorBuilder, RegionNumber, StorageEngine,
|
||||
RegionDescriptorBuilder, RegionId, RegionNumber, StorageEngine,
|
||||
};
|
||||
use table::engine::{region_id, table_dir};
|
||||
use table::engine::table_dir;
|
||||
use table::metadata::{TableInfoBuilder, TableMetaBuilder, TableType};
|
||||
use table::requests::CreateTableRequest;
|
||||
use table::TableRef;
|
||||
@@ -283,7 +283,7 @@ impl<S: StorageEngine> TableCreator<S> {
|
||||
}
|
||||
|
||||
// We need to create that region.
|
||||
let region_id = region_id(self.data.request.id, *number);
|
||||
let region_id = RegionId::new(self.data.request.id, *number);
|
||||
let region_desc = RegionDescriptorBuilder::default()
|
||||
.id(region_id)
|
||||
.name(region_name.clone())
|
||||
|
||||
@@ -30,7 +30,6 @@ use storage::region::RegionImpl;
|
||||
use storage::EngineImpl;
|
||||
use store_api::manifest::Manifest;
|
||||
use store_api::storage::{ReadContext, ScanRequest};
|
||||
use table::engine::region_id;
|
||||
use table::metadata::TableType;
|
||||
use table::requests::{
|
||||
AddColumnRequest, AlterKind, DeleteRequest, FlushTableRequest, TableOptions,
|
||||
@@ -525,16 +524,6 @@ async fn test_open_table() {
|
||||
assert_eq!(reopened.manifest().last_version(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_region_id() {
|
||||
assert_eq!(1, region_id(0, 1));
|
||||
assert_eq!(4294967296, region_id(1, 0));
|
||||
assert_eq!(4294967297, region_id(1, 1));
|
||||
assert_eq!(4294967396, region_id(1, 100));
|
||||
assert_eq!(8589934602, region_id(2, 10));
|
||||
assert_eq!(18446744069414584330, region_id(u32::MAX, 10));
|
||||
}
|
||||
|
||||
fn new_add_columns_req(
|
||||
table_id: TableId,
|
||||
new_tag: &ColumnSchema,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user