Skip to content

Commit 447a459

Browse files
committed
[Java IO] Address ArrowFlightIO review feedback
1 parent add980a commit 447a459

9 files changed

Lines changed: 173 additions & 101 deletions

File tree

CHANGES.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@
185185

186186
## I/Os
187187

188-
189188
* DebeziumIO (Java): added `OffsetRetainer` interface and `FileSystemOffsetRetainer` implementation to persist and restore CDC offsets across pipeline restarts, and exposed `withStartOffset` / `withOffsetRetainer` on `DebeziumIO.Read` and the cross-language `ReadBuilder` ([#28248](https://github.com/apache/beam/issues/28248)).
190189

191190
## New Features / Improvements

buildSrc/src/main/groovy/org/apache/beam/gradle/BeamModulePlugin.groovy

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,6 @@ class BeamModulePlugin implements Plugin<Project> {
939939
arrow_memory_core : "org.apache.arrow:arrow-memory-core:$arrow_version",
940940
arrow_memory_netty : "org.apache.arrow:arrow-memory-netty:$arrow_version",
941941
arrow_flight_core : "org.apache.arrow:flight-core:$arrow_version",
942-
arrow_flight_sql : "org.apache.arrow:flight-sql:$arrow_version",
943942
],
944943
groovy: [
945944
groovy_all: "org.codehaus.groovy:groovy-all:2.4.13",

sdks/java/extensions/arrow/src/main/java/org/apache/beam/sdk/extensions/arrow/ArrowConversion.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.io.InputStream;
2424
import java.nio.channels.Channels;
25+
import java.util.Collections;
2526
import java.util.Iterator;
2627
import java.util.List;
2728
import java.util.Optional;
@@ -35,6 +36,7 @@
3536
import org.apache.arrow.vector.ipc.ReadChannel;
3637
import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
3738
import org.apache.arrow.vector.ipc.message.MessageSerializer;
39+
import org.apache.arrow.vector.types.FloatingPointPrecision;
3840
import org.apache.arrow.vector.types.TimeUnit;
3941
import org.apache.arrow.vector.types.pojo.ArrowType;
4042
import org.apache.arrow.vector.util.Text;
@@ -57,6 +59,57 @@
5759
*/
5860
public class ArrowConversion {
5961

62+
/** Get Arrow Field from Beam Field. */
63+
private static org.apache.arrow.vector.types.pojo.Field toArrowField(Field field) {
64+
FieldType beamFieldType = field.getType();
65+
ArrowType arrowType;
66+
// TODO: Support aggregate and logical Beam field types.
67+
switch (beamFieldType.getTypeName()) {
68+
case BYTE:
69+
arrowType = new ArrowType.Int(8, true);
70+
break;
71+
case INT16:
72+
arrowType = new ArrowType.Int(16, true);
73+
break;
74+
case INT32:
75+
arrowType = new ArrowType.Int(32, true);
76+
break;
77+
case INT64:
78+
arrowType = new ArrowType.Int(64, true);
79+
break;
80+
case FLOAT:
81+
arrowType = new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE);
82+
break;
83+
case DOUBLE:
84+
arrowType = new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE);
85+
break;
86+
case STRING:
87+
arrowType = ArrowType.Utf8.INSTANCE;
88+
break;
89+
case BOOLEAN:
90+
arrowType = ArrowType.Bool.INSTANCE;
91+
break;
92+
case BYTES:
93+
arrowType = ArrowType.Binary.INSTANCE;
94+
break;
95+
case DATETIME:
96+
arrowType = new ArrowType.Timestamp(TimeUnit.MILLISECOND, "UTC");
97+
break;
98+
default:
99+
throw new IllegalArgumentException(
100+
String.format(
101+
"Arrow schema conversion does not support Beam type '%s' for field '%s'.",
102+
beamFieldType.getTypeName(), field.getName()));
103+
}
104+
105+
org.apache.arrow.vector.types.pojo.FieldType arrowFieldType =
106+
beamFieldType.getNullable()
107+
? org.apache.arrow.vector.types.pojo.FieldType.nullable(arrowType)
108+
: org.apache.arrow.vector.types.pojo.FieldType.notNullable(arrowType);
109+
return new org.apache.arrow.vector.types.pojo.Field(
110+
field.getName(), arrowFieldType, Collections.emptyList());
111+
}
112+
60113
/** Get Beam Field from Arrow Field. */
61114
private static Field toBeamField(org.apache.arrow.vector.types.pojo.Field field) {
62115
FieldType beamFieldType = toFieldType(field.getFieldType(), field.getChildren());
@@ -546,6 +599,14 @@ private ArrowConversion() {}
546599
/** Converts Arrow schema to Beam row schema. */
547600
public static class ArrowSchemaTranslator {
548601

602+
/** Converts a supported Beam row schema to an Arrow schema. */
603+
public static org.apache.arrow.vector.types.pojo.Schema toArrowSchema(Schema schema) {
604+
return new org.apache.arrow.vector.types.pojo.Schema(
605+
schema.getFields().stream()
606+
.map(ArrowConversion::toArrowField)
607+
.collect(Collectors.toList()));
608+
}
609+
549610
public static Schema toBeamSchema(org.apache.arrow.vector.types.pojo.Schema schema) {
550611
return toBeamSchema(schema.getFields());
551612
}

sdks/java/extensions/arrow/src/test/java/org/apache/beam/sdk/extensions/arrow/ArrowConversionTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,39 @@ public void toBeamSchema_convertsSimpleArrowSchema() {
8383
assertThat(ArrowConversion.ArrowSchemaTranslator.toBeamSchema(arrowSchema), equalTo(expected));
8484
}
8585

86+
@Test
87+
public void toArrowSchema_convertsSimpleBeamSchema() {
88+
Schema beamSchema =
89+
Schema.builder()
90+
.addByteField("int8")
91+
.addInt16Field("int16")
92+
.addInt32Field("int32")
93+
.addInt64Field("int64")
94+
.addFloatField("float32")
95+
.addDoubleField("float64")
96+
.addNullableField("string", FieldType.STRING)
97+
.addBooleanField("boolean")
98+
.addByteArrayField("bytes")
99+
.addDateTimeField("timestamp")
100+
.build();
101+
102+
org.apache.arrow.vector.types.pojo.Schema expected =
103+
new org.apache.arrow.vector.types.pojo.Schema(
104+
ImmutableList.of(
105+
field("int8", new ArrowType.Int(8, true)),
106+
field("int16", new ArrowType.Int(16, true)),
107+
field("int32", new ArrowType.Int(32, true)),
108+
field("int64", new ArrowType.Int(64, true)),
109+
field("float32", new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE)),
110+
field("float64", new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE)),
111+
field("string", true, ArrowType.Utf8.INSTANCE),
112+
field("boolean", ArrowType.Bool.INSTANCE),
113+
field("bytes", ArrowType.Binary.INSTANCE),
114+
field("timestamp", new ArrowType.Timestamp(TimeUnit.MILLISECOND, "UTC"))));
115+
116+
assertThat(ArrowConversion.ArrowSchemaTranslator.toArrowSchema(beamSchema), equalTo(expected));
117+
}
118+
86119
@Test
87120
public void rowIterator() {
88121
org.apache.arrow.vector.types.pojo.Schema schema =

sdks/java/io/arrow-flight/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ dependencies {
3939
}
4040

4141
test {
42+
// Keep aligned with ArrowFlightIO runtime guidance for Java 17+.
4243
jvmArgs '--add-opens=java.base/java.nio=ALL-UNNAMED'
4344
}

sdks/java/io/arrow-flight/src/main/java/org/apache/beam/sdk/io/arrowflight/ArrowFlightIO.java

Lines changed: 46 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,6 @@
5555
import org.apache.arrow.vector.VarBinaryVector;
5656
import org.apache.arrow.vector.VarCharVector;
5757
import org.apache.arrow.vector.VectorSchemaRoot;
58-
import org.apache.arrow.vector.types.FloatingPointPrecision;
59-
import org.apache.arrow.vector.types.TimeUnit;
60-
import org.apache.arrow.vector.types.pojo.ArrowType;
61-
import org.apache.arrow.vector.types.pojo.Field;
62-
import org.apache.arrow.vector.types.pojo.FieldType;
6358
import org.apache.beam.sdk.coders.Coder;
6459
import org.apache.beam.sdk.coders.RowCoder;
6560
import org.apache.beam.sdk.extensions.arrow.ArrowConversion;
@@ -84,9 +79,7 @@
8479
* IO to read and write data using <a href="https://arrow.apache.org/docs/format/Flight.html">Apache
8580
* Arrow Flight</a>.
8681
*
87-
* <p>Arrow Flight is a high-performance RPC framework for transferring Arrow-formatted data over
88-
* gRPC. It enables 10-50x faster data transfer compared to JDBC/ODBC by avoiding
89-
* serialization/deserialization overhead.
82+
* <p>Arrow Flight is an RPC framework for transferring Arrow-formatted data over gRPC.
9083
*
9184
* <h3>Reading from an Arrow Flight server</h3>
9285
*
@@ -115,6 +108,14 @@
115108
* .withDescriptor("my_table")
116109
* .withBatchSize(1024));
117110
* }</pre>
111+
*
112+
* <h3>Java runtime configuration</h3>
113+
*
114+
* <p>On Java 17 or later, Arrow memory access requires {@code
115+
* --add-opens=java.base/java.nio=ALL-UNNAMED} on JVMs executing this connector. Beam SDK containers
116+
* enable it by default. For other environments, add it to the local or runner JVM and pass {@code
117+
* --JdkAddOpenModules=java.base/java.nio=ALL-UNNAMED} to configure SDK harness JVMs. See the <a
118+
* href="https://arrow.apache.org/docs/java/install.html">Arrow Java installation guide</a>.
118119
*/
119120
public class ArrowFlightIO {
120121

@@ -136,25 +137,10 @@ private static CallOption[] callOptions(byte @Nullable [] token) {
136137
}
137138

138139
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-
}
140+
try {
141+
ArrowConversion.ArrowSchemaTranslator.toArrowSchema(schema);
142+
} catch (IllegalArgumentException e) {
143+
throw new IllegalArgumentException("ArrowFlightIO.write(): " + e.getMessage(), e);
158144
}
159145
}
160146

