@@ -76,6 +76,10 @@ pub struct ConfigSnapshot {
7676 /// uses the compiled templates in `Config.templates`, not these raw strings.
7777 pub extra_templates : HashMap < String , String > ,
7878
79+ /// User-defined tags for categorizing or labeling this config snapshot.
80+ /// Tags are metadata and do not affect the config hash.
81+ pub tags : HashMap < String , String > ,
82+
7983 __private : ( ) ,
8084}
8185
@@ -105,7 +109,6 @@ impl SnapshotHash {
105109 }
106110}
107111
108- #[ cfg( any( test, feature = "e2e_tests" ) ) ]
109112impl std:: str:: FromStr for SnapshotHash {
110113 type Err = std:: convert:: Infallible ;
111114
@@ -137,11 +140,13 @@ impl ConfigSnapshot {
137140 message : format ! ( "Failed to serialize stored config: {e}" ) ,
138141 } )
139142 } ) ?) ;
143+
140144 let hash = ConfigSnapshot :: hash ( & stored_config_toml, & extra_templates) ?;
141145 Ok ( Self {
142146 config : stored_config,
143147 hash,
144148 extra_templates,
149+ tags : HashMap :: new ( ) ,
145150 __private : ( ) ,
146151 } )
147152 }
@@ -169,6 +174,7 @@ impl ConfigSnapshot {
169174 config : StoredConfig :: default ( ) ,
170175 hash : SnapshotHash :: new_test ( ) ,
171176 extra_templates : HashMap :: new ( ) ,
177+ tags : HashMap :: new ( ) ,
172178 __private : ( ) ,
173179 }
174180 }
@@ -177,9 +183,14 @@ impl ConfigSnapshot {
177183 ///
178184 /// This is used when loading a previously stored config snapshot from ClickHouse.
179185 /// The hash is recomputed from the config and templates to ensure consistency.
186+ ///
187+ /// Note: We deserialize as `StoredConfig` (not `UninitializedConfig`) to support
188+ /// backward compatibility with historical snapshots that may contain deprecated
189+ /// fields like `timeouts`.
180190 pub fn from_stored (
181191 config_toml : & str ,
182192 extra_templates : HashMap < String , String > ,
193+ tags : HashMap < String , String > ,
183194 original_hash : & SnapshotHash ,
184195 ) -> Result < Self , Error > {
185196 let table: toml:: Table = config_toml. parse ( ) . map_err ( |e| {
@@ -189,19 +200,24 @@ impl ConfigSnapshot {
189200 } ) ?;
190201
191202 let sorted_table = prepare_table_for_snapshot ( table) ;
192- let config = UninitializedConfig :: try_from ( sorted_table. clone ( ) ) ?;
193- let hash = ConfigSnapshot :: hash ( & sorted_table, & extra_templates) ?;
194- if hash != * original_hash {
195- return Err ( Error :: new ( ErrorDetails :: ConfigSnapshotHashMismatch {
196- expected : original_hash. clone ( ) ,
197- actual : hash. clone ( ) ,
198- } ) ) ;
199- }
200203
204+ // Deserialize as StoredConfig to accept deprecated fields (e.g., `timeouts`)
205+ let stored_config: StoredConfig =
206+ serde_path_to_error:: deserialize ( sorted_table) . map_err ( |e| {
207+ let path = e. path ( ) . clone ( ) ;
208+ Error :: new ( ErrorDetails :: Config {
209+ message : format ! ( "{}: {}" , path, e. into_inner( ) . message( ) ) ,
210+ } )
211+ } ) ?;
212+
213+ // Use the original hash from the database rather than recomputing it.
214+ // Recomputing can produce different hashes due to floating-point serialization
215+ // differences (e.g., 0.2 vs 0.20000000298023224) even when the config is identical.
201216 Ok ( Self {
202- config : config . into ( ) ,
203- hash,
217+ config : stored_config ,
218+ hash : original_hash . clone ( ) ,
204219 extra_templates,
220+ tags,
205221 __private : ( ) ,
206222 } )
207223 }
0 commit comments