@@ -9,7 +9,7 @@ use lance_file::version::{ConcreteFileVersion, LanceFileVersion};
99use lance_io:: object_store:: { ObjectStore , ObjectStoreParams } ;
1010use lance_select:: RowAddrTreeMap ;
1111use lance_table:: {
12- format:: { DataStorageFormat , is_detached_version} ,
12+ format:: { DataFile , DataStorageFormat , Fragment , is_detached_version} ,
1313 io:: commit:: { CommitConfig , CommitHandler , ManifestNamingScheme } ,
1414} ;
1515
@@ -59,6 +59,79 @@ pub struct CommitBuilder<'a> {
5959/// Default timeout applied to [`CommitBuilder::execute`] when none is set.
6060pub const DEFAULT_COMMIT_TIMEOUT : Duration = Duration :: from_secs ( 1800 ) ;
6161
62+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
63+ enum OperationVersionState {
64+ Versionless ,
65+ NoFiles ,
66+ ReferencesTarget ,
67+ ReferencesOther ,
68+ }
69+
70+ fn data_files_version_state < ' a > (
71+ files : impl IntoIterator < Item = & ' a DataFile > ,
72+ version : ConcreteFileVersion ,
73+ ) -> Result < OperationVersionState > {
74+ let mut saw_file = false ;
75+ for file in files {
76+ saw_file = true ;
77+ if file. file_version ( ) ? == version {
78+ return Ok ( OperationVersionState :: ReferencesTarget ) ;
79+ }
80+ }
81+ Ok ( if saw_file {
82+ OperationVersionState :: ReferencesOther
83+ } else {
84+ OperationVersionState :: NoFiles
85+ } )
86+ }
87+
88+ fn fragments_version_state < ' a > (
89+ fragments : impl IntoIterator < Item = & ' a Fragment > ,
90+ version : ConcreteFileVersion ,
91+ ) -> Result < OperationVersionState > {
92+ data_files_version_state (
93+ fragments
94+ . into_iter ( )
95+ . flat_map ( Fragment :: referenced_lance_files) ,
96+ version,
97+ )
98+ }
99+
100+ fn operation_version_state (
101+ operation : & Operation ,
102+ version : ConcreteFileVersion ,
103+ ) -> Result < OperationVersionState > {
104+ match operation {
105+ Operation :: Append { fragments }
106+ | Operation :: Overwrite { fragments, .. }
107+ | Operation :: Merge { fragments, .. } => fragments_version_state ( fragments, version) ,
108+ Operation :: Rewrite { groups, .. } => fragments_version_state (
109+ groups. iter ( ) . flat_map ( |group| group. new_fragments . iter ( ) ) ,
110+ version,
111+ ) ,
112+ Operation :: Update {
113+ updated_fragments,
114+ new_fragments,
115+ ..
116+ } => fragments_version_state (
117+ updated_fragments. iter ( ) . chain ( new_fragments. iter ( ) ) ,
118+ version,
119+ ) ,
120+ Operation :: DataReplacement { replacements } => data_files_version_state (
121+ replacements. iter ( ) . map ( |replacement| & replacement. 1 ) ,
122+ version,
123+ ) ,
124+ Operation :: DataOverlay { groups } => data_files_version_state (
125+ groups
126+ . iter ( )
127+ . flat_map ( |group| group. overlays . iter ( ) )
128+ . map ( |overlay| & overlay. data_file ) ,
129+ version,
130+ ) ,
131+ _ => Ok ( OperationVersionState :: Versionless ) ,
132+ }
133+ }
134+
62135impl < ' a > CommitBuilder < ' a > {
63136 pub fn new ( dest : impl Into < WriteDestination < ' a > > ) -> Self {
64137 Self {
@@ -98,8 +171,9 @@ impl<'a> CommitBuilder<'a> {
98171 /// This is only needed when creating a new empty table. If any data files are
99172 /// passed, the storage format will be inferred from the data files.
100173 ///
101- /// All data files must use the same storage format as the existing dataset.
102- /// If a different format is passed, an error will be returned.
174+ /// For an existing dataset, the manifest fallback remains unchanged. If
175+ /// prewritten fragments introduce another exact V2 version, the commit
176+ /// derives the required mixed-version capability from the final manifest.
103177 pub fn with_storage_format ( mut self , storage_format : LanceFileVersion ) -> Self {
104178 self . storage_format = Some ( storage_format. resolve ( ) ) ;
105179
@@ -409,20 +483,24 @@ impl<'a> CommitBuilder<'a> {
409483 } else {
410484 self . use_stable_row_ids . unwrap_or ( false )
411485 } ;
412- // Validate storage format matches existing dataset
413- if let Some ( ds ) = dest. dataset ( )
486+
487+ if let Some ( dataset ) = dest. dataset ( )
414488 && let Some ( storage_format) = self . storage_format
489+ && dataset. manifest . data_storage_format . lance_file_format ( ) != storage_format
490+ && !matches ! ( transaction. operation, Operation :: Overwrite { .. } )
491+ && matches ! (
492+ operation_version_state( & transaction. operation, storage_format) ?,
493+ OperationVersionState :: Versionless | OperationVersionState :: ReferencesOther
494+ )
415495 {
416- let passed_storage_format = DataStorageFormat :: new ( storage_format) ;
417- if ds. manifest . data_storage_format != passed_storage_format
418- && !matches ! ( transaction. operation, Operation :: Overwrite { .. } )
419- {
420- return Err ( Error :: invalid_input_source ( format ! (
421- "Storage format mismatch. Existing dataset uses {:?}, but new data uses {:?}" ,
422- ds. manifest. data_storage_format,
423- passed_storage_format
424- ) . into ( ) ) ) ;
425- }
496+ return Err ( Error :: invalid_input_source (
497+ format ! (
498+ "Storage format mismatch. Existing dataset fallback is {:?}, but the commit requested {:?} without referencing data files in that version" ,
499+ dataset. manifest. data_storage_format,
500+ DataStorageFormat :: new( storage_format)
501+ )
502+ . into ( ) ,
503+ ) ) ;
426504 }
427505
428506 let manifest_config = ManifestWriteConfig {
@@ -652,6 +730,26 @@ mod tests {
652730 }
653731 }
654732
733+ #[ test]
734+ fn empty_file_writes_do_not_require_a_target_version ( ) {
735+ let operation = Operation :: Update {
736+ updated_fragments : vec ! [ ] ,
737+ new_fragments : vec ! [ ] ,
738+ removed_fragment_ids : vec ! [ ] ,
739+ fields_modified : vec ! [ ] ,
740+ compacted_sstables : vec ! [ ] ,
741+ fields_for_preserving_frag_bitmap : vec ! [ ] ,
742+ update_mode : None ,
743+ inserted_rows_filter : None ,
744+ updated_fragment_offsets : None ,
745+ } ;
746+
747+ assert_eq ! (
748+ operation_version_state( & operation, ConcreteFileVersion :: V2_2 ) . unwrap( ) ,
749+ OperationVersionState :: NoFiles
750+ ) ;
751+ }
752+
655753 #[ derive( Debug ) ]
656754 struct SlowConflictingCommitHandler ;
657755
0 commit comments