Files
greptimedb/src/servers/tests/mod.rs
T
discord9andNing Sun 94d7e2c7fc feat!: upgrade DataFusion to 55 (#8555)
* feat!: upgrade DataFusion dependencies to 55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* refactor: migrate DataFusion 55 APIs

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: preserve table function planning behavior

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: preserve PostgreSQL query compatibility

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: preserve distributed execution plan behavior

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: cover DataFusion 55 behavior regressions

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: update DataFusion 55 SQLness expectations

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: complete DataFusion 55 test API migration

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: address DataFusion 55 CI regressions

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: address remaining DataFusion 55 regressions

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: adapt latest base code to DataFusion 55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: normalize environment-specific DataFusion 55 plans

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: align final DataFusion 55 expectations

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: isolate DataFusion 55 regression cases

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: preserve empty result schema in timestamp widening

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: preserve JSON source column order

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* chore: use released DataFusion 55 integrations

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: adapt latest execution plan mock to DataFusion 55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: pin DataFusion recursive schema and date repairs

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix(promql): align dictionary temporality match keys

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: retain Greptime DataFusion fork behaviors on version 55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: restore ordinary function error expectations

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: refresh distributed count compatibility plan

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix(query): adapt last-row cast hint to DataFusion 55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: refresh instant last-row empty results for Arrow 59

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* style: simplify DataFusion expression visitor imports

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: restore sorting and PostgreSQL column-order assertions

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix(function): restore primitive numeric coercion signatures

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* refactor(function): share geo integer signature types

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: cover timestamp widening overflow boundaries

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: fix decimal coercion regression imports

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix(function): preserve scalar count_hash NULL state semantics

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: simplify decimal clamp case type inference

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: retain historical count_hash wrapper result

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: restore timestamp widening equality and IN pruning

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: carry upstream aggregate dynamic filter correctness fix

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: carry upstream null and predicate simplification fixes

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: restore baseline JSON ordering expectations

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: restore histogram JSON ordering expectations

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: refresh empty PromQL range result schemas

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: align native timestamp plan with DF55 decimal display

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: refresh native timestamp SQLness results for DF55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: regenerate NULL sample empty result headers for DF55

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: use DF55 child replacement API in timestamp regressions

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: expose pushed scan dynamic filters to DF55 producers

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: encode string-backed PostgreSQL OID aliases in binary results

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: verify REGPROC binary and text over PostgreSQL protocol

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: register real PostgreSQL catalogs in server fixtures

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: complete DF55 expression inventories for custom query plans

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: correct RangeSelect expression fixture and column identities

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* ci: wait for Kafka WAL helper deployment rollout

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* test: update custom storage empty result headers

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: require exact row counts in scan statistics

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

* fix: suppress deprecated partition_statistics warning in test

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>

---------

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
Co-authored-by: Ning Sun <sunng@protonmail.com>
2026-09-15 11:42:38 +00:00

276 lines
9.1 KiB
Rust

// 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 api::v1::greptime_request::Request;
use api::v1::query_request::Query;
use async_trait::async_trait;
use catalog::memory::MemoryCatalogManager;
use catalog::system_schema::SystemSchemaProvider;
use catalog::system_schema::pg_catalog::PGCatalogProvider;
use catalog::{CatalogManager, RegisterTableRequest};
use common_catalog::consts::{DEFAULT_CATALOG_NAME, PG_CATALOG_NAME};
use common_error::ext::BoxedError;
use common_grpc::flight::do_put::DoPutResponse;
use common_query::Output;
use datafusion_expr::LogicalPlan;
use futures_util::TryFutureExt;
use query::options::QueryOptions;
use query::parser::{PromQuery, QueryStatement};
use query::query_engine::DescribeResult;
use query::{QueryEngineFactory, QueryEngineRef};
use servers::error::{ExecuteQuerySnafu, NotSupportedSnafu, Result};
use servers::query_handler::grpc::GrpcQueryHandler;
use servers::query_handler::sql::{ServerSqlQueryHandlerRef, SqlQueryHandler};
use session::context::QueryContextRef;
use snafu::{ResultExt, ensure};
use sql::parser::{ParseOptions, ParserContext};
use sql::statements::statement::Statement;
use table::TableRef;
mod http;
mod interceptor;
mod mysql;
mod postgres;
mod prom_remote_write_v2_test;
pub struct DummyInstance {
query_engine: QueryEngineRef,
}
impl DummyInstance {
fn new(query_engine: QueryEngineRef) -> Self {
Self { query_engine }
}
}
#[async_trait]
impl SqlQueryHandler for DummyInstance {
async fn do_query(&self, query: &str, query_ctx: QueryContextRef) -> Vec<Result<Output>> {
let mut results = vec![];
let statements = ParserContext::create_with_dialect(
query,
query_ctx.sql_dialect(),
ParseOptions::default(),
)
.map(|x| x.into_iter().map(QueryStatement::Sql).collect::<Vec<_>>())
.unwrap();
for statement in &statements {
let result = self
.query_engine
.planner()
.plan(statement, query_ctx.clone())
.and_then(|plan| self.query_engine.execute(plan, query_ctx.clone()))
.await
.map_err(BoxedError::new)
.context(ExecuteQuerySnafu);
results.push(result);
}
results
}
async fn do_analyze_stream_query(
&self,
query: &str,
query_ctx: QueryContextRef,
) -> Result<Output> {
let mut statements = ParserContext::create_with_dialect(
query,
query_ctx.sql_dialect(),
ParseOptions::default(),
)
.map_err(BoxedError::new)
.context(ExecuteQuerySnafu)?;
ensure!(
statements.len() == 1,
NotSupportedSnafu {
feat: "execute multiple statements in analyze stream query"
}
);
let mut statement = statements.remove(0);
let Statement::Explain(explain) = &mut statement else {
return NotSupportedSnafu {
feat: "non EXPLAIN ANALYZE VERBOSE analyze stream query",
}
.fail();
};
ensure!(
explain.analyze && explain.verbose,
NotSupportedSnafu {
feat: "non EXPLAIN ANALYZE VERBOSE analyze stream query"
}
);
match &explain.format {
None => {}
Some(format) if format.to_string().eq_ignore_ascii_case("json") => {
explain.format = None;
}
Some(_) => {
return NotSupportedSnafu {
feat: "non-JSON analyze stream format",
}
.fail();
}
}
query_ctx.set_explain_format("JSON".to_string());
self.query_engine
.planner()
.plan(&QueryStatement::Sql(statement), query_ctx.clone())
.and_then(|plan| self.query_engine.execute(plan, query_ctx.clone()))
.await
.map_err(BoxedError::new)
.context(ExecuteQuerySnafu)
}
async fn do_exec_plan(
&self,
plan: LogicalPlan,
_stmt: Option<Statement>,
query_ctx: QueryContextRef,
) -> Result<Output> {
Ok(self.query_engine.execute(plan, query_ctx).await.unwrap())
}
async fn do_promql_query(&self, _: &PromQuery, _: QueryContextRef) -> Vec<Result<Output>> {
unimplemented!()
}
async fn do_describe(
&self,
stmt: Statement,
query_ctx: QueryContextRef,
) -> Result<Option<DescribeResult>> {
if let Statement::Query(_) = stmt {
let plan = self
.query_engine
.planner()
.plan(&QueryStatement::Sql(stmt), query_ctx.clone())
.await
.unwrap();
let schema = self.query_engine.describe(plan, query_ctx).await.unwrap();
Ok(Some(schema))
} else {
Ok(None)
}
}
async fn is_valid_schema(&self, catalog: &str, schema: &str) -> Result<bool> {
Ok(self
.query_engine
.engine_state()
.catalog_manager()
.schema_exists(catalog, schema, None)
.await
.unwrap())
}
}
#[async_trait]
impl GrpcQueryHandler for DummyInstance {
async fn do_query(&self, request: Request, ctx: QueryContextRef) -> Result<Output> {
let output = match request {
Request::Inserts(_)
| Request::Deletes(_)
| Request::RowInserts(_)
| Request::RowDeletes(_) => unimplemented!(),
Request::Query(query_request) => {
let query = query_request.query.unwrap();
match query {
Query::Sql(sql) => {
let mut result = SqlQueryHandler::do_query(self, &sql, ctx).await;
ensure!(
result.len() == 1,
NotSupportedSnafu {
feat: "execute multiple statements in SQL query string through GRPC interface"
}
);
result.remove(0)?
}
Query::LogicalPlan(_) | Query::InsertIntoPlan(_) => unimplemented!(),
Query::PromRangeQuery(promql) => {
let prom_query = PromQuery {
query: promql.query,
start: promql.start,
end: promql.end,
step: promql.step,
lookback: promql.lookback,
alias: None,
};
let mut result =
SqlQueryHandler::do_promql_query(self, &prom_query, ctx).await;
ensure!(
result.len() == 1,
NotSupportedSnafu {
feat: "execute multiple statements in PromQL query string through GRPC interface"
}
);
result.remove(0)?
}
}
}
Request::Ddl(_) => unimplemented!(),
};
Ok(output)
}
fn handle_put_record_batch_stream(
&self,
_stream: servers::grpc::flight::PutRecordBatchRequestStream,
_ctx: QueryContextRef,
) -> std::pin::Pin<Box<dyn futures::Stream<Item = Result<DoPutResponse>> + Send>> {
unimplemented!()
}
}
fn create_testing_instance(table: TableRef) -> DummyInstance {
let catalog_manager = MemoryCatalogManager::new_with_table(table);
let pg_catalog = PGCatalogProvider::new(
DEFAULT_CATALOG_NAME.to_string(),
Arc::downgrade(&(catalog_manager.clone() as Arc<dyn CatalogManager>)),
);
for table in pg_catalog.tables().values() {
catalog_manager
.register_table_sync(RegisterTableRequest {
catalog: DEFAULT_CATALOG_NAME.to_string(),
schema: PG_CATALOG_NAME.to_string(),
table_name: table.table_info().name.clone(),
table_id: table.table_info().ident.table_id,
table: table.clone(),
})
.unwrap();
}
let query_engine = QueryEngineFactory::new(
catalog_manager,
None,
None,
None,
None,
false,
QueryOptions::default(),
)
.query_engine();
DummyInstance::new(query_engine)
}
fn create_testing_sql_query_handler(table: TableRef) -> ServerSqlQueryHandlerRef {
Arc::new(create_testing_instance(table)) as _
}