@@ -415,36 +401,48 @@ static class FlightBoundedReader extends BoundedSource.BoundedReader<Row> {
415401
private transient FlightStream stream;
416402
private transient Iterator<Row> currentBatchIterator;
417403
private transient Row current;
404+
private FlightBoundedSource currentSource;
418405

419406
FlightBoundedReader(FlightBoundedSource source) {
420407
this.source = source;
408+
this.currentSource = source;
421409
}
422410

423411
@Override
424412
public boolean start() throws IOException {
425413
allocator = new RootAllocator(Long.MAX_VALUE);
426414
Read spec = source.spec;
427415

428-
String hostName = checkNotNull(spec.host(), "host");
429-
if (source.endpoint != null) {
430-
String host = source.endpoint.getHost(hostName);
431-
int port = source.endpoint.getPort(spec.port());
432-
client = createClient(allocator, host, port, spec.useTls());
433-
stream = client.getStream(source.endpoint.getTicket(), spec.callOptions());
434-
} else {
435-
client = createClient(allocator, hostName, spec.port(), spec.useTls());
436-
FlightInfo info =
437-
client.getInfo(
438-
FlightDescriptor.command(
439-
checkNotNull(spec.command(), "command").getBytes(StandardCharsets.UTF_8)),
440-
spec.callOptions());
441-
List<FlightEndpoint> endpoints = info.getEndpoints();
442-
if (endpoints.isEmpty()) {
443-
return false;
416+
String defaultHost = checkNotNull(spec.host(), "host");
417+
SerializableEndpoint endpoint = source.endpoint;
418+
if (endpoint == null) {
419+
try (FlightClient discoveryClient =
420+
createClient(allocator, defaultHost, spec.port(), spec.useTls())) {
421+
FlightInfo info =
422+
discoveryClient.getInfo(
423+
FlightDescriptor.command(
424+
checkNotNull(spec.command(), "command").getBytes(StandardCharsets.UTF_8)),
425+
spec.callOptions());
426+
List<FlightEndpoint> endpoints = info.getEndpoints();
427+
if (endpoints.isEmpty()) {
428+
return false;
429+
}
430+
endpoint =
431+
SerializableEndpoint.fromFlightEndpoint(endpoints.get(0), defaultHost, spec.port());
432+
} catch (InterruptedException e) {
433+
Thread.currentThread().interrupt();
434+
throw new IOException("Interrupted while discovering Flight endpoints", e);
444435
}
445-
stream = client.getStream(endpoints.get(0).getTicket(), spec.callOptions());
436+
currentSource = new FlightBoundedSource(spec, source.beamSchema, endpoint);
446437
}
447438

439+
client =
440+
createClient(
441+
allocator,
442+
endpoint.getHost(defaultHost),
443+
endpoint.getPort(spec.port()),
444+
spec.useTls());
445+
stream = client.getStream(endpoint.getTicket(), spec.callOptions());
448446
currentBatchIterator = Collections.emptyIterator();
449447
return advance();
450448
}
@@ -509,12 +507,13 @@ public void close() throws IOException {
509507

510508
@Override
511509
public BoundedSource<Row> getCurrentSource() {
512-
return source;
510+
return currentSource;
513511
}
514512
}
515513

516514
// ======================== WRITE ========================
517515

516+
// TODO: Return a write result with failed rows for dead-letter handling instead of PDone.
518517
@AutoValue
519518
public abstract static class Write extends PTransform<PCollection<Row>, PDone> {
520519

@@ -621,7 +620,6 @@ static class FlightWriteFn extends DoFn<Row, Void> {
621620
private transient @Nullable FlightClient client;
622621
private transient FlightClient.@Nullable ClientStreamListener listener;
623622
private transient @Nullable VectorSchemaRoot root;
624-
private transient org.apache.arrow.vector.types.pojo.Schema arrowSchema;
625623
private transient List<Row> batch;
626624

627625
FlightWriteFn(Write spec, Schema beamSchema) {
@@ -687,11 +685,8 @@ private void ensureConnection() {
687685
currentAllocator, checkNotNull(spec.host(), "host"), spec.port(), spec.useTls());
688686
client = currentClient;
689687

690-
List<Field> arrowFields = new ArrayList<>();
691-
for (Schema.Field beamField : beamSchema.getFields()) {
692-
arrowFields.add(toArrowField(beamField));
693-
}
694-
arrowSchema = new org.apache.arrow.vector.types.pojo.Schema(arrowFields);
688+
org.apache.arrow.vector.types.pojo.Schema arrowSchema =
689+
ArrowConversion.ArrowSchemaTranslator.toArrowSchema(beamSchema);
695690
VectorSchemaRoot currentRoot = VectorSchemaRoot.create(arrowSchema, currentAllocator);
696691
root = currentRoot;
697692

@@ -703,43 +698,6 @@ private void ensureConnection() {
703698
}
704699
}
705700

706-
private Field toArrowField(Schema.Field beamField) {
707-
ArrowType arrowType = beamTypeToArrowType(beamField.getType());
708-
FieldType fieldType =
709-
beamField.getType().getNullable()
710-
? FieldType.nullable(arrowType)
711-
: FieldType.notNullable(arrowType);
712-
return new Field(beamField.getName(), fieldType, Collections.emptyList());
713-
}
714-
715-
private ArrowType beamTypeToArrowType(Schema.FieldType beamType) {
716-
switch (beamType.getTypeName()) {
717-
case BYTE:
718-
return new ArrowType.Int(8, true);
719-
case INT16:
720-
return new ArrowType.Int(16, true);
721-
case INT32:
722-
return new ArrowType.Int(32, true);
723-
case INT64:
724-
return new ArrowType.Int(64, true);
725-
case FLOAT:
726-
return new ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE);
727-
case DOUBLE:
728-
return new ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE);
729-
case STRING:
730-
return ArrowType.Utf8.INSTANCE;
731-
case BOOLEAN:
732-
return ArrowType.Bool.INSTANCE;
733-
case BYTES:
734-
return ArrowType.Binary.INSTANCE;
735-
case DATETIME:
736-
return new ArrowType.Timestamp(TimeUnit.MILLISECOND, "UTC");
737-
default:
738-
throw new IllegalArgumentException(
739-
"Unsupported Beam type for ArrowFlightIO.write(): " + beamType.getTypeName());
740-
}
741-
}
742-
743701
@SuppressWarnings("nullness")
744702
private void flush() {
745703
if (batch == null || batch.isEmpty()) {

sdks/java/io/arrow-flight/src/main/java/org/apache/beam/sdk/io/arrowflight/package-info.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
*
2323
* <p>Arrow Flight is a high-performance RPC framework for fast data transport using the Apache
2424
* Arrow columnar format over gRPC. This connector enables Beam pipelines to read from and write to
25-
* any Arrow Flight-compatible data system, including Dremio, ClickHouse, Apache Doris, InfluxDB 3,
26-
* DataFusion, and custom Flight servers.
25+
* Arrow Flight-compatible data systems.
2726
*
2827
* @see org.apache.beam.sdk.io.arrowflight.ArrowFlightIO
2928
*/

0 commit comments

Comments
 (0)