diff --git a/expected/pathman_only.out b/expected/pathman_only.out
index 1b9f6a6b..f44f2256 100644
--- a/expected/pathman_only.out
+++ b/expected/pathman_only.out
@@ -3,13 +3,31 @@
  *  NOTE: This test behaves differenly on PgPro
  * ---------------------------------------------
  *
- * Since 12 (608b167f9f), CTEs which are scanned once are no longer an
- * optimization fence, which changes practically all plans here. There is
+ * --------------------
+ *  pathman_only_1.sql
+ * --------------------
+ * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer
+ * an optimization fence, which changes practically all plans here. There is
  * an option to forcibly make them MATERIALIZED, but we also need to run tests
  * on older versions, so create pathman_only_1.out instead.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * --------------------
+ *  pathman_only_2.sql
+ * --------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * --------------------
+ *  pathman_only_3.sql
+ * --------------------
+ * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed,
+ * which affected the output of the "Prune by" in EXPLAIN.
+ *
+ * --------------------
+ *  pathman_only_4.sql
+ * --------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 \set VERBOSITY terse
 SET search_path = 'public';
diff --git a/expected/pathman_only_1.out b/expected/pathman_only_1.out
index b92a8eaf..ce6fd127 100644
--- a/expected/pathman_only_1.out
+++ b/expected/pathman_only_1.out
@@ -3,13 +3,31 @@
  *  NOTE: This test behaves differenly on PgPro
  * ---------------------------------------------
  *
- * Since 12 (608b167f9f), CTEs which are scanned once are no longer an
- * optimization fence, which changes practically all plans here. There is
+ * --------------------
+ *  pathman_only_1.sql
+ * --------------------
+ * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer
+ * an optimization fence, which changes practically all plans here. There is
  * an option to forcibly make them MATERIALIZED, but we also need to run tests
  * on older versions, so create pathman_only_1.out instead.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * --------------------
+ *  pathman_only_2.sql
+ * --------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * --------------------
+ *  pathman_only_3.sql
+ * --------------------
+ * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed,
+ * which affected the output of the "Prune by" in EXPLAIN.
+ *
+ * --------------------
+ *  pathman_only_4.sql
+ * --------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 \set VERBOSITY terse
 SET search_path = 'public';
diff --git a/expected/pathman_only_2.out b/expected/pathman_only_2.out
index c37dd5f4..6aeadb76 100644
--- a/expected/pathman_only_2.out
+++ b/expected/pathman_only_2.out
@@ -3,13 +3,31 @@
  *  NOTE: This test behaves differenly on PgPro
  * ---------------------------------------------
  *
- * Since 12 (608b167f9f), CTEs which are scanned once are no longer an
- * optimization fence, which changes practically all plans here. There is
+ * --------------------
+ *  pathman_only_1.sql
+ * --------------------
+ * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer
+ * an optimization fence, which changes practically all plans here. There is
  * an option to forcibly make them MATERIALIZED, but we also need to run tests
  * on older versions, so create pathman_only_1.out instead.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * --------------------
+ *  pathman_only_2.sql
+ * --------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * --------------------
+ *  pathman_only_3.sql
+ * --------------------
+ * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed,
+ * which affected the output of the "Prune by" in EXPLAIN.
+ *
+ * --------------------
+ *  pathman_only_4.sql
+ * --------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 \set VERBOSITY terse
 SET search_path = 'public';
diff --git a/expected/pathman_only_3.out b/expected/pathman_only_3.out
index 2f2fcc75..1999309d 100644
--- a/expected/pathman_only_3.out
+++ b/expected/pathman_only_3.out
@@ -3,13 +3,31 @@
  *  NOTE: This test behaves differenly on PgPro
  * ---------------------------------------------
  *
- * Since 12 (608b167f9f), CTEs which are scanned once are no longer an
- * optimization fence, which changes practically all plans here. There is
+ * --------------------
+ *  pathman_only_1.sql
+ * --------------------
+ * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer
+ * an optimization fence, which changes practically all plans here. There is
  * an option to forcibly make them MATERIALIZED, but we also need to run tests
  * on older versions, so create pathman_only_1.out instead.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * --------------------
