2626import java .net .URI ;
2727import java .nio .charset .StandardCharsets ;
2828import java .util .ArrayList ;
29+ import java .util .Arrays ;
2930import java .util .Collections ;
3031import java .util .Iterator ;
3132import java .util .List ;
33+ import org .apache .arrow .flight .AsyncPutListener ;
34+ import org .apache .arrow .flight .CallOption ;
3235import org .apache .arrow .flight .FlightCallHeaders ;
3336import org .apache .arrow .flight .FlightClient ;
3437import org .apache .arrow .flight .FlightDescriptor ;
3740import org .apache .arrow .flight .FlightStream ;
3841import org .apache .arrow .flight .HeaderCallOption ;
3942import org .apache .arrow .flight .Location ;
40- import org .apache .arrow .flight .PutResult ;
4143import org .apache .arrow .flight .Ticket ;
4244import org .apache .arrow .memory .BufferAllocator ;
4345import org .apache .arrow .memory .RootAllocator ;
@@ -120,6 +122,42 @@ public class ArrowFlightIO {
120122
121123 private ArrowFlightIO () {}
122124
125+ private static byte [] copyToken (byte [] token ) {
126+ return Arrays .copyOf (checkNotNull (token , "token" ), token .length );
127+ }
128+
129+ private static CallOption [] callOptions (byte @ Nullable [] token ) {
130+ if (token == null ) {
131+ return new CallOption [0 ];
132+ }
133+ FlightCallHeaders headers = new FlightCallHeaders ();
134+ headers .insert ("authorization" , "Bearer " + new String (token , StandardCharsets .UTF_8 ));
135+ return new CallOption [] {new HeaderCallOption (headers )};
136+ }
137+
138+ private static void validateWriteSchema (Schema schema ) {
139+ for (Schema .Field field : schema .getFields ()) {
140+ switch (field .getType ().getTypeName ()) {
141+ case BYTE :
142+ case INT16 :
143+ case INT32 :
144+ case INT64 :
145+ case FLOAT :
146+ case DOUBLE :
147+ case STRING :
148+ case BOOLEAN :
149+ case BYTES :
150+ case DATETIME :
151+ break ;
152+ default :
153+ throw new IllegalArgumentException (
154+ String .format (
155+ "ArrowFlightIO.write() does not support Beam type '%s' for field '%s'." ,
156+ field .getType ().getTypeName (), field .getName ()));
157+ }
158+ }
159+ }
160+
123161 public static Read read () {
124162 return new AutoValue_ArrowFlightIO_Read .Builder ().setPort (47470 ).setUseTls (false ).build ();
125163 }
@@ -242,7 +280,7 @@ public Read withCommand(String command) {
242280
243281 /** Sets a bearer token for authentication. */
244282 public Read withToken (byte [] token ) {
245- return builder ().setToken (token ).build ();
283+ return builder ().setToken (copyToken ( token ) ).build ();
246284 }
247285
248286 @ Override
@@ -270,15 +308,8 @@ public PCollection<Row> expand(PBegin input) {
270308 .setRowSchema (beamSchema );
271309 }
272310
273- HeaderCallOption [] callOptions () {
274- if (token () != null ) {
275- FlightCallHeaders headers = new FlightCallHeaders ();
276- headers .insert (
277- "authorization" ,
278- "Bearer " + new String (checkNotNull (token (), "token" ), StandardCharsets .UTF_8 ));
279- return new HeaderCallOption [] {new HeaderCallOption (headers )};
280- }
281- return new HeaderCallOption [0 ];
311+ CallOption [] callOptions () {
312+ return ArrowFlightIO .callOptions (token ());
282313 }
283314
284315 @ Override
@@ -547,15 +578,21 @@ public Write withBatchSize(int batchSize) {
547578
548579 /** Sets a bearer token for authentication. */
549580 public Write withToken (byte [] token ) {
550- return builder ().setToken (token ).build ();
581+ return builder ().setToken (copyToken (token )).build ();
582+ }
583+
584+ CallOption [] callOptions () {
585+ return ArrowFlightIO .callOptions (token ());
551586 }
552587
553588 @ Override
554589 public PDone expand (PCollection <Row > input ) {
555590 checkArgument (host () != null , "withHost() is required" );
556591 checkArgument (descriptor () != null , "withDescriptor() is required" );
592+ Schema inputSchema = checkNotNull (input .getSchema (), "input schema" );
593+ validateWriteSchema (inputSchema );
557594
558- input .apply (ParDo .of (new FlightWriteFn (this )));
595+ input .apply (ParDo .of (new FlightWriteFn (this , inputSchema )));
559596 return PDone .in (input .getPipeline ());
560597 }
561598
@@ -579,16 +616,17 @@ static class FlightWriteFn extends DoFn<Row, Void> {
579616 Metrics .counter (ArrowFlightIO .class , "batchesWritten" );
580617
581618 private final Write spec ;
582- private transient BufferAllocator allocator ;
583- private transient FlightClient client ;
584- private transient FlightClient .ClientStreamListener listener ;
585- private transient VectorSchemaRoot root ;
619+ private final Schema beamSchema ;
620+ private transient @ Nullable BufferAllocator allocator ;
621+ private transient @ Nullable FlightClient client ;
622+ private transient FlightClient .@ Nullable ClientStreamListener listener ;
623+ private transient @ Nullable VectorSchemaRoot root ;
586624 private transient org .apache .arrow .vector .types .pojo .Schema arrowSchema ;
587625 private transient List <Row > batch ;
588- private transient @ Nullable Schema beamSchema ;
589626
590- FlightWriteFn (Write spec ) {
627+ FlightWriteFn (Write spec , Schema beamSchema ) {
591628 this .spec = spec ;
629+ this .beamSchema = beamSchema ;
592630 }
593631
594632 @ StartBundle
@@ -598,9 +636,9 @@ public void startBundle() {
598636
599637 @ ProcessElement
600638 public void processElement (@ Element Row row ) {
601- if ( beamSchema == null ) {
602- beamSchema = row .getSchema ();
603- }
639+ checkArgument (
640+ row .getSchema (). equivalent ( beamSchema ),
641+ "ArrowFlightIO.write() requires all rows to use the same schema." );
604642 batch .add (row );
605643 if (batch .size () >= spec .batchSize ()) {
606644 flush ();
@@ -609,30 +647,59 @@ public void processElement(@Element Row row) {
609647
610648 @ FinishBundle
611649 public void finishBundle () {
612- flush ();
613- closeConnection ();
650+ RuntimeException failure = null ;
651+ try {
652+ flush ();
653+ } catch (RuntimeException e ) {
654+ failure = e ;
655+ }
656+
657+ try {
658+ closeConnection ();
659+ } catch (RuntimeException e ) {
660+ if (failure == null ) {
661+ failure = e ;
662+ } else {
663+ failure .addSuppressed (e );
664+ }
665+ }
666+
667+ if (failure != null ) {
668+ throw failure ;
669+ }
614670 }
615671
616672 @ Teardown
617673 public void teardown () {
618- closeConnection ();
674+ try {
675+ closeConnection ();
676+ } catch (RuntimeException e ) {
677+ LOG .warn ("Error closing Flight write connection during teardown" , e );
678+ }
619679 }
620680
621- @ SuppressWarnings ("nullness" )
622681 private void ensureConnection () {
623682 if (client == null ) {
624- allocator = new RootAllocator (Long .MAX_VALUE );
625- client = createClient (allocator , spec .host (), spec .port (), spec .useTls ());
683+ BufferAllocator currentAllocator = new RootAllocator (Long .MAX_VALUE );
684+ allocator = currentAllocator ;
685+ FlightClient currentClient =
686+ createClient (
687+ currentAllocator , checkNotNull (spec .host (), "host" ), spec .port (), spec .useTls ());
688+ client = currentClient ;
626689
627690 List <Field > arrowFields = new ArrayList <>();
628691 for (Schema .Field beamField : beamSchema .getFields ()) {
629692 arrowFields .add (toArrowField (beamField ));
630693 }
631694 arrowSchema = new org .apache .arrow .vector .types .pojo .Schema (arrowFields );
632- root = VectorSchemaRoot .create (arrowSchema , allocator );
633-
634- FlightDescriptor descriptor = FlightDescriptor .path (spec .descriptor ());
635- listener = client .startPut (descriptor , root , new AsyncPutListener ());
695+ VectorSchemaRoot currentRoot = VectorSchemaRoot .create (arrowSchema , currentAllocator );
696+ root = currentRoot ;
697+
698+ FlightDescriptor descriptor =
699+ FlightDescriptor .path (checkNotNull (spec .descriptor (), "descriptor" ));
700+ listener =
701+ currentClient .startPut (
702+ descriptor , currentRoot , new AsyncPutListener (), spec .callOptions ());
636703 }
637704 }
638705
@@ -668,8 +735,8 @@ private ArrowType beamTypeToArrowType(Schema.FieldType beamType) {
668735 case DATETIME :
669736 return new ArrowType .Timestamp (TimeUnit .MILLISECOND , "UTC" );
670737 default :
671- LOG . warn ( "Unsupported Beam type {}, falling back to Utf8" , beamType . getTypeName ());
672- return ArrowType . Utf8 . INSTANCE ;
738+ throw new IllegalArgumentException (
739+ "Unsupported Beam type for ArrowFlightIO.write(): " + beamType . getTypeName ()) ;
673740 }
674741 }
675742
@@ -745,76 +812,70 @@ private void setVectorValue(
745812 ((TimeStampMilliTZVector ) vector ).setSafe (index , millis );
746813 break ;
747814 default :
748- ((VarCharVector ) vector )
749- .setSafe (index , value .toString ().getBytes (StandardCharsets .UTF_8 ));
750- break ;
815+ throw new IllegalArgumentException (
816+ "Unsupported Beam type for ArrowFlightIO.write(): " + type .getTypeName ());
751817 }
752818 }
753819
754- @ SuppressWarnings ("nullness" )
755820 private void closeConnection () {
821+ RuntimeException failure = null ;
822+ FlightClient .ClientStreamListener currentListener = listener ;
823+ listener = null ;
756824 try {
757- if (listener != null ) {
758- listener .completed ();
759- listener .getResult ();
760- listener = null ;
825+ if (currentListener != null ) {
826+ currentListener .completed ();
827+ currentListener .getResult ();
761828 }
762- } catch (Exception e ) {
763- LOG . warn ( "Error completing Flight put" , e ) ;
829+ } catch (RuntimeException e ) {
830+ failure = e ;
764831 }
832+
833+ VectorSchemaRoot currentRoot = root ;
834+ root = null ;
765835 try {
766- if (root != null ) {
767- root .close ();
768- root = null ;
836+ if (currentRoot != null ) {
837+ currentRoot .close ();
769838 }
770839 } catch (Exception e ) {
771- LOG .warn ("Error closing VectorSchemaRoot" , e );
840+ if (failure == null ) {
841+ failure = new RuntimeException ("Error closing VectorSchemaRoot" , e );
842+ } else {
843+ failure .addSuppressed (e );
844+ }
772845 }
846+
847+ FlightClient currentClient = client ;
848+ client = null ;
773849 try {
774- if (client != null ) {
775- client .close ();
776- client = null ;
850+ if (currentClient != null ) {
851+ currentClient .close ();
777852 }
778853 } catch (InterruptedException e ) {
779854 Thread .currentThread ().interrupt ();
780- LOG .warn ("Interrupted closing FlightClient" , e );
855+ RuntimeException closeFailure = new RuntimeException ("Interrupted closing FlightClient" , e );
856+ if (failure == null ) {
857+ failure = closeFailure ;
858+ } else {
859+ failure .addSuppressed (closeFailure );
860+ }
781861 }
862+
863+ BufferAllocator currentAllocator = allocator ;
864+ allocator = null ;
782865 try {
783- if (allocator != null ) {
784- allocator .close ();
785- allocator = null ;
866+ if (currentAllocator != null ) {
867+ currentAllocator .close ();
786868 }
787869 } catch (Exception e ) {
788- LOG .warn ("Error closing BufferAllocator" , e );
870+ if (failure == null ) {
871+ failure = new RuntimeException ("Error closing BufferAllocator" , e );
872+ } else {
873+ failure .addSuppressed (e );
874+ }
789875 }
790- }
791- }
792-
793- /** A no-op listener for async put operations. */
794- static class AsyncPutListener implements FlightClient .PutListener {
795- private volatile @ Nullable Throwable error ;
796-
797- @ Override
798- public void onNext (PutResult val ) {}
799876
800- @ Override
801- public void onError (Throwable t ) {
802- this .error = t ;
803- }
804-
805- @ Override
806- public void onCompleted () {}
807-
808- @ Override
809- public boolean isCancelled () {
810- return false ;
811- }
812-
813- @ Override
814- public void getResult () {
815- Throwable t = error ;
816- if (t != null ) {
817- throw new RuntimeException ("Error during Flight put" , t );
877+ if (failure != null ) {
878+ throw failure ;
818879 }
819880 }
820881 }
0 commit comments