mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-05-17 13:30:38 +00:00
fix(server): describe EXPLAIN statements so bind parameters work (#8035)
* fix(server): describe EXPLAIN statements so bind parameters work `do_describe_inner` only planned `Insert`/`Query`/`Delete`, so `EXPLAIN` and `EXPLAIN ANALYZE` fell through to the non-plan branch and had no parameter-type inference. At Bind time the Postgres handler then reported `unsupported_parameter_type` even though the inner query would have worked on its own. Recurse one level into `Statement::Explain` so that an EXPLAIN wrapping a plannable statement goes through the same describe path. Adds a tokio-postgres integration test that exercises `EXPLAIN`/`EXPLAIN ANALYZE` over the extended query protocol. Fixes #8029 Signed-off-by: BootstrapperSBL <yvanwww@gmail.com> * refactor(server): extract plannable-inner check into closure Reduce duplication between the direct match and the EXPLAIN inner match by factoring out is_inner_plannable. Behaviour unchanged. Signed-off-by: BootstrapperSBL <yvanwww@gmail.com> --------- Signed-off-by: BootstrapperSBL <yvanwww@gmail.com>
This commit is contained in:
@@ -707,10 +707,19 @@ impl Instance {
|
||||
) -> Result<Option<DescribeResult>> {
|
||||
ensure!(!self.is_suspended(), error::SuspendedSnafu);
|
||||
|
||||
if matches!(
|
||||
stmt,
|
||||
Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
|
||||
) {
|
||||
// EXPLAIN / EXPLAIN ANALYZE wrap an inner statement; describe them when the
|
||||
// wrapped statement is something we already plan (so that bind parameters
|
||||
// in the inner query get their types inferred). See #8029.
|
||||
let is_inner_plannable = |s: &Statement| {
|
||||
matches!(
|
||||
s,
|
||||
Statement::Insert(_) | Statement::Query(_) | Statement::Delete(_)
|
||||
)
|
||||
};
|
||||
let plannable = is_inner_plannable(&stmt)
|
||||
|| matches!(&stmt, Statement::Explain(explain) if is_inner_plannable(explain.statement.as_ref()));
|
||||
|
||||
if plannable {
|
||||
self.plugins
|
||||
.get::<PermissionCheckerRef>()
|
||||
.as_ref()
|
||||
|
||||
@@ -83,6 +83,7 @@ macro_rules! sql_tests {
|
||||
test_postgres_intervalstyle,
|
||||
test_postgres_parameter_inference,
|
||||
test_postgres_uint64_parameter,
|
||||
test_postgres_explain_bind_parameter,
|
||||
test_postgres_array_types,
|
||||
test_mysql_prepare_stmt_insert_timestamp,
|
||||
test_mysql_federated_prepare_stmt,
|
||||
@@ -1353,6 +1354,65 @@ pub async fn test_postgres_uint64_parameter(store_type: StorageType) {
|
||||
guard.remove_all().await;
|
||||
}
|
||||
|
||||
pub async fn test_postgres_explain_bind_parameter(store_type: StorageType) {
|
||||
// Regression test for #8029: EXPLAIN / EXPLAIN ANALYZE must accept bind
|
||||
// parameters over the Postgres extended query protocol.
|
||||
let (mut guard, fe_pg_server) =
|
||||
setup_pg_server(store_type, "test_postgres_explain_bind_parameter").await;
|
||||
let addr = fe_pg_server.bind_addr().unwrap().to_string();
|
||||
|
||||
let (client, connection) = tokio_postgres::connect(&format!("postgres://{addr}/public"), NoTls)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
connection.await.unwrap();
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
|
||||
let _ = client
|
||||
.simple_query(
|
||||
"create table t (k varchar(36) not null, ts timestamp(3) not null, time index(ts))",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let _ = client
|
||||
.simple_query("insert into t (k, ts) values ('a', 1), ('b', 2), ('c', 3)")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Sanity check: the underlying SELECT with a bind parameter works.
|
||||
let rows = client
|
||||
.query("SELECT k FROM t WHERE k = $1", &[&"a"])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(1, rows.len());
|
||||
|
||||
// EXPLAIN with a bind parameter must succeed.
|
||||
let rows = client
|
||||
.query("EXPLAIN SELECT k FROM t WHERE k = $1", &[&"a"])
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(!rows.is_empty(), "EXPLAIN should produce at least one row");
|
||||
|
||||
// EXPLAIN ANALYZE with a bind parameter must also succeed.
|
||||
let rows = client
|
||||
.query("EXPLAIN ANALYZE SELECT k FROM t WHERE k = $1", &[&"a"])
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
!rows.is_empty(),
|
||||
"EXPLAIN ANALYZE should produce at least one row"
|
||||
);
|
||||
|
||||
drop(client);
|
||||
rx.await.unwrap();
|
||||
|
||||
let _ = fe_pg_server.shutdown().await;
|
||||
guard.remove_all().await;
|
||||
}
|
||||
|
||||
pub async fn test_mysql_async_timestamp(store_type: StorageType) {
|
||||
use mysql_async::prelude::*;
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
Reference in New Issue
Block a user