+ *  pathman_only_2.sql
+ * --------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * --------------------
+ *  pathman_only_3.sql
+ * --------------------
+ * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed,
+ * which affected the output of the "Prune by" in EXPLAIN.
+ *
+ * --------------------
+ *  pathman_only_4.sql
+ * --------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 \set VERBOSITY terse
 SET search_path = 'public';
diff --git a/expected/pathman_only_4.out b/expected/pathman_only_4.out
new file mode 100644
index 00000000..fbcc397c
--- /dev/null
+++ b/expected/pathman_only_4.out
@@ -0,0 +1,299 @@
+/*
+ * ---------------------------------------------
+ *  NOTE: This test behaves differenly on PgPro
+ * ---------------------------------------------
+ *
+ * --------------------
+ *  pathman_only_1.sql
+ * --------------------
+ * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer
+ * an optimization fence, which changes practically all plans here. There is
+ * an option to forcibly make them MATERIALIZED, but we also need to run tests
+ * on older versions, so create pathman_only_1.out instead.
+ *
+ * --------------------
+ *  pathman_only_2.sql
+ * --------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * --------------------
+ *  pathman_only_3.sql
+ * --------------------
+ * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed,
+ * which affected the output of the "Prune by" in EXPLAIN.
+ *
+ * --------------------
+ *  pathman_only_4.sql
+ * --------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
+ */
+\set VERBOSITY terse
+SET search_path = 'public';
+CREATE EXTENSION pg_pathman;
+CREATE SCHEMA test_only;
+/* Test special case: ONLY statement with not-ONLY for partitioned table */
+CREATE TABLE test_only.from_only_test(val INT NOT NULL);
+INSERT INTO test_only.from_only_test SELECT generate_series(1, 20);
+SELECT create_range_partitions('test_only.from_only_test', 'val', 1, 2);
+ create_range_partitions 
+-------------------------
+                      10
+(1 row)
+
+VACUUM ANALYZE;
+/* should be OK */
+EXPLAIN (COSTS OFF)
+SELECT * FROM ONLY test_only.from_only_test
+UNION SELECT * FROM test_only.from_only_test;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ HashAggregate
+   Group Key: from_only_test.val
+   ->  Append
+         ->  Seq Scan on from_only_test
+         ->  Append
+               ->  Seq Scan on from_only_test_1 from_only_test_2
+               ->  Seq Scan on from_only_test_2 from_only_test_3
+               ->  Seq Scan on from_only_test_3 from_only_test_4
+               ->  Seq Scan on from_only_test_4 from_only_test_5
+               ->  Seq Scan on from_only_test_5 from_only_test_6
+               ->  Seq Scan on from_only_test_6 from_only_test_7
+               ->  Seq Scan on from_only_test_7 from_only_test_8
+               ->  Seq Scan on from_only_test_8 from_only_test_9
+               ->  Seq Scan on from_only_test_9 from_only_test_10
+               ->  Seq Scan on from_only_test_10 from_only_test_11
+(15 rows)
+
+/* should be OK */
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_only.from_only_test
+UNION SELECT * FROM ONLY test_only.from_only_test;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ HashAggregate
+   Group Key: from_only_test.val
+   ->  Append
+         ->  Append
+               ->  Seq Scan on from_only_test_1
+               ->  Seq Scan on from_only_test_2
+               ->  Seq Scan on from_only_test_3
+               ->  Seq Scan on from_only_test_4
+               ->  Seq Scan on from_only_test_5
+               ->  Seq Scan on from_only_test_6
+               ->  Seq Scan on from_only_test_7
+               ->  Seq Scan on from_only_test_8
+               ->  Seq Scan on from_only_test_9
+               ->  Seq Scan on from_only_test_10
+         ->  Seq Scan on from_only_test from_only_test_11
+(15 rows)
+
+/* should be OK */
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_only.from_only_test
+UNION SELECT * FROM test_only.from_only_test
+UNION SELECT * FROM ONLY test_only.from_only_test;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ HashAggregate
+   Group Key: from_only_test.val
+   ->  Append
+         ->  Append
+               ->  Seq Scan on from_only_test_1
+               ->  Seq Scan on from_only_test_2
+               ->  Seq Scan on from_only_test_3
+               ->  Seq Scan on from_only_test_4
+               ->  Seq Scan on from_only_test_5
+               ->  Seq Scan on from_only_test_6
+               ->  Seq Scan on from_only_test_7
+               ->  Seq Scan on from_only_test_8
+               ->  Seq Scan on from_only_test_9
+               ->  Seq Scan on from_only_test_10
+         ->  Append
+               ->  Seq Scan on from_only_test_1 from_only_test_12
+               ->  Seq Scan on from_only_test_2 from_only_test_13
+               ->  Seq Scan on from_only_test_3 from_only_test_14
+               ->  Seq Scan on from_only_test_4 from_only_test_15
+               ->  Seq Scan on from_only_test_5 from_only_test_16
+               ->  Seq Scan on from_only_test_6 from_only_test_17
+               ->  Seq Scan on from_only_test_7 from_only_test_18
+               ->  Seq Scan on from_only_test_8 from_only_test_19
+               ->  Seq Scan on from_only_test_9 from_only_test_20
+               ->  Seq Scan on from_only_test_10 from_only_test_21
+         ->  Seq Scan on from_only_test from_only_test_22
+(26 rows)
+
+/* should be OK */
+EXPLAIN (COSTS OFF)
+SELECT * FROM ONLY test_only.from_only_test
+UNION SELECT * FROM test_only.from_only_test
+UNION SELECT * FROM test_only.from_only_test;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ HashAggregate
+   Group Key: from_only_test.val
+   ->  Append
+         ->  Seq Scan on from_only_test
+         ->  Append
+               ->  Seq Scan on from_only_test_1 from_only_test_2
+               ->  Seq Scan on from_only_test_2 from_only_test_3
+               ->  Seq Scan on from_only_test_3 from_only_test_4
+               ->  Seq Scan on from_only_test_4 from_only_test_5
+               ->  Seq Scan on from_only_test_5 from_only_test_6
+               ->  Seq Scan on from_only_test_6 from_only_test_7
+               ->  Seq Scan on from_only_test_7 from_only_test_8
+               ->  Seq Scan on from_only_test_8 from_only_test_9
+               ->  Seq Scan on from_only_test_9 from_only_test_10
+               ->  Seq Scan on from_only_test_10 from_only_test_11
+         ->  Append
+               ->  Seq Scan on from_only_test_1 from_only_test_13
+               ->  Seq Scan on from_only_test_2 from_only_test_14
+               ->  Seq Scan on from_only_test_3 from_only_test_15
+               ->  Seq Scan on from_only_test_4 from_only_test_16
+               ->  Seq Scan on from_only_test_5 from_only_test_17
+               ->  Seq Scan on from_only_test_6 from_only_test_18
+               ->  Seq Scan on from_only_test_7 from_only_test_19
+               ->  Seq Scan on from_only_test_8 from_only_test_20
+               ->  Seq Scan on from_only_test_9 from_only_test_21
+               ->  Seq Scan on from_only_test_10 from_only_test_22
+(26 rows)
+
+/* not ok, ONLY|non-ONLY in one query (this is not the case for PgPro) */
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_only.from_only_test a
+JOIN ONLY test_only.from_only_test b USING(val);
+                 QUERY PLAN                  
+---------------------------------------------
+ Nested Loop
+   ->  Seq Scan on from_only_test b
+   ->  Custom Scan (RuntimeAppend)
+         Prune by: (a.val = b.val)
+         ->  Seq Scan on from_only_test_1 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_2 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_3 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_4 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_5 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_6 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_7 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_8 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_9 a
+               Filter: (b.val = val)
+         ->  Seq Scan on from_only_test_10 a
+               Filter: (b.val = val)
+(24 rows)
+
+/* should be OK */
+EXPLAIN (COSTS OFF)
+WITH q1 AS (SELECT * FROM test_only.from_only_test),
+	 q2 AS (SELECT * FROM ONLY test_only.from_only_test)
+SELECT * FROM q1 JOIN q2 USING(val);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Nested Loop
+   ->  Seq Scan on from_only_test from_only_test_1
+   ->  Custom Scan (RuntimeAppend)
+         Prune by: (from_only_test.val = from_only_test_1.val)
+         ->  Seq Scan on from_only_test_1 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_2 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_3 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_4 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_5 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_6 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_7 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_8 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_9 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_10 from_only_test
+               Filter: (from_only_test_1.val = val)
+(24 rows)
+
+/* should be OK */
+EXPLAIN (COSTS OFF)
+WITH q1 AS (SELECT * FROM ONLY test_only.from_only_test)
+SELECT * FROM test_only.from_only_test JOIN q1 USING(val);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Nested Loop
+   ->  Seq Scan on from_only_test from_only_test_1
+   ->  Custom Scan (RuntimeAppend)
+         Prune by: (from_only_test.val = from_only_test_1.val)
+         ->  Seq Scan on from_only_test_1 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_2 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_3 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_4 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_5 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_6 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_7 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_8 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_9 from_only_test
+               Filter: (from_only_test_1.val = val)
+         ->  Seq Scan on from_only_test_10 from_only_test
+               Filter: (from_only_test_1.val = val)
+(24 rows)
+
+/* should be OK */
+EXPLAIN (COSTS OFF)
+SELECT * FROM test_only.from_only_test
+WHERE val = (SELECT val FROM ONLY test_only.from_only_test
+			 ORDER BY val ASC
+			 LIMIT 1);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Custom Scan (RuntimeAppend)
+   Prune by: (from_only_test.val = (InitPlan 1).col1)
+   InitPlan 1
+     ->  Limit
+           ->  Sort
+                 Sort Key: from_only_test_1.val
+                 ->  Seq Scan on from_only_test from_only_test_1
+   ->  Seq Scan on from_only_test_1 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_2 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_3 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_4 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_5 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_6 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_7 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_8 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_9 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+   ->  Seq Scan on from_only_test_10 from_only_test
+         Filter: (val = (InitPlan 1).col1)
+(27 rows)
+
+DROP TABLE test_only.from_only_test CASCADE;
+NOTICE:  drop cascades to 11 other objects
+DROP SCHEMA test_only;
+DROP EXTENSION pg_pathman;
diff --git a/expected/pathman_rowmarks.out b/expected/pathman_rowmarks.out
index ea047c9e..6d4611ee 100644
--- a/expected/pathman_rowmarks.out
+++ b/expected/pathman_rowmarks.out
@@ -1,13 +1,30 @@
 /*
  * -------------------------------------------
- *  NOTE: This test behaves differenly on 9.5
+ *  NOTE: This test behaves differenly on PgPro
  * -------------------------------------------
  *
- * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated,
- * causing different output; pathman_rowmarks_2.out is the updated version.
+ * ------------------------
+ *  pathman_rowmarks_1.sql
+ * ------------------------
+ * Since PostgreSQL 9.5, output of EXPLAIN was changed.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * ------------------------
+ *  pathman_rowmarks_2.sql
+ * ------------------------
+ * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are
+ * eliminated, causing different output.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 SET search_path = 'public';
 CREATE EXTENSION pg_pathman;
diff --git a/expected/pathman_rowmarks_1.out b/expected/pathman_rowmarks_1.out
index 256b8637..063fca8d 100644
--- a/expected/pathman_rowmarks_1.out
+++ b/expected/pathman_rowmarks_1.out
@@ -1,13 +1,30 @@
 /*
  * -------------------------------------------
- *  NOTE: This test behaves differenly on 9.5
+ *  NOTE: This test behaves differenly on PgPro
  * -------------------------------------------
  *
- * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated,
- * causing different output; pathman_rowmarks_2.out is the updated version.
+ * ------------------------
+ *  pathman_rowmarks_1.sql
+ * ------------------------
+ * Since PostgreSQL 9.5, output of EXPLAIN was changed.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * ------------------------
+ *  pathman_rowmarks_2.sql
+ * ------------------------
+ * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are
+ * eliminated, causing different output.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 SET search_path = 'public';
 CREATE EXTENSION pg_pathman;
diff --git a/expected/pathman_rowmarks_2.out b/expected/pathman_rowmarks_2.out
index 06fb88ac..91d7804e 100644
--- a/expected/pathman_rowmarks_2.out
+++ b/expected/pathman_rowmarks_2.out
@@ -1,13 +1,30 @@
 /*
  * -------------------------------------------
- *  NOTE: This test behaves differenly on 9.5
+ *  NOTE: This test behaves differenly on PgPro
  * -------------------------------------------
  *
- * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated,
- * causing different output; pathman_rowmarks_2.out is the updated version.
+ * ------------------------
+ *  pathman_rowmarks_1.sql
+ * ------------------------
+ * Since PostgreSQL 9.5, output of EXPLAIN was changed.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * ------------------------
+ *  pathman_rowmarks_2.sql
+ * ------------------------
+ * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are
+ * eliminated, causing different output.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 SET search_path = 'public';
 CREATE EXTENSION pg_pathman;
diff --git a/expected/pathman_rowmarks_3.out b/expected/pathman_rowmarks_3.out
index af61e5f7..e8644292 100644
--- a/expected/pathman_rowmarks_3.out
+++ b/expected/pathman_rowmarks_3.out
@@ -1,13 +1,30 @@
 /*
  * -------------------------------------------
- *  NOTE: This test behaves differenly on 9.5
+ *  NOTE: This test behaves differenly on PgPro
  * -------------------------------------------
  *
- * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated,
- * causing different output; pathman_rowmarks_2.out is the updated version.
+ * ------------------------
+ *  pathman_rowmarks_1.sql
+ * ------------------------
+ * Since PostgreSQL 9.5, output of EXPLAIN was changed.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * ------------------------
+ *  pathman_rowmarks_2.sql
+ * ------------------------
+ * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are
+ * eliminated, causing different output.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 SET search_path = 'public';
 CREATE EXTENSION pg_pathman;
diff --git a/expected/pathman_rowmarks_4.out b/expected/pathman_rowmarks_4.out
new file mode 100644
index 00000000..5fbec84d
--- /dev/null
+++ b/expected/pathman_rowmarks_4.out
@@ -0,0 +1,407 @@
+/*
+ * -------------------------------------------
+ *  NOTE: This test behaves differenly on PgPro
+ * -------------------------------------------
+ *
+ * ------------------------
+ *  pathman_rowmarks_1.sql
+ * ------------------------
+ * Since PostgreSQL 9.5, output of EXPLAIN was changed.
+ *
+ * ------------------------
+ *  pathman_rowmarks_2.sql
+ * ------------------------
+ * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are
+ * eliminated, causing different output.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
+ */
+SET search_path = 'public';
+CREATE EXTENSION pg_pathman;
+CREATE SCHEMA rowmarks;
+CREATE TABLE rowmarks.first(id int NOT NULL);
+CREATE TABLE rowmarks.second(id int NOT NULL);
+INSERT INTO rowmarks.first SELECT generate_series(1, 10);
+INSERT INTO rowmarks.second SELECT generate_series(1, 10);
+SELECT create_hash_partitions('rowmarks.first', 'id', 5);
+ create_hash_partitions 
+------------------------
+                      5
+(1 row)
+
+VACUUM ANALYZE;
+/* Not partitioned */
+SELECT * FROM rowmarks.second ORDER BY id FOR UPDATE;
+ id 
+----
+  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+(10 rows)
+
+/* Simple case (plan) */
+EXPLAIN (COSTS OFF)
+SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE;
+                  QUERY PLAN                   
+-----------------------------------------------
+ LockRows
+   ->  Sort
+         Sort Key: first.id
+         ->  Append
+               ->  Seq Scan on first_0 first_1
+               ->  Seq Scan on first_1 first_2
+               ->  Seq Scan on first_2 first_3
+               ->  Seq Scan on first_3 first_4
+               ->  Seq Scan on first_4 first_5
+(9 rows)
+
+/* Simple case (execution) */
+SELECT * FROM rowmarks.first ORDER BY id FOR UPDATE;
+ id 
+----
+  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+(10 rows)
+
+SELECT FROM rowmarks.first ORDER BY id FOR UPDATE;
+--
+(10 rows)
+
+SELECT tableoid > 0 FROM rowmarks.first ORDER BY id FOR UPDATE;
+ ?column? 
+----------
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+(10 rows)
+
+/* A little harder (plan) */
+EXPLAIN (COSTS OFF)
+SELECT * FROM rowmarks.first
+WHERE id = (SELECT id FROM rowmarks.first
+			ORDER BY id
+			OFFSET 10 LIMIT 1
+			FOR UPDATE)
+FOR SHARE;
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ LockRows
+   InitPlan 1
+     ->  Limit
+           ->  LockRows
+                 ->  Sort
+                       Sort Key: first_1.id
+                       ->  Append
+                             ->  Seq Scan on first_0 first_2
+                             ->  Seq Scan on first_1 first_3
+                             ->  Seq Scan on first_2 first_4
+                             ->  Seq Scan on first_3 first_5
+                             ->  Seq Scan on first_4 first_6
+   ->  Custom Scan (RuntimeAppend)
+         Prune by: (first.id = (InitPlan 1).col1)
+         ->  Seq Scan on first_0 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_1 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_2 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_3 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_4 first
+               Filter: (id = (InitPlan 1).col1)
+(24 rows)
+
+/* A little harder (execution) */
+SELECT * FROM rowmarks.first
+WHERE id = (SELECT id FROM rowmarks.first
+			ORDER BY id
+			OFFSET 5 LIMIT 1
+			FOR UPDATE)
+FOR SHARE;
+ id 
+----
+  6
+(1 row)
+
+/* Two tables (plan) */
+EXPLAIN (COSTS OFF)
+SELECT * FROM rowmarks.first
+WHERE id = (SELECT id FROM rowmarks.second
+			ORDER BY id
+			OFFSET 5 LIMIT 1
+			FOR UPDATE)
+FOR SHARE;
+                    QUERY PLAN                    
+--------------------------------------------------
+ LockRows
+   InitPlan 1
+     ->  Limit
+           ->  LockRows
+                 ->  Sort
+                       Sort Key: second.id
+                       ->  Seq Scan on second
+   ->  Custom Scan (RuntimeAppend)
+         Prune by: (first.id = (InitPlan 1).col1)
+         ->  Seq Scan on first_0 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_1 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_2 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_3 first
+               Filter: (id = (InitPlan 1).col1)
+         ->  Seq Scan on first_4 first
+               Filter: (id = (InitPlan 1).col1)
+(19 rows)
+
+/* Two tables (execution) */
+SELECT * FROM rowmarks.first
+WHERE id = (SELECT id FROM rowmarks.second
+			ORDER BY id
+			OFFSET 5 LIMIT 1
+			FOR UPDATE)
+FOR SHARE;
+ id 
+----
+  6
+(1 row)
+
+/* JOIN (plan) */
+EXPLAIN (COSTS OFF)
+SELECT * FROM rowmarks.first
+JOIN rowmarks.second USING(id)
+ORDER BY id
+FOR UPDATE;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ LockRows
+   ->  Sort
+         Sort Key: first.id
+         ->  Hash Join
+               Hash Cond: (first.id = second.id)
+               ->  Append
+                     ->  Seq Scan on first_0 first_1
+                     ->  Seq Scan on first_1 first_2
+                     ->  Seq Scan on first_2 first_3
+                     ->  Seq Scan on first_3 first_4
+                     ->  Seq Scan on first_4 first_5
+               ->  Hash
+                     ->  Seq Scan on second
+(13 rows)
+
+/* JOIN (execution) */
+SELECT * FROM rowmarks.first
+JOIN rowmarks.second USING(id)
+ORDER BY id
+FOR UPDATE;
+ id 
+----
+  1
+  2
+  3
+  4
+  5
+  6
+  7
+  8
+  9
+ 10
+(10 rows)
+
+/* ONLY (plan) */
+EXPLAIN (COSTS OFF)
+SELECT * FROM ONLY rowmarks.first FOR SHARE;
+       QUERY PLAN        
+-------------------------
+ LockRows
+   ->  Seq Scan on first
+(2 rows)
+
+/* ONLY (execution) */
+SELECT * FROM ONLY rowmarks.first FOR SHARE;
+ id 
+----
+(0 rows)
+
+/* Check updates (plan) */
+SET enable_hashjoin = f;	/* Hash Semi Join on 10 vs Hash Join on 9.6 */
+SET enable_mergejoin = f;	/* Merge Semi Join on 10 vs Merge Join on 9.6 */
+EXPLAIN (COSTS OFF)
+UPDATE rowmarks.second SET id = 2
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1);
+              QUERY PLAN               
+---------------------------------------
+ Update on second
+   ->  Nested Loop Semi Join
+         ->  Seq Scan on second
+               Filter: (id = 1)
+         ->  Seq Scan on first_0 first
+               Filter: (id = 1)
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+UPDATE rowmarks.second SET id = 2
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Update on second
+   ->  Nested Loop Semi Join
+         Join Filter: (second.id = first.id)
+         ->  Seq Scan on second
+         ->  Materialize
+               ->  Append
+                     ->  Seq Scan on first_0 first_1
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_1 first_2
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_2 first_3
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_3 first_4
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_4 first_5
+                           Filter: (id < 1)
+(16 rows)
+
+EXPLAIN (COSTS OFF)
+UPDATE rowmarks.second SET id = 2
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Update on second
+   ->  Nested Loop Semi Join
+         Join Filter: (second.id = first.id)
+         ->  Seq Scan on second
+         ->  Materialize
+               ->  Append
+                     ->  Seq Scan on first_0 first_1
+                           Filter: (id = 1)
+                     ->  Seq Scan on first_1 first_2
+                           Filter: (id = 2)
+(10 rows)
+
+EXPLAIN (COSTS OFF)
+UPDATE rowmarks.second SET id = 2
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1)
+RETURNING *, tableoid::regclass;
+              QUERY PLAN               
+---------------------------------------
+ Update on second
+   ->  Nested Loop Semi Join
+         ->  Seq Scan on second
+               Filter: (id = 1)
+         ->  Seq Scan on first_0 first
+               Filter: (id = 1)
+(6 rows)
+
+SET enable_hashjoin = t;
+SET enable_mergejoin = t;
+/* Check updates (execution) */
+UPDATE rowmarks.second SET id = 1
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2)
+RETURNING *, tableoid::regclass;
+ id |    tableoid     
+----+-----------------
+  1 | rowmarks.second
+  1 | rowmarks.second
+(2 rows)
+
+/* Check deletes (plan) */
+SET enable_hashjoin = f;	/* Hash Semi Join on 10 vs Hash Join on 9.6 */
+SET enable_mergejoin = f;	/* Merge Semi Join on 10 vs Merge Join on 9.6 */
+EXPLAIN (COSTS OFF)
+DELETE FROM rowmarks.second
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1);
+              QUERY PLAN               
+---------------------------------------
+ Delete on second
+   ->  Nested Loop Semi Join
+         ->  Seq Scan on second
+               Filter: (id = 1)
+         ->  Seq Scan on first_0 first
+               Filter: (id = 1)
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+DELETE FROM rowmarks.second
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id < 1);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Delete on second
+   ->  Nested Loop Semi Join
+         Join Filter: (second.id = first.id)
+         ->  Seq Scan on second
+         ->  Materialize
+               ->  Append
+                     ->  Seq Scan on first_0 first_1
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_1 first_2
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_2 first_3
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_3 first_4
+                           Filter: (id < 1)
+                     ->  Seq Scan on first_4 first_5
+                           Filter: (id < 1)
+(16 rows)
+
+EXPLAIN (COSTS OFF)
+DELETE FROM rowmarks.second
+WHERE rowmarks.second.id IN (SELECT id FROM rowmarks.first WHERE id = 1 OR id = 2);
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Delete on second
+   ->  Nested Loop Semi Join
+         Join Filter: (second.id = first.id)
+         ->  Seq Scan on second
+         ->  Materialize
+               ->  Append
+                     ->  Seq Scan on first_0 first_1
+                           Filter: (id = 1)
+                     ->  Seq Scan on first_1 first_2
+                           Filter: (id = 2)
+(10 rows)
+
+SET enable_hashjoin = t;
+SET enable_mergejoin = t;
+DROP TABLE rowmarks.first CASCADE;
+NOTICE:  drop cascades to 5 other objects
+DETAIL:  drop cascades to table rowmarks.first_0
+drop cascades to table rowmarks.first_1
+drop cascades to table rowmarks.first_2
+drop cascades to table rowmarks.first_3
+drop cascades to table rowmarks.first_4
+DROP TABLE rowmarks.second CASCADE;
+DROP SCHEMA rowmarks;
+DROP EXTENSION pg_pathman;
diff --git a/sql/pathman_only.sql b/sql/pathman_only.sql
index 88f4e88a..68dc4ca1 100644
--- a/sql/pathman_only.sql
+++ b/sql/pathman_only.sql
@@ -3,13 +3,31 @@
  *  NOTE: This test behaves differenly on PgPro
  * ---------------------------------------------
  *
