Skip to main content

risingwave_common/session_config/
mod.rs

1// Copyright 2022 RisingWave Labs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod iceberg_query_storage_mode;
16mod non_zero64;
17mod opt;
18pub mod parallelism;
19mod query_mode;
20mod search_path;
21pub mod sink_decouple;
22mod statement_timeout;
23mod transaction_isolation_level;
24mod visibility_mode;
25
26use chrono_tz::Tz;
27pub use iceberg_query_storage_mode::IcebergQueryStorageMode;
28use itertools::Itertools;
29pub use opt::OptionConfig;
30pub use query_mode::QueryMode;
31use risingwave_common_proc_macro::{ConfigDoc, SessionConfig};
32pub use search_path::{SearchPath, USER_NAME_WILD_CARD};
33use serde::{Deserialize, Serialize};
34pub use statement_timeout::StatementTimeout;
35use thiserror::Error;
36
37use self::non_zero64::ConfigNonZeroU64;
38use crate::config::mutate::TomlTableMutateExt;
39use crate::config::streaming::{CacheRefillPolicy, JoinEncodingType, OverWindowCachePolicy};
40use crate::config::{ConfigMergeError, StreamingConfig, merge_streaming_config_section};
41use crate::hash::VirtualNode;
42use crate::session_config::parallelism::{ConfigBackfillParallelism, ConfigParallelism};
43use crate::session_config::sink_decouple::SinkDecouple;
44use crate::session_config::transaction_isolation_level::IsolationLevel;
45pub use crate::session_config::visibility_mode::VisibilityMode;
46use crate::{PG_VERSION, SERVER_ENCODING, SERVER_VERSION_NUM, STANDARD_CONFORMING_STRINGS};
47
48pub const SESSION_CONFIG_LIST_SEP: &str = ", ";
49
50#[derive(Error, Debug)]
51pub enum SessionConfigError {
52    #[error("Invalid value `{value}` for `{entry}`")]
53    InvalidValue {
54        entry: &'static str,
55        value: String,
56        source: anyhow::Error,
57    },
58
59    #[error("Unrecognized config entry `{0}`")]
60    UnrecognizedEntry(String),
61}
62
63type SessionConfigResult<T> = std::result::Result<T, SessionConfigError>;
64
65// NOTE(kwannoel): We declare it separately as a constant,
66// otherwise seems like it can't infer the type of -1 when written inline.
67const DISABLE_BACKFILL_RATE_LIMIT: i32 = -1;
68const DISABLE_SOURCE_RATE_LIMIT: i32 = -1;
69const DISABLE_DML_RATE_LIMIT: i32 = -1;
70const DISABLE_SINK_RATE_LIMIT: i32 = -1;
71
72/// Default to bypass cluster limits iff in debug mode.
73const BYPASS_CLUSTER_LIMITS: bool = cfg!(debug_assertions);
74
75/// This is the Session Config of RisingWave.
76///
77/// All config entries implement `Display` and `FromStr` for getter and setter, to be read and
78/// altered within a session.
79///
80/// Users can change the default value of a configuration entry using `ALTER SYSTEM SET`. To
81/// facilitate this, a `serde` implementation is used as the wire format for retrieving initial
82/// configurations and updates from the meta service. It's important to note that the meta
83/// service stores the overridden value of each configuration entry per row with `Display` in
84/// the meta store, rather than using the `serde` format. However, we still delegate the `serde`
85/// impl of all fields to `Display`/`FromStr` to make it consistent.
86#[serde_with::apply(_ => #[serde_as(as = "serde_with::DisplayFromStr")] )]
87#[serde_with::serde_as]
88#[derive(Clone, Debug, Deserialize, Serialize, SessionConfig, ConfigDoc, PartialEq)]
89pub struct SessionConfig {
90    /// If `RW_IMPLICIT_FLUSH` is on, then every INSERT/UPDATE/DELETE statement will block
91    /// until the entire dataflow is refreshed. In other words, every related table & MV will
92    /// be able to see the write.
93    #[parameter(default = false, alias = "rw_implicit_flush")]
94    implicit_flush: bool,
95
96    /// If `DML_WAIT_PERSISTENCE` is on, then every INSERT/UPDATE/DELETE statement waits until
97    /// the transaction is included in a checkpoint. This is ignored when `IMPLICIT_FLUSH` is on.
98    #[parameter(default = false)]
99    dml_wait_persistence: bool,
100
101    /// If `CREATE_COMPACTION_GROUP_FOR_MV` is on, dedicated compaction groups will be created in
102    /// MV creation.
103    #[parameter(default = false)]
104    create_compaction_group_for_mv: bool,
105
106    /// A temporary config variable to force query running in either local or distributed mode.
107    /// The default value is auto which means let the system decide to run batch queries in local
108    /// or distributed mode automatically.
109    #[parameter(default = QueryMode::default())]
110    query_mode: QueryMode,
111
112    /// For Iceberg engine tables, which storage to use for batch SELECT: Iceberg (columnar) or
113    /// Hummock (row). Only affects batch SELECT on tables with ENGINE = ICEBERG.
114    #[parameter(default = IcebergQueryStorageMode::default())]
115    iceberg_query_storage_mode: IcebergQueryStorageMode,
116
117    /// Sets the number of digits displayed for floating-point values.
118    /// See <https://www.postgresql.org/docs/current/runtime-config-client.html#:~:text=for%20more%20information.-,extra_float_digits,-(integer)>
119    #[parameter(default = 1)]
120    extra_float_digits: i32,
121
122    /// Sets the application name to be reported in statistics and logs.
123    /// See <https://www.postgresql.org/docs/14/runtime-config-logging.html#:~:text=What%20to%20Log-,application_name,-(string)>
124    #[parameter(default = "", flags = "REPORT")]
125    application_name: String,
126
127    /// It is typically set by an application upon connection to the server.
128    /// see <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-DATESTYLE>
129    #[parameter(default = "", rename = "datestyle")]
130    date_style: String,
131
132    /// Force the use of lookup join instead of hash join when possible for local batch execution.
133    #[parameter(default = true, alias = "rw_batch_enable_lookup_join")]
134    batch_enable_lookup_join: bool,
135
136    /// Enable usage of sortAgg instead of hash agg when order property is satisfied in batch
137    /// execution
138    #[parameter(default = true, alias = "rw_batch_enable_sort_agg")]
139    batch_enable_sort_agg: bool,
140
141    /// Enable distributed DML, so an insert, delete, and update statement can be executed in a distributed way (e.g. running in multiple compute nodes).
142    /// No atomicity guarantee in this mode. Its goal is to gain the best ingestion performance for initial batch ingestion where users always can drop their table when failure happens.
143    #[parameter(default = false, rename = "batch_enable_distributed_dml")]
144    batch_enable_distributed_dml: bool,
145
146    /// Evaluate expression in strict mode for batch queries.
147    /// If set to false, an expression failure will not cause an error but leave a null value
148    /// on the result set.
149    #[parameter(default = true)]
150    batch_expr_strict_mode: bool,
151
152    /// The max gap allowed to transform small range scan into multi point lookup.
153    #[parameter(default = 8)]
154    max_split_range_gap: i32,
155
156    /// Sets the order in which schemas are searched when an object (table, data type, function, etc.)
157    /// is referenced by a simple name with no schema specified.
158    /// See <https://www.postgresql.org/docs/14/runtime-config-client.html#GUC-SEARCH-PATH>
159    #[parameter(default = SearchPath::default())]
160    search_path: SearchPath,
161
162    /// If `VISIBILITY_MODE` is all, we will support querying data without checkpoint.
163    #[parameter(default = VisibilityMode::default())]
164    visibility_mode: VisibilityMode,
165
166    /// See <https://www.postgresql.org/docs/current/transaction-iso.html>
167    #[parameter(default = IsolationLevel::default())]
168    transaction_isolation: IsolationLevel,
169
170    /// Select as of specific epoch.
171    /// Sets the historical epoch for querying data. If 0, querying latest data.
172    #[parameter(default = ConfigNonZeroU64::default())]
173    query_epoch: ConfigNonZeroU64,
174
175    /// Session timezone. Defaults to UTC.
176    #[parameter(default = "UTC", check_hook = check_timezone)]
177    timezone: String,
178
179    /// The execution parallelism for streaming queries, including tables, materialized views,
180    /// indexes, and sinks. Defaults to `default`, which preserves the legacy adaptive
181    /// scheduling behavior during effective resolution.
182    #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
183    streaming_parallelism: ConfigParallelism,
184
185    /// Specific parallelism for backfill. Only `default` and a fixed positive integer are
186    /// supported here. Adaptive backfill strategies are deferred to a later change.
187    #[parameter(
188        default = ConfigBackfillParallelism::Default,
189        check_hook = check_streaming_parallelism_for_backfill,
190        flags = "SESSION_INIT"
191    )]
192    streaming_parallelism_for_backfill: ConfigBackfillParallelism,
193
194    /// Specific parallelism for table. Defaults to `default`, which preserves the legacy
195    /// bounded adaptive behavior only when the global parallelism itself remains `default`.
196    /// Otherwise it follows the explicit global parallelism.
197    #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
198    streaming_parallelism_for_table: ConfigParallelism,
199
200    /// Specific parallelism for sink. By default, it will fall back to `STREAMING_PARALLELISM`.
201    #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
202    streaming_parallelism_for_sink: ConfigParallelism,
203
204    /// Specific parallelism for index. By default, it will fall back to `STREAMING_PARALLELISM`.
205    #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
206    streaming_parallelism_for_index: ConfigParallelism,
207
208    /// Specific parallelism for source. Defaults to `default`, which preserves the legacy
209    /// bounded adaptive behavior only when the global parallelism itself remains `default`.
210    /// Otherwise it follows the explicit global parallelism.
211    #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
212    streaming_parallelism_for_source: ConfigParallelism,
213
214    /// Specific parallelism for materialized view. By default, it will fall back to `STREAMING_PARALLELISM`.
215    #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
216    streaming_parallelism_for_materialized_view: ConfigParallelism,
217
218    /// Enable delta join for streaming queries. Defaults to false.
219    #[parameter(default = false, alias = "rw_streaming_enable_delta_join")]
220    streaming_enable_delta_join: bool,
221
222    /// Enable bushy join for streaming queries. Defaults to true.
223    #[parameter(default = true, alias = "rw_streaming_enable_bushy_join")]
224    streaming_enable_bushy_join: bool,
225
226    /// Force filtering to be done inside the join whenever there's a choice between optimizations.
227    /// Defaults to false.
228    #[parameter(default = false, alias = "rw_streaming_force_filter_inside_join")]
229    streaming_force_filter_inside_join: bool,
230
231    /// Deprecated. Arrangement backfill is always used as the fallback backfill type for new
232    /// streaming jobs, and this setting is ignored.
233    #[parameter(
234        default = true,
235        deprecated = "The session variable STREAMING_USE_ARRANGEMENT_BACKFILL has been deprecated and is ignored. Arrangement backfill is always used as the fallback backfill type for new streaming jobs."
236    )]
237    streaming_use_arrangement_backfill: bool,
238
239    #[parameter(default = true)]
240    streaming_use_snapshot_backfill: bool,
241
242    /// Enable serverless backfill for streaming queries. Defaults to false.
243    #[parameter(default = false)]
244    enable_serverless_backfill: bool,
245
246    /// Allow `jsonb` in stream key
247    #[parameter(default = false, alias = "rw_streaming_allow_jsonb_in_stream_key")]
248    streaming_allow_jsonb_in_stream_key: bool,
249
250    /// Unsafe: allow impure expressions on non-append-only streams without materialization.
251    ///
252    /// This may lead to inconsistent results or panics due to re-evaluation on updates/retracts.
253    #[parameter(default = false)]
254    streaming_unsafe_allow_unmaterialized_impure_expr: bool,
255
256    /// Unsafe: allow an upsert sink to use downstream primary-key columns that are not part of
257    /// the upstream stream key.
258    ///
259    /// This may leave stale rows in the downstream system if a downstream primary-key column
260    /// changes without the upsert stream providing its old value.
261    #[parameter(default = false)]
262    streaming_unsafe_allow_upsert_sink_pk_mismatch: bool,
263
264    /// Separate consecutive `StreamHashJoin` by no-shuffle `StreamExchange`
265    #[parameter(default = false)]
266    streaming_separate_consecutive_join: bool,
267
268    /// Separate `StreamSink` by no-shuffle `StreamExchange`
269    #[parameter(default = false)]
270    streaming_separate_sink: bool,
271
272    /// Determine which encoding will be used to encode join rows in operator cache.
273    ///
274    /// This overrides the corresponding entry from the `[streaming.developer]` section in the config file,
275    /// taking effect for new streaming jobs created in the current session.
276    #[parameter(default = None)]
277    streaming_join_encoding: OptionConfig<JoinEncodingType>,
278
279    /// Enable join ordering for streaming and batch queries. Defaults to true.
280    #[parameter(default = true, alias = "rw_enable_join_ordering")]
281    enable_join_ordering: bool,
282
283    /// Enable two phase agg optimization. Defaults to true.
284    /// Setting this to true will always set `FORCE_TWO_PHASE_AGG` to false.
285    #[parameter(default = true, flags = "SETTER", alias = "rw_enable_two_phase_agg")]
286    enable_two_phase_agg: bool,
287
288    /// Force two phase agg optimization whenever there's a choice between
289    /// optimizations. Defaults to false.
290    /// Setting this to true will always set `ENABLE_TWO_PHASE_AGG` to false.
291    #[parameter(default = false, flags = "SETTER", alias = "rw_force_two_phase_agg")]
292    force_two_phase_agg: bool,
293
294    /// Enable sharing of common sub-plans.
295    /// This means that DAG structured query plans can be constructed,
296    #[parameter(default = true, alias = "rw_enable_share_plan")]
297    /// rather than only tree structured query plans.
298    enable_share_plan: bool,
299
300    /// Enable split distinct agg
301    #[parameter(default = false, alias = "rw_force_split_distinct_agg")]
302    force_split_distinct_agg: bool,
303
304    /// See <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-INTERVALSTYLE>
305    #[parameter(default = "", rename = "intervalstyle")]
306    interval_style: String,
307
308    /// If `BATCH_PARALLELISM` is non-zero, batch queries will use this parallelism.
309    #[parameter(default = ConfigNonZeroU64::default())]
310    batch_parallelism: ConfigNonZeroU64,
311
312    /// The version of PostgreSQL that Risingwave claims to be.
313    #[parameter(default = PG_VERSION)]
314    server_version: String,
315
316    /// The version of PostgreSQL that Risingwave claims to be.
317    #[parameter(default = SERVER_VERSION_NUM)]
318    server_version_num: i32,
319
320    /// see <https://www.postgresql.org/docs/15/runtime-config-client.html#GUC-CLIENT-MIN-MESSAGES>
321    #[parameter(default = "notice")]
322    client_min_messages: String,
323
324    /// see <https://www.postgresql.org/docs/15/runtime-config-client.html#GUC-CLIENT-ENCODING>
325    #[parameter(default = SERVER_ENCODING, check_hook = check_client_encoding)]
326    client_encoding: String,
327
328    /// Enable decoupling sink and internal streaming graph or not
329    #[parameter(default = SinkDecouple::default())]
330    sink_decouple: SinkDecouple,
331
332    /// See <https://www.postgresql.org/docs/current/runtime-config-compatible.html#RUNTIME-CONFIG-COMPATIBLE-VERSION>
333    /// Unused in RisingWave, support for compatibility.
334    #[parameter(default = false)]
335    synchronize_seqscans: bool,
336
337    /// Abort query statement that takes more than the specified amount of time in sec. If
338    /// `log_min_error_statement` is set to ERROR or lower, the statement that timed out will also be
339    /// logged. If this value is specified without units, it is taken as milliseconds. A value of
340    /// zero (the default) disables the timeout.
341    #[parameter(default = StatementTimeout::default())]
342    statement_timeout: StatementTimeout,
343
344    /// Terminate any session that has been idle (that is, waiting for a client query) within an open transaction for longer than the specified amount of time in milliseconds.
345    #[parameter(default = 60000u32)]
346    idle_in_transaction_session_timeout: u32,
347
348    /// See <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-LOCK-TIMEOUT>
349    /// Unused in RisingWave, support for compatibility.
350    #[parameter(default = 0)]
351    lock_timeout: i32,
352
353    /// For limiting the startup time of a shareable CDC streaming source when the source is being created. Unit: seconds.
354    #[parameter(default = 60)]
355    cdc_source_wait_streaming_start_timeout: i32,
356
357    /// see <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-ROW-SECURITY>.
358    /// Unused in RisingWave, support for compatibility.
359    #[parameter(default = true)]
360    row_security: bool,
361
362    /// see <https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STANDARD-CONFORMING-STRINGS>
363    #[parameter(default = STANDARD_CONFORMING_STRINGS)]
364    standard_conforming_strings: String,
365
366    /// Set streaming rate limit (rows per second) for each parallelism for mv / source / sink backfilling
367    /// If set to -1, disable rate limit.
368    /// If set to 0, this pauses the snapshot read / source read.
369    #[parameter(default = DISABLE_BACKFILL_RATE_LIMIT)]
370    backfill_rate_limit: i32,
371
372    /// Set streaming rate limit (rows per second) for each parallelism for mv / source backfilling, source reads.
373    /// If set to -1, disable rate limit.
374    /// If set to 0, this pauses the snapshot read / source read.
375    #[parameter(default = DISABLE_SOURCE_RATE_LIMIT)]
376    source_rate_limit: i32,
377
378    /// Set streaming rate limit (rows per second) for each parallelism for table DML.
379    /// If set to -1, disable rate limit.
380    /// If set to 0, this pauses the DML.
381    #[parameter(default = DISABLE_DML_RATE_LIMIT)]
382    dml_rate_limit: i32,
383
384    /// Set sink rate limit (rows per second) for each parallelism for external sink.
385    /// If set to -1, disable rate limit.
386    /// If set to 0, this pauses the sink.
387    #[parameter(default = DISABLE_SINK_RATE_LIMIT)]
388    sink_rate_limit: i32,
389
390    /// Cache policy for partition cache in streaming over window.
391    /// Can be `full`, `recent`, `recent_first_n` or `recent_last_n`.
392    ///
393    /// This overrides the corresponding entry from the `[streaming.developer]` section in the config file,
394    /// taking effect for new streaming jobs created in the current session.
395    #[parameter(default = None, alias = "rw_streaming_over_window_cache_policy")]
396    streaming_over_window_cache_policy: OptionConfig<OverWindowCachePolicy>,
397
398    /// Cache refill policy for streaming cache refill feature.
399    /// Can be `enabled`, `disabled`, `streaming`, `serving` or `both`.
400    ///
401    /// This overrides the corresponding entry from the `[streaming.developer]` section in the config file,
402    /// taking effect for new streaming jobs created in the current session.
403    #[parameter(default = None)]
404    streaming_cache_refill_policy: OptionConfig<CacheRefillPolicy>,
405
406    /// Run DDL statements in background
407    #[parameter(default = false)]
408    background_ddl: bool,
409
410    /// Enable shared source. Currently only for Kafka.
411    ///
412    /// When enabled, `CREATE SOURCE` will create a source streaming job, and `CREATE MATERIALIZED VIEWS` from the source
413    /// will forward the data from the same source streaming job, and also backfill prior data from the external source.
414    #[parameter(default = true)]
415    streaming_use_shared_source: bool,
416
417    /// Enable in-memory cache for `AsOf` join executor.
418    ///
419    /// When enabled (default), `AsOf` join uses the cache-based implementation.
420    ///
421    /// When disabled, `AsOf` join uses a no-cache implementation that directly queries
422    /// the state table on-demand, reducing unnecessary data fetches for cache.
423    #[parameter(default = true)]
424    streaming_asof_join_use_cache: bool,
425
426    /// Shows the server-side character set encoding. At present, this parameter can be shown but not set, because the encoding is determined at database creation time.
427    #[parameter(default = SERVER_ENCODING)]
428    server_encoding: String,
429
430    #[parameter(default = "hex", check_hook = check_bytea_output)]
431    bytea_output: String,
432
433    /// Bypass checks on cluster limits
434    ///
435    /// When enabled, `CREATE MATERIALIZED VIEW` will not fail if the cluster limit is hit.
436    #[parameter(default = BYPASS_CLUSTER_LIMITS)]
437    bypass_cluster_limits: bool,
438
439    /// The maximum number of parallelism a streaming query can use. Defaults to 256.
440    ///
441    /// Compared to `STREAMING_PARALLELISM`, which configures the initial parallelism, this configures
442    /// the maximum parallelism a streaming query can use in the future, if the cluster size changes or
443    /// users manually change the parallelism with `ALTER .. SET PARALLELISM`.
444    ///
445    /// It's not always a good idea to set this to a very large number, as it may cause performance
446    /// degradation when performing range scans on the table or the materialized view.
447    // a.k.a. vnode count
448    #[parameter(default = VirtualNode::COUNT_FOR_COMPAT, check_hook = check_streaming_max_parallelism)]
449    streaming_max_parallelism: usize,
450
451    /// Used to provide the connection information for the iceberg engine.
452    /// Format: `iceberg_engine_connection` = `schema_name.connection_name`.
453    #[parameter(default = "", check_hook = check_iceberg_engine_connection)]
454    iceberg_engine_connection: String,
455
456    /// Whether the streaming join should be unaligned or not.
457    #[parameter(default = false)]
458    streaming_enable_unaligned_join: bool,
459
460    /// The timeout for reading from the buffer of the sync log store on barrier.
461    /// Every epoch we will attempt to read the full buffer of the sync log store.
462    /// If we hit the timeout, we will stop reading and continue.
463    ///
464    /// This overrides the corresponding entry from the `[streaming.developer]` section in the config file,
465    /// taking effect for new streaming jobs created in the current session.
466    #[parameter(default = None)]
467    streaming_sync_log_store_pause_duration_ms: OptionConfig<usize>,
468
469    /// The max buffer size for sync logstore, before we start flushing.
470    ///
471    /// This overrides the corresponding entry from the `[streaming.developer]` section in the config file,
472    /// taking effect for new streaming jobs created in the current session.
473    #[parameter(default = None)]
474    streaming_sync_log_store_buffer_size: OptionConfig<usize>,
475
476    /// Whether to disable purifying the definition of the table or source upon retrieval.
477    /// Only set this if encountering issues with functionalities like `SHOW` or `ALTER TABLE/SOURCE`.
478    /// This config may be removed in the future.
479    #[parameter(default = false, flags = "NO_ALTER_SYS")]
480    disable_purify_definition: bool,
481
482    /// The `ef_search` used in querying hnsw vector index
483    #[parameter(default = 40_usize)] // default value borrowed from pg_vector
484    batch_hnsw_ef_search: usize,
485
486    /// Enable index selection for queries
487    #[parameter(default = true)]
488    enable_index_selection: bool,
489
490    /// Enable mv selection for queries
491    #[parameter(default = false)]
492    enable_mv_selection: bool,
493
494    /// Enable locality backfill for streaming queries. Defaults to false.
495    #[parameter(default = false)]
496    enable_locality_backfill: bool,
497
498    /// Duration in seconds before notifying the user that a long-running DDL operation (e.g., DROP TABLE, CANCEL JOBS)
499    /// is still running. Set to 0 to disable notifications. Defaults to 30 seconds.
500    #[parameter(default = 30u32)]
501    slow_ddl_notification_secs: u32,
502
503    /// Unsafe: Enable storage retention for non-append-only tables.
504    /// Enabling this can lead to streaming inconsistency and node panic
505    /// if there is any row INSERT/UPDATE/DELETE operation corresponding to the ttled primary key.
506    #[parameter(default = false)]
507    unsafe_enable_storage_retention_for_non_append_only_tables: bool,
508
509    /// Enable DataFusion Engine
510    /// When enabled, queries involving Iceberg tables will be executed using the DataFusion engine.
511    #[parameter(default = true)]
512    enable_datafusion_engine: bool,
513
514    /// Prefer hash join over sort merge join in DataFusion engine
515    /// When enabled, the DataFusion engine will prioritize hash joins for query execution plans,
516    /// potentially improving performance for certain workloads, but may cause OOM for large datasets.
517    #[parameter(default = true)]
518    datafusion_prefer_hash_join: bool,
519
520    /// Emit chunks in upsert format for `UPDATE` and `DELETE` DMLs.
521    /// May lead to undefined behavior if the table is created with `ON CONFLICT DO NOTHING`.
522    ///
523    /// When enabled:
524    /// - `UPDATE` will only emit `Insert` records for new rows, instead of `Update` records.
525    /// - `DELETE` will only include key columns and pad the rest with NULL, instead of emitting complete rows.
526    #[parameter(default = false)]
527    upsert_dml: bool,
528}
529
530fn check_iceberg_engine_connection(val: &str) -> Result<(), String> {
531    if val.is_empty() {
532        return Ok(());
533    }
534
535    let parts: Vec<&str> = val.split('.').collect();
536    if parts.len() != 2 {
537        return Err("Invalid iceberg engine connection format, Should be set to this format: schema_name.connection_name.".to_owned());
538    }
539
540    Ok(())
541}
542
543fn check_timezone(val: &str) -> Result<(), String> {
544    // Check if the provided string is a valid timezone.
545    Tz::from_str_insensitive(val).map_err(|_e| "Not a valid timezone")?;
546    Ok(())
547}
548
549fn check_client_encoding(val: &str) -> Result<(), String> {
550    // https://github.com/postgres/postgres/blob/REL_15_3/src/common/encnames.c#L525
551    let clean = val.replace(|c: char| !c.is_ascii_alphanumeric(), "");
552    if !clean.eq_ignore_ascii_case("UTF8") {
553        Err("Only support 'UTF8' for CLIENT_ENCODING".to_owned())
554    } else {
555        Ok(())
556    }
557}
558
559fn check_bytea_output(val: &str) -> Result<(), String> {
560    if val == "hex" {
561        Ok(())
562    } else {
563        Err("Only support 'hex' for BYTEA_OUTPUT".to_owned())
564    }
565}
566
567/// Check if the provided value is a valid max parallelism.
568fn check_streaming_max_parallelism(val: &usize) -> Result<(), String> {
569    match val {
570        // TODO(var-vnode): this is to prevent confusion with singletons, after we distinguish
571        // them better, we may allow 1 as the max parallelism (though not much point).
572        0 | 1 => Err("STREAMING_MAX_PARALLELISM must be greater than 1".to_owned()),
573        2..=VirtualNode::MAX_COUNT => Ok(()),
574        _ => Err(format!(
575            "STREAMING_MAX_PARALLELISM must be less than or equal to {}",
576            VirtualNode::MAX_COUNT
577        )),
578    }
579}
580
581fn check_streaming_parallelism_for_backfill(val: &ConfigBackfillParallelism) -> Result<(), String> {
582    match val {
583        ConfigBackfillParallelism::Default | ConfigBackfillParallelism::Fixed(_) => Ok(()),
584        ConfigBackfillParallelism::Adaptive
585        | ConfigBackfillParallelism::Bounded(_)
586        | ConfigBackfillParallelism::Ratio(_) => Err(
587            "Only `default` or fixed backfill parallelism is supported here; adaptive backfill strategy is deferred to a later change.".to_owned(),
588        ),
589    }
590}
591
592impl SessionConfig {
593    pub fn set_force_two_phase_agg(
594        &mut self,
595        val: bool,
596        reporter: &mut impl ConfigReporter,
597    ) -> SessionConfigResult<bool> {
598        let set_val = self.set_force_two_phase_agg_inner(val, reporter)?;
599        if self.force_two_phase_agg {
600            self.set_enable_two_phase_agg(true, reporter)
601        } else {
602            Ok(set_val)
603        }
604    }
605
606    pub fn set_enable_two_phase_agg(
607        &mut self,
608        val: bool,
609        reporter: &mut impl ConfigReporter,
610    ) -> SessionConfigResult<bool> {
611        let set_val = self.set_enable_two_phase_agg_inner(val, reporter)?;
612        if !self.force_two_phase_agg {
613            self.set_force_two_phase_agg(false, reporter)
614        } else {
615            Ok(set_val)
616        }
617    }
618}
619
620pub struct VariableInfo {
621    pub name: String,
622    pub setting: String,
623    pub description: String,
624}
625
626/// Report status or notice to caller.
627pub trait ConfigReporter {
628    fn report_status(&mut self, key: &str, new_val: String);
629}
630
631// Report nothing.
632impl ConfigReporter for () {
633    fn report_status(&mut self, _key: &str, _new_val: String) {}
634}
635
636def_anyhow_newtype! {
637    pub SessionConfigToOverrideError,
638    toml::ser::Error => "failed to serialize session config",
639    ConfigMergeError => transparent,
640}
641
642impl SessionConfig {
643    /// Generate an initial override for the streaming config from the session config.
644    pub fn to_initial_streaming_config_override(
645        &self,
646    ) -> Result<String, SessionConfigToOverrideError> {
647        let mut table = toml::Table::new();
648
649        // TODO: make this more type safe.
650        // We `unwrap` here to assert the hard-coded keys are correct.
651        if let Some(v) = self.streaming_join_encoding.as_ref() {
652            table
653                .upsert("streaming.developer.join_encoding_type", v)
654                .unwrap();
655        }
656        if let Some(v) = self.streaming_sync_log_store_pause_duration_ms.as_ref() {
657            table
658                .upsert("streaming.developer.sync_log_store_pause_duration_ms", v)
659                .unwrap();
660        }
661        if let Some(v) = self.streaming_sync_log_store_buffer_size.as_ref() {
662            table
663                .upsert("streaming.developer.sync_log_store_buffer_size", v)
664                .unwrap();
665        }
666        if let Some(v) = self.streaming_over_window_cache_policy.as_ref() {
667            table
668                .upsert("streaming.developer.over_window_cache_policy", v)
669                .unwrap();
670        }
671        if let Some(v) = self.streaming_cache_refill_policy.as_ref() {
672            table
673                .upsert("streaming.developer.cache_refill_policy", v)
674                .unwrap();
675        }
676
677        let res = toml::to_string(&table)?;
678
679        // Validate all fields are valid by trying to merge it to the default config.
680        if !res.is_empty() {
681            let merged =
682                merge_streaming_config_section(&StreamingConfig::default(), res.as_str())?.unwrap();
683
684            let unrecognized_keys = merged.unrecognized_keys().collect_vec();
685            if !unrecognized_keys.is_empty() {
686                bail!("unrecognized configs: {:?}", unrecognized_keys);
687            }
688        }
689
690        Ok(res)
691    }
692}
693
694#[cfg(test)]
695mod test {
696    use expect_test::expect;
697
698    use super::*;
699
700    #[derive(SessionConfig)]
701    struct TestConfig {
702        #[parameter(default = 1, flags = "NO_ALTER_SYS", alias = "test_param_alias" | "alias_param_test")]
703        test_param: i32,
704        #[parameter(default = false, deprecated = "deprecated test notice")]
705        deprecated_test_param: bool,
706    }
707
708    #[test]
709    fn test_session_config_alias() {
710        let mut config = TestConfig::default();
711        config.set("test_param", "2".to_owned(), &mut ()).unwrap();
712        assert_eq!(config.get("test_param_alias").unwrap(), "2");
713        config
714            .set("alias_param_test", "3".to_owned(), &mut ())
715            .unwrap();
716        assert_eq!(config.get("test_param_alias").unwrap(), "3");
717        assert!(TestConfig::check_no_alter_sys("test_param").unwrap());
718        assert_eq!(
719            TestConfig::deprecated_notice("deprecated_test_param").unwrap(),
720            Some("deprecated test notice")
721        );
722        assert_eq!(TestConfig::deprecated_notice("test_param").unwrap(), None);
723    }
724
725    #[test]
726    fn test_initial_streaming_config_override() {
727        let mut config = SessionConfig::default();
728        config
729            .set_streaming_join_encoding(Some(JoinEncodingType::Cpu).into(), &mut ())
730            .unwrap();
731        config
732            .set_streaming_over_window_cache_policy(
733                Some(OverWindowCachePolicy::RecentFirstN).into(),
734                &mut (),
735            )
736            .unwrap();
737        config
738            .set_streaming_cache_refill_policy(Some(CacheRefillPolicy::Both).into(), &mut ())
739            .unwrap();
740
741        // Check the converted config override string.
742        let override_str = config.to_initial_streaming_config_override().unwrap();
743        expect![[r#"
744            [streaming.developer]
745            cache_refill_policy = "both"
746            join_encoding_type = "cpu_optimized"
747            over_window_cache_policy = "recent_first_n"
748        "#]]
749        .assert_eq(&override_str);
750
751        // Try merging it to the default streaming config.
752        let merged = merge_streaming_config_section(&StreamingConfig::default(), &override_str)
753            .unwrap()
754            .unwrap();
755        assert_eq!(merged.developer.join_encoding_type, JoinEncodingType::Cpu);
756        assert_eq!(
757            merged.developer.over_window_cache_policy,
758            OverWindowCachePolicy::RecentFirstN
759        );
760        assert_eq!(
761            merged.developer.cache_refill_policy,
762            CacheRefillPolicy::Both
763        );
764    }
765
766    #[test]
767    fn test_streaming_parallelism_defaults() {
768        let config = SessionConfig::default();
769
770        assert_eq!(config.streaming_parallelism(), ConfigParallelism::Default);
771        assert_eq!(
772            config.streaming_parallelism_for_table(),
773            ConfigParallelism::Default
774        );
775        assert_eq!(
776            config.streaming_parallelism_for_source(),
777            ConfigParallelism::Default
778        );
779        assert_eq!(
780            config.streaming_parallelism_for_sink(),
781            ConfigParallelism::Default
782        );
783        assert_eq!(
784            config.streaming_parallelism_for_index(),
785            ConfigParallelism::Default
786        );
787        assert_eq!(
788            config.streaming_parallelism_for_materialized_view(),
789            ConfigParallelism::Default
790        );
791        assert!(!config.streaming_unsafe_allow_upsert_sink_pk_mismatch());
792    }
793
794    #[test]
795    fn test_streaming_parallelism_default_round_trip() {
796        let mut config = SessionConfig::default();
797
798        assert_eq!(config.get("streaming_parallelism").unwrap(), "default");
799        assert_eq!(
800            config.get("streaming_parallelism_for_table").unwrap(),
801            "default"
802        );
803        assert_eq!(
804            config.get("streaming_parallelism_for_source").unwrap(),
805            "default"
806        );
807
808        config
809            .set("streaming_parallelism", "default".to_owned(), &mut ())
810            .unwrap();
811        assert_eq!(config.get("streaming_parallelism").unwrap(), "default");
812
813        config
814            .set("streaming_parallelism", "bounded(16)".to_owned(), &mut ())
815            .unwrap();
816        config
817            .set(
818                "streaming_parallelism_for_table",
819                "bounded(8)".to_owned(),
820                &mut (),
821            )
822            .unwrap();
823        config
824            .set(
825                "streaming_parallelism_for_source",
826                "bounded(8)".to_owned(),
827                &mut (),
828            )
829            .unwrap();
830
831        assert_eq!(
832            config.reset("streaming_parallelism", &mut ()).unwrap(),
833            "default"
834        );
835        assert_eq!(
836            config
837                .reset("streaming_parallelism_for_table", &mut ())
838                .unwrap(),
839            "default"
840        );
841        assert_eq!(
842            config
843                .reset("streaming_parallelism_for_source", &mut ())
844                .unwrap(),
845            "default"
846        );
847    }
848    #[test]
849    fn test_streaming_parallelism_for_backfill_accepts_default_and_fixed() {
850        let mut config = SessionConfig::default();
851
852        config
853            .set(
854                "streaming_parallelism_for_backfill",
855                "default".to_owned(),
856                &mut (),
857            )
858            .unwrap();
859        assert_eq!(
860            config.get("streaming_parallelism_for_backfill").unwrap(),
861            "default"
862        );
863
864        config
865            .set(
866                "streaming_parallelism_for_backfill",
867                "2".to_owned(),
868                &mut (),
869            )
870            .unwrap();
871        assert_eq!(config.streaming_parallelism_for_backfill().to_string(), "2");
872    }
873
874    #[test]
875    fn test_streaming_parallelism_for_backfill_rejects_adaptive_modes() {
876        let mut config = SessionConfig::default();
877        let expected = "Only `default` or fixed backfill parallelism is supported here; adaptive backfill strategy is deferred to a later change.";
878
879        for value in ["adaptive", "bounded(2)", "ratio(0.5)"] {
880            let err = config
881                .set(
882                    "streaming_parallelism_for_backfill",
883                    value.to_owned(),
884                    &mut (),
885                )
886                .unwrap_err();
887
888            match err {
889                SessionConfigError::InvalidValue {
890                    entry,
891                    value: actual_value,
892                    source,
893                } => {
894                    assert_eq!(entry, "streaming_parallelism_for_backfill");
895                    assert_eq!(actual_value, value);
896                    assert_eq!(source.to_string(), expected);
897                }
898                other => panic!("unexpected error: {other:?}"),
899            }
900        }
901    }
902}