데이터 가져오기

데이터 가져오기 (Import Data)

DuckDB-Wasm에는 데이터 포맷에 따라 여러 방식으로 데이터를 가져올 수 있는 방법이 있어요. 가져오기는 두 단계로 이루어져요.

  1. 데이터 파일을 register 함수들(registerFileBuffer, registerFileHandle, registerFileText, registerFileURL) 중 하나로 가상 파일시스템에 등록해요. 등록된 파일은 모든 커넥션에 보이므로 데이터베이스 객체(db)에서 호출돼요.
  2. 데이터를 insert 함수들(insertArrowFromIPCStream, insertArrowTable, insertCSVFromPath, insertJSONFromPath) 중 하나로 DuckDB에 로드해요. 이 함수들은 커넥션(conn)에서 호출돼요. 또는 Parquet, JSON, Wasm-flavored httpfs 확장이 등록된(또는 원격) 파일을 FROM SQL 쿼리에서 직접 읽을 수 있고, [INSERT 문]({% link docs/current/data/insert.md %})으로 값을 인라인 로드할 수 있어요.

아래 예시들은 모두 [DuckDB-Wasm을 인스턴스화할 때]({% link docs/current/clients/wasm/instantiation.md %}) 만든 db 객체에서 연 conn 커넥션을 사용해요.

출처: 문서

본문

커넥션 열고 닫기

모든 삽입과 쿼리는 커넥션에서 실행돼요. connect()로 열고, 끝나면 메모리를 해제하기 위해 닫아요.

// Create a new connection
const conn = await db.connect();

// ... import data

// Close the connection to release memory
await conn.close();

Apache Arrow

Apache Arrow는 DuckDB-Wasm의 네이티브 데이터 프로토콜이므로 Arrow 데이터를 직접 삽입할 수 있어요. insertArrowTable()에 기존 arrow.Table을 넘겨요.

Warning DuckDB-Wasm이 의존하는 apache-arrow 패키지(현재 ^17)와 같은 메이저 버전을 사용하세요. 더 새로운 메이저 버전은 insertArrowTable()·insertArrowFromIPCStream() 같은 Arrow 삽입을 실패시켜요 — 종종 조용히 실패해서 대상 테이블이 만들어지지 않아요. DuckDB-Wasm의 apache-arrow 의존성을 확인하고 apache-arrow 설치를 그것에 맞게 고정하세요.

import { tableFromArrays } from 'apache-arrow';

const arrowTable = tableFromArrays({
  id: [1, 2, 3],
  name: ['John', 'Jane', 'Jack'],
  age: [20, 21, 22],
});

await conn.insertArrowTable(arrowTable, { name: 'arrow_table' });

raw Arrow IPC 스트림으로 도착하는 데이터(예: fetch() 응답)를 삽입하려면 각 청크를 읽어 insertArrowFromIPCStream()에 넘기고, 스트림 끝(EOS) 마커를 써서 테이블이 완성됐음을 알려요.

// EOS signal according to the Arrow IPC streaming format
// See https://arrow.apache.org/docs/format/Columnar.html#ipc-streaming-format
const EOS = new Uint8Array([255, 255, 255, 255, 0, 0, 0, 0]);

const streamResponse = await fetch(`someapi`);
const streamReader = streamResponse.body.getReader();
const streamInserts = [];
while (true) {
    const { value, done } = await streamReader.read();
    if (done) break;
    streamInserts.push(conn.insertArrowFromIPCStream(value, { name: 'streamed' }));
}

// Write the EOS marker
streamInserts.push(conn.insertArrowFromIPCStream(EOS, { name: 'streamed' }));

await Promise.all(streamInserts);

CSV

CSV 텍스트를 파일로 등록한 뒤 insertCSVFromPath()로 로드해요. insert 옵션은 대상 테이블을, 그리고 자동 감지가 꺼져 있으면 CSV 방언과 열 타입을 설명해요.

import { Int32, Utf8 } from 'apache-arrow';

const csvContent = '1|foo\n2|bar\n';
await db.registerFileText('data.csv', csvContent);

await conn.insertCSVFromPath('data.csv', {
    schema: 'main',
    name: 'foo',
    detect: false,
    header: false,
    delimiter: '|',
    columns: {
        col1: new Int32(),
        col2: new Utf8(),
    },
});

