1mod iceberg_query_storage_mode;
16mod locality_backfill_mode;
17mod non_zero64;
18mod opt;
19pub mod parallelism;
20mod query_mode;
21mod search_path;
22pub mod sink_decouple;
23mod statement_timeout;
24mod transaction_isolation_level;
25mod visibility_mode;
26
27use chrono_tz::Tz;
28pub use iceberg_query_storage_mode::IcebergQueryStorageMode;
29use itertools::Itertools;
30pub use locality_backfill_mode::LocalityBackfillMode;
31pub use opt::OptionConfig;
32pub use query_mode::QueryMode;
33use risingwave_common_proc_macro::{ConfigDoc, SessionConfig};
34pub use search_path::{SearchPath, USER_NAME_WILD_CARD};
35use serde::{Deserialize, Serialize};
36pub use statement_timeout::StatementTimeout;
37use thiserror::Error;
38
39use self::non_zero64::ConfigNonZeroU64;
40use crate::config::mutate::TomlTableMutateExt;
41use crate::config::streaming::{CacheRefillPolicy, JoinEncodingType, OverWindowCachePolicy};
42use crate::config::{ConfigMergeError, StreamingConfig, merge_streaming_config_section};
43use crate::hash::VirtualNode;
44use crate::session_config::parallelism::{ConfigBackfillParallelism, ConfigParallelism};
45use crate::session_config::sink_decouple::SinkDecouple;
46use crate::session_config::transaction_isolation_level::IsolationLevel;
47pub use crate::session_config::visibility_mode::VisibilityMode;
48use crate::{PG_VERSION, SERVER_ENCODING, SERVER_VERSION_NUM, STANDARD_CONFORMING_STRINGS};
49
50pub const SESSION_CONFIG_LIST_SEP: &str = ", ";
51
52#[derive(Error, Debug)]
53pub enum SessionConfigError {
54 #[error("Invalid value `{value}` for `{entry}`")]
55 InvalidValue {
56 entry: &'static str,
57 value: String,
58 source: anyhow::Error,
59 },
60
61 #[error("Unrecognized config entry `{0}`")]
62 UnrecognizedEntry(String),
63}
64
65type SessionConfigResult<T> = std::result::Result<T, SessionConfigError>;
66
67const AUTO_LOCALITY_BACKFILL_MIN_SIZE: u64 = 10 * 1024 * 1024 * 1024;
68
69fn default_auto_locality_backfill_min_size() -> u64 {
70 AUTO_LOCALITY_BACKFILL_MIN_SIZE
71}
72
73fn default_legacy_locality_backfill_mode() -> LocalityBackfillMode {
74 LocalityBackfillMode::Always
75}
76
77const DISABLE_BACKFILL_RATE_LIMIT: i32 = -1;
80const DISABLE_SOURCE_RATE_LIMIT: i32 = -1;
81const DISABLE_DML_RATE_LIMIT: i32 = -1;
82const DISABLE_SINK_RATE_LIMIT: i32 = -1;
83
84const BYPASS_CLUSTER_LIMITS: bool = cfg!(debug_assertions);
86
87#[serde_with::apply(_ => #[serde_as(as = "serde_with::DisplayFromStr")] )]
99#[serde_with::serde_as]
100#[derive(Clone, Debug, Deserialize, Serialize, SessionConfig, ConfigDoc, PartialEq)]
101pub struct SessionConfig {
102 #[parameter(default = false, alias = "rw_implicit_flush")]
106 implicit_flush: bool,
107
108 #[parameter(default = false)]
111 dml_wait_persistence: bool,
112
113 #[parameter(default = false)]
116 create_compaction_group_for_mv: bool,
117
118 #[parameter(default = QueryMode::default())]
122 query_mode: QueryMode,
123
124 #[parameter(default = IcebergQueryStorageMode::default())]
127 iceberg_query_storage_mode: IcebergQueryStorageMode,
128
129 #[parameter(default = 1)]
132 extra_float_digits: i32,
133
134 #[parameter(default = "", flags = "REPORT")]
137 application_name: String,
138
139 #[parameter(default = "", rename = "datestyle")]
142 date_style: String,
143
144 #[parameter(default = true, alias = "rw_batch_enable_lookup_join")]
146 batch_enable_lookup_join: bool,
147
148 #[parameter(default = true, alias = "rw_batch_enable_sort_agg")]
151 batch_enable_sort_agg: bool,
152
153 #[parameter(default = false, rename = "batch_enable_distributed_dml")]
156 batch_enable_distributed_dml: bool,
157
158 #[parameter(default = true)]
162 batch_expr_strict_mode: bool,
163
164 #[parameter(default = 8)]
166 max_split_range_gap: i32,
167
168 #[parameter(default = SearchPath::default())]
172 search_path: SearchPath,
173
174 #[parameter(default = VisibilityMode::default())]
176 visibility_mode: VisibilityMode,
177
178 #[parameter(default = false)]
186 batch_read_latest_committed: bool,
187
188 #[parameter(default = IsolationLevel::default())]
190 transaction_isolation: IsolationLevel,
191
192 #[parameter(default = ConfigNonZeroU64::default())]
195 query_epoch: ConfigNonZeroU64,
196
197 #[parameter(default = "UTC", check_hook = check_timezone)]
199 timezone: String,
200
201 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
205 streaming_parallelism: ConfigParallelism,
206
207 #[parameter(
210 default = ConfigBackfillParallelism::Default,
211 check_hook = check_streaming_parallelism_for_backfill,
212 flags = "SESSION_INIT"
213 )]
214 streaming_parallelism_for_backfill: ConfigBackfillParallelism,
215
216 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
220 streaming_parallelism_for_table: ConfigParallelism,
221
222 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
224 streaming_parallelism_for_sink: ConfigParallelism,
225
226 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
228 streaming_parallelism_for_index: ConfigParallelism,
229
230 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
234 streaming_parallelism_for_source: ConfigParallelism,
235
236 #[parameter(default = ConfigParallelism::Default, flags = "SESSION_INIT")]
238 streaming_parallelism_for_materialized_view: ConfigParallelism,
239
240 #[parameter(default = false, alias = "rw_streaming_enable_delta_join")]
242 streaming_enable_delta_join: bool,
243
244 #[parameter(default = true, alias = "rw_streaming_enable_bushy_join")]
246 streaming_enable_bushy_join: bool,
247
248 #[parameter(default = false, alias = "rw_streaming_force_filter_inside_join")]
251 streaming_force_filter_inside_join: bool,
252
253 #[parameter(
256 default = true,
257 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."
258 )]
259 streaming_use_arrangement_backfill: bool,
260
261 #[parameter(default = true)]
262 streaming_use_snapshot_backfill: bool,
263
264 #[parameter(default = false)]
266 enable_serverless_backfill: bool,
267
268 #[parameter(default = false, alias = "rw_streaming_allow_jsonb_in_stream_key")]
270 streaming_allow_jsonb_in_stream_key: bool,
271
272 #[parameter(default = false)]
276 streaming_unsafe_allow_unmaterialized_impure_expr: bool,
277
278 #[parameter(default = false)]
284 streaming_unsafe_allow_upsert_sink_pk_mismatch: bool,
285
286 #[parameter(default = false)]
288 streaming_separate_consecutive_join: bool,
289
290 #[parameter(default = false)]
292 streaming_separate_sink: bool,
293
294 #[parameter(default = None)]
299 streaming_join_encoding: OptionConfig<JoinEncodingType>,
300
301 #[parameter(default = true, alias = "rw_enable_join_ordering")]
303 enable_join_ordering: bool,
304
305 #[parameter(default = true, flags = "SETTER", alias = "rw_enable_two_phase_agg")]
308 enable_two_phase_agg: bool,
309
310 #[parameter(default = false, flags = "SETTER", alias = "rw_force_two_phase_agg")]
314 force_two_phase_agg: bool,
315
316 #[parameter(default = true, alias = "rw_enable_share_plan")]
319 enable_share_plan: bool,
321
322 #[parameter(default = false, alias = "rw_force_split_distinct_agg")]
324 force_split_distinct_agg: bool,
325
326 #[parameter(default = "", rename = "intervalstyle")]
328 interval_style: String,
329
330 #[parameter(default = ConfigNonZeroU64::default())]
332 batch_parallelism: ConfigNonZeroU64,
333
334 #[parameter(default = PG_VERSION)]
336 server_version: String,
337
338 #[parameter(default = SERVER_VERSION_NUM)]
340 server_version_num: i32,
341
342 #[parameter(default = "notice")]
344 client_min_messages: String,
345
346 #[parameter(default = SERVER_ENCODING, check_hook = check_client_encoding)]
348 client_encoding: String,
349
350 #[parameter(default = SinkDecouple::default())]
352 sink_decouple: SinkDecouple,
353
354 #[parameter(default = false)]
357 synchronize_seqscans: bool,
358
359 #[parameter(default = StatementTimeout::default())]
364 statement_timeout: StatementTimeout,
365
366 #[parameter(default = 60000u32)]
368 idle_in_transaction_session_timeout: u32,
369
370 #[parameter(default = 0)]
373 lock_timeout: i32,
374
375 #[parameter(default = 60)]
377 cdc_source_wait_streaming_start_timeout: i32,
378
379 #[parameter(default = true)]
382 row_security: bool,
383
384 #[parameter(default = STANDARD_CONFORMING_STRINGS)]
386 standard_conforming_strings: String,
387
388 #[parameter(default = DISABLE_BACKFILL_RATE_LIMIT)]
392 backfill_rate_limit: i32,
393
394 #[parameter(default = DISABLE_SOURCE_RATE_LIMIT)]
398 source_rate_limit: i32,
399
400 #[parameter(default = DISABLE_DML_RATE_LIMIT)]
404 dml_rate_limit: i32,
405
406 #[parameter(default = DISABLE_SINK_RATE_LIMIT)]
410 sink_rate_limit: i32,
411
412 #[parameter(default = None, alias = "rw_streaming_over_window_cache_policy")]
418 streaming_over_window_cache_policy: OptionConfig<OverWindowCachePolicy>,
419
420 #[parameter(default = None)]
426 streaming_cache_refill_policy: OptionConfig<CacheRefillPolicy>,
427
428 #[parameter(default = false)]
430 background_ddl: bool,
431
432 #[parameter(default = true)]
437 streaming_use_shared_source: bool,
438
439 #[parameter(default = true)]
446 streaming_asof_join_use_cache: bool,
447
448 #[parameter(default = SERVER_ENCODING)]
450 server_encoding: String,
451
452 #[parameter(default = "hex", check_hook = check_bytea_output)]
453 bytea_output: String,
454
455 #[parameter(default = BYPASS_CLUSTER_LIMITS)]
459 bypass_cluster_limits: bool,
460
461 #[parameter(default = VirtualNode::COUNT_FOR_COMPAT, check_hook = check_streaming_max_parallelism)]
471 streaming_max_parallelism: usize,
472
473 #[parameter(default = "", check_hook = check_iceberg_engine_connection)]
476 iceberg_engine_connection: String,
477
478 #[parameter(default = false)]
480 streaming_enable_unaligned_join: bool,
481
482 #[parameter(default = None)]
489 streaming_sync_log_store_pause_duration_ms: OptionConfig<usize>,
490
491 #[parameter(default = None)]
496 streaming_sync_log_store_buffer_size: OptionConfig<usize>,
497
498 #[parameter(default = false, flags = "NO_ALTER_SYS")]
502 disable_purify_definition: bool,
503
504 #[parameter(default = 40_usize)] batch_hnsw_ef_search: usize,
507
508 #[parameter(default = true)]
510 enable_index_selection: bool,
511
512 #[parameter(default = false)]
514 enable_mv_selection: bool,
515
516 #[parameter(default = true)]
519 enable_locality_backfill: bool,
520
521 #[serde(default = "default_legacy_locality_backfill_mode")]
525 #[parameter(default = LocalityBackfillMode::Auto)]
526 locality_backfill_mode: LocalityBackfillMode,
527
528 #[serde(default = "default_auto_locality_backfill_min_size")]
531 #[parameter(default = AUTO_LOCALITY_BACKFILL_MIN_SIZE)]
532 auto_locality_backfill_min_size: u64,
533
534 #[parameter(default = true)]
540 enable_locality_sort_buffer: bool,
541
542 #[parameter(default = 30u32)]
545 slow_ddl_notification_secs: u32,
546
547 #[parameter(default = false)]
551 unsafe_enable_storage_retention_for_non_append_only_tables: bool,
552
553 #[parameter(default = true)]
556 enable_datafusion_engine: bool,
557
558 #[parameter(default = true)]
562 datafusion_prefer_hash_join: bool,
563
564 #[parameter(default = false)]
571 upsert_dml: bool,
572}
573
574fn check_iceberg_engine_connection(val: &str) -> Result<(), String> {
575 if val.is_empty() {
576 return Ok(());
577 }
578
579 let parts: Vec<&str> = val.split('.').collect();
580 if parts.len() != 2 {
581 return Err("Invalid iceberg engine connection format, Should be set to this format: schema_name.connection_name.".to_owned());
582 }
583
584 Ok(())
585}
586
587fn check_timezone(val: &str) -> Result<(), String> {
588 Tz::from_str_insensitive(val).map_err(|_e| "Not a valid timezone")?;
590 Ok(())
591}
592
593fn check_client_encoding(val: &str) -> Result<(), String> {
594 let clean = val.replace(|c: char| !c.is_ascii_alphanumeric(), "");
596 if !clean.eq_ignore_ascii_case("UTF8") {
597 Err("Only support 'UTF8' for CLIENT_ENCODING".to_owned())
598 } else {
599 Ok(())
600 }
601}
602
603fn check_bytea_output(val: &str) -> Result<(), String> {
604 if val == "hex" {
605 Ok(())
606 } else {
607 Err("Only support 'hex' for BYTEA_OUTPUT".to_owned())
608 }
609}
610
611fn check_streaming_max_parallelism(val: &usize) -> Result<(), String> {
613 match val {
614 0 | 1 => Err("STREAMING_MAX_PARALLELISM must be greater than 1".to_owned()),
617 2..=VirtualNode::MAX_COUNT => Ok(()),
618 _ => Err(format!(
619 "STREAMING_MAX_PARALLELISM must be less than or equal to {}",
620 VirtualNode::MAX_COUNT
621 )),
622 }
623}
624
625fn check_streaming_parallelism_for_backfill(val: &ConfigBackfillParallelism) -> Result<(), String> {
626 match val {
627 ConfigBackfillParallelism::Default | ConfigBackfillParallelism::Fixed(_) => Ok(()),
628 ConfigBackfillParallelism::Adaptive
629 | ConfigBackfillParallelism::Bounded(_)
630 | ConfigBackfillParallelism::Ratio(_) => Err(
631 "Only `default` or fixed backfill parallelism is supported here; adaptive backfill strategy is deferred to a later change.".to_owned(),
632 ),
633 }
634}
635
636impl SessionConfig {
637 pub fn set_force_two_phase_agg(
638 &mut self,
639 val: bool,
640 reporter: &mut impl ConfigReporter,
641 ) -> SessionConfigResult<bool> {
642 let set_val = self.set_force_two_phase_agg_inner(val, reporter)?;
643 if self.force_two_phase_agg {
644 self.set_enable_two_phase_agg(true, reporter)
645 } else {
646 Ok(set_val)
647 }
648 }
649
650 pub fn set_enable_two_phase_agg(
651 &mut self,
652 val: bool,
653 reporter: &mut impl ConfigReporter,
654 ) -> SessionConfigResult<bool> {
655 let set_val = self.set_enable_two_phase_agg_inner(val, reporter)?;
656 if !self.force_two_phase_agg {
657 self.set_force_two_phase_agg(false, reporter)
658 } else {
659 Ok(set_val)
660 }
661 }
662}
663
664pub struct VariableInfo {
665 pub name: String,
666 pub setting: String,
667 pub description: String,
668}
669
670pub trait ConfigReporter {
672 fn report_status(&mut self, key: &str, new_val: String);
673}
674
675impl ConfigReporter for () {
677 fn report_status(&mut self, _key: &str, _new_val: String) {}
678}
679
680def_anyhow_newtype! {
681 pub SessionConfigToOverrideError,
682 toml::ser::Error => "failed to serialize session config",
683 ConfigMergeError => transparent,
684}
685
686impl SessionConfig {
687 pub fn to_initial_streaming_config_override(
689 &self,
690 ) -> Result<String, SessionConfigToOverrideError> {
691 let mut table = toml::Table::new();
692
693 if let Some(v) = self.streaming_join_encoding.as_ref() {
696 table
697 .upsert("streaming.developer.join_encoding_type", v)
698 .unwrap();
699 }
700 if let Some(v) = self.streaming_sync_log_store_pause_duration_ms.as_ref() {
701 table
702 .upsert("streaming.developer.sync_log_store_pause_duration_ms", v)
703 .unwrap();
704 }
705 if let Some(v) = self.streaming_sync_log_store_buffer_size.as_ref() {
706 table
707 .upsert("streaming.developer.sync_log_store_buffer_size", v)
708 .unwrap();
709 }
710 if let Some(v) = self.streaming_over_window_cache_policy.as_ref() {
711 table
712 .upsert("streaming.developer.over_window_cache_policy", v)
713 .unwrap();
714 }
715 if let Some(v) = self.streaming_cache_refill_policy.as_ref() {
716 table
717 .upsert("streaming.developer.cache_refill_policy", v)
718 .unwrap();
719 }
720
721 let res = toml::to_string(&table)?;
722
723 if !res.is_empty() {
725 let merged =
726 merge_streaming_config_section(&StreamingConfig::default(), res.as_str())?.unwrap();
727
728 let unrecognized_keys = merged.unrecognized_keys().collect_vec();
729 if !unrecognized_keys.is_empty() {
730 bail!("unrecognized configs: {:?}", unrecognized_keys);
731 }
732 }
733
734 Ok(res)
735 }
736}
737
738#[cfg(test)]
739mod test {
740 use expect_test::expect;
741
742 use super::*;
743
744 #[derive(SessionConfig)]
745 struct TestConfig {
746 #[parameter(default = 1, flags = "NO_ALTER_SYS", alias = "test_param_alias" | "alias_param_test")]
747 test_param: i32,
748 #[parameter(default = false, deprecated = "deprecated test notice")]
749 deprecated_test_param: bool,
750 }
751
752 #[test]
753 fn test_batch_read_latest_committed() {
754 let mut config = SessionConfig::default();
755 assert!(!config.batch_read_latest_committed());
756 assert_eq!(config.get("batch_read_latest_committed").unwrap(), "false");
757 config
758 .set("batch_read_latest_committed", "true".to_owned(), &mut ())
759 .unwrap();
760 assert!(config.batch_read_latest_committed());
761 assert_eq!(config.get("batch_read_latest_committed").unwrap(), "true");
762 config
763 .reset("batch_read_latest_committed", &mut ())
764 .unwrap();
765 assert!(!config.batch_read_latest_committed());
766 }
767
768 #[test]
769 fn test_session_config_alias() {
770 let mut config = TestConfig::default();
771 config.set("test_param", "2".to_owned(), &mut ()).unwrap();
772 assert_eq!(config.get("test_param_alias").unwrap(), "2");
773 config
774 .set("alias_param_test", "3".to_owned(), &mut ())
775 .unwrap();
776 assert_eq!(config.get("test_param_alias").unwrap(), "3");
777 assert!(TestConfig::check_no_alter_sys("test_param").unwrap());
778 assert_eq!(
779 TestConfig::deprecated_notice("deprecated_test_param").unwrap(),
780 Some("deprecated test notice")
781 );
782 assert_eq!(TestConfig::deprecated_notice("test_param").unwrap(), None);
783 }
784
785 #[test]
786 fn test_initial_streaming_config_override() {
787 let mut config = SessionConfig::default();
788 config
789 .set_streaming_join_encoding(Some(JoinEncodingType::Cpu).into(), &mut ())
790 .unwrap();
791 config
792 .set_streaming_over_window_cache_policy(
793 Some(OverWindowCachePolicy::RecentFirstN).into(),
794 &mut (),
795 )
796 .unwrap();
797 config
798 .set_streaming_cache_refill_policy(Some(CacheRefillPolicy::Both).into(), &mut ())
799 .unwrap();
800
801 let override_str = config.to_initial_streaming_config_override().unwrap();
803 expect![[r#"
804 [streaming.developer]
805 cache_refill_policy = "both"
806 join_encoding_type = "cpu_optimized"
807 over_window_cache_policy = "recent_first_n"
808 "#]]
809 .assert_eq(&override_str);
810
811 let merged = merge_streaming_config_section(&StreamingConfig::default(), &override_str)
813 .unwrap()
814 .unwrap();
815 assert_eq!(merged.developer.join_encoding_type, JoinEncodingType::Cpu);
816 assert_eq!(
817 merged.developer.over_window_cache_policy,
818 OverWindowCachePolicy::RecentFirstN
819 );
820 assert_eq!(
821 merged.developer.cache_refill_policy,
822 CacheRefillPolicy::Both
823 );
824 }
825
826 #[test]
827 fn test_streaming_parallelism_defaults() {
828 let config = SessionConfig::default();
829
830 assert_eq!(config.streaming_parallelism(), ConfigParallelism::Default);
831 assert_eq!(
832 config.streaming_parallelism_for_table(),
833 ConfigParallelism::Default
834 );
835 assert_eq!(
836 config.streaming_parallelism_for_source(),
837 ConfigParallelism::Default
838 );
839 assert_eq!(
840 config.streaming_parallelism_for_sink(),
841 ConfigParallelism::Default
842 );
843 assert_eq!(
844 config.streaming_parallelism_for_index(),
845 ConfigParallelism::Default
846 );
847 assert_eq!(
848 config.streaming_parallelism_for_materialized_view(),
849 ConfigParallelism::Default
850 );
851 assert!(!config.streaming_unsafe_allow_upsert_sink_pk_mismatch());
852 }
853
854 #[test]
855 fn test_streaming_parallelism_default_round_trip() {
856 let mut config = SessionConfig::default();
857
858 assert_eq!(config.get("streaming_parallelism").unwrap(), "default");
859 assert_eq!(
860 config.get("streaming_parallelism_for_table").unwrap(),
861 "default"
862 );
863 assert_eq!(
864 config.get("streaming_parallelism_for_source").unwrap(),
865 "default"
866 );
867
868 config
869 .set("streaming_parallelism", "default".to_owned(), &mut ())
870 .unwrap();
871 assert_eq!(config.get("streaming_parallelism").unwrap(), "default");
872
873 config
874 .set("streaming_parallelism", "bounded(16)".to_owned(), &mut ())
875 .unwrap();
876 config
877 .set(
878 "streaming_parallelism_for_table",
879 "bounded(8)".to_owned(),
880 &mut (),
881 )
882 .unwrap();
883 config
884 .set(
885 "streaming_parallelism_for_source",
886 "bounded(8)".to_owned(),
887 &mut (),
888 )
889 .unwrap();
890
891 assert_eq!(
892 config.reset("streaming_parallelism", &mut ()).unwrap(),
893 "default"
894 );
895 assert_eq!(
896 config
897 .reset("streaming_parallelism_for_table", &mut ())
898 .unwrap(),
899 "default"
900 );
901 assert_eq!(
902 config
903 .reset("streaming_parallelism_for_source", &mut ())
904 .unwrap(),
905 "default"
906 );
907 }
908 #[test]
909 fn test_streaming_parallelism_for_backfill_accepts_default_and_fixed() {
910 let mut config = SessionConfig::default();
911
912 config
913 .set(
914 "streaming_parallelism_for_backfill",
915 "default".to_owned(),
916 &mut (),
917 )
918 .unwrap();
919 assert_eq!(
920 config.get("streaming_parallelism_for_backfill").unwrap(),
921 "default"
922 );
923
924 config
925 .set(
926 "streaming_parallelism_for_backfill",
927 "2".to_owned(),
928 &mut (),
929 )
930 .unwrap();
931 assert_eq!(config.streaming_parallelism_for_backfill().to_string(), "2");
932 }
933
934 #[test]
935 fn test_streaming_parallelism_for_backfill_rejects_adaptive_modes() {
936 let mut config = SessionConfig::default();
937 let expected = "Only `default` or fixed backfill parallelism is supported here; adaptive backfill strategy is deferred to a later change.";
938
939 for value in ["adaptive", "bounded(2)", "ratio(0.5)"] {
940 let err = config
941 .set(
942 "streaming_parallelism_for_backfill",
943 value.to_owned(),
944 &mut (),
945 )
946 .unwrap_err();
947
948 match err {
949 SessionConfigError::InvalidValue {
950 entry,
951 value: actual_value,
952 source,
953 } => {
954 assert_eq!(entry, "streaming_parallelism_for_backfill");
955 assert_eq!(actual_value, value);
956 assert_eq!(source.to_string(), expected);
957 }
958 other => panic!("unexpected error: {other:?}"),
959 }
960 }
961 }
962}