Documentation Index
Fetch the complete documentation index at: https://mintlify.com/duckdb/duckdb/llms.txt
Use this file to discover all available pages before exploring further.
Overview
DuckDB’s C++ API provides three types of query results:
QueryResult - Base class for all query results
MaterializedQueryResult - Complete result set loaded in memory
StreamQueryResult - Incrementally fetched results for large datasets
All result types provide methods for accessing data, checking for errors, and iterating over rows.
QueryResult Base Class
Defined in: duckdb/main/query_result.hpp (line 61)
class QueryResult : public BaseQueryResult {
public:
QueryResultType type;
StatementType statement_type;
StatementProperties properties;
vector<LogicalType> types;
vector<string> names;
ClientProperties client_properties;
unique_ptr<QueryResult> next;
};
Common Properties
Type of result: MATERIALIZED_RESULT, STREAM_RESULT, PENDING_RESULT, or ARROW_RESULT.
Type of SQL statement that produced this result (SELECT, INSERT, etc.).
Logical types of each column in the result.
Names of each column in the result.
Error Handling
bool HasError() const
const string &GetError() const
const ExceptionType &GetErrorType() const
ErrorData &GetErrorObject()
void ThrowError(const string &prepended_message = "") const
Example:
auto result = con.Query("SELECT * FROM non_existent_table");
if (result->HasError()) {
std::cerr << "Error: " << result->GetError() << std::endl;
return 1;
}
idx_t ColumnCount()
const string &ColumnName(idx_t index) const
Example:
auto result = con.Query("SELECT name, age, city FROM users");
std::cout << "Columns: ";
for (idx_t i = 0; i < result->ColumnCount(); i++) {
std::cout << result->ColumnName(i);
if (i < result->ColumnCount() - 1) std::cout << ", ";
}
std::cout << std::endl;
Fetching Data
unique_ptr<DataChunk> Fetch()
unique_ptr<DataChunk> FetchRaw()
Fetches the next chunk of normalized (flat) vectors. Returns nullptr when no more data is available.
Fetches the next chunk without normalization. May return non-flat vector types.
Example:
auto result = con.Query("SELECT * FROM users");
while (auto chunk = result->Fetch()) {
for (idx_t row = 0; row < chunk->size(); row++) {
for (idx_t col = 0; col < chunk->ColumnCount(); col++) {
auto value = chunk->GetValue(col, row);
std::cout << value.ToString() << "\t";
}
std::cout << std::endl;
}
}
Output Methods
void Print()
string ToString()
string ToBox(ClientContext &context, const BoxRendererConfig &config)
Example:
auto result = con.Query("SELECT * FROM users LIMIT 10");
result->Print(); // Prints to stdout in table format
MaterializedQueryResult
Defined in: duckdb/main/materialized_query_result.hpp (line 19)
MaterializedQueryResult contains the complete result set in memory. This is returned by Connection::Query().
class MaterializedQueryResult : public QueryResult {
public:
static constexpr const QueryResultType TYPE = QueryResultType::MATERIALIZED_RESULT;
Value GetValue(idx_t column, idx_t index);
idx_t RowCount() const;
ColumnDataCollection &Collection();
unique_ptr<ColumnDataCollection> TakeCollection();
};
Constructor
MaterializedQueryResult(
StatementType statement_type,
StatementProperties properties,
vector<string> names,
unique_ptr<ColumnDataCollection> collection,
ClientProperties client_properties
)
GetValue
Value GetValue(idx_t column, idx_t index)
Gets a specific value from the result set by column and row index.
The value at the specified position.
This method is very slow. For performance, iterate over chunks using Fetch() instead.
Example:
auto result = con.Query("SELECT name, age FROM users");
auto &materialized = result->Cast<MaterializedQueryResult>();
// Access individual values (slow)
for (idx_t row = 0; row < materialized.RowCount(); row++) {
auto name = materialized.GetValue(0, row).ToString();
auto age = materialized.GetValue(1, row).GetValue<int32_t>();
std::cout << name << ": " << age << std::endl;
}
RowCount
Total number of rows in the result set.
Example:
auto result = con.Query("SELECT * FROM users");
std::cout << "Total rows: " << result->Cast<MaterializedQueryResult>().RowCount() << std::endl;
Collection
ColumnDataCollection &Collection()
unique_ptr<ColumnDataCollection> TakeCollection()
Returns a reference to the underlying column data collection.
TakeCollection()
unique_ptr<ColumnDataCollection>
Takes ownership of the collection. The result’s collection becomes null after this operation.
StreamQueryResult
Defined in: duckdb/main/stream_query_result.hpp (line 26)
StreamQueryResult allows incremental fetching of results, which is more memory-efficient for large result sets. This is returned by Connection::SendQuery().
class StreamQueryResult : public QueryResult {
public:
static constexpr const QueryResultType TYPE = QueryResultType::STREAM_RESULT;
void WaitForTask();
StreamExecutionResult ExecuteTask();
unique_ptr<MaterializedQueryResult> Materialize();
bool IsOpen();
void Close();
shared_ptr<ClientContext> context;
};
Constructor
StreamQueryResult(
StatementType statement_type,
StatementProperties properties,
vector<LogicalType> types,
vector<string> names,
ClientProperties client_properties,
shared_ptr<BufferedData> buffered_data
)
Execution Control
void WaitForTask()
StreamExecutionResult ExecuteTask()
Reschedules tasks and waits until at least one task can be executed.
Executes a single task within the pipeline. Returns the execution status.
Example:
auto result = con.SendQuery("SELECT * FROM large_table");
while (auto chunk = result->Fetch()) {
// Process chunk
for (idx_t i = 0; i < chunk->size(); i++) {
// Process row i
}
}
Materialize
unique_ptr<MaterializedQueryResult> Materialize()
Converts the streaming result into a materialized result by fetching all remaining data.
return
unique_ptr<MaterializedQueryResult>
Materialized version of the complete result set.
Example:
auto stream_result = con.SendQuery("SELECT * FROM users");
auto materialized = stream_result->Materialize();
std::cout << "Total rows: " << materialized->RowCount() << std::endl;
IsOpen / Close
bool IsOpen()
void Close()
Returns true if the stream is still open and can be fetched from.
Example:
auto result = con.SendQuery("SELECT * FROM data");
if (result->IsOpen()) {
auto chunk = result->Fetch();
// ...
}
result->Close();
Iterating Over Results
Range-Based For Loop
QueryResult supports C++ range-based for loops:
auto result = con.Query("SELECT name, age FROM users");
for (auto &row : *result) {
auto name = row.GetValue<string>(0);
auto age = row.GetValue<int32_t>(1);
std::cout << name << ": " << age << std::endl;
}
Manual Iteration
auto result = con.Query("SELECT * FROM users");
for (auto it = result->begin(); it != result->end(); ++it) {
auto &row = *it;
for (idx_t col = 0; col < result->ColumnCount(); col++) {
std::cout << row.GetBaseValue(col).ToString() << "\t";
}
std::cout << std::endl;
}
Chunk-Based Iteration (Recommended)
For best performance, iterate over chunks:
auto result = con.Query("SELECT name, age, city FROM users");
while (auto chunk = result->Fetch()) {
// Process entire chunk at once for vectorized operations
auto name_vector = chunk->data[0]; // Column 0
auto age_vector = chunk->data[1]; // Column 1
auto city_vector = chunk->data[2]; // Column 2
for (idx_t row = 0; row < chunk->size(); row++) {
auto name = chunk->GetValue(0, row);
auto age = chunk->GetValue(1, row);
auto city = chunk->GetValue(2, row);
// Process values...
}
}
Value Access
DuckDB values can be accessed in multiple ways:
auto result = con.Query("SELECT name, age, balance FROM users");
while (auto chunk = result->Fetch()) {
for (idx_t row = 0; row < chunk->size(); row++) {
// Get as generic Value
auto name_value = chunk->GetValue(0, row);
std::string name = name_value.ToString();
// Get as typed value
auto age = chunk->GetValue(1, row).GetValue<int32_t>();
auto balance = chunk->GetValue(2, row).GetValue<double>();
// Check for NULL
if (chunk->GetValue(1, row).IsNull()) {
std::cout << "Age is NULL" << std::endl;
}
}
}
Usage Examples
Basic Result Iteration
#include "duckdb.hpp"
using namespace duckdb;
int main() {
DuckDB db(nullptr);
Connection con(db);
con.Query("CREATE TABLE users(name VARCHAR, age INTEGER)");
con.Query("INSERT INTO users VALUES ('Alice', 30), ('Bob', 25)");
auto result = con.Query("SELECT * FROM users");
// Print column names
for (idx_t i = 0; i < result->ColumnCount(); i++) {
std::cout << result->ColumnName(i) << "\t";
}
std::cout << std::endl;
// Print data
while (auto chunk = result->Fetch()) {
for (idx_t row = 0; row < chunk->size(); row++) {
for (idx_t col = 0; col < chunk->ColumnCount(); col++) {
std::cout << chunk->GetValue(col, row).ToString() << "\t";
}
std::cout << std::endl;
}
}
}
Streaming Large Results
Connection con(db);
// Use streaming for large result sets
auto result = con.SendQuery("SELECT * FROM large_table");
idx_t total_rows = 0;
while (auto chunk = result->Fetch()) {
total_rows += chunk->size();
// Process chunk incrementally
}
std::cout << "Processed " << total_rows << " rows" << std::endl;
Type-Safe Value Access
auto result = con.Query(
"SELECT name, age, balance, active FROM users"
);
while (auto chunk = result->Fetch()) {
for (idx_t i = 0; i < chunk->size(); i++) {
auto name = chunk->GetValue(0, i).GetValue<string>();
auto age = chunk->GetValue(1, i).GetValue<int32_t>();
auto balance = chunk->GetValue(2, i).GetValue<double>();
auto active = chunk->GetValue(3, i).GetValue<bool>();
if (active && balance > 1000.0) {
std::cout << name << " (age " << age << ") has balance: "
<< balance << std::endl;
}
}
}
Error Handling
auto result = con.Query("SELECT * FROM possibly_missing_table");
if (result->HasError()) {
std::cerr << "Query failed: " << result->GetError() << std::endl;
std::cerr << "Error type: " << (int)result->GetErrorType() << std::endl;
return 1;
}
result->Print();
See Also