쿼리 실행
쿼리 실행 (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과 매핑되므로, None은 NULL로 바인딩되고 NULL 결과는 None으로 다시 읽혀요.
경고: DuckDB에 대량의 데이터를 삽입하는 데 prepared statements는 사용하지 마세요. Appender와 같은 더 빠른 옵션은 [Import Data]({% link docs/current/clients/rust/data_import.md %})를 참고해요.
Prepared Statements
Connection::prepare()는 구문을 한 번 컴파일해 반복 실행할 수 있게 해줘요. 반환된 Statement는 execute()로 실행하거나 쿼리해 행을 다시 읽을 수 있어요.
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()이 반환해요. Value는 Value::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열기.