- * Since 12 (608b167f9f), CTEs which are scanned once are no longer an
- * optimization fence, which changes practically all plans here. There is
+ * --------------------
+ *  pathman_only_1.sql
+ * --------------------
+ * Since 608b167f9f in PostgreSQL 12, CTEs which are scanned once are no longer
+ * an optimization fence, which changes practically all plans here. There is
  * an option to forcibly make them MATERIALIZED, but we also need to run tests
  * on older versions, so create pathman_only_1.out instead.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * --------------------
+ *  pathman_only_2.sql
+ * --------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13, output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * --------------------
+ *  pathman_only_3.sql
+ * --------------------
+ * Since a5fc46414de in PostgreSQL 16, the order of the operands was changed,
+ * which affected the output of the "Prune by" in EXPLAIN.
+ *
+ * --------------------
+ *  pathman_only_4.sql
+ * --------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 
 \set VERBOSITY terse
diff --git a/sql/pathman_rowmarks.sql b/sql/pathman_rowmarks.sql
index bb7719ea..8847b80c 100644
--- a/sql/pathman_rowmarks.sql
+++ b/sql/pathman_rowmarks.sql
@@ -1,13 +1,30 @@
 /*
  * -------------------------------------------
- *  NOTE: This test behaves differenly on 9.5
+ *  NOTE: This test behaves differenly on PgPro
  * -------------------------------------------
  *
- * Also since 8edd0e794 (>= 12) Append nodes with single subplan are eliminated,
- * causing different output; pathman_rowmarks_2.out is the updated version.
+ * ------------------------
+ *  pathman_rowmarks_1.sql
+ * ------------------------
+ * Since PostgreSQL 9.5, output of EXPLAIN was changed.
  *
- * Since 55a1954da16 and 6ef77cf46e8 (>= 13) output of EXPLAIN was changed,
- * now it includes aliases for inherited tables.
+ * ------------------------
+ *  pathman_rowmarks_2.sql
+ * ------------------------
+ * Since 8edd0e794 in PostgreSQL 12, append nodes with single subplan are
+ * eliminated, causing different output.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since 55a1954da16 and 6ef77cf46e8 in PostgreSQL 13 output of EXPLAIN was
+ * changed, now it includes aliases for inherited tables.
+ *
+ * ------------------------
+ *  pathman_rowmarks_3.sql
+ * ------------------------
+ * Since fd0398fcb09 in PostgreSQL 17, output of EXPLAIN was
+ * changed, now it displays SubPlan nodes and output parameters.
  */
 SET search_path = 'public';
 CREATE EXTENSION pg_pathman;
