diff --git a/src/backend/distributed/planner/distributed_planner.c b/src/backend/distributed/planner/distributed_planner.c index 193e2f250..d381d8768 100644 --- a/src/backend/distributed/planner/distributed_planner.c +++ b/src/backend/distributed/planner/distributed_planner.c @@ -2002,6 +2002,8 @@ multi_relation_restriction_hook(PlannerInfo *root, RelOptInfo *relOptInfo, relationRestriction->outerPlanParamsList = OuterPlanParamsList(root); relationRestriction->translatedVars = TranslatedVars(root, relationRestriction->index); + plannerRestrictionContext->relationRestrictionContext->hasPseudoConstantQuals = + root->hasPseudoConstantQuals; RelationRestrictionContext *relationRestrictionContext = plannerRestrictionContext->relationRestrictionContext; diff --git a/src/backend/distributed/planner/query_pushdown_planning.c b/src/backend/distributed/planner/query_pushdown_planning.c index 5317e578d..6150f4987 100644 --- a/src/backend/distributed/planner/query_pushdown_planning.c +++ b/src/backend/distributed/planner/query_pushdown_planning.c @@ -84,7 +84,6 @@ int ValuesMaterializationThreshold = 100; /* Local functions forward declarations */ static bool JoinTreeContainsSubqueryWalker(Node *joinTreeNode, void *context); static bool IsFunctionOrValuesRTE(Node *node); -static bool IsOuterJoinExpr(Node *node); static bool WindowPartitionOnDistributionColumn(Query *query); static DeferredErrorMessage * DeferErrorIfFromClauseRecurs(Query *queryTree); static RecurringTuplesType FromClauseRecurringTupleType(Query *queryTree); @@ -394,7 +393,7 @@ IsNodeSubquery(Node *node) /* * IsOuterJoinExpr returns whether the given node is an outer join expression. */ -static bool +bool IsOuterJoinExpr(Node *node) { bool isOuterJoin = false; diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index d65a64410..59b6ffaa0 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -137,7 +137,8 @@ static bool ShouldRecursivelyPlanNonColocatedSubqueries(Query *subquery, RecursivePlanningContext * context); static bool ContainsSubquery(Query *query); -static bool ShouldRecursivelyPlanOuterJoins(RecursivePlanningContext *context); +static bool ShouldRecursivelyPlanOuterJoins(Query *query, + RecursivePlanningContext *context); static void RecursivelyPlanNonColocatedSubqueries(Query *subquery, RecursivePlanningContext *context); static void RecursivelyPlanNonColocatedJoinWalker(Node *joinNode, @@ -355,7 +356,7 @@ RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context * result and logical planner can handle the new query since it's of the from * " LEFT JOIN ". */ - if (ShouldRecursivelyPlanOuterJoins(context)) + if (ShouldRecursivelyPlanOuterJoins(query, context)) { RecursivelyPlanRecurringTupleOuterJoinWalker((Node *) query->jointree, query, context); @@ -468,7 +469,7 @@ ContainsSubquery(Query *query) * join(s) that might need to be recursively planned. */ static bool -ShouldRecursivelyPlanOuterJoins(RecursivePlanningContext *context) +ShouldRecursivelyPlanOuterJoins(Query *query, RecursivePlanningContext *context) { if (!context || !context->plannerRestrictionContext || !context->plannerRestrictionContext->joinRestrictionContext) @@ -477,7 +478,39 @@ ShouldRecursivelyPlanOuterJoins(RecursivePlanningContext *context) "planning context"))); } - return context->plannerRestrictionContext->joinRestrictionContext->hasOuterJoin; + bool hasOuterJoin = + context->plannerRestrictionContext->joinRestrictionContext->hasOuterJoin; +#if PG_VERSION_NUM < PG_VERSION_17 + bool hasPseudoConstantQuals = + context->plannerRestrictionContext->relationRestrictionContext-> + hasPseudoConstantQuals; + + /* + * PG15 commit d1ef5631e620f9a5b6480a32bb70124c857af4f1 + * PG16 commit 695f5deb7902865901eb2d50a70523af655c3a00 + * disallows replacing joins with scans in queries with pseudoconstant quals. + * This commit prevents the set_join_pathlist_hook from being called + * if any of the join restrictions is a pseudo-constant. + * So in these cases, citus has no info on the join, never sees that the query + * has an outer join, and ends up producing an incorrect plan. + * PG17 fixes this by commit 9e9931d2bf40e2fea447d779c2e133c2c1256ef3 + * Therefore, we take this extra measure here for PG versions less than 17. + */ + if (hasPseudoConstantQuals && !hasOuterJoin) + { + if (FindNodeMatchingCheckFunction((Node *) query->jointree, + IsOuterJoinExpr)) + { + ereport(ERROR, (errmsg("Distributed queries with outer joins and " + "pseudoconstant quals are not supported in PG15 and PG16."), + errdetail( + "PG15 and PG16 disallow replacing joins with scans when the" + " query has pseudoconstant quals"), + errhint("Consider upgrading your PG version to PG17+"))); + } + } +#endif + return hasOuterJoin; } diff --git a/src/include/distributed/distributed_planner.h b/src/include/distributed/distributed_planner.h index 33a9c2fa8..04e98e258 100644 --- a/src/include/distributed/distributed_planner.h +++ b/src/include/distributed/distributed_planner.h @@ -36,6 +36,7 @@ extern int PlannerLevel; typedef struct RelationRestrictionContext { bool allReferenceTables; + bool hasPseudoConstantQuals; List *relationRestrictionList; } RelationRestrictionContext; diff --git a/src/include/distributed/query_pushdown_planning.h b/src/include/distributed/query_pushdown_planning.h index 47a34cee0..7035f5fc2 100644 --- a/src/include/distributed/query_pushdown_planning.h +++ b/src/include/distributed/query_pushdown_planning.h @@ -47,6 +47,7 @@ extern DeferredErrorMessage * DeferErrorIfCannotPushdownSubquery(Query *subquery outerMostQueryHasLimit); extern DeferredErrorMessage * DeferErrorIfUnsupportedUnionQuery(Query *queryTree); extern bool IsJsonTableRTE(RangeTblEntry *rte); +extern bool IsOuterJoinExpr(Node *node); #endif /* QUERY_PUSHDOWN_PLANNING_H */ diff --git a/src/test/regress/expected/pg17.out b/src/test/regress/expected/pg17.out index edda22f26..721087d3d 100644 --- a/src/test/regress/expected/pg17.out +++ b/src/test/regress/expected/pg17.out @@ -343,6 +343,165 @@ NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table pg17_corr_subq_folding.test drop cascades to table pg17_corr_subq_folding.users drop cascades to table pg17_corr_subq_folding.events +-- Queries with outer joins with pseudoconstant quals work only in PG17 +-- Relevant PG17 commit: +-- https://github.com/postgres/postgres/commit/9e9931d2b +CREATE SCHEMA pg17_outerjoin; +SET search_path to pg17_outerjoin, public; +SET citus.next_shard_id TO 20250321; +-- issue https://github.com/citusdata/citus/issues/7697 +create table t0 (vkey int4 , c3 timestamp); +create table t3 ( vkey int4 ,c26 timestamp); +create table t4 ( vkey int4 ); +insert into t0 (vkey, c3) values (13,make_timestamp(2019, 10, 23, 15, 34, 50)); +insert into t3 (vkey,c26) values (1, make_timestamp(2024, 3, 26, 17, 36, 53)); +insert into t4 (vkey) values (1); +select * from + (t0 full outer join t3 + on (t0.c3 = t3.c26 )) +where (exists (select * from t4)) order by 1, 2, 3; + vkey | c3 | vkey | c26 +--------------------------------------------------------------------- + 13 | Wed Oct 23 15:34:50 2019 | | + | | 1 | Tue Mar 26 17:36:53 2024 +(2 rows) + +SELECT create_distributed_table('t0', 'vkey'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg17_outerjoin.t0$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +select * from + (t0 full outer join t3 + on (t0.c3 = t3.c26 )) +where (exists (select * from t4)) order by 1, 2, 3; + vkey | c3 | vkey | c26 +--------------------------------------------------------------------- + 13 | Wed Oct 23 15:34:50 2019 | | + | | 1 | Tue Mar 26 17:36:53 2024 +(2 rows) + +-- issue https://github.com/citusdata/citus/issues/7696 +create table t1 ( vkey int4 ); +create table t2 ( vkey int4 ); +insert into t2 (vkey) values (5); +select * from (t2 full outer join t1 on(t2.vkey = t1.vkey )) +where not((85) in (select 1 from t2)); + vkey | vkey +--------------------------------------------------------------------- + 5 | +(1 row) + +SELECT create_distributed_table('t1', 'vkey'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('t2'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg17_outerjoin.t2$$) + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +select * from (t2 full outer join t1 on(t2.vkey = t1.vkey )) +where not((85) in (select 1 from t2)); + vkey | vkey +--------------------------------------------------------------------- + 5 | +(1 row) + +-- issue https://github.com/citusdata/citus/issues/7698 +create table t5 ( vkey int4, c10 int4 ); +create table t6 ( vkey int4 ); +insert into t5 (vkey,c10) values (4, -70); +insert into t6 (vkey) values (1); +select t6.vkey +from (t5 right outer join t6 + on (t5.c10 = t6.vkey)) +where exists (select * from t6); + vkey +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT create_distributed_table('t5', 'vkey'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg17_outerjoin.t5$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +select t6.vkey +from (t5 right outer join t6 + on (t5.c10 = t6.vkey)) +where exists (select * from t6); + vkey +--------------------------------------------------------------------- + 1 +(1 row) + +-- issue https://github.com/citusdata/citus/issues/7119 +-- this test was removed in +-- https://github.com/citusdata/citus/commit/a5ce601c0 +-- Citus doesn't support it in PG15 and PG16, but supports it in PG17 +CREATE TABLE users_table_local AS SELECT * FROM users_table; +CREATE TABLE events_table_local AS SELECT * FROM events_table; +SET client_min_messages TO DEBUG1; +-- subquery in FROM -> FROM -> WHERE -> WHERE should be replaced if +-- it contains onle local tables +-- Later the upper level query is also recursively planned due to LIMIT +SELECT user_id, array_length(events_table, 1) +FROM ( + SELECT user_id, array_agg(event ORDER BY time) AS events_table + FROM ( + SELECT + u.user_id, e.event_type::text AS event, e.time + FROM + users_table AS u, + events_table AS e + WHERE u.user_id = e.user_id AND + u.user_id IN + ( + SELECT + user_id + FROM + users_table + WHERE value_2 >= 5 + AND EXISTS (SELECT user_id FROM events_table_local WHERE event_type > 1 AND event_type <= 3 AND value_3 > 1) + AND NOT EXISTS (SELECT user_id FROM events_table WHERE event_type > 3 AND event_type <= 4 AND value_3 > 1 AND user_id = users_table.user_id) + LIMIT 5 + ) + ) t + GROUP BY user_id +) q +ORDER BY 2 DESC, 1; +DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM pg17_outerjoin.events_table_local WHERE ((event_type OPERATOR(pg_catalog.>) 1) AND (event_type OPERATOR(pg_catalog.<=) 3) AND (value_3 OPERATOR(pg_catalog.>) (1)::double precision)) +DEBUG: push down of limit count: 5 +DEBUG: generating subplan XXX_2 for subquery SELECT user_id FROM public.users_table WHERE ((value_2 OPERATOR(pg_catalog.>=) 5) AND (EXISTS (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer))) AND (NOT (EXISTS (SELECT events_table.user_id FROM public.events_table WHERE ((events_table.event_type OPERATOR(pg_catalog.>) 3) AND (events_table.event_type OPERATOR(pg_catalog.<=) 4) AND (events_table.value_3 OPERATOR(pg_catalog.>) (1)::double precision) AND (events_table.user_id OPERATOR(pg_catalog.=) users_table.user_id)))))) LIMIT 5 +DEBUG: Plan XXX query after replacing subqueries and CTEs: SELECT user_id, array_length(events_table, 1) AS array_length FROM (SELECT t.user_id, array_agg(t.event ORDER BY t."time") AS events_table FROM (SELECT u.user_id, (e.event_type)::text AS event, e."time" FROM public.users_table u, public.events_table e WHERE ((u.user_id OPERATOR(pg_catalog.=) e.user_id) AND (u.user_id OPERATOR(pg_catalog.=) ANY (SELECT intermediate_result.user_id FROM read_intermediate_result('XXX_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer))))) t GROUP BY t.user_id) q ORDER BY (array_length(events_table, 1)) DESC, user_id + user_id | array_length +--------------------------------------------------------------------- + 5 | 364 +(1 row) + +RESET search_path; +SET citus.next_shard_id TO 20240023; +SET client_min_messages TO ERROR; +DROP SCHEMA pg17_outerjoin CASCADE; +RESET client_min_messages; \if :server_version_ge_17 \else \q diff --git a/src/test/regress/expected/pg17_0.out b/src/test/regress/expected/pg17_0.out index 09db03e4c..6f65f6099 100644 --- a/src/test/regress/expected/pg17_0.out +++ b/src/test/regress/expected/pg17_0.out @@ -287,6 +287,151 @@ NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table pg17_corr_subq_folding.test drop cascades to table pg17_corr_subq_folding.users drop cascades to table pg17_corr_subq_folding.events +-- Queries with outer joins with pseudoconstant quals work only in PG17 +-- Relevant PG17 commit: +-- https://github.com/postgres/postgres/commit/9e9931d2b +CREATE SCHEMA pg17_outerjoin; +SET search_path to pg17_outerjoin, public; +SET citus.next_shard_id TO 20250321; +-- issue https://github.com/citusdata/citus/issues/7697 +create table t0 (vkey int4 , c3 timestamp); +create table t3 ( vkey int4 ,c26 timestamp); +create table t4 ( vkey int4 ); +insert into t0 (vkey, c3) values (13,make_timestamp(2019, 10, 23, 15, 34, 50)); +insert into t3 (vkey,c26) values (1, make_timestamp(2024, 3, 26, 17, 36, 53)); +insert into t4 (vkey) values (1); +select * from + (t0 full outer join t3 + on (t0.c3 = t3.c26 )) +where (exists (select * from t4)) order by 1, 2, 3; + vkey | c3 | vkey | c26 +--------------------------------------------------------------------- + 13 | Wed Oct 23 15:34:50 2019 | | + | | 1 | Tue Mar 26 17:36:53 2024 +(2 rows) + +SELECT create_distributed_table('t0', 'vkey'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg17_outerjoin.t0$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +select * from + (t0 full outer join t3 + on (t0.c3 = t3.c26 )) +where (exists (select * from t4)) order by 1, 2, 3; +ERROR: Distributed queries with outer joins and pseudoconstant quals are not supported in PG15 and PG16. +DETAIL: PG15 and PG16 disallow replacing joins with scans when the query has pseudoconstant quals +HINT: Consider upgrading your PG version to PG17+ +-- issue https://github.com/citusdata/citus/issues/7696 +create table t1 ( vkey int4 ); +create table t2 ( vkey int4 ); +insert into t2 (vkey) values (5); +select * from (t2 full outer join t1 on(t2.vkey = t1.vkey )) +where not((85) in (select 1 from t2)); + vkey | vkey +--------------------------------------------------------------------- + 5 | +(1 row) + +SELECT create_distributed_table('t1', 'vkey'); + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +SELECT create_reference_table('t2'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg17_outerjoin.t2$$) + create_reference_table +--------------------------------------------------------------------- + +(1 row) + +select * from (t2 full outer join t1 on(t2.vkey = t1.vkey )) +where not((85) in (select 1 from t2)); +ERROR: Distributed queries with outer joins and pseudoconstant quals are not supported in PG15 and PG16. +DETAIL: PG15 and PG16 disallow replacing joins with scans when the query has pseudoconstant quals +HINT: Consider upgrading your PG version to PG17+ +-- issue https://github.com/citusdata/citus/issues/7698 +create table t5 ( vkey int4, c10 int4 ); +create table t6 ( vkey int4 ); +insert into t5 (vkey,c10) values (4, -70); +insert into t6 (vkey) values (1); +select t6.vkey +from (t5 right outer join t6 + on (t5.c10 = t6.vkey)) +where exists (select * from t6); + vkey +--------------------------------------------------------------------- + 1 +(1 row) + +SELECT create_distributed_table('t5', 'vkey'); +NOTICE: Copying data from local table... +NOTICE: copying the data has completed +DETAIL: The local data in the table is no longer visible, but is still on disk. +HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg17_outerjoin.t5$$) + create_distributed_table +--------------------------------------------------------------------- + +(1 row) + +select t6.vkey +from (t5 right outer join t6 + on (t5.c10 = t6.vkey)) +where exists (select * from t6); +ERROR: Distributed queries with outer joins and pseudoconstant quals are not supported in PG15 and PG16. +DETAIL: PG15 and PG16 disallow replacing joins with scans when the query has pseudoconstant quals +HINT: Consider upgrading your PG version to PG17+ +-- issue https://github.com/citusdata/citus/issues/7119 +-- this test was removed in +-- https://github.com/citusdata/citus/commit/a5ce601c0 +-- Citus doesn't support it in PG15 and PG16, but supports it in PG17 +CREATE TABLE users_table_local AS SELECT * FROM users_table; +CREATE TABLE events_table_local AS SELECT * FROM events_table; +SET client_min_messages TO DEBUG1; +-- subquery in FROM -> FROM -> WHERE -> WHERE should be replaced if +-- it contains onle local tables +-- Later the upper level query is also recursively planned due to LIMIT +SELECT user_id, array_length(events_table, 1) +FROM ( + SELECT user_id, array_agg(event ORDER BY time) AS events_table + FROM ( + SELECT + u.user_id, e.event_type::text AS event, e.time + FROM + users_table AS u, + events_table AS e + WHERE u.user_id = e.user_id AND + u.user_id IN + ( + SELECT + user_id + FROM + users_table + WHERE value_2 >= 5 + AND EXISTS (SELECT user_id FROM events_table_local WHERE event_type > 1 AND event_type <= 3 AND value_3 > 1) + AND NOT EXISTS (SELECT user_id FROM events_table WHERE event_type > 3 AND event_type <= 4 AND value_3 > 1 AND user_id = users_table.user_id) + LIMIT 5 + ) + ) t + GROUP BY user_id +) q +ORDER BY 2 DESC, 1; +DEBUG: generating subplan XXX_1 for subquery SELECT user_id FROM pg17_outerjoin.events_table_local WHERE ((event_type OPERATOR(pg_catalog.>) 1) AND (event_type OPERATOR(pg_catalog.<=) 3) AND (value_3 OPERATOR(pg_catalog.>) (1)::double precision)) +ERROR: complex joins are only supported when all distributed tables are co-located and joined on their distribution columns +RESET search_path; +SET citus.next_shard_id TO 20240023; +SET client_min_messages TO ERROR; +DROP SCHEMA pg17_outerjoin CASCADE; +RESET client_min_messages; \if :server_version_ge_17 \else \q diff --git a/src/test/regress/sql/pg17.sql b/src/test/regress/sql/pg17.sql index 72998fce0..60ea6d9e7 100644 --- a/src/test/regress/sql/pg17.sql +++ b/src/test/regress/sql/pg17.sql @@ -170,6 +170,109 @@ RESET client_min_messages; RESET search_path; DROP SCHEMA pg17_corr_subq_folding CASCADE; +-- Queries with outer joins with pseudoconstant quals work only in PG17 +-- Relevant PG17 commit: +-- https://github.com/postgres/postgres/commit/9e9931d2b + +CREATE SCHEMA pg17_outerjoin; +SET search_path to pg17_outerjoin, public; +SET citus.next_shard_id TO 20250321; + +-- issue https://github.com/citusdata/citus/issues/7697 +create table t0 (vkey int4 , c3 timestamp); +create table t3 ( vkey int4 ,c26 timestamp); +create table t4 ( vkey int4 ); +insert into t0 (vkey, c3) values (13,make_timestamp(2019, 10, 23, 15, 34, 50)); +insert into t3 (vkey,c26) values (1, make_timestamp(2024, 3, 26, 17, 36, 53)); +insert into t4 (vkey) values (1); + +select * from + (t0 full outer join t3 + on (t0.c3 = t3.c26 )) +where (exists (select * from t4)) order by 1, 2, 3; + +SELECT create_distributed_table('t0', 'vkey'); + +select * from + (t0 full outer join t3 + on (t0.c3 = t3.c26 )) +where (exists (select * from t4)) order by 1, 2, 3; + +-- issue https://github.com/citusdata/citus/issues/7696 +create table t1 ( vkey int4 ); +create table t2 ( vkey int4 ); +insert into t2 (vkey) values (5); + +select * from (t2 full outer join t1 on(t2.vkey = t1.vkey )) +where not((85) in (select 1 from t2)); + +SELECT create_distributed_table('t1', 'vkey'); +SELECT create_reference_table('t2'); + +select * from (t2 full outer join t1 on(t2.vkey = t1.vkey )) +where not((85) in (select 1 from t2)); + +-- issue https://github.com/citusdata/citus/issues/7698 +create table t5 ( vkey int4, c10 int4 ); +create table t6 ( vkey int4 ); +insert into t5 (vkey,c10) values (4, -70); +insert into t6 (vkey) values (1); + +select t6.vkey +from (t5 right outer join t6 + on (t5.c10 = t6.vkey)) +where exists (select * from t6); + +SELECT create_distributed_table('t5', 'vkey'); + +select t6.vkey +from (t5 right outer join t6 + on (t5.c10 = t6.vkey)) +where exists (select * from t6); + +-- issue https://github.com/citusdata/citus/issues/7119 +-- this test was removed in +-- https://github.com/citusdata/citus/commit/a5ce601c0 +-- Citus doesn't support it in PG15 and PG16, but supports it in PG17 +CREATE TABLE users_table_local AS SELECT * FROM users_table; +CREATE TABLE events_table_local AS SELECT * FROM events_table; + +SET client_min_messages TO DEBUG1; +-- subquery in FROM -> FROM -> WHERE -> WHERE should be replaced if +-- it contains onle local tables +-- Later the upper level query is also recursively planned due to LIMIT +SELECT user_id, array_length(events_table, 1) +FROM ( + SELECT user_id, array_agg(event ORDER BY time) AS events_table + FROM ( + SELECT + u.user_id, e.event_type::text AS event, e.time + FROM + users_table AS u, + events_table AS e + WHERE u.user_id = e.user_id AND + u.user_id IN + ( + SELECT + user_id + FROM + users_table + WHERE value_2 >= 5 + AND EXISTS (SELECT user_id FROM events_table_local WHERE event_type > 1 AND event_type <= 3 AND value_3 > 1) + AND NOT EXISTS (SELECT user_id FROM events_table WHERE event_type > 3 AND event_type <= 4 AND value_3 > 1 AND user_id = users_table.user_id) + LIMIT 5 + ) + ) t + GROUP BY user_id +) q +ORDER BY 2 DESC, 1; + +RESET search_path; +SET citus.next_shard_id TO 20240023; +SET client_min_messages TO ERROR; +DROP SCHEMA pg17_outerjoin CASCADE; +RESET client_min_messages; + \if :server_version_ge_17 \else \q