@@ -26,8 +26,9 @@ import com.nvidia.spark.rapids.fileio.iceberg.IcebergFileIO
2626import com .nvidia .spark .rapids .iceberg .GpuIcebergPartitioner
2727import org .apache .hadoop .mapreduce .Job
2828import org .apache .hadoop .shaded .org .apache .commons .lang3 .reflect .{FieldUtils , MethodUtils }
29- import org .apache .iceberg .{DataFile , FileFormat , PartitionSpec , Schema , SerializableTable , SnapshotUpdate , Table }
29+ import org .apache .iceberg .{DataFile , FileFormat , PartitionSpec , Schema , SerializableTable , SnapshotUpdate , Table , TableProperties }
3030import org .apache .iceberg .io .{DataWriteResult , FileIO , GpuClusteredDataWriter , GpuFanoutDataWriter , GpuRollingDataWriter , OutputFileFactory , PartitioningWriter }
31+ import org .apache .iceberg .spark .{Spark3Util , SparkSchemaUtil }
3132import org .apache .iceberg .spark .functions .{GpuFieldTransform , GpuTransform }
3233import org .apache .iceberg .spark .source .SparkWrite .TaskCommit
3334
@@ -39,6 +40,7 @@ import org.apache.spark.sql.connector.distributions.Distribution
3940import org .apache .spark .sql .connector .expressions .SortOrder
4041import org .apache .spark .sql .connector .write .{BatchWrite , DataWriter , DataWriterFactory , RequiresDistributionAndOrdering , Write , WriterCommitMessage }
4142import org .apache .spark .sql .connector .write .streaming .StreamingWrite
43+ import org .apache .spark .sql .execution .datasources .v2 .AtomicCreateTableAsSelectExec
4244import org .apache .spark .sql .rapids .GpuWriteJobStatsTracker
4345import org .apache .spark .sql .types .StructType
4446import org .apache .spark .sql .vectorized .ColumnarBatch
@@ -141,28 +143,30 @@ object GpuSparkWrite {
141143 classOf [SparkWrite ].isAssignableFrom(cpuClass)
142144 }
143145
144- def tagForGpu (cpuWrite : Write , meta : SparkPlanMeta [_]): Unit = {
145- if (! supports(cpuWrite.getClass)) {
146- meta.willNotWorkOnGpu(s " GpuSparkWrite only supports ${classOf [SparkWrite ].getName}, " +
147- s " but got: ${cpuWrite.getClass.getName}" )
148- return
149- }
150-
151- val dataFileFormat : FileFormat = FieldUtils .readField(cpuWrite, " format" , true )
152- .asInstanceOf [FileFormat ]
153-
146+ /**
147+ * Tag for GPU support for Iceberg write operations.
148+ * This method checks:
149+ * 1. File format is supported (only Parquet)
150+ * 2. Partition transforms are supported
151+ *
152+ * @param dataFileFormat The file format for the write operation
153+ * @param partitionSpec The partition specification
154+ * @param dsSchema The DataFrame schema
155+ * @param writeSchema The write schema
156+ * @param meta The metadata for tagging
157+ */
158+ private def tagForGpuWrite (
159+ dataFileFormat : FileFormat ,
160+ partitionSpec : PartitionSpec ,
161+ dsSchema : StructType ,
162+ writeSchema : Schema ,
163+ meta : SparkPlanMeta [_]): Unit = {
164+ // Check file format support
154165 if (! dataFileFormat.equals(FileFormat .PARQUET )) {
155166 meta.willNotWorkOnGpu(s " GpuSparkWrite only supports Parquet, but got: $dataFileFormat" )
156167 }
157168
158- val table : Table = FieldUtils .readField(cpuWrite, " table" , true ).asInstanceOf [Table ]
159- val partitionSpec = table.spec()
160-
161- val dsSchema = FieldUtils .readField(cpuWrite, " dsSchema" , true )
162- .asInstanceOf [StructType ]
163- val writeSchema = FieldUtils .readField(cpuWrite, " writeSchema" , true )
164- .asInstanceOf [Schema ]
165-
169+ // Check partition transform support
166170 if (partitionSpec.isPartitioned) {
167171 for (partitionField <- partitionSpec.fields().asScala) {
168172 val transform = partitionField.transform()
@@ -180,6 +184,55 @@ object GpuSparkWrite {
180184 }
181185 }
182186
187+ def tagForGpu (cpuWrite : Write , meta : SparkPlanMeta [_]): Unit = {
188+ if (! supports(cpuWrite.getClass)) {
189+ meta.willNotWorkOnGpu(s " GpuSparkWrite only supports ${classOf [SparkWrite ].getName}, " +
190+ s " but got: ${cpuWrite.getClass.getName}" )
191+ return
192+ }
193+
194+ val dataFileFormat : FileFormat = FieldUtils .readField(cpuWrite, " format" , true )
195+ .asInstanceOf [FileFormat ]
196+
197+ val table : Table = FieldUtils .readField(cpuWrite, " table" , true ).asInstanceOf [Table ]
198+ val partitionSpec = table.spec()
199+
200+ val dsSchema = FieldUtils .readField(cpuWrite, " dsSchema" , true )
201+ .asInstanceOf [StructType ]
202+ val writeSchema = FieldUtils .readField(cpuWrite, " writeSchema" , true )
203+ .asInstanceOf [Schema ]
204+
205+ tagForGpuWrite(dataFileFormat, partitionSpec, dsSchema, writeSchema, meta)
206+ }
207+
208+ /**
209+ * Tag for GPU support for Iceberg CTAS operations.
210+ * This method checks file format and partitioning support for CREATE TABLE AS SELECT.
211+ *
212+ * @param cpuExec The CPU AtomicCreateTableAsSelectExec
213+ * @param meta The metadata for tagging
214+ */
215+ def tagForGpuCtas (
216+ cpuExec : AtomicCreateTableAsSelectExec ,
217+ meta : SparkPlanMeta [_]): Unit = {
218+ val properties : Map [String , String ] = Spark3Util
219+ .rebuildCreateProperties(cpuExec.tableSpec.properties.asJava)
220+ .asScala.toMap
221+ val fileFormatStr = properties.getOrElse(TableProperties .DEFAULT_FILE_FORMAT ,
222+ TableProperties .DEFAULT_FILE_FORMAT_DEFAULT )
223+
224+ val fileFormat = FileFormat .fromString(fileFormatStr)
225+
226+ // Convert Spark schema to Iceberg schema
227+ val querySchema = cpuExec.query.schema
228+ val icebergSchema = SparkSchemaUtil .convert(querySchema)
229+
230+ // Convert Spark connector transforms to Iceberg PartitionSpec
231+ val partitionSpec = Spark3Util .toPartitionSpec(icebergSchema, cpuExec.partitioning.toArray)
232+
233+ // Reuse tagForGpuWrite for validation
234+ tagForGpuWrite(fileFormat, partitionSpec, querySchema, icebergSchema, meta)
235+ }
183236
184237 def convert (cpuWrite : Write ): GpuSparkWrite = {
185238 new GpuSparkWrite (cpuWrite.asInstanceOf [SparkWrite ])
0 commit comments