diff --git a/src/pl_funcs.c b/src/pl_funcs.c
index 10538bea..75c1c12a 100644
--- a/src/pl_funcs.c
+++ b/src/pl_funcs.c
@@ -174,7 +174,12 @@ get_partition_cooked_key_pl(PG_FUNCTION_ARGS)
 
 	expr_cstr = TextDatumGetCString(values[Anum_pathman_config_expr - 1]);
 	expr = cook_partitioning_expression(relid, expr_cstr, NULL);
+
+#if PG_VERSION_NUM >= 170000 /* for commit d20d8fbd3e4d */
+	cooked_cstr = nodeToStringWithLocations(expr);
+#else
 	cooked_cstr = nodeToString(expr);
+#endif
 
 	pfree(expr_cstr);
 	pfree(expr);
@@ -196,7 +201,13 @@ get_cached_partition_cooked_key_pl(PG_FUNCTION_ARGS)
 
 	prel = get_pathman_relation_info(relid);
 	shout_if_prel_is_invalid(relid, prel, PT_ANY);
+
+#if PG_VERSION_NUM >= 170000 /* for commit d20d8fbd3e4d */
+	res = CStringGetTextDatum(nodeToStringWithLocations(prel->expr));
+#else
 	res = CStringGetTextDatum(nodeToString(prel->expr));
+#endif
+
 	close_pathman_relation_info(prel);
 
 	PG_RETURN_DATUM(res);
diff --git a/src/relation_info.c b/src/relation_info.c
index db75646f..2794a183 100644
--- a/src/relation_info.c
+++ b/src/relation_info.c
@@ -1491,7 +1491,8 @@ parse_partitioning_expression(const Oid relid,
 	return ((ResTarget *) linitial(select_stmt->targetList))->val;
 }
 
-/* Parse partitioning expression and return its type and nodeToString() as TEXT */
+/* Parse partitioning expression and return its type and nodeToString()
+ * (or nodeToStringWithLocations() in version 17 and higher) as TEXT */
 Node *
 cook_partitioning_expression(const Oid relid,
 							 const char *expr_cstr,