1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
use std::collections::HashSet;
use super::{ExecutionMode, Transaction};
use crate::actions::{DomainMetadata, INTERNAL_DOMAIN_PREFIX};
use crate::error::KernelError;
use crate::row_tracking::{
RowTrackingDomainMetadata, ROW_TRACKING_DOMAIN_NAME, ROW_TRACKING_INITIAL_HIGH_WATER_MARK,
};
use crate::table_features::TableFeature;
use crate::{Engine, KernelResult};
impl<STATE, MODE: ExecutionMode> Transaction<STATE, MODE> {
/// Validate domain metadata operations for both create-table and existing-table transactions.
///
/// Enforces the following rules:
/// - DomainMetadata feature must be supported if any domain operations are present
/// - System domains must correspond to a known feature
/// - User domains cannot use the delta.* prefix (system-reserved)
/// - Domain removals are not allowed in create-table transactions
/// - No duplicate domains within a single transaction (across user and system operations)
pub(super) fn validate_domain_metadata_operations(&self) -> KernelResult<()> {
// Feature validation (applies to all transactions with domain operations)
let has_domain_ops = !self.system_domain_metadata_additions.is_empty()
|| self.provided_row_tracking_high_water_mark.is_some()
|| !self.user_domain_metadata_additions.is_empty()
|| !self.user_domain_removals.is_empty();
// Early return if no domain operations to validate
if !has_domain_ops {
return Ok(());
}
if !self
.effective_table_config
.is_feature_supported(&TableFeature::DomainMetadata)
{
return Err(KernelError::unsupported(
"Domain metadata operations require writer version 7 and the 'domainMetadata' writer feature",
));
}
let is_create = self.is_create_table();
let mut seen_domains = HashSet::with_capacity(
self.system_domain_metadata_additions.len()
+ usize::from(self.provided_row_tracking_high_water_mark.is_some())
+ self.user_domain_metadata_additions.len()
+ self.user_domain_removals.len(),
);
// Validate system-domain additions produced by create-table transforms.
for dm in &self.system_domain_metadata_additions {
let domain = dm.domain();
// Validate the system domain corresponds to a known feature
self.validate_system_domain_feature(domain)?;
// Check for duplicates
if !seen_domains.insert(domain) {
return Err(KernelError::generic(format!(
"Metadata for domain {domain} already specified in this transaction"
)));
}
}
// Validate the dedicated existing-table row-tracking operation with the other domains.
if self.provided_row_tracking_high_water_mark.is_some() {
self.validate_system_domain_feature(ROW_TRACKING_DOMAIN_NAME)?;
if !seen_domains.insert(ROW_TRACKING_DOMAIN_NAME) {
return Err(KernelError::generic(format!(
"Metadata for domain {ROW_TRACKING_DOMAIN_NAME} already specified in this transaction"
)));
}
}
// Validate USER domain additions (via with_domain_metadata API)
for dm in &self.user_domain_metadata_additions {
let domain = dm.domain();
// Users cannot add system domains via the public API
if domain.starts_with(INTERNAL_DOMAIN_PREFIX) {
return Err(KernelError::generic(
"Cannot modify domains that start with 'delta.' as those are system controlled",
));
}
// Check for duplicates (spans both system and user domains)
if !seen_domains.insert(domain) {
return Err(KernelError::generic(format!(
"Metadata for domain {domain} already specified in this transaction"
)));
}
}
// No removals allowed for create-table.
// Note: CreateTableTransaction does not expose with_domain_metadata_removed(),
// so this is a defensive check. See #1768.
if is_create && !self.user_domain_removals.is_empty() {
return Err(KernelError::unsupported(
"Domain metadata removals are not supported in create-table transactions",
));
}
// Validate domain removals (for non-create-table)
for domain in &self.user_domain_removals {
// Cannot remove system domains
if domain.starts_with(INTERNAL_DOMAIN_PREFIX) {
return Err(KernelError::generic(
"Cannot modify domains that start with 'delta.' as those are system controlled",
));
}
// Check for duplicates
if !seen_domains.insert(domain.as_str()) {
return Err(KernelError::generic(format!(
"Metadata for domain {domain} already specified in this transaction"
)));
}
}
Ok(())
}
/// Validate that a system domain corresponds to a known feature and that the feature is
/// supported.
///
/// This prevents arbitrary `delta.*` domains from entering a transaction through internal
/// transforms or dedicated system-domain operations. Each known system domain must have its
/// corresponding feature enabled in the protocol.
fn validate_system_domain_feature(&self, domain: &str) -> KernelResult<()> {
let table_config = &self.effective_table_config;
// Map domain to its required feature
let required_feature = match domain {
ROW_TRACKING_DOMAIN_NAME => Some(TableFeature::RowTracking),
// Will be changed to a constant in a follow up clustering create table feature PR
"delta.clustering" => Some(TableFeature::ClusteredTable),
_ => {
return Err(KernelError::generic(format!(
"Unknown system domain '{domain}'. Only known system domains are allowed."
)));
}
};
// If the domain requires a feature, validate it's supported
if let Some(feature) = required_feature {
if !table_config.is_feature_supported(&feature) {
return Err(KernelError::generic(format!(
"System domain '{domain}' requires the '{feature}' feature to be enabled"
)));
}
}
Ok(())
}
/// Generate removal actions for user domain metadata by scanning the log.
///
/// This performs an expensive log replay operation to fetch the previous configuration
/// value for each domain being removed, as required by the Delta spec for tombstones.
/// Returns an empty vector if there are no domain removals.
pub(super) fn generate_user_domain_removal_actions(
&self,
engine: &dyn Engine,
) -> KernelResult<Vec<DomainMetadata>> {
if self.user_domain_removals.is_empty() {
return Ok(vec![]);
}
// Scan log to fetch existing configurations for tombstones.
// Pass the specific set of domains to remove so that log replay can terminate early
// once all target domains have been found, instead of replaying the entire log.
let domains: HashSet<&str> = self
.user_domain_removals
.iter()
.map(String::as_str)
.collect();
let existing_domains = self
.read_snapshot()?
.get_domain_metadatas_internal(engine, Some(&domains))?;
// Create removal tombstones with pre-image configurations
Ok(self
.user_domain_removals
.iter()
.filter_map(|domain| {
// If domain doesn't exist in the log, this is a no-op (filter it out)
existing_domains.get(domain).map(|existing| {
DomainMetadata::remove(domain.clone(), existing.configuration().to_owned())
})
})
.collect())
}
/// Generates domain metadata actions (assumes domain metadata operations have already
/// been validated)
///
/// Incorporates the calculated row-tracking high-water mark and returns the domain changes
/// for action encoding, commit metadata, and the post-commit
/// [`CrcDelta`](crate::crc::CrcDelta). Returns an error if a provided high-water mark is
/// below the calculated value or if domain state cannot be read.
///
/// This function may perform an expensive log replay operation if there are any domain
/// removals. The log replay is required to fetch the previous configuration value for the
/// domain to preserve in removal tombstones as mandated by the Delta spec.
pub(super) fn generate_domain_metadata_actions(
&self,
engine: &dyn Engine,
row_tracking_high_watermark: Option<RowTrackingDomainMetadata>,
) -> KernelResult<Vec<DomainMetadata>> {
let removal_actions = self.generate_user_domain_removal_actions(engine)?;
let row_tracking_high_watermark =
if let Some(provided) = self.provided_row_tracking_high_water_mark {
let calculated = match row_tracking_high_watermark {
Some(metadata) => metadata.high_water_mark(),
None => self
.read_snapshot()?
.get_row_tracking_high_water_mark(engine)?
.unwrap_or(ROW_TRACKING_INITIAL_HIGH_WATER_MARK),
};
if provided < calculated {
return Err(KernelError::generic(format!(
"Provided row-tracking high-water mark {provided} cannot be less than the \
calculated value {calculated}",
)));
}
Some(RowTrackingDomainMetadata::new(provided))
} else {
row_tracking_high_watermark
};
// Generate the single row-tracking domain action, if any.
let row_tracking_domain_action = row_tracking_high_watermark
.map(DomainMetadata::try_from)
.transpose()?
.into_iter();
// Chain all domain actions: system domains, row tracking, user domains, removals
let dm_actions_vec: Vec<DomainMetadata> = self
.system_domain_metadata_additions
.iter()
.cloned()
.chain(row_tracking_domain_action)
.chain(self.user_domain_metadata_additions.iter().cloned())
.chain(removal_actions)
.collect();
Ok(dm_actions_vec)
}
}
/// Returns the unique domains, or the caller's error for the first duplicate.
pub(super) fn validate_unique_domains<'a>(
domains: impl IntoIterator<Item = &'a str>,
duplicate_error: impl FnOnce(&str) -> KernelError,
) -> KernelResult<HashSet<&'a str>> {
let domains = domains.into_iter();
let mut seen = HashSet::with_capacity(domains.size_hint().0);
for domain in domains {
if !seen.insert(domain) {
return Err(duplicate_error(domain));
}
}
Ok(seen)
}