Skip to content

feat(java/core): add fluent ingest API - #4466

Merged
lidavidm merged 4 commits into
apache:mainfrom
lidavidm:java-ingest
Jul 14, 2026
Merged

feat(java/core): add fluent ingest API#4466
lidavidm merged 4 commits into
apache:mainfrom
lidavidm:java-ingest

Conversation

@lidavidm

@lidavidm lidavidm commented Jul 6, 2026

Copy link
Copy Markdown
Member

Add a more natural API for bulk ingest that (by default) hides the statement within, making for a clearer API.

@lidavidm
lidavidm marked this pull request as ready for review July 7, 2026 06:04
@lidavidm
lidavidm requested review from amoeba and zeroshade July 7, 2026 06:04

@amoeba amoeba left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really nice. I left some comments and questions.

import org.apache.arrow.vector.ipc.ArrowReader;

/** A fluent builder-style interface for configuring and executing a bulk ingest. */
public interface BulkIngestBuilder extends AutoCloseable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add javadoc comments to these interface methods, something like,

diff --git a/java/core/src/main/java/org/apache/arrow/adbc/core/BulkIngestBuilder.java b/java/core/src/main/java/org/apache/arrow/adbc/core/BulkIngestBuilder.java
index d8b52f815..6a96f31e9 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/BulkIngestBuilder.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/BulkIngestBuilder.java
@@ -21,38 +21,128 @@ import org.apache.arrow.vector.ipc.ArrowReader;
 
 /** A fluent builder-style interface for configuring and executing a bulk ingest. */
 public interface BulkIngestBuilder extends AutoCloseable {
+  /**
+   * Set the ingest mode to {@link BulkIngestMode#CREATE}: create the table and insert data, error
+   * if the table already exists.
+   */
   BulkIngestBuilder create() throws AdbcException;
 
+  /**
+   * Set the ingest mode to {@link BulkIngestMode#APPEND}: append data to an existing table, error
+   * if the table does not exist or its schema does not match.
+   */
   BulkIngestBuilder append() throws AdbcException;
 
+  /**
+   * Set the ingest mode to {@link BulkIngestMode#REPLACE}: drop the existing table (if any) and
+   * create a new one, then insert data.
+   *
+   * @since ADBC API revision 1.1.0
+   */
   BulkIngestBuilder replace() throws AdbcException;
 
+  /**
+   * Set the ingest mode to {@link BulkIngestMode#CREATE_APPEND}: insert data, creating the table
+   * if it does not exist; error if the table exists but the schema does not match.
+   */
   BulkIngestBuilder createAppend() throws AdbcException;
 
+  /**
+   * Set the ingest mode explicitly.
+   *
+   * <p>Prefer the convenience methods {@link #create()}, {@link #append()}, {@link #replace()}, and
+   * {@link #createAppend()} when possible.
+   *
+   * @param mode the ingest mode to use
+   */
   BulkIngestBuilder mode(BulkIngestMode mode) throws AdbcException;
 
+  /**
+   * Bind a {@link VectorSchemaRoot} as the data source for ingestion.
+   *
+   * <p>The builder will NOT close the root after use.
+   *
+   * @param root the data to ingest
+   */
   BulkIngestBuilder bind(VectorSchemaRoot root) throws AdbcException;
 
+  /**
+   * Bind an {@link ArrowReader} as the data source for ingestion.
+   *
+   * <p>The underlying statement will close the reader after use.
+   *
+   * @param stream the data to ingest
+   */
   BulkIngestBuilder bind(ArrowReader stream) throws AdbcException;
 
+  /**
+   * Set the name of the target table to ingest into.
+   *
+   * @param table the table name (required)
+   */
   BulkIngestBuilder targetTable(String table) throws AdbcException;
 
+  /**
+   * Set the schema that contains the target table.
+   *
+   * @param schema the schema name, or {@code null} to use the connection default
+   */
   BulkIngestBuilder targetSchema(String schema) throws AdbcException;
 
+  /**
+   * Set the catalog that contains the target table.
+   *
+   * @param catalog the catalog name, or {@code null} to use the connection default
+   */
   BulkIngestBuilder targetCatalog(String catalog) throws AdbcException;
 
+  /**
+   * Mark the target table as temporary.
+   *
+   * <p>Equivalent to calling {@link #temporary(boolean) temporary(true)}.
+   */
   default BulkIngestBuilder temporary() throws AdbcException {
     return this.temporary(true);
   }
 
+  /**
+   * Control whether the target table should be created as a temporary table.
+   *
+   * @param isTemporary {@code true} to create a temporary table, {@code false} for a persistent one
+   */
   BulkIngestBuilder temporary(boolean isTemporary) throws AdbcException;
 
+  /**
+   * Set a driver-specific ingest option.
+   *
+   * @param option the option to set
+   */
   BulkIngestBuilder option(IngestOption option) throws AdbcException;
 
+  /**
+   * Set a typed driver-specific ingest option.
+   *
+   * @param key the option key
+   * @param value the option value
+   */
   <T> BulkIngestBuilder option(TypedKey<T> key, T value) throws AdbcException;
 
+  /**
+   * Build and return the configured {@link AdbcStatement} without executing it.
+   *
+   * <p>The caller is responsible for closing the returned statement. Prefer {@link #ingest()} when
+   * you do not need to inspect or reuse the statement.
+   */
   AdbcStatement toStatement() throws AdbcException;
 
+  /**
+   * Execute the ingest and return the number of affected rows.
+   *
+   * <p>This is a convenience method that calls {@link #toStatement()}, executes it, closes it, and
+   * returns the {@link AdbcStatement.UpdateResult}.
+   *
+   * @return the result of the update, including the number of rows affected
+   */
   default AdbcStatement.UpdateResult ingest() throws AdbcException {
     try (var statement = toStatement()) {
       return statement.executeUpdate();

Comment on lines +24 to +30
BulkIngestBuilder create() throws AdbcException;

BulkIngestBuilder append() throws AdbcException;

BulkIngestBuilder replace() throws AdbcException;

BulkIngestBuilder createAppend() throws AdbcException;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These helpers that are specific to each ingest mode are nice but each one is kinda verbish so the user ends up writing,

...
    .create()
    .ingest()

That is, a method like .create() kinda feels like it should take action.

Maybe an alternative would be to have a finalize method on the builder like,

...
    .create()
    .finish()

b.ingest()

Just thinking out loud here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to say createMode etc. instead.


<T> BulkIngestBuilder option(TypedKey<T> key, T value) throws AdbcException;

AdbcStatement toStatement() throws AdbcException;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be the way for users to cancel their ingest after they run .ingest() right? Would be good to document in the javadoc comment that goes here.

@lidavidm
lidavidm merged commit 9be0298 into apache:main Jul 14, 2026
19 checks passed
@lidavidm lidavidm added this to the ADBC Libraries 24 milestone Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants