결과 처리

결과 처리 (Handle Results, Java/JDBC)

개요 (Overview)

표준 JDBC ResultSet 외에도, DuckDB 드라이버는 결과를 Apache Arrow로 넘기고, 큰 결과를 물리화하지 않고 스트리밍하며, 원시 열 지향 데이터 청크를 노출할 수 있어요. 이 페이지는 각 결과 처리 옵션을 다룬다.

출처: 문서

본문

Arrow 메서드

DuckDB 결과 집합은 Java Arrow 바인딩을 통해 Apache Arrow와 통합돼요. DuckDBResultSet은 그 행을 Arrow 기반 도구가 소비할 수 있는 Arrow 스트림으로 내보낼 수 있고, 다른 곳에서 만든 Arrow 스트림은 연결에 등록해 DuckDB 테이블로 쿼리할 수 있어요. 타입 시그니처는 API Reference를 참고해요.

Arrow 내보내기

다음 예시는 쿼리 결과에서 Arrow 스트림을 내보내고 Java Arrow 바인딩으로 소비해요. 각 배치가 차례로 로드되고, 그 벡터가 VectorSchemaRoot에서 읽혀요.

import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.duckdb.DuckDBResultSet;

try (var conn = DriverManager.getConnection("jdbc:duckdb:");
    var stmt = conn.prepareStatement("SELECT * FROM generate_series(2000)");
    var resultset = (DuckDBResultSet) stmt.executeQuery();
    var allocator = new RootAllocator()) {
    try (var reader = (ArrowReader) resultset.arrowExportStream(allocator, 256)) {
        while (reader.loadNextBatch()) {
            System.out.println(reader.getVectorSchemaRoot().getVector("generate_series"));
        }
    }
}

Arrow 가져오기

다음 예시는 Java Arrow 바인딩이 만든 Arrow 스트림을 소비하고 registerArrowStream()으로 DuckDB 연결에 등록해요. 등록되면 그 메서드에 전달된 이름으로 스트림이 주소가 지정되고 다른 테이블처럼 쿼리할 수 있어요.

import org.apache.arrow.c.ArrowArrayStream;
import org.apache.arrow.c.Data;
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.ipc.ArrowStreamReader;
import org.duckdb.DuckDBConnection;
import org.duckdb.DuckDBResultSet;

// Arrow binding
try (var allocator = new RootAllocator();
     ArrowStreamReader reader = null; // 물론 null이면 안 됨
     var arrow_array_stream = ArrowArrayStream.allocateNew(allocator)) {
    Data.exportArrayStream(allocator, reader, arrow_array_stream);

    // DuckDB setup
    try (var conn = (DuckDBConnection) DriverManager.getConnection("jdbc:duckdb:")) {
        conn.registerArrowStream("asdf", arrow_array_stream);

        // 쿼리 실행
        try (var stmt = conn.createStatement();
             var rs = (DuckDBResultSet) stmt.executeQuery("SELECT count(*) FROM asdf")) {
            while (rs.next()) {
                System.out.println(rs.getInt(1));
            }
        }
    }
}

결과 스트리밍 (Streaming Results)

결과 스트리밍은 JDBC 드라이버에서 옵트인이에요. 쿼리를 실행하기 전에 jdbc_stream_results 설정을 true로 설정해 활성화해요. 가장 쉬운 방법은 Properties 객체에 전달하는 것이에요.

Properties props = new Properties();
props.setProperty(DuckDBDriver.JDBC_STREAM_RESULTS, String.valueOf(true));

Connection conn = DriverManager.getConnection("jdbc:duckdb:", props);

청크 결과 (Chunked Results)

JDBC 드라이버는 org.duckdb.DuckDBChunkedResult 클래스를 통해 쿼리 결과를 지연 fetch되는 열 지향 데이터 청크 시퀀스로 반환할 수 있어요. 이것은 C API의 duckdb_fetch_chunk 함수를 Java에 노출하고, JDBC ResultSet 인터페이스가 필요로 하는 행별 오버헤드를 피해요. 청크 내용은 [user-defined functions]({% link docs/current/clients/java/functions.md %})에서 사용하는 것과 같은 DuckDBDataChunkReader API로 읽어요.

DuckDBPreparedStatement에서 query()를 호출해 DuckDBChunkedResult를 얻은 다음, nextChunk()로 청크를 진행하고 chunk()로 현재 청크를 얻은 후, 각 컬럼 벡터를 인덱스로 읽어요.

예시:

import java.sql.DriverManager;
import org.duckdb.DuckDBConnection;
import org.duckdb.DuckDBPreparedStatement;
import org.duckdb.DuckDBChunkedResult;
import org.duckdb.DuckDBDataChunkReader;
import org.duckdb.DuckDBReadableVector;

try (DuckDBConnection conn = DriverManager
        .getConnection("jdbc:duckdb:")
        .unwrap(DuckDBConnection.class);
     DuckDBPreparedStatement ps = conn.prepare("SELECT ? AS col1")) {

    // statement 파라미터는 1-based
    ps.setInt(1, 42);

    try (DuckDBChunkedResult res = ps.query()) {

        // 다음 청크로 진행, 성공 시 true 반환
        while (res.nextChunk()) {

            // 결과에서 현재 청크 얻기
            DuckDBDataChunkReader chunk = res.chunk();

            // 청크 컬럼 순회, 모든 인덱스 0-based
            for (long col = 0; col < chunk.columnCount(); col++) {

                // 지정된 컬럼의 벡터 얻기
                DuckDBReadableVector vector = chunk.vector(col);

                // 벡터 행 순회
                for (long row = 0; row < chunk.rowCount(); row++) {
                    int val = vector.getInt(row);
                    System.out.println(val);
                }
            }
        }
    }
}

Statement 파라미터는 JDBC 규칙에 따라 1-based로 유지되고, 청크 컬럼과 행은 C API와 user-defined function 인터페이스에 맞춰 0-based예요.

청크 결과 API는 현재 기본 데이터 타입만 지원해요. LISTSTRUCT 같은 복합 타입은 아직 이 인터페이스로 읽을 수 없어요. query() 메서드는 prepared statements에서만 사용 가능하며 query(String) 오버로드는 없어요.

더 알아보기 (Learn more)

  • [Run Queries]({% link docs/current/clients/java/querying.md %}) — 이 페이지가 읽는 결과를 내는 쿼리 전송 및 표준 ResultSet 접근자.
  • [Define Functions]({% link docs/current/clients/java/functions.md %}) — 청크 결과와 공유하는 DuckDBDataChunkReader API는 user-defined functions 작성에도 사용돼요.
  • [Define Connections]({% link docs/current/clients/java/connecting.md %}) — 결과 스트리밍을 활성화하는 jdbc_stream_results 옵션.
  • [C API]({% link docs/current/clients/c/api.md %}) — 청크 결과 API가 Java에 노출하는 duckdb_fetch_chunk 함수.