diff --git a/config/config.md b/config/config.md
index 0fae0caaa4..aa0c6701a0 100644
--- a/config/config.md
+++ b/config/config.md
@@ -14,6 +14,7 @@
| --- | -----| ------- | ----------- |
| `default_timezone` | String | Unset | The default timezone of the server. |
| `default_column_prefix` | String | Unset | The default column prefix for auto-created time index and value columns. |
+| `auto_create_table` | Bool | `true` | Server-side global switch for auto table creation on write. When `false`, a missing table is never auto-created even if the request sets the `auto_create_table` hint to `true`. Default: `true`. |
| `user_provider` | String | Unset | The user provider for authentication. Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd" |
| `max_in_flight_write_bytes` | String | Unset | Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight). Set to 0 to disable the limit. Default: "0" (unlimited) |
| `write_bytes_exhausted_policy` | String | Unset | Policy when write bytes quota is exhausted. Options: "wait" (default, 10s timeout), "wait()" (e.g., "wait(30s)"), "fail" |
@@ -230,6 +231,7 @@
| --- | -----| ------- | ----------- |
| `default_timezone` | String | Unset | The default timezone of the server. |
| `default_column_prefix` | String | Unset | The default column prefix for auto-created time index and value columns. |
+| `auto_create_table` | Bool | `true` | Server-side global switch for auto table creation on write. When `false`, a missing table is never auto-created even if the request sets the `auto_create_table` hint to `true`. Default: `true`. |
| `user_provider` | String | Unset | The user provider for authentication. Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd" |
| `max_in_flight_write_bytes` | String | Unset | Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight). Set to 0 to disable the limit. Default: "0" (unlimited) |
| `write_bytes_exhausted_policy` | String | Unset | Policy when write bytes quota is exhausted. Options: "wait" (default, 10s timeout), "wait()" (e.g., "wait(30s)"), "fail" |
diff --git a/config/frontend.example.toml b/config/frontend.example.toml
index 39f38fbef9..a044aebda6 100644
--- a/config/frontend.example.toml
+++ b/config/frontend.example.toml
@@ -6,6 +6,10 @@ default_timezone = "UTC"
## @toml2docs:none-default
default_column_prefix = "greptime"
+## Server-side global switch for auto table creation on write.
+## When `false`, a missing table is never auto-created even if the request sets the `auto_create_table` hint to `true`. Default: `true`.
+#+ auto_create_table = true
+
## The user provider for authentication.
## Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd"
## @toml2docs:none-default
diff --git a/config/standalone.example.toml b/config/standalone.example.toml
index d5c42e744c..5740e0e1cf 100644
--- a/config/standalone.example.toml
+++ b/config/standalone.example.toml
@@ -6,6 +6,10 @@ default_timezone = "UTC"
## @toml2docs:none-default
default_column_prefix = "greptime"
+## Server-side global switch for auto table creation on write.
+## When `false`, a missing table is never auto-created even if the request sets the `auto_create_table` hint to `true`. Default: `true`.
+#+ auto_create_table = true
+
## The user provider for authentication.
## Examples: "static_user_provider:file:/path/to/users", "static_user_provider:cmd:greptime_user=greptime_pwd"
## @toml2docs:none-default
diff --git a/src/cmd/tests/load_config_test.rs b/src/cmd/tests/load_config_test.rs
index 6cffcd67c2..cee29e4456 100644
--- a/src/cmd/tests/load_config_test.rs
+++ b/src/cmd/tests/load_config_test.rs
@@ -114,6 +114,7 @@ fn test_load_frontend_example_config() {
component: FrontendOptions {
default_timezone: Some("UTC".to_string()),
default_column_prefix: Some("greptime".to_string()),
+ auto_create_table: true,
meta_client: Some(MetaClientOptions {
metasrv_addrs: vec!["127.0.0.1:3002".to_string()],
timeout: Duration::from_secs(3),
@@ -267,6 +268,7 @@ fn test_load_standalone_example_config() {
component: StandaloneOptions {
default_timezone: Some("UTC".to_string()),
default_column_prefix: Some("greptime".to_string()),
+ auto_create_table: true,
wal: DatanodeWalConfig::RaftEngine(RaftEngineConfig {
dir: Some(format!("{}/{}", DEFAULT_DATA_HOME, WAL_DIR)),
sync_period: Some(Duration::from_secs(10)),
diff --git a/src/flow/src/server.rs b/src/flow/src/server.rs
index 913128f386..c2d986f645 100644
--- a/src/flow/src/server.rs
+++ b/src/flow/src/server.rs
@@ -566,11 +566,15 @@ impl FrontendInvoker {
name: TABLE_FLOWNODE_SET_CACHE_NAME,
})?;
+ // TODO(auto_create_table): flow sink tables are created through a controlled
+ // `CREATE FLOW` path, not client writes, so they are intentionally exempt from
+ // the frontend's global auto-create switch. Revisit if flow should honor it.
let inserter = Arc::new(Inserter::new(
catalog_manager.clone(),
partition_manager.clone(),
node_manager.clone(),
table_flownode_cache,
+ true,
));
let deleter = Arc::new(Deleter::new(
diff --git a/src/frontend/src/frontend.rs b/src/frontend/src/frontend.rs
index fb3b096f06..918185cb8f 100644
--- a/src/frontend/src/frontend.rs
+++ b/src/frontend/src/frontend.rs
@@ -44,6 +44,11 @@ pub struct FrontendOptions {
pub node_id: Option,
pub default_timezone: Option,
pub default_column_prefix: Option,
+ /// Server-side global switch for auto table creation on write.
+ /// Acts as an upper bound: when `false`, missing tables are never auto-created
+ /// even if a request sets the `auto_create_table` hint to `true`. When `true`
+ /// (default), the per-request hint still applies. Default: `true`.
+ pub auto_create_table: bool,
/// Maximum total memory for all concurrent write request bodies and messages (HTTP, gRPC, Flight).
/// Set to 0 to disable the limit. Default: "0" (unlimited)
pub max_in_flight_write_bytes: ReadableSize,
@@ -82,6 +87,7 @@ impl Default for FrontendOptions {
node_id: None,
default_timezone: None,
default_column_prefix: None,
+ auto_create_table: true,
max_in_flight_write_bytes: ReadableSize(0),
write_bytes_exhausted_policy: OnExhaustedPolicy::default(),
http: HttpOptions::default(),
diff --git a/src/frontend/src/instance/builder.rs b/src/frontend/src/instance/builder.rs
index ff857ed768..526d8aac73 100644
--- a/src/frontend/src/instance/builder.rs
+++ b/src/frontend/src/instance/builder.rs
@@ -185,6 +185,7 @@ impl FrontendBuilder {
partition_manager.clone(),
node_manager.clone(),
table_flownode_cache,
+ self.options.auto_create_table,
));
let deleter = Arc::new(Deleter::new(
self.catalog_manager.clone(),
diff --git a/src/operator/src/insert.rs b/src/operator/src/insert.rs
index ff8ed2b78b..9ef4d2ff94 100644
--- a/src/operator/src/insert.rs
+++ b/src/operator/src/insert.rs
@@ -83,6 +83,10 @@ pub struct Inserter {
pub(crate) partition_manager: PartitionRuleManagerRef,
pub(crate) node_manager: NodeManagerRef,
pub(crate) table_flownode_set_cache: TableFlownodeSetCacheRef,
+ /// Server-side upper bound for auto table creation on write.
+ /// When `false`, missing tables are never auto-created regardless of the
+ /// per-request `auto_create_table` hint. When `true`, the hint still applies.
+ auto_create_table: bool,
}
pub type InserterRef = Arc;
@@ -135,12 +139,14 @@ impl Inserter {
partition_manager: PartitionRuleManagerRef,
node_manager: NodeManagerRef,
table_flownode_set_cache: TableFlownodeSetCacheRef,
+ auto_create_table: bool,
) -> Self {
Self {
catalog_manager,
partition_manager,
node_manager,
table_flownode_set_cache,
+ auto_create_table,
}
}
@@ -469,6 +475,30 @@ impl Inserter {
Ok(inserts)
}
+ /// Returns `None` if auto table creation is allowed, or `Some(reason)` if
+ /// disabled by either the global config or the request hint. The reason tells
+ /// which one, for a clearer error.
+ fn auto_create_disabled_reason(&self, ctx: &QueryContextRef) -> Result