iceberg/spec/
table_properties.rs1use std::collections::HashMap;
19
20fn parse_property<T: std::str::FromStr>(
23 properties: &HashMap<String, String>,
24 key: &str,
25 default: T,
26) -> Result<T, anyhow::Error>
27where
28 <T as std::str::FromStr>::Err: std::fmt::Display,
29{
30 properties.get(key).map_or(Ok(default), |value| {
31 value
32 .parse::<T>()
33 .map_err(|e| anyhow::anyhow!("Invalid value for {key}: {e}"))
34 })
35}
36
37#[derive(Debug)]
39pub struct TableProperties {
40 pub commit_num_retries: usize,
42 pub commit_min_retry_wait_ms: u64,
44 pub commit_max_retry_wait_ms: u64,
46 pub commit_total_retry_timeout_ms: u64,
48 pub write_format_default: String,
50 pub write_target_file_size_bytes: usize,
52 pub write_datafusion_fanout_enabled: bool,
54}
55
56impl TableProperties {
57 pub const PROPERTY_FORMAT_VERSION: &str = "format-version";
67 pub const PROPERTY_UUID: &str = "uuid";
69 pub const PROPERTY_SNAPSHOT_COUNT: &str = "snapshot-count";
71 pub const PROPERTY_CURRENT_SNAPSHOT_SUMMARY: &str = "current-snapshot-summary";
73 pub const PROPERTY_CURRENT_SNAPSHOT_ID: &str = "current-snapshot-id";
75 pub const PROPERTY_CURRENT_SNAPSHOT_TIMESTAMP: &str = "current-snapshot-timestamp-ms";
77 pub const PROPERTY_CURRENT_SCHEMA: &str = "current-schema";
79 pub const PROPERTY_DEFAULT_PARTITION_SPEC: &str = "default-partition-spec";
81 pub const PROPERTY_DEFAULT_SORT_ORDER: &str = "default-sort-order";
83
84 pub const PROPERTY_METADATA_PREVIOUS_VERSIONS_MAX: &str =
86 "write.metadata.previous-versions-max";
87 pub const PROPERTY_METADATA_PREVIOUS_VERSIONS_MAX_DEFAULT: usize = 100;
89
90 pub const PROPERTY_WRITE_PARTITION_SUMMARY_LIMIT: &str = "write.summary.partition-limit";
92 pub const PROPERTY_WRITE_PARTITION_SUMMARY_LIMIT_DEFAULT: u64 = 0;
94
95 pub const RESERVED_PROPERTIES: [&str; 9] = [
100 Self::PROPERTY_FORMAT_VERSION,
101 Self::PROPERTY_UUID,
102 Self::PROPERTY_SNAPSHOT_COUNT,
103 Self::PROPERTY_CURRENT_SNAPSHOT_ID,
104 Self::PROPERTY_CURRENT_SNAPSHOT_SUMMARY,
105 Self::PROPERTY_CURRENT_SNAPSHOT_TIMESTAMP,
106 Self::PROPERTY_CURRENT_SCHEMA,
107 Self::PROPERTY_DEFAULT_PARTITION_SPEC,
108 Self::PROPERTY_DEFAULT_SORT_ORDER,
109 ];
110
111 pub const PROPERTY_COMMIT_NUM_RETRIES: &str = "commit.retry.num-retries";
113 pub const PROPERTY_COMMIT_NUM_RETRIES_DEFAULT: usize = 4;
115
116 pub const PROPERTY_COMMIT_MIN_RETRY_WAIT_MS: &str = "commit.retry.min-wait-ms";
118 pub const PROPERTY_COMMIT_MIN_RETRY_WAIT_MS_DEFAULT: u64 = 100;
120
121 pub const PROPERTY_COMMIT_MAX_RETRY_WAIT_MS: &str = "commit.retry.max-wait-ms";
123 pub const PROPERTY_COMMIT_MAX_RETRY_WAIT_MS_DEFAULT: u64 = 60 * 1000; pub const PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS: &str = "commit.retry.total-timeout-ms";
128 pub const PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS_DEFAULT: u64 = 30 * 60 * 1000; pub const PROPERTY_DEFAULT_FILE_FORMAT: &str = "write.format.default";
133 pub const PROPERTY_DELETE_DEFAULT_FILE_FORMAT: &str = "write.delete.format.default";
135 pub const PROPERTY_DEFAULT_FILE_FORMAT_DEFAULT: &str = "parquet";
137
138 pub const PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES: &str = "write.target-file-size-bytes";
140 pub const PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES_DEFAULT: usize = 512 * 1024 * 1024; pub const PROPERTY_DATAFUSION_WRITE_FANOUT_ENABLED: &str = "write.datafusion.fanout.enabled";
145 pub const PROPERTY_DATAFUSION_WRITE_FANOUT_ENABLED_DEFAULT: bool = true;
147}
148
149impl TryFrom<&HashMap<String, String>> for TableProperties {
150 type Error = anyhow::Error;
152
153 fn try_from(props: &HashMap<String, String>) -> Result<Self, Self::Error> {
154 Ok(TableProperties {
155 commit_num_retries: parse_property(
156 props,
157 TableProperties::PROPERTY_COMMIT_NUM_RETRIES,
158 TableProperties::PROPERTY_COMMIT_NUM_RETRIES_DEFAULT,
159 )?,
160 commit_min_retry_wait_ms: parse_property(
161 props,
162 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS,
163 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS_DEFAULT,
164 )?,
165 commit_max_retry_wait_ms: parse_property(
166 props,
167 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS,
168 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS_DEFAULT,
169 )?,
170 commit_total_retry_timeout_ms: parse_property(
171 props,
172 TableProperties::PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS,
173 TableProperties::PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS_DEFAULT,
174 )?,
175 write_format_default: parse_property(
176 props,
177 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT,
178 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT_DEFAULT.to_string(),
179 )?,
180 write_target_file_size_bytes: parse_property(
181 props,
182 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES,
183 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES_DEFAULT,
184 )?,
185 write_datafusion_fanout_enabled: parse_property(
186 props,
187 TableProperties::PROPERTY_DATAFUSION_WRITE_FANOUT_ENABLED,
188 TableProperties::PROPERTY_DATAFUSION_WRITE_FANOUT_ENABLED_DEFAULT,
189 )?,
190 })
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_table_properties_default() {
200 let props = HashMap::new();
201 let table_properties = TableProperties::try_from(&props).unwrap();
202 assert_eq!(
203 table_properties.commit_num_retries,
204 TableProperties::PROPERTY_COMMIT_NUM_RETRIES_DEFAULT
205 );
206 assert_eq!(
207 table_properties.commit_min_retry_wait_ms,
208 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS_DEFAULT
209 );
210 assert_eq!(
211 table_properties.commit_max_retry_wait_ms,
212 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS_DEFAULT
213 );
214 assert_eq!(
215 table_properties.write_format_default,
216 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT_DEFAULT.to_string()
217 );
218 assert_eq!(
219 table_properties.write_target_file_size_bytes,
220 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES_DEFAULT
221 );
222 }
223
224 #[test]
225 fn test_table_properties_valid() {
226 let props = HashMap::from([
227 (
228 TableProperties::PROPERTY_COMMIT_NUM_RETRIES.to_string(),
229 "10".to_string(),
230 ),
231 (
232 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS.to_string(),
233 "20".to_string(),
234 ),
235 (
236 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT.to_string(),
237 "avro".to_string(),
238 ),
239 (
240 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES.to_string(),
241 "512".to_string(),
242 ),
243 ]);
244 let table_properties = TableProperties::try_from(&props).unwrap();
245 assert_eq!(table_properties.commit_num_retries, 10);
246 assert_eq!(table_properties.commit_max_retry_wait_ms, 20);
247 assert_eq!(table_properties.write_format_default, "avro".to_string());
248 assert_eq!(table_properties.write_target_file_size_bytes, 512);
249 }
250
251 #[test]
252 fn test_table_properties_invalid() {
253 let invalid_retries = HashMap::from([(
254 TableProperties::PROPERTY_COMMIT_NUM_RETRIES.to_string(),
255 "abc".to_string(),
256 )]);
257
258 let table_properties = TableProperties::try_from(&invalid_retries).unwrap_err();
259 assert!(
260 table_properties.to_string().contains(
261 "Invalid value for commit.retry.num-retries: invalid digit found in string"
262 )
263 );
264
265 let invalid_min_wait = HashMap::from([(
266 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS.to_string(),
267 "abc".to_string(),
268 )]);
269 let table_properties = TableProperties::try_from(&invalid_min_wait).unwrap_err();
270 assert!(
271 table_properties.to_string().contains(
272 "Invalid value for commit.retry.min-wait-ms: invalid digit found in string"
273 )
274 );
275
276 let invalid_max_wait = HashMap::from([(
277 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS.to_string(),
278 "abc".to_string(),
279 )]);
280 let table_properties = TableProperties::try_from(&invalid_max_wait).unwrap_err();
281 assert!(
282 table_properties.to_string().contains(
283 "Invalid value for commit.retry.max-wait-ms: invalid digit found in string"
284 )
285 );
286
287 let invalid_target_size = HashMap::from([(
288 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES.to_string(),
289 "abc".to_string(),
290 )]);
291 let table_properties = TableProperties::try_from(&invalid_target_size).unwrap_err();
292 assert!(table_properties.to_string().contains(
293 "Invalid value for write.target-file-size-bytes: invalid digit found in string"
294 ));
295 }
296}