mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-01-08 22:32:55 +00:00
Compare commits
36 Commits
v0.2.0-nig
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7d2ede4a4 | ||
|
|
df6ebbf934 | ||
|
|
719fbf2f3a | ||
|
|
cc14dea913 | ||
|
|
cc7c313937 | ||
|
|
a6e41cdd7b | ||
|
|
a5771e2ec3 | ||
|
|
68e64a6ce9 | ||
|
|
90cd3bb5c9 | ||
|
|
bea37e30d8 | ||
|
|
d988b43996 | ||
|
|
0fc816fb0c | ||
|
|
43391e0162 | ||
|
|
3e7f7e3e8d | ||
|
|
0819582a26 | ||
|
|
9fa871a3fa | ||
|
|
76640402ba | ||
|
|
c20dbda598 | ||
|
|
33dbf7264f | ||
|
|
716bde8f04 | ||
|
|
9f2825495d | ||
|
|
ae21c1c1e9 | ||
|
|
6b6617f9cb | ||
|
|
d5f0ba4ad9 | ||
|
|
e021da2eee | ||
|
|
fac9c17a9b | ||
|
|
dfc2a45de1 | ||
|
|
3e8ec8b73a | ||
|
|
a90798a2c1 | ||
|
|
f5cf5685cc | ||
|
|
1a21a6ea41 | ||
|
|
09f003d01d | ||
|
|
29c6155ae3 | ||
|
|
804348966d | ||
|
|
b7bdee6de9 | ||
|
|
c850e9695a |
@@ -3,3 +3,13 @@ linker = "aarch64-linux-gnu-gcc"
|
||||
|
||||
[alias]
|
||||
sqlness = "run --bin sqlness-runner --"
|
||||
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
# lints
|
||||
# TODO: use lint configuration in cargo https://github.com/rust-lang/cargo/issues/5034
|
||||
"-Wclippy::print_stdout",
|
||||
"-Wclippy::print_stderr",
|
||||
"-Wclippy::implicit_clone",
|
||||
]
|
||||
|
||||
2
.github/workflows/develop.yml
vendored
2
.github/workflows/develop.yml
vendored
@@ -183,7 +183,7 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Run cargo clippy
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings -D clippy::print_stdout -D clippy::print_stderr
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
coverage:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
46
.github/workflows/release.yml
vendored
46
.github/workflows/release.yml
vendored
@@ -341,49 +341,3 @@ jobs:
|
||||
name: "Release ${{ github.ref_name }}"
|
||||
files: |
|
||||
**/greptime-*
|
||||
|
||||
docker-push-uhub:
|
||||
name: Push docker image to UCloud Container Registry
|
||||
needs: [docker]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'GreptimeTeam/greptimedb' && github.event_name != 'workflow_dispatch'
|
||||
# Push to uhub may fail(500 error), but we don't want to block the release process. The failed job will be retried manually.
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to UCloud Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: uhub.service.ucloud.cn
|
||||
username: ${{ secrets.UCLOUD_USERNAME }}
|
||||
password: ${{ secrets.UCLOUD_PASSWORD }}
|
||||
|
||||
- name: Configure scheduled build image tag # the tag would be ${SCHEDULED_BUILD_VERSION_PREFIX}-YYYYMMDD-${SCHEDULED_PERIOD}
|
||||
shell: bash
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
buildTime=`date "+%Y%m%d"`
|
||||
SCHEDULED_BUILD_VERSION=${{ env.SCHEDULED_BUILD_VERSION_PREFIX }}-$buildTime-${{ env.SCHEDULED_PERIOD }}
|
||||
echo "IMAGE_TAG=${SCHEDULED_BUILD_VERSION:1}" >> $GITHUB_ENV
|
||||
|
||||
- name: Configure tag # If the release tag is v0.1.0, then the image version tag will be 0.1.0.
|
||||
shell: bash
|
||||
if: github.event_name != 'schedule'
|
||||
run: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
echo "IMAGE_TAG=${VERSION:1}" >> $GITHUB_ENV
|
||||
|
||||
- name: Push image to uhub # Use 'docker buildx imagetools create' to create a new image base on source image.
|
||||
run: |
|
||||
docker buildx imagetools create \
|
||||
--tag uhub.service.ucloud.cn/greptime/greptimedb:latest \
|
||||
--tag uhub.service.ucloud.cn/greptime/greptimedb:${{ env.IMAGE_TAG }} \
|
||||
greptime/greptimedb:${{ env.IMAGE_TAG }}
|
||||
|
||||
@@ -51,7 +51,7 @@ GreptimeDB uses the [Apache 2.0 license](https://github.com/GreptimeTeam/greptim
|
||||
- To ensure that community is free and confident in its ability to use your contributions, please sign the Contributor License Agreement (CLA) which will be incorporated in the pull request process.
|
||||
- Make sure all your codes are formatted and follow the [coding style](https://pingcap.github.io/style-guide/rust/).
|
||||
- Make sure all unit tests are passed (using `cargo test --workspace` or [nextest](https://nexte.st/index.html) `cargo nextest run`).
|
||||
- Make sure all clippy warnings are fixed (you can check it locally by running `cargo clippy --workspace --all-targets -- -D warnings -D clippy::print_stdout -D clippy::print_stderr`).
|
||||
- Make sure all clippy warnings are fixed (you can check it locally by running `cargo clippy --workspace --all-targets -- -D warnings`).
|
||||
|
||||
#### `pre-commit` Hooks
|
||||
|
||||
|
||||
607
Cargo.lock
generated
607
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -24,6 +24,7 @@ members = [
|
||||
"src/common/time",
|
||||
"src/datanode",
|
||||
"src/datatypes",
|
||||
"src/file-table-engine",
|
||||
"src/frontend",
|
||||
"src/log-store",
|
||||
"src/meta-client",
|
||||
@@ -46,38 +47,38 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
arrow = { version = "36.0" }
|
||||
arrow-array = "36.0"
|
||||
arrow-flight = "36.0"
|
||||
arrow-schema = { version = "36.0", features = ["serde"] }
|
||||
arrow = { version = "37.0" }
|
||||
arrow-array = "37.0"
|
||||
arrow-flight = "37.0"
|
||||
arrow-schema = { version = "37.0", features = ["serde"] }
|
||||
async-stream = "0.3"
|
||||
async-trait = "0.1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
datafusion = { git = "https://github.com/apache/arrow-datafusion.git", rev = "21bf4ffccadfeea824ab6e29c0b872930d0e190a" }
|
||||
datafusion-common = { git = "https://github.com/apache/arrow-datafusion.git", rev = "21bf4ffccadfeea824ab6e29c0b872930d0e190a" }
|
||||
datafusion-expr = { git = "https://github.com/apache/arrow-datafusion.git", rev = "21bf4ffccadfeea824ab6e29c0b872930d0e190a" }
|
||||
datafusion-optimizer = { git = "https://github.com/apache/arrow-datafusion.git", rev = "21bf4ffccadfeea824ab6e29c0b872930d0e190a" }
|
||||
datafusion-physical-expr = { git = "https://github.com/apache/arrow-datafusion.git", rev = "21bf4ffccadfeea824ab6e29c0b872930d0e190a" }
|
||||
datafusion-sql = { git = "https://github.com/apache/arrow-datafusion.git", rev = "21bf4ffccadfeea824ab6e29c0b872930d0e190a" }
|
||||
datafusion = { git = "https://github.com/apache/arrow-datafusion.git", rev = "74a778ca6016a853a3c3add3fa8c6f12f4fe4561" }
|
||||
datafusion-common = { git = "https://github.com/apache/arrow-datafusion.git", rev = "74a778ca6016a853a3c3add3fa8c6f12f4fe4561" }
|
||||
datafusion-expr = { git = "https://github.com/apache/arrow-datafusion.git", rev = "74a778ca6016a853a3c3add3fa8c6f12f4fe4561" }
|
||||
datafusion-optimizer = { git = "https://github.com/apache/arrow-datafusion.git", rev = "74a778ca6016a853a3c3add3fa8c6f12f4fe4561" }
|
||||
datafusion-physical-expr = { git = "https://github.com/apache/arrow-datafusion.git", rev = "74a778ca6016a853a3c3add3fa8c6f12f4fe4561" }
|
||||
datafusion-sql = { git = "https://github.com/apache/arrow-datafusion.git", rev = "74a778ca6016a853a3c3add3fa8c6f12f4fe4561" }
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
parquet = "36.0"
|
||||
parquet = "37.0"
|
||||
paste = "1.0"
|
||||
prost = "0.11"
|
||||
rand = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
sqlparser = "0.32"
|
||||
sqlparser = "0.33"
|
||||
tempfile = "3"
|
||||
tokio = { version = "1.24.2", features = ["full"] }
|
||||
tokio-util = "0.7"
|
||||
tonic = { version = "0.8", features = ["tls"] }
|
||||
tokio-util = { version = "0.7", features = ["io-util"] }
|
||||
tonic = { version = "0.9", features = ["tls"] }
|
||||
uuid = { version = "1", features = ["serde", "v4", "fast-rng"] }
|
||||
|
||||
[profile.release]
|
||||
|
||||
6
Makefile
6
Makefile
@@ -21,6 +21,10 @@ fmt: ## Format all the Rust code.
|
||||
|
||||
.PHONY: fmt-toml
|
||||
fmt-toml: ## Format all TOML files.
|
||||
taplo format --option "indent_string= "
|
||||
|
||||
.PHONY: check-toml
|
||||
check-toml: ## Check all TOML files.
|
||||
taplo format --check --option "indent_string= "
|
||||
|
||||
.PHONY: docker-image
|
||||
@@ -47,7 +51,7 @@ check: ## Cargo check all the targets.
|
||||
|
||||
.PHONY: clippy
|
||||
clippy: ## Check clippy rules.
|
||||
cargo clippy --workspace --all-targets -- -D warnings -D clippy::print_stdout -D clippy::print_stderr
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
.PHONY: fmt-check
|
||||
fmt-check: ## Check code format.
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
<a href="https://twitter.com/greptime"><img src="https://img.shields.io/badge/twitter-follow_us-1d9bf0.svg"></a>
|
||||
|
||||
<a href="https://www.linkedin.com/company/greptime/"><img src="https://img.shields.io/badge/linkedin-connect_with_us-0a66c2.svg"></a>
|
||||
|
||||
<a href="https://greptime.com/slack"><img src="https://img.shields.io/badge/slack-GreptimeDB-0abd59?logo=slack" alt="slack" /></a>
|
||||
</p>
|
||||
|
||||
## What is GreptimeDB
|
||||
|
||||
@@ -128,10 +128,10 @@ fn convert_record_batch(record_batch: RecordBatch) -> (Vec<Column>, u32) {
|
||||
let (values, datatype) = build_values(array);
|
||||
|
||||
let column = Column {
|
||||
column_name: field.name().to_owned(),
|
||||
column_name: field.name().clone(),
|
||||
values: Some(values),
|
||||
null_mask: array
|
||||
.data()
|
||||
.to_data()
|
||||
.nulls()
|
||||
.map(|bitmap| bitmap.buffer().as_slice().to_vec())
|
||||
.unwrap_or_default(),
|
||||
@@ -225,7 +225,7 @@ fn build_values(column: &ArrayRef) -> (Values, ColumnDataType) {
|
||||
| DataType::FixedSizeList(_, _)
|
||||
| DataType::LargeList(_)
|
||||
| DataType::Struct(_)
|
||||
| DataType::Union(_, _, _)
|
||||
| DataType::Union(_, _)
|
||||
| DataType::Dictionary(_, _)
|
||||
| DataType::Decimal128(_, _)
|
||||
| DataType::Decimal256(_, _)
|
||||
|
||||
74
docs/how-to/how-to-implement-sql-statement.md
Normal file
74
docs/how-to/how-to-implement-sql-statement.md
Normal file
@@ -0,0 +1,74 @@
|
||||
This document introduces how to implement SQL statements in GreptimeDB.
|
||||
|
||||
The execution entry point for SQL statements locates at Frontend Instance. You can see it has
|
||||
implemented `SqlQueryHandler`:
|
||||
|
||||
```rust
|
||||
impl SqlQueryHandler for Instance {
|
||||
type Error = Error;
|
||||
|
||||
async fn do_query(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Normally, when a SQL query arrives at GreptimeDB, the `do_query` method will be called. After some parsing work, the SQL
|
||||
will be feed into `StatementExecutor`:
|
||||
|
||||
```rust
|
||||
// in Frontend Instance:
|
||||
self.statement_executor.execute_sql(stmt, query_ctx).await
|
||||
```
|
||||
|
||||
That's where we handle our SQL statements. You can just create a new match arm for your statement there, then the
|
||||
statement is implemented for both GreptimeDB Standalone and Cluster. You can see how `DESCRIBE TABLE` is implemented as
|
||||
an example.
|
||||
|
||||
Now, what if the statements should be handled differently for GreptimeDB Standalone and Cluster? You can see there's
|
||||
a `SqlStatementExecutor` field in `StatementExecutor`. Each GreptimeDB Standalone and Cluster has its own implementation
|
||||
of `SqlStatementExecutor`. If you are going to implement the statements differently in the two mode (
|
||||
like `CREATE TABLE`), you have to implement them in their own `SqlStatementExecutor`s.
|
||||
|
||||
Summarize as the diagram below:
|
||||
|
||||
```text
|
||||
SQL query
|
||||
|
|
||||
v
|
||||
+---------------------------+
|
||||
| SqlQueryHandler::do_query |
|
||||
+---------------------------+
|
||||
|
|
||||
| SQL parsing
|
||||
v
|
||||
+--------------------------------+
|
||||
| StatementExecutor::execute_sql |
|
||||
+--------------------------------+
|
||||
|
|
||||
| SQL execution
|
||||
v
|
||||
+----------------------------------+
|
||||
| commonly handled statements like |
|
||||
| "plan_exec" for selection or |
|
||||
+----------------------------------+
|
||||
| |
|
||||
For Standalone | | For Cluster
|
||||
v v
|
||||
+---------------------------+ +---------------------------+
|
||||
| SqlStatementExecutor impl | | SqlStatementExecutor impl |
|
||||
| in Datanode Instance | | in Frontend DistInstance |
|
||||
+---------------------------+ +---------------------------+
|
||||
```
|
||||
|
||||
Note that some SQL statements can be executed in our QueryEngine, in the form of `LogicalPlan`. You can follow the
|
||||
invocation path down to the `QueryEngine` implementation from `StatementExecutor::plan_exec`. For now, there's only
|
||||
one `DatafusionQueryEngine` for both GreptimeDB Standalone and Cluster. That lone query engine works for both modes is
|
||||
because GreptimeDB read/write data through `Table` trait, and each mode has its own `Table` implementation.
|
||||
|
||||
We don't have any bias towards whether statements should be handled in query engine or `StatementExecutor`. You can
|
||||
implement one kind of statement in both places. For example, `Insert` with selection is handled in query engine, because
|
||||
we can easily do the query part there. However, `Insert` without selection is not, for the cost of parsing statement
|
||||
to `LogicalPlan` is not neglectable. So generally if the SQL query is simple enough, you can handle it
|
||||
in `StatementExecutor`; otherwise if it is complex or has some part of selection, it should be parsed to `LogicalPlan`
|
||||
and handled in query engine.
|
||||
@@ -10,10 +10,10 @@ common-base = { path = "../common/base" }
|
||||
common-error = { path = "../common/error" }
|
||||
common-time = { path = "../common/time" }
|
||||
datatypes = { path = "../datatypes" }
|
||||
greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "59afacdae59eae4241cfaf851021361caaeaed21" }
|
||||
greptime-proto = { git = "https://github.com/GreptimeTeam/greptime-proto.git", rev = "0bebe5f69c91cdfbce85cb8f45f9fcd28185261c" }
|
||||
prost.workspace = true
|
||||
snafu = { version = "0.7", features = ["backtraces"] }
|
||||
tonic.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.8"
|
||||
tonic-build = "0.9"
|
||||
|
||||
@@ -419,6 +419,13 @@ impl CatalogManager for LocalCatalogManager {
|
||||
schema: schema_name,
|
||||
})?;
|
||||
|
||||
let _lock = self.register_lock.lock().await;
|
||||
ensure!(
|
||||
!schema.table_exist(&request.new_table_name)?,
|
||||
TableExistsSnafu {
|
||||
table: &request.new_table_name
|
||||
}
|
||||
);
|
||||
let old_table = schema
|
||||
.table(&request.table_name)
|
||||
.await?
|
||||
@@ -437,9 +444,11 @@ impl CatalogManager for LocalCatalogManager {
|
||||
engine,
|
||||
)
|
||||
.await?;
|
||||
Ok(schema
|
||||
.rename_table(&request.table_name, request.new_table_name)
|
||||
.is_ok())
|
||||
|
||||
let renamed = schema
|
||||
.rename_table(&request.table_name, request.new_table_name.clone())
|
||||
.is_ok();
|
||||
Ok(renamed)
|
||||
}
|
||||
|
||||
async fn deregister_table(&self, request: DeregisterTableRequest) -> Result<bool> {
|
||||
|
||||
@@ -324,16 +324,20 @@ impl SchemaProvider for MemorySchemaProvider {
|
||||
|
||||
fn rename_table(&self, name: &str, new_name: String) -> Result<TableRef> {
|
||||
let mut tables = self.tables.write().unwrap();
|
||||
if tables.get(name).is_some() {
|
||||
let table = tables.remove(name).unwrap();
|
||||
tables.insert(new_name, table.clone());
|
||||
Ok(table)
|
||||
} else {
|
||||
TableNotFoundSnafu {
|
||||
let Some(table) = tables.remove(name) else {
|
||||
return TableNotFoundSnafu {
|
||||
table_info: name.to_string(),
|
||||
}
|
||||
.fail()?
|
||||
}
|
||||
.fail()?;
|
||||
};
|
||||
let e = match tables.entry(new_name) {
|
||||
Entry::Vacant(e) => e,
|
||||
Entry::Occupied(e) => {
|
||||
return TableExistsSnafu { table: e.key() }.fail();
|
||||
}
|
||||
};
|
||||
e.insert(table.clone());
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn deregister_table(&self, name: &str) -> Result<Option<TableRef>> {
|
||||
@@ -459,7 +463,7 @@ mod tests {
|
||||
assert!(schema.table_exist(table_name).unwrap());
|
||||
|
||||
// rename table
|
||||
let new_table_name = "numbers";
|
||||
let new_table_name = "numbers_new";
|
||||
let rename_table_req = RenameTableRequest {
|
||||
catalog: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
|
||||
@@ -33,7 +33,6 @@ use table::engine::manager::TableEngineManagerRef;
|
||||
use table::engine::EngineContext;
|
||||
use table::metadata::TableId;
|
||||
use table::requests::{CreateTableRequest, OpenTableRequest};
|
||||
use table::table::numbers::NumbersTable;
|
||||
use table::TableRef;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
@@ -423,15 +422,6 @@ impl CatalogManager for RemoteCatalogManager {
|
||||
})?;
|
||||
handle_system_table_request(self, engine, &mut system_table_requests).await?;
|
||||
info!("All system table opened");
|
||||
|
||||
self.catalog(DEFAULT_CATALOG_NAME)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.schema(DEFAULT_SCHEMA_NAME)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.register_table("numbers".to_string(), Arc::new(NumbersTable::default()))
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -173,7 +173,6 @@ mod tests {
|
||||
.schema(DEFAULT_SCHEMA_NAME)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(vec!["numbers"], default_schema.table_names().unwrap());
|
||||
|
||||
// register a new table with an nonexistent catalog
|
||||
let catalog_name = DEFAULT_CATALOG_NAME.to_string();
|
||||
@@ -209,14 +208,7 @@ mod tests {
|
||||
table,
|
||||
};
|
||||
assert!(catalog_manager.register_table(reg_req).await.unwrap());
|
||||
assert_eq!(
|
||||
HashSet::from([table_name, "numbers".to_string()]),
|
||||
default_schema
|
||||
.table_names()
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>()
|
||||
);
|
||||
assert_eq!(vec![table_name], default_schema.table_names().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -27,3 +27,4 @@ pub const SYSTEM_CATALOG_TABLE_ID: u32 = 0;
|
||||
pub const SCRIPTS_TABLE_ID: u32 = 1;
|
||||
|
||||
pub const MITO_ENGINE: &str = "mito";
|
||||
pub const IMMUTABLE_FILE_ENGINE: &str = "file";
|
||||
|
||||
@@ -5,9 +5,24 @@ edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
arrow.workspace = true
|
||||
arrow-schema.workspace = true
|
||||
async-compression = { version = "0.3", features = [
|
||||
"bzip2",
|
||||
"gzip",
|
||||
"xz",
|
||||
"zstd",
|
||||
"futures-io",
|
||||
"tokio",
|
||||
] }
|
||||
async-trait.workspace = true
|
||||
common-error = { path = "../error" }
|
||||
common-runtime = { path = "../runtime" }
|
||||
datafusion.workspace = true
|
||||
futures.workspace = true
|
||||
object-store = { path = "../../object-store" }
|
||||
regex = "1.7"
|
||||
snafu.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
url = "2.3"
|
||||
|
||||
84
src/common/datasource/src/compression.rs
Normal file
84
src/common/datasource/src/compression.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_compression::tokio::bufread::{BzDecoder, GzipDecoder, XzDecoder, ZstdDecoder};
|
||||
use tokio::io::{AsyncRead, BufReader};
|
||||
|
||||
use crate::error::{self, Error, Result};
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum CompressionType {
|
||||
/// Gzip-ed file
|
||||
GZIP,
|
||||
/// Bzip2-ed file
|
||||
BZIP2,
|
||||
/// Xz-ed file (liblzma)
|
||||
XZ,
|
||||
/// Zstd-ed file,
|
||||
ZSTD,
|
||||
/// Uncompressed file
|
||||
UNCOMPRESSED,
|
||||
}
|
||||
|
||||
impl FromStr for CompressionType {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let s = s.to_uppercase();
|
||||
match s.as_str() {
|
||||
"GZIP" | "GZ" => Ok(Self::GZIP),
|
||||
"BZIP2" | "BZ2" => Ok(Self::BZIP2),
|
||||
"XZ" => Ok(Self::XZ),
|
||||
"ZST" | "ZSTD" => Ok(Self::ZSTD),
|
||||
"" => Ok(Self::UNCOMPRESSED),
|
||||
_ => error::UnsupportedCompressionTypeSnafu {
|
||||
compression_type: s,
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CompressionType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::GZIP => "GZIP",
|
||||
Self::BZIP2 => "BZIP2",
|
||||
Self::XZ => "XZ",
|
||||
Self::ZSTD => "ZSTD",
|
||||
Self::UNCOMPRESSED => "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CompressionType {
|
||||
pub const fn is_compressed(&self) -> bool {
|
||||
!matches!(self, &Self::UNCOMPRESSED)
|
||||
}
|
||||
|
||||
pub fn convert_async_read<T: AsyncRead + Unpin + Send + 'static>(
|
||||
&self,
|
||||
s: T,
|
||||
) -> Box<dyn AsyncRead + Unpin + Send> {
|
||||
match self {
|
||||
CompressionType::GZIP => Box::new(GzipDecoder::new(BufReader::new(s))),
|
||||
CompressionType::BZIP2 => Box::new(BzDecoder::new(BufReader::new(s))),
|
||||
CompressionType::XZ => Box::new(XzDecoder::new(BufReader::new(s))),
|
||||
CompressionType::ZSTD => Box::new(ZstdDecoder::new(BufReader::new(s))),
|
||||
CompressionType::UNCOMPRESSED => Box::new(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,9 @@ use url::ParseError;
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[snafu(display("Unsupported compression type: {}", compression_type))]
|
||||
UnsupportedCompressionType { compression_type: String },
|
||||
|
||||
#[snafu(display("Unsupported backend protocol: {}", protocol))]
|
||||
UnsupportedBackendProtocol { protocol: String },
|
||||
|
||||
@@ -33,12 +36,44 @@ pub enum Error {
|
||||
#[snafu(display("Invalid url: {}, error :{}", url, source))]
|
||||
InvalidUrl { url: String, source: ParseError },
|
||||
|
||||
#[snafu(display("Failed to decompression, source: {}", source))]
|
||||
Decompression {
|
||||
source: object_store::Error,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to build backend, source: {}", source))]
|
||||
BuildBackend {
|
||||
source: object_store::Error,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to read object from path: {}, source: {}", path, source))]
|
||||
ReadObject {
|
||||
path: String,
|
||||
location: Location,
|
||||
source: object_store::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to read parquet source: {}", source))]
|
||||
ReadParquetSnafu {
|
||||
location: Location,
|
||||
source: datafusion::parquet::errors::ParquetError,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to convert parquet to schema: {}", source))]
|
||||
ParquetToSchema {
|
||||
location: Location,
|
||||
source: datafusion::parquet::errors::ParquetError,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to infer schema from file: {}, source: {}", path, source))]
|
||||
InferSchema {
|
||||
path: String,
|
||||
location: Location,
|
||||
source: arrow_schema::ArrowError,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to list object in path: {}, source: {}", path, source))]
|
||||
ListObjects {
|
||||
path: String,
|
||||
@@ -48,6 +83,12 @@ pub enum Error {
|
||||
|
||||
#[snafu(display("Invalid connection: {}", msg))]
|
||||
InvalidConnection { msg: String },
|
||||
|
||||
#[snafu(display("Failed to join handle: {}", source))]
|
||||
JoinHandle {
|
||||
location: Location,
|
||||
source: tokio::task::JoinError,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -56,13 +97,21 @@ impl ErrorExt for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
use Error::*;
|
||||
match self {
|
||||
BuildBackend { .. } | ListObjects { .. } => StatusCode::StorageUnavailable,
|
||||
BuildBackend { .. } | ListObjects { .. } | ReadObject { .. } => {
|
||||
StatusCode::StorageUnavailable
|
||||
}
|
||||
|
||||
UnsupportedBackendProtocol { .. }
|
||||
| UnsupportedCompressionType { .. }
|
||||
| InvalidConnection { .. }
|
||||
| InvalidUrl { .. }
|
||||
| EmptyHostPath { .. }
|
||||
| InvalidPath { .. } => StatusCode::InvalidArguments,
|
||||
| InvalidPath { .. }
|
||||
| InferSchema { .. }
|
||||
| ReadParquetSnafu { .. }
|
||||
| ParquetToSchema { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
Decompression { .. } | JoinHandle { .. } => StatusCode::Unexpected,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +120,23 @@ impl ErrorExt for Error {
|
||||
}
|
||||
|
||||
fn location_opt(&self) -> Option<common_error::snafu::Location> {
|
||||
use Error::*;
|
||||
match self {
|
||||
Error::BuildBackend { location, .. } => Some(*location),
|
||||
Error::ListObjects { location, .. } => Some(*location),
|
||||
Error::UnsupportedBackendProtocol { .. }
|
||||
| Error::EmptyHostPath { .. }
|
||||
| Error::InvalidPath { .. }
|
||||
| Error::InvalidUrl { .. }
|
||||
| Error::InvalidConnection { .. } => None,
|
||||
BuildBackend { location, .. } => Some(*location),
|
||||
ReadObject { location, .. } => Some(*location),
|
||||
ListObjects { location, .. } => Some(*location),
|
||||
InferSchema { location, .. } => Some(*location),
|
||||
ReadParquetSnafu { location, .. } => Some(*location),
|
||||
ParquetToSchema { location, .. } => Some(*location),
|
||||
Decompression { location, .. } => Some(*location),
|
||||
JoinHandle { location, .. } => Some(*location),
|
||||
|
||||
UnsupportedBackendProtocol { .. }
|
||||
| EmptyHostPath { .. }
|
||||
| InvalidPath { .. }
|
||||
| InvalidUrl { .. }
|
||||
| InvalidConnection { .. }
|
||||
| UnsupportedCompressionType { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
src/common/datasource/src/file_format.rs
Normal file
30
src/common/datasource/src/file_format.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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 mod csv;
|
||||
pub mod json;
|
||||
pub mod parquet;
|
||||
|
||||
pub const DEFAULT_SCHEMA_INFER_MAX_RECORD: usize = 1000;
|
||||
|
||||
use arrow::datatypes::SchemaRef;
|
||||
use async_trait::async_trait;
|
||||
use object_store::ObjectStore;
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
#[async_trait]
|
||||
pub trait FileFormat: Send + Sync + std::fmt::Debug {
|
||||
async fn infer_schema(&self, store: &ObjectStore, path: String) -> Result<SchemaRef>;
|
||||
}
|
||||
158
src/common/datasource/src/file_format/csv.rs
Normal file
158
src/common/datasource/src/file_format/csv.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// 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 arrow::csv::reader::infer_reader_schema as infer_csv_schema;
|
||||
use arrow_schema::SchemaRef;
|
||||
use async_trait::async_trait;
|
||||
use common_runtime;
|
||||
use object_store::ObjectStore;
|
||||
use snafu::ResultExt;
|
||||
use tokio_util::io::SyncIoBridge;
|
||||
|
||||
use crate::compression::CompressionType;
|
||||
use crate::error::{self, Result};
|
||||
use crate::file_format::{self, FileFormat};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CsvFormat {
|
||||
pub has_header: bool,
|
||||
pub delimiter: u8,
|
||||
pub schema_infer_max_record: Option<usize>,
|
||||
pub compression_type: CompressionType,
|
||||
}
|
||||
|
||||
impl Default for CsvFormat {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
has_header: true,
|
||||
delimiter: b',',
|
||||
schema_infer_max_record: Some(file_format::DEFAULT_SCHEMA_INFER_MAX_RECORD),
|
||||
compression_type: CompressionType::UNCOMPRESSED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileFormat for CsvFormat {
|
||||
async fn infer_schema(&self, store: &ObjectStore, path: String) -> Result<SchemaRef> {
|
||||
let reader = store
|
||||
.reader(&path)
|
||||
.await
|
||||
.context(error::ReadObjectSnafu { path: &path })?;
|
||||
|
||||
let decoded = self.compression_type.convert_async_read(reader);
|
||||
|
||||
let delimiter = self.delimiter;
|
||||
let schema_infer_max_record = self.schema_infer_max_record;
|
||||
let has_header = self.has_header;
|
||||
|
||||
common_runtime::spawn_blocking_read(move || {
|
||||
let reader = SyncIoBridge::new(decoded);
|
||||
|
||||
let (schema, _records_read) =
|
||||
infer_csv_schema(reader, delimiter, schema_infer_max_record, has_header)
|
||||
.context(error::InferSchemaSnafu { path: &path })?;
|
||||
|
||||
Ok(Arc::new(schema))
|
||||
})
|
||||
.await
|
||||
.context(error::JoinHandleSnafu)?
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::file_format::FileFormat;
|
||||
use crate::test_util::{self, format_schema, test_store};
|
||||
|
||||
fn test_data_root() -> String {
|
||||
test_util::get_data_dir("tests/csv").display().to_string()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn infer_schema_basic() {
|
||||
let csv = CsvFormat::default();
|
||||
let store = test_store(&test_data_root());
|
||||
let schema = csv
|
||||
.infer_schema(&store, "simple.csv".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let formatted: Vec<_> = format_schema(schema);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
"c1: Utf8: NULL",
|
||||
"c2: Int64: NULL",
|
||||
"c3: Int64: NULL",
|
||||
"c4: Int64: NULL",
|
||||
"c5: Int64: NULL",
|
||||
"c6: Int64: NULL",
|
||||
"c7: Int64: NULL",
|
||||
"c8: Int64: NULL",
|
||||
"c9: Int64: NULL",
|
||||
"c10: Int64: NULL",
|
||||
"c11: Float64: NULL",
|
||||
"c12: Float64: NULL",
|
||||
"c13: Utf8: NULL"
|
||||
],
|
||||
formatted,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn infer_schema_with_limit() {
|
||||
let json = CsvFormat {
|
||||
schema_infer_max_record: Some(3),
|
||||
..CsvFormat::default()
|
||||
};
|
||||
let store = test_store(&test_data_root());
|
||||
let schema = json
|
||||
.infer_schema(&store, "schema_infer_limit.csv".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let formatted: Vec<_> = format_schema(schema);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
"a: Int64: NULL",
|
||||
"b: Float64: NULL",
|
||||
"c: Int64: NULL",
|
||||
"d: Int64: NULL"
|
||||
],
|
||||
formatted
|
||||
);
|
||||
|
||||
let json = CsvFormat::default();
|
||||
let store = test_store(&test_data_root());
|
||||
let schema = json
|
||||
.infer_schema(&store, "schema_infer_limit.csv".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let formatted: Vec<_> = format_schema(schema);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
"a: Int64: NULL",
|
||||
"b: Float64: NULL",
|
||||
"c: Int64: NULL",
|
||||
"d: Utf8: NULL"
|
||||
],
|
||||
formatted
|
||||
);
|
||||
}
|
||||
}
|
||||
121
src/common/datasource/src/file_format/json.rs
Normal file
121
src/common/datasource/src/file_format/json.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
// 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::io::BufReader;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow::datatypes::SchemaRef;
|
||||
use arrow::json::reader::{infer_json_schema_from_iterator, ValueIter};
|
||||
use async_trait::async_trait;
|
||||
use common_runtime;
|
||||
use object_store::ObjectStore;
|
||||
use snafu::ResultExt;
|
||||
use tokio_util::io::SyncIoBridge;
|
||||
|
||||
use crate::compression::CompressionType;
|
||||
use crate::error::{self, Result};
|
||||
use crate::file_format::{self, FileFormat};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct JsonFormat {
|
||||
pub schema_infer_max_record: Option<usize>,
|
||||
pub compression_type: CompressionType,
|
||||
}
|
||||
|
||||
impl Default for JsonFormat {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
schema_infer_max_record: Some(file_format::DEFAULT_SCHEMA_INFER_MAX_RECORD),
|
||||
compression_type: CompressionType::UNCOMPRESSED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl FileFormat for JsonFormat {
|
||||
async fn infer_schema(&self, store: &ObjectStore, path: String) -> Result<SchemaRef> {
|
||||
let reader = store
|
||||
.reader(&path)
|
||||
.await
|
||||
.context(error::ReadObjectSnafu { path: &path })?;
|
||||
|
||||
let decoded = self.compression_type.convert_async_read(reader);
|
||||
|
||||
let schema_infer_max_record = self.schema_infer_max_record;
|
||||
|
||||
common_runtime::spawn_blocking_read(move || {
|
||||
let mut reader = BufReader::new(SyncIoBridge::new(decoded));
|
||||
|
||||
let iter = ValueIter::new(&mut reader, schema_infer_max_record);
|
||||
|
||||
let schema = infer_json_schema_from_iterator(iter)
|
||||
.context(error::InferSchemaSnafu { path: &path })?;
|
||||
|
||||
Ok(Arc::new(schema))
|
||||
})
|
||||
.await
|
||||
.context(error::JoinHandleSnafu)?
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::file_format::FileFormat;
|
||||
use crate::test_util::{self, format_schema, test_store};
|
||||
|
||||
fn test_data_root() -> String {
|
||||
test_util::get_data_dir("tests/json").display().to_string()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn infer_schema_basic() {
|
||||
let json = JsonFormat::default();
|
||||
let store = test_store(&test_data_root());
|
||||
let schema = json
|
||||
.infer_schema(&store, "simple.json".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let formatted: Vec<_> = format_schema(schema);
|
||||
|
||||
assert_eq!(
|
||||
vec![
|
||||
"a: Int64: NULL",
|
||||
"b: Float64: NULL",
|
||||
"c: Boolean: NULL",
|
||||
"d: Utf8: NULL",
|
||||
],
|
||||
formatted
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn infer_schema_with_limit() {
|
||||
let json = JsonFormat {
|
||||
schema_infer_max_record: Some(3),
|
||||
..JsonFormat::default()
|
||||
};
|
||||
let store = test_store(&test_data_root());
|
||||
let schema = json
|
||||
.infer_schema(&store, "schema_infer_limit.json".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let formatted: Vec<_> = format_schema(schema);
|
||||
|
||||
assert_eq!(
|
||||
vec!["a: Int64: NULL", "b: Float64: NULL", "c: Boolean: NULL"],
|
||||
formatted
|
||||
);
|
||||
}
|
||||
}
|
||||
78
src/common/datasource/src/file_format/parquet.rs
Normal file
78
src/common/datasource/src/file_format/parquet.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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 arrow_schema::SchemaRef;
|
||||
use async_trait::async_trait;
|
||||
use datafusion::parquet::arrow::async_reader::AsyncFileReader;
|
||||
use datafusion::parquet::arrow::parquet_to_arrow_schema;
|
||||
use object_store::ObjectStore;
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::file_format::FileFormat;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ParquetFormat {}
|
||||
|
||||
#[async_trait]
|
||||
impl FileFormat for ParquetFormat {
|
||||
async fn infer_schema(&self, store: &ObjectStore, path: String) -> Result<SchemaRef> {
|
||||
let mut reader = store
|
||||
.reader(&path)
|
||||
.await
|
||||
.context(error::ReadObjectSnafu { path: &path })?;
|
||||
|
||||
let metadata = reader
|
||||
.get_metadata()
|
||||
.await
|
||||
.context(error::ReadParquetSnafuSnafu)?;
|
||||
|
||||
let file_metadata = metadata.file_metadata();
|
||||
let schema = parquet_to_arrow_schema(
|
||||
file_metadata.schema_descr(),
|
||||
file_metadata.key_value_metadata(),
|
||||
)
|
||||
.context(error::ParquetToSchemaSnafu)?;
|
||||
|
||||
Ok(Arc::new(schema))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::file_format::FileFormat;
|
||||
use crate::test_util::{self, format_schema, test_store};
|
||||
|
||||
fn test_data_root() -> String {
|
||||
test_util::get_data_dir("tests/parquet")
|
||||
.display()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn infer_schema_basic() {
|
||||
let json = ParquetFormat::default();
|
||||
let store = test_store(&test_data_root());
|
||||
let schema = json
|
||||
.infer_schema(&store, "basic.parquet".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let formatted: Vec<_> = format_schema(schema);
|
||||
|
||||
assert_eq!(vec!["num: Int64: NULL", "str: Utf8: NULL"], formatted);
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
pub mod compression;
|
||||
pub mod error;
|
||||
pub mod file_format;
|
||||
pub mod lister;
|
||||
pub mod object_store;
|
||||
pub mod test_util;
|
||||
pub mod util;
|
||||
|
||||
48
src/common/datasource/src/test_util.rs
Normal file
48
src/common/datasource/src/test_util.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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::path::PathBuf;
|
||||
|
||||
use arrow_schema::SchemaRef;
|
||||
use object_store::services::Fs;
|
||||
use object_store::ObjectStore;
|
||||
|
||||
pub fn get_data_dir(path: &str) -> PathBuf {
|
||||
// https://doc.rust-lang.org/cargo/reference/environment-variables.html
|
||||
let dir = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
PathBuf::from(dir).join(path)
|
||||
}
|
||||
|
||||
pub fn format_schema(schema: SchemaRef) -> Vec<String> {
|
||||
schema
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|f| {
|
||||
format!(
|
||||
"{}: {:?}: {}",
|
||||
f.name(),
|
||||
f.data_type(),
|
||||
if f.is_nullable() { "NULL" } else { "NOT NULL" }
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn test_store(root: &str) -> ObjectStore {
|
||||
let mut builder = Fs::default();
|
||||
builder.root(root);
|
||||
|
||||
ObjectStore::new(builder).unwrap().finish()
|
||||
}
|
||||
24
src/common/datasource/tests/README.md
Normal file
24
src/common/datasource/tests/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
### Parquet
|
||||
The `parquet/basic.parquet` was converted from `csv/basic.csv` via [bdt](https://github.com/andygrove/bdt).
|
||||
|
||||
Internal of `parquet/basic.parquet`:
|
||||
|
||||
Data:
|
||||
```
|
||||
+-----+-------+
|
||||
| num | str |
|
||||
+-----+-------+
|
||||
| 5 | test |
|
||||
| 2 | hello |
|
||||
| 4 | foo |
|
||||
+-----+-------+
|
||||
```
|
||||
Schema:
|
||||
```
|
||||
+-------------+-----------+-------------+
|
||||
| column_name | data_type | is_nullable |
|
||||
+-------------+-----------+-------------+
|
||||
| num | Int64 | YES |
|
||||
| str | Utf8 | YES |
|
||||
+-------------+-----------+-------------+
|
||||
```
|
||||
4
src/common/datasource/tests/csv/basic.csv
Normal file
4
src/common/datasource/tests/csv/basic.csv
Normal file
@@ -0,0 +1,4 @@
|
||||
num,str
|
||||
5,test
|
||||
2,hello
|
||||
4,foo
|
||||
|
5
src/common/datasource/tests/csv/schema_infer_limit.csv
Normal file
5
src/common/datasource/tests/csv/schema_infer_limit.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
a,b,c,d
|
||||
1,2,3,4
|
||||
1,2,3,4
|
||||
1,2.0,3,4
|
||||
1,2,4,test
|
||||
|
11
src/common/datasource/tests/csv/simple.csv
Normal file
11
src/common/datasource/tests/csv/simple.csv
Normal file
@@ -0,0 +1,11 @@
|
||||
c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12,c13
|
||||
c,2,1,18109,2033001162,-6513304855495910254,25,43062,1491205016,5863949479783605708,0.110830784,0.9294097332465232,6WfVFBVGJSQb7FhA7E0lBwdvjfZnSW
|
||||
d,5,-40,22614,706441268,-7542719935673075327,155,14337,3373581039,11720144131976083864,0.69632107,0.3114712539863804,C2GT5KVyOPZpgKVl110TyZO0NcJ434
|
||||
b,1,29,-18218,994303988,5983957848665088916,204,9489,3275293996,14857091259186476033,0.53840446,0.17909035118828576,AyYVExXK6AR2qUTxNZ7qRHQOVGMLcz
|
||||
a,1,-85,-15154,1171968280,1919439543497968449,77,52286,774637006,12101411955859039553,0.12285209,0.6864391962767343,0keZ5G8BffGwgF2RwQD59TFzMStxCB
|
||||
b,5,-82,22080,1824882165,7373730676428214987,208,34331,3342719438,3330177516592499461,0.82634634,0.40975383525297016,Ig1QcuKsjHXkproePdERo2w0mYzIqd
|
||||
b,4,-111,-1967,-4229382,1892872227362838079,67,9832,1243785310,8382489916947120498,0.06563997,0.152498292971736,Sfx0vxv1skzZWT1PqVdoRDdO6Sb6xH
|
||||
e,3,104,-25136,1738331255,300633854973581194,139,20807,3577318119,13079037564113702254,0.40154034,0.7764360990307122,DuJNG8tufSqW0ZstHqWj3aGvFLMg4A
|
||||
a,3,13,12613,1299719633,2020498574254265315,191,17835,3998790955,14881411008939145569,0.041445434,0.8813167497816289,Amn2K87Db5Es3dFQO9cw9cvpAM6h35
|
||||
d,1,38,18384,-335410409,-1632237090406591229,26,57510,2712615025,1842662804748246269,0.6064476,0.6404495093354053,4HX6feIvmNXBN7XGqgO4YVBkhu8GDI
|
||||
a,4,-38,20744,762932956,308913475857409919,7,45465,1787652631,878137512938218976,0.7459874,0.02182578039211991,ydkwycaISlYSlEq3TlkS2m15I2pcp8
|
||||
|
4
src/common/datasource/tests/json/schema_infer_limit.json
Normal file
4
src/common/datasource/tests/json/schema_infer_limit.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{"a":1}
|
||||
{"a":-10, "b":-3.5}
|
||||
{"a":2, "b":0.6, "c":false}
|
||||
{"a":1, "b":2.0, "c":false, "d":"4"}
|
||||
12
src/common/datasource/tests/json/simple.json
Normal file
12
src/common/datasource/tests/json/simple.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{"a":1, "b":2.0, "c":false, "d":"4"}
|
||||
{"a":-10, "b":-3.5, "c":true, "d":"4"}
|
||||
{"a":2, "b":0.6, "c":false, "d":"text"}
|
||||
{"a":1, "b":2.0, "c":false, "d":"4"}
|
||||
{"a":7, "b":-3.5, "c":true, "d":"4"}
|
||||
{"a":1, "b":0.6, "c":false, "d":"text"}
|
||||
{"a":1, "b":2.0, "c":false, "d":"4"}
|
||||
{"a":5, "b":-3.5, "c":true, "d":"4"}
|
||||
{"a":1, "b":0.6, "c":false, "d":"text"}
|
||||
{"a":1, "b":2.0, "c":false, "d":"4"}
|
||||
{"a":1, "b":-3.5, "c":true, "d":"4"}
|
||||
{"a":100000000000000, "b":0.6, "c":false, "d":"text"}
|
||||
BIN
src/common/datasource/tests/parquet/basic.parquet
Normal file
BIN
src/common/datasource/tests/parquet/basic.parquet
Normal file
Binary file not shown.
@@ -212,7 +212,7 @@ fn build_calc_fn(
|
||||
fn calc(input: &[ColumnarValue]) -> Result<ColumnarValue, DataFusionError> {
|
||||
assert_eq!(input.len(), #num_params);
|
||||
|
||||
#( let #range_array_names = RangeArray::try_new(extract_array(&input[#param_numbers])?.data().clone().into())?; )*
|
||||
#( let #range_array_names = RangeArray::try_new(extract_array(&input[#param_numbers])?.to_data().into())?; )*
|
||||
|
||||
// TODO(ruihang): add ensure!()
|
||||
|
||||
|
||||
@@ -752,6 +752,14 @@ mod utils {
|
||||
BuiltinScalarFunction::Uuid => "uuid",
|
||||
BuiltinScalarFunction::Struct => "struct",
|
||||
BuiltinScalarFunction::ArrowTypeof => "arrow_type_of",
|
||||
BuiltinScalarFunction::Acosh => "acosh",
|
||||
BuiltinScalarFunction::Asinh => "asinh",
|
||||
BuiltinScalarFunction::Atanh => "atanh",
|
||||
BuiltinScalarFunction::Cbrt => "cbrt",
|
||||
BuiltinScalarFunction::Cosh => "cosh",
|
||||
BuiltinScalarFunction::Pi => "pi",
|
||||
BuiltinScalarFunction::Sinh => "sinh",
|
||||
BuiltinScalarFunction::Tanh => "tanh",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use chrono::{Local, LocalResult, NaiveDateTime, TimeZone};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::ResultExt;
|
||||
|
||||
use crate::error::{Error, ParseDateStrSnafu, Result};
|
||||
use crate::error::{Error, InvalidDateStrSnafu, Result};
|
||||
|
||||
const DATETIME_FORMAT: &str = "%F %T";
|
||||
const DATETIME_FORMAT_WITH_TZ: &str = "%F %T%z";
|
||||
|
||||
/// [DateTime] represents the **seconds elapsed since "1970-01-01 00:00:00 UTC" (UNIX Epoch)**.
|
||||
#[derive(
|
||||
@@ -32,7 +32,13 @@ pub struct DateTime(i64);
|
||||
impl Display for DateTime {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(abs_time) = NaiveDateTime::from_timestamp_opt(self.0, 0) {
|
||||
write!(f, "{}", abs_time.format(DATETIME_FORMAT))
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
Local {}
|
||||
.from_utc_datetime(&abs_time)
|
||||
.format(DATETIME_FORMAT_WITH_TZ)
|
||||
)
|
||||
} else {
|
||||
write!(f, "DateTime({})", self.0)
|
||||
}
|
||||
@@ -49,9 +55,21 @@ impl FromStr for DateTime {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let datetime = NaiveDateTime::parse_from_str(s, DATETIME_FORMAT)
|
||||
.context(ParseDateStrSnafu { raw: s })?;
|
||||
Ok(Self(datetime.timestamp()))
|
||||
let local = Local {};
|
||||
let timestamp = if let Ok(d) = NaiveDateTime::parse_from_str(s, DATETIME_FORMAT) {
|
||||
match local.from_local_datetime(&d) {
|
||||
LocalResult::None => {
|
||||
return InvalidDateStrSnafu { raw: s }.fail();
|
||||
}
|
||||
LocalResult::Single(d) | LocalResult::Ambiguous(d, _) => d.naive_utc().timestamp(),
|
||||
}
|
||||
} else if let Ok(v) = chrono::DateTime::parse_from_str(s, DATETIME_FORMAT_WITH_TZ) {
|
||||
v.timestamp()
|
||||
} else {
|
||||
return InvalidDateStrSnafu { raw: s }.fail();
|
||||
};
|
||||
|
||||
Ok(Self(timestamp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,14 +99,16 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
pub fn test_new_date_time() {
|
||||
assert_eq!("1970-01-01 00:00:00", DateTime::new(0).to_string());
|
||||
assert_eq!("1970-01-01 00:00:01", DateTime::new(1).to_string());
|
||||
assert_eq!("1969-12-31 23:59:59", DateTime::new(-1).to_string());
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
assert_eq!("1970-01-01 08:00:00+0800", DateTime::new(0).to_string());
|
||||
assert_eq!("1970-01-01 08:00:01+0800", DateTime::new(1).to_string());
|
||||
assert_eq!("1970-01-01 07:59:59+0800", DateTime::new(-1).to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_parse_from_string() {
|
||||
let time = "1970-01-01 00:00:00";
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
let time = "1970-01-01 00:00:00+0800";
|
||||
let dt = DateTime::from_str(time).unwrap();
|
||||
assert_eq!(time, &dt.to_string());
|
||||
}
|
||||
@@ -98,4 +118,22 @@ mod tests {
|
||||
let d: DateTime = 42.into();
|
||||
assert_eq!(42, d.val());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_local_date_time() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
assert_eq!(
|
||||
-28800,
|
||||
DateTime::from_str("1970-01-01 00:00:00").unwrap().val()
|
||||
);
|
||||
assert_eq!(0, DateTime::from_str("1970-01-01 08:00:00").unwrap().val());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_local_date_time_with_tz() {
|
||||
let ts = DateTime::from_str("1970-01-01 08:00:00+0000")
|
||||
.unwrap()
|
||||
.val();
|
||||
assert_eq!(28800, ts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ pub enum Error {
|
||||
#[snafu(display("Failed to parse string to date, raw: {}, source: {}", raw, source))]
|
||||
ParseDateStr { raw: String, source: ParseError },
|
||||
|
||||
#[snafu(display("Invalid date string, raw: {}", raw))]
|
||||
InvalidDateStr { raw: String, location: Location },
|
||||
|
||||
#[snafu(display("Failed to parse a string into Timestamp, raw string: {}", raw))]
|
||||
ParseTimestamp { raw: String, location: Location },
|
||||
|
||||
@@ -46,7 +49,9 @@ impl ErrorExt for Error {
|
||||
StatusCode::InvalidArguments
|
||||
}
|
||||
Error::TimestampOverflow { .. } => StatusCode::Internal,
|
||||
Error::ArithmeticOverflow { .. } => StatusCode::InvalidArguments,
|
||||
Error::InvalidDateStr { .. } | Error::ArithmeticOverflow { .. } => {
|
||||
StatusCode::InvalidArguments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +65,7 @@ impl ErrorExt for Error {
|
||||
| Error::TimestampOverflow { location, .. }
|
||||
| Error::ArithmeticOverflow { location, .. } => Some(*location),
|
||||
Error::ParseDateStr { .. } => None,
|
||||
Error::InvalidDateStr { location, .. } => Some(*location),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,16 +164,20 @@ impl Timestamp {
|
||||
/// Format timestamp to ISO8601 string. If the timestamp exceeds what chrono timestamp can
|
||||
/// represent, this function simply print the timestamp unit and value in plain string.
|
||||
pub fn to_iso8601_string(&self) -> String {
|
||||
if let LocalResult::Single(datetime) = self.to_chrono_datetime() {
|
||||
format!("{}", datetime.format("%Y-%m-%d %H:%M:%S%.f%z"))
|
||||
if let Some(v) = self.to_chrono_datetime() {
|
||||
let local = Local {};
|
||||
format!(
|
||||
"{}",
|
||||
local.from_utc_datetime(&v).format("%Y-%m-%d %H:%M:%S%.f%z")
|
||||
)
|
||||
} else {
|
||||
format!("[Timestamp{}: {}]", self.unit, self.value)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_chrono_datetime(&self) -> LocalResult<DateTime<Utc>> {
|
||||
pub fn to_chrono_datetime(&self) -> Option<NaiveDateTime> {
|
||||
let (sec, nsec) = self.split();
|
||||
Utc.timestamp_opt(sec, nsec)
|
||||
NaiveDateTime::from_timestamp_opt(sec, nsec)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,31 +640,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_to_iso8601_string() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
let datetime_str = "2020-09-08 13:42:29.042+0000";
|
||||
let ts = Timestamp::from_str(datetime_str).unwrap();
|
||||
assert_eq!(datetime_str, ts.to_iso8601_string());
|
||||
assert_eq!("2020-09-08 21:42:29.042+0800", ts.to_iso8601_string());
|
||||
|
||||
let ts_millis = 1668070237000;
|
||||
let ts = Timestamp::new_millisecond(ts_millis);
|
||||
assert_eq!("2022-11-10 08:50:37+0000", ts.to_iso8601_string());
|
||||
assert_eq!("2022-11-10 16:50:37+0800", ts.to_iso8601_string());
|
||||
|
||||
let ts_millis = -1000;
|
||||
let ts = Timestamp::new_millisecond(ts_millis);
|
||||
assert_eq!("1969-12-31 23:59:59+0000", ts.to_iso8601_string());
|
||||
assert_eq!("1970-01-01 07:59:59+0800", ts.to_iso8601_string());
|
||||
|
||||
let ts_millis = -1;
|
||||
let ts = Timestamp::new_millisecond(ts_millis);
|
||||
assert_eq!("1969-12-31 23:59:59.999+0000", ts.to_iso8601_string());
|
||||
assert_eq!("1970-01-01 07:59:59.999+0800", ts.to_iso8601_string());
|
||||
|
||||
let ts_millis = -1001;
|
||||
let ts = Timestamp::new_millisecond(ts_millis);
|
||||
assert_eq!("1969-12-31 23:59:58.999+0000", ts.to_iso8601_string());
|
||||
assert_eq!("1970-01-01 07:59:58.999+0800", ts.to_iso8601_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialize_to_json_value() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
assert_eq!(
|
||||
"1970-01-01 00:00:01+0000",
|
||||
"1970-01-01 08:00:01+0800",
|
||||
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Second)) {
|
||||
Value::String(s) => s,
|
||||
_ => unreachable!(),
|
||||
@@ -668,7 +674,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"1970-01-01 00:00:00.001+0000",
|
||||
"1970-01-01 08:00:00.001+0800",
|
||||
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Millisecond)) {
|
||||
Value::String(s) => s,
|
||||
_ => unreachable!(),
|
||||
@@ -676,7 +682,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"1970-01-01 00:00:00.000001+0000",
|
||||
"1970-01-01 08:00:00.000001+0800",
|
||||
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Microsecond)) {
|
||||
Value::String(s) => s,
|
||||
_ => unreachable!(),
|
||||
@@ -684,7 +690,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
"1970-01-01 00:00:00.000000001+0000",
|
||||
"1970-01-01 08:00:00.000000001+0800",
|
||||
match serde_json::Value::from(Timestamp::new(1, TimeUnit::Nanosecond)) {
|
||||
Value::String(s) => s,
|
||||
_ => unreachable!(),
|
||||
@@ -869,4 +875,18 @@ mod tests {
|
||||
assert_eq!(1, res.value);
|
||||
assert_eq!(TimeUnit::Second, res.unit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_in_time_zone() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
assert_eq!(
|
||||
Timestamp::new(0, TimeUnit::Nanosecond),
|
||||
Timestamp::from_str("1970-01-01 08:00:00.000").unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Timestamp::new(0, TimeUnit::Second),
|
||||
Timestamp::from_str("1970-01-01 08:00:00").unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,11 +423,13 @@ pub enum Error {
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"File Schema mismatch, expected table schema: {} but found :{}",
|
||||
"File schema mismatch at index {}, expected table schema: {} but found: {}",
|
||||
index,
|
||||
table_schema,
|
||||
file_schema
|
||||
))]
|
||||
InvalidSchema {
|
||||
index: usize,
|
||||
table_schema: String,
|
||||
file_schema: String,
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ use std::time::Duration;
|
||||
|
||||
use api::v1::meta::{HeartbeatRequest, HeartbeatResponse, NodeStat, Peer};
|
||||
use catalog::{datanode_stat, CatalogManagerRef};
|
||||
use common_telemetry::{error, info, warn};
|
||||
use common_telemetry::{error, info, trace, warn};
|
||||
use meta_client::client::{HeartbeatSender, MetaClient};
|
||||
use snafu::ResultExt;
|
||||
|
||||
@@ -84,7 +84,7 @@ impl HeartbeatTask {
|
||||
}
|
||||
|
||||
async fn handle_response(resp: HeartbeatResponse) {
|
||||
info!("heartbeat response: {:?}", resp);
|
||||
trace!("heartbeat response: {:?}", resp);
|
||||
}
|
||||
|
||||
/// Start heartbeat task, spawn background task.
|
||||
|
||||
@@ -20,6 +20,7 @@ use async_trait::async_trait;
|
||||
use common_query::Output;
|
||||
use query::parser::{PromQuery, QueryLanguageParser, QueryStatement};
|
||||
use query::plan::LogicalPlan;
|
||||
use query::query_engine::SqlStatementExecutor;
|
||||
use servers::query_handler::grpc::GrpcQueryHandler;
|
||||
use session::context::{QueryContext, QueryContextRef};
|
||||
use snafu::prelude::*;
|
||||
@@ -65,7 +66,7 @@ impl Instance {
|
||||
match stmt {
|
||||
// TODO(LFC): Remove SQL execution branch here.
|
||||
// Keep this because substrait can't handle much of SQLs now.
|
||||
QueryStatement::Sql(Statement::Query(_)) => {
|
||||
QueryStatement::Sql(Statement::Query(_)) | QueryStatement::Promql(_) => {
|
||||
let plan = self
|
||||
.query_engine
|
||||
.planner()
|
||||
@@ -77,7 +78,9 @@ impl Instance {
|
||||
.await
|
||||
.context(ExecuteLogicalPlanSnafu)
|
||||
}
|
||||
_ => self.execute_stmt(stmt, ctx).await,
|
||||
QueryStatement::Sql(stmt) => {
|
||||
self.execute_sql(stmt, ctx).await.context(ExecuteSqlSnafu)
|
||||
}
|
||||
}
|
||||
}
|
||||
Query::LogicalPlan(plan) => self.execute_logical(plan).await,
|
||||
@@ -242,12 +245,11 @@ mod test {
|
||||
let output = instance.do_query(query, QueryContext::arc()).await.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(0)));
|
||||
|
||||
let stmt = QueryLanguageParser::parse_sql(
|
||||
let Ok(QueryStatement::Sql(stmt)) = QueryLanguageParser::parse_sql(
|
||||
"INSERT INTO my_database.my_table (a, b, ts) VALUES ('s', 1, 1672384140000)",
|
||||
)
|
||||
.unwrap();
|
||||
) else { unreachable!() };
|
||||
let output = instance
|
||||
.execute_stmt(stmt, QueryContext::arc())
|
||||
.execute_sql(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(1)));
|
||||
|
||||
@@ -21,7 +21,7 @@ use common_telemetry::logging::info;
|
||||
use common_telemetry::timer;
|
||||
use query::error::QueryExecutionSnafu;
|
||||
use query::parser::{PromQuery, QueryLanguageParser, QueryStatement};
|
||||
use query::query_engine::StatementHandler;
|
||||
use query::query_engine::SqlStatementExecutor;
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::prelude::*;
|
||||
use sql::ast::ObjectName;
|
||||
@@ -31,27 +31,23 @@ use table::engine::TableReference;
|
||||
use table::requests::{CopyDirection, CopyTableRequest, CreateDatabaseRequest, DropTableRequest};
|
||||
|
||||
use crate::error::{
|
||||
self, BumpTableIdSnafu, ExecuteSqlSnafu, ExecuteStatementSnafu, PlanStatementSnafu, Result,
|
||||
TableIdProviderNotFoundSnafu,
|
||||
self, BumpTableIdSnafu, ExecuteSqlSnafu, ExecuteStatementSnafu, NotSupportSqlSnafu,
|
||||
PlanStatementSnafu, Result, TableIdProviderNotFoundSnafu,
|
||||
};
|
||||
use crate::instance::Instance;
|
||||
use crate::metrics;
|
||||
use crate::sql::{SqlHandler, SqlRequest};
|
||||
|
||||
impl Instance {
|
||||
pub async fn execute_stmt(
|
||||
&self,
|
||||
stmt: QueryStatement,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
async fn do_execute_sql(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
match stmt {
|
||||
QueryStatement::Sql(Statement::Insert(insert)) => {
|
||||
Statement::Insert(insert) => {
|
||||
let request =
|
||||
SqlHandler::insert_to_request(self.catalog_manager.clone(), *insert, query_ctx)
|
||||
.await?;
|
||||
self.sql_handler.insert(request).await
|
||||
}
|
||||
QueryStatement::Sql(Statement::CreateDatabase(create_database)) => {
|
||||
Statement::CreateDatabase(create_database) => {
|
||||
let request = CreateDatabaseRequest {
|
||||
db_name: create_database.name.to_string(),
|
||||
create_if_not_exists: create_database.if_not_exists,
|
||||
@@ -64,7 +60,7 @@ impl Instance {
|
||||
.await
|
||||
}
|
||||
|
||||
QueryStatement::Sql(Statement::CreateTable(create_table)) => {
|
||||
Statement::CreateTable(create_table) => {
|
||||
let table_id = self
|
||||
.table_id_provider
|
||||
.as_ref()
|
||||
@@ -88,10 +84,7 @@ impl Instance {
|
||||
.execute(SqlRequest::CreateTable(request), query_ctx)
|
||||
.await
|
||||
}
|
||||
QueryStatement::Sql(Statement::CreateExternalTable(_create_external_table)) => {
|
||||
unimplemented!()
|
||||
}
|
||||
QueryStatement::Sql(Statement::Alter(alter_table)) => {
|
||||
Statement::Alter(alter_table) => {
|
||||
let name = alter_table.table_name().clone();
|
||||
let (catalog, schema, table) = table_idents_to_full_name(&name, query_ctx.clone())?;
|
||||
let table_ref = TableReference::full(&catalog, &schema, &table);
|
||||
@@ -100,7 +93,7 @@ impl Instance {
|
||||
.execute(SqlRequest::Alter(req), query_ctx)
|
||||
.await
|
||||
}
|
||||
QueryStatement::Sql(Statement::DropTable(drop_table)) => {
|
||||
Statement::DropTable(drop_table) => {
|
||||
let (catalog_name, schema_name, table_name) =
|
||||
table_idents_to_full_name(drop_table.table_name(), query_ctx.clone())?;
|
||||
let req = DropTableRequest {
|
||||
@@ -112,20 +105,7 @@ impl Instance {
|
||||
.execute(SqlRequest::DropTable(req), query_ctx)
|
||||
.await
|
||||
}
|
||||
QueryStatement::Sql(Statement::ShowDatabases(show_databases)) => {
|
||||
self.sql_handler
|
||||
.execute(SqlRequest::ShowDatabases(show_databases), query_ctx)
|
||||
.await
|
||||
}
|
||||
QueryStatement::Sql(Statement::ShowTables(show_tables)) => {
|
||||
self.sql_handler
|
||||
.execute(SqlRequest::ShowTables(show_tables), query_ctx)
|
||||
.await
|
||||
}
|
||||
QueryStatement::Sql(Statement::ShowCreateTable(_show_create_table)) => {
|
||||
unimplemented!("SHOW CREATE TABLE is unimplemented yet");
|
||||
}
|
||||
QueryStatement::Sql(Statement::Copy(copy_table)) => {
|
||||
Statement::Copy(copy_table) => {
|
||||
let req = match copy_table {
|
||||
CopyTable::To(copy_table) => {
|
||||
let CopyTableArgument {
|
||||
@@ -173,13 +153,10 @@ impl Instance {
|
||||
.execute(SqlRequest::CopyTable(req), query_ctx)
|
||||
.await
|
||||
}
|
||||
QueryStatement::Sql(Statement::Query(_))
|
||||
| QueryStatement::Sql(Statement::Explain(_))
|
||||
| QueryStatement::Sql(Statement::Use(_))
|
||||
| QueryStatement::Sql(Statement::Tql(_))
|
||||
| QueryStatement::Sql(Statement::Delete(_))
|
||||
| QueryStatement::Sql(Statement::DescribeTable(_))
|
||||
| QueryStatement::Promql(_) => unreachable!(),
|
||||
_ => NotSupportSqlSnafu {
|
||||
msg: format!("not supported to execute {stmt:?}"),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +165,7 @@ impl Instance {
|
||||
promql: &PromQuery,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
let _timer = timer!(metrics::METRIC_HANDLE_PROMQL_ELAPSED);
|
||||
let _timer = timer!(metrics::HANDLE_PROMQL_ELAPSED);
|
||||
|
||||
let stmt = QueryLanguageParser::parse_promql(promql).context(ExecuteSqlSnafu)?;
|
||||
|
||||
@@ -276,13 +253,13 @@ pub fn table_idents_to_full_name(
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StatementHandler for Instance {
|
||||
async fn handle_statement(
|
||||
impl SqlStatementExecutor for Instance {
|
||||
async fn execute_sql(
|
||||
&self,
|
||||
stmt: QueryStatement,
|
||||
stmt: Statement,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> query::error::Result<Output> {
|
||||
self.execute_stmt(stmt, query_ctx)
|
||||
self.do_execute_sql(stmt, query_ctx)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(QueryExecutionSnafu)
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
|
||||
//! datanode metrics
|
||||
|
||||
pub const METRIC_HANDLE_SQL_ELAPSED: &str = "datanode.handle_sql_elapsed";
|
||||
pub const METRIC_HANDLE_PROMQL_ELAPSED: &str = "datanode.handle_promql_elapsed";
|
||||
pub const HANDLE_SQL_ELAPSED: &str = "datanode.handle_sql_elapsed";
|
||||
pub const HANDLE_PROMQL_ELAPSED: &str = "datanode.handle_promql_elapsed";
|
||||
|
||||
@@ -19,18 +19,15 @@ use common_error::prelude::BoxedError;
|
||||
use common_procedure::ProcedureManagerRef;
|
||||
use common_query::Output;
|
||||
use common_telemetry::error;
|
||||
use query::sql::{show_databases, show_tables};
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use sql::statements::show::{ShowDatabases, ShowTables};
|
||||
use table::engine::manager::TableEngineManagerRef;
|
||||
use table::engine::{TableEngineProcedureRef, TableEngineRef, TableReference};
|
||||
use table::requests::*;
|
||||
use table::{Table, TableRef};
|
||||
|
||||
use crate::error::{
|
||||
self, CloseTableEngineSnafu, ExecuteSqlSnafu, Result, TableEngineNotFoundSnafu,
|
||||
TableNotFoundSnafu,
|
||||
self, CloseTableEngineSnafu, Result, TableEngineNotFoundSnafu, TableNotFoundSnafu,
|
||||
};
|
||||
use crate::instance::sql::table_idents_to_full_name;
|
||||
|
||||
@@ -49,8 +46,6 @@ pub enum SqlRequest {
|
||||
Alter(AlterTableRequest),
|
||||
DropTable(DropTableRequest),
|
||||
FlushTable(FlushTableRequest),
|
||||
ShowDatabases(ShowDatabases),
|
||||
ShowTables(ShowTables),
|
||||
CopyTable(CopyTableRequest),
|
||||
}
|
||||
|
||||
@@ -59,6 +54,8 @@ pub enum SqlRequest {
|
||||
pub struct SqlHandler {
|
||||
table_engine_manager: TableEngineManagerRef,
|
||||
catalog_manager: CatalogManagerRef,
|
||||
// TODO(yingwen): Support multiple table engine. We need to add a method
|
||||
// to TableEngineManagerRef to return engine procedure by engine name.
|
||||
engine_procedure: TableEngineProcedureRef,
|
||||
procedure_manager: Option<ProcedureManagerRef>,
|
||||
}
|
||||
@@ -92,13 +89,6 @@ impl SqlHandler {
|
||||
CopyDirection::Export => self.copy_table_to(req).await,
|
||||
CopyDirection::Import => self.copy_table_from(req).await,
|
||||
},
|
||||
SqlRequest::ShowDatabases(req) => {
|
||||
show_databases(req, self.catalog_manager.clone()).context(ExecuteSqlSnafu)
|
||||
}
|
||||
SqlRequest::ShowTables(req) => {
|
||||
show_tables(req, self.catalog_manager.clone(), query_ctx.clone())
|
||||
.context(ExecuteSqlSnafu)
|
||||
}
|
||||
SqlRequest::FlushTable(req) => self.flush_table(req).await,
|
||||
};
|
||||
if let Err(e) = &result {
|
||||
|
||||
@@ -13,18 +13,25 @@
|
||||
// limitations under the License.
|
||||
|
||||
use catalog::RenameTableRequest;
|
||||
use common_procedure::{watcher, ProcedureManagerRef, ProcedureWithId};
|
||||
use common_query::Output;
|
||||
use common_telemetry::logging::info;
|
||||
use snafu::prelude::*;
|
||||
use sql::statements::alter::{AlterTable, AlterTableOperation};
|
||||
use sql::statements::column_def_to_schema;
|
||||
use table::engine::{EngineContext, TableReference};
|
||||
use table::requests::{AddColumnRequest, AlterKind, AlterTableRequest};
|
||||
use table_procedure::AlterTableProcedure;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::sql::SqlHandler;
|
||||
|
||||
impl SqlHandler {
|
||||
pub(crate) async fn alter(&self, req: AlterTableRequest) -> Result<Output> {
|
||||
if let Some(procedure_manager) = &self.procedure_manager {
|
||||
return self.alter_table_by_procedure(procedure_manager, req).await;
|
||||
}
|
||||
|
||||
let ctx = EngineContext {};
|
||||
let table_name = req.table_name.clone();
|
||||
let table_ref = TableReference {
|
||||
@@ -71,6 +78,33 @@ impl SqlHandler {
|
||||
Ok(Output::AffectedRows(0))
|
||||
}
|
||||
|
||||
pub(crate) async fn alter_table_by_procedure(
|
||||
&self,
|
||||
procedure_manager: &ProcedureManagerRef,
|
||||
req: AlterTableRequest,
|
||||
) -> Result<Output> {
|
||||
let table_name = req.table_name.clone();
|
||||
let procedure = AlterTableProcedure::new(
|
||||
req,
|
||||
self.catalog_manager.clone(),
|
||||
self.engine_procedure.clone(),
|
||||
);
|
||||
let procedure_with_id = ProcedureWithId::with_random_id(Box::new(procedure));
|
||||
let procedure_id = procedure_with_id.id;
|
||||
|
||||
info!("Alter table {} by procedure {}", table_name, procedure_id);
|
||||
|
||||
let mut watcher = procedure_manager
|
||||
.submit(procedure_with_id)
|
||||
.await
|
||||
.context(error::SubmitProcedureSnafu { procedure_id })?;
|
||||
|
||||
watcher::wait(&mut watcher)
|
||||
.await
|
||||
.context(error::WaitProcedureSnafu { procedure_id })?;
|
||||
Ok(Output::AffectedRows(0))
|
||||
}
|
||||
|
||||
pub(crate) fn alter_to_request(
|
||||
&self,
|
||||
alter_table: AlterTable,
|
||||
@@ -112,12 +146,15 @@ mod tests {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use query::parser::{QueryLanguageParser, QueryStatement};
|
||||
use query::query_engine::SqlStatementExecutor;
|
||||
use session::context::QueryContext;
|
||||
use sql::dialect::GenericDialect;
|
||||
use sql::parser::ParserContext;
|
||||
use sql::statements::statement::Statement;
|
||||
|
||||
use super::*;
|
||||
use crate::tests::test_util::create_mock_sql_handler;
|
||||
use crate::tests::test_util::{create_mock_sql_handler, MockInstance};
|
||||
|
||||
fn parse_sql(sql: &str) -> AlterTable {
|
||||
let mut stmt = ParserContext::create_with_dialect(sql, &GenericDialect {}).unwrap();
|
||||
@@ -182,4 +219,41 @@ mod tests {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn alter_table_by_procedure() {
|
||||
let instance = MockInstance::with_procedure_enabled("alter_table_by_procedure").await;
|
||||
|
||||
// Create table first.
|
||||
let sql = r#"create table test_alter(
|
||||
host string,
|
||||
ts timestamp,
|
||||
cpu double default 0,
|
||||
TIME INDEX (ts),
|
||||
PRIMARY KEY(host)
|
||||
) engine=mito with(regions=1);"#;
|
||||
let stmt = match QueryLanguageParser::parse_sql(sql).unwrap() {
|
||||
QueryStatement::Sql(sql) => sql,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let output = instance
|
||||
.inner()
|
||||
.execute_sql(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(0)));
|
||||
|
||||
// Alter table.
|
||||
let sql = r#"alter table test_alter add column memory double"#;
|
||||
let stmt = match QueryLanguageParser::parse_sql(sql).unwrap() {
|
||||
QueryStatement::Sql(sql) => sql,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let output = instance
|
||||
.inner()
|
||||
.execute_sql(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,24 +13,26 @@
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
|
||||
use async_compat::CompatExt;
|
||||
use common_base::readable_size::ReadableSize;
|
||||
use common_datasource::lister::{Lister, Source};
|
||||
use common_datasource::object_store::{build_backend, parse_url};
|
||||
use common_datasource::util::find_dir_and_filename;
|
||||
use common_query::Output;
|
||||
use common_recordbatch::error::DataTypesSnafu;
|
||||
use datafusion::parquet::arrow::ParquetRecordBatchStreamBuilder;
|
||||
use datatypes::arrow::record_batch::RecordBatch;
|
||||
use datatypes::vectors::{Helper, VectorRef};
|
||||
use futures_util::TryStreamExt;
|
||||
use datatypes::arrow::datatypes::{DataType, SchemaRef};
|
||||
use datatypes::vectors::Helper;
|
||||
use futures_util::StreamExt;
|
||||
use regex::Regex;
|
||||
use snafu::{ensure, ResultExt};
|
||||
use snafu::ResultExt;
|
||||
use table::engine::TableReference;
|
||||
use table::requests::{CopyTableRequest, InsertRequest};
|
||||
use tokio::io::BufReader;
|
||||
|
||||
use crate::error::{self, Result};
|
||||
use crate::error::{self, ParseDataTypesSnafu, Result};
|
||||
use crate::sql::SqlHandler;
|
||||
|
||||
impl SqlHandler {
|
||||
@@ -65,8 +67,15 @@ impl SqlHandler {
|
||||
|
||||
let entries = lister.list().await.context(error::ListObjectsSnafu)?;
|
||||
|
||||
let mut buf: Vec<RecordBatch> = Vec::new();
|
||||
let fields = table
|
||||
.schema()
|
||||
.arrow_schema()
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|f| f.name().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut rows_inserted = 0;
|
||||
for entry in entries.iter() {
|
||||
let path = entry.path();
|
||||
let reader = object_store
|
||||
@@ -80,66 +89,202 @@ impl SqlHandler {
|
||||
.await
|
||||
.context(error::ReadParquetSnafu)?;
|
||||
|
||||
ensure!(
|
||||
builder.schema() == table.schema().arrow_schema(),
|
||||
error::InvalidSchemaSnafu {
|
||||
table_schema: table.schema().arrow_schema().to_string(),
|
||||
file_schema: (*(builder.schema())).to_string()
|
||||
}
|
||||
);
|
||||
ensure_schema_matches_ignore_timezone(builder.schema(), table.schema().arrow_schema())?;
|
||||
|
||||
let stream = builder
|
||||
let mut stream = builder
|
||||
.build()
|
||||
.context(error::BuildParquetRecordBatchStreamSnafu)?;
|
||||
|
||||
let chunk = stream
|
||||
.try_collect::<Vec<_>>()
|
||||
.await
|
||||
.context(error::ReadParquetSnafu)?;
|
||||
// TODO(hl): make this configurable through options.
|
||||
let pending_mem_threshold = ReadableSize::mb(32).as_bytes();
|
||||
let mut pending_mem_size = 0;
|
||||
let mut pending = vec![];
|
||||
|
||||
buf.extend(chunk.into_iter());
|
||||
while let Some(r) = stream.next().await {
|
||||
let record_batch = r.context(error::ReadParquetSnafu)?;
|
||||
let vectors = Helper::try_into_vectors(record_batch.columns())
|
||||
.context(DataTypesSnafu)
|
||||
.context(ParseDataTypesSnafu)?;
|
||||
|
||||
pending_mem_size += vectors.iter().map(|v| v.memory_size()).sum::<usize>();
|
||||
|
||||
let columns_values = fields
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(vectors.into_iter())
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
pending.push(table.insert(InsertRequest {
|
||||
catalog_name: req.catalog_name.to_string(),
|
||||
schema_name: req.schema_name.to_string(),
|
||||
table_name: req.table_name.to_string(),
|
||||
columns_values,
|
||||
//TODO: support multi-regions
|
||||
region_number: 0,
|
||||
}));
|
||||
|
||||
if pending_mem_size as u64 >= pending_mem_threshold {
|
||||
rows_inserted +=
|
||||
batch_insert(&mut pending, &mut pending_mem_size, &req.table_name).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if !pending.is_empty() {
|
||||
rows_inserted +=
|
||||
batch_insert(&mut pending, &mut pending_mem_size, &req.table_name).await?;
|
||||
}
|
||||
}
|
||||
|
||||
let fields = table
|
||||
.schema()
|
||||
.arrow_schema()
|
||||
.fields()
|
||||
.iter()
|
||||
.map(|f| f.name().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Vec<Columns>
|
||||
let column_chunks = buf
|
||||
.into_iter()
|
||||
.map(|c| Helper::try_into_vectors(c.columns()).context(DataTypesSnafu))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut futs = Vec::with_capacity(column_chunks.len());
|
||||
|
||||
for column_chunk in column_chunks.into_iter() {
|
||||
let column_chunk = column_chunk.context(error::ParseDataTypesSnafu)?;
|
||||
let columns_values = fields
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(column_chunk.into_iter())
|
||||
.collect::<HashMap<String, VectorRef>>();
|
||||
|
||||
futs.push(table.insert(InsertRequest {
|
||||
catalog_name: req.catalog_name.to_string(),
|
||||
schema_name: req.schema_name.to_string(),
|
||||
table_name: req.table_name.to_string(),
|
||||
columns_values,
|
||||
//TODO: support multi-regions
|
||||
region_number: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
let result = futures::future::try_join_all(futs)
|
||||
.await
|
||||
.context(error::InsertSnafu {
|
||||
table_name: req.table_name.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(Output::AffectedRows(result.iter().sum()))
|
||||
Ok(Output::AffectedRows(rows_inserted))
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes all pending inserts all at once, drain pending requests and reset pending bytes.
|
||||
async fn batch_insert(
|
||||
pending: &mut Vec<impl Future<Output = table::error::Result<usize>>>,
|
||||
pending_bytes: &mut usize,
|
||||
table_name: &str,
|
||||
) -> Result<usize> {
|
||||
let batch = pending.drain(..);
|
||||
let res: usize = futures::future::try_join_all(batch)
|
||||
.await
|
||||
.context(error::InsertSnafu { table_name })?
|
||||
.iter()
|
||||
.sum();
|
||||
*pending_bytes = 0;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn ensure_schema_matches_ignore_timezone(left: &SchemaRef, right: &SchemaRef) -> Result<()> {
|
||||
let not_match = left
|
||||
.fields
|
||||
.iter()
|
||||
.zip(right.fields.iter())
|
||||
.map(|(l, r)| (l.data_type(), r.data_type()))
|
||||
.enumerate()
|
||||
.find(|(_, (l, r))| !data_type_equals_ignore_timezone(l, r));
|
||||
|
||||
if let Some((index, _)) = not_match {
|
||||
error::InvalidSchemaSnafu {
|
||||
index,
|
||||
table_schema: left.to_string(),
|
||||
file_schema: right.to_string(),
|
||||
}
|
||||
.fail()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn data_type_equals_ignore_timezone(l: &DataType, r: &DataType) -> bool {
|
||||
match (l, r) {
|
||||
(DataType::List(a), DataType::List(b))
|
||||
| (DataType::LargeList(a), DataType::LargeList(b)) => {
|
||||
a.is_nullable() == b.is_nullable()
|
||||
&& data_type_equals_ignore_timezone(a.data_type(), b.data_type())
|
||||
}
|
||||
(DataType::FixedSizeList(a, a_size), DataType::FixedSizeList(b, b_size)) => {
|
||||
a_size == b_size
|
||||
&& a.is_nullable() == b.is_nullable()
|
||||
&& data_type_equals_ignore_timezone(a.data_type(), b.data_type())
|
||||
}
|
||||
(DataType::Struct(a), DataType::Struct(b)) => {
|
||||
a.len() == b.len()
|
||||
&& a.iter().zip(b).all(|(a, b)| {
|
||||
a.is_nullable() == b.is_nullable()
|
||||
&& data_type_equals_ignore_timezone(a.data_type(), b.data_type())
|
||||
})
|
||||
}
|
||||
(DataType::Map(a_field, a_is_sorted), DataType::Map(b_field, b_is_sorted)) => {
|
||||
a_field == b_field && a_is_sorted == b_is_sorted
|
||||
}
|
||||
(DataType::Timestamp(l_unit, _), DataType::Timestamp(r_unit, _)) => l_unit == r_unit,
|
||||
_ => l == r,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use datatypes::arrow::datatypes::{Field, Schema};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test_schema_matches(l: (DataType, bool), r: (DataType, bool), matches: bool) {
|
||||
let s1 = Arc::new(Schema::new(vec![Field::new("col", l.0, l.1)]));
|
||||
let s2 = Arc::new(Schema::new(vec![Field::new("col", r.0, r.1)]));
|
||||
let res = ensure_schema_matches_ignore_timezone(&s1, &s2);
|
||||
assert_eq!(matches, res.is_ok())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ensure_datatype_matches_ignore_timezone() {
|
||||
test_schema_matches(
|
||||
(
|
||||
DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Second, None),
|
||||
true,
|
||||
),
|
||||
(
|
||||
DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Second, None),
|
||||
true,
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
test_schema_matches(
|
||||
(
|
||||
DataType::Timestamp(
|
||||
datatypes::arrow::datatypes::TimeUnit::Second,
|
||||
Some("UTC".into()),
|
||||
),
|
||||
true,
|
||||
),
|
||||
(
|
||||
DataType::Timestamp(datatypes::arrow::datatypes::TimeUnit::Second, None),
|
||||
true,
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
test_schema_matches(
|
||||
(
|
||||
DataType::Timestamp(
|
||||
datatypes::arrow::datatypes::TimeUnit::Second,
|
||||
Some("UTC".into()),
|
||||
),
|
||||
true,
|
||||
),
|
||||
(
|
||||
DataType::Timestamp(
|
||||
datatypes::arrow::datatypes::TimeUnit::Second,
|
||||
Some("PDT".into()),
|
||||
),
|
||||
true,
|
||||
),
|
||||
true,
|
||||
);
|
||||
|
||||
test_schema_matches(
|
||||
(
|
||||
DataType::Timestamp(
|
||||
datatypes::arrow::datatypes::TimeUnit::Second,
|
||||
Some("UTC".into()),
|
||||
),
|
||||
true,
|
||||
),
|
||||
(
|
||||
DataType::Timestamp(
|
||||
datatypes::arrow::datatypes::TimeUnit::Millisecond,
|
||||
Some("UTC".into()),
|
||||
),
|
||||
true,
|
||||
),
|
||||
false,
|
||||
);
|
||||
|
||||
test_schema_matches((DataType::Int8, true), (DataType::Int8, true), true);
|
||||
|
||||
test_schema_matches((DataType::Int8, true), (DataType::Int16, true), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,8 @@ mod tests {
|
||||
use common_base::readable_size::ReadableSize;
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::Schema;
|
||||
use query::parser::QueryLanguageParser;
|
||||
use query::parser::{QueryLanguageParser, QueryStatement};
|
||||
use query::query_engine::SqlStatementExecutor;
|
||||
use session::context::QueryContext;
|
||||
use sql::dialect::GenericDialect;
|
||||
use sql::parser::ParserContext;
|
||||
@@ -560,10 +561,10 @@ mod tests {
|
||||
TIME INDEX (ts),
|
||||
PRIMARY KEY(host)
|
||||
) engine=mito with(regions=1);"#;
|
||||
let stmt = QueryLanguageParser::parse_sql(sql).unwrap();
|
||||
let Ok(QueryStatement::Sql(stmt)) = QueryLanguageParser::parse_sql(sql) else { unreachable!() };
|
||||
let output = instance
|
||||
.inner()
|
||||
.execute_stmt(stmt, QueryContext::arc())
|
||||
.execute_sql(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(0)));
|
||||
@@ -577,10 +578,10 @@ mod tests {
|
||||
TIME INDEX (ts),
|
||||
PRIMARY KEY(host)
|
||||
) engine=mito with(regions=1);"#;
|
||||
let stmt = QueryLanguageParser::parse_sql(sql).unwrap();
|
||||
let Ok(QueryStatement::Sql(stmt)) = QueryLanguageParser::parse_sql(sql) else { unreachable!() };
|
||||
let output = instance
|
||||
.inner()
|
||||
.execute_stmt(stmt, QueryContext::arc())
|
||||
.execute_sql(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(matches!(output, Output::AffectedRows(0)));
|
||||
|
||||
@@ -10,6 +10,7 @@ test = []
|
||||
|
||||
[dependencies]
|
||||
arrow.workspace = true
|
||||
arrow-array.workspace = true
|
||||
arrow-schema.workspace = true
|
||||
common-base = { path = "../common/base" }
|
||||
common-error = { path = "../common/error" }
|
||||
|
||||
@@ -362,7 +362,7 @@ mod tests {
|
||||
ConcreteDataType::String(_)
|
||||
));
|
||||
assert_eq!(
|
||||
ConcreteDataType::from_arrow_type(&ArrowDataType::List(Box::new(Field::new(
|
||||
ConcreteDataType::from_arrow_type(&ArrowDataType::List(Arc::new(Field::new(
|
||||
"item",
|
||||
ArrowDataType::Int32,
|
||||
true,
|
||||
|
||||
@@ -272,7 +272,7 @@ impl TryFrom<Arc<ArrowSchema>> for Schema {
|
||||
let mut column_schemas = Vec::with_capacity(arrow_schema.fields.len());
|
||||
let mut name_to_index = HashMap::with_capacity(arrow_schema.fields.len());
|
||||
for field in &arrow_schema.fields {
|
||||
let column_schema = ColumnSchema::try_from(field)?;
|
||||
let column_schema = ColumnSchema::try_from(field.as_ref())?;
|
||||
name_to_index.insert(field.name().to_string(), column_schemas.len());
|
||||
column_schemas.push(column_schema);
|
||||
}
|
||||
|
||||
@@ -125,11 +125,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_to_serde_json_value() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
let ts = TimestampSecond::new(123);
|
||||
let val = serde_json::Value::from(ts);
|
||||
match val {
|
||||
serde_json::Value::String(s) => {
|
||||
assert_eq!("1970-01-01 00:02:03+0000", s);
|
||||
assert_eq!("1970-01-01 08:02:03+0800", s);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow::datatypes::{DataType as ArrowDataType, Field};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -63,7 +65,7 @@ impl DataType for ListType {
|
||||
}
|
||||
|
||||
fn as_arrow_type(&self) -> ArrowDataType {
|
||||
let field = Box::new(Field::new("item", self.item_type.as_arrow_type(), true));
|
||||
let field = Arc::new(Field::new("item", self.item_type.as_arrow_type(), true));
|
||||
ArrowDataType::List(field)
|
||||
}
|
||||
|
||||
@@ -94,7 +96,7 @@ mod tests {
|
||||
t.default_value()
|
||||
);
|
||||
assert_eq!(
|
||||
ArrowDataType::List(Box::new(Field::new("item", ArrowDataType::Boolean, true))),
|
||||
ArrowDataType::List(Arc::new(Field::new("item", ArrowDataType::Boolean, true))),
|
||||
t.as_arrow_type()
|
||||
);
|
||||
assert_eq!(ConcreteDataType::boolean_datatype(), *t.item_type());
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow::datatypes::{DataType as ArrowDataType, Field};
|
||||
use common_base::bytes::{Bytes, StringBytes};
|
||||
@@ -271,7 +272,7 @@ fn to_null_value(output_type: &ConcreteDataType) -> ScalarValue {
|
||||
ConcreteDataType::DateTime(_) => ScalarValue::Date64(None),
|
||||
ConcreteDataType::Timestamp(t) => timestamp_to_scalar_value(t.unit(), None),
|
||||
ConcreteDataType::List(_) => {
|
||||
ScalarValue::List(None, Box::new(new_item_field(output_type.as_arrow_type())))
|
||||
ScalarValue::List(None, Arc::new(new_item_field(output_type.as_arrow_type())))
|
||||
}
|
||||
ConcreteDataType::Dictionary(dict) => ScalarValue::Dictionary(
|
||||
Box::new(dict.key_type().as_arrow_type()),
|
||||
@@ -490,7 +491,7 @@ impl ListValue {
|
||||
|
||||
Ok(ScalarValue::List(
|
||||
vs,
|
||||
Box::new(new_item_field(output_type.item_type().as_arrow_type())),
|
||||
Arc::new(new_item_field(output_type.item_type().as_arrow_type())),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1345,6 +1346,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_display() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
assert_eq!(Value::Null.to_string(), "Null");
|
||||
assert_eq!(Value::UInt8(8).to_string(), "8");
|
||||
assert_eq!(Value::UInt16(16).to_string(), "16");
|
||||
@@ -1366,11 +1368,11 @@ mod tests {
|
||||
assert_eq!(Value::Date(Date::new(0)).to_string(), "1970-01-01");
|
||||
assert_eq!(
|
||||
Value::DateTime(DateTime::new(0)).to_string(),
|
||||
"1970-01-01 00:00:00"
|
||||
"1970-01-01 08:00:00+0800"
|
||||
);
|
||||
assert_eq!(
|
||||
Value::Timestamp(Timestamp::new(1000, TimeUnit::Millisecond)).to_string(),
|
||||
"1970-01-01 00:00:01+0000"
|
||||
"1970-01-01 08:00:01+0800"
|
||||
);
|
||||
assert_eq!(
|
||||
Value::List(ListValue::new(
|
||||
|
||||
@@ -217,8 +217,7 @@ macro_rules! impl_try_from_arrow_array_for_vector {
|
||||
.with_context(|| crate::error::ConversionSnafu {
|
||||
from: std::format!("{:?}", array.as_ref().data_type()),
|
||||
})?
|
||||
.data()
|
||||
.clone();
|
||||
.to_data();
|
||||
|
||||
let concrete_array = $Array::from(data);
|
||||
Ok($Vector::from(concrete_array))
|
||||
@@ -229,7 +228,7 @@ macro_rules! impl_try_from_arrow_array_for_vector {
|
||||
|
||||
macro_rules! impl_validity_for_vector {
|
||||
($array: expr) => {
|
||||
Validity::from_array_data($array.data())
|
||||
Validity::from_array_data($array.to_data())
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,7 @@ impl BinaryVector {
|
||||
}
|
||||
|
||||
fn to_array_data(&self) -> ArrayData {
|
||||
self.array.data().clone()
|
||||
}
|
||||
|
||||
fn from_array_data(data: ArrayData) -> BinaryVector {
|
||||
BinaryVector {
|
||||
array: BinaryArray::from(data),
|
||||
}
|
||||
self.array.to_data()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,8 +100,8 @@ impl Vector for BinaryVector {
|
||||
}
|
||||
|
||||
fn slice(&self, offset: usize, length: usize) -> VectorRef {
|
||||
let data = self.array.data().slice(offset, length);
|
||||
Arc::new(Self::from_array_data(data))
|
||||
let array = self.array.slice(offset, length);
|
||||
Arc::new(Self { array })
|
||||
}
|
||||
|
||||
fn get(&self, index: usize) -> Value {
|
||||
@@ -286,7 +280,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_from_arrow_array() {
|
||||
let arrow_array = BinaryArray::from_iter_values([vec![1, 2, 3], vec![1, 2, 3]]);
|
||||
let original = BinaryArray::from(arrow_array.data().clone());
|
||||
let original = BinaryArray::from(arrow_array.to_data());
|
||||
let vector = BinaryVector::from(arrow_array);
|
||||
assert_eq!(original, vector.array);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl BooleanVector {
|
||||
}
|
||||
|
||||
fn to_array_data(&self) -> ArrayData {
|
||||
self.array.data().clone()
|
||||
self.array.to_data()
|
||||
}
|
||||
|
||||
fn from_array_data(data: ArrayData) -> BooleanVector {
|
||||
@@ -132,7 +132,7 @@ impl Vector for BooleanVector {
|
||||
}
|
||||
|
||||
fn slice(&self, offset: usize, length: usize) -> VectorRef {
|
||||
let data = self.array.data().slice(offset, length);
|
||||
let data = self.array.to_data().slice(offset, length);
|
||||
Arc::new(Self::from_array_data(data))
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ mod tests {
|
||||
assert!(!v.is_const());
|
||||
assert!(v.validity().is_all_valid());
|
||||
assert!(!v.only_null());
|
||||
assert_eq!(64, v.memory_size());
|
||||
assert_eq!(2, v.memory_size());
|
||||
|
||||
for (i, b) in bools.iter().enumerate() {
|
||||
assert!(!v.is_null(i));
|
||||
|
||||
@@ -24,7 +24,7 @@ pub type DateVectorBuilder = PrimitiveVectorBuilder<DateType>;
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow::array::Array;
|
||||
use arrow_array::ArrayRef;
|
||||
use common_time::date::Date;
|
||||
|
||||
use super::*;
|
||||
@@ -84,7 +84,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_date_from_arrow() {
|
||||
let vector = DateVector::from_slice([1, 2]);
|
||||
let arrow = vector.as_arrow().slice(0, vector.len());
|
||||
let arrow: ArrayRef = Arc::new(vector.as_arrow().slice(0, vector.len()));
|
||||
let vector2 = DateVector::try_from_arrow_array(&arrow).unwrap();
|
||||
assert_eq!(vector, vector2);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use arrow::array::{Array, PrimitiveArray};
|
||||
use arrow_array::ArrayRef;
|
||||
use common_time::DateTime;
|
||||
use datafusion_common::from_slice::FromSlice;
|
||||
|
||||
@@ -37,6 +38,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_datetime_vector() {
|
||||
std::env::set_var("TZ", "Asia/Shanghai");
|
||||
let v = DateTimeVector::new(PrimitiveArray::from_slice([1, 2, 3]));
|
||||
assert_eq!(ConcreteDataType::datetime_datatype(), v.data_type());
|
||||
assert_eq!(3, v.len());
|
||||
@@ -63,7 +65,7 @@ mod tests {
|
||||
unreachable!()
|
||||
}
|
||||
assert_eq!(
|
||||
"[\"1970-01-01 00:00:01\",\"1970-01-01 00:00:02\",\"1970-01-01 00:00:03\"]",
|
||||
"[\"1970-01-01 08:00:01+0800\",\"1970-01-01 08:00:02+0800\",\"1970-01-01 08:00:03+0800\"]",
|
||||
serde_json::to_string(&v.serialize_to_json().unwrap()).unwrap()
|
||||
);
|
||||
}
|
||||
@@ -107,8 +109,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_datetime_from_arrow() {
|
||||
let vector = DateTimeVector::from_wrapper_slice([DateTime::new(1), DateTime::new(2)]);
|
||||
let arrow = vector.as_arrow().slice(0, vector.len());
|
||||
let vector2 = DateTimeVector::try_from_arrow_array(&arrow).unwrap();
|
||||
let arrow: ArrayRef = Arc::new(vector.as_arrow().slice(0, vector.len())) as _;
|
||||
let vector2 = DateTimeVector::try_from_arrow_array(arrow).unwrap();
|
||||
assert_eq!(vector, vector2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,16 +238,18 @@ impl Helper {
|
||||
ArrowDataType::Date64 => Arc::new(DateTimeVector::try_from_arrow_array(array)?),
|
||||
ArrowDataType::List(_) => Arc::new(ListVector::try_from_arrow_array(array)?),
|
||||
ArrowDataType::Timestamp(unit, _) => match unit {
|
||||
TimeUnit::Second => Arc::new(TimestampSecondVector::try_from_arrow_array(array)?),
|
||||
TimeUnit::Millisecond => {
|
||||
Arc::new(TimestampMillisecondVector::try_from_arrow_array(array)?)
|
||||
}
|
||||
TimeUnit::Microsecond => {
|
||||
Arc::new(TimestampMicrosecondVector::try_from_arrow_array(array)?)
|
||||
}
|
||||
TimeUnit::Nanosecond => {
|
||||
Arc::new(TimestampNanosecondVector::try_from_arrow_array(array)?)
|
||||
}
|
||||
TimeUnit::Second => Arc::new(
|
||||
TimestampSecondVector::try_from_arrow_timestamp_array(array)?,
|
||||
),
|
||||
TimeUnit::Millisecond => Arc::new(
|
||||
TimestampMillisecondVector::try_from_arrow_timestamp_array(array)?,
|
||||
),
|
||||
TimeUnit::Microsecond => Arc::new(
|
||||
TimestampMicrosecondVector::try_from_arrow_timestamp_array(array)?,
|
||||
),
|
||||
TimeUnit::Nanosecond => Arc::new(
|
||||
TimestampNanosecondVector::try_from_arrow_timestamp_array(array)?,
|
||||
),
|
||||
},
|
||||
ArrowDataType::Float16
|
||||
| ArrowDataType::Time32(_)
|
||||
@@ -260,7 +262,7 @@ impl Helper {
|
||||
| ArrowDataType::LargeList(_)
|
||||
| ArrowDataType::FixedSizeList(_, _)
|
||||
| ArrowDataType::Struct(_)
|
||||
| ArrowDataType::Union(_, _, _)
|
||||
| ArrowDataType::Union(_, _)
|
||||
| ArrowDataType::Dictionary(_, _)
|
||||
| ArrowDataType::Decimal128(_, _)
|
||||
| ArrowDataType::Decimal256(_, _)
|
||||
@@ -357,7 +359,7 @@ mod tests {
|
||||
ScalarValue::Int32(Some(1)),
|
||||
ScalarValue::Int32(Some(2)),
|
||||
]),
|
||||
Box::new(Field::new("item", ArrowDataType::Int32, true)),
|
||||
Arc::new(Field::new("item", ArrowDataType::Int32, true)),
|
||||
);
|
||||
let vector = Helper::try_from_scalar_value(value, 3).unwrap();
|
||||
assert_eq!(
|
||||
|
||||
@@ -47,7 +47,7 @@ impl ListVector {
|
||||
}
|
||||
|
||||
fn to_array_data(&self) -> ArrayData {
|
||||
self.array.data().clone()
|
||||
self.array.to_data()
|
||||
}
|
||||
|
||||
fn from_array_data_and_type(data: ArrayData, item_type: ConcreteDataType) -> Self {
|
||||
@@ -106,7 +106,7 @@ impl Vector for ListVector {
|
||||
}
|
||||
|
||||
fn slice(&self, offset: usize, length: usize) -> VectorRef {
|
||||
let data = self.array.data().slice(offset, length);
|
||||
let data = self.array.to_data().slice(offset, length);
|
||||
Arc::new(Self::from_array_data_and_type(data, self.item_type.clone()))
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ impl ScalarVectorBuilder for ListVectorBuilder {
|
||||
let len = self.len();
|
||||
let values_vector = self.values_builder.to_vector();
|
||||
let values_arr = values_vector.to_arrow_array();
|
||||
let values_data = values_arr.data();
|
||||
let values_data = values_arr.to_data();
|
||||
|
||||
let offset_buffer = self.offsets_builder.finish();
|
||||
let null_bit_buffer = self.null_buffer_builder.finish();
|
||||
@@ -355,7 +355,7 @@ impl ScalarVectorBuilder for ListVectorBuilder {
|
||||
let array_data_builder = ArrayData::builder(data_type)
|
||||
.len(len)
|
||||
.add_buffer(offset_buffer)
|
||||
.add_child_data(values_data.clone())
|
||||
.add_child_data(values_data)
|
||||
.null_bit_buffer(null_bit_buffer);
|
||||
|
||||
let array_data = unsafe { array_data_builder.build_unchecked() };
|
||||
|
||||
@@ -46,7 +46,7 @@ impl NullVector {
|
||||
}
|
||||
|
||||
fn to_array_data(&self) -> ArrayData {
|
||||
self.array.data().clone()
|
||||
self.array.to_data()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,10 @@ use std::sync::Arc;
|
||||
|
||||
use arrow::array::{
|
||||
Array, ArrayBuilder, ArrayData, ArrayIter, ArrayRef, PrimitiveArray, PrimitiveBuilder,
|
||||
TimestampMicrosecondArray, TimestampMillisecondArray, TimestampNanosecondArray,
|
||||
TimestampSecondArray,
|
||||
};
|
||||
use arrow_schema::DataType;
|
||||
use serde_json::Value as JsonValue;
|
||||
use snafu::OptionExt;
|
||||
|
||||
@@ -64,12 +67,49 @@ impl<T: LogicalPrimitiveType> PrimitiveVector<T> {
|
||||
.with_context(|| error::ConversionSnafu {
|
||||
from: format!("{:?}", array.as_ref().data_type()),
|
||||
})?
|
||||
.data()
|
||||
.clone();
|
||||
.to_data();
|
||||
let concrete_array = PrimitiveArray::<T::ArrowPrimitive>::from(data);
|
||||
Ok(Self::new(concrete_array))
|
||||
}
|
||||
|
||||
/// Converts arrow timestamp array to vectors, ignoring time zone info.
|
||||
pub fn try_from_arrow_timestamp_array(array: impl AsRef<dyn Array>) -> Result<Self> {
|
||||
let array = array.as_ref();
|
||||
let array_data = match array.data_type() {
|
||||
DataType::Timestamp(unit, _) => match unit {
|
||||
arrow_schema::TimeUnit::Second => array
|
||||
.as_any()
|
||||
.downcast_ref::<TimestampSecondArray>()
|
||||
.unwrap()
|
||||
.with_timezone_opt(None::<String>)
|
||||
.to_data(),
|
||||
arrow_schema::TimeUnit::Millisecond => array
|
||||
.as_any()
|
||||
.downcast_ref::<TimestampMillisecondArray>()
|
||||
.unwrap()
|
||||
.with_timezone_opt(None::<String>)
|
||||
.to_data(),
|
||||
arrow_schema::TimeUnit::Microsecond => array
|
||||
.as_any()
|
||||
.downcast_ref::<TimestampMicrosecondArray>()
|
||||
.unwrap()
|
||||
.with_timezone_opt(None::<String>)
|
||||
.to_data(),
|
||||
arrow_schema::TimeUnit::Nanosecond => array
|
||||
.as_any()
|
||||
.downcast_ref::<TimestampNanosecondArray>()
|
||||
.unwrap()
|
||||
.with_timezone_opt(None::<String>)
|
||||
.to_data(),
|
||||
},
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
};
|
||||
let concrete_array = PrimitiveArray::<T::ArrowPrimitive>::from(array_data);
|
||||
Ok(Self::new(concrete_array))
|
||||
}
|
||||
|
||||
pub fn from_slice<P: AsRef<[T::Native]>>(slice: P) -> Self {
|
||||
let iter = slice.as_ref().iter().copied();
|
||||
Self {
|
||||
@@ -101,7 +141,7 @@ impl<T: LogicalPrimitiveType> PrimitiveVector<T> {
|
||||
}
|
||||
|
||||
fn to_array_data(&self) -> ArrayData {
|
||||
self.array.data().clone()
|
||||
self.array.to_data()
|
||||
}
|
||||
|
||||
fn from_array_data(data: ArrayData) -> Self {
|
||||
@@ -112,7 +152,7 @@ impl<T: LogicalPrimitiveType> PrimitiveVector<T> {
|
||||
|
||||
// To distinguish with `Vector::slice()`.
|
||||
fn get_slice(&self, offset: usize, length: usize) -> Self {
|
||||
let data = self.array.data().slice(offset, length);
|
||||
let data = self.array.to_data().slice(offset, length);
|
||||
Self::from_array_data(data)
|
||||
}
|
||||
}
|
||||
@@ -161,7 +201,7 @@ impl<T: LogicalPrimitiveType> Vector for PrimitiveVector<T> {
|
||||
}
|
||||
|
||||
fn slice(&self, offset: usize, length: usize) -> VectorRef {
|
||||
let data = self.array.data().slice(offset, length);
|
||||
let data = self.array.to_data().slice(offset, length);
|
||||
Arc::new(Self::from_array_data(data))
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ impl StringVector {
|
||||
}
|
||||
|
||||
fn to_array_data(&self) -> ArrayData {
|
||||
self.array.data().clone()
|
||||
self.array.to_data()
|
||||
}
|
||||
|
||||
fn from_array_data(data: ArrayData) -> Self {
|
||||
@@ -146,7 +146,7 @@ impl Vector for StringVector {
|
||||
}
|
||||
|
||||
fn slice(&self, offset: usize, length: usize) -> VectorRef {
|
||||
let data = self.array.data().slice(offset, length);
|
||||
let data = self.array.to_data().slice(offset, length);
|
||||
Arc::new(Self::from_array_data(data))
|
||||
}
|
||||
|
||||
|
||||
@@ -16,10 +16,10 @@ use arrow::array::ArrayData;
|
||||
use arrow::buffer::NullBuffer;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum ValidityKind<'a> {
|
||||
enum ValidityKind {
|
||||
/// Whether the array slot is valid or not (null).
|
||||
Slots {
|
||||
bitmap: &'a NullBuffer,
|
||||
bitmap: NullBuffer,
|
||||
len: usize,
|
||||
null_count: usize,
|
||||
},
|
||||
@@ -31,17 +31,17 @@ enum ValidityKind<'a> {
|
||||
|
||||
/// Validity of a vector.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Validity<'a> {
|
||||
kind: ValidityKind<'a>,
|
||||
pub struct Validity {
|
||||
kind: ValidityKind,
|
||||
}
|
||||
|
||||
impl<'a> Validity<'a> {
|
||||
impl Validity {
|
||||
/// Creates a `Validity` from [`ArrayData`].
|
||||
pub fn from_array_data(data: &'a ArrayData) -> Validity<'a> {
|
||||
pub fn from_array_data(data: ArrayData) -> Validity {
|
||||
match data.nulls() {
|
||||
Some(bitmap) => Validity {
|
||||
Some(null_buf) => Validity {
|
||||
kind: ValidityKind::Slots {
|
||||
bitmap,
|
||||
bitmap: null_buf.clone(),
|
||||
len: data.len(),
|
||||
null_count: data.null_count(),
|
||||
},
|
||||
@@ -51,14 +51,14 @@ impl<'a> Validity<'a> {
|
||||
}
|
||||
|
||||
/// Returns `Validity` that all elements are valid.
|
||||
pub fn all_valid(len: usize) -> Validity<'a> {
|
||||
pub fn all_valid(len: usize) -> Validity {
|
||||
Validity {
|
||||
kind: ValidityKind::AllValid { len },
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Validity` that all elements are null.
|
||||
pub fn all_null(len: usize) -> Validity<'a> {
|
||||
pub fn all_null(len: usize) -> Validity {
|
||||
Validity {
|
||||
kind: ValidityKind::AllNull { len },
|
||||
}
|
||||
@@ -66,9 +66,9 @@ impl<'a> Validity<'a> {
|
||||
|
||||
/// Returns whether `i-th` bit is set.
|
||||
pub fn is_set(&self, i: usize) -> bool {
|
||||
match self.kind {
|
||||
match &self.kind {
|
||||
ValidityKind::Slots { bitmap, .. } => bitmap.is_valid(i),
|
||||
ValidityKind::AllValid { len } => i < len,
|
||||
ValidityKind::AllValid { len } => i < *len,
|
||||
ValidityKind::AllNull { .. } => false,
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_from_array_data() {
|
||||
let array = Int32Array::from_iter([None, Some(1), None]);
|
||||
let validity = Validity::from_array_data(array.data());
|
||||
let validity = Validity::from_array_data(array.to_data());
|
||||
assert_eq!(2, validity.null_count());
|
||||
assert!(!validity.is_set(0));
|
||||
assert!(validity.is_set(1));
|
||||
@@ -145,13 +145,13 @@ mod tests {
|
||||
assert!(!validity.is_all_valid());
|
||||
|
||||
let array = Int32Array::from_iter([None, None]);
|
||||
let validity = Validity::from_array_data(array.data());
|
||||
let validity = Validity::from_array_data(array.to_data());
|
||||
assert!(validity.is_all_null());
|
||||
assert!(!validity.is_all_valid());
|
||||
assert_eq!(2, validity.null_count());
|
||||
|
||||
let array = Int32Array::from_iter_values([1, 2]);
|
||||
let validity = Validity::from_array_data(array.data());
|
||||
let validity = Validity::from_array_data(array.to_data());
|
||||
assert!(!validity.is_all_null());
|
||||
assert!(validity.is_all_valid());
|
||||
assert_eq!(0, validity.null_count());
|
||||
|
||||
32
src/file-table-engine/Cargo.toml
Normal file
32
src/file-table-engine/Cargo.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "file-table-engine"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
test = ["common-test-util"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
common-catalog = { path = "../common/catalog" }
|
||||
common-error = { path = "../common/error" }
|
||||
common-procedure = { path = "../common/procedure" }
|
||||
common-query = { path = "../common/query" }
|
||||
common-telemetry = { path = "../common/telemetry" }
|
||||
common-time = { path = "../common/time" }
|
||||
datatypes = { path = "../datatypes" }
|
||||
futures.workspace = true
|
||||
object-store = { path = "../object-store" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
snafu.workspace = true
|
||||
storage = { path = "../storage" }
|
||||
store-api = { path = "../store-api" }
|
||||
table = { path = "../table" }
|
||||
common-test-util = { path = "../common/test-util", optional = true }
|
||||
tokio.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
common-test-util = { path = "../common/test-util" }
|
||||
16
src/file-table-engine/src/config.rs
Normal file
16
src/file-table-engine/src/config.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
// 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.
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EngineConfig {}
|
||||
21
src/file-table-engine/src/engine.rs
Normal file
21
src/file-table-engine/src/engine.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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 mod immutable;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use table::metadata::TableVersion;
|
||||
|
||||
const INIT_TABLE_VERSION: TableVersion = 0;
|
||||
400
src/file-table-engine/src/engine/immutable.rs
Normal file
400
src/file-table-engine/src/engine/immutable.rs
Normal file
@@ -0,0 +1,400 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common_catalog::consts::IMMUTABLE_FILE_ENGINE;
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_telemetry::{debug, logging};
|
||||
use datatypes::schema::Schema;
|
||||
use object_store::ObjectStore;
|
||||
use snafu::ResultExt;
|
||||
use table::engine::{table_dir, EngineContext, TableEngine, TableReference};
|
||||
use table::error::TableOperationSnafu;
|
||||
use table::metadata::{TableInfo, TableInfoBuilder, TableMetaBuilder, TableType};
|
||||
use table::requests::{AlterTableRequest, CreateTableRequest, DropTableRequest, OpenTableRequest};
|
||||
use table::{error as table_error, Result as TableResult, Table, TableRef};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::config::EngineConfig;
|
||||
use crate::engine::INIT_TABLE_VERSION;
|
||||
use crate::error::{
|
||||
BuildTableInfoSnafu, BuildTableMetaSnafu, DropTableSnafu, InvalidRawSchemaSnafu, Result,
|
||||
TableExistsSnafu,
|
||||
};
|
||||
use crate::manifest::immutable::{delete_table_manifest, ImmutableMetadata};
|
||||
use crate::manifest::table_manifest_dir;
|
||||
use crate::table::immutable::{ImmutableFileTable, ImmutableFileTableRef};
|
||||
|
||||
/// [TableEngine] implementation.
|
||||
#[derive(Clone)]
|
||||
pub struct ImmutableFileTableEngine {
|
||||
inner: Arc<EngineInner>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TableEngine for ImmutableFileTableEngine {
|
||||
fn name(&self) -> &str {
|
||||
IMMUTABLE_FILE_ENGINE
|
||||
}
|
||||
|
||||
async fn create_table(
|
||||
&self,
|
||||
ctx: &EngineContext,
|
||||
request: CreateTableRequest,
|
||||
) -> TableResult<TableRef> {
|
||||
self.inner
|
||||
.create_table(ctx, request)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(table_error::TableOperationSnafu)
|
||||
}
|
||||
|
||||
async fn open_table(
|
||||
&self,
|
||||
ctx: &EngineContext,
|
||||
request: OpenTableRequest,
|
||||
) -> TableResult<Option<TableRef>> {
|
||||
self.inner
|
||||
.open_table(ctx, request)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(table_error::TableOperationSnafu)
|
||||
}
|
||||
|
||||
async fn alter_table(
|
||||
&self,
|
||||
_ctx: &EngineContext,
|
||||
_req: AlterTableRequest,
|
||||
) -> TableResult<TableRef> {
|
||||
table_error::UnsupportedSnafu {
|
||||
operation: "ALTER TABLE",
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
|
||||
fn get_table(
|
||||
&self,
|
||||
_ctx: &EngineContext,
|
||||
table_ref: &TableReference,
|
||||
) -> TableResult<Option<TableRef>> {
|
||||
Ok(self.inner.get_table(table_ref))
|
||||
}
|
||||
|
||||
fn table_exists(&self, _ctx: &EngineContext, table_ref: &TableReference) -> bool {
|
||||
self.inner.get_table(table_ref).is_some()
|
||||
}
|
||||
|
||||
async fn drop_table(
|
||||
&self,
|
||||
_ctx: &EngineContext,
|
||||
request: DropTableRequest,
|
||||
) -> TableResult<bool> {
|
||||
self.inner
|
||||
.drop_table(request)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(table_error::TableOperationSnafu)
|
||||
}
|
||||
|
||||
async fn close(&self) -> TableResult<()> {
|
||||
self.inner.close().await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl ImmutableFileTableEngine {
|
||||
pub async fn close_table(&self, table_ref: &TableReference<'_>) -> TableResult<()> {
|
||||
self.inner.close_table(table_ref).await
|
||||
}
|
||||
}
|
||||
|
||||
impl ImmutableFileTableEngine {
|
||||
pub fn new(config: EngineConfig, object_store: ObjectStore) -> Self {
|
||||
ImmutableFileTableEngine {
|
||||
inner: Arc::new(EngineInner::new(config, object_store)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EngineInner {
|
||||
/// All tables opened by the engine. Map key is formatted [TableReference].
|
||||
///
|
||||
/// Writing to `tables` should also hold the `table_mutex`.
|
||||
tables: RwLock<HashMap<String, ImmutableFileTableRef>>,
|
||||
object_store: ObjectStore,
|
||||
|
||||
/// Table mutex is used to protect the operations such as creating/opening/closing
|
||||
/// a table, to avoid things like opening the same table simultaneously.
|
||||
table_mutex: Mutex<()>,
|
||||
}
|
||||
|
||||
impl EngineInner {
|
||||
pub fn new(_config: EngineConfig, object_store: ObjectStore) -> Self {
|
||||
EngineInner {
|
||||
tables: RwLock::new(HashMap::default()),
|
||||
object_store,
|
||||
table_mutex: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_table(
|
||||
&self,
|
||||
_ctx: &EngineContext,
|
||||
request: CreateTableRequest,
|
||||
) -> Result<TableRef> {
|
||||
let CreateTableRequest {
|
||||
catalog_name,
|
||||
schema_name,
|
||||
table_name,
|
||||
create_if_not_exists,
|
||||
table_options,
|
||||
..
|
||||
} = request;
|
||||
let table_ref = TableReference {
|
||||
catalog: &catalog_name,
|
||||
schema: &schema_name,
|
||||
table: &table_name,
|
||||
};
|
||||
|
||||
if let Some(table) = self.get_table(&table_ref) {
|
||||
return if create_if_not_exists {
|
||||
Ok(table)
|
||||
} else {
|
||||
TableExistsSnafu { table_name }.fail()
|
||||
};
|
||||
}
|
||||
|
||||
let table_schema =
|
||||
Arc::new(Schema::try_from(request.schema).context(InvalidRawSchemaSnafu)?);
|
||||
|
||||
let table_id = request.id;
|
||||
let table_dir = table_dir(&catalog_name, &schema_name, table_id);
|
||||
|
||||
let table_full_name = table_ref.to_string();
|
||||
|
||||
let _lock = self.table_mutex.lock().await;
|
||||
// Checks again, read lock should be enough since we are guarded by the mutex.
|
||||
if let Some(table) = self.get_table_by_full_name(&table_full_name) {
|
||||
return if request.create_if_not_exists {
|
||||
Ok(table)
|
||||
} else {
|
||||
TableExistsSnafu { table_name }.fail()
|
||||
};
|
||||
}
|
||||
|
||||
let table_meta = TableMetaBuilder::new_external_table()
|
||||
.schema(table_schema)
|
||||
.engine(IMMUTABLE_FILE_ENGINE)
|
||||
.options(table_options)
|
||||
.build()
|
||||
.context(BuildTableMetaSnafu {
|
||||
table_name: &table_full_name,
|
||||
})?;
|
||||
|
||||
let table_info = TableInfoBuilder::new(&table_name, table_meta)
|
||||
.ident(table_id)
|
||||
.table_version(INIT_TABLE_VERSION)
|
||||
.table_type(TableType::Base)
|
||||
.catalog_name(catalog_name.to_string())
|
||||
.schema_name(schema_name.to_string())
|
||||
.desc(request.desc)
|
||||
.build()
|
||||
.context(BuildTableInfoSnafu {
|
||||
table_name: &table_full_name,
|
||||
})?;
|
||||
|
||||
let table = Arc::new(
|
||||
ImmutableFileTable::create(
|
||||
&table_full_name,
|
||||
&table_dir,
|
||||
table_info,
|
||||
self.object_store.clone(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
|
||||
logging::info!(
|
||||
"Immutable file engine created table: {} in schema: {}, table_id: {}.",
|
||||
table_name,
|
||||
schema_name,
|
||||
table_id
|
||||
);
|
||||
|
||||
self.tables
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(table_full_name, table.clone());
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
fn get_table_by_full_name(&self, full_name: &str) -> Option<TableRef> {
|
||||
self.tables
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(full_name)
|
||||
.cloned()
|
||||
.map(|table| table as _)
|
||||
}
|
||||
|
||||
fn get_table(&self, table_ref: &TableReference) -> Option<TableRef> {
|
||||
self.get_table_by_full_name(&table_ref.to_string())
|
||||
}
|
||||
|
||||
async fn open_table(
|
||||
&self,
|
||||
_ctx: &EngineContext,
|
||||
request: OpenTableRequest,
|
||||
) -> TableResult<Option<TableRef>> {
|
||||
let OpenTableRequest {
|
||||
catalog_name,
|
||||
schema_name,
|
||||
table_name,
|
||||
..
|
||||
} = request;
|
||||
let table_ref = TableReference {
|
||||
catalog: &catalog_name,
|
||||
schema: &schema_name,
|
||||
table: &table_name,
|
||||
};
|
||||
|
||||
let table_full_name = table_ref.to_string();
|
||||
|
||||
if let Some(table) = self.get_table_by_full_name(&table_full_name) {
|
||||
return Ok(Some(table));
|
||||
}
|
||||
|
||||
let table = {
|
||||
let _lock = self.table_mutex.lock().await;
|
||||
// Checks again, read lock should be enough since we are guarded by the mutex.
|
||||
if let Some(table) = self.get_table_by_full_name(&table_full_name) {
|
||||
return Ok(Some(table));
|
||||
}
|
||||
|
||||
let table_id = request.table_id;
|
||||
let table_dir = table_dir(&catalog_name, &schema_name, table_id);
|
||||
|
||||
let (metadata, table_info) = self
|
||||
.recover_table_manifest_and_info(&table_full_name, &table_dir)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(TableOperationSnafu)?;
|
||||
|
||||
debug!(
|
||||
"Opening table {}, table info recovered: {:?}",
|
||||
table_id, table_info
|
||||
);
|
||||
|
||||
let table = Arc::new(ImmutableFileTable::new(table_info, metadata));
|
||||
|
||||
self.tables
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(table_full_name, table.clone());
|
||||
Some(table as _)
|
||||
};
|
||||
|
||||
logging::info!(
|
||||
"Immutable file engine opened table: {} in schema: {}",
|
||||
table_name,
|
||||
schema_name
|
||||
);
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
async fn drop_table(&self, req: DropTableRequest) -> Result<bool> {
|
||||
let table_ref = TableReference {
|
||||
catalog: &req.catalog_name,
|
||||
schema: &req.schema_name,
|
||||
table: &req.table_name,
|
||||
};
|
||||
|
||||
let table_full_name = table_ref.to_string();
|
||||
let _lock = self.table_mutex.lock().await;
|
||||
if let Some(table) = self.get_table_by_full_name(&table_full_name) {
|
||||
let table_id = table.table_info().ident.table_id;
|
||||
let table_dir = table_dir(&req.catalog_name, &req.schema_name, table_id);
|
||||
|
||||
delete_table_manifest(
|
||||
&table_full_name,
|
||||
&table_manifest_dir(&table_dir),
|
||||
&self.object_store,
|
||||
)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(DropTableSnafu {
|
||||
table_name: &table_full_name,
|
||||
})?;
|
||||
self.tables.write().unwrap().remove(&table_full_name);
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn close(&self) -> TableResult<()> {
|
||||
let _lock = self.table_mutex.lock().await;
|
||||
|
||||
let tables = self.tables.read().unwrap().clone();
|
||||
|
||||
futures::future::try_join_all(tables.values().map(|t| t.close()))
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(table_error::TableOperationSnafu)?;
|
||||
|
||||
// Releases all closed table
|
||||
self.tables.write().unwrap().clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recover_table_manifest_and_info(
|
||||
&self,
|
||||
table_name: &str,
|
||||
table_dir: &str,
|
||||
) -> Result<(ImmutableMetadata, TableInfo)> {
|
||||
ImmutableFileTable::recover_table_info(
|
||||
table_name,
|
||||
&table_manifest_dir(table_dir),
|
||||
&self.object_store,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl EngineInner {
|
||||
pub async fn close_table(&self, table_ref: &TableReference<'_>) -> TableResult<()> {
|
||||
let full_name = table_ref.to_string();
|
||||
|
||||
let _lock = self.table_mutex.lock().await;
|
||||
|
||||
if let Some(table) = self.get_table_by_full_name(&full_name) {
|
||||
table
|
||||
.close()
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(table_error::TableOperationSnafu)?;
|
||||
}
|
||||
|
||||
self.tables.write().unwrap().remove(&full_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
189
src/file-table-engine/src/engine/tests.rs
Normal file
189
src/file-table-engine/src/engine/tests.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME, IMMUTABLE_FILE_ENGINE};
|
||||
use table::engine::{EngineContext, TableEngine, TableReference};
|
||||
use table::requests::{AlterKind, AlterTableRequest, DropTableRequest, OpenTableRequest};
|
||||
use table::{error as table_error, Table};
|
||||
|
||||
use crate::manifest::immutable::manifest_path;
|
||||
use crate::table::immutable::ImmutableFileTable;
|
||||
use crate::test_util::{self, TestEngineComponents, TEST_TABLE_NAME};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_table() {
|
||||
let TestEngineComponents {
|
||||
table_engine,
|
||||
table_ref: table,
|
||||
dir: _dir,
|
||||
..
|
||||
} = test_util::setup_test_engine_and_table("test_get_table").await;
|
||||
let table_info = table.table_info();
|
||||
let table_ref = TableReference {
|
||||
catalog: &table_info.catalog_name,
|
||||
schema: &table_info.schema_name,
|
||||
table: &table_info.name,
|
||||
};
|
||||
|
||||
let got = table_engine
|
||||
.get_table(&EngineContext::default(), &table_ref)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(table.schema(), got.schema());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_open_table() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
let ctx = EngineContext::default();
|
||||
let open_req = OpenTableRequest {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
table_name: test_util::TEST_TABLE_NAME.to_string(),
|
||||
// the test table id is 1
|
||||
table_id: 1,
|
||||
};
|
||||
|
||||
let table_ref = TableReference {
|
||||
catalog: DEFAULT_CATALOG_NAME,
|
||||
schema: DEFAULT_SCHEMA_NAME,
|
||||
table: test_util::TEST_TABLE_NAME,
|
||||
};
|
||||
|
||||
let TestEngineComponents {
|
||||
table_engine,
|
||||
table_ref: table,
|
||||
dir: _dir,
|
||||
..
|
||||
} = test_util::setup_test_engine_and_table("test_open_table").await;
|
||||
|
||||
assert_eq!(IMMUTABLE_FILE_ENGINE, table_engine.name());
|
||||
|
||||
table_engine.close_table(&table_ref).await.unwrap();
|
||||
|
||||
let reopened = table_engine
|
||||
.open_table(&ctx, open_req.clone())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let reopened = reopened
|
||||
.as_any()
|
||||
.downcast_ref::<ImmutableFileTable>()
|
||||
.unwrap();
|
||||
|
||||
let left = table.table_info();
|
||||
let right = reopened.table_info();
|
||||
|
||||
// assert recovered table_info is correct
|
||||
assert_eq!(left, right);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_close_all_table() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
|
||||
let table_ref = TableReference {
|
||||
catalog: DEFAULT_CATALOG_NAME,
|
||||
schema: DEFAULT_SCHEMA_NAME,
|
||||
table: test_util::TEST_TABLE_NAME,
|
||||
};
|
||||
|
||||
let TestEngineComponents {
|
||||
table_engine,
|
||||
dir: _dir,
|
||||
..
|
||||
} = test_util::setup_test_engine_and_table("test_close_all_table").await;
|
||||
|
||||
table_engine.close().await.unwrap();
|
||||
|
||||
let exist = table_engine.table_exists(&EngineContext::default(), &table_ref);
|
||||
|
||||
assert!(!exist);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_alter_table() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
let TestEngineComponents {
|
||||
table_engine,
|
||||
dir: _dir,
|
||||
..
|
||||
} = test_util::setup_test_engine_and_table("test_alter_table").await;
|
||||
|
||||
let alter_req = AlterTableRequest {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
table_name: TEST_TABLE_NAME.to_string(),
|
||||
alter_kind: AlterKind::RenameTable {
|
||||
new_table_name: "foo".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let unsupported = table_engine
|
||||
.alter_table(&EngineContext::default(), alter_req)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
|
||||
assert_matches!(unsupported, table_error::Error::Unsupported { .. })
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_drop_table() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
|
||||
let drop_req = DropTableRequest {
|
||||
catalog_name: DEFAULT_CATALOG_NAME.to_string(),
|
||||
schema_name: DEFAULT_SCHEMA_NAME.to_string(),
|
||||
table_name: TEST_TABLE_NAME.to_string(),
|
||||
};
|
||||
|
||||
let TestEngineComponents {
|
||||
table_engine,
|
||||
object_store,
|
||||
dir: _dir,
|
||||
table_dir,
|
||||
table_ref: table,
|
||||
..
|
||||
} = test_util::setup_test_engine_and_table("test_drop_table").await;
|
||||
|
||||
let table_info = table.table_info();
|
||||
let table_ref = TableReference {
|
||||
catalog: &table_info.catalog_name,
|
||||
schema: &table_info.schema_name,
|
||||
table: &table_info.name,
|
||||
};
|
||||
|
||||
let dropped = table_engine
|
||||
.drop_table(&EngineContext::default(), drop_req)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(dropped);
|
||||
|
||||
let exist = table_engine.table_exists(&EngineContext::default(), &table_ref);
|
||||
assert!(!exist);
|
||||
|
||||
// check table_dir manifest
|
||||
let exist = object_store
|
||||
.is_exist(&manifest_path(&table_dir))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!exist);
|
||||
}
|
||||
161
src/file-table-engine/src/error.rs
Normal file
161
src/file-table-engine/src/error.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
// 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::any::Any;
|
||||
|
||||
use common_error::prelude::*;
|
||||
use serde_json::error::Error as JsonError;
|
||||
use snafu::Location;
|
||||
use table::metadata::{TableInfoBuilderError, TableMetaBuilderError};
|
||||
|
||||
#[derive(Debug, Snafu)]
|
||||
#[snafu(visibility(pub))]
|
||||
pub enum Error {
|
||||
#[snafu(display("Failed to check object from path: {}, source: {}", path, source))]
|
||||
CheckObject {
|
||||
path: String,
|
||||
location: Location,
|
||||
source: object_store::Error,
|
||||
},
|
||||
|
||||
#[snafu(display("Fail to encode object into json, source: {}", source))]
|
||||
EncodeJson {
|
||||
location: Location,
|
||||
source: JsonError,
|
||||
},
|
||||
|
||||
#[snafu(display("Fail to decode object from json, source: {}", source))]
|
||||
DecodeJson {
|
||||
location: Location,
|
||||
source: JsonError,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to drop table, table: {}, source: {}", table_name, source))]
|
||||
DropTable {
|
||||
source: BoxedError,
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to write table manifest, table: {}, source: {}",
|
||||
table_name,
|
||||
source,
|
||||
))]
|
||||
WriteTableManifest {
|
||||
source: object_store::Error,
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Failed to write immutable manifest, path: {}", path))]
|
||||
WriteImmutableManifest { path: String, location: Location },
|
||||
|
||||
#[snafu(display("Failed to delete table table manifest, source: {}", source,))]
|
||||
DeleteTableManifest {
|
||||
source: object_store::Error,
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to read table manifest, table: {}, source: {}",
|
||||
table_name,
|
||||
source,
|
||||
))]
|
||||
ReadTableManifest {
|
||||
source: object_store::Error,
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to build table meta for table: {}, source: {}",
|
||||
table_name,
|
||||
source
|
||||
))]
|
||||
BuildTableMeta {
|
||||
source: TableMetaBuilderError,
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to build table info for table: {}, source: {}",
|
||||
table_name,
|
||||
source
|
||||
))]
|
||||
BuildTableInfo {
|
||||
source: TableInfoBuilderError,
|
||||
table_name: String,
|
||||
location: Location,
|
||||
},
|
||||
|
||||
#[snafu(display("Table already exists: {}", table_name))]
|
||||
TableExists {
|
||||
location: Location,
|
||||
table_name: String,
|
||||
},
|
||||
|
||||
#[snafu(display(
|
||||
"Failed to convert metadata from deserialized data, source: {}",
|
||||
source
|
||||
))]
|
||||
ConvertRaw {
|
||||
#[snafu(backtrace)]
|
||||
source: table::metadata::ConvertError,
|
||||
},
|
||||
|
||||
#[snafu(display("Invalid schema, source: {}", source))]
|
||||
InvalidRawSchema {
|
||||
#[snafu(backtrace)]
|
||||
source: datatypes::error::Error,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
impl ErrorExt for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
use Error::*;
|
||||
|
||||
match self {
|
||||
TableExists { .. }
|
||||
| BuildTableMeta { .. }
|
||||
| BuildTableInfo { .. }
|
||||
| InvalidRawSchema { .. } => StatusCode::InvalidArguments,
|
||||
|
||||
WriteTableManifest { .. }
|
||||
| DeleteTableManifest { .. }
|
||||
| ReadTableManifest { .. }
|
||||
| CheckObject { .. } => StatusCode::StorageUnavailable,
|
||||
|
||||
EncodeJson { .. }
|
||||
| DecodeJson { .. }
|
||||
| ConvertRaw { .. }
|
||||
| DropTable { .. }
|
||||
| WriteImmutableManifest { .. } => StatusCode::Unexpected,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for common_procedure::Error {
|
||||
fn from(e: Error) -> common_procedure::Error {
|
||||
common_procedure::Error::from_error_ext(e)
|
||||
}
|
||||
}
|
||||
23
src/file-table-engine/src/lib.rs
Normal file
23
src/file-table-engine/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
#![feature(assert_matches)]
|
||||
|
||||
pub mod config;
|
||||
pub mod engine;
|
||||
pub mod error;
|
||||
pub mod manifest;
|
||||
pub mod table;
|
||||
#[cfg(any(test, feature = "test"))]
|
||||
pub(crate) mod test_util;
|
||||
20
src/file-table-engine/src/manifest.rs
Normal file
20
src/file-table-engine/src/manifest.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// 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 mod immutable;
|
||||
|
||||
#[inline]
|
||||
pub fn table_manifest_dir(table_dir: &str) -> String {
|
||||
format!("{table_dir}/manifest/")
|
||||
}
|
||||
192
src/file-table-engine/src/manifest/immutable.rs
Normal file
192
src/file-table-engine/src/manifest/immutable.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
// 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 object_store::ObjectStore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{ensure, ResultExt};
|
||||
use table::metadata::RawTableInfo;
|
||||
|
||||
use crate::error::{
|
||||
CheckObjectSnafu, DecodeJsonSnafu, DeleteTableManifestSnafu, EncodeJsonSnafu,
|
||||
ReadTableManifestSnafu, Result, WriteImmutableManifestSnafu, WriteTableManifestSnafu,
|
||||
};
|
||||
|
||||
pub type MetadataVersion = u32;
|
||||
pub const INIT_META_VERSION: MetadataVersion = 0;
|
||||
|
||||
const IMMUTABLE_MANIFEST_FILE: &str = "_immutable_manifest";
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ImmutableMetadata {
|
||||
pub table_info: RawTableInfo,
|
||||
pub version: MetadataVersion,
|
||||
}
|
||||
|
||||
fn encode_metadata(item: &ImmutableMetadata) -> Result<Vec<u8>> {
|
||||
serde_json::to_vec(&item).context(EncodeJsonSnafu)
|
||||
}
|
||||
|
||||
fn decode_metadata(src: &[u8]) -> Result<ImmutableMetadata> {
|
||||
serde_json::from_slice(src).context(DecodeJsonSnafu)
|
||||
}
|
||||
|
||||
pub fn manifest_path(dir: &str) -> String {
|
||||
format!("{}{}", dir, IMMUTABLE_MANIFEST_FILE)
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_table_manifest(
|
||||
table_name: &str,
|
||||
dir: &str,
|
||||
object_store: &ObjectStore,
|
||||
) -> Result<()> {
|
||||
object_store
|
||||
.delete(&manifest_path(dir))
|
||||
.await
|
||||
.context(DeleteTableManifestSnafu { table_name })
|
||||
}
|
||||
|
||||
pub(crate) async fn write_table_manifest(
|
||||
table_name: &str,
|
||||
dir: &str,
|
||||
object_store: &ObjectStore,
|
||||
metadata: &ImmutableMetadata,
|
||||
) -> Result<()> {
|
||||
let path = &manifest_path(dir);
|
||||
let exist = object_store
|
||||
.is_exist(path)
|
||||
.await
|
||||
.context(CheckObjectSnafu { path })?;
|
||||
|
||||
ensure!(!exist, WriteImmutableManifestSnafu { path });
|
||||
|
||||
let bs = encode_metadata(metadata)?;
|
||||
|
||||
object_store
|
||||
.write(path, bs)
|
||||
.await
|
||||
.context(WriteTableManifestSnafu { table_name })
|
||||
}
|
||||
|
||||
pub(crate) async fn read_table_manifest(
|
||||
table_name: &str,
|
||||
dir: &str,
|
||||
object_store: &ObjectStore,
|
||||
) -> Result<ImmutableMetadata> {
|
||||
let path = manifest_path(dir);
|
||||
let bs = object_store
|
||||
.read(&path)
|
||||
.await
|
||||
.context(ReadTableManifestSnafu { table_name })?;
|
||||
|
||||
decode_metadata(&bs)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::assert_matches::assert_matches;
|
||||
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
use crate::manifest::table_manifest_dir;
|
||||
use crate::test_util::{build_test_table_metadata, new_test_object_store, TEST_TABLE_NAME};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_write_table_manifest() {
|
||||
let (_dir, store) = new_test_object_store("test_write_table_manifest");
|
||||
let metadata = build_test_table_metadata();
|
||||
|
||||
write_table_manifest(
|
||||
TEST_TABLE_NAME,
|
||||
&table_manifest_dir(TEST_TABLE_NAME),
|
||||
&store,
|
||||
&metadata,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// try to overwrite immutable manifest
|
||||
let write_immutable = write_table_manifest(
|
||||
TEST_TABLE_NAME,
|
||||
&table_manifest_dir(TEST_TABLE_NAME),
|
||||
&store,
|
||||
&metadata,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_matches!(write_immutable, Error::WriteImmutableManifest { .. })
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_table_manifest() {
|
||||
let (_dir, store) = new_test_object_store("test_read_table_manifest");
|
||||
let metadata = build_test_table_metadata();
|
||||
|
||||
write_table_manifest(
|
||||
TEST_TABLE_NAME,
|
||||
&table_manifest_dir(TEST_TABLE_NAME),
|
||||
&store,
|
||||
&metadata,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let read = read_table_manifest(
|
||||
TEST_TABLE_NAME,
|
||||
&table_manifest_dir(TEST_TABLE_NAME),
|
||||
&store,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(read, metadata);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_non_exist_table_manifest() {
|
||||
let (_dir, store) = new_test_object_store("test_read_non_exist_table_manifest");
|
||||
let not_fount = read_table_manifest(
|
||||
TEST_TABLE_NAME,
|
||||
&table_manifest_dir(TEST_TABLE_NAME),
|
||||
&store,
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_matches!(not_fount, Error::ReadTableManifest { .. })
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_table_manifest() {
|
||||
let (_dir, store) = new_test_object_store("test_delete_table_manifest");
|
||||
|
||||
let metadata = build_test_table_metadata();
|
||||
let table_dir = &table_manifest_dir(TEST_TABLE_NAME);
|
||||
write_table_manifest(TEST_TABLE_NAME, table_dir, &store, &metadata)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let exist = store.is_exist(&manifest_path(table_dir)).await.unwrap();
|
||||
|
||||
assert!(exist);
|
||||
|
||||
delete_table_manifest(TEST_TABLE_NAME, table_dir, &store)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let exist = store.is_exist(&manifest_path(table_dir)).await.unwrap();
|
||||
|
||||
assert!(!exist);
|
||||
}
|
||||
}
|
||||
15
src/file-table-engine/src/table.rs
Normal file
15
src/file-table-engine/src/table.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 mod immutable;
|
||||
130
src/file-table-engine/src/table/immutable.rs
Normal file
130
src/file-table-engine/src/table/immutable.rs
Normal file
@@ -0,0 +1,130 @@
|
||||
// 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::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use common_query::physical_plan::PhysicalPlanRef;
|
||||
use common_query::prelude::Expr;
|
||||
use datatypes::schema::SchemaRef;
|
||||
use object_store::ObjectStore;
|
||||
use snafu::ResultExt;
|
||||
use store_api::storage::RegionNumber;
|
||||
use table::error::Result as TableResult;
|
||||
use table::metadata::{RawTableInfo, TableInfo, TableInfoRef, TableType};
|
||||
use table::Table;
|
||||
|
||||
use crate::error::{ConvertRawSnafu, Result};
|
||||
use crate::manifest::immutable::{
|
||||
read_table_manifest, write_table_manifest, ImmutableMetadata, INIT_META_VERSION,
|
||||
};
|
||||
use crate::manifest::table_manifest_dir;
|
||||
|
||||
pub struct ImmutableFileTable {
|
||||
metadata: ImmutableMetadata,
|
||||
// currently, it's immutable
|
||||
table_info: Arc<TableInfo>,
|
||||
}
|
||||
|
||||
pub type ImmutableFileTableRef = Arc<ImmutableFileTable>;
|
||||
|
||||
#[async_trait]
|
||||
impl Table for ImmutableFileTable {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn schema(&self) -> SchemaRef {
|
||||
self.table_info().meta.schema.clone()
|
||||
}
|
||||
|
||||
fn table_type(&self) -> TableType {
|
||||
self.table_info().table_type
|
||||
}
|
||||
|
||||
fn table_info(&self) -> TableInfoRef {
|
||||
self.table_info.clone()
|
||||
}
|
||||
|
||||
async fn scan(
|
||||
&self,
|
||||
_projection: Option<&Vec<usize>>,
|
||||
_filters: &[Expr],
|
||||
_limit: Option<usize>,
|
||||
) -> TableResult<PhysicalPlanRef> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn flush(
|
||||
&self,
|
||||
_region_number: Option<RegionNumber>,
|
||||
_wait: Option<bool>,
|
||||
) -> TableResult<()> {
|
||||
// nothing to flush
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn close(&self) -> TableResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ImmutableFileTable {
|
||||
#[inline]
|
||||
pub fn metadata(&self) -> &ImmutableMetadata {
|
||||
&self.metadata
|
||||
}
|
||||
|
||||
pub(crate) fn new(table_info: TableInfo, metadata: ImmutableMetadata) -> Self {
|
||||
Self {
|
||||
metadata,
|
||||
table_info: Arc::new(table_info),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
table_name: &str,
|
||||
table_dir: &str,
|
||||
table_info: TableInfo,
|
||||
object_store: ObjectStore,
|
||||
) -> Result<ImmutableFileTable> {
|
||||
let metadata = ImmutableMetadata {
|
||||
table_info: RawTableInfo::from(table_info.clone()),
|
||||
version: INIT_META_VERSION,
|
||||
};
|
||||
|
||||
write_table_manifest(
|
||||
table_name,
|
||||
&table_manifest_dir(table_dir),
|
||||
&object_store,
|
||||
&metadata,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ImmutableFileTable::new(table_info, metadata))
|
||||
}
|
||||
|
||||
pub(crate) async fn recover_table_info(
|
||||
table_name: &str,
|
||||
table_dir: &str,
|
||||
object_store: &ObjectStore,
|
||||
) -> Result<(ImmutableMetadata, TableInfo)> {
|
||||
let metadata = read_table_manifest(table_name, table_dir, object_store).await?;
|
||||
let table_info =
|
||||
TableInfo::try_from(metadata.table_info.clone()).context(ConvertRawSnafu)?;
|
||||
|
||||
Ok((metadata, table_info))
|
||||
}
|
||||
}
|
||||
144
src/file-table-engine/src/test_util.rs
Normal file
144
src/file-table-engine/src/test_util.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use common_catalog::consts::IMMUTABLE_FILE_ENGINE;
|
||||
use common_test_util::temp_dir::{create_temp_dir, TempDir};
|
||||
use datatypes::prelude::ConcreteDataType;
|
||||
use datatypes::schema::{ColumnSchema, RawSchema, Schema, SchemaBuilder, SchemaRef};
|
||||
use object_store::services::Fs;
|
||||
use object_store::ObjectStore;
|
||||
use table::engine::{table_dir, EngineContext, TableEngine};
|
||||
use table::metadata::{RawTableInfo, TableInfo, TableInfoBuilder, TableMetaBuilder, TableType};
|
||||
use table::requests::{CreateTableRequest, TableOptions};
|
||||
use table::TableRef;
|
||||
|
||||
use crate::config::EngineConfig;
|
||||
use crate::engine::immutable::ImmutableFileTableEngine;
|
||||
use crate::manifest::immutable::ImmutableMetadata;
|
||||
|
||||
pub const TEST_TABLE_NAME: &str = "demo";
|
||||
|
||||
pub fn new_test_object_store(prefix: &str) -> (TempDir, ObjectStore) {
|
||||
let dir = create_temp_dir(prefix);
|
||||
let store_dir = dir.path().to_string_lossy();
|
||||
let mut builder = Fs::default();
|
||||
builder.root(&store_dir);
|
||||
(dir, ObjectStore::new(builder).unwrap().finish())
|
||||
}
|
||||
|
||||
pub fn test_schema() -> Schema {
|
||||
let column_schemas = vec![
|
||||
ColumnSchema::new("host", ConcreteDataType::string_datatype(), false),
|
||||
// Nullable value column: cpu
|
||||
ColumnSchema::new("cpu", ConcreteDataType::float64_datatype(), true),
|
||||
// Non-null value column: memory
|
||||
ColumnSchema::new("memory", ConcreteDataType::float64_datatype(), false),
|
||||
ColumnSchema::new(
|
||||
"ts",
|
||||
ConcreteDataType::timestamp_datatype(common_time::timestamp::TimeUnit::Millisecond),
|
||||
true,
|
||||
)
|
||||
.with_time_index(true),
|
||||
];
|
||||
|
||||
SchemaBuilder::try_from(column_schemas)
|
||||
.unwrap()
|
||||
.build()
|
||||
.expect("ts must be timestamp column")
|
||||
}
|
||||
|
||||
pub fn build_test_table_info() -> TableInfo {
|
||||
let schema = test_schema();
|
||||
let table_meta = TableMetaBuilder::new_external_table()
|
||||
.schema(Arc::new(schema))
|
||||
.engine(IMMUTABLE_FILE_ENGINE)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
TableInfoBuilder::new(TEST_TABLE_NAME, table_meta)
|
||||
.table_version(0)
|
||||
.table_type(TableType::Base)
|
||||
.catalog_name("greptime".to_string())
|
||||
.schema_name("public".to_string())
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn build_test_table_metadata() -> ImmutableMetadata {
|
||||
let table_info = build_test_table_info();
|
||||
ImmutableMetadata {
|
||||
table_info: RawTableInfo::from(table_info),
|
||||
version: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestEngineComponents {
|
||||
pub table_engine: ImmutableFileTableEngine,
|
||||
pub table_ref: TableRef,
|
||||
pub schema_ref: SchemaRef,
|
||||
pub object_store: ObjectStore,
|
||||
pub table_dir: String,
|
||||
pub dir: TempDir,
|
||||
}
|
||||
|
||||
pub fn new_create_request(schema: SchemaRef) -> CreateTableRequest {
|
||||
CreateTableRequest {
|
||||
id: 1,
|
||||
catalog_name: "greptime".to_string(),
|
||||
schema_name: "public".to_string(),
|
||||
table_name: TEST_TABLE_NAME.to_string(),
|
||||
desc: Some("a test table".to_string()),
|
||||
schema: RawSchema::from(&*schema),
|
||||
region_numbers: vec![0],
|
||||
create_if_not_exists: true,
|
||||
primary_key_indices: vec![0],
|
||||
table_options: TableOptions::default(),
|
||||
engine: IMMUTABLE_FILE_ENGINE.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn setup_test_engine_and_table(prefix: &str) -> TestEngineComponents {
|
||||
let (dir, object_store) = new_test_object_store(prefix);
|
||||
|
||||
let table_engine = ImmutableFileTableEngine::new(EngineConfig::default(), object_store.clone());
|
||||
|
||||
let schema_ref = Arc::new(test_schema());
|
||||
|
||||
let table_ref = table_engine
|
||||
.create_table(
|
||||
&EngineContext::default(),
|
||||
new_create_request(schema_ref.clone()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let table_info = table_ref.table_info();
|
||||
|
||||
let table_dir = table_dir(
|
||||
&table_info.catalog_name,
|
||||
&table_info.schema_name,
|
||||
table_info.ident.table_id,
|
||||
);
|
||||
|
||||
TestEngineComponents {
|
||||
table_engine,
|
||||
table_ref,
|
||||
schema_ref,
|
||||
object_store,
|
||||
table_dir,
|
||||
dir,
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,6 @@ openmetrics-parser = "0.4"
|
||||
partition = { path = "../partition" }
|
||||
prost.workspace = true
|
||||
query = { path = "../query" }
|
||||
rustls = "0.20"
|
||||
script = { path = "../script", features = ["python"], optional = true }
|
||||
serde = "1.0"
|
||||
serde_json = "1.0"
|
||||
|
||||
@@ -32,12 +32,14 @@ use catalog::{
|
||||
RegisterSchemaRequest, RegisterSystemTableRequest, RegisterTableRequest, RenameTableRequest,
|
||||
SchemaProvider, SchemaProviderRef,
|
||||
};
|
||||
use common_catalog::consts::{DEFAULT_CATALOG_NAME, DEFAULT_SCHEMA_NAME};
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_telemetry::error;
|
||||
use futures::StreamExt;
|
||||
use meta_client::rpc::TableName;
|
||||
use partition::manager::PartitionRuleManagerRef;
|
||||
use snafu::prelude::*;
|
||||
use table::table::numbers::NumbersTable;
|
||||
use table::TableRef;
|
||||
|
||||
use crate::datanode::DatanodeClients;
|
||||
@@ -373,17 +375,21 @@ impl SchemaProvider for FrontendSchemaProvider {
|
||||
|
||||
std::thread::spawn(|| {
|
||||
common_runtime::block_on_read(async move {
|
||||
let mut tables = vec![];
|
||||
if catalog_name == DEFAULT_CATALOG_NAME && schema_name == DEFAULT_SCHEMA_NAME {
|
||||
tables.push("numbers".to_string());
|
||||
}
|
||||
|
||||
let key = build_table_global_prefix(catalog_name, schema_name);
|
||||
let mut iter = backend.range(key.as_bytes());
|
||||
let mut res = HashSet::new();
|
||||
|
||||
while let Some(r) = iter.next().await {
|
||||
let Kv(k, _) = r?;
|
||||
let key = TableGlobalKey::parse(String::from_utf8_lossy(&k))
|
||||
.context(InvalidCatalogValueSnafu)?;
|
||||
res.insert(key.table_name);
|
||||
tables.push(key.table_name);
|
||||
}
|
||||
Ok(res.into_iter().collect())
|
||||
Ok(tables)
|
||||
})
|
||||
})
|
||||
.join()
|
||||
@@ -391,6 +397,13 @@ impl SchemaProvider for FrontendSchemaProvider {
|
||||
}
|
||||
|
||||
async fn table(&self, name: &str) -> catalog::error::Result<Option<TableRef>> {
|
||||
if self.catalog_name == DEFAULT_CATALOG_NAME
|
||||
&& self.schema_name == DEFAULT_SCHEMA_NAME
|
||||
&& name == "numbers"
|
||||
{
|
||||
return Ok(Some(Arc::new(NumbersTable::default())));
|
||||
}
|
||||
|
||||
let table_global_key = TableGlobalKey {
|
||||
catalog_name: self.catalog_name.clone(),
|
||||
schema_name: self.schema_name.clone(),
|
||||
|
||||
@@ -36,7 +36,6 @@ use common_catalog::consts::MITO_ENGINE;
|
||||
use common_error::ext::BoxedError;
|
||||
use common_grpc::channel_manager::{ChannelConfig, ChannelManager};
|
||||
use common_query::Output;
|
||||
use common_recordbatch::RecordBatches;
|
||||
use common_telemetry::logging::{debug, info};
|
||||
use common_telemetry::timer;
|
||||
use datafusion::sql::sqlparser::ast::ObjectName;
|
||||
@@ -50,7 +49,6 @@ use partition::manager::PartitionRuleManager;
|
||||
use partition::route::TableRoutes;
|
||||
use query::parser::{PromQuery, QueryLanguageParser, QueryStatement};
|
||||
use query::query_engine::options::{validate_catalog_and_schema, QueryOptions};
|
||||
use query::query_engine::StatementHandlerRef;
|
||||
use query::{QueryEngineFactory, QueryEngineRef};
|
||||
use servers::error as server_error;
|
||||
use servers::error::{ExecuteQuerySnafu, ParsePromQLSnafu};
|
||||
@@ -61,22 +59,18 @@ use servers::query_handler::sql::SqlQueryHandler;
|
||||
use servers::query_handler::{
|
||||
InfluxdbLineProtocolHandler, OpentsdbProtocolHandler, PrometheusProtocolHandler, ScriptHandler,
|
||||
};
|
||||
use session::context::{QueryContext, QueryContextRef};
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::prelude::*;
|
||||
use sql::dialect::GenericDialect;
|
||||
use sql::parser::ParserContext;
|
||||
use sql::statements::copy::CopyTable;
|
||||
use sql::statements::describe::DescribeTable;
|
||||
use sql::statements::statement::Statement;
|
||||
use sql::statements::tql::Tql;
|
||||
|
||||
use crate::catalog::FrontendCatalogManager;
|
||||
use crate::datanode::DatanodeClients;
|
||||
use crate::error::{
|
||||
self, CatalogSnafu, DescribeStatementSnafu, Error, ExecLogicalPlanSnafu, ExecutePromqlSnafu,
|
||||
ExecuteStatementSnafu, ExternalSnafu, InvalidInsertRequestSnafu, MissingMetasrvOptsSnafu,
|
||||
NotSupportedSnafu, ParseQuerySnafu, ParseSqlSnafu, PlanStatementSnafu, Result,
|
||||
SqlExecInterceptedSnafu, TableNotFoundSnafu,
|
||||
self, Error, ExecutePromqlSnafu, ExternalSnafu, InvalidInsertRequestSnafu,
|
||||
MissingMetasrvOptsSnafu, ParseSqlSnafu, PlanStatementSnafu, Result, SqlExecInterceptedSnafu,
|
||||
};
|
||||
use crate::expr_factory::{CreateExprFactoryRef, DefaultCreateExprFactory};
|
||||
use crate::frontend::FrontendOptions;
|
||||
@@ -84,6 +78,7 @@ use crate::instance::standalone::StandaloneGrpcQueryHandler;
|
||||
use crate::metric;
|
||||
use crate::script::ScriptExecutor;
|
||||
use crate::server::{start_server, ServerHandlers, Services};
|
||||
use crate::statement::StatementExecutor;
|
||||
|
||||
#[async_trait]
|
||||
pub trait FrontendInstance:
|
||||
@@ -107,7 +102,7 @@ pub type FrontendInstanceRef = Arc<dyn FrontendInstance>;
|
||||
pub struct Instance {
|
||||
catalog_manager: CatalogManagerRef,
|
||||
script_executor: Arc<ScriptExecutor>,
|
||||
statement_handler: StatementHandlerRef,
|
||||
statement_executor: Arc<StatementExecutor>,
|
||||
query_engine: QueryEngineRef,
|
||||
grpc_query_handler: GrpcQueryHandlerRef<Error>,
|
||||
|
||||
@@ -154,11 +149,17 @@ impl Instance {
|
||||
let script_executor =
|
||||
Arc::new(ScriptExecutor::new(catalog_manager.clone(), query_engine.clone()).await?);
|
||||
|
||||
let statement_executor = Arc::new(StatementExecutor::new(
|
||||
catalog_manager.clone(),
|
||||
query_engine.clone(),
|
||||
dist_instance.clone(),
|
||||
));
|
||||
|
||||
Ok(Instance {
|
||||
catalog_manager,
|
||||
script_executor,
|
||||
create_expr_factory: Arc::new(DefaultCreateExprFactory),
|
||||
statement_handler: dist_instance.clone(),
|
||||
statement_executor,
|
||||
query_engine,
|
||||
grpc_query_handler: dist_instance,
|
||||
plugins: plugins.clone(),
|
||||
@@ -201,11 +202,18 @@ impl Instance {
|
||||
let query_engine = dn_instance.query_engine();
|
||||
let script_executor =
|
||||
Arc::new(ScriptExecutor::new(catalog_manager.clone(), query_engine.clone()).await?);
|
||||
|
||||
let statement_executor = Arc::new(StatementExecutor::new(
|
||||
catalog_manager.clone(),
|
||||
query_engine.clone(),
|
||||
dn_instance.clone(),
|
||||
));
|
||||
|
||||
Ok(Instance {
|
||||
catalog_manager: catalog_manager.clone(),
|
||||
script_executor,
|
||||
create_expr_factory: Arc::new(DefaultCreateExprFactory),
|
||||
statement_handler: dn_instance.clone(),
|
||||
statement_executor,
|
||||
query_engine,
|
||||
grpc_query_handler: StandaloneGrpcQueryHandler::arc(dn_instance.clone()),
|
||||
plugins: Default::default(),
|
||||
@@ -235,10 +243,17 @@ impl Instance {
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let statement_executor = Arc::new(StatementExecutor::new(
|
||||
catalog_manager.clone(),
|
||||
query_engine.clone(),
|
||||
dist_instance.clone(),
|
||||
));
|
||||
|
||||
Instance {
|
||||
catalog_manager,
|
||||
script_executor,
|
||||
statement_handler: dist_instance.clone(),
|
||||
statement_executor,
|
||||
query_engine,
|
||||
create_expr_factory: Arc::new(DefaultCreateExprFactory),
|
||||
grpc_query_handler: dist_instance,
|
||||
@@ -389,21 +404,6 @@ impl Instance {
|
||||
.await
|
||||
}
|
||||
|
||||
fn handle_use(&self, db: String, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
let catalog = &query_ctx.current_catalog();
|
||||
ensure!(
|
||||
self.catalog_manager
|
||||
.schema(catalog, &db)
|
||||
.context(error::CatalogSnafu)?
|
||||
.is_some(),
|
||||
error::SchemaNotFoundSnafu { schema_info: &db }
|
||||
);
|
||||
|
||||
query_ctx.set_current_schema(&db);
|
||||
|
||||
Ok(Output::RecordBatches(RecordBatches::empty()))
|
||||
}
|
||||
|
||||
pub fn set_plugins(&mut self, map: Arc<Plugins>) {
|
||||
self.plugins = map;
|
||||
}
|
||||
@@ -418,6 +418,11 @@ impl Instance {
|
||||
.context(error::ShutdownServerSnafu)
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn statement_executor(&self) -> Arc<StatementExecutor> {
|
||||
self.statement_executor.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -437,104 +442,11 @@ fn parse_stmt(sql: &str) -> Result<Vec<Statement>> {
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub(crate) async fn plan_exec(
|
||||
&self,
|
||||
stmt: QueryStatement,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
let planner = self.query_engine.planner();
|
||||
let plan = planner
|
||||
.plan(stmt, query_ctx.clone())
|
||||
.await
|
||||
.context(PlanStatementSnafu)?;
|
||||
self.query_engine
|
||||
.execute(plan, query_ctx)
|
||||
.await
|
||||
.context(ExecLogicalPlanSnafu)
|
||||
}
|
||||
|
||||
async fn execute_tql(&self, tql: Tql, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
let plan = match tql {
|
||||
Tql::Eval(eval) => {
|
||||
let promql = PromQuery {
|
||||
start: eval.start,
|
||||
end: eval.end,
|
||||
step: eval.step,
|
||||
query: eval.query,
|
||||
};
|
||||
let stmt = QueryLanguageParser::parse_promql(&promql).context(ParseQuerySnafu)?;
|
||||
self.query_engine
|
||||
.planner()
|
||||
.plan(stmt, query_ctx.clone())
|
||||
.await
|
||||
.context(PlanStatementSnafu)?
|
||||
}
|
||||
Tql::Explain(_) => unimplemented!(),
|
||||
};
|
||||
self.query_engine
|
||||
.execute(plan, query_ctx)
|
||||
.await
|
||||
.context(ExecLogicalPlanSnafu)
|
||||
}
|
||||
|
||||
async fn describe_table(
|
||||
&self,
|
||||
stmt: DescribeTable,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
let (catalog, schema, table) = table_idents_to_full_name(stmt.name(), query_ctx)
|
||||
.map_err(BoxedError::new)
|
||||
.context(ExternalSnafu)?;
|
||||
|
||||
let table = self
|
||||
.catalog_manager
|
||||
.table(&catalog, &schema, &table)
|
||||
.await
|
||||
.context(CatalogSnafu)?
|
||||
.with_context(|| TableNotFoundSnafu {
|
||||
table_name: stmt.name().to_string(),
|
||||
})?;
|
||||
|
||||
query::sql::describe_table(table).context(DescribeStatementSnafu)
|
||||
}
|
||||
|
||||
async fn query_statement(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
check_permission(self.plugins.clone(), &stmt, &query_ctx)?;
|
||||
|
||||
match stmt {
|
||||
Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
|
||||
self.plan_exec(QueryStatement::Sql(stmt), query_ctx).await
|
||||
}
|
||||
|
||||
// For performance consideration, only "insert with select" is executed by query engine.
|
||||
// Plain insert ("insert with values") is still executed directly in statement.
|
||||
Statement::Insert(ref insert) if insert.is_insert_select() => {
|
||||
self.plan_exec(QueryStatement::Sql(stmt), query_ctx).await
|
||||
}
|
||||
|
||||
Statement::Tql(tql) => self.execute_tql(tql, query_ctx).await,
|
||||
|
||||
Statement::DescribeTable(stmt) => self.describe_table(stmt, query_ctx).await,
|
||||
|
||||
Statement::CreateDatabase(_)
|
||||
| Statement::CreateExternalTable(_)
|
||||
| Statement::ShowDatabases(_)
|
||||
| Statement::CreateTable(_)
|
||||
| Statement::ShowTables(_)
|
||||
| Statement::Insert(_)
|
||||
| Statement::Alter(_)
|
||||
| Statement::DropTable(_)
|
||||
| Statement::Copy(_) => self
|
||||
.statement_handler
|
||||
.handle_statement(QueryStatement::Sql(stmt), query_ctx)
|
||||
.await
|
||||
.context(ExecuteStatementSnafu),
|
||||
Statement::Use(db) => self.handle_use(db, query_ctx),
|
||||
Statement::ShowCreateTable(_) => NotSupportedSnafu {
|
||||
feat: format!("{stmt:?}"),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
let stmt = QueryStatement::Sql(stmt);
|
||||
self.statement_executor.execute_stmt(stmt, query_ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,13 +496,16 @@ impl SqlQueryHandler for Instance {
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_promql_query(&self, query: &PromQuery, _: QueryContextRef) -> Vec<Result<Output>> {
|
||||
let result =
|
||||
PromHandler::do_query(self, query)
|
||||
.await
|
||||
.with_context(|_| ExecutePromqlSnafu {
|
||||
query: format!("{query:?}"),
|
||||
});
|
||||
async fn do_promql_query(
|
||||
&self,
|
||||
query: &PromQuery,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Vec<Result<Output>> {
|
||||
let result = PromHandler::do_query(self, query, query_ctx)
|
||||
.await
|
||||
.with_context(|_| ExecutePromqlSnafu {
|
||||
query: format!("{query:?}"),
|
||||
});
|
||||
vec![result]
|
||||
}
|
||||
|
||||
@@ -626,11 +541,16 @@ impl SqlQueryHandler for Instance {
|
||||
|
||||
#[async_trait]
|
||||
impl PromHandler for Instance {
|
||||
async fn do_query(&self, query: &PromQuery) -> server_error::Result<Output> {
|
||||
async fn do_query(
|
||||
&self,
|
||||
query: &PromQuery,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> server_error::Result<Output> {
|
||||
let stmt = QueryLanguageParser::parse_promql(query).with_context(|_| ParsePromQLSnafu {
|
||||
query: query.clone(),
|
||||
})?;
|
||||
self.plan_exec(stmt, QueryContext::arc())
|
||||
self.statement_executor
|
||||
.execute_stmt(stmt, query_ctx)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.with_context(|_| ExecuteQuerySnafu {
|
||||
@@ -732,6 +652,7 @@ mod tests {
|
||||
|
||||
use api::v1::column::Values;
|
||||
use catalog::helper::{TableGlobalKey, TableGlobalValue};
|
||||
use common_recordbatch::RecordBatches;
|
||||
use datatypes::prelude::{ConcreteDataType, Value};
|
||||
use datatypes::schema::{ColumnDefaultConstraint, ColumnSchema};
|
||||
use query::query_engine::options::QueryOptions;
|
||||
|
||||
@@ -44,9 +44,7 @@ use meta_client::rpc::{
|
||||
};
|
||||
use partition::partition::{PartitionBound, PartitionDef};
|
||||
use query::error::QueryExecutionSnafu;
|
||||
use query::parser::QueryStatement;
|
||||
use query::query_engine::StatementHandler;
|
||||
use query::sql::{show_databases, show_tables};
|
||||
use query::query_engine::SqlStatementExecutor;
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::{ensure, OptionExt, ResultExt};
|
||||
use sql::ast::Value as SqlValue;
|
||||
@@ -62,10 +60,10 @@ use crate::catalog::FrontendCatalogManager;
|
||||
use crate::datanode::DatanodeClients;
|
||||
use crate::error::{
|
||||
self, AlterExprToRequestSnafu, CatalogEntrySerdeSnafu, CatalogSnafu, ColumnDataTypeSnafu,
|
||||
DeserializePartitionSnafu, InvokeDatanodeSnafu, NotSupportedSnafu, ParseSqlSnafu,
|
||||
PrimaryKeyNotFoundSnafu, RequestDatanodeSnafu, RequestMetaSnafu, Result, SchemaExistsSnafu,
|
||||
StartMetaClientSnafu, TableAlreadyExistSnafu, TableNotFoundSnafu, TableSnafu,
|
||||
ToTableInsertRequestSnafu, UnrecognizedTableOptionSnafu,
|
||||
DeserializePartitionSnafu, InvokeDatanodeSnafu, ParseSqlSnafu, PrimaryKeyNotFoundSnafu,
|
||||
RequestDatanodeSnafu, RequestMetaSnafu, Result, SchemaExistsSnafu, StartMetaClientSnafu,
|
||||
TableAlreadyExistSnafu, TableNotFoundSnafu, TableSnafu, ToTableInsertRequestSnafu,
|
||||
UnrecognizedTableOptionSnafu,
|
||||
};
|
||||
use crate::expr_factory;
|
||||
use crate::table::DistTable;
|
||||
@@ -95,6 +93,7 @@ impl DistInstance {
|
||||
create_table: &mut CreateTableExpr,
|
||||
partitions: Option<Partitions>,
|
||||
) -> Result<TableRef> {
|
||||
let _timer = common_telemetry::timer!(crate::metrics::DIST_CREATE_TABLE);
|
||||
let table_name = TableName::new(
|
||||
&create_table.catalog_name,
|
||||
&create_table.schema_name,
|
||||
@@ -191,6 +190,7 @@ impl DistInstance {
|
||||
create_table, datanode, create_expr_for_region.region_ids,
|
||||
);
|
||||
|
||||
let _timer = common_telemetry::timer!(crate::metrics::DIST_CREATE_TABLE_IN_DATANODE);
|
||||
client
|
||||
.create(create_expr_for_region)
|
||||
.await
|
||||
@@ -342,10 +342,6 @@ impl DistInstance {
|
||||
let table_name = TableName::new(catalog, schema, table);
|
||||
return self.drop_table(table_name).await;
|
||||
}
|
||||
Statement::ShowDatabases(stmt) => show_databases(stmt, self.catalog_manager.clone()),
|
||||
Statement::ShowTables(stmt) => {
|
||||
show_tables(stmt, self.catalog_manager.clone(), query_ctx)
|
||||
}
|
||||
Statement::Insert(insert) => {
|
||||
let (catalog, schema, table) =
|
||||
table_idents_to_full_name(insert.table_name(), query_ctx.clone())
|
||||
@@ -466,6 +462,7 @@ impl DistInstance {
|
||||
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();
|
||||
@@ -521,21 +518,16 @@ impl DistInstance {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl StatementHandler for DistInstance {
|
||||
async fn handle_statement(
|
||||
impl SqlStatementExecutor for DistInstance {
|
||||
async fn execute_sql(
|
||||
&self,
|
||||
stmt: QueryStatement,
|
||||
stmt: Statement,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> query::error::Result<Output> {
|
||||
match stmt {
|
||||
QueryStatement::Sql(stmt) => self.handle_statement(stmt, query_ctx).await,
|
||||
QueryStatement::Promql(_) => NotSupportedSnafu {
|
||||
feat: "distributed execute promql".to_string(),
|
||||
}
|
||||
.fail(),
|
||||
}
|
||||
.map_err(BoxedError::new)
|
||||
.context(QueryExecutionSnafu)
|
||||
self.handle_statement(stmt, query_ctx)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(QueryExecutionSnafu)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,16 +685,12 @@ fn find_partition_columns(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use itertools::Itertools;
|
||||
use query::parser::QueryLanguageParser;
|
||||
use query::query_engine::StatementHandlerRef;
|
||||
use session::context::QueryContext;
|
||||
use sql::dialect::GenericDialect;
|
||||
use sql::parser::ParserContext;
|
||||
use sql::statements::statement::Statement;
|
||||
|
||||
use super::*;
|
||||
use crate::instance::parse_stmt;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_parse_partitions() {
|
||||
@@ -744,107 +732,4 @@ ENGINE=mito",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_sql(instance: &Arc<DistInstance>, sql: &str) -> Output {
|
||||
let stmt = parse_stmt(sql).unwrap().remove(0);
|
||||
instance
|
||||
.handle_statement(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_show_databases() {
|
||||
let instance = crate::tests::create_distributed_instance("test_show_databases").await;
|
||||
let dist_instance = &instance.dist_instance;
|
||||
|
||||
let sql = "create database test_show_databases";
|
||||
let output = handle_sql(dist_instance, sql).await;
|
||||
match output {
|
||||
Output::AffectedRows(rows) => assert_eq!(rows, 1),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let sql = "show databases";
|
||||
let output = handle_sql(dist_instance, sql).await;
|
||||
match output {
|
||||
Output::RecordBatches(r) => {
|
||||
let expected1 = vec![
|
||||
"+---------------------+",
|
||||
"| Schemas |",
|
||||
"+---------------------+",
|
||||
"| public |",
|
||||
"| test_show_databases |",
|
||||
"+---------------------+",
|
||||
]
|
||||
.into_iter()
|
||||
.join("\n");
|
||||
let expected2 = vec![
|
||||
"+---------------------+",
|
||||
"| Schemas |",
|
||||
"+---------------------+",
|
||||
"| test_show_databases |",
|
||||
"| public |",
|
||||
"+---------------------+",
|
||||
]
|
||||
.into_iter()
|
||||
.join("\n");
|
||||
let lines = r.pretty_print().unwrap();
|
||||
assert!(lines == expected1 || lines == expected2)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_show_tables() {
|
||||
let instance = crate::tests::create_distributed_instance("test_show_tables").await;
|
||||
let dist_instance = &instance.dist_instance;
|
||||
let datanode_instances = instance.datanodes;
|
||||
|
||||
let sql = "create database test_show_tables";
|
||||
handle_sql(dist_instance, sql).await;
|
||||
|
||||
let sql = "
|
||||
CREATE TABLE greptime.test_show_tables.dist_numbers (
|
||||
ts BIGINT,
|
||||
n INT,
|
||||
TIME INDEX (ts),
|
||||
)
|
||||
PARTITION BY RANGE COLUMNS (n) (
|
||||
PARTITION r0 VALUES LESS THAN (10),
|
||||
PARTITION r1 VALUES LESS THAN (20),
|
||||
PARTITION r2 VALUES LESS THAN (50),
|
||||
PARTITION r3 VALUES LESS THAN (MAXVALUE),
|
||||
)
|
||||
ENGINE=mito";
|
||||
handle_sql(dist_instance, sql).await;
|
||||
|
||||
async fn assert_show_tables(handler: StatementHandlerRef) {
|
||||
let sql = "show tables in test_show_tables";
|
||||
let stmt = QueryLanguageParser::parse_sql(sql).unwrap();
|
||||
let output = handler
|
||||
.handle_statement(stmt, QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
match output {
|
||||
Output::RecordBatches(r) => {
|
||||
let expected = r#"+--------------+
|
||||
| Tables |
|
||||
+--------------+
|
||||
| dist_numbers |
|
||||
+--------------+"#;
|
||||
assert_eq!(r.pretty_print().unwrap(), expected);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
assert_show_tables(dist_instance.clone()).await;
|
||||
|
||||
// Asserts that new table is created in Datanode as well.
|
||||
for x in datanode_instances.values() {
|
||||
assert_show_tables(x.clone()).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ pub mod grpc;
|
||||
pub mod influxdb;
|
||||
pub mod instance;
|
||||
pub(crate) mod metric;
|
||||
mod metrics;
|
||||
pub mod mysql;
|
||||
pub mod opentsdb;
|
||||
pub mod postgres;
|
||||
@@ -31,6 +32,7 @@ pub mod prom;
|
||||
pub mod prometheus;
|
||||
mod script;
|
||||
mod server;
|
||||
pub(crate) mod statement;
|
||||
mod table;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
22
src/frontend/src/metrics.rs
Normal file
22
src/frontend/src/metrics.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! 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";
|
||||
132
src/frontend/src/statement.rs
Normal file
132
src/frontend/src/statement.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod describe;
|
||||
mod show;
|
||||
mod tql;
|
||||
|
||||
use catalog::CatalogManagerRef;
|
||||
use common_query::Output;
|
||||
use common_recordbatch::RecordBatches;
|
||||
use query::parser::QueryStatement;
|
||||
use query::query_engine::SqlStatementExecutorRef;
|
||||
use query::QueryEngineRef;
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::{ensure, ResultExt};
|
||||
use sql::statements::statement::Statement;
|
||||
|
||||
use crate::error::{
|
||||
CatalogSnafu, ExecLogicalPlanSnafu, ExecuteStatementSnafu, PlanStatementSnafu, Result,
|
||||
SchemaNotFoundSnafu,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct StatementExecutor {
|
||||
catalog_manager: CatalogManagerRef,
|
||||
query_engine: QueryEngineRef,
|
||||
sql_stmt_executor: SqlStatementExecutorRef,
|
||||
}
|
||||
|
||||
impl StatementExecutor {
|
||||
pub(crate) fn new(
|
||||
catalog_manager: CatalogManagerRef,
|
||||
query_engine: QueryEngineRef,
|
||||
sql_stmt_executor: SqlStatementExecutorRef,
|
||||
) -> Self {
|
||||
Self {
|
||||
catalog_manager,
|
||||
query_engine,
|
||||
sql_stmt_executor,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute_stmt(
|
||||
&self,
|
||||
stmt: QueryStatement,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
match stmt {
|
||||
QueryStatement::Sql(stmt) => self.execute_sql(stmt, query_ctx).await,
|
||||
QueryStatement::Promql(_) => self.plan_exec(stmt, query_ctx).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_sql(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
match stmt {
|
||||
Statement::Query(_) | Statement::Explain(_) | Statement::Delete(_) => {
|
||||
self.plan_exec(QueryStatement::Sql(stmt), query_ctx).await
|
||||
}
|
||||
|
||||
// For performance consideration, only "insert with select" is executed by query engine.
|
||||
// Plain insert ("insert with values") is still executed directly in statement.
|
||||
Statement::Insert(ref insert) if insert.is_insert_select() => {
|
||||
self.plan_exec(QueryStatement::Sql(stmt), query_ctx).await
|
||||
}
|
||||
|
||||
Statement::Tql(tql) => self.execute_tql(tql, query_ctx).await,
|
||||
|
||||
Statement::DescribeTable(stmt) => self.describe_table(stmt, query_ctx).await,
|
||||
|
||||
Statement::Use(db) => self.handle_use(db, query_ctx),
|
||||
|
||||
Statement::ShowDatabases(stmt) => self.show_databases(stmt),
|
||||
|
||||
Statement::ShowTables(stmt) => self.show_tables(stmt, query_ctx),
|
||||
|
||||
Statement::CreateDatabase(_)
|
||||
| Statement::CreateTable(_)
|
||||
| Statement::CreateExternalTable(_)
|
||||
| Statement::Insert(_)
|
||||
| Statement::Alter(_)
|
||||
| Statement::DropTable(_)
|
||||
| Statement::Copy(_)
|
||||
| Statement::ShowCreateTable(_) => self
|
||||
.sql_stmt_executor
|
||||
.execute_sql(stmt, query_ctx)
|
||||
.await
|
||||
.context(ExecuteStatementSnafu),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn plan_exec(
|
||||
&self,
|
||||
stmt: QueryStatement,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
let planner = self.query_engine.planner();
|
||||
let plan = planner
|
||||
.plan(stmt, query_ctx.clone())
|
||||
.await
|
||||
.context(PlanStatementSnafu)?;
|
||||
self.query_engine
|
||||
.execute(plan, query_ctx)
|
||||
.await
|
||||
.context(ExecLogicalPlanSnafu)
|
||||
}
|
||||
|
||||
fn handle_use(&self, db: String, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
let catalog = &query_ctx.current_catalog();
|
||||
ensure!(
|
||||
self.catalog_manager
|
||||
.schema(catalog, &db)
|
||||
.context(CatalogSnafu)?
|
||||
.is_some(),
|
||||
SchemaNotFoundSnafu { schema_info: &db }
|
||||
);
|
||||
|
||||
query_ctx.set_current_schema(&db);
|
||||
|
||||
Ok(Output::RecordBatches(RecordBatches::empty()))
|
||||
}
|
||||
}
|
||||
48
src/frontend/src/statement/describe.rs
Normal file
48
src/frontend/src/statement/describe.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2023 Greptime Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use common_error::prelude::BoxedError;
|
||||
use common_query::Output;
|
||||
use datanode::instance::sql::table_idents_to_full_name;
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use sql::statements::describe::DescribeTable;
|
||||
|
||||
use crate::error::{
|
||||
CatalogSnafu, DescribeStatementSnafu, ExternalSnafu, Result, TableNotFoundSnafu,
|
||||
};
|
||||
use crate::statement::StatementExecutor;
|
||||
|
||||
impl StatementExecutor {
|
||||
pub(super) async fn describe_table(
|
||||
&self,
|
||||
stmt: DescribeTable,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
let (catalog, schema, table) = table_idents_to_full_name(stmt.name(), query_ctx)
|
||||
.map_err(BoxedError::new)
|
||||
.context(ExternalSnafu)?;
|
||||
|
||||
let table = self
|
||||
.catalog_manager
|
||||
.table(&catalog, &schema, &table)
|
||||
.await
|
||||
.context(CatalogSnafu)?
|
||||
.with_context(|| TableNotFoundSnafu {
|
||||
table_name: stmt.name().to_string(),
|
||||
})?;
|
||||
|
||||
query::sql::describe_table(table).context(DescribeStatementSnafu)
|
||||
}
|
||||
}
|
||||
37
src/frontend/src/statement/show.rs
Normal file
37
src/frontend/src/statement/show.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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_query::Output;
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::ResultExt;
|
||||
use sql::statements::show::{ShowDatabases, ShowTables};
|
||||
|
||||
use crate::error::{ExecuteStatementSnafu, Result};
|
||||
use crate::statement::StatementExecutor;
|
||||
|
||||
impl StatementExecutor {
|
||||
pub(super) fn show_databases(&self, stmt: ShowDatabases) -> Result<Output> {
|
||||
query::sql::show_databases(stmt, self.catalog_manager.clone())
|
||||
.context(ExecuteStatementSnafu)
|
||||
}
|
||||
|
||||
pub(super) fn show_tables(
|
||||
&self,
|
||||
stmt: ShowTables,
|
||||
query_ctx: QueryContextRef,
|
||||
) -> Result<Output> {
|
||||
query::sql::show_tables(stmt, self.catalog_manager.clone(), query_ctx)
|
||||
.context(ExecuteStatementSnafu)
|
||||
}
|
||||
}
|
||||
55
src/frontend/src/statement/tql.rs
Normal file
55
src/frontend/src/statement/tql.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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_query::Output;
|
||||
use query::parser::{PromQuery, QueryLanguageParser};
|
||||
use session::context::QueryContextRef;
|
||||
use snafu::ResultExt;
|
||||
use sql::statements::tql::Tql;
|
||||
|
||||
use crate::error::{
|
||||
ExecLogicalPlanSnafu, NotSupportedSnafu, ParseQuerySnafu, PlanStatementSnafu, Result,
|
||||
};
|
||||
use crate::statement::StatementExecutor;
|
||||
|
||||
impl StatementExecutor {
|
||||
pub(super) async fn execute_tql(&self, tql: Tql, query_ctx: QueryContextRef) -> Result<Output> {
|
||||
let plan = match tql {
|
||||
Tql::Eval(eval) => {
|
||||
let promql = PromQuery {
|
||||
start: eval.start,
|
||||
end: eval.end,
|
||||
step: eval.step,
|
||||
query: eval.query,
|
||||
};
|
||||
let stmt = QueryLanguageParser::parse_promql(&promql).context(ParseQuerySnafu)?;
|
||||
self.query_engine
|
||||
.planner()
|
||||
.plan(stmt, query_ctx.clone())
|
||||
.await
|
||||
.context(PlanStatementSnafu)?
|
||||
}
|
||||
Tql::Explain(_) => {
|
||||
return NotSupportedSnafu {
|
||||
feat: "TQL EXPLAIN",
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
};
|
||||
self.query_engine
|
||||
.execute(plan, query_ctx)
|
||||
.await
|
||||
.context(ExecLogicalPlanSnafu)
|
||||
}
|
||||
}
|
||||
@@ -382,11 +382,11 @@ mod test {
|
||||
use catalog::error::Result;
|
||||
use catalog::remote::{KvBackend, ValueIter};
|
||||
use common_query::physical_plan::DfPhysicalPlanAdapter;
|
||||
use common_query::DfPhysicalPlan;
|
||||
use common_recordbatch::adapter::RecordBatchStreamAdapter;
|
||||
use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
|
||||
use datafusion::physical_plan::expressions::{col as physical_col, PhysicalSortExpr};
|
||||
use datafusion::physical_plan::sorts::sort::SortExec;
|
||||
use datafusion::physical_plan::ExecutionPlan;
|
||||
use datafusion::prelude::SessionContext;
|
||||
use datafusion::sql::sqlparser;
|
||||
use datafusion_expr::expr_fn::{and, binary_expr, col, or};
|
||||
@@ -764,15 +764,13 @@ mod test {
|
||||
let merge =
|
||||
CoalescePartitionsExec::new(Arc::new(DfPhysicalPlanAdapter(table_scan.clone())));
|
||||
|
||||
let sort = SortExec::try_new(
|
||||
let sort = SortExec::new(
|
||||
vec![PhysicalSortExpr {
|
||||
expr: physical_col("a", table_scan.schema().arrow_schema()).unwrap(),
|
||||
options: SortOptions::default(),
|
||||
}],
|
||||
Arc::new(merge),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
assert_eq!(sort.output_partitioning().partition_count(), 1);
|
||||
|
||||
let session_ctx = SessionContext::new();
|
||||
|
||||
@@ -324,8 +324,6 @@ async fn test_execute_query(instance: Arc<dyn MockInstance>) {
|
||||
|
||||
#[apply(both_instances_cases)]
|
||||
async fn test_execute_show_databases_tables(instance: Arc<dyn MockInstance>) {
|
||||
let is_distributed_mode = instance.is_distributed_mode();
|
||||
|
||||
let instance = instance.frontend();
|
||||
|
||||
let output = execute_sql(&instance, "show databases").await;
|
||||
@@ -358,24 +356,14 @@ async fn test_execute_show_databases_tables(instance: Arc<dyn MockInstance>) {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let expected = if is_distributed_mode {
|
||||
"\
|
||||
+---------+
|
||||
| Tables |
|
||||
+---------+
|
||||
| scripts |
|
||||
+---------+\
|
||||
"
|
||||
} else {
|
||||
"\
|
||||
let expected = "\
|
||||
+---------+
|
||||
| Tables |
|
||||
+---------+
|
||||
| numbers |
|
||||
| scripts |
|
||||
+---------+\
|
||||
"
|
||||
};
|
||||
";
|
||||
let output = execute_sql(&instance, "show tables").await;
|
||||
check_unordered_output_stream(output, expected).await;
|
||||
|
||||
@@ -385,17 +373,7 @@ async fn test_execute_show_databases_tables(instance: Arc<dyn MockInstance>) {
|
||||
).await;
|
||||
|
||||
let output = execute_sql(&instance, "show tables").await;
|
||||
let expected = if is_distributed_mode {
|
||||
"\
|
||||
+---------+
|
||||
| Tables |
|
||||
+---------+
|
||||
| demo |
|
||||
| scripts |
|
||||
+---------+\
|
||||
"
|
||||
} else {
|
||||
"\
|
||||
let expected = "\
|
||||
+---------+
|
||||
| Tables |
|
||||
+---------+
|
||||
@@ -403,8 +381,7 @@ async fn test_execute_show_databases_tables(instance: Arc<dyn MockInstance>) {
|
||||
| numbers |
|
||||
| scripts |
|
||||
+---------+\
|
||||
"
|
||||
};
|
||||
";
|
||||
check_unordered_output_stream(output, expected).await;
|
||||
|
||||
// show tables like [string]
|
||||
@@ -686,8 +663,7 @@ async fn test_insert_with_default_value(instance: Arc<dyn MockInstance>) {
|
||||
test_insert_with_default_value_for_type(instance.frontend(), "bigint").await;
|
||||
}
|
||||
|
||||
// should apply to both instance. tracked in #1294
|
||||
#[apply(standalone_instance_case)]
|
||||
#[apply(both_instances_cases)]
|
||||
async fn test_use_database(instance: Arc<dyn MockInstance>) {
|
||||
let instance = instance.frontend();
|
||||
|
||||
@@ -928,8 +904,6 @@ async fn test_execute_copy_from_s3(instance: Arc<dyn MockInstance>) {
|
||||
|
||||
#[apply(both_instances_cases)]
|
||||
async fn test_information_schema(instance: Arc<dyn MockInstance>) {
|
||||
let is_distributed_mode = instance.is_distributed_mode();
|
||||
|
||||
let instance = instance.frontend();
|
||||
|
||||
let sql = "create table another_table(i bigint time index)";
|
||||
@@ -942,24 +916,14 @@ async fn test_information_schema(instance: Arc<dyn MockInstance>) {
|
||||
let sql = "select table_catalog, table_schema, table_name, table_type from information_schema.tables where table_type != 'SYSTEM VIEW' order by table_name";
|
||||
|
||||
let output = execute_sql(&instance, sql).await;
|
||||
let expected = if is_distributed_mode {
|
||||
"\
|
||||
+---------------+--------------------+------------+------------+
|
||||
| table_catalog | table_schema | table_name | table_type |
|
||||
+---------------+--------------------+------------+------------+
|
||||
| greptime | public | scripts | BASE TABLE |
|
||||
| greptime | information_schema | tables | VIEW |
|
||||
+---------------+--------------------+------------+------------+"
|
||||
} else {
|
||||
"\
|
||||
let expected = "\
|
||||
+---------------+--------------------+------------+------------+
|
||||
| table_catalog | table_schema | table_name | table_type |
|
||||
+---------------+--------------------+------------+------------+
|
||||
| greptime | public | numbers | BASE TABLE |
|
||||
| greptime | public | scripts | BASE TABLE |
|
||||
| greptime | information_schema | tables | VIEW |
|
||||
+---------------+--------------------+------------+------------+"
|
||||
};
|
||||
+---------------+--------------------+------------+------------+";
|
||||
check_output_stream(output, expected).await;
|
||||
|
||||
let output = execute_sql_with(&instance, sql, query_ctx).await;
|
||||
|
||||
@@ -53,6 +53,7 @@ async fn create_insert_query_assert(
|
||||
eval_stmt.lookback_delta = lookback;
|
||||
|
||||
let query_output = instance
|
||||
.statement_executor()
|
||||
.plan_exec(QueryStatement::Promql(eval_stmt), QueryContext::arc())
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -468,12 +469,12 @@ async fn stddev_by_label(instance: Arc<dyn MockInstance>) {
|
||||
unix_epoch_plus_100s(),
|
||||
Duration::from_secs(60),
|
||||
Duration::from_secs(0),
|
||||
"+----------+---------------------+-----------------------------+\
|
||||
\n| instance | ts | STDDEV(http_requests.value) |\
|
||||
\n+----------+---------------------+-----------------------------+\
|
||||
\n| 0 | 1970-01-01T00:00:00 | 258.19888974716116 |\
|
||||
\n| 1 | 1970-01-01T00:00:00 | 258.19888974716116 |\
|
||||
\n+----------+---------------------+-----------------------------+",
|
||||
"+----------+---------------------+--------------------------------+\
|
||||
\n| instance | ts | STDDEVPOP(http_requests.value) |\
|
||||
\n+----------+---------------------+--------------------------------+\
|
||||
\n| 0 | 1970-01-01T00:00:00 | 223.606797749979 |\
|
||||
\n| 1 | 1970-01-01T00:00:00 | 223.606797749979 |\
|
||||
\n+----------+---------------------+--------------------------------+",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -26,28 +26,18 @@ use crate::tests::{
|
||||
|
||||
pub(crate) trait MockInstance {
|
||||
fn frontend(&self) -> Arc<Instance>;
|
||||
|
||||
fn is_distributed_mode(&self) -> bool;
|
||||
}
|
||||
|
||||
impl MockInstance for MockStandaloneInstance {
|
||||
fn frontend(&self) -> Arc<Instance> {
|
||||
self.instance.clone()
|
||||
}
|
||||
|
||||
fn is_distributed_mode(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl MockInstance for MockDistributedInstance {
|
||||
fn frontend(&self) -> Arc<Instance> {
|
||||
self.frontend.clone()
|
||||
}
|
||||
|
||||
fn is_distributed_mode(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn standalone() -> Arc<dyn MockInstance> {
|
||||
|
||||
@@ -72,7 +72,7 @@ async fn run() {
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(res) = receiver.message().await.unwrap() {
|
||||
event!(Level::INFO, "heartbeat response: {:#?}", res);
|
||||
event!(Level::TRACE, "heartbeat response: {:#?}", res);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ impl From<UnlockRequest> for PbUnlockRequest {
|
||||
fn from(req: UnlockRequest) -> Self {
|
||||
Self {
|
||||
header: None,
|
||||
key: req.key.to_vec(),
|
||||
key: req.key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
use api::v1::meta::{BatchPutRequest, HeartbeatRequest, KeyValue};
|
||||
use common_telemetry::{info, warn};
|
||||
use common_telemetry::{trace, warn};
|
||||
use common_time::util as time_util;
|
||||
use tokio::sync::mpsc::{self, Sender};
|
||||
|
||||
@@ -76,7 +76,7 @@ impl HeartbeatHandler for KeepLeaseHandler {
|
||||
node_addr: peer.addr.clone(),
|
||||
};
|
||||
|
||||
info!("Receive a heartbeat: {key:?}, {value:?}");
|
||||
trace!("Receive a heartbeat: {key:?}, {value:?}");
|
||||
|
||||
let key = key.try_into()?;
|
||||
let value = value.try_into()?;
|
||||
|
||||
@@ -45,6 +45,6 @@ mod tests {
|
||||
let res = health_handler.handle(path, ¶ms).await.unwrap();
|
||||
|
||||
assert!(res.status().is_success());
|
||||
assert_eq!(HTTP_OK.to_owned(), res.body().to_owned());
|
||||
assert_eq!(HTTP_OK.to_owned(), res.body().clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,9 +673,9 @@ mod tests {
|
||||
let batch_get: BatchGet = req.try_into().unwrap();
|
||||
let keys = batch_get.keys;
|
||||
|
||||
assert_eq!(b"k1".to_vec(), keys.get(0).unwrap().to_vec());
|
||||
assert_eq!(b"k2".to_vec(), keys.get(1).unwrap().to_vec());
|
||||
assert_eq!(b"k3".to_vec(), keys.get(2).unwrap().to_vec());
|
||||
assert_eq!(b"k1".to_vec(), keys.get(0).unwrap().clone());
|
||||
assert_eq!(b"k2".to_vec(), keys.get(1).unwrap().clone());
|
||||
assert_eq!(b"k3".to_vec(), keys.get(2).unwrap().clone());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -707,9 +707,9 @@ mod tests {
|
||||
let batch_delete: BatchDelete = req.try_into().unwrap();
|
||||
|
||||
assert_eq!(batch_delete.keys.len(), 3);
|
||||
assert_eq!(b"k1".to_vec(), batch_delete.keys.get(0).unwrap().to_vec());
|
||||
assert_eq!(b"k2".to_vec(), batch_delete.keys.get(1).unwrap().to_vec());
|
||||
assert_eq!(b"k3".to_vec(), batch_delete.keys.get(2).unwrap().to_vec());
|
||||
assert_eq!(b"k1".to_vec(), batch_delete.keys.get(0).unwrap().clone());
|
||||
assert_eq!(b"k2".to_vec(), batch_delete.keys.get(1).unwrap().clone());
|
||||
assert_eq!(b"k3".to_vec(), batch_delete.keys.get(2).unwrap().clone());
|
||||
assert!(batch_delete.options.is_some());
|
||||
}
|
||||
|
||||
|
||||
@@ -48,13 +48,14 @@ use table::{error as table_error, Result as TableResult, Table};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::config::EngineConfig;
|
||||
use crate::engine::procedure::{AlterMitoTable, CreateMitoTable};
|
||||
use crate::engine::procedure::{AlterMitoTable, CreateMitoTable, DropMitoTable};
|
||||
use crate::error::{
|
||||
self, BuildColumnDescriptorSnafu, BuildColumnFamilyDescriptorSnafu, BuildRegionDescriptorSnafu,
|
||||
BuildRowKeyDescriptorSnafu, InvalidPrimaryKeySnafu, InvalidRawSchemaSnafu,
|
||||
MissingTimestampIndexSnafu, RegionNotFoundSnafu, Result, TableExistsSnafu,
|
||||
};
|
||||
use crate::manifest::TableManifest;
|
||||
use crate::metrics;
|
||||
use crate::table::MitoTable;
|
||||
pub const INIT_COLUMN_ID: ColumnId = 0;
|
||||
const INIT_TABLE_VERSION: TableVersion = 0;
|
||||
@@ -95,6 +96,7 @@ impl<S: StorageEngine> TableEngine for MitoEngine<S> {
|
||||
ctx: &EngineContext,
|
||||
request: CreateTableRequest,
|
||||
) -> TableResult<TableRef> {
|
||||
let _timer = common_telemetry::timer!(metrics::MITO_CREATE_TABLE_ELAPSED);
|
||||
self.inner
|
||||
.create_table(ctx, request)
|
||||
.await
|
||||
@@ -107,6 +109,7 @@ impl<S: StorageEngine> TableEngine for MitoEngine<S> {
|
||||
ctx: &EngineContext,
|
||||
request: OpenTableRequest,
|
||||
) -> TableResult<Option<TableRef>> {
|
||||
let _timer = common_telemetry::timer!(metrics::MITO_OPEN_TABLE_ELAPSED);
|
||||
self.inner
|
||||
.open_table(ctx, request)
|
||||
.await
|
||||
@@ -119,6 +122,7 @@ impl<S: StorageEngine> TableEngine for MitoEngine<S> {
|
||||
ctx: &EngineContext,
|
||||
req: AlterTableRequest,
|
||||
) -> TableResult<TableRef> {
|
||||
let _timer = common_telemetry::timer!(metrics::MITO_ALTER_TABLE_ELAPSED);
|
||||
self.inner
|
||||
.alter_table(ctx, req)
|
||||
.await
|
||||
@@ -185,6 +189,19 @@ impl<S: StorageEngine> TableEngineProcedure for MitoEngine<S> {
|
||||
);
|
||||
Ok(procedure)
|
||||
}
|
||||
|
||||
fn drop_table_procedure(
|
||||
&self,
|
||||
_ctx: &EngineContext,
|
||||
request: DropTableRequest,
|
||||
) -> TableResult<BoxedProcedure> {
|
||||
let procedure = Box::new(
|
||||
DropMitoTable::new(request, self.inner.clone())
|
||||
.map_err(BoxedError::new)
|
||||
.context(table_error::TableOperationSnafu)?,
|
||||
);
|
||||
Ok(procedure)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MitoEngineInner<S: StorageEngine> {
|
||||
@@ -410,12 +427,14 @@ impl<S: StorageEngine> MitoEngineInner<S> {
|
||||
compaction_time_window: request.table_options.compaction_time_window,
|
||||
};
|
||||
|
||||
let region = self
|
||||
.storage_engine
|
||||
.create_region(&StorageEngineContext::default(), region_descriptor, &opts)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(error::CreateRegionSnafu)?;
|
||||
let region = {
|
||||
let _timer = common_telemetry::timer!(crate::metrics::MITO_CREATE_REGION_ELAPSED);
|
||||
self.storage_engine
|
||||
.create_region(&StorageEngineContext::default(), region_descriptor, &opts)
|
||||
.await
|
||||
.map_err(BoxedError::new)
|
||||
.context(error::CreateRegionSnafu)?
|
||||
};
|
||||
info!(
|
||||
"Mito engine created region: {}, id: {}",
|
||||
region.name(),
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
|
||||
mod alter;
|
||||
mod create;
|
||||
mod drop;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) use alter::AlterMitoTable;
|
||||
use common_procedure::ProcedureManager;
|
||||
pub(crate) use create::CreateMitoTable;
|
||||
pub(crate) use drop::DropMitoTable;
|
||||
use store_api::storage::StorageEngine;
|
||||
|
||||
use crate::engine::MitoEngineInner;
|
||||
@@ -34,7 +36,8 @@ pub(crate) fn register_procedure_loaders<S: StorageEngine>(
|
||||
) {
|
||||
// The procedure names are expected to be unique, so we just panic on error.
|
||||
CreateMitoTable::register_loader(engine_inner.clone(), procedure_manager);
|
||||
AlterMitoTable::register_loader(engine_inner, procedure_manager);
|
||||
AlterMitoTable::register_loader(engine_inner.clone(), procedure_manager);
|
||||
DropMitoTable::register_loader(engine_inner, procedure_manager);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -371,7 +371,7 @@ mod tests {
|
||||
let TestEnv {
|
||||
table_engine,
|
||||
dir: _dir,
|
||||
} = procedure_test_util::setup_test_engine("create_procedure").await;
|
||||
} = procedure_test_util::setup_test_engine("add_column").await;
|
||||
let schema = Arc::new(test_util::schema_for_test());
|
||||
let request = test_util::new_create_request(schema.clone());
|
||||
|
||||
@@ -426,7 +426,7 @@ mod tests {
|
||||
let TestEnv {
|
||||
table_engine,
|
||||
dir: _dir,
|
||||
} = procedure_test_util::setup_test_engine("create_procedure").await;
|
||||
} = procedure_test_util::setup_test_engine("drop_column").await;
|
||||
let schema = Arc::new(test_util::schema_for_test());
|
||||
let request = test_util::new_create_request(schema.clone());
|
||||
|
||||
@@ -491,7 +491,7 @@ mod tests {
|
||||
let TestEnv {
|
||||
table_engine,
|
||||
dir: _dir,
|
||||
} = procedure_test_util::setup_test_engine("create_procedure").await;
|
||||
} = procedure_test_util::setup_test_engine("rename").await;
|
||||
let schema = Arc::new(test_util::schema_for_test());
|
||||
let create_request = test_util::new_create_request(schema.clone());
|
||||
|
||||
|
||||
228
src/mito/src/engine/procedure/drop.rs
Normal file
228
src/mito/src/engine/procedure/drop.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
// 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 async_trait::async_trait;
|
||||
use common_procedure::error::{Error, FromJsonSnafu, ToJsonSnafu};
|
||||
use common_procedure::{Context, LockKey, Procedure, ProcedureManager, Result, Status};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use snafu::{OptionExt, ResultExt};
|
||||
use store_api::storage::StorageEngine;
|
||||
use table::engine::TableReference;
|
||||
use table::requests::DropTableRequest;
|
||||
use table::Table;
|
||||
|
||||
use crate::engine::MitoEngineInner;
|
||||
use crate::error::TableNotFoundSnafu;
|
||||
use crate::table::MitoTable;
|
||||
|
||||
/// Procedure to drop a [MitoTable].
|
||||
pub(crate) struct DropMitoTable<S: StorageEngine> {
|
||||
data: DropTableData,
|
||||
engine_inner: Arc<MitoEngineInner<S>>,
|
||||
table: Arc<MitoTable<S::Region>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S: StorageEngine> Procedure for DropMitoTable<S> {
|
||||
fn type_name(&self) -> &str {
|
||||
Self::TYPE_NAME
|
||||
}
|
||||
|
||||
async fn execute(&mut self, _ctx: &Context) -> Result<Status> {
|
||||
match self.data.state {
|
||||
DropTableState::Prepare => self.on_prepare(),
|
||||
DropTableState::CloseRegions => self.on_close_regions().await,
|
||||
}
|
||||
}
|
||||
|
||||
fn dump(&self) -> Result<String> {
|
||||
let json = serde_json::to_string(&self.data).context(ToJsonSnafu)?;
|
||||
Ok(json)
|
||||
}
|
||||
|
||||
fn lock_key(&self) -> LockKey {
|
||||
let table_ref = self.data.table_ref();
|
||||
let info = self.table.table_info();
|
||||
let keys = info
|
||||
.meta
|
||||
.region_numbers
|
||||
.iter()
|
||||
.map(|number| format!("{table_ref}/region-{number}"));
|
||||
LockKey::new(keys)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: StorageEngine> DropMitoTable<S> {
|
||||
const TYPE_NAME: &str = "mito::DropMitoTable";
|
||||
|
||||
/// Returns a new [DropMitoTable].
|
||||
pub(crate) fn new(
|
||||
request: DropTableRequest,
|
||||
engine_inner: Arc<MitoEngineInner<S>>,
|
||||
) -> Result<Self> {
|
||||
let data = DropTableData {
|
||||
state: DropTableState::Prepare,
|
||||
request,
|
||||
};
|
||||
let table_ref = data.table_ref();
|
||||
let table =
|
||||
engine_inner
|
||||
.get_mito_table(&table_ref)
|
||||
.with_context(|| TableNotFoundSnafu {
|
||||
table_name: table_ref.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(DropMitoTable {
|
||||
data,
|
||||
engine_inner,
|
||||
table,
|
||||
})
|
||||
}
|
||||
|
||||
/// Register the loader of this procedure to the `procedure_manager`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics on error.
|
||||
pub(crate) fn register_loader(
|
||||
engine_inner: Arc<MitoEngineInner<S>>,
|
||||
procedure_manager: &dyn ProcedureManager,
|
||||
) {
|
||||
procedure_manager
|
||||
.register_loader(
|
||||
Self::TYPE_NAME,
|
||||
Box::new(move |data| {
|
||||
Self::from_json(data, engine_inner.clone()).map(|p| Box::new(p) as _)
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Recover the procedure from json.
|
||||
fn from_json(json: &str, engine_inner: Arc<MitoEngineInner<S>>) -> Result<Self> {
|
||||
let data: DropTableData = serde_json::from_str(json).context(FromJsonSnafu)?;
|
||||
let table_ref = data.table_ref();
|
||||
let table =
|
||||
engine_inner
|
||||
.get_mito_table(&table_ref)
|
||||
.with_context(|| TableNotFoundSnafu {
|
||||
table_name: table_ref.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(DropMitoTable {
|
||||
data,
|
||||
engine_inner,
|
||||
table,
|
||||
})
|
||||
}
|
||||
|
||||
/// Prepare table info.
|
||||
fn on_prepare(&mut self) -> Result<Status> {
|
||||
self.data.state = DropTableState::CloseRegions;
|
||||
|
||||
Ok(Status::executing(true))
|
||||
}
|
||||
|
||||
/// Close all regions.
|
||||
async fn on_close_regions(&mut self) -> Result<Status> {
|
||||
// Remove the table from the engine to avoid further access from users.
|
||||
let table_ref = self.data.table_ref();
|
||||
self.engine_inner
|
||||
.tables
|
||||
.write()
|
||||
.unwrap()
|
||||
.remove(&table_ref.to_string());
|
||||
|
||||
// Close the table to close all regions. Closing a region is idempotent.
|
||||
self.table.close().await.map_err(Error::from_error_ext)?;
|
||||
|
||||
// TODO(yingwen): Currently, DROP TABLE doesn't remove data. We can
|
||||
// write a drop meta update to the table and remove all files in the
|
||||
// background.
|
||||
Ok(Status::Done)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents each step while dropping table in the mito engine.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
enum DropTableState {
|
||||
/// Prepare to drop table.
|
||||
Prepare,
|
||||
/// Close regions of this table.
|
||||
CloseRegions,
|
||||
}
|
||||
|
||||
/// Serializable data of [DropMitoTable].
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct DropTableData {
|
||||
state: DropTableState,
|
||||
request: DropTableRequest,
|
||||
}
|
||||
|
||||
impl DropTableData {
|
||||
fn table_ref(&self) -> TableReference {
|
||||
TableReference {
|
||||
catalog: &self.request.catalog_name,
|
||||
schema: &self.request.schema_name,
|
||||
table: &self.request.table_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use table::engine::{EngineContext, TableEngine, TableEngineProcedure};
|
||||
|
||||
use super::*;
|
||||
use crate::engine::procedure::procedure_test_util::{self, TestEnv};
|
||||
use crate::table::test_util;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_procedure_drop_table() {
|
||||
common_telemetry::init_default_ut_logging();
|
||||
|
||||
let TestEnv {
|
||||
table_engine,
|
||||
dir: _dir,
|
||||
} = procedure_test_util::setup_test_engine("add_column").await;
|
||||
let schema = Arc::new(test_util::schema_for_test());
|
||||
let request = test_util::new_create_request(schema.clone());
|
||||
|
||||
let engine_ctx = EngineContext::default();
|
||||
// Create table first.
|
||||
let mut procedure = table_engine
|
||||
.create_table_procedure(&engine_ctx, request.clone())
|
||||
.unwrap();
|
||||
procedure_test_util::execute_procedure_until_done(&mut procedure).await;
|
||||
|
||||
// Drop the table.
|
||||
let request = test_util::new_drop_request();
|
||||
let mut procedure = table_engine
|
||||
.drop_table_procedure(&engine_ctx, request.clone())
|
||||
.unwrap();
|
||||
procedure_test_util::execute_procedure_until_done(&mut procedure).await;
|
||||
|
||||
// The table is dropped.
|
||||
let table_ref = TableReference {
|
||||
catalog: &request.catalog_name,
|
||||
schema: &request.schema_name,
|
||||
table: &request.table_name,
|
||||
};
|
||||
assert!(table_engine
|
||||
.get_table(&engine_ctx, &table_ref)
|
||||
.unwrap()
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,5 @@ pub mod config;
|
||||
pub mod engine;
|
||||
pub mod error;
|
||||
mod manifest;
|
||||
mod metrics;
|
||||
pub mod table;
|
||||
|
||||
27
src/mito/src/metrics.rs
Normal file
27
src/mito/src/metrics.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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.
|
||||
|
||||
// ! mito table engine metrics
|
||||
|
||||
/// Elapsed time of creating tables
|
||||
pub const MITO_CREATE_TABLE_ELAPSED: &str = "datanode.mito.create_table";
|
||||
/// Elapsed time of creating single region when creating tables.
|
||||
pub const MITO_CREATE_REGION_ELAPSED: &str = "datanode.mito.create_table.create_region";
|
||||
/// Elapsed time of updating table manifest when creating tables.
|
||||
pub const MITO_CREATE_TABLE_UPDATE_MANIFEST_ELAPSED: &str =
|
||||
"datanode.mito.create_table.update_manifest";
|
||||
/// Elapsed time of opening tables
|
||||
pub const MITO_OPEN_TABLE_ELAPSED: &str = "datanode.mito.open_table";
|
||||
/// Elapsed time of altering tables
|
||||
pub const MITO_ALTER_TABLE_ELAPSED: &str = "datanode.mito.alter_table";
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user