Skip to content

Conversation

@Hunaid2000
Copy link
Contributor

This PR contains following changes:

  • Implements WHERE pushdown optimization feature. Currently supported operators: =, >=, >, <=, <, BETWEEN, and LIKE 'prefix%'.
  • Readme changes for new feature.

This feature is consistent with FDW options in CREATE FOREIGN TABLE.

Note: For verification, I tried out multiple examples along with different create table options. I checked if self.fetch_results contained only filtered results at end of begin_scan function. The explain plan also showed no line Rows Removed by Filter: after the patch.

A few examples:

-- current result set
etcd_fdw=# SELECT * FROM test;
 key  |    value
------+-------------
 !    | hashvalue
 $    | dollarvalue
 123  | intvalue
 @    | atvalue
 abc  | abcvalue
 code | etcd_fdw
 foo  | foovalue
 key0 | value0
 key1 | value1
 key2 | value2
 key3 | value3
 key4 | value4
 key5 | value5
 key6 | value6
 key7 | value7
 key8 | value8
 key9 | value9
 zoo  | zoovalue
(18 rows)

Without pushdown:

-- explain plan without pushdown
etcd_fdw=# explain analyze SELECT * FROM test where key >= 'key1' and key < 'key5';
                                                                                                     QUERY PLAN

-------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------
 Foreign Scan on test  (cost=0.00..1.00 rows=1 width=0) (actual time=0.060..0.073 rows=4 loops=1)
   Filter: ((key >= 'key1'::text) AND (key < 'key5'::text))
   Rows Removed by Filter: 10
   Wrappers: quals = [Qual { field: "key", operator: ">=", value: Cell(String("key1")), use_or: false, param: None }, Qual { field: "
key", operator: "<", value: Cell(String("key5")), use_or: false, param: None }]
   Wrappers: tgts = [Column { name: "key", num: 1, type_oid: 25 }, Column { name: "value", num: 2, type_oid: 25 }]
   Wrappers: sorts = []
   Wrappers: limit = None
 Planning Time: 9.520 ms
 Execution Time: 6.513 ms
(9 rows)

Note: Rows Removed by Filter: 10 in the plan.

With pushdown:

etcd_fdw=# SELECT * FROM test where key >= 'key1' and key < 'key5';
 key  | value
------+--------
 key1 | value1
 key2 | value2
 key3 | value3
 key4 | value4
(4 rows)

etcd_fdw=# explain analyze SELECT * FROM test where key >= 'key1' and key < 'key5';
                                                                                                     QUERY PLAN

-------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------
 Foreign Scan on test  (cost=0.00..1.00 rows=1 width=0) (actual time=0.037..0.048 rows=4 loops=1)
   Filter: ((key >= 'key1'::text) AND (key < 'key5'::text))
   Wrappers: quals = [Qual { field: "key", operator: ">=", value: Cell(String("key1")), use_or: false, param: None }, Qual { field: "
key", operator: "<", value: Cell(String("key5")), use_or: false, param: None }]
   Wrappers: tgts = [Column { name: "key", num: 1, type_oid: 25 }, Column { name: "value", num: 2, type_oid: 25 }]
   Wrappers: sorts = []
   Wrappers: limit = None
 Planning Time: 2.468 ms
 Execution Time: 3.740 ms
(8 rows)

Note no Rows Removed by Filter: in the plan.

Output with range_end option.

etcd_fdw=# CREATE foreign table test (key text, value text) server my_etcd_server options(rowid_column 'key', range_end 'key7');
CREATE FOREIGN TABLE
etcd_fdw=# SELECT * FROM test where key >= 'key3'; -- no rows after key6 since range_end is 'key7'
 key  | value
------+--------
 key3 | value3
 key4 | value4
 key5 | value5
 key6 | value6
(4 rows)

etcd_fdw=# explain analyze SELECT * FROM test where key >= 'key3';
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Foreign Scan on test  (cost=0.00..1.00 rows=1 width=0) (actual time=0.043..0.057 rows=4 loops=1)
   Filter: (key >= 'key3'::text)
   Wrappers: quals = [Qual { field: "key", operator: ">=", value: Cell(String("key3")), use_or: false, param: None }]
   Wrappers: tgts = [Column { name: "key", num: 1, type_oid: 25 }, Column { name: "value", num: 2, type_oid: 25 }]
   Wrappers: sorts = []
   Wrappers: limit = None
 Planning Time: 2.665 ms
 Execution Time: 4.027 ms
(8 rows)

-- LIKE
etcd_fdw=# explain analyze SELECT * FROM test where key like 'key%';
                                                      QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
 Foreign Scan on test  (cost=0.00..1.00 rows=1 width=0) (actual time=0.028..0.042 rows=7 loops=1)
   Filter: (key ~~ 'key%'::text)
   Wrappers: quals = [Qual { field: "key", operator: "~~", value: Cell(String("key%")), use_or: false, param: None }]
   Wrappers: tgts = [Column { name: "key", num: 1, type_oid: 25 }, Column { name: "value", num: 2, type_oid: 25 }]
   Wrappers: sorts = []
   Wrappers: limit = None
 Planning Time: 2.593 ms
 Execution Time: 3.627 ms
(8 rows)

etcd_fdw=# SELECT * FROM test where key like 'key%';
 key  | value
------+--------
 key0 | value0
 key1 | value1
 key2 | value2
 key3 | value3
 key4 | value4
 key5 | value5
 key6 | value6
(7 rows)

Looking forward to feedback.

@if-loop69420
Copy link
Contributor

LGTM

@if-loop69420 if-loop69420 merged commit b6b7084 into cybertec-postgresql:main Dec 9, 2025
1 check passed
@Hunaid2000 Hunaid2000 deleted the where_pushdown branch December 11, 2025 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants