1- #include " storage/postgres_index_set.hpp"
2- #include " storage/postgres_schema_entry.hpp"
3- #include " storage/postgres_transaction.hpp"
41#include " storage/postgres_optimizer.hpp"
2+
53#include " duckdb/planner/operator/logical_get.hpp"
64#include " duckdb/planner/operator/logical_limit.hpp"
7- #include " storage/postgres_catalog.hpp"
5+
6+ #include " dbconnector/optimizer/order_by_and_limit_optimizer.hpp"
7+ #include " dbconnector/optimizer/optimizer_util.hpp"
8+
89#include " postgres_scanner.hpp"
10+ #include " storage/postgres_index_set.hpp"
11+ #include " storage/postgres_schema_entry.hpp"
12+ #include " storage/postgres_transaction.hpp"
13+ #include " storage/postgres_catalog.hpp"
914
1015namespace duckdb {
1116
1217struct PostgresOperators {
1318 reference_map_t <PostgresCatalog, vector<reference<LogicalGet>>> scans;
1419};
1520
16- static void OptimizePostgresScanLimitPushdown (unique_ptr<LogicalOperator> &op) {
17- if (op->type == LogicalOperatorType::LOGICAL_LIMIT ) {
18- auto &limit = op->Cast <LogicalLimit>();
19- reference<LogicalOperator> child = *op->children [0 ];
20-
21- while (child.get ().type == LogicalOperatorType::LOGICAL_PROJECTION ) {
22- child = *child.get ().children [0 ];
23- }
24-
25- if (child.get ().type != LogicalOperatorType::LOGICAL_GET ) {
26- OptimizePostgresScanLimitPushdown (op->children [0 ]);
27- return ;
28- }
29-
30- auto &get = child.get ().Cast <LogicalGet>();
31- if (!PostgresCatalog::IsPostgresScan (get.function .name )) {
32- OptimizePostgresScanLimitPushdown (op->children [0 ]);
33- return ;
34- }
35-
36- switch (limit.limit_val .Type ()) {
37- case LimitNodeType::CONSTANT_VALUE :
38- case LimitNodeType::UNSET :
39- break ;
40- default :
41- // not a constant or unset limit
42- OptimizePostgresScanLimitPushdown (op->children [0 ]);
43- return ;
44- }
45- switch (limit.offset_val .Type ()) {
46- case LimitNodeType::CONSTANT_VALUE :
47- case LimitNodeType::UNSET :
48- break ;
49- default :
50- // not a constant or unset offset
51- OptimizePostgresScanLimitPushdown (op->children [0 ]);
52- return ;
53- }
54-
55- auto &bind_data = get.bind_data ->Cast <PostgresBindData>();
56-
57- string generated_limit_clause = " " ;
58- if (limit.limit_val .Type () != LimitNodeType::UNSET ) {
59- generated_limit_clause += " LIMIT " + to_string (limit.limit_val .GetConstantValue ());
60- }
61- if (limit.offset_val .Type () != LimitNodeType::UNSET ) {
62- generated_limit_clause += " OFFSET " + to_string (limit.offset_val .GetConstantValue ());
63- }
64-
65- if (!generated_limit_clause.empty ()) {
66- bind_data.limit = generated_limit_clause;
67- // When LIMIT is pushed down to Postgres, we must ensure single-task execution
68- // to avoid each task (whether parallel or sequential) applying the LIMIT independently.
69- // Setting pages_approx = 0 disables CTID-based task splitting, ensuring a single query.
70- bind_data.pages_approx = 0 ;
71- bind_data.max_threads = 1 ;
72-
73- op = std::move (op->children [0 ]);
74- return ;
75- }
76- }
77-
78- for (auto &child : op->children ) {
79- OptimizePostgresScanLimitPushdown (child);
80- }
81- }
82-
83- void GatherPostgresScans (LogicalOperator &op, PostgresOperators &result) {
21+ static void GatherPostgresScans (LogicalOperator &op, PostgresOperators &result) {
8422 if (op.type == LogicalOperatorType::LOGICAL_GET ) {
8523 auto &get = op.Cast <LogicalGet>();
8624 auto &table_scan = get.function ;
@@ -102,9 +40,35 @@ void GatherPostgresScans(LogicalOperator &op, PostgresOperators &result) {
10240 }
10341}
10442
43+ static void DisableParallelLimit (LogicalOperator &op) {
44+ LogicalGet *get = nullptr ;
45+ dbconnector::BindData *bind_data = nullptr ;
46+ if (dbconnector::optimizer::OptimizerUtil::FindExtensionGet (" postgres_scan" , op, get, bind_data)) {
47+ auto &pg_bind_data = bind_data->Cast <PostgresBindData>();
48+ if (!pg_bind_data.order_by_and_limit_bind_data .limit_clause .empty ()) {
49+ // When LIMIT is pushed down to Postgres, we must ensure single-task execution
50+ // to avoid each task (whether parallel or sequential) applying the LIMIT independently.
51+ // Setting pages_approx = 0 disables CTID-based task splitting, ensuring a single query.
52+ pg_bind_data.pages_approx = 0 ;
53+ pg_bind_data.max_threads = 1 ;
54+ }
55+ }
56+
57+ for (auto &child : op.children ) {
58+ DisableParallelLimit (*child);
59+ }
60+ }
61+
10562void PostgresOptimizer::Optimize (OptimizerExtensionInput &input, unique_ptr<LogicalOperator> &plan) {
63+ using namespace dbconnector ;
10664 // look at query plan and check if we can find LIMIT/OFFSET to pushdown
107- OptimizePostgresScanLimitPushdown (plan);
65+ // OptimizePostgresScanLimitPushdown(plan);
66+
67+ auto order_config = optimizer::OrderByAndLimitOptimizer::CreateConfig (
68+ input.context , " pg_order_pushdown" , ' "' , query::QuoteEscapeStyle::DOUBLE_QUOTE , " postgres_scan" );
69+ optimizer::OrderByAndLimitOptimizer::Optimize (order_config, input, plan);
70+ DisableParallelLimit (*plan);
71+
10872 // look at the query plan and check if we can enable streaming query scans
10973 PostgresOperators operators;
11074 GatherPostgresScans (*plan, operators);
0 commit comments