11/*
2- * Licensed to the Apache Software Foundation (ASF) under one
3- * or more contributor license agreements. See the NOTICE file
4- * distributed with this work for additional information
5- * regarding copyright ownership. The ASF licenses this file
6- * to you under the Apache License, Version 2.0 (the
7- * "License"); you may not use this file except in compliance
8- * with the License. You may obtain a copy of the License at
2+ * Licensed to the Apache Software Foundation (ASF) under one or more
3+ * contributor license agreements. See the NOTICE file distributed with
4+ * this work for additional information regarding copyright ownership.
5+ * The ASF licenses this file to You under the Apache License, Version 2.0
6+ * (the "License"); you may not use this file except in compliance with
7+ * the License. You may obtain a copy of the License at
98 *
10- * http://www.apache.org/licenses/LICENSE-2.0
9+ * http://www.apache.org/licenses/LICENSE-2.0
1110 *
12- * Unless required by applicable law or agreed to in writing,
13- * software distributed under the License is distributed on an
14- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15- * KIND, either express or implied. See the License for the
16- * specific language governing permissions and limitations
17- * under the License.
11+ * Unless required by applicable law or agreed to in writing, software
12+ * distributed under the License is distributed on an "AS IS" BASIS,
13+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+ * See the License for the specific language governing permissions and
15+ * limitations under the License.
1816 */
17+
1918package org .apache .hadoop .ozone .iceberg ;
2019
20+ import java .util .HashSet ;
21+ import java .util .List ;
2122import java .util .Objects ;
23+ import java .util .Set ;
2224import java .util .UUID ;
2325import java .util .concurrent .ExecutorService ;
2426import java .util .concurrent .Executors ;
2527import java .util .concurrent .TimeUnit ;
26-
2728import org .apache .iceberg .HasTableOperations ;
29+ import org .apache .iceberg .PartitionStatisticsFile ;
2830import org .apache .iceberg .RewriteTablePathUtil ;
31+ import org .apache .iceberg .RewriteTablePathUtil .RewriteResult ;
32+ import org .apache .iceberg .Snapshot ;
33+ import org .apache .iceberg .StaticTableOperations ;
2934import org .apache .iceberg .Table ;
3035import org .apache .iceberg .TableMetadata ;
3136import org .apache .iceberg .TableMetadata .MetadataLogEntry ;
37+ import org .apache .iceberg .TableMetadataParser ;
3238import org .apache .iceberg .actions .ImmutableRewriteTablePath ;
3339import org .apache .iceberg .actions .RewriteTablePath ;
40+ import org .apache .iceberg .util .Pair ;
3441
42+ /**
43+ * An implementation of {@link RewriteTablePath} for Apache Ozone backed Iceberg tables.
44+ *
45+ * <p>This action rewrites table's metadata and position delete file paths by replacing a source
46+ * prefix with a target prefix. It processes table versions, snapshots, manifests and position delete files.</p>
47+ *
48+ * <p>The rewrite can be scoped between optional start and end metadata versions,
49+ * and all rewritten files are staged in a temporary directory.</p>
50+ */
3551public class RewriteTablePathOzoneAction implements RewriteTablePath {
3652
37- private static final String RESULT_LOCATION = "file-list" ;
38-
3953 private String sourcePrefix ;
4054 private String targetPrefix ;
4155 private String startVersionName ;
@@ -44,7 +58,6 @@ public class RewriteTablePathOzoneAction implements RewriteTablePath {
4458 private int parallelism ;
4559
4660 private final Table table ;
47- private ExecutorService executorService ;
4861
4962 public RewriteTablePathOzoneAction (Table table ) {
5063 this .table = table ;
@@ -58,38 +71,39 @@ public RewriteTablePathOzoneAction(Table table, int parallelism) {
5871
5972 @ Override
6073 public RewriteTablePath rewriteLocationPrefix (String sPrefix , String tPrefix ) {
61- checkNonNullNonEmpty (sPrefix , "Source prefix" );
62- checkNonNullNonEmpty (tPrefix , "Target prefix" );
74+ RewriteTablePathOzoneUtils . checkNonNullNonEmpty (sPrefix , "Source prefix" );
75+ RewriteTablePathOzoneUtils . checkNonNullNonEmpty (tPrefix , "Target prefix" );
6376 this .sourcePrefix = sPrefix ;
6477 this .targetPrefix = tPrefix ;
6578 return this ;
6679 }
6780
6881 @ Override
6982 public RewriteTablePath startVersion (String sVersion ) {
70- checkNonNullNonEmpty (sVersion , "Start version" );
83+ RewriteTablePathOzoneUtils . checkNonNullNonEmpty (sVersion , "Start version" );
7184 this .startVersionName = sVersion ;
7285 return this ;
7386 }
7487
7588 @ Override
7689 public RewriteTablePath endVersion (String eVersion ) {
77- checkNonNullNonEmpty (eVersion , "End version" );
90+ RewriteTablePathOzoneUtils . checkNonNullNonEmpty (eVersion , "End version" );
7891 this .endVersionName = eVersion ;
7992 return this ;
8093 }
8194
8295 @ Override
8396 public RewriteTablePath stagingLocation (String stagingLocation ) {
84- checkNonNullNonEmpty (stagingLocation , "Staging location" );
97+ RewriteTablePathOzoneUtils . checkNonNullNonEmpty (stagingLocation , "Staging location" );
8598 this .stagingDir = stagingLocation ;
8699 return this ;
87100 }
88101
89102 @ Override
90103 public Result execute () {
91104 validateInputs ();
92- this .executorService = Executors .newFixedThreadPool (parallelism );
105+ // TODO: should use for parallel manifest and position delete file rewriting.
106+ ExecutorService executorService = Executors .newFixedThreadPool (parallelism );
93107 try {
94108 return doExecute ();
95109 } finally {
@@ -121,12 +135,13 @@ private void validateInputs() {
121135 "Source prefix cannot be the same as target prefix (%s)" , sourcePrefix ));
122136 }
123137
124- validateAndSetEndVersion ();
125- validateAndSetStartVersion ();
138+ TableMetadata tableMetadata = ((HasTableOperations ) table ).operations ().current ();
139+ validateAndSetEndVersion (tableMetadata );
140+ validateAndSetStartVersion (tableMetadata );
126141
127142 if (stagingDir == null ) {
128143 stagingDir =
129- getMetadataLocation (table )
144+ RewriteTablePathOzoneUtils . getMetadataLocation (table )
130145 + "copy-table-staging-"
131146 + UUID .randomUUID ()
132147 + RewriteTablePathUtil .FILE_SEPARATOR ;
@@ -135,9 +150,7 @@ private void validateInputs() {
135150 }
136151 }
137152
138- private void validateAndSetEndVersion () {
139- TableMetadata tableMetadata = ((HasTableOperations ) table ).operations ().current ();
140-
153+ private void validateAndSetEndVersion (TableMetadata tableMetadata ) {
141154 if (endVersionName == null ) {
142155 Objects .requireNonNull (
143156 tableMetadata .metadataFileLocation (), "Metadata file location should not be null" );
@@ -147,9 +160,7 @@ private void validateAndSetEndVersion() {
147160 }
148161 }
149162
150- private void validateAndSetStartVersion () {
151- TableMetadata tableMetadata = ((HasTableOperations ) table ).operations ().current ();
152-
163+ private void validateAndSetStartVersion (TableMetadata tableMetadata ) {
153164 if (startVersionName != null ) {
154165 this .startVersionName = validateVersion (tableMetadata , startVersionName );
155166 }
@@ -159,11 +170,12 @@ private String validateVersion(TableMetadata tableMetadata, String versionFileNa
159170 String versionFile = null ;
160171 if (versionInFilePath (tableMetadata .metadataFileLocation (), versionFileName )) {
161172 versionFile = tableMetadata .metadataFileLocation ();
162- }
163-
164- for (MetadataLogEntry log : tableMetadata .previousFiles ()) {
165- if (versionInFilePath (log .file (), versionFileName )) {
166- versionFile = log .file ();
173+ } else {
174+ for (MetadataLogEntry log : tableMetadata .previousFiles ()) {
175+ if (versionInFilePath (log .file (), versionFileName )) {
176+ versionFile = log .file ();
177+ break ;
178+ }
167179 }
168180 }
169181
@@ -172,7 +184,7 @@ private String validateVersion(TableMetadata tableMetadata, String versionFileNa
172184 String .format (
173185 "Cannot find provided version file %s in metadata log." , versionFileName ));
174186 }
175- if (!fileExist (versionFile )) {
187+ if (!RewriteTablePathOzoneUtils . fileExist (versionFile , table . io () )) {
176188 throw new IllegalArgumentException (
177189 String .format ("Version file %s does not exist." , versionFile ));
178190 }
@@ -184,36 +196,59 @@ private boolean versionInFilePath(String path, String version) {
184196 }
185197
186198 private String rebuildMetadata () {
187- //TODO need to implement rewrite of metadata files , manifest list , manifest files and position delete files.
188- return null ;
189- }
199+ //TODO need to implement rewrite of manifest list , manifest files and position delete files.
200+ TableMetadata endMetadata = new StaticTableOperations (endVersionName , table .io ()).current ();
190201
191- private boolean fileExist ( String path ) {
192- if (path == null || path . trim () .isEmpty ()) {
193- return false ;
202+ List < PartitionStatisticsFile > partitionStats = endMetadata . partitionStatisticsFiles ();
203+ if (partitionStats != null && ! partitionStats .isEmpty ()) {
204+ throw new IllegalArgumentException ( "Partition statistics files are not supported yet." ) ;
194205 }
195- return table .io ().newInputFile (path ).exists ();
206+
207+ RewriteResult <Snapshot > rewriteVersionResult = rewriteVersionFiles (endMetadata );
208+
209+ Set <Pair <String , String >> copyPlan = new HashSet <>();
210+ copyPlan .addAll (rewriteVersionResult .copyPlan ());
211+
212+ return RewriteTablePathOzoneUtils .saveFileList (copyPlan , stagingDir , table .io ());
196213 }
197214
198- private String getMetadataLocation (Table tbl ) {
199- String currentMetadataPath =
200- ((HasTableOperations ) tbl ).operations ().current ().metadataFileLocation ();
201- int lastIndex = currentMetadataPath .lastIndexOf (RewriteTablePathUtil .FILE_SEPARATOR );
202- String metadataDir = "" ;
203- if (lastIndex != -1 ) {
204- metadataDir = currentMetadataPath .substring (0 , lastIndex + 1 );
205- }
215+ private RewriteResult <Snapshot > rewriteVersionFiles (TableMetadata endMetadata ) {
216+ RewriteResult <Snapshot > result = new RewriteResult <>();
217+ result .toRewrite ().addAll (endMetadata .snapshots ());
218+ result .copyPlan ().addAll (rewriteVersionFile (endMetadata , endVersionName ));
219+
220+ List <MetadataLogEntry > versions = endMetadata .previousFiles ();
221+ for (int i = versions .size () - 1 ; i >= 0 ; i --) {
222+ String versionFilePath = versions .get (i ).file ();
223+ if (versionFilePath .equals (startVersionName )) {
224+ break ;
225+ }
226+
227+ if (!RewriteTablePathOzoneUtils .fileExist (versionFilePath , table .io ())) {
228+ throw new IllegalArgumentException (String .format ("Version file %s doesn't exist" , versionFilePath ));
229+ }
230+
231+ TableMetadata tableMetadata = new StaticTableOperations (versionFilePath , table .io ()).current ();
206232
207- if ( metadataDir . isEmpty ()) {
208- throw new IllegalArgumentException ( "Failed to get the metadata file root directory" );
233+ result . toRewrite (). addAll ( tableMetadata . snapshots ());
234+ result . copyPlan (). addAll ( rewriteVersionFile ( tableMetadata , versionFilePath ) );
209235 }
210- return metadataDir ;
236+
237+ return result ;
211238 }
212239
213- private static void checkNonNullNonEmpty (String value , String name ) {
214- Objects .requireNonNull (value , () -> name + " is null" );
215- if (value .trim ().isEmpty ()) {
216- throw new IllegalArgumentException (name + " is empty" );
217- }
240+ private Set <Pair <String , String >> rewriteVersionFile (TableMetadata metadata , String versionFilePath ) {
241+ Set <Pair <String , String >> result = new HashSet <>();
242+ String stagingPath = RewriteTablePathUtil .stagingPath (versionFilePath , sourcePrefix , stagingDir );
243+
244+ System .out .println ("Processing version file " + versionFilePath );
245+ TableMetadata newTableMetadata = RewriteTablePathUtil .replacePaths (metadata , sourcePrefix , targetPrefix );
246+ TableMetadataParser .overwrite (newTableMetadata , table .io ().newOutputFile (stagingPath ));
247+
248+ result .add (Pair .of (stagingPath , RewriteTablePathUtil .newPath (versionFilePath , sourcePrefix , targetPrefix )));
249+ result .addAll (RewriteTablePathOzoneUtils .statsFileCopyPlan (
250+ metadata .statisticsFiles (), newTableMetadata .statisticsFiles ()));
251+
252+ return result ;
218253 }
219254}
0 commit comments