쿼리 실행

쿼리 실행 (Run Queries, Rust)

개요 (Overview)

Connection은 두 계열의 메서드를 통해 SQL을 실행해요:

  • execute_* 메서드(execute()execute_batch())는 INSERT나 DDL처럼 행을 반환하지 않는 구문을 보내고, 영향을 받은 행 수를 보고해요.
  • query_* 메서드(query(), query_map(), query_row())는 행을 반환하는 구문을 실행하고 각 Row를 Rust 값으로 매핑하도록 넘겨줘요.

Prepared statements는 둘 다에서 동작해요. 아래 섹션이 구문 전송, 파라미터 바인딩, 행을 타이핑된 Rust 값으로 바꾸는 방법을 안내해요. 이들이 실행되는 Connection을 여는 방법은 [Connect]({% link docs/current/clients/rust/connecting.md %})를 참고해요.

출처: 문서

본문

구문 전송 (Sending Statements)

행을 반환하지 않는 단일 구문(예: [INSERT]({% link docs/current/sql/statements/insert.md %})나 [UPDATE]({% link docs/current/sql/statements/update.md %}))에는 execute()를 사용해요. SQL과 파라미터 목록을 받고 영향을 받은 행 수를 반환해요. 파라미터가 없으면 빈 슬라이스 []를 전달해요:

conn.execute(
    "CREATE TABLE person (id INTEGER, name TEXT, data BLOB)",
    [],
)?;

let rows_changed = conn.execute(
    "INSERT INTO person (id, name) VALUES (1, 'Steven')",
    [],
)?;

한 번에 여러 구문을 실행하려면(예: 스키마 설정 스크립트) execute_batch()를 사용해요:

conn.execute_batch(
    r"CREATE SEQUENCE seq;
      CREATE TABLE person (
          id   INTEGER PRIMARY KEY DEFAULT NEXTVAL('seq'),
          name TEXT NOT NULL,
          data BLOB
      );",
)?;

파라미터 바인딩 (Binding Parameters)

값은 SQL 문자열에 포맷되는 대신 구문의 플레이스홀더에 바인딩돼요. DuckDB는 위치(?)와 번호(?1, ?2) 플레이스홀더를 사용해요. params! 매크로는 이질적인 값 목록을 파라미터 슬라이스로 포장해요:

use duckdb::params;

conn.execute(
    "INSERT INTO person (id, name, data) VALUES (?, ?, ?)",
    params![1, "Steven", None::<Vec<u8>>],
)?;

$name 같은 명명된 플레이스홀더에는 named_params! 매크로를 사용해요:

use duckdb::named_params;

conn.query_row(
    "SELECT $age >= 18 AND $name = 'Alice'",
    named_params! {
        "age": min_age,
        "name": name,
    },
    |row| row.get(0),
)?;

파라미터로 바인딩되는 모든 값은 ToSql 트레잇을 구현하고, 행에서 읽는 모든 값은 FromSql을 구현해요. 크레이트는 표준 Rust 숫자, 문자열, 바이트 슬라이스, boolean 타입 모두에 둘을 제공해요. Option<T>는 SQL NULL과 매핑되므로, NoneNULL로 바인딩되고 NULL 결과는 None으로 다시 읽혀요.

경고: DuckDB에 대량의 데이터를 삽입하는 데 prepared statements는 사용하지 마세요. Appender와 같은 더 빠른 옵션은 [Import Data]({% link docs/current/clients/rust/data_import.md %})를 참고해요.

Prepared Statements

Connection::prepare()는 구문을 한 번 컴파일해 반복 실행할 수 있게 해줘요. 반환된 Statementexecute()로 실행하거나 쿼리해 행을 다시 읽을 수 있어요.

let mut stmt = conn.prepare("INSERT INTO person (id, name) VALUES (?, ?)")?;
stmt.execute(params![1, "Steven"])?;
stmt.execute(params![2, "Jane"])?;

행을 Rust 값으로 매핑 (Mapping Rows to Rust Values)

결과 집합을 읽으려면 SELECT를 준비하고, 각 Row에서 값을 만드는 클로저를 전달하며 query_map()을 호출해요. 컬럼은 0-기반 인덱스로 row.get()으로 읽으며, 반환 타입은 할당되는 struct 필드에서 추론돼요. query_map()Result 반복자를 산출하며 Vec으로 수집할 수 있어요:

#[derive(Debug)]
struct Person {
    id: i32,
    name: String,
    data: Option<Vec<u8>>,
}

let mut stmt = conn.prepare("SELECT id, name, data FROM person")?;
let people = stmt
    .query_map([], |row| {
        Ok(Person {
            id: row.get(0)?,
            name: row.get(1)?,
            data: row.get(2)?,
        })
    })?
    .collect::<Result<Vec<_>>>()?;

for person in people {
    println!("Found person {person:?}");
}

컬럼은 row.get("name")으로 이름으로도 읽을 수 있어요. 이 예시는 크레이트의 basic 예시를 따르는.

단일 행 읽기 (Reading a Single Row)

쿼리가 정확히 한 행을 반환하면 Connection::query_row()가 statement를 손으로 준비하지 않고 한 호출로 그 행에 클로저를 적용하며 실행해요. <(T,)>::try_from(row) 헬퍼는 단일-컬럼 행을 튜플로 변환해요:

let count = conn.query_row(
    "SELECT count(*) FROM person",
    [],
    |row| row.get::<_, i64>(0),
)?;

// 전체 행을 한 단계로 튜플로 변환할 수 있음.
let (n,) = conn.query_row("SELECT count(*) FROM person", [], |row| {
    <(i64,)>::try_from(row)
})?;

query_row_and_then()은 커스텀 오류 타입을 반환하는 클로저용 대응 메서드예요.

중첩 및 복합 타입 읽기

DuckDB의 중첩 및 복합 타입은 Value 열거형으로 읽으며, 대상 타입이 Value일 때 row.get()이 반환해요. ValueValue::List, Value::Struct, Value::Map, Value::Array, Value::Union, Value::Enum, Value::Decimal 같은 변형을 가지므로, 중첩 컬럼을 선택하는 쿼리를 매치할 수 있어요:

use duckdb::types::Value;

let mut stmt = conn.prepare("SELECT [1, 2, 3] AS l, {'a': 1, 'b': 2} AS s")?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
    let list: Value = row.get(0)?;
    let strukt: Value = row.get(1)?;
    println!("{list:?} {strukt:?}");
}

Value, ValueRef, Type은 non-exhaustive로 표시되어 있으므로, DuckDB가 타입을 추가할 때 앞으로 호환되도록 match는 와일드카드 암(arm)을 포함해야 해요. 행 단위가 아니라 결과 집합 전체를 열 지향 배치로 읽으려면 [Handle Results]({% link docs/current/clients/rust/result_handling.md %})를 참고해요.

더 알아보기 (Learn more)

  • [Handle Results]({% link docs/current/clients/rust/result_handling.md %}) — 행 단위가 아니라 Apache Arrow record batch나 Polars 데이터 프레임으로 결과 읽기.
  • [Import Data]({% link docs/current/clients/rust/data_import.md %}) — 벌크 삽입에 prepared statements의 권장 대안인 Appender.
  • [Prepared Statements]({% link docs/current/sql/query_syntax/prepared_statements.md %}) — 여기서 사용한 파라미터화 쿼리에 대한 DuckDB의 SQL 수준 지원.
  • [Connect]({% link docs/current/clients/rust/connecting.md %}) — 이 구문들이 실행되는 Connection 열기.