@@ -31,6 +31,8 @@ import com.spotify.scio.parquet.avro.ParquetAvroIO.WriteParam._
3131import com .spotify .scio .parquet .read .ParquetReadConfiguration
3232import com .spotify .scio .parquet .{GcsConnectorUtil , ParquetConfiguration }
3333import com .spotify .scio .testing .TestDataManager
34+ import com .spotify .scio .transforms ._
35+ import com .spotify .scio .transforms .DoFnWithResource .ResourceType
3436import com .spotify .scio .util .{FilenamePolicySupplier , ScioUtil }
3537import com .spotify .scio .values .SCollection
3638import org .apache .avro .Schema
@@ -55,6 +57,7 @@ import scala.jdk.CollectionConverters._
5557import scala .reflect .ClassTag
5658
5759sealed trait ParquetAvroIO [T <: IndexedRecord ] extends ScioIO [T ] {
60+ import ParquetAvroIO ._
5861
5962 override type ReadP = ParquetAvroIO .ReadParam [T ]
6063 override type WriteP = ParquetAvroIO .WriteParam [T ]
@@ -141,36 +144,29 @@ sealed trait ParquetAvroIO[T <: IndexedRecord] extends ScioIO[T] {
141144 override protected def readTest (sc : ScioContext , params : ReadP ): SCollection [T ] = {
142145 val datumFactory = Option (params.datumFactory).getOrElse(defaultDatumFactory)
143146 implicit val coder : Coder [T ] = avroCoder(datumFactory, schema)
144- // SpecificData.getForClass is only available for 1.9+
145- val recordClass = datumFactory.getType
146- val data = if (classOf [SpecificRecordBase ].isAssignableFrom(recordClass)) {
147- val classModelField = recordClass.getDeclaredField(" MODEL$" )
148- classModelField.setAccessible(true )
149- classModelField.get(null ).asInstanceOf [SpecificData ]
150- } else {
151- SpecificData .get()
152- }
147+
153148 // The projection function is not part of the test input, so it must be applied directly
154149 val projectedFields = Option (params.projection).map(_.getFields.asScala.map(_.name()).toSet)
155150 TestDataManager
156151 .getInput(sc.testId.get)(this )
157152 .toSCollection(sc)
158- .map { record =>
159- projectedFields match {
160- case None => record
161- case Some (projection) =>
162- // beam forbids mutations. Create a new record
163- val copy = data.deepCopy(record.getSchema, record)
164- record.getSchema.getFields.asScala
165- .foldLeft(copy) { (c, f) =>
166- val names = Set (f.name()) ++ f.aliases().asScala.toSet
167- if (projection.intersect(names).isEmpty) {
168- // field is not part of the projection. user default value
169- c.put(f.pos(), data.getDefaultValue(f))
153+ .mapWithResource(dataForClass(datumFactory.getType), ResourceType .PER_INSTANCE ) {
154+ case (data, record) =>
155+ projectedFields match {
156+ case None => record
157+ case Some (projection) =>
158+ // beam forbids mutations. Create a new record
159+ val copy = data.deepCopy(record.getSchema, record)
160+ record.getSchema.getFields.asScala
161+ .foldLeft(copy) { (c, f) =>
162+ val names = Set (f.name()) ++ f.aliases().asScala.toSet
163+ if (projection.intersect(names).isEmpty) {
164+ // field is not part of the projection. user default value
165+ c.put(f.pos(), data.getDefaultValue(f))
166+ }
167+ c
170168 }
171- c
172- }
173- }
169+ }
174170 }
175171 }
176172
@@ -235,6 +231,17 @@ sealed trait ParquetAvroIO[T <: IndexedRecord] extends ScioIO[T] {
235231
236232object ParquetAvroIO {
237233
234+ // SpecificData.getForClass is only available for 1.9+
235+ private def dataForClass [T ](recordClass : Class [T ]) = {
236+ if (classOf [SpecificRecordBase ].isAssignableFrom(recordClass)) {
237+ val classModelField = recordClass.getDeclaredField(" MODEL$" )
238+ classModelField.setAccessible(true )
239+ classModelField.get(null ).asInstanceOf [SpecificData ]
240+ } else {
241+ SpecificData .get()
242+ }
243+ }
244+
238245 private class Identity [T ](cls : Class [T ])
239246 extends SimpleFunction [T , T ](SerializableFunctions .identity[T ]) {
240247 override def getInputTypeDescriptor : TypeDescriptor [T ] = TypeDescriptor .of(cls)
0 commit comments