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}
53
54impl TableProperties {
55 pub const PROPERTY_FORMAT_VERSION: &str = "format-version";
65 pub const PROPERTY_UUID: &str = "uuid";
67 pub const PROPERTY_SNAPSHOT_COUNT: &str = "snapshot-count";
69 pub const PROPERTY_CURRENT_SNAPSHOT_SUMMARY: &str = "current-snapshot-summary";
71 pub const PROPERTY_CURRENT_SNAPSHOT_ID: &str = "current-snapshot-id";
73 pub const PROPERTY_CURRENT_SNAPSHOT_TIMESTAMP: &str = "current-snapshot-timestamp-ms";
75 pub const PROPERTY_CURRENT_SCHEMA: &str = "current-schema";
77 pub const PROPERTY_DEFAULT_PARTITION_SPEC: &str = "default-partition-spec";
79 pub const PROPERTY_DEFAULT_SORT_ORDER: &str = "default-sort-order";
81
82 pub const PROPERTY_METADATA_PREVIOUS_VERSIONS_MAX: &str =
84 "write.metadata.previous-versions-max";
85 pub const PROPERTY_METADATA_PREVIOUS_VERSIONS_MAX_DEFAULT: usize = 100;
87
88 pub const PROPERTY_WRITE_PARTITION_SUMMARY_LIMIT: &str = "write.summary.partition-limit";
90 pub const PROPERTY_WRITE_PARTITION_SUMMARY_LIMIT_DEFAULT: u64 = 0;
92
93 pub const RESERVED_PROPERTIES: [&str; 9] = [
98 Self::PROPERTY_FORMAT_VERSION,
99 Self::PROPERTY_UUID,
100 Self::PROPERTY_SNAPSHOT_COUNT,
101 Self::PROPERTY_CURRENT_SNAPSHOT_ID,
102 Self::PROPERTY_CURRENT_SNAPSHOT_SUMMARY,
103 Self::PROPERTY_CURRENT_SNAPSHOT_TIMESTAMP,
104 Self::PROPERTY_CURRENT_SCHEMA,
105 Self::PROPERTY_DEFAULT_PARTITION_SPEC,
106 Self::PROPERTY_DEFAULT_SORT_ORDER,
107 ];
108
109 pub const PROPERTY_COMMIT_NUM_RETRIES: &str = "commit.retry.num-retries";
111 pub const PROPERTY_COMMIT_NUM_RETRIES_DEFAULT: usize = 4;
113
114 pub const PROPERTY_COMMIT_MIN_RETRY_WAIT_MS: &str = "commit.retry.min-wait-ms";
116 pub const PROPERTY_COMMIT_MIN_RETRY_WAIT_MS_DEFAULT: u64 = 100;
118
119 pub const PROPERTY_COMMIT_MAX_RETRY_WAIT_MS: &str = "commit.retry.max-wait-ms";
121 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";
126 pub const PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS_DEFAULT: u64 = 30 * 60 * 1000; pub const PROPERTY_DEFAULT_FILE_FORMAT: &str = "write.format.default";
131 pub const PROPERTY_DELETE_DEFAULT_FILE_FORMAT: &str = "write.delete.format.default";
133 pub const PROPERTY_DEFAULT_FILE_FORMAT_DEFAULT: &str = "parquet";
135
136 pub const PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES: &str = "write.target-file-size-bytes";
138 pub const PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES_DEFAULT: usize = 512 * 1024 * 1024; }
141
142impl TryFrom<&HashMap<String, String>> for TableProperties {
143 type Error = anyhow::Error;
145
146 fn try_from(props: &HashMap<String, String>) -> Result<Self, Self::Error> {
147 Ok(TableProperties {
148 commit_num_retries: parse_property(
149 props,
150 TableProperties::PROPERTY_COMMIT_NUM_RETRIES,
151 TableProperties::PROPERTY_COMMIT_NUM_RETRIES_DEFAULT,
152 )?,
153 commit_min_retry_wait_ms: parse_property(
154 props,
155 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS,
156 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS_DEFAULT,
157 )?,
158 commit_max_retry_wait_ms: parse_property(
159 props,
160 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS,
161 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS_DEFAULT,
162 )?,
163 commit_total_retry_timeout_ms: parse_property(
164 props,
165 TableProperties::PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS,
166 TableProperties::PROPERTY_COMMIT_TOTAL_RETRY_TIME_MS_DEFAULT,
167 )?,
168 write_format_default: parse_property(
169 props,
170 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT,
171 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT_DEFAULT.to_string(),
172 )?,
173 write_target_file_size_bytes: parse_property(
174 props,
175 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES,
176 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES_DEFAULT,
177 )?,
178 })
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_table_properties_default() {
188 let props = HashMap::new();
189 let table_properties = TableProperties::try_from(&props).unwrap();
190 assert_eq!(
191 table_properties.commit_num_retries,
192 TableProperties::PROPERTY_COMMIT_NUM_RETRIES_DEFAULT
193 );
194 assert_eq!(
195 table_properties.commit_min_retry_wait_ms,
196 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS_DEFAULT
197 );
198 assert_eq!(
199 table_properties.commit_max_retry_wait_ms,
200 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS_DEFAULT
201 );
202 assert_eq!(
203 table_properties.write_format_default,
204 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT_DEFAULT.to_string()
205 );
206 assert_eq!(
207 table_properties.write_target_file_size_bytes,
208 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES_DEFAULT
209 );
210 }
211
212 #[test]
213 fn test_table_properties_valid() {
214 let props = HashMap::from([
215 (
216 TableProperties::PROPERTY_COMMIT_NUM_RETRIES.to_string(),
217 "10".to_string(),
218 ),
219 (
220 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS.to_string(),
221 "20".to_string(),
222 ),
223 (
224 TableProperties::PROPERTY_DEFAULT_FILE_FORMAT.to_string(),
225 "avro".to_string(),
226 ),
227 (
228 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES.to_string(),
229 "512".to_string(),
230 ),
231 ]);
232 let table_properties = TableProperties::try_from(&props).unwrap();
233 assert_eq!(table_properties.commit_num_retries, 10);
234 assert_eq!(table_properties.commit_max_retry_wait_ms, 20);
235 assert_eq!(table_properties.write_format_default, "avro".to_string());
236 assert_eq!(table_properties.write_target_file_size_bytes, 512);
237 }
238
239 #[test]
240 fn test_table_properties_invalid() {
241 let invalid_retries = HashMap::from([(
242 TableProperties::PROPERTY_COMMIT_NUM_RETRIES.to_string(),
243 "abc".to_string(),
244 )]);
245
246 let table_properties = TableProperties::try_from(&invalid_retries).unwrap_err();
247 assert!(
248 table_properties.to_string().contains(
249 "Invalid value for commit.retry.num-retries: invalid digit found in string"
250 )
251 );
252
253 let invalid_min_wait = HashMap::from([(
254 TableProperties::PROPERTY_COMMIT_MIN_RETRY_WAIT_MS.to_string(),
255 "abc".to_string(),
256 )]);
257 let table_properties = TableProperties::try_from(&invalid_min_wait).unwrap_err();
258 assert!(
259 table_properties.to_string().contains(
260 "Invalid value for commit.retry.min-wait-ms: invalid digit found in string"
261 )
262 );
263
264 let invalid_max_wait = HashMap::from([(
265 TableProperties::PROPERTY_COMMIT_MAX_RETRY_WAIT_MS.to_string(),
266 "abc".to_string(),
267 )]);
268 let table_properties = TableProperties::try_from(&invalid_max_wait).unwrap_err();
269 assert!(
270 table_properties.to_string().contains(
271 "Invalid value for commit.retry.max-wait-ms: invalid digit found in string"
272 )
273 );
274
275 let invalid_target_size = HashMap::from([(
276 TableProperties::PROPERTY_WRITE_TARGET_FILE_SIZE_BYTES.to_string(),
277 "abc".to_string(),
278 )]);
279 let table_properties = TableProperties::try_from(&invalid_target_size).unwrap_err();
280 assert!(table_properties.to_string().contains(
281 "Invalid value for write.target-file-size-bytes: invalid digit found in string"
282 ));
283 }
284}