JSON

insertJSONFromPath()는 행 우선(row-major)과 열 우선(column-major) JSON 둘 다 받아들여요. 문서를 먼저 등록한 뒤 삽입해요.

// Row-major format
const jsonRowContent = [
    { "col1": 1, "col2": "foo" },
    { "col1": 2, "col2": "bar" },
];
await db.registerFileText('rows.json', JSON.stringify(jsonRowContent));
await conn.insertJSONFromPath('rows.json', { name: 'rows' });

// Column-major format
const jsonColContent = {
    "col1": [1, 2],
    "col2": ["foo", "bar"]
};
await db.registerFileText('columns.json', JSON.stringify(jsonColContent));
await conn.insertJSONFromPath('columns.json', { name: 'columns' });

API에서 fetch한 JSON을 가져오려면 응답 바이트를 파일 버퍼로 등록해요.

const streamResponse = await fetch(`someapi/content.json`);
await db.registerFileBuffer('file.json', new Uint8Array(await streamResponse.arrayBuffer()));
await conn.insertJSONFromPath('file.json', { name: 'JSONContent' });

Parquet

Parquet 파일은 전용 insert 함수로 로드하지 않고 등록한 뒤 SQL로 읽어요. 사용자가 고른 로컬 파일은 registerFileHandle(), 원격 파일은 registerFileURL(), 이미 fetch한 바이트는 registerFileBuffer()로 등록해요.

// Local file chosen by the user
const pickedFile: File = letUserPickFile();
await db.registerFileHandle('local.parquet', pickedFile, DuckDBDataProtocol.BROWSER_FILEREADER, true);

// Remote file, read lazily over HTTP
await db.registerFileURL('remote.parquet', 'https://origin/remote.parquet', DuckDBDataProtocol.HTTP, false);

// Bytes already fetched into memory
const res = await fetch('https://origin/remote.parquet');
await db.registerFileBuffer('buffer.parquet', new Uint8Array(await res.arrayBuffer()));

파일이 등록되면 이름으로 쿼리해요.

await conn.query(`CREATE TABLE local AS SELECT * FROM 'local.parquet'`);

httpfs (Wasm-Flavored)

[Wasm-flavored httpfs 확장]({% link docs/current/clients/wasm/extensions.md %}#httpfs)을 쓰면 등록을 건너뛰고 SQL 텍스트에 URL을 넣어 원격 파일을 직접 읽을 수 있어요.

await conn.query(`
    CREATE TABLE direct AS
        SELECT * FROM 'https://origin/remote.parquet'
`);

Tip 원격 파일을 읽으면 브라우저가 요청을 내므로 브라우저의 CORS 정책을 따라야 해요. S3나 다른 원격 파일에 대한 쿼리가 네트워크 오류로 실패하면 [Troubleshoot]({% link docs/current/clients/wasm/troubleshoot.md %}#network-error-when-querying-remote-files)을 참고하세요.

Insert 문

마지막으로 [INSERT 문]({% link docs/current/data/insert.md %})은 파일을 등록하지 않고 값을 직접 로드해요. 소량의 데이터나 기존 테이블에 추가할 때 유용해요.

await conn.query(`
    INSERT INTO existing_table
    VALUES (1, 'foo'), (2, 'bar')`);

더 알아보기 (Learn more)

  • [Run Queries]({% link docs/current/clients/wasm/query.md %}) — 여기서 가져온 데이터를 쿼리하고 결과를 내보내기.
  • [Load Extensions]({% link docs/current/clients/wasm/extensions.md %}) — SQL에서 파일을 직접 읽는 데 쓰는 Parquet, JSON, Wasm-flavored httpfs 확장.
  • [INSERT 문]({% link docs/current/data/insert.md %}) — 위 insert 함수들의 대안인 DuckDB의 SQL 레벨 INSERT.
  • [Instantiate]({% link docs/current/clients/wasm/instantiation.md %}) — 이 가져오기들이 실행되는 db와 커넥션 만들기.
  • [Troubleshoot]({% link docs/current/clients/wasm/troubleshoot.md %}) — 원격 파일을 읽을 때의 CORS 네트워크 오류.