Skip to content

Commit 5fd7f69

Browse files
committed
strip comments
1 parent 426f6cc commit 5fd7f69

2 files changed

Lines changed: 20 additions & 64 deletions

File tree

src/duckdb_py/pyrelation.cpp

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,6 @@ unique_ptr<DuckDBPyRelation> DuckDBPyRelation::Distinct() {
801801

802802
duckdb::pyarrow::RecordBatchReader DuckDBPyRelation::FetchRecordBatchReader(idx_t rows_per_batch) {
803803
AssertResult();
804-
// FetchRecordBatchReader routes by the stored result's type: a con.execute() stream is
805-
// wrapped directly; a pre-executed relation's MaterializedQueryResult is re-fed.
806804
return result->FetchRecordBatchReader(rows_per_batch);
807805
}
808806

@@ -993,15 +991,10 @@ py::object DuckDBPyRelation::ToArrowCapsule(const py::object &requested_schema)
993991
if (!rel) {
994992
return py::none();
995993
}
996-
// Fresh relation: execute lazily as a stream on the user's own context. The
997-
// resulting StreamQueryResult owns that context, so the capsule survives a
998-
// later `del conn` (natively fixing #492); it shares the connection's single
999-
// active-stream slot, so it must be consumed before reusing the connection.
994+
// Fresh relation: stream lazily on the user's context (capsule survives `del conn`,
995+
// but shares the single active-stream slot - consume before reusing the connection).
1000996
ExecuteOrThrow(true);
1001997
}
1002-
// FetchArrowCapsule routes by the result's type: a StreamQueryResult (fresh or a
1003-
// con.execute() cursor) is wrapped directly; a pre-executed relation's
1004-
// MaterializedQueryResult is re-fed through the engine as a stream.
1005998
AssertResultOpen();
1006999
auto res = result->FetchArrowCapsule();
10071000
result = nullptr;
@@ -1049,7 +1042,6 @@ duckdb::pyarrow::RecordBatchReader DuckDBPyRelation::ToRecordBatch(idx_t batch_s
10491042
// Fresh relation: stream lazily on the user's own context (survives `del conn`).
10501043
ExecuteOrThrow(true);
10511044
}
1052-
// FetchRecordBatchReader routes by type (stream wrapped directly, materialized re-fed).
10531045
AssertResultOpen();
10541046
auto res = result->FetchRecordBatchReader(batch_size);
10551047
result = nullptr;

src/duckdb_py/pyresult.cpp

Lines changed: 18 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -427,26 +427,17 @@ py::dict DuckDBPyResult::FetchTF() {
427427
return result_dict;
428428
}
429429

430-
// Build a `SELECT * FROM <column data>` statement that scans `collection` in place, to be
431-
// executed directly via ClientContext::PendingQuery(statement, ...).
432-
//
433-
// This deliberately avoids ClientContext::PendingQuery(relation, ...), whose
434-
// RelationStatement constructor eagerly calls relation->GetQuery() and stringifies the
435-
// ENTIRE ColumnDataCollection (formatting every value - an O(rows) pathology that costs
436-
// >8s / 10M rows). The query string is never used for binding (Binder::Bind just calls
437-
// relation->Bind), so executing the SelectStatement skips it entirely while keeping the
438-
// zero-copy, parallel ColumnDataScan. The ColumnDataRef owns the collection (and the bound
439-
// LogicalColumnDataGet takes it over), so the data stays alive for the result - important
440-
// for the lazy StreamQueryResult path.
430+
// `SELECT * FROM <column data>` over `collection`, executed as a SelectStatement rather
431+
// than via PendingQuery(relation) - the latter's RelationStatement stringifies the whole
432+
// collection (O(rows)). The ColumnDataRef owns the collection, so it outlives the result
433+
// (needed by the lazy stream path).
441434
static unique_ptr<SelectStatement> MakeColumnDataScanStatement(unique_ptr<ColumnDataCollection> collection,
442435
const vector<string> &names) {
443-
// The binder rejects a ColumnDataRef with duplicate column names, so de-duplicate
444-
// them (a, a -> a, a_1); callers restore the original output names on the result.
436+
// The binder rejects duplicate column names; callers restore the originals afterwards.
445437
auto deduplicated_names = names;
446438
QueryResult::DeduplicateColumns(deduplicated_names);
447439
auto table_ref = make_uniq<ColumnDataRef>(std::move(collection), std::move(deduplicated_names));
448-
// Binding requires a set alias (BindingAlias::GetAlias asserts otherwise).
449-
table_ref->alias = "materialized";
440+
table_ref->alias = "materialized"; // binding asserts on an unset alias
450441
auto select_node = make_uniq<SelectNode>();
451442
select_node->select_list.push_back(make_uniq<StarExpression>());
452443
select_node->from_table = std::move(table_ref);
@@ -455,20 +446,14 @@ static unique_ptr<SelectStatement> MakeColumnDataScanStatement(unique_ptr<Column
455446
return select;
456447
}
457448

449+
// Re-feed a materialized result through a PhysicalArrowCollector on the user's own context
450+
// (parallel conversion, correct Arrow settings) -> ArrowQueryResult.
458451
void DuckDBPyResult::PromoteMaterializedToArrow(idx_t batch_size) {
459-
// Only an already-materialized result (a ColumnDataCollection in memory) is pulled
460-
// back through the engine. A lazy StreamQueryResult is converted directly instead -
461-
// see FetchArrowTable - so we never materialize a lazy result just to re-feed it.
462452
D_ASSERT(result->type == QueryResultType::MATERIALIZED_RESULT);
463453
auto client_context = result->client_properties.client_context;
464454
if (!client_context) {
465455
throw InternalException("Cannot promote result to Arrow: the originating client context is gone");
466456
}
467-
// Re-run the in-memory result through the engine with a PhysicalArrowCollector on
468-
// the user's own context. The ColumnDataScan over the collection is parallel, so the
469-
// Arrow conversion runs in parallel, just like the fresh relational to_arrow_table
470-
// path. Running on the user's context means the result carries the user's Arrow
471-
// output settings automatically (no need to replicate them).
472457
auto context = client_context->shared_from_this();
473458
auto &materialized = result->Cast<MaterializedQueryResult>();
474459
auto names = result->names;
@@ -494,25 +479,19 @@ void DuckDBPyResult::PromoteMaterializedToArrow(idx_t batch_size) {
494479
if (new_result->HasError()) {
495480
new_result->ThrowError();
496481
}
497-
// Re-binding the ColumnDataRef de-duplicates duplicate output column names
498-
// (a, a -> a, a_1). Restore the originals so the Arrow output is unchanged.
499-
new_result->names = std::move(names);
482+
new_result->names = std::move(names); // restore names de-duplicated by re-binding
500483
result = std::move(new_result);
501484
}
502485

486+
// Re-feed a materialized result as a lazy stream on the user's own context. The
487+
// StreamQueryResult co-owns the context, so conversion survives `del conn` and runs under a
488+
// live transaction (geometry/extension correctness, #492).
503489
void DuckDBPyResult::PromoteMaterializedToStream() {
504-
// As above, only an already-materialized result is re-fed; a lazy StreamQueryResult
505-
// is wrapped directly (it already has a live context).
506490
D_ASSERT(result->type == QueryResultType::MATERIALIZED_RESULT);
507491
auto client_context = result->client_properties.client_context;
508492
if (!client_context) {
509493
throw InternalException("Cannot promote result to an Arrow stream: the originating client context is gone");
510494
}
511-
// Re-run the materialized result as a lazy stream on the user's own context. The
512-
// resulting StreamQueryResult co-owns that ClientContext (shared_ptr), so the stream
513-
// - and the Arrow conversion it drives - survives `del conn` and runs under a live
514-
// transaction (the geometry/extension correctness win, native #492 fix). The
515-
// ColumnDataRef owns the collection, so the data outlives the temporary relation.
516495
auto context = client_context->shared_from_this();
517496
auto &materialized = result->Cast<MaterializedQueryResult>();
518497
auto names = result->names;
@@ -536,12 +515,8 @@ duckdb::pyarrow::Table DuckDBPyResult::FetchArrowTable(idx_t rows_per_batch, boo
536515
if (!result) {
537516
throw InvalidInputException("There is no query result");
538517
}
539-
// Route by result type:
540-
// - ARROW_RESULT: fresh to_arrow_table already ran a PhysicalArrowCollector.
541-
// - MATERIALIZED_RESULT (rel.execute()): a CDC in memory - re-feed it through a
542-
// PhysicalArrowCollector for parallel conversion (-> ARROW_RESULT below).
543-
// - STREAM_RESULT (con.execute()): a lazy result with a live context - convert it
544-
// directly, batch by batch. We never materialize a lazy result just to re-feed it.
518+
// ARROW_RESULT: fresh collector output. MATERIALIZED: re-feed for parallel conversion.
519+
// STREAM: a live result, converted directly below (never materialized to re-feed).
545520
if (result->type == QueryResultType::MATERIALIZED_RESULT) {
546521
PromoteMaterializedToArrow(rows_per_batch);
547522
}
@@ -600,18 +575,13 @@ ArrowArrayStream DuckDBPyResult::FetchArrowArrayStream(idx_t rows_per_batch) {
600575
if (!result) {
601576
throw InvalidInputException("There is no query result");
602577
}
603-
// The lazy Arrow surfaces (capsule, reader) need a context-owning StreamQueryResult.
604-
// A pre-executed relation hands us a MaterializedQueryResult (a CDC) - re-feed it as a
605-
// stream so the conversion gets a live, owned context (survives `del conn`, runs under
606-
// a live txn). A StreamQueryResult (con.execute(), or a fresh streaming relation)
607-
// already has a live context, so we wrap it directly - never materializing a lazy
608-
// result.
578+
// Re-feed a materialized result to get a context-owning stream; a StreamQueryResult is
579+
// wrapped directly (already has a live context).
609580
if (result->type == QueryResultType::MATERIALIZED_RESULT) {
610581
PromoteMaterializedToStream();
611582
}
583+
// The wrapper is owned by the ArrowArrayStream's private_data (released with the stream).
612584
ResultArrowArrayStreamWrapper *result_stream = new ResultArrowArrayStreamWrapper(std::move(result), rows_per_batch);
613-
// The 'result_stream' is part of the 'private_data' of the ArrowArrayStream and its lifetime is bound to that of
614-
// the ArrowArrayStream.
615585
return result_stream->stream;
616586
}
617587

@@ -643,13 +613,7 @@ py::object DuckDBPyResult::FetchArrowCapsule(idx_t rows_per_batch) {
643613
if (!result) {
644614
throw InvalidInputException("There is no query result");
645615
}
646-
// The capsule is a lazy streaming object backed by a StreamQueryResult, which
647-
// owns its own ClientContext. The caller (DuckDBPyRelation) ensures `result` is
648-
// a stream: a fresh relation streams on the user's connection; an already-
649-
// executed result is re-fed via PromoteToDedicatedStream(). Because the stream
650-
// owns its context, core's ResultArrowArrayStreamWrapper can lazily compute the
651-
// schema and convert (incl. extension/geometry types under a live transaction)
652-
// after the originating connection is gone - no eager schema caching needed.
616+
// Lazy streaming capsule backed by a context-owning stream (see FetchArrowArrayStream).
653617
auto inner_stream = FetchArrowArrayStream(rows_per_batch);
654618
auto stream = new ArrowArrayStream();
655619
*stream = inner_stream;

0 commit comments

Comments
 (0)