diff --git a/config/config.md b/config/config.md
index f8ce99cae1..7eba8a472e 100644
--- a/config/config.md
+++ b/config/config.md
@@ -32,6 +32,7 @@
| `http.enable_cors` | Bool | `true` | HTTP CORS support, it's turned on by default
This allows browser to access http APIs without CORS restrictions |
| `http.cors_allowed_origins` | Array | Unset | Customize allowed origins for HTTP CORS. |
| `http.prom_validation_mode` | String | `strict` | Whether to enable validation for Prometheus remote write requests.
Available options:
- strict: deny invalid UTF-8 strings (default).
- lossy: allow invalid UTF-8 strings, replace invalid characters with REPLACEMENT_CHARACTER(U+FFFD).
- unchecked: do not valid strings. |
+| `http.experimental_enable_explain_analyze_stream` | Bool | `false` | Experimental: enable POST /v1/sql/analyze/stream for streaming EXPLAIN ANALYZE VERBOSE metrics. |
| `grpc` | -- | -- | The gRPC server options. |
| `grpc.bind_addr` | String | `127.0.0.1:4001` | The address to bind the gRPC server. |
| `grpc.runtime_size` | Integer | `8` | The number of server worker threads. |
@@ -247,6 +248,7 @@
| `http.enable_cors` | Bool | `true` | HTTP CORS support, it's turned on by default
This allows browser to access http APIs without CORS restrictions |
| `http.cors_allowed_origins` | Array | Unset | Customize allowed origins for HTTP CORS. |
| `http.prom_validation_mode` | String | `strict` | Whether to enable validation for Prometheus remote write requests.
Available options:
- strict: deny invalid UTF-8 strings (default).
- lossy: allow invalid UTF-8 strings, replace invalid characters with REPLACEMENT_CHARACTER(U+FFFD).
- unchecked: do not valid strings. |
+| `http.experimental_enable_explain_analyze_stream` | Bool | `false` | Experimental: enable POST /v1/sql/analyze/stream for streaming EXPLAIN ANALYZE VERBOSE metrics. |
| `grpc` | -- | -- | The gRPC server options. |
| `grpc.bind_addr` | String | `127.0.0.1:4001` | The address to bind the gRPC server. |
| `grpc.server_addr` | String | `127.0.0.1:4001` | The address advertised to the metasrv, and used for connections from outside the host.
If left empty or unset, the server will automatically use the IP address of the first network interface
on the host, with the same port number as the one specified in `grpc.bind_addr`. |
diff --git a/config/frontend.example.toml b/config/frontend.example.toml
index 2331cdf028..06b4efd36e 100644
--- a/config/frontend.example.toml
+++ b/config/frontend.example.toml
@@ -57,6 +57,8 @@ cors_allowed_origins = ["https://example.com"]
## - lossy: allow invalid UTF-8 strings, replace invalid characters with REPLACEMENT_CHARACTER(U+FFFD).
## - unchecked: do not valid strings.
prom_validation_mode = "strict"
+## Experimental: enable POST /v1/sql/analyze/stream for streaming EXPLAIN ANALYZE VERBOSE metrics.
+experimental_enable_explain_analyze_stream = false
## The gRPC server options.
[grpc]
diff --git a/config/standalone.example.toml b/config/standalone.example.toml
index 79ed2814f3..407280278d 100644
--- a/config/standalone.example.toml
+++ b/config/standalone.example.toml
@@ -71,6 +71,8 @@ cors_allowed_origins = ["https://example.com"]
## - lossy: allow invalid UTF-8 strings, replace invalid characters with REPLACEMENT_CHARACTER(U+FFFD).
## - unchecked: do not valid strings.
prom_validation_mode = "strict"
+## Experimental: enable POST /v1/sql/analyze/stream for streaming EXPLAIN ANALYZE VERBOSE metrics.
+experimental_enable_explain_analyze_stream = false
## The gRPC server options.
[grpc]
diff --git a/src/frontend/src/instance.rs b/src/frontend/src/instance.rs
index 1ee45ef438..8a6bc062f2 100644
--- a/src/frontend/src/instance.rs
+++ b/src/frontend/src/instance.rs
@@ -89,7 +89,7 @@ use sql::statements::comment::CommentObject;
use sql::statements::copy::{CopyDatabase, CopyTable};
use sql::statements::statement::Statement;
use sql::statements::tql::Tql;
-use sqlparser::ast::ObjectName;
+use sqlparser::ast::{AnalyzeFormat, ObjectName};
pub use standalone::StandaloneDatanodeManager;
use table::requests::{OTLP_METRIC_COMPAT_KEY, OTLP_METRIC_COMPAT_PROM};
use tracing::Span;
@@ -195,35 +195,66 @@ fn parse_stmt(sql: &str, dialect: &(dyn Dialect + Send + Sync)) -> Result Result<()> {
+ let Statement::Explain(explain) = stmt else {
+ return InvalidSqlSnafu {
+ err_msg: "only EXPLAIN ANALYZE VERBOSE statement is supported",
+ }
+ .fail();
+ };
+ ensure!(
+ explain.analyze && explain.verbose,
+ InvalidSqlSnafu {
+ err_msg: "statement must be EXPLAIN ANALYZE VERBOSE"
+ }
+ );
+ match explain.format {
+ None | Some(AnalyzeFormat::JSON) => {
+ // Keep explicit FORMAT JSON accepted, but pass JSON through
+ // QueryContext.explain_format instead of the statement to avoid the
+ // planner's current `EXPLAIN VERBOSE with FORMAT` limitation.
+ explain.format = None;
+ Ok(())
+ }
+ Some(_) => InvalidSqlSnafu {
+ err_msg: "only FORMAT JSON is supported for analyze stream",
+ }
+ .fail(),
+ }
+}
+
impl Instance {
+ fn statement_slow_query_timer(
+ &self,
+ stmt: &Statement,
+ schema_name: String,
+ ) -> Option {
+ if !stmt.is_readonly() || !self.slow_query_options.enable {
+ return None;
+ }
+
+ self.event_recorder.clone().map(|event_recorder| {
+ SlowQueryTimer::new(
+ CatalogQueryStatement::Sql(stmt.clone()),
+ schema_name,
+ self.slow_query_options.threshold,
+ self.slow_query_options.sample_ratio,
+ self.slow_query_options.record_type,
+ event_recorder,
+ )
+ })
+ }
+
async fn query_statement(&self, stmt: Statement, query_ctx: QueryContextRef) -> Result