From 1106e143858951dc4a5911f94e6d03f59775aef7 Mon Sep 17 00:00:00 2001 From: Hanefi Onaldi Date: Fri, 28 Dec 2018 12:46:18 +0300 Subject: [PATCH 01/20] Wrap functions in subqueries remove debug logs to fix travis tests Support RowType functions in joins Regression tests for a custom type function in join --- .../distributed/planner/recursive_planning.c | 188 ++++++++++++++- .../expected/multi_function_in_join.out | 227 ++++++++++++++++++ ...ulti_subquery_complex_reference_clause.out | 44 ++-- src/test/regress/expected/set_operations.out | 2 +- src/test/regress/multi_schedule | 2 +- .../regress/sql/multi_function_in_join.sql | 144 +++++++++++ 6 files changed, 580 insertions(+), 27 deletions(-) create mode 100644 src/test/regress/expected/multi_function_in_join.out create mode 100644 src/test/regress/sql/multi_function_in_join.sql diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index fc8c65746..389f5a68b 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -50,6 +50,7 @@ */ #include "postgres.h" +#include "funcapi.h" #include "catalog/pg_type.h" #include "catalog/pg_class.h" @@ -162,7 +163,9 @@ static bool CteReferenceListWalker(Node *node, CteReferenceWalkerContext *contex static bool ContainsReferencesToOuterQuery(Query *query); static bool ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *context); - +static void WrapFunctionsInQuery(Query *query); +static void TransformFunctionRTE(RangeTblEntry *rangeTblEntry); +static bool ShouldTransformRTE(RangeTblEntry *rangeTableEntry); /* * GenerateSubplansForSubqueriesAndCTEs is a wrapper around RecursivelyPlanSubqueriesAndCTEs. @@ -263,6 +266,9 @@ RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context return NULL; } + /* make sure function calls in joins are executed in the coordinator */ + WrapFunctionsInQuery(query); + /* descend into subqueries */ query_tree_walker(query, RecursivelyPlanSubqueryWalker, context, 0); @@ -1305,6 +1311,186 @@ ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *conte } +/* + * WrapFunctionsInQuery iterates over all the immediate Range Table Entries + * of a query and wraps the functions inside (SELECT * FROM fnc() f) subqueries. + * + * We currently wrap only those functions that return a single value. If a + * function returns records or a table we leave it as it is + * */ +static void +WrapFunctionsInQuery(Query *query) +{ + List *rangeTableList = query->rtable; + ListCell *rangeTableCell = NULL; + + /* there needs to be at least two RTEs for a join operation */ + if (list_length(rangeTableList) < 2) + { + return; + } + + foreach(rangeTableCell, rangeTableList) + { + RangeTblEntry *rangeTableEntry = (RangeTblEntry *) lfirst(rangeTableCell); + + if (ShouldTransformRTE(rangeTableEntry)) + { + TransformFunctionRTE(rangeTableEntry); + } + } +} + + +/* + * TransformFunctionRTE wraps a given function RangeTableEntry + * inside a (SELECT * from function() f) subquery. + * + * The said RangeTableEntry is modified and now points to the new subquery. + * */ +static void +TransformFunctionRTE(RangeTblEntry *rangeTblEntry) +{ + Query *subquery = makeNode(Query); + RangeTblRef *newRangeTableRef = makeNode(RangeTblRef); + RangeTblEntry *newRangeTableEntry = NULL; + Var *targetColumn = NULL; + TargetEntry *targetEntry = NULL; + RangeTblFunction *rangeTblFunction = NULL; + int targetColumnIndex = 0; + TupleDesc tupleDesc = NULL; + + rangeTblFunction = linitial(rangeTblEntry->functions); + + subquery->commandType = CMD_SELECT; + + /* copy the input rangeTblEntry to prevent cycles */ + newRangeTableEntry = copyObject(rangeTblEntry); + + /* set the FROM expression to the subquery */ + subquery->rtable = list_make1(newRangeTableEntry); + newRangeTableRef->rtindex = 1; + subquery->jointree = makeFromExpr(list_make1(newRangeTableRef), NULL); + + /* Determine the result type of the function. + * + * If function return type is not composite or rowtype can't be determined, + * tupleDesc is set to null here + */ + tupleDesc = (TupleDesc) get_expr_result_tupdesc(rangeTblFunction->funcexpr, + true); + + /* if tupleDesc is not null, we iterate over all the attributes and + * create targetEntries*/ + if (tupleDesc) + { + for (targetColumnIndex = 0; targetColumnIndex < tupleDesc->natts; + targetColumnIndex++) + { + FormData_pg_attribute *attribute = TupleDescAttr(tupleDesc, + targetColumnIndex); + Oid columnType = attribute->atttypid; + char *columnName = attribute->attname.data; + + /* + * The indexing of attributes and TupleDesc and varattno differ + * + * varattno=0 corresponds to whole row + * varattno=1 corrensponds to first column that is stored in tupDesc->attrs[0] */ + targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, InvalidOid, + 0); + targetEntry = makeTargetEntry((Expr *) targetColumn, targetColumnIndex + 1, + columnName, + false); + subquery->targetList = lappend(subquery->targetList, targetEntry); + } + } + /* + * If tupleDesc is NULL we have several cases: + * + * 1. The function returns a record but the attributes can not be + * determined before running the query. In this case the column names and + * types must be defined explicitly in the query + * + * 2. The function returns a non-composite type + * */ + else + { + /* create target entries for all columns returned by the function */ + List *functionColumnNames = NULL; + ListCell *functionColumnName = NULL; + + functionColumnNames = rangeTblEntry->eref->colnames; + foreach(functionColumnName, functionColumnNames) + { + char *columnName = strVal(lfirst(functionColumnName)); + Oid columnType = InvalidOid; + + /* use explicitly defined types in the query if they are available */ + if (list_length(rangeTblFunction->funccoltypes) > 0) + { + columnType = list_nth_oid(rangeTblFunction->funccoltypes, + targetColumnIndex); + } + /* use the types in the function definition otherwise */ + else + { + FuncExpr *funcExpr = (FuncExpr *) rangeTblFunction->funcexpr; + columnType = funcExpr->funcresulttype; + } + + targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, + InvalidOid, 0); + targetEntry = makeTargetEntry((Expr *) targetColumn, + targetColumnIndex + 1, columnName, + false); + subquery->targetList = lappend(subquery->targetList, targetEntry); + + targetColumnIndex++; + } + } + + /* replace the function with the constructed subquery */ + rangeTblEntry->rtekind = RTE_SUBQUERY; + rangeTblEntry->subquery = subquery; +} + + +/* + * ShouldTransformRTE determines whether a given RTE should bne wrapped in a + * subquery. + * + * Not all functions can be wrapped in a subquery for now. As we support more + * functions to be used in joins, the constraints here will be relaxed. + * */ +static bool +ShouldTransformRTE(RangeTblEntry *rangeTableEntry) +{ + /* wrap only function rtes */ + if (rangeTableEntry->rtekind != RTE_FUNCTION) + { + return false; + } + + /* + * TODO: remove this check once lateral joins are supported + * We do not support lateral joins on functions for now, as referencing + * columns of an outer query is quite tricky */ + if (rangeTableEntry->lateral) + { + return false; + } + + /* We do not want to wrap read-intermediate-result function calls */ + if (ContainsReadIntermediateResultFunction(linitial(rangeTableEntry->functions))) + { + return false; + } + + return true; +} + + /* * BuildSubPlanResultQuery returns a query of the form: * diff --git a/src/test/regress/expected/multi_function_in_join.out b/src/test/regress/expected/multi_function_in_join.out new file mode 100644 index 000000000..ef65aac82 --- /dev/null +++ b/src/test/regress/expected/multi_function_in_join.out @@ -0,0 +1,227 @@ +-- +-- multi function in join queries aims to test the function calls that are +-- used in joins. +-- +-- These functions are supposed to be executed on the worker and to ensure +-- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. +-- +-- We do not yet support those functions that: +-- - return records +-- - return tables +-- - are user-defined and immutable +CREATE SCHEMA functions_in_joins; +SET search_path TO 'functions_in_joins'; +SET citus.next_shard_id TO 2500000; +CREATE TABLE table1 (id int, data int); +SELECT create_distributed_table('table1','id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO table1 +SELECT x, x*x +from generate_series(1, 100) as f (x); +-- Verbose messages for observing the subqueries that wrapped function calls +SET client_min_messages TO DEBUG1; +-- Check joins on a sequence +CREATE SEQUENCE numbers; +SELECT * FROM table1 JOIN nextval('numbers') n ON (id = n) ORDER BY id ASC; +DEBUG: generating subplan 2_1 for subquery SELECT n FROM nextval('functions_in_joins.numbers'::regclass) n(n) +DEBUG: Plan 2 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, n.n FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.n FROM read_intermediate_result('2_1'::text, 'binary'::citus_copy_format) intermediate_result(n bigint)) n ON ((table1.id OPERATOR(pg_catalog.=) n.n))) ORDER BY table1.id + id | data | n +----+------+--- + 1 | 1 | 1 +(1 row) + +-- Check joins of a function that returns a single integer +CREATE FUNCTION add(integer, integer) RETURNS integer +AS 'SELECT $1 + $2;' +LANGUAGE SQL; +SELECT * FROM table1 JOIN add(3,5) sum ON (id = sum) ORDER BY id ASC; +DEBUG: generating subplan 3_1 for subquery SELECT sum FROM functions_in_joins.add(3, 5) sum(sum) +DEBUG: Plan 3 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, sum.sum FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.sum FROM read_intermediate_result('3_1'::text, 'binary'::citus_copy_format) intermediate_result(sum integer)) sum ON ((table1.id OPERATOR(pg_catalog.=) sum.sum))) ORDER BY table1.id + id | data | sum +----+------+----- + 8 | 64 | 8 +(1 row) + +-- Check join of plpgsql functions +-- a function returning a single integer +CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$ +BEGIN + RETURN i + 1; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM table1 JOIN increment(2) val ON (id = val) ORDER BY id ASC; +DEBUG: generating subplan 4_1 for subquery SELECT val FROM functions_in_joins.increment(2) val(val) +DEBUG: Plan 4 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, val.val FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.val FROM read_intermediate_result('4_1'::text, 'binary'::citus_copy_format) intermediate_result(val integer)) val ON ((table1.id OPERATOR(pg_catalog.=) val.val))) ORDER BY table1.id + id | data | val +----+------+----- + 3 | 9 | 3 +(1 row) + +-- a function that returns a set of integers +CREATE OR REPLACE FUNCTION next_k_integers(IN first_value INTEGER, + IN k INTEGER DEFAULT 3, + OUT result INTEGER) + RETURNS SETOF INTEGER AS $$ +BEGIN + RETURN QUERY SELECT x FROM generate_series(first_value, first_value+k-1) f(x); +END; +$$ LANGUAGE plpgsql; +SELECT * +FROM table1 JOIN next_k_integers(3,2) next_integers ON (id = next_integers.result) +ORDER BY id ASC; +DEBUG: generating subplan 5_1 for subquery SELECT result FROM functions_in_joins.next_k_integers(3, 2) next_integers(result) +DEBUG: Plan 5 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, next_integers.result FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.result FROM read_intermediate_result('5_1'::text, 'binary'::citus_copy_format) intermediate_result(result integer)) next_integers ON ((table1.id OPERATOR(pg_catalog.=) next_integers.result))) ORDER BY table1.id + id | data | result +----+------+-------- + 3 | 9 | 3 + 4 | 16 | 4 +(2 rows) + +-- a function returning set of records +CREATE FUNCTION get_set_of_records() RETURNS SETOF RECORD AS $cmd$ +SELECT x, x+1 FROM generate_series(0,4) f(x) +$cmd$ +LANGUAGE SQL; +SELECT * FROM table1 JOIN get_set_of_records() AS t2(x int, y int) ON (id = x) ORDER BY id ASC; +DEBUG: generating subplan 6_1 for subquery SELECT x, y FROM functions_in_joins.get_set_of_records() t2(x integer, y integer) +DEBUG: Plan 6 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, t2.x, t2.y FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('6_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) t2 ON ((table1.id OPERATOR(pg_catalog.=) t2.x))) ORDER BY table1.id + id | data | x | y +----+------+---+--- + 1 | 1 | 1 | 2 + 2 | 4 | 2 | 3 + 3 | 9 | 3 | 4 + 4 | 16 | 4 | 5 +(4 rows) + +-- a function returning table +CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text) +AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ +LANGUAGE SQL; +SELECT f.* FROM table1 t JOIN dup(32) f ON (f1 = id); +DEBUG: generating subplan 7_1 for subquery SELECT f1, f2 FROM functions_in_joins.dup(32) f(f1, f2) +DEBUG: Plan 7 query after replacing subqueries and CTEs: SELECT f.f1, f.f2 FROM (functions_in_joins.table1 t JOIN (SELECT intermediate_result.f1, intermediate_result.f2 FROM read_intermediate_result('7_1'::text, 'binary'::citus_copy_format) intermediate_result(f1 integer, f2 text)) f ON ((f.f1 OPERATOR(pg_catalog.=) t.id))) + f1 | f2 +----+------------ + 32 | 32 is text +(1 row) + +-- a stable function +CREATE OR REPLACE FUNCTION the_minimum_id() + RETURNS INTEGER STABLE AS 'SELECT min(id) FROM table1' LANGUAGE SQL; +SELECT * FROM table1 JOIN the_minimum_id() min_id ON (id = min_id); +DEBUG: generating subplan 8_1 for subquery SELECT min_id FROM functions_in_joins.the_minimum_id() min_id(min_id) +DEBUG: Plan 8 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, min_id.min_id FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.min_id FROM read_intermediate_result('8_1'::text, 'binary'::citus_copy_format) intermediate_result(min_id integer)) min_id ON ((table1.id OPERATOR(pg_catalog.=) min_id.min_id))) + id | data | min_id +----+------+-------- + 1 | 1 | 1 +(1 row) + +-- a built-in immutable function +SELECT * FROM table1 JOIN abs(100) as hundred ON (id = hundred) ORDER BY id ASC; + id | data | hundred +-----+-------+--------- + 100 | 10000 | 100 +(1 row) + +-- function joins inside a CTE +WITH next_row_to_process AS ( + SELECT * FROM table1 JOIN nextval('numbers') n ON (id = n) + ) +SELECT * +FROM table1, next_row_to_process +WHERE table1.data <= next_row_to_process.data +ORDER BY 1,2 ASC; +DEBUG: generating subplan 11_1 for CTE next_row_to_process: SELECT table1.id, table1.data, n.n FROM (functions_in_joins.table1 JOIN nextval('functions_in_joins.numbers'::regclass) n(n) ON ((table1.id OPERATOR(pg_catalog.=) n.n))) +DEBUG: generating subplan 12_1 for subquery SELECT n FROM nextval('functions_in_joins.numbers'::regclass) n(n) +DEBUG: Plan 12 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, n.n FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.n FROM read_intermediate_result('12_1'::text, 'binary'::citus_copy_format) intermediate_result(n bigint)) n ON ((table1.id OPERATOR(pg_catalog.=) n.n))) +DEBUG: Plan 11 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, next_row_to_process.id, next_row_to_process.data, next_row_to_process.n FROM functions_in_joins.table1, (SELECT intermediate_result.id, intermediate_result.data, intermediate_result.n FROM read_intermediate_result('11_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer, data integer, n bigint)) next_row_to_process WHERE (table1.data OPERATOR(pg_catalog.<=) next_row_to_process.data) ORDER BY table1.id, table1.data + id | data | id | data | n +----+------+----+------+--- + 1 | 1 | 2 | 4 | 2 + 2 | 4 | 2 | 4 | 2 +(2 rows) + +-- Multiple functions in an RTE +SELECT * FROM ROWS FROM (next_k_integers(5), next_k_integers(10)) AS f(a, b), + table1 WHERE id = a ORDER BY id ASC; +DEBUG: generating subplan 13_1 for subquery SELECT a, b FROM ROWS FROM(functions_in_joins.next_k_integers(5), functions_in_joins.next_k_integers(10)) f(a, b) +DEBUG: Plan 13 query after replacing subqueries and CTEs: SELECT f.a, f.b, table1.id, table1.data FROM (SELECT intermediate_result.a, intermediate_result.b FROM read_intermediate_result('13_1'::text, 'binary'::citus_copy_format) intermediate_result(a integer, b integer)) f(a, b), functions_in_joins.table1 WHERE (table1.id OPERATOR(pg_catalog.=) f.a) ORDER BY table1.id + a | b | id | data +---+----+----+------ + 5 | 10 | 5 | 25 + 6 | 11 | 6 | 36 + 7 | 12 | 7 | 49 +(3 rows) + +-- Custom Type returning function used in a join +CREATE TYPE min_and_max AS ( + minimum INT, + maximum INT +); +CREATE OR REPLACE FUNCTION max_and_min () RETURNS + min_and_max AS $$ +DECLARE + result min_and_max%rowtype; +begin + select into result min(data) as minimum, max(data) as maximum from table1; + return result; +end; +$$ language plpgsql; +SELECT * FROM table1 JOIN max_and_min() m ON (m.maximum = data OR m.minimum = data); +DEBUG: generating subplan 14_1 for subquery SELECT minimum, maximum FROM functions_in_joins.max_and_min() m(minimum, maximum) +DEBUG: Plan 14 query after replacing subqueries and CTEs: SELECT table1.id, table1.data, m.minimum, m.maximum FROM (functions_in_joins.table1 JOIN (SELECT intermediate_result.minimum, intermediate_result.maximum FROM read_intermediate_result('14_1'::text, 'binary'::citus_copy_format) intermediate_result(minimum integer, maximum integer)) m ON (((m.maximum OPERATOR(pg_catalog.=) table1.data) OR (m.minimum OPERATOR(pg_catalog.=) table1.data)))) + id | data | minimum | maximum +-----+-------+---------+--------- + 1 | 1 | 1 | 10000 + 100 | 10000 | 1 | 10000 +(2 rows) + +-- The following tests will fail as we do not support all joins on +-- all kinds of functions +SET client_min_messages TO ERROR; +-- function joins in CTE results can create lateral joins that are not supported +SELECT public.raise_failed_execution($cmd$ +WITH one_row AS ( + SELECT * FROM table1 WHERE id=52 + ) +SELECT table1.id, table1.data +FROM one_row, table1, next_k_integers(one_row.id, 5) next_five_ids +WHERE table1.id = next_five_ids; +$cmd$); +ERROR: Task failed to execute +CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE +-- a user-defined immutable function +CREATE OR REPLACE FUNCTION the_answer_to_life() + RETURNS INTEGER IMMUTABLE AS 'SELECT 42' LANGUAGE SQL; +SELECT public.raise_failed_execution($cmd$ +SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer) +$cmd$); +ERROR: Task failed to execute +CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE +-- WITH ORDINALITY clause forcing the result type to be RECORD/RECORDs +SELECT * +FROM table1 + JOIN next_k_integers(10,5) WITH ORDINALITY next_integers + ON (id = next_integers.result) +ORDER BY id ASC; +ERROR: attribute 2 of type record has wrong type +DETAIL: Table has type bigint, but query expects integer. +RESET client_min_messages; +DROP SCHEMA functions_in_joins CASCADE; +NOTICE: drop cascades to 11 other objects +DETAIL: drop cascades to table table1 +drop cascades to sequence numbers +drop cascades to function add(integer,integer) +drop cascades to function increment(integer) +drop cascades to function next_k_integers(integer,integer) +drop cascades to function get_set_of_records() +drop cascades to function dup(integer) +drop cascades to function the_minimum_id() +drop cascades to type min_and_max +drop cascades to function max_and_min() +drop cascades to function the_answer_to_life() +SET search_path TO DEFAULT; diff --git a/src/test/regress/expected/multi_subquery_complex_reference_clause.out b/src/test/regress/expected/multi_subquery_complex_reference_clause.out index 20b689a61..7aec16798 100644 --- a/src/test/regress/expected/multi_subquery_complex_reference_clause.out +++ b/src/test/regress/expected/multi_subquery_complex_reference_clause.out @@ -281,10 +281,8 @@ SET client_min_messages TO DEBUG; SELECT count(*) FROM (SELECT random() FROM user_buy_test_table JOIN random() AS users_ref_test_table(id) ON user_buy_test_table.item_id > users_ref_test_table.id) subquery_1; -DEBUG: generating subplan 30_1 for subquery SELECT random() AS random FROM (public.user_buy_test_table JOIN random() users_ref_test_table(id) ON (((user_buy_test_table.item_id)::double precision OPERATOR(pg_catalog.>) users_ref_test_table.id))) -DEBUG: Plan 30 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.random FROM read_intermediate_result('30_1'::text, 'binary'::citus_copy_format) intermediate_result(random double precision)) subquery_1 -DEBUG: Creating router plan -DEBUG: Plan is router executable +DEBUG: generating subplan 30_1 for subquery SELECT id FROM random() users_ref_test_table(id) +DEBUG: Plan 30 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT random() AS random FROM (public.user_buy_test_table JOIN (SELECT intermediate_result.id FROM read_intermediate_result('30_1'::text, 'binary'::citus_copy_format) intermediate_result(id double precision)) users_ref_test_table(id) ON (((user_buy_test_table.item_id)::double precision OPERATOR(pg_catalog.>) users_ref_test_table.id)))) subquery_1 count ------- 4 @@ -295,10 +293,8 @@ SELECT count(*) FROM (SELECT item_id FROM user_buy_test_table JOIN generate_series(random()::int,10) AS users_ref_test_table(id) ON user_buy_test_table.item_id > users_ref_test_table.id) subquery_1 WHERE item_id = 6; -DEBUG: generating subplan 32_1 for subquery SELECT user_buy_test_table.item_id FROM (public.user_buy_test_table JOIN generate_series((random())::integer, 10) users_ref_test_table(id) ON ((user_buy_test_table.item_id OPERATOR(pg_catalog.>) users_ref_test_table.id))) -DEBUG: Plan 32 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.item_id FROM read_intermediate_result('32_1'::text, 'binary'::citus_copy_format) intermediate_result(item_id integer)) subquery_1 WHERE (item_id OPERATOR(pg_catalog.=) 6) -DEBUG: Creating router plan -DEBUG: Plan is router executable +DEBUG: generating subplan 31_1 for subquery SELECT id FROM generate_series((random())::integer, 10) users_ref_test_table(id) +DEBUG: Plan 31 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT user_buy_test_table.item_id FROM (public.user_buy_test_table JOIN (SELECT intermediate_result.id FROM read_intermediate_result('31_1'::text, 'binary'::citus_copy_format) intermediate_result(id integer)) users_ref_test_table(id) ON ((user_buy_test_table.item_id OPERATOR(pg_catalog.>) users_ref_test_table.id)))) subquery_1 WHERE (item_id OPERATOR(pg_catalog.=) 6) count ------- 0 @@ -309,11 +305,11 @@ SELECT count(*) FROM (SELECT user_id FROM user_buy_test_table UNION ALL SELECT id FROM generate_series(1,10) AS users_ref_test_table(id)) subquery_1; -DEBUG: generating subplan 34_1 for subquery SELECT user_id FROM public.user_buy_test_table +DEBUG: generating subplan 32_1 for subquery SELECT user_id FROM public.user_buy_test_table DEBUG: Creating router plan DEBUG: Plan is router executable -DEBUG: generating subplan 34_2 for subquery SELECT intermediate_result.user_id FROM read_intermediate_result('34_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION ALL SELECT users_ref_test_table.id FROM generate_series(1, 10) users_ref_test_table(id) -DEBUG: Plan 34 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('34_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) subquery_1 +DEBUG: generating subplan 32_2 for subquery SELECT intermediate_result.user_id FROM read_intermediate_result('32_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION ALL SELECT users_ref_test_table.id FROM generate_series(1, 10) users_ref_test_table(id) +DEBUG: Plan 32 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('32_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) subquery_1 DEBUG: Creating router plan DEBUG: Plan is router executable count @@ -359,11 +355,11 @@ SELECT count(*) FROM (SELECT user_id FROM user_buy_test_table UNION ALL SELECT id FROM (SELECT 5 AS id) users_ref_test_table) subquery_1; -DEBUG: generating subplan 41_1 for subquery SELECT user_id FROM public.user_buy_test_table +DEBUG: generating subplan 39_1 for subquery SELECT user_id FROM public.user_buy_test_table DEBUG: Creating router plan DEBUG: Plan is router executable -DEBUG: generating subplan 41_2 for subquery SELECT intermediate_result.user_id FROM read_intermediate_result('41_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION ALL SELECT users_ref_test_table.id FROM (SELECT 5 AS id) users_ref_test_table -DEBUG: Plan 41 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('41_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) subquery_1 +DEBUG: generating subplan 39_2 for subquery SELECT intermediate_result.user_id FROM read_intermediate_result('39_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) UNION ALL SELECT users_ref_test_table.id FROM (SELECT 5 AS id) users_ref_test_table +DEBUG: Plan 39 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT intermediate_result.user_id FROM read_intermediate_result('39_2'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer)) subquery_1 DEBUG: Creating router plan DEBUG: Plan is router executable count @@ -379,11 +375,11 @@ SELECT * FROM UNION SELECT user_id FROM user_buy_test_table) sub ORDER BY 1 DESC; -DEBUG: generating subplan 44_1 for subquery SELECT user_id FROM public.user_buy_test_table +DEBUG: generating subplan 42_1 for subquery SELECT user_id FROM public.user_buy_test_table DEBUG: Creating router plan DEBUG: Plan is router executable -DEBUG: generating subplan 44_2 for subquery SELECT users_ref_test_table.id FROM public.users_ref_test_table UNION SELECT intermediate_result.user_id FROM read_intermediate_result('44_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) -DEBUG: Plan 44 query after replacing subqueries and CTEs: SELECT id FROM (SELECT intermediate_result.id FROM read_intermediate_result('44_2'::text, 'binary'::citus_copy_format) intermediate_result(id integer)) sub ORDER BY id DESC +DEBUG: generating subplan 42_2 for subquery SELECT users_ref_test_table.id FROM public.users_ref_test_table UNION SELECT intermediate_result.user_id FROM read_intermediate_result('42_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer) +DEBUG: Plan 42 query after replacing subqueries and CTEs: SELECT id FROM (SELECT intermediate_result.id FROM read_intermediate_result('42_2'::text, 'binary'::citus_copy_format) intermediate_result(id integer)) sub ORDER BY id DESC DEBUG: Creating router plan DEBUG: Plan is router executable id @@ -403,11 +399,11 @@ SELECT * FROM UNION SELECT user_id, random() * 0 FROM (SELECT user_id FROM user_buy_test_table) sub2) sub ORDER BY 1 DESC; -DEBUG: generating subplan 47_1 for subquery SELECT user_id, (random() OPERATOR(pg_catalog.*) (0)::double precision) FROM (SELECT user_buy_test_table.user_id FROM public.user_buy_test_table) sub2 +DEBUG: generating subplan 45_1 for subquery SELECT user_id, (random() OPERATOR(pg_catalog.*) (0)::double precision) FROM (SELECT user_buy_test_table.user_id FROM public.user_buy_test_table) sub2 DEBUG: Creating router plan DEBUG: Plan is router executable -DEBUG: generating subplan 47_2 for subquery SELECT sub1.id, (random() OPERATOR(pg_catalog.*) (0)::double precision) FROM (SELECT users_ref_test_table.id FROM public.users_ref_test_table) sub1 UNION SELECT intermediate_result.user_id, intermediate_result."?column?" FROM read_intermediate_result('47_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "?column?" double precision) -DEBUG: Plan 47 query after replacing subqueries and CTEs: SELECT id, "?column?" FROM (SELECT intermediate_result.id, intermediate_result."?column?" FROM read_intermediate_result('47_2'::text, 'binary'::citus_copy_format) intermediate_result(id integer, "?column?" double precision)) sub ORDER BY id DESC +DEBUG: generating subplan 45_2 for subquery SELECT sub1.id, (random() OPERATOR(pg_catalog.*) (0)::double precision) FROM (SELECT users_ref_test_table.id FROM public.users_ref_test_table) sub1 UNION SELECT intermediate_result.user_id, intermediate_result."?column?" FROM read_intermediate_result('45_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, "?column?" double precision) +DEBUG: Plan 45 query after replacing subqueries and CTEs: SELECT id, "?column?" FROM (SELECT intermediate_result.id, intermediate_result."?column?" FROM read_intermediate_result('45_2'::text, 'binary'::citus_copy_format) intermediate_result(id integer, "?column?" double precision)) sub ORDER BY id DESC DEBUG: Creating router plan DEBUG: Plan is router executable id | ?column? @@ -1334,8 +1330,8 @@ SELECT count(*) FROM (SELECT user_buy_test_table.user_id, random() FROM user_buy_test_table LEFT JOIN users_ref_test_table ON user_buy_test_table.user_id > users_ref_test_table.id) subquery_2 WHERE subquery_1.user_id != subquery_2.user_id ; -DEBUG: generating subplan 86_1 for subquery SELECT user_buy_test_table.user_id, random() AS random FROM (public.user_buy_test_table LEFT JOIN public.users_ref_test_table ON ((user_buy_test_table.user_id OPERATOR(pg_catalog.>) users_ref_test_table.id))) -DEBUG: Plan 86 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT user_buy_test_table.user_id, random() AS random FROM (public.user_buy_test_table LEFT JOIN public.users_ref_test_table ON ((user_buy_test_table.item_id OPERATOR(pg_catalog.>) users_ref_test_table.id)))) subquery_1, (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('86_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) subquery_2 WHERE (subquery_1.user_id OPERATOR(pg_catalog.<>) subquery_2.user_id) +DEBUG: generating subplan 84_1 for subquery SELECT user_buy_test_table.user_id, random() AS random FROM (public.user_buy_test_table LEFT JOIN public.users_ref_test_table ON ((user_buy_test_table.user_id OPERATOR(pg_catalog.>) users_ref_test_table.id))) +DEBUG: Plan 84 query after replacing subqueries and CTEs: SELECT count(*) AS count FROM (SELECT user_buy_test_table.user_id, random() AS random FROM (public.user_buy_test_table LEFT JOIN public.users_ref_test_table ON ((user_buy_test_table.item_id OPERATOR(pg_catalog.>) users_ref_test_table.id)))) subquery_1, (SELECT intermediate_result.user_id, intermediate_result.random FROM read_intermediate_result('84_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, random double precision)) subquery_2 WHERE (subquery_1.user_id OPERATOR(pg_catalog.<>) subquery_2.user_id) count ------- 67 @@ -1380,8 +1376,8 @@ count(*) AS cnt, "generated_group_field" ORDER BY cnt DESC, generated_group_field ASC LIMIT 10; -DEBUG: generating subplan 88_1 for subquery SELECT user_id, value_2 AS generated_group_field FROM public.users_table users -DEBUG: Plan 88 query after replacing subqueries and CTEs: SELECT count(*) AS cnt, generated_group_field FROM (SELECT "eventQuery".user_id, random() AS random, "eventQuery".generated_group_field FROM (SELECT multi_group_wrapper_1."time", multi_group_wrapper_1.event_user_id, multi_group_wrapper_1.user_id, left_group_by_1.generated_group_field, random() AS random FROM ((SELECT temp_data_queries."time", temp_data_queries.event_user_id, user_filters_1.user_id FROM ((SELECT events."time", events.user_id AS event_user_id FROM public.events_table events WHERE (events.user_id OPERATOR(pg_catalog.>) 2)) temp_data_queries JOIN (SELECT users.user_id FROM public.users_reference_table users WHERE ((users.user_id OPERATOR(pg_catalog.>) 2) AND (users.value_2 OPERATOR(pg_catalog.=) 5))) user_filters_1 ON ((temp_data_queries.event_user_id OPERATOR(pg_catalog.<) user_filters_1.user_id)))) multi_group_wrapper_1 RIGHT JOIN (SELECT intermediate_result.user_id, intermediate_result.generated_group_field FROM read_intermediate_result('88_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, generated_group_field integer)) left_group_by_1 ON ((left_group_by_1.user_id OPERATOR(pg_catalog.>) multi_group_wrapper_1.event_user_id)))) "eventQuery") "pushedDownQuery" GROUP BY generated_group_field ORDER BY (count(*)) DESC, generated_group_field LIMIT 10 +DEBUG: generating subplan 86_1 for subquery SELECT user_id, value_2 AS generated_group_field FROM public.users_table users +DEBUG: Plan 86 query after replacing subqueries and CTEs: SELECT count(*) AS cnt, generated_group_field FROM (SELECT "eventQuery".user_id, random() AS random, "eventQuery".generated_group_field FROM (SELECT multi_group_wrapper_1."time", multi_group_wrapper_1.event_user_id, multi_group_wrapper_1.user_id, left_group_by_1.generated_group_field, random() AS random FROM ((SELECT temp_data_queries."time", temp_data_queries.event_user_id, user_filters_1.user_id FROM ((SELECT events."time", events.user_id AS event_user_id FROM public.events_table events WHERE (events.user_id OPERATOR(pg_catalog.>) 2)) temp_data_queries JOIN (SELECT users.user_id FROM public.users_reference_table users WHERE ((users.user_id OPERATOR(pg_catalog.>) 2) AND (users.value_2 OPERATOR(pg_catalog.=) 5))) user_filters_1 ON ((temp_data_queries.event_user_id OPERATOR(pg_catalog.<) user_filters_1.user_id)))) multi_group_wrapper_1 RIGHT JOIN (SELECT intermediate_result.user_id, intermediate_result.generated_group_field FROM read_intermediate_result('86_1'::text, 'binary'::citus_copy_format) intermediate_result(user_id integer, generated_group_field integer)) left_group_by_1 ON ((left_group_by_1.user_id OPERATOR(pg_catalog.>) multi_group_wrapper_1.event_user_id)))) "eventQuery") "pushedDownQuery" GROUP BY generated_group_field ORDER BY (count(*)) DESC, generated_group_field LIMIT 10 ERROR: cannot pushdown the subquery DETAIL: Complex subqueries and CTEs cannot be in the outer part of the outer join RESET client_min_messages; diff --git a/src/test/regress/expected/set_operations.out b/src/test/regress/expected/set_operations.out index 1fde06dae..14d7f6268 100644 --- a/src/test/regress/expected/set_operations.out +++ b/src/test/regress/expected/set_operations.out @@ -509,7 +509,7 @@ DEBUG: generating subplan 100_2 for subquery SELECT x, y FROM recursive_union.t DEBUG: Creating router plan DEBUG: Plan is router executable DEBUG: generating subplan 100_3 for subquery SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('100_1'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) EXCEPT SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('100_2'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer) -DEBUG: Plan 100 query after replacing subqueries and CTEs: SELECT u.x, u.y FROM ((SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('100_3'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) u JOIN generate_series(1, 10) x(x) USING (x)) ORDER BY u.x, u.y +DEBUG: Plan 100 query after replacing subqueries and CTEs: SELECT u.x, u.y FROM ((SELECT intermediate_result.x, intermediate_result.y FROM read_intermediate_result('100_3'::text, 'binary'::citus_copy_format) intermediate_result(x integer, y integer)) u JOIN (SELECT x_1.x FROM generate_series(1, 10) x_1(x)) x USING (x)) ORDER BY u.x, u.y DEBUG: Creating router plan DEBUG: Plan is router executable x | y diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index d227bbcf9..9095dabaf 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -64,7 +64,7 @@ test: multi_deparse_shard_query multi_distributed_transaction_id multi_real_time test: multi_explain test: multi_basic_queries multi_complex_expressions multi_subquery multi_subquery_complex_queries multi_subquery_behavioral_analytics test: multi_subquery_complex_reference_clause multi_subquery_window_functions multi_view multi_sql_function multi_prepare_sql -test: sql_procedure +test: sql_procedure multi_function_in_join test: multi_subquery_in_where_reference_clause test: multi_subquery_union multi_subquery_in_where_clause multi_subquery_misc test: multi_agg_distinct multi_agg_approximate_distinct multi_limit_clause_approximate multi_outer_join_reference multi_single_relation_subquery multi_prepare_plsql diff --git a/src/test/regress/sql/multi_function_in_join.sql b/src/test/regress/sql/multi_function_in_join.sql new file mode 100644 index 000000000..86fe2a769 --- /dev/null +++ b/src/test/regress/sql/multi_function_in_join.sql @@ -0,0 +1,144 @@ +-- +-- multi function in join queries aims to test the function calls that are +-- used in joins. +-- +-- These functions are supposed to be executed on the worker and to ensure +-- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. +-- +-- We do not yet support those functions that: +-- - return records +-- - return tables +-- - are user-defined and immutable + +CREATE SCHEMA functions_in_joins; +SET search_path TO 'functions_in_joins'; +SET citus.next_shard_id TO 2500000; + +CREATE TABLE table1 (id int, data int); +SELECT create_distributed_table('table1','id'); + +INSERT INTO table1 +SELECT x, x*x +from generate_series(1, 100) as f (x); + +-- Verbose messages for observing the subqueries that wrapped function calls +SET client_min_messages TO DEBUG1; + +-- Check joins on a sequence +CREATE SEQUENCE numbers; +SELECT * FROM table1 JOIN nextval('numbers') n ON (id = n) ORDER BY id ASC; + +-- Check joins of a function that returns a single integer +CREATE FUNCTION add(integer, integer) RETURNS integer +AS 'SELECT $1 + $2;' +LANGUAGE SQL; +SELECT * FROM table1 JOIN add(3,5) sum ON (id = sum) ORDER BY id ASC; + +-- Check join of plpgsql functions +-- a function returning a single integer +CREATE OR REPLACE FUNCTION increment(i integer) RETURNS integer AS $$ +BEGIN + RETURN i + 1; +END; +$$ LANGUAGE plpgsql; +SELECT * FROM table1 JOIN increment(2) val ON (id = val) ORDER BY id ASC; + +-- a function that returns a set of integers +CREATE OR REPLACE FUNCTION next_k_integers(IN first_value INTEGER, + IN k INTEGER DEFAULT 3, + OUT result INTEGER) + RETURNS SETOF INTEGER AS $$ +BEGIN + RETURN QUERY SELECT x FROM generate_series(first_value, first_value+k-1) f(x); +END; +$$ LANGUAGE plpgsql; +SELECT * +FROM table1 JOIN next_k_integers(3,2) next_integers ON (id = next_integers.result) +ORDER BY id ASC; + +-- a function returning set of records +CREATE FUNCTION get_set_of_records() RETURNS SETOF RECORD AS $cmd$ +SELECT x, x+1 FROM generate_series(0,4) f(x) +$cmd$ +LANGUAGE SQL; +SELECT * FROM table1 JOIN get_set_of_records() AS t2(x int, y int) ON (id = x) ORDER BY id ASC; + +-- a function returning table +CREATE FUNCTION dup(int) RETURNS TABLE(f1 int, f2 text) +AS $$ SELECT $1, CAST($1 AS text) || ' is text' $$ +LANGUAGE SQL; + +SELECT f.* FROM table1 t JOIN dup(32) f ON (f1 = id); + +-- a stable function +CREATE OR REPLACE FUNCTION the_minimum_id() + RETURNS INTEGER STABLE AS 'SELECT min(id) FROM table1' LANGUAGE SQL; +SELECT * FROM table1 JOIN the_minimum_id() min_id ON (id = min_id); + +-- a built-in immutable function +SELECT * FROM table1 JOIN abs(100) as hundred ON (id = hundred) ORDER BY id ASC; + +-- function joins inside a CTE +WITH next_row_to_process AS ( + SELECT * FROM table1 JOIN nextval('numbers') n ON (id = n) + ) +SELECT * +FROM table1, next_row_to_process +WHERE table1.data <= next_row_to_process.data +ORDER BY 1,2 ASC; + +-- Multiple functions in an RTE +SELECT * FROM ROWS FROM (next_k_integers(5), next_k_integers(10)) AS f(a, b), + table1 WHERE id = a ORDER BY id ASC; + + +-- Custom Type returning function used in a join +CREATE TYPE min_and_max AS ( + minimum INT, + maximum INT +); + +CREATE OR REPLACE FUNCTION max_and_min () RETURNS + min_and_max AS $$ +DECLARE + result min_and_max%rowtype; +begin + select into result min(data) as minimum, max(data) as maximum from table1; + return result; +end; +$$ language plpgsql; + +SELECT * FROM table1 JOIN max_and_min() m ON (m.maximum = data OR m.minimum = data); + +-- The following tests will fail as we do not support all joins on +-- all kinds of functions +SET client_min_messages TO ERROR; + +-- function joins in CTE results can create lateral joins that are not supported +SELECT public.raise_failed_execution($cmd$ +WITH one_row AS ( + SELECT * FROM table1 WHERE id=52 + ) +SELECT table1.id, table1.data +FROM one_row, table1, next_k_integers(one_row.id, 5) next_five_ids +WHERE table1.id = next_five_ids; +$cmd$); + + +-- a user-defined immutable function +CREATE OR REPLACE FUNCTION the_answer_to_life() + RETURNS INTEGER IMMUTABLE AS 'SELECT 42' LANGUAGE SQL; +SELECT public.raise_failed_execution($cmd$ +SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer) +$cmd$); + +-- WITH ORDINALITY clause forcing the result type to be RECORD/RECORDs +SELECT * +FROM table1 + JOIN next_k_integers(10,5) WITH ORDINALITY next_integers + ON (id = next_integers.result) +ORDER BY id ASC; + +RESET client_min_messages; +DROP SCHEMA functions_in_joins CASCADE; +SET search_path TO DEFAULT; From 574b071113057cb82c9c859992e81611e8717be4 Mon Sep 17 00:00:00 2001 From: Hanefi Onaldi Date: Wed, 23 Jan 2019 11:53:33 +0300 Subject: [PATCH 02/20] Add wrapper function introduced in PG11 for compatibility --- .../distributed/planner/recursive_planning.c | 1 + src/include/distributed/version_compat.h | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index 389f5a68b..553ed922f 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -67,6 +67,7 @@ #include "distributed/query_colocation_checker.h" #include "distributed/recursive_planning.h" #include "distributed/relation_restriction_equivalence.h" +#include "distributed/version_compat.h" #include "lib/stringinfo.h" #include "optimizer/planner.h" #include "optimizer/prep.h" diff --git a/src/include/distributed/version_compat.h b/src/include/distributed/version_compat.h index cb2ab66ed..44c50d1c0 100644 --- a/src/include/distributed/version_compat.h +++ b/src/include/distributed/version_compat.h @@ -23,6 +23,7 @@ #include "optimizer/prep.h" #include "postmaster/bgworker.h" #include "utils/memutils.h" +#include "funcapi.h" /* PostgreSQL 11 splits hash procs into "standard" and "extended" */ #define HASHSTANDARD_PROC HASHPROC @@ -134,6 +135,33 @@ canonicalize_qual_compat(Expr *qual, bool is_check) } +/* + * A convenient wrapper around get_expr_result_type() that is added on PG11 + * + * Note that this function ignores the second parameter and behaves + * slightly differently. + * + * 1. The original function throws errors if noError flag is not set, we ignore + * this flag here and return NULL in that case + * 2. TYPEFUNC_COMPOSITE_DOMAIN is introduced in PG11, and references to this + * macro is removed + * */ +static inline TupleDesc +get_expr_result_tupdesc(Node *expr, bool noError) +{ + TupleDesc tupleDesc; + TypeFuncClass functypclass; + + functypclass = get_expr_result_type(expr, NULL, &tupleDesc); + + if (functypclass == TYPEFUNC_COMPOSITE) + { + return tupleDesc; + } + return NULL; +} + + #endif #if (PG_VERSION_NUM >= 110000) From 825666f912273ed3e139d85cd0a593132858047b Mon Sep 17 00:00:00 2001 From: Hanefi Onaldi Date: Wed, 23 Jan 2019 14:32:25 +0300 Subject: [PATCH 03/20] Query samples in docs and better errors --- .../distributed/planner/recursive_planning.c | 97 ++++++++++++------- src/include/distributed/version_compat.h | 2 +- .../expected/multi_function_in_join.out | 12 ++- .../regress/sql/multi_function_in_join.sql | 8 +- 4 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index 553ed922f..d07de832d 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -164,7 +164,7 @@ static bool CteReferenceListWalker(Node *node, CteReferenceWalkerContext *contex static bool ContainsReferencesToOuterQuery(Query *query); static bool ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *context); -static void WrapFunctionsInQuery(Query *query); +static void WrapFunctionsInSubqueries(Query *query); static void TransformFunctionRTE(RangeTblEntry *rangeTblEntry); static bool ShouldTransformRTE(RangeTblEntry *rangeTableEntry); @@ -268,7 +268,7 @@ RecursivelyPlanSubqueriesAndCTEs(Query *query, RecursivePlanningContext *context } /* make sure function calls in joins are executed in the coordinator */ - WrapFunctionsInQuery(query); + WrapFunctionsInSubqueries(query); /* descend into subqueries */ query_tree_walker(query, RecursivelyPlanSubqueryWalker, context, 0); @@ -1313,14 +1313,14 @@ ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *conte /* - * WrapFunctionsInQuery iterates over all the immediate Range Table Entries + * WrapFunctionsInSubqueries iterates over all the immediate Range Table Entries * of a query and wraps the functions inside (SELECT * FROM fnc() f) subqueries. * * We currently wrap only those functions that return a single value. If a * function returns records or a table we leave it as it is * */ static void -WrapFunctionsInQuery(Query *query) +WrapFunctionsInSubqueries(Query *query) { List *rangeTableList = query->rtable; ListCell *rangeTableCell = NULL; @@ -1358,7 +1358,7 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) Var *targetColumn = NULL; TargetEntry *targetEntry = NULL; RangeTblFunction *rangeTblFunction = NULL; - int targetColumnIndex = 0; + AttrNumber targetColumnIndex = 0; TupleDesc tupleDesc = NULL; rangeTblFunction = linitial(rangeTblEntry->functions); @@ -1381,10 +1381,20 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) tupleDesc = (TupleDesc) get_expr_result_tupdesc(rangeTblFunction->funcexpr, true); - /* if tupleDesc is not null, we iterate over all the attributes and - * create targetEntries*/ + /* + * If tupleDesc is not null, we iterate over all the attributes and + * create targetEntries + * */ if (tupleDesc) { + /* + * A sample function join that end up here: + * + * CREATE FUNCTION f(..) RETURNS TABLE(c1 int, c2 text) AS .. ; + * SELECT .. FROM table JOIN f(..) ON ( .. ) ; + * + * We will iterate over Tuple Description attributes. i.e (c1 int, c2 text) + */ for (targetColumnIndex = 0; targetColumnIndex < tupleDesc->natts; targetColumnIndex++) { @@ -1397,7 +1407,7 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) * The indexing of attributes and TupleDesc and varattno differ * * varattno=0 corresponds to whole row - * varattno=1 corrensponds to first column that is stored in tupDesc->attrs[0] */ + * varattno=1 corresponds to first column that is stored in tupDesc->attrs[0] */ targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, InvalidOid, 0); targetEntry = makeTargetEntry((Expr *) targetColumn, targetColumnIndex + 1, @@ -1407,13 +1417,13 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) } } /* - * If tupleDesc is NULL we have several cases: + * If tupleDesc is NULL we have 2 different cases: * * 1. The function returns a record but the attributes can not be - * determined before running the query. In this case the column names and - * types must be defined explicitly in the query + * determined just by looking at the function definition. In this case the + * column names and types must be defined explicitly in the query * - * 2. The function returns a non-composite type + * 2. The function returns a non-composite type (e.g. int, text, jsonb ..) * */ else { @@ -1427,24 +1437,56 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) char *columnName = strVal(lfirst(functionColumnName)); Oid columnType = InvalidOid; - /* use explicitly defined types in the query if they are available */ + /* + * If the function returns a set of records, the query needs + * to explicitly name column names and types + * + * Use explicitly defined types in the query if they are + * available + * */ if (list_length(rangeTblFunction->funccoltypes) > 0) { + /* + * A sample function join that end up here: + * + * CREATE FUNCTION get_set_of_records() RETURNS SETOF RECORD AS + * $cmd$ + * SELECT x, x+1 FROM generate_series(0,4) f(x) + * $cmd$ + * LANGUAGE SQL; + * + * SELECT * + * FROM table1 JOIN get_set_of_records() AS t2(x int, y int) + * ON (id = x); + * + * Note that the function definition does not have column + * names and types. Therefore the user needs to explicitly + * state them in the query + * */ columnType = list_nth_oid(rangeTblFunction->funccoltypes, targetColumnIndex); } /* use the types in the function definition otherwise */ else { + /* + * Only functions returning simple types end up here. + * A sample function: + * + * CREATE FUNCTION add(integer, integer) RETURNS integer AS + * 'SELECT $1 + $2;' + * LANGUAGE SQL; + * SELECT * FROM table JOIN add(3,5) sum ON ( .. ) ; + * */ FuncExpr *funcExpr = (FuncExpr *) rangeTblFunction->funcexpr; columnType = funcExpr->funcresulttype; } + /* Note that the column k is associated with varattno/resno of k+1 */ targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, InvalidOid, 0); targetEntry = makeTargetEntry((Expr *) targetColumn, - targetColumnIndex + 1, columnName, - false); + targetColumnIndex + 1, columnName, false); subquery->targetList = lappend(subquery->targetList, targetEntry); targetColumnIndex++; @@ -1461,33 +1503,22 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) * ShouldTransformRTE determines whether a given RTE should bne wrapped in a * subquery. * - * Not all functions can be wrapped in a subquery for now. As we support more + * Not all functions should be wrapped in a subquery for now. As we support more * functions to be used in joins, the constraints here will be relaxed. * */ static bool ShouldTransformRTE(RangeTblEntry *rangeTableEntry) { - /* wrap only function rtes */ - if (rangeTableEntry->rtekind != RTE_FUNCTION) - { - return false; - } - /* - * TODO: remove this check once lateral joins are supported - * We do not support lateral joins on functions for now, as referencing - * columns of an outer query is quite tricky */ - if (rangeTableEntry->lateral) + * We should wrap only function rtes that are not LATERAL and + * without WITH CARDINALITY clause + * */ + if (rangeTableEntry->rtekind != RTE_FUNCTION || + rangeTableEntry->lateral || + rangeTableEntry->funcordinality) { return false; } - - /* We do not want to wrap read-intermediate-result function calls */ - if (ContainsReadIntermediateResultFunction(linitial(rangeTableEntry->functions))) - { - return false; - } - return true; } diff --git a/src/include/distributed/version_compat.h b/src/include/distributed/version_compat.h index 44c50d1c0..4803313b5 100644 --- a/src/include/distributed/version_compat.h +++ b/src/include/distributed/version_compat.h @@ -139,7 +139,7 @@ canonicalize_qual_compat(Expr *qual, bool is_check) * A convenient wrapper around get_expr_result_type() that is added on PG11 * * Note that this function ignores the second parameter and behaves - * slightly differently. + * slightly differently than the PG11 version. * * 1. The original function throws errors if noError flag is not set, we ignore * this flag here and return NULL in that case diff --git a/src/test/regress/expected/multi_function_in_join.out b/src/test/regress/expected/multi_function_in_join.out index ef65aac82..d06016cae 100644 --- a/src/test/regress/expected/multi_function_in_join.out +++ b/src/test/regress/expected/multi_function_in_join.out @@ -6,8 +6,8 @@ -- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. -- -- We do not yet support those functions that: --- - return records --- - return tables +-- - have lateral joins +-- - have WITH ORDINALITY clause -- - are user-defined and immutable CREATE SCHEMA functions_in_joins; SET search_path TO 'functions_in_joins'; @@ -202,14 +202,16 @@ SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer) $cmd$); ERROR: Task failed to execute CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE --- WITH ORDINALITY clause forcing the result type to be RECORD/RECORDs +-- WITH ORDINALITY clause +SELECT public.raise_failed_execution($cmd$ SELECT * FROM table1 JOIN next_k_integers(10,5) WITH ORDINALITY next_integers ON (id = next_integers.result) ORDER BY id ASC; -ERROR: attribute 2 of type record has wrong type -DETAIL: Table has type bigint, but query expects integer. +$cmd$); +ERROR: Task failed to execute +CONTEXT: PL/pgSQL function public.raise_failed_execution(text) line 6 at RAISE RESET client_min_messages; DROP SCHEMA functions_in_joins CASCADE; NOTICE: drop cascades to 11 other objects diff --git a/src/test/regress/sql/multi_function_in_join.sql b/src/test/regress/sql/multi_function_in_join.sql index 86fe2a769..61d9700e0 100644 --- a/src/test/regress/sql/multi_function_in_join.sql +++ b/src/test/regress/sql/multi_function_in_join.sql @@ -6,8 +6,8 @@ -- that we wrap those functions inside (SELECT * FROM fnc()) sub queries. -- -- We do not yet support those functions that: --- - return records --- - return tables +-- - have lateral joins +-- - have WITH ORDINALITY clause -- - are user-defined and immutable CREATE SCHEMA functions_in_joins; @@ -132,12 +132,14 @@ SELECT public.raise_failed_execution($cmd$ SELECT * FROM table1 JOIN the_answer_to_life() the_answer ON (id = the_answer) $cmd$); --- WITH ORDINALITY clause forcing the result type to be RECORD/RECORDs +-- WITH ORDINALITY clause +SELECT public.raise_failed_execution($cmd$ SELECT * FROM table1 JOIN next_k_integers(10,5) WITH ORDINALITY next_integers ON (id = next_integers.result) ORDER BY id ASC; +$cmd$); RESET client_min_messages; DROP SCHEMA functions_in_joins CASCADE; From 148dcad0bbc5240cb5456a1e4d0489309d4e3b41 Mon Sep 17 00:00:00 2001 From: Hanefi Onaldi Date: Mon, 4 Feb 2019 19:12:51 +0300 Subject: [PATCH 04/20] More documentation and stale comments rewritten --- .../distributed/planner/recursive_planning.c | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/backend/distributed/planner/recursive_planning.c b/src/backend/distributed/planner/recursive_planning.c index d07de832d..19415a762 100644 --- a/src/backend/distributed/planner/recursive_planning.c +++ b/src/backend/distributed/planner/recursive_planning.c @@ -1314,10 +1314,12 @@ ContainsReferencesToOuterQueryWalker(Node *node, VarLevelsUpWalkerContext *conte /* * WrapFunctionsInSubqueries iterates over all the immediate Range Table Entries - * of a query and wraps the functions inside (SELECT * FROM fnc() f) subqueries. + * of a query and wraps the functions inside (SELECT * FROM fnc() f) + * subqueries, so that those functions will be executed on the coordinator if + * necessary. * - * We currently wrap only those functions that return a single value. If a - * function returns records or a table we leave it as it is + * We wrap all the functions that are used in joins except the ones that are + * laterally joined or have WITH ORDINALITY clauses. * */ static void WrapFunctionsInSubqueries(Query *query) @@ -1325,12 +1327,25 @@ WrapFunctionsInSubqueries(Query *query) List *rangeTableList = query->rtable; ListCell *rangeTableCell = NULL; - /* there needs to be at least two RTEs for a join operation */ + /* + * If we have only one function call in a query without any joins, we can + * easily decide where to execute it. + * + * If there are some subqueries and/or functions that are joined with a + * function, it is not trivial to decide whether we should run this + * function in the coordinator or in workers and therefore we may need to + * wrap some of those functions in subqueries. + * + * If we have only one RTE, we leave the parsed query tree as it is. This + * also makes sure we do not wrap an already wrapped function call + * because we know that there will always be 1 RTE in a wrapped function. + * */ if (list_length(rangeTableList) < 2) { return; } + /* iterate over all RTEs and wrap them if necessary */ foreach(rangeTableCell, rangeTableList) { RangeTblEntry *rangeTableEntry = (RangeTblEntry *) lfirst(rangeTableCell); @@ -1407,12 +1422,14 @@ TransformFunctionRTE(RangeTblEntry *rangeTblEntry) * The indexing of attributes and TupleDesc and varattno differ * * varattno=0 corresponds to whole row - * varattno=1 corresponds to first column that is stored in tupDesc->attrs[0] */ + * varattno=1 corresponds to first column that is stored in tupDesc->attrs[0] + * + * That's why we need to add one to the targetColumnIndex + * */ targetColumn = makeVar(1, targetColumnIndex + 1, columnType, -1, InvalidOid, 0); targetEntry = makeTargetEntry((Expr *) targetColumn, targetColumnIndex + 1, - columnName, - false); + columnName, false); subquery->targetList = lappend(subquery->targetList, targetEntry); } } From 1623c44fc751cebbaf80387c4712ca0cdcb03c0f Mon Sep 17 00:00:00 2001 From: Nils Dijk Date: Wed, 28 Nov 2018 18:15:39 +0100 Subject: [PATCH 05/20] Simplify make file for citus sql files --- src/backend/distributed/.gitignore | 5 - src/backend/distributed/Makefile | 230 +----------------- .../distributed/{citus.sql => citus--5.0.sql} | 0 3 files changed, 1 insertion(+), 234 deletions(-) rename src/backend/distributed/{citus.sql => citus--5.0.sql} (100%) diff --git a/src/backend/distributed/.gitignore b/src/backend/distributed/.gitignore index cdc226f03..5e8cb1a95 100644 --- a/src/backend/distributed/.gitignore +++ b/src/backend/distributed/.gitignore @@ -8,8 +8,3 @@ /regression.out /results/ /tmp_check* - -# ignore latest install file -citus--?.?.sql -citus--?.?-*.sql -!citus--?.?-*--?.?-*.sql diff --git a/src/backend/distributed/Makefile b/src/backend/distributed/Makefile index 1289cfd33..0f1afbb94 100644 --- a/src/backend/distributed/Makefile +++ b/src/backend/distributed/Makefile @@ -5,26 +5,9 @@ citus_top_builddir = ../../.. MODULE_big = citus EXTENSION = citus -EXTVERSIONS = 5.0 5.0-1 5.0-2 \ - 5.1-1 5.1-2 5.1-3 5.1-4 5.1-5 5.1-6 5.1-7 5.1-8 \ - 5.2-1 5.2-2 5.2-3 5.2-4 \ - 6.0-1 6.0-2 6.0-3 6.0-4 6.0-5 6.0-6 6.0-7 6.0-8 6.0-9 6.0-10 6.0-11 6.0-12 6.0-13 6.0-14 6.0-15 6.0-16 6.0-17 6.0-18 \ - 6.1-1 6.1-2 6.1-3 6.1-4 6.1-5 6.1-6 6.1-7 6.1-8 6.1-9 6.1-10 6.1-11 6.1-12 6.1-13 6.1-14 6.1-15 6.1-16 6.1-17 \ - 6.2-1 6.2-2 6.2-3 6.2-4 \ - 7.0-1 7.0-2 7.0-3 7.0-4 7.0-5 7.0-6 7.0-7 7.0-8 7.0-9 7.0-10 7.0-11 7.0-12 7.0-13 7.0-14 7.0-15 \ - 7.1-1 7.1-2 7.1-3 7.1-4 \ - 7.2-1 7.2-2 7.2-3 \ - 7.3-1 7.3-2 7.3-3 \ - 7.4-1 7.4-2 7.4-3 \ - 7.5-1 7.5-2 7.5-3 7.5-4 7.5-5 7.5-6 7.5-7 \ - 8.0-1 8.0-2 8.0-3 8.0-4 8.0-5 8.0-6 8.0-7 8.0-8 8.0-9 8.0-10 8.0-11 8.0-12 8.0-13 \ - 8.1-1\ - 8.2-1 # All citus--*.sql files in the source directory -DATA = $(patsubst $(citus_abs_srcdir)/%.sql,%.sql,$(wildcard $(citus_abs_srcdir)/$(EXTENSION)--*--*.sql)) -# Generated files for each version -DATA_built = $(foreach v,$(EXTVERSIONS),$(EXTENSION)--$(v).sql) +DATA = $(patsubst $(citus_abs_srcdir)/%.sql,%.sql,$(wildcard $(citus_abs_srcdir)/$(EXTENSION)--*.sql)) # directories with source files SUBDIRS = . commands connection ddl executor master metadata planner progress relay test transaction utils worker @@ -37,217 +20,6 @@ OBJS += \ # be explicit about the default target all: -# generate each version's file installation file by concatenating -# previous upgrade scripts -$(EXTENSION)--5.0.sql: $(EXTENSION).sql - cat $^ > $@ -$(EXTENSION)--5.0-1.sql: $(EXTENSION)--5.0.sql $(EXTENSION)--5.0--5.0-1.sql - cat $^ > $@ -$(EXTENSION)--5.0-2.sql: $(EXTENSION)--5.0-1.sql $(EXTENSION)--5.0-1--5.0-2.sql - cat $^ > $@ -$(EXTENSION)--5.1-1.sql: $(EXTENSION)--5.0-2.sql $(EXTENSION)--5.0-2--5.1-1.sql - cat $^ > $@ -$(EXTENSION)--5.1-2.sql: $(EXTENSION)--5.1-1.sql $(EXTENSION)--5.1-1--5.1-2.sql - cat $^ > $@ -$(EXTENSION)--5.1-3.sql: $(EXTENSION)--5.1-2.sql $(EXTENSION)--5.1-2--5.1-3.sql - cat $^ > $@ -$(EXTENSION)--5.1-4.sql: $(EXTENSION)--5.1-3.sql $(EXTENSION)--5.1-3--5.1-4.sql - cat $^ > $@ -$(EXTENSION)--5.1-5.sql: $(EXTENSION)--5.1-4.sql $(EXTENSION)--5.1-4--5.1-5.sql - cat $^ > $@ -$(EXTENSION)--5.1-6.sql: $(EXTENSION)--5.1-5.sql $(EXTENSION)--5.1-5--5.1-6.sql - cat $^ > $@ -$(EXTENSION)--5.1-7.sql: $(EXTENSION)--5.1-6.sql $(EXTENSION)--5.1-6--5.1-7.sql - cat $^ > $@ -$(EXTENSION)--5.1-8.sql: $(EXTENSION)--5.1-7.sql $(EXTENSION)--5.1-7--5.1-8.sql - cat $^ > $@ -$(EXTENSION)--5.2-1.sql: $(EXTENSION)--5.1-8.sql $(EXTENSION)--5.1-8--5.2-1.sql - cat $^ > $@ -$(EXTENSION)--5.2-2.sql: $(EXTENSION)--5.2-1.sql $(EXTENSION)--5.2-1--5.2-2.sql - cat $^ > $@ -$(EXTENSION)--5.2-3.sql: $(EXTENSION)--5.2-2.sql $(EXTENSION)--5.2-2--5.2-3.sql - cat $^ > $@ -$(EXTENSION)--5.2-4.sql: $(EXTENSION)--5.2-3.sql $(EXTENSION)--5.2-3--5.2-4.sql - cat $^ > $@ -$(EXTENSION)--6.0-1.sql: $(EXTENSION)--5.2-4.sql $(EXTENSION)--5.2-4--6.0-1.sql - cat $^ > $@ -$(EXTENSION)--6.0-2.sql: $(EXTENSION)--6.0-1.sql $(EXTENSION)--6.0-1--6.0-2.sql - cat $^ > $@ -$(EXTENSION)--6.0-3.sql: $(EXTENSION)--6.0-2.sql $(EXTENSION)--6.0-2--6.0-3.sql - cat $^ > $@ -$(EXTENSION)--6.0-4.sql: $(EXTENSION)--6.0-3.sql $(EXTENSION)--6.0-3--6.0-4.sql - cat $^ > $@ -$(EXTENSION)--6.0-5.sql: $(EXTENSION)--6.0-4.sql $(EXTENSION)--6.0-4--6.0-5.sql - cat $^ > $@ -$(EXTENSION)--6.0-6.sql: $(EXTENSION)--6.0-5.sql $(EXTENSION)--6.0-5--6.0-6.sql - cat $^ > $@ -$(EXTENSION)--6.0-7.sql: $(EXTENSION)--6.0-6.sql $(EXTENSION)--6.0-6--6.0-7.sql - cat $^ > $@ -$(EXTENSION)--6.0-8.sql: $(EXTENSION)--6.0-7.sql $(EXTENSION)--6.0-7--6.0-8.sql - cat $^ > $@ -$(EXTENSION)--6.0-9.sql: $(EXTENSION)--6.0-8.sql $(EXTENSION)--6.0-8--6.0-9.sql - cat $^ > $@ -$(EXTENSION)--6.0-10.sql: $(EXTENSION)--6.0-9.sql $(EXTENSION)--6.0-9--6.0-10.sql - cat $^ > $@ -$(EXTENSION)--6.0-11.sql: $(EXTENSION)--6.0-10.sql $(EXTENSION)--6.0-10--6.0-11.sql - cat $^ > $@ -$(EXTENSION)--6.0-12.sql: $(EXTENSION)--6.0-11.sql $(EXTENSION)--6.0-11--6.0-12.sql - cat $^ > $@ -$(EXTENSION)--6.0-13.sql: $(EXTENSION)--6.0-12.sql $(EXTENSION)--6.0-12--6.0-13.sql - cat $^ > $@ -$(EXTENSION)--6.0-14.sql: $(EXTENSION)--6.0-13.sql $(EXTENSION)--6.0-13--6.0-14.sql - cat $^ > $@ -$(EXTENSION)--6.0-15.sql: $(EXTENSION)--6.0-14.sql $(EXTENSION)--6.0-14--6.0-15.sql - cat $^ > $@ -$(EXTENSION)--6.0-16.sql: $(EXTENSION)--6.0-15.sql $(EXTENSION)--6.0-15--6.0-16.sql - cat $^ > $@ -$(EXTENSION)--6.0-17.sql: $(EXTENSION)--6.0-16.sql $(EXTENSION)--6.0-16--6.0-17.sql - cat $^ > $@ -$(EXTENSION)--6.0-18.sql: $(EXTENSION)--6.0-17.sql $(EXTENSION)--6.0-17--6.0-18.sql - cat $^ > $@ -$(EXTENSION)--6.1-1.sql: $(EXTENSION)--6.0-18.sql $(EXTENSION)--6.0-18--6.1-1.sql - cat $^ > $@ -$(EXTENSION)--6.1-2.sql: $(EXTENSION)--6.1-1.sql $(EXTENSION)--6.1-1--6.1-2.sql - cat $^ > $@ -$(EXTENSION)--6.1-3.sql: $(EXTENSION)--6.1-2.sql $(EXTENSION)--6.1-2--6.1-3.sql - cat $^ > $@ -$(EXTENSION)--6.1-4.sql: $(EXTENSION)--6.1-3.sql $(EXTENSION)--6.1-3--6.1-4.sql - cat $^ > $@ -$(EXTENSION)--6.1-5.sql: $(EXTENSION)--6.1-4.sql $(EXTENSION)--6.1-4--6.1-5.sql - cat $^ > $@ -$(EXTENSION)--6.1-6.sql: $(EXTENSION)--6.1-5.sql $(EXTENSION)--6.1-5--6.1-6.sql - cat $^ > $@ -$(EXTENSION)--6.1-7.sql: $(EXTENSION)--6.1-6.sql $(EXTENSION)--6.1-6--6.1-7.sql - cat $^ > $@ -$(EXTENSION)--6.1-8.sql: $(EXTENSION)--6.1-7.sql $(EXTENSION)--6.1-7--6.1-8.sql - cat $^ > $@ -$(EXTENSION)--6.1-9.sql: $(EXTENSION)--6.1-8.sql $(EXTENSION)--6.1-8--6.1-9.sql - cat $^ > $@ -$(EXTENSION)--6.1-10.sql: $(EXTENSION)--6.1-9.sql $(EXTENSION)--6.1-9--6.1-10.sql - cat $^ > $@ -$(EXTENSION)--6.1-11.sql: $(EXTENSION)--6.1-10.sql $(EXTENSION)--6.1-10--6.1-11.sql - cat $^ > $@ -$(EXTENSION)--6.1-12.sql: $(EXTENSION)--6.1-11.sql $(EXTENSION)--6.1-11--6.1-12.sql - cat $^ > $@ -$(EXTENSION)--6.1-13.sql: $(EXTENSION)--6.1-12.sql $(EXTENSION)--6.1-12--6.1-13.sql - cat $^ > $@ -$(EXTENSION)--6.1-14.sql: $(EXTENSION)--6.1-13.sql $(EXTENSION)--6.1-13--6.1-14.sql - cat $^ > $@ -$(EXTENSION)--6.1-15.sql: $(EXTENSION)--6.1-14.sql $(EXTENSION)--6.1-14--6.1-15.sql - cat $^ > $@ -$(EXTENSION)--6.1-16.sql: $(EXTENSION)--6.1-15.sql $(EXTENSION)--6.1-15--6.1-16.sql - cat $^ > $@ -$(EXTENSION)--6.1-17.sql: $(EXTENSION)--6.1-16.sql $(EXTENSION)--6.1-16--6.1-17.sql - cat $^ > $@ -$(EXTENSION)--6.2-1.sql: $(EXTENSION)--6.1-17.sql $(EXTENSION)--6.1-17--6.2-1.sql - cat $^ > $@ -$(EXTENSION)--6.2-2.sql: $(EXTENSION)--6.2-1.sql $(EXTENSION)--6.2-1--6.2-2.sql - cat $^ > $@ -$(EXTENSION)--6.2-3.sql: $(EXTENSION)--6.2-2.sql $(EXTENSION)--6.2-2--6.2-3.sql - cat $^ > $@ -$(EXTENSION)--6.2-4.sql: $(EXTENSION)--6.2-3.sql $(EXTENSION)--6.2-3--6.2-4.sql - cat $^ > $@ -$(EXTENSION)--7.0-1.sql: $(EXTENSION)--6.2-4.sql $(EXTENSION)--6.2-4--7.0-1.sql - cat $^ > $@ -$(EXTENSION)--7.0-2.sql: $(EXTENSION)--7.0-1.sql $(EXTENSION)--7.0-1--7.0-2.sql - cat $^ > $@ -$(EXTENSION)--7.0-3.sql: $(EXTENSION)--7.0-2.sql $(EXTENSION)--7.0-2--7.0-3.sql - cat $^ > $@ -$(EXTENSION)--7.0-4.sql: $(EXTENSION)--7.0-3.sql $(EXTENSION)--7.0-3--7.0-4.sql - cat $^ > $@ -$(EXTENSION)--7.0-5.sql: $(EXTENSION)--7.0-4.sql $(EXTENSION)--7.0-4--7.0-5.sql - cat $^ > $@ -$(EXTENSION)--7.0-6.sql: $(EXTENSION)--7.0-5.sql $(EXTENSION)--7.0-5--7.0-6.sql - cat $^ > $@ -$(EXTENSION)--7.0-7.sql: $(EXTENSION)--7.0-6.sql $(EXTENSION)--7.0-6--7.0-7.sql - cat $^ > $@ -$(EXTENSION)--7.0-8.sql: $(EXTENSION)--7.0-7.sql $(EXTENSION)--7.0-7--7.0-8.sql - cat $^ > $@ -$(EXTENSION)--7.0-9.sql: $(EXTENSION)--7.0-8.sql $(EXTENSION)--7.0-8--7.0-9.sql - cat $^ > $@ -$(EXTENSION)--7.0-10.sql: $(EXTENSION)--7.0-9.sql $(EXTENSION)--7.0-9--7.0-10.sql - cat $^ > $@ -$(EXTENSION)--7.0-11.sql: $(EXTENSION)--7.0-10.sql $(EXTENSION)--7.0-10--7.0-11.sql - cat $^ > $@ -$(EXTENSION)--7.0-12.sql: $(EXTENSION)--7.0-11.sql $(EXTENSION)--7.0-11--7.0-12.sql - cat $^ > $@ -$(EXTENSION)--7.0-13.sql: $(EXTENSION)--7.0-12.sql $(EXTENSION)--7.0-12--7.0-13.sql - cat $^ > $@ -$(EXTENSION)--7.0-14.sql: $(EXTENSION)--7.0-13.sql $(EXTENSION)--7.0-13--7.0-14.sql - cat $^ > $@ -$(EXTENSION)--7.0-15.sql: $(EXTENSION)--7.0-14.sql $(EXTENSION)--7.0-14--7.0-15.sql - cat $^ > $@ -$(EXTENSION)--7.1-1.sql: $(EXTENSION)--7.0-15.sql $(EXTENSION)--7.0-15--7.1-1.sql - cat $^ > $@ -$(EXTENSION)--7.1-2.sql: $(EXTENSION)--7.1-1.sql $(EXTENSION)--7.1-1--7.1-2.sql - cat $^ > $@ -$(EXTENSION)--7.1-3.sql: $(EXTENSION)--7.1-2.sql $(EXTENSION)--7.1-2--7.1-3.sql - cat $^ > $@ -$(EXTENSION)--7.1-4.sql: $(EXTENSION)--7.1-3.sql $(EXTENSION)--7.1-3--7.1-4.sql - cat $^ > $@ -$(EXTENSION)--7.2-1.sql: $(EXTENSION)--7.1-4.sql $(EXTENSION)--7.1-4--7.2-1.sql - cat $^ > $@ -$(EXTENSION)--7.2-2.sql: $(EXTENSION)--7.2-1.sql $(EXTENSION)--7.2-1--7.2-2.sql - cat $^ > $@ -$(EXTENSION)--7.2-3.sql: $(EXTENSION)--7.2-2.sql $(EXTENSION)--7.2-2--7.2-3.sql - cat $^ > $@ -$(EXTENSION)--7.3-1.sql: $(EXTENSION)--7.2-3.sql $(EXTENSION)--7.2-3--7.3-1.sql - cat $^ > $@ -$(EXTENSION)--7.3-2.sql: $(EXTENSION)--7.3-1.sql $(EXTENSION)--7.3-1--7.3-2.sql - cat $^ > $@ -$(EXTENSION)--7.3-3.sql: $(EXTENSION)--7.3-2.sql $(EXTENSION)--7.3-2--7.3-3.sql - cat $^ > $@ -$(EXTENSION)--7.4-1.sql: $(EXTENSION)--7.3-3.sql $(EXTENSION)--7.3-3--7.4-1.sql - cat $^ > $@ -$(EXTENSION)--7.4-2.sql: $(EXTENSION)--7.4-1.sql $(EXTENSION)--7.4-1--7.4-2.sql - cat $^ > $@ -$(EXTENSION)--7.4-3.sql: $(EXTENSION)--7.4-2.sql $(EXTENSION)--7.4-2--7.4-3.sql - cat $^ > $@ -$(EXTENSION)--7.5-1.sql: $(EXTENSION)--7.4-3.sql $(EXTENSION)--7.4-3--7.5-1.sql - cat $^ > $@ -$(EXTENSION)--7.5-2.sql: $(EXTENSION)--7.5-1.sql $(EXTENSION)--7.5-1--7.5-2.sql - cat $^ > $@ -$(EXTENSION)--7.5-3.sql: $(EXTENSION)--7.5-2.sql $(EXTENSION)--7.5-2--7.5-3.sql - cat $^ > $@ -$(EXTENSION)--7.5-4.sql: $(EXTENSION)--7.5-3.sql $(EXTENSION)--7.5-3--7.5-4.sql - cat $^ > $@ -$(EXTENSION)--7.5-5.sql: $(EXTENSION)--7.5-4.sql $(EXTENSION)--7.5-4--7.5-5.sql - cat $^ > $@ -$(EXTENSION)--7.5-6.sql: $(EXTENSION)--7.5-5.sql $(EXTENSION)--7.5-5--7.5-6.sql - cat $^ > $@ -$(EXTENSION)--7.5-7.sql: $(EXTENSION)--7.5-6.sql $(EXTENSION)--7.5-6--7.5-7.sql - cat $^ > $@ -$(EXTENSION)--8.0-1.sql: $(EXTENSION)--7.5-7.sql $(EXTENSION)--7.5-7--8.0-1.sql - cat $^ > $@ -$(EXTENSION)--8.0-2.sql: $(EXTENSION)--8.0-1.sql $(EXTENSION)--8.0-1--8.0-2.sql - cat $^ > $@ -$(EXTENSION)--8.0-3.sql: $(EXTENSION)--8.0-2.sql $(EXTENSION)--8.0-2--8.0-3.sql - cat $^ > $@ -$(EXTENSION)--8.0-4.sql: $(EXTENSION)--8.0-3.sql $(EXTENSION)--8.0-3--8.0-4.sql - cat $^ > $@ -$(EXTENSION)--8.0-5.sql: $(EXTENSION)--8.0-4.sql $(EXTENSION)--8.0-4--8.0-5.sql - cat $^ > $@ -$(EXTENSION)--8.0-6.sql: $(EXTENSION)--8.0-5.sql $(EXTENSION)--8.0-5--8.0-6.sql - cat $^ > $@ -$(EXTENSION)--8.0-7.sql: $(EXTENSION)--8.0-6.sql $(EXTENSION)--8.0-6--8.0-7.sql - cat $^ > $@ -$(EXTENSION)--8.0-8.sql: $(EXTENSION)--8.0-7.sql $(EXTENSION)--8.0-7--8.0-8.sql - cat $^ > $@ -$(EXTENSION)--8.0-9.sql: $(EXTENSION)--8.0-8.sql $(EXTENSION)--8.0-8--8.0-9.sql - cat $^ > $@ -$(EXTENSION)--8.0-10.sql: $(EXTENSION)--8.0-9.sql $(EXTENSION)--8.0-9--8.0-10.sql - cat $^ > $@ -$(EXTENSION)--8.0-11.sql: $(EXTENSION)--8.0-10.sql $(EXTENSION)--8.0-10--8.0-11.sql - cat $^ > $@ -$(EXTENSION)--8.0-12.sql: $(EXTENSION)--8.0-11.sql $(EXTENSION)--8.0-11--8.0-12.sql - cat $^ > $@ -$(EXTENSION)--8.0-13.sql: $(EXTENSION)--8.0-12.sql $(EXTENSION)--8.0-12--8.0-13.sql - cat $^ > $@ -$(EXTENSION)--8.1-1.sql: $(EXTENSION)--8.0-13.sql $(EXTENSION)--8.0-13--8.1-1.sql - cat $^ > $@ -$(EXTENSION)--8.2-1.sql: $(EXTENSION)--8.1-1.sql $(EXTENSION)--8.1-1--8.2-1.sql - cat $^ > $@ - NO_PGXS = 1 SHLIB_LINK = $(libpq) diff --git a/src/backend/distributed/citus.sql b/src/backend/distributed/citus--5.0.sql similarity index 100% rename from src/backend/distributed/citus.sql rename to src/backend/distributed/citus--5.0.sql From f144bb491116502debffafc3204eed3f59f6d4ff Mon Sep 17 00:00:00 2001 From: Onder Kalaci Date: Sun, 10 Feb 2019 20:05:59 +0300 Subject: [PATCH 06/20] Introduce fast path router planning In this context, we define "Fast Path Planning for SELECT" as trivial queries where Citus can skip relying on the standard_planner() and handle all the planning. For router planner, standard_planner() is mostly important to generate the necessary restriction information. Later, the restriction information generated by the standard_planner is used to decide whether all the shards that a distributed query touches reside on a single worker node. However, standard_planner() does a lot of extra things such as cost estimation and execution path generations which are completely unnecessary in the context of distributed planning. There are certain types of queries where Citus could skip relying on standard_planner() to generate the restriction information. For queries in the following format, Citus does not need any information that the standard_planner() generates: SELECT ... FROM single_table WHERE distribution_key = X; or DELETE FROM single_table WHERE distribution_key = X; or UPDATE single_table SET value_1 = value_2 + 1 WHERE distribution_key = X; Note that the queries might not be as simple as the above such that GROUP BY, WINDOW FUNCIONS, ORDER BY or HAVING etc. are all acceptable. The only rule is that the query is on a single distributed (or reference) table and there is a "distribution_key = X;" in the WHERE clause. With that, we could use to decide the shard that a distributed query touches reside on a worker node. --- .../distributed/planner/distributed_planner.c | 20 +- .../planner/fast_path_router_planner.c | 431 ++++ .../planner/multi_physical_planner.c | 12 +- .../planner/multi_router_planner.c | 110 +- .../distributed/planner/planner_readme.md | 19 +- src/backend/distributed/shared_library_init.c | 10 + src/include/distributed/distributed_planner.h | 1 + .../distributed/multi_router_planner.h | 17 +- .../expected/fast_path_router_modify.out | 362 +++ .../expected/multi_function_evaluation.out | 4 + .../regress/expected/multi_hash_pruning.out | 4 + .../regress/expected/multi_insert_select.out | 1 + .../expected/multi_mx_router_planner.out | 4 + .../regress/expected/multi_prepare_plsql.out | 4 + .../regress/expected/multi_prepare_sql.out | 4 + .../regress/expected/multi_router_planner.out | 4 + .../multi_router_planner_fast_path.out | 2204 +++++++++++++++++ .../regress/expected/multi_simple_queries.out | 52 + .../expected/multi_simple_queries_0.out | 52 + .../expected/multi_task_assignment_policy.out | 6 + .../set_operation_and_local_tables.out | 2 + src/test/regress/multi_schedule | 4 +- .../regress/sql/fast_path_router_modify.sql | 113 + .../regress/sql/multi_function_evaluation.sql | 5 + src/test/regress/sql/multi_hash_pruning.sql | 6 + .../regress/sql/multi_mx_router_planner.sql | 5 + src/test/regress/sql/multi_prepare_plsql.sql | 4 + src/test/regress/sql/multi_prepare_sql.sql | 5 + src/test/regress/sql/multi_router_planner.sql | 6 +- .../sql/multi_router_planner_fast_path.sql | 829 +++++++ src/test/regress/sql/multi_simple_queries.sql | 12 + 31 files changed, 4272 insertions(+), 40 deletions(-) create mode 100644 src/backend/distributed/planner/fast_path_router_planner.c create mode 100644 src/test/regress/expected/fast_path_router_modify.out create mode 100644 src/test/regress/expected/multi_router_planner_fast_path.out create mode 100644 src/test/regress/sql/fast_path_router_modify.sql create mode 100644 src/test/regress/sql/multi_router_planner_fast_path.sql diff --git a/src/backend/distributed/planner/distributed_planner.c b/src/backend/distributed/planner/distributed_planner.c index 331460b90..b49be5790 100644 --- a/src/backend/distributed/planner/distributed_planner.c +++ b/src/backend/distributed/planner/distributed_planner.c @@ -64,7 +64,6 @@ static DistributedPlan * CreateDistributedPlan(uint64 planId, Query *originalQue plannerRestrictionContext); static DeferredErrorMessage * DeferErrorIfPartitionTableNotSingleReplicated(Oid relationId); -static Node * ResolveExternalParams(Node *inputNode, ParamListInfo boundParams); static void AssignRTEIdentities(Query *queryTree); static void AssignRTEIdentity(RangeTblEntry *rangeTableEntry, int rteIdentifier); @@ -147,11 +146,22 @@ distributed_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) PG_TRY(); { /* - * First call into standard planner. This is required because the Citus - * planner relies on parse tree transformations made by postgres' planner. + * For trivial queries, we're skipping the standard_planner() in + * order to eliminate its overhead. + * + * Otherwise, call into standard planner. This is required because the Citus + * planner relies on both the restriction information per table and parse tree + * transformations made by postgres' planner. */ - result = standard_planner(parse, cursorOptions, boundParams); + if (needsDistributedPlanning && FastPathRouterQuery(originalQuery)) + { + result = FastPathPlanner(originalQuery, parse, boundParams); + } + else + { + result = standard_planner(parse, cursorOptions, boundParams); + } if (needsDistributedPlanning) { @@ -831,7 +841,7 @@ DeferErrorIfPartitionTableNotSingleReplicated(Oid relationId) * Note that this function is inspired by eval_const_expr() on Postgres. * We cannot use that function because it requires access to PlannerInfo. */ -static Node * +Node * ResolveExternalParams(Node *inputNode, ParamListInfo boundParams) { /* consider resolving external parameters only when boundParams exists */ diff --git a/src/backend/distributed/planner/fast_path_router_planner.c b/src/backend/distributed/planner/fast_path_router_planner.c new file mode 100644 index 000000000..f795ef480 --- /dev/null +++ b/src/backend/distributed/planner/fast_path_router_planner.c @@ -0,0 +1,431 @@ +/*------------------------------------------------------------------------- + * + * fast_path_router_planner.c + * + * Planning logic for fast path router planner queries. In this context, + * we define "Fast Path Planning" as trivial queries where Citus + * can skip relying on the standard_planner() and handle all the planning. + * + * For router planner, standard_planner() is mostly important to generate + * the necessary restriction information. Later, the restriction information + * generated by the standard_planner is used to decide whether all the shards + * that a distributed query touches reside on a single worker node. However, + * standard_planner() does a lot of extra things such as cost estimation and + * execution path generations which are completely unnecessary in the context + * of distributed planning. + * + * There are certain types of queries where Citus could skip relying on + * standard_planner() to generate the restriction information. For queries + * in the following format, Citus does not need any information that the + * standard_planner() generates: + * SELECT ... FROM single_table WHERE distribution_key = X; or + * DELETE FROM single_table WHERE distribution_key = X; or + * UPDATE single_table SET value_1 = value_2 + 1 WHERE distribution_key = X; + * + * Note that the queries might not be as simple as the above such that + * GROUP BY, WINDOW FUNCIONS, ORDER BY or HAVING etc. are all acceptable. The + * only rule is that the query is on a single distributed (or reference) table + * and there is a "distribution_key = X;" in the WHERE clause. With that, we + * could use to decide the shard that a distributed query touches reside on + * a worker node. + * + * Copyright (c) 2019, Citus Data, Inc. + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "distributed/distributed_planner.h" +#include "distributed/multi_physical_planner.h" /* only to use some utility functions */ +#include "distributed/metadata_cache.h" +#include "distributed/multi_router_planner.h" +#include "distributed/pg_dist_partition.h" +#include "distributed/shardinterval_utils.h" +#include "distributed/shard_pruning.h" +#include "nodes/nodeFuncs.h" +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" +#include "optimizer/clauses.h" + +bool EnableFastPathRouterPlanner = true; + +static bool ColumnAppearsMultipleTimes(Node *quals, Var *distributionKey); +static bool ConjunctionContainsColumnFilter(Node *node, Var *column); +static bool DistKeyInSimpleOpExpression(Expr *clause, Var *distColumn); + + +/* + * FastPathPlanner is intended to be used instead of standard_planner() for trivial + * queries defined by FastPathRouterQuery(). + * + * The basic idea is that we need a very little of what standard_planner() does for + * the trivial queries. So skip calling standard_planner() to save CPU cycles. + * + */ +PlannedStmt * +FastPathPlanner(Query *originalQuery, Query *parse, ParamListInfo boundParams) +{ + PlannedStmt *result = NULL; + + /* + * To support prepared statements for fast-path queries, we resolve the + * external parameters at this point. Note that this is normally done by + * eval_const_expr() in standard planner when the boundParams are avaliable. + * If not avaliable, as does for all other types of queries, Citus goes + * through the logic of increasing the cost of the plan and forcing + * PostgreSQL to pick custom plans. + * + * We're also only interested in resolving the quals since we'd want to + * do shard pruning based on the filter on the distribution column. + */ + originalQuery->jointree->quals = + ResolveExternalParams((Node *) originalQuery->jointree->quals, + copyParamList(boundParams)); + + /* + * Citus planner relies on some of the transformations on constant + * evaluation on the parse tree. + */ + parse->targetList = + (List *) eval_const_expressions(NULL, (Node *) parse->targetList); + parse->jointree->quals = + (Node *) eval_const_expressions(NULL, (Node *) parse->jointree->quals); + + + result = GeneratePlaceHolderPlannedStmt(originalQuery); + + return result; +} + + +/* + * GeneratePlaceHolderPlannedStmt creates a planned statement which contains + * a sequential scan on the relation that is accessed by the input query. + * The returned PlannedStmt is not proper (e.g., set_plan_references() is + * not called on the plan or the quals are not set), so should not be + * passed to the executor directly. This is only useful to have a + * placeholder PlannedStmt where target list is properly set. Note that + * this is what router executor relies on. + * + * This function makes the assumption (and the assertion) that + * the input query is in the form defined by FastPathRouterQuery(). + */ +PlannedStmt * +GeneratePlaceHolderPlannedStmt(Query *parse) +{ + PlannedStmt *result = makeNode(PlannedStmt); + SeqScan *seqScanNode = makeNode(SeqScan); + Plan *plan = &seqScanNode->plan; + Oid relationId = InvalidOid; + + AssertArg(FastPathRouterQuery(parse)); + + /* there is only a single relation rte */ + seqScanNode->scanrelid = 1; + + plan->targetlist = copyObject(parse->targetList); + plan->qual = NULL; + plan->lefttree = NULL; + plan->righttree = NULL; + plan->plan_node_id = 1; + + /* rtable is used for access permission checks */ + result->commandType = parse->commandType; + result->queryId = parse->queryId; + result->stmt_len = parse->stmt_len; + + result->rtable = copyObject(parse->rtable); + result->planTree = (Plan *) plan; + + relationId = ExtractFirstDistributedTableId(parse); + result->relationOids = list_make1_oid(relationId); + + return result; +} + + +/* + * FastPathRouterQuery gets a query and returns true if the query is eligable for + * being a fast path router query. + * The requirements for the fast path query can be listed below: + * + * - SELECT query without CTES, sublinks-subqueries, set operations + * - The query should touch only a single hash distributed or reference table + * - The distribution with equality operator should be in the WHERE clause + * and it should be ANDed with any other filters. Also, the distribution + * key should only exists once in the WHERE clause. So basically, + * SELECT ... FROM dist_table WHERE dist_key = X + * - No returning for UPDATE/DELETE queries + */ +bool +FastPathRouterQuery(Query *query) +{ + RangeTblEntry *rangeTableEntry = NULL; + FromExpr *joinTree = query->jointree; + Node *quals = NULL; + Oid distributedTableId = InvalidOid; + Var *distributionKey = NULL; + DistTableCacheEntry *cacheEntry = NULL; + + if (!EnableFastPathRouterPlanner) + { + return false; + } + + if (!(query->commandType == CMD_SELECT || query->commandType == CMD_UPDATE || + query->commandType == CMD_DELETE)) + { + return false; + } + + /* + * We want to deal with only very simple select queries. Some of the + * checks might be too restrictive, still we prefer this way. + */ + if (query->cteList != NIL || query->returningList != NIL || + query->hasSubLinks || query->setOperations != NULL || + query->hasTargetSRFs || query->hasModifyingCTE) + { + return false; + } + + /* make sure that the only range table in FROM clause */ + if (list_length(query->rtable) != 1) + { + return false; + } + + rangeTableEntry = (RangeTblEntry *) linitial(query->rtable); + if (rangeTableEntry->rtekind != RTE_RELATION) + { + return false; + } + + /* we don't want to deal with append/range distributed tables */ + distributedTableId = rangeTableEntry->relid; + cacheEntry = DistributedTableCacheEntry(distributedTableId); + if (!(cacheEntry->partitionMethod == DISTRIBUTE_BY_HASH || + cacheEntry->partitionMethod == DISTRIBUTE_BY_NONE)) + { + return false; + } + + /* + * hasForUpdate is tricky because Citus does support only when + * replication = 1 or reference tables. + */ + if (query->hasForUpdate) + { + if (cacheEntry->partitionMethod == DISTRIBUTE_BY_NONE || + SingleReplicatedTable(distributedTableId)) + { + return true; + } + + return false; + } + + /* WHERE clause should not be empty for distributed tables */ + if (joinTree == NULL || + (cacheEntry->partitionMethod != DISTRIBUTE_BY_NONE && joinTree->quals == NULL)) + { + return false; + } + + /* if that's a reference table, we don't need to check anything further */ + distributionKey = PartitionColumn(distributedTableId, 1); + if (!distributionKey) + { + return true; + } + + /* convert list of expressions into expression tree for further processing */ + quals = joinTree->quals; + if (quals != NULL && IsA(quals, List)) + { + quals = (Node *) make_ands_explicit((List *) quals); + } + + /* + * Distribution column must be used in a simple equality match check and it must be + * place at top level conjustion operator. In simple words, we should have + * WHERE dist_key = VALUE [AND ....]; + * + * We're also not allowing any other appearances of the distribution key in the quals. + * + * Overall the logic is might sound fuzzy since it involves two individual checks: + * (a) Check for top level AND operator with one side being "dist_key = const" + * (b) Only allow single appearance of "dist_key" in the quals + * + * This is to simplify both of the individual checks and omit various edge cases + * that might arise with multiple distribution keys in the quals. + */ + if (ConjunctionContainsColumnFilter(quals, distributionKey) && + !ColumnAppearsMultipleTimes(quals, distributionKey)) + { + return true; + } + + return false; +} + + +/* + * ColumnAppearsMultipleTimes returns true if the given input + * appears more than once in the quals. + */ +static bool +ColumnAppearsMultipleTimes(Node *quals, Var *distributionKey) +{ + ListCell *varClauseCell = NULL; + List *varClauseList = NIL; + int partitionColumnReferenceCount = 0; + + /* make sure partition column is used only once in the quals */ + varClauseList = pull_var_clause_default(quals); + foreach(varClauseCell, varClauseList) + { + Var *column = (Var *) lfirst(varClauseCell); + if (equal(column, distributionKey)) + { + partitionColumnReferenceCount++; + + if (partitionColumnReferenceCount > 1) + { + return true; + } + } + } + + return false; +} + + +/* + * ConjunctionContainsColumnFilter returns true if the query contains an exact + * match (equal) expression on the provided column. The function returns true only + * if the match expression has an AND relation with the rest of the expression tree. + */ +static bool +ConjunctionContainsColumnFilter(Node *node, Var *column) +{ + if (node == NULL) + { + return false; + } + + if (IsA(node, OpExpr)) + { + OpExpr *opExpr = (OpExpr *) node; + bool distKeyInSimpleOpExpression = + DistKeyInSimpleOpExpression((Expr *) opExpr, column); + + if (!distKeyInSimpleOpExpression) + { + return false; + } + + return OperatorImplementsEquality(opExpr->opno); + } + else if (IsA(node, BoolExpr)) + { + BoolExpr *boolExpr = (BoolExpr *) node; + List *argumentList = boolExpr->args; + ListCell *argumentCell = NULL; + + + /* + * We do not descend into boolean expressions other than AND. + * If the column filter appears in an OR clause, we do not + * consider it even if it is logically the same as a single value + * comparison (e.g. ` = OR false`) + */ + if (boolExpr->boolop != AND_EXPR) + { + return false; + } + + foreach(argumentCell, argumentList) + { + Node *argumentNode = (Node *) lfirst(argumentCell); + + if (ConjunctionContainsColumnFilter(argumentNode, column)) + { + return true; + } + } + } + + return false; +} + + +/* + * DistKeyInSimpleOpExpression checks whether given expression is a simple operator + * expression with either (dist_key = param) or (dist_key = const). Note that the + * operands could be in the reverse order as well. + */ +static bool +DistKeyInSimpleOpExpression(Expr *clause, Var *distColumn) +{ + Node *leftOperand = NULL; + Node *rightOperand = NULL; + Param *paramClause = NULL; + Const *constantClause = NULL; + + Var *columnInExpr = NULL; + + if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2) + { + leftOperand = get_leftop(clause); + rightOperand = get_rightop(clause); + } + else + { + return false; /* not a binary opclause */ + } + + /* strip coercions before doing check */ + leftOperand = strip_implicit_coercions(leftOperand); + rightOperand = strip_implicit_coercions(rightOperand); + + if (IsA(rightOperand, Param) && IsA(leftOperand, Var)) + { + paramClause = (Param *) rightOperand; + columnInExpr = (Var *) leftOperand; + } + else if (IsA(leftOperand, Param) && IsA(rightOperand, Var)) + { + paramClause = (Param *) leftOperand; + columnInExpr = (Var *) rightOperand; + } + else if (IsA(rightOperand, Const) && IsA(leftOperand, Var)) + { + constantClause = (Const *) rightOperand; + columnInExpr = (Var *) leftOperand; + } + else if (IsA(leftOperand, Const) && IsA(rightOperand, Var)) + { + constantClause = (Const *) leftOperand; + columnInExpr = (Var *) rightOperand; + } + else + { + return false; + } + + if (paramClause && paramClause->paramkind != PARAM_EXTERN) + { + /* we can only handle param_externs */ + return false; + } + else if (constantClause && constantClause->constisnull) + { + /* we can only handle non-null constants */ + return false; + } + + /* at this point we should have the columnInExpr */ + Assert(columnInExpr); + + return equal(distColumn, columnInExpr); +} diff --git a/src/backend/distributed/planner/multi_physical_planner.c b/src/backend/distributed/planner/multi_physical_planner.c index a61cdf835..eaecbb8e7 100644 --- a/src/backend/distributed/planner/multi_physical_planner.c +++ b/src/backend/distributed/planner/multi_physical_planner.c @@ -2005,13 +2005,11 @@ BuildJobTreeTaskList(Job *jobTree, PlannerRestrictionContext *plannerRestriction if (job->subqueryPushdown) { bool isMultiShardQuery = false; - List *prunedRelationShardList = TargetShardIntervalsForQuery(job->jobQuery, - plannerRestrictionContext - -> - relationRestrictionContext, - & - isMultiShardQuery, - NULL); + List *prunedRelationShardList = + TargetShardIntervalsForRestrictInfo(plannerRestrictionContext-> + relationRestrictionContext, + &isMultiShardQuery, NULL); + sqlTaskList = QueryPushdownSqlTaskList(job->jobQuery, job->jobId, plannerRestrictionContext-> relationRestrictionContext, diff --git a/src/backend/distributed/planner/multi_router_planner.c b/src/backend/distributed/planner/multi_router_planner.c index 4f34f13d9..666c5a207 100644 --- a/src/backend/distributed/planner/multi_router_planner.c +++ b/src/backend/distributed/planner/multi_router_planner.c @@ -146,6 +146,9 @@ static List * get_all_actual_clauses(List *restrictinfo_list); static int CompareInsertValuesByShardId(const void *leftElement, const void *rightElement); static uint64 GetInitialShardId(List *relationShardList); +static List * TargetShardIntervalForFastPathQuery(Query *query, + Const **partitionValueConst, + bool *isMultiShardQuery); static List * SingleShardSelectTaskList(Query *query, uint64 jobId, List *relationShardList, List *placementList, uint64 shardId); @@ -1886,11 +1889,46 @@ PlanRouterQuery(Query *originalQuery, *placementList = NIL; - prunedRelationShardList = TargetShardIntervalsForQuery(originalQuery, - plannerRestrictionContext-> - relationRestrictionContext, - &isMultiShardQuery, - partitionValueConst); + /* + * When FastPathRouterQuery() returns true, we know that standard_planner() has + * not been called. Thus, restriction information is not avaliable and we do the + * shard pruning based on the distribution column in the quals of the query. + */ + if (FastPathRouterQuery(originalQuery)) + { + List *shardIntervalList = + TargetShardIntervalForFastPathQuery(originalQuery, partitionValueConst, + &isMultiShardQuery); + + /* + * This could only happen when there is a parameter on the distribution key. + * We defer error here, later the planner is forced to use a generic plan + * by assigning arbitrarily high cost to the plan. + */ + if (UpdateOrDeleteQuery(originalQuery) && isMultiShardQuery) + { + planningError = DeferredError(ERRCODE_FEATURE_NOT_SUPPORTED, + "Router planner cannot handle multi-shard " + "modify queries", NULL, NULL); + return planningError; + } + + prunedRelationShardList = list_make1(shardIntervalList); + + if (!isMultiShardQuery) + { + ereport(DEBUG2, (errmsg("Distributed planning for a fast-path router " + "query"))); + } + } + else + { + prunedRelationShardList = + TargetShardIntervalsForRestrictInfo(plannerRestrictionContext-> + relationRestrictionContext, + &isMultiShardQuery, + partitionValueConst); + } if (isMultiShardQuery) { @@ -2065,19 +2103,59 @@ GetInitialShardId(List *relationShardList) /* - * TargetShardIntervalsForQuery performs shard pruning for all referenced relations - * in the query and returns list of shards per relation. Shard pruning is done based - * on provided restriction context per relation. The function sets multiShardQuery - * to true if any of the relations pruned down to more than one active shard. It - * also records pruned shard intervals in relation restriction context to be used - * later on. Some queries may have contradiction clauses like 'and false' or - * 'and 1=0', such queries are treated as if all of the shards of joining - * relations are pruned out. + * TargetShardIntervalForFastPathQuery gets a query which is in + * the form defined by FastPathRouterQuery() and returns exactly + * one shard interval (see FastPathRouterQuery() for the detail). + * + * Also set the outgoing partition column value if requested via + * partitionValueConst + */ +static List * +TargetShardIntervalForFastPathQuery(Query *query, Const **partitionValueConst, + bool *isMultiShardQuery) +{ + Const *queryPartitionValueConst = NULL; + + Oid relationId = ExtractFirstDistributedTableId(query); + Node *quals = query->jointree->quals; + + int relationIndex = 1; + + List *prunedShardIntervalList = + PruneShards(relationId, relationIndex, make_ands_implicit((Expr *) quals), + &queryPartitionValueConst); + + /* we're only expecting single shard from a single table */ + Assert(FastPathRouterQuery(query)); + + if (list_length(prunedShardIntervalList) > 1) + { + *isMultiShardQuery = true; + } + else if (list_length(prunedShardIntervalList) == 1 && + queryPartitionValueConst != NULL) + { + /* set the outgoing partition column value if requested */ + *partitionValueConst = queryPartitionValueConst; + } + + return prunedShardIntervalList; +} + + +/* + * TargetShardIntervalsForRestrictInfo performs shard pruning for all referenced + * relations in the relation restriction context and returns list of shards per + * relation. Shard pruning is done based on provided restriction context per relation. + * The function sets multiShardQuery to true if any of the relations pruned down to + * more than one active shard. It also records pruned shard intervals in relation + * restriction context to be used later on. Some queries may have contradiction + * clauses like 'and false' or 'and 1=0', such queries are treated as if all of + * the shards of joining relations are pruned out. */ List * -TargetShardIntervalsForQuery(Query *query, - RelationRestrictionContext *restrictionContext, - bool *multiShardQuery, Const **partitionValueConst) +TargetShardIntervalsForRestrictInfo(RelationRestrictionContext *restrictionContext, + bool *multiShardQuery, Const **partitionValueConst) { List *prunedRelationShardList = NIL; ListCell *restrictionCell = NULL; diff --git a/src/backend/distributed/planner/planner_readme.md b/src/backend/distributed/planner/planner_readme.md index e7a4f3049..c680963cf 100644 --- a/src/backend/distributed/planner/planner_readme.md +++ b/src/backend/distributed/planner/planner_readme.md @@ -2,14 +2,23 @@ The distributed query planner is entered through the `distributed_planner` function in `distributed_planner.c`. This is the hook that Postgres calls instead of `standard_planner`. -We always first call `standard_planner` to build a `PlannedStmt`. For queries containing a distributed table or reference table, we then proceed with distributed planning, which overwrites the `planTree` in the `PlannedStmt`. +If the input query is trivial (e.g., no joins, no subqueries/ctes, single table and single shard), we create a very simple `PlannedStmt`. If the query is not trivial, call `standard_planner` to build a `PlannedStmt`. For queries containing a distributed table or reference table, we then proceed with distributed planning, which overwrites the `planTree` in the `PlannedStmt`. Distributed planning (`CreateDistributedPlan`) tries several different methods to plan the query: - 1. Router planner, proceed if the query prunes down to a single set of co-located shards - 2. Modification planning, proceed if the query is a DML command and all joins are co-located - 3. Recursive planning, find CTEs and subqueries that cannot be pushed down and go back to 1 - 4. Logical planner, constructs a multi-relational algebra tree to find a distributed execution plan + + 1. Fast-path router planner, proceed if the query prunes down to a single shard of a single table + 2. Router planner, proceed if the query prunes down to a single set of co-located shards + 3. Modification planning, proceed if the query is a DML command and all joins are co-located + 4. Recursive planning, find CTEs and subqueries that cannot be pushed down and go back to 1 + 5. Logical planner, constructs a multi-relational algebra tree to find a distributed execution plan + +## Fast-path router planner + +By examining the query tree, if we can decide that the query hits only a single shard of a single table, we can skip calling `standard_planner()`. Later on the execution, we simply fetch the filter on the distribution key and do the pruning. + +As the name reveals, this can be considered as a sub-item of Router planner described below. The only difference is that fast-path planner doesn't rely on `standard_planner()` for collecting restriction information. + ## Router planner diff --git a/src/backend/distributed/shared_library_init.c b/src/backend/distributed/shared_library_init.c index 92f2d6ae2..037f517a8 100644 --- a/src/backend/distributed/shared_library_init.c +++ b/src/backend/distributed/shared_library_init.c @@ -411,6 +411,16 @@ RegisterCitusConfigVariables(void) GUC_NO_SHOW_ALL, NULL, NULL, NULL); + DefineCustomBoolVariable( + "citus.enable_fast_path_router_planner", + gettext_noop("Enables fast path router planner"), + NULL, + &EnableFastPathRouterPlanner, + true, + PGC_USERSET, + GUC_NO_SHOW_ALL, + NULL, NULL, NULL); + DefineCustomBoolVariable( "citus.override_table_visibility", gettext_noop("Enables replacing occurencens of pg_catalog.pg_table_visible() " diff --git a/src/include/distributed/distributed_planner.h b/src/include/distributed/distributed_planner.h index 03b27b8f5..4c76dcb49 100644 --- a/src/include/distributed/distributed_planner.h +++ b/src/include/distributed/distributed_planner.h @@ -97,6 +97,7 @@ extern bool IsModifyCommand(Query *query); extern bool IsUpdateOrDelete(struct DistributedPlan *distributedPlan); extern bool IsModifyDistributedPlan(struct DistributedPlan *distributedPlan); extern void EnsurePartitionTableNotReplicated(Oid relationId); +extern Node * ResolveExternalParams(Node *inputNode, ParamListInfo boundParams); extern bool IsMultiTaskPlan(struct DistributedPlan *distributedPlan); extern bool IsMultiShardModifyPlan(struct DistributedPlan *distributedPlan); extern RangeTblEntry * RemoteScanRangeTableEntry(List *columnNameList); diff --git a/src/include/distributed/multi_router_planner.h b/src/include/distributed/multi_router_planner.h index 833c0ce2f..4ba110e2d 100644 --- a/src/include/distributed/multi_router_planner.h +++ b/src/include/distributed/multi_router_planner.h @@ -25,6 +25,7 @@ #define CITUS_TABLE_ALIAS "citus_table_alias" extern bool EnableRouterExecution; +extern bool EnableFastPathRouterPlanner; extern DistributedPlan * CreateRouterPlan(Query *originalQuery, Query *query, PlannerRestrictionContext * @@ -42,10 +43,10 @@ extern DeferredErrorMessage * PlanRouterQuery(Query *originalQuery, Const **partitionValueConst); extern List * RouterInsertTaskList(Query *query, DeferredErrorMessage **planningError); extern Const * ExtractInsertPartitionKeyValue(Query *query); -extern List * TargetShardIntervalsForQuery(Query *query, - RelationRestrictionContext *restrictionContext, - bool *multiShardQuery, - Const **partitionValueConst); +extern List * TargetShardIntervalsForRestrictInfo(RelationRestrictionContext * + restrictionContext, + bool *multiShardQuery, + Const **partitionValueConst); extern List * WorkersContainingAllShards(List *prunedShardIntervalsList); extern List * IntersectPlacementList(List *lhsPlacementList, List *rhsPlacementList); extern DeferredErrorMessage * ModifyQuerySupported(Query *queryTree, Query *originalQuery, @@ -68,5 +69,13 @@ extern void AddShardIntervalRestrictionToSelect(Query *subqery, extern bool UpdateOrDeleteQuery(Query *query); extern List * WorkersContainingAllShards(List *prunedShardIntervalsList); +/* + * FastPathPlanner is a subset of router planner, that's why we prefer to + * keep the external function here. + */extern PlannedStmt * GeneratePlaceHolderPlannedStmt(Query *parse); + +extern PlannedStmt * FastPathPlanner(Query *originalQuery, Query *parse, ParamListInfo + boundParams); +extern bool FastPathRouterQuery(Query *query); #endif /* MULTI_ROUTER_PLANNER_H */ diff --git a/src/test/regress/expected/fast_path_router_modify.out b/src/test/regress/expected/fast_path_router_modify.out new file mode 100644 index 000000000..641c379b9 --- /dev/null +++ b/src/test/regress/expected/fast_path_router_modify.out @@ -0,0 +1,362 @@ +CREATE SCHEMA fast_path_router_modify; +SET search_path TO fast_path_router_modify; +SET citus.next_shard_id TO 1840000; +-- all the tests in this file is intended for testing fast-path +-- router planner, so we're explicitly enabling itin this file. +-- We've bunch of other tests that triggers non-fast-path-router +-- planner (note this is already true by default) +SET citus.enable_fast_path_router_planner TO true; +SET citus.shard_replication_factor TO 1; +CREATE TABLE modify_fast_path(key int, value_1 int, value_2 text); +SELECT create_distributed_table('modify_fast_path', 'key'); + create_distributed_table +-------------------------- + +(1 row) + +SET citus.shard_replication_factor TO 2; +CREATE TABLE modify_fast_path_replication_2(key int, value_1 int, value_2 text); +SELECT create_distributed_table('modify_fast_path_replication_2', 'key'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE modify_fast_path_reference(key int, value_1 int, value_2 text); +SELECT create_reference_table('modify_fast_path_reference'); + create_reference_table +------------------------ + +(1 row) + +-- show the output +SET client_min_messages TO DEBUG; +-- very simple queries goes through fast-path planning +DELETE FROM modify_fast_path WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +UPDATE modify_fast_path SET value_1 = 1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +UPDATE modify_fast_path SET value_1 = value_1 + value_2::int WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +DELETE FROM modify_fast_path WHERE value_1 = 15 AND (key = 1 AND value_2 = 'citus'); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +DELETE FROM modify_fast_path WHERE key = 1 and FALSE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +-- UPDATE may include complex target entries +UPDATE modify_fast_path SET value_1 = value_1 + 12 * value_1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +UPDATE modify_fast_path SET value_1 = abs(-19) WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +-- cannot go through fast-path because there are multiple keys +DELETE FROM modify_fast_path WHERE key = 1 AND key = 2; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DELETE FROM modify_fast_path WHERE key = 1 AND (key = 2 AND value_1 = 15); +DEBUG: Creating router plan +DEBUG: Plan is router executable +-- cannot go through fast-path because key is not on the top level +DELETE FROM modify_fast_path WHERE value_1 = 15 OR (key = 1 AND value_2 = 'citus'); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DELETE FROM modify_fast_path WHERE value_1 = 15 AND (key = 1 OR value_2 = 'citus'); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +-- goes through fast-path planning even if the key is updated to the same value +UPDATE modify_fast_path SET key = 1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +UPDATE modify_fast_path SET key = 1::float WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +-- cannot support if key changes +UPDATE modify_fast_path SET key = 2 WHERE key = 1; +DEBUG: modifying the partition value of rows is not allowed +ERROR: modifying the partition value of rows is not allowed +UPDATE modify_fast_path SET key = 2::numeric WHERE key = 1; +DEBUG: modifying the partition value of rows is not allowed +ERROR: modifying the partition value of rows is not allowed +-- returning is not supported via fast-path +DELETE FROM modify_fast_path WHERE key = 1 RETURNING *; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + key | value_1 | value_2 +-----+---------+--------- +(0 rows) + +-- modifying ctes are not supported via fast-path +WITH t1 AS (DELETE FROM modify_fast_path WHERE key = 1), t2 AS (SELECT * FROM modify_fast_path) SELECT * FROM t2; +DEBUG: data-modifying statements are not supported in the WITH clauses of distributed queries +DEBUG: generating subplan 18_1 for CTE t1: DELETE FROM fast_path_router_modify.modify_fast_path WHERE (key OPERATOR(pg_catalog.=) 1) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +DEBUG: generating subplan 18_2 for CTE t2: SELECT key, value_1, value_2 FROM fast_path_router_modify.modify_fast_path +DEBUG: Plan 18 query after replacing subqueries and CTEs: SELECT key, value_1, value_2 FROM (SELECT intermediate_result.key, intermediate_result.value_1, intermediate_result.value_2 FROM read_intermediate_result('18_2'::text, 'binary'::citus_copy_format) intermediate_result(key integer, value_1 integer, value_2 text)) t2 +DEBUG: Creating router plan +DEBUG: Plan is router executable + key | value_1 | value_2 +-----+---------+--------- +(0 rows) + +-- for update/share is supported via fast-path when replication factor = 1 or reference table +SELECT * FROM modify_fast_path WHERE key = 1 FOR UPDATE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + key | value_1 | value_2 +-----+---------+--------- +(0 rows) + +SELECT * FROM modify_fast_path WHERE key = 1 FOR SHARE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + key | value_1 | value_2 +-----+---------+--------- +(0 rows) + +SELECT * FROM modify_fast_path_reference WHERE key = 1 FOR UPDATE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + key | value_1 | value_2 +-----+---------+--------- +(0 rows) + +SELECT * FROM modify_fast_path_reference WHERE key = 1 FOR SHARE; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + key | value_1 | value_2 +-----+---------+--------- +(0 rows) + +-- for update/share is not supported via fast-path wen replication factor > 1 +SELECT * FROM modify_fast_path_replication_2 WHERE key = 1 FOR UPDATE; +ERROR: could not run distributed query with FOR UPDATE/SHARE commands +HINT: Consider using an equality filter on the distributed table's partition column. +SELECT * FROM modify_fast_path_replication_2 WHERE key = 1 FOR SHARE; +ERROR: could not run distributed query with FOR UPDATE/SHARE commands +HINT: Consider using an equality filter on the distributed table's partition column. +-- very simple queries on reference tables goes through fast-path planning +DELETE FROM modify_fast_path_reference WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +UPDATE modify_fast_path_reference SET value_1 = 1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +UPDATE modify_fast_path_reference SET value_1 = value_1 + 1 WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +UPDATE modify_fast_path_reference SET value_1 = value_1 + value_2::int WHERE key = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +-- joins are not supported via fast-path +UPDATE modify_fast_path + SET value_1 = 1 + FROM modify_fast_path_reference + WHERE + modify_fast_path.key = modify_fast_path_reference.key AND + modify_fast_path.key = 1 AND + modify_fast_path_reference.key = 1; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +PREPARE p1 (int, int, int) AS + UPDATE modify_fast_path SET value_1 = value_1 + $1 WHERE key = $2 AND value_1 = $3; +EXECUTE p1(1,1,1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +EXECUTE p1(2,2,2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 +EXECUTE p1(3,3,3); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 3 +EXECUTE p1(4,4,4); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 +EXECUTE p1(5,5,5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 +EXECUTE p1(6,6,6); +DEBUG: Router planner cannot handle multi-shard modify queries +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 6 +CREATE FUNCTION modify_fast_path_plpsql(int, int) RETURNS void as $$ +BEGIN + DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2; +END; +$$ LANGUAGE plpgsql; +SELECT modify_fast_path_plpsql(1,1); +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +SELECT modify_fast_path_plpsql(2,2); +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +SELECT modify_fast_path_plpsql(3,3); +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 3 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +SELECT modify_fast_path_plpsql(4,4); +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +SELECT modify_fast_path_plpsql(5,5); +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +SELECT modify_fast_path_plpsql(6,6); +DEBUG: Router planner cannot handle multi-shard modify queries +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 6 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +SELECT modify_fast_path_plpsql(6,6); +DEBUG: Distributed planning for a fast-path router query +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Creating router plan +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement +DEBUG: Plan is router executable +DETAIL: distribution column value: 6 +CONTEXT: SQL statement "DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2" +PL/pgSQL function modify_fast_path_plpsql(integer,integer) line 3 at SQL statement + modify_fast_path_plpsql +------------------------- + +(1 row) + +RESET client_min_messages; +DROP SCHEMA fast_path_router_modify CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table modify_fast_path +drop cascades to table modify_fast_path_replication_2 +drop cascades to table modify_fast_path_reference +drop cascades to function modify_fast_path_plpsql(integer,integer) diff --git a/src/test/regress/expected/multi_function_evaluation.out b/src/test/regress/expected/multi_function_evaluation.out index 7a3c05584..52bedb6fc 100644 --- a/src/test/regress/expected/multi_function_evaluation.out +++ b/src/test/regress/expected/multi_function_evaluation.out @@ -2,6 +2,10 @@ -- MULTI_FUNCTION_EVALUATION -- SET citus.next_shard_id TO 1200000; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- nextval() works (no good way to test DEFAULT, or, by extension, SERIAL) CREATE TABLE example (key INT, value INT); SELECT master_create_distributed_table('example', 'key', 'hash'); diff --git a/src/test/regress/expected/multi_hash_pruning.out b/src/test/regress/expected/multi_hash_pruning.out index bd409ecdf..0e923e25e 100644 --- a/src/test/regress/expected/multi_hash_pruning.out +++ b/src/test/regress/expected/multi_hash_pruning.out @@ -5,6 +5,10 @@ SET citus.next_shard_id TO 630000; SET citus.shard_count to 4; SET citus.shard_replication_factor to 1; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- Create a table partitioned on integer column and update partition type to -- hash. Then load data into this table and update shard min max values with -- hashed ones. Hash value of 1, 2, 3 and 4 are consecutively -1905060026, diff --git a/src/test/regress/expected/multi_insert_select.out b/src/test/regress/expected/multi_insert_select.out index 89b382f0a..88ac0d400 100644 --- a/src/test/regress/expected/multi_insert_select.out +++ b/src/test/regress/expected/multi_insert_select.out @@ -1179,6 +1179,7 @@ FROM DEBUG: cannot perform distributed INSERT INTO ... SELECT because the partition columns in the source table and subquery do not match DETAIL: The target table's partition column should correspond to a partition column in the subquery. DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable -- foo2 is recursively planned and INSERT...SELECT is done via coordinator diff --git a/src/test/regress/expected/multi_mx_router_planner.out b/src/test/regress/expected/multi_mx_router_planner.out index 9ef20e5ff..409b57286 100644 --- a/src/test/regress/expected/multi_mx_router_planner.out +++ b/src/test/regress/expected/multi_mx_router_planner.out @@ -64,6 +64,10 @@ DEBUG: Creating router plan DEBUG: Plan is router executable DETAIL: distribution column value: 10 -- single-shard tests +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- test simple select for a single row SELECT * FROM articles_hash_mx WHERE author_id = 10 AND id = 50; DEBUG: Creating router plan diff --git a/src/test/regress/expected/multi_prepare_plsql.out b/src/test/regress/expected/multi_prepare_plsql.out index 72f98a03c..4c01a183c 100644 --- a/src/test/regress/expected/multi_prepare_plsql.out +++ b/src/test/regress/expected/multi_prepare_plsql.out @@ -4,6 +4,10 @@ -- Many of the queries are taken from other regression test files -- and converted into both plain SQL and PL/pgsql functions, which -- use prepared statements internally. +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; CREATE FUNCTION plpgsql_test_1() RETURNS TABLE(count bigint) AS $$ DECLARE BEGIN diff --git a/src/test/regress/expected/multi_prepare_sql.out b/src/test/regress/expected/multi_prepare_sql.out index 5224d6ab4..94be852ff 100644 --- a/src/test/regress/expected/multi_prepare_sql.out +++ b/src/test/regress/expected/multi_prepare_sql.out @@ -1,6 +1,10 @@ -- -- MULTI_PREPARE_SQL -- +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- Tests covering PREPARE statements. Many of the queries are -- taken from other regression test files and converted into -- prepared statements. diff --git a/src/test/regress/expected/multi_router_planner.out b/src/test/regress/expected/multi_router_planner.out index 76e3c23e0..b3a9eaf10 100644 --- a/src/test/regress/expected/multi_router_planner.out +++ b/src/test/regress/expected/multi_router_planner.out @@ -2,6 +2,10 @@ SET citus.next_shard_id TO 840000; -- =================================================================== -- test router planner functionality for single shard select queries -- =================================================================== +-- all the tests in this file is intended for testing non-fast-path +-- router planner, so we're disabling it in this file. We've bunch of +-- other tests that triggers fast-path-router planner +SET citus.enable_fast_path_router_planner TO false; CREATE TABLE articles_hash ( id bigint NOT NULL, author_id bigint NOT NULL, diff --git a/src/test/regress/expected/multi_router_planner_fast_path.out b/src/test/regress/expected/multi_router_planner_fast_path.out new file mode 100644 index 000000000..f6502d87a --- /dev/null +++ b/src/test/regress/expected/multi_router_planner_fast_path.out @@ -0,0 +1,2204 @@ +CREATE SCHEMA fast_path_router_select; +SET search_path TO fast_path_router_select; +SET citus.next_shard_id TO 1840000; +-- all the tests in this file is intended for testing fast-path +-- router planner, so we're explicitly enabling itin this file. +-- We've bunch of other tests that triggers non-fast-path-router +-- planner (note this is already true by default) +SET citus.enable_fast_path_router_planner TO true; +-- =================================================================== +-- test router planner functionality for via fast path on +-- single shard select queries +-- =================================================================== +CREATE TABLE articles_hash ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); +CREATE TABLE articles_range ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); +CREATE TABLE articles_append ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); +-- Check for the existence of line 'DEBUG: Creating router plan' +-- to determine if router planner is used. +-- this table is used in a CTE test +CREATE TABLE authors_hash ( name varchar(20), id bigint ); +CREATE TABLE authors_range ( name varchar(20), id bigint ); +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 2; +SELECT create_distributed_table('articles_hash', 'author_id'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE authors_reference ( name varchar(20), id bigint ); +SELECT create_reference_table('authors_reference'); + create_reference_table +------------------------ + +(1 row) + +-- create a bunch of test data +INSERT INTO articles_hash VALUES (1, 1, 'arsenous', 9572), (2, 2, 'abducing', 13642),( 3, 3, 'asternal', 10480),( 4, 4, 'altdorfer', 14551),( 5, 5, 'aruru', 11389), + (6, 6, 'atlases', 15459),(7, 7, 'aseptic', 12298),( 8, 8, 'agatized', 16368),(9, 9, 'alligate', 438), + (10, 10, 'aggrandize', 17277),(11, 1, 'alamo', 1347),(12, 2, 'archiblast', 18185), + (13, 3, 'aseyev', 2255),(14, 4, 'andesite', 19094),(15, 5, 'adversa', 3164), + (16, 6, 'allonym', 2),(17, 7, 'auriga', 4073),(18, 8, 'assembly', 911),(19, 9, 'aubergiste', 4981), + (20, 10, 'absentness', 1820),(21, 1, 'arcading', 5890),(22, 2, 'antipope', 2728),(23, 3, 'abhorring', 6799), + (24, 4, 'audacious', 3637),(25, 5, 'antehall', 7707),(26, 6, 'abington', 4545),(27, 7, 'arsenous', 8616), + (28, 8, 'aerophyte', 5454),(29, 9, 'amateur', 9524),(30, 10, 'andelee', 6363),(31, 1, 'athwartships', 7271), + (32, 2, 'amazon', 11342),(33, 3, 'autochrome', 8180),(34, 4, 'amnestied', 12250),(35, 5, 'aminate', 9089), + (36, 6, 'ablation', 13159),(37, 7, 'archduchies', 9997),(38, 8, 'anatine', 14067),(39, 9, 'anchises', 10906), + (40, 10, 'attemper', 14976),(41, 1, 'aznavour', 11814),(42, 2, 'ausable', 15885),(43, 3, 'affixal', 12723), + (44, 4, 'anteport', 16793),(45, 5, 'afrasia', 864),(46, 6, 'atlanta', 17702),(47, 7, 'abeyance', 1772), + (48, 8, 'alkylic', 18610),(49, 9, 'anyone', 2681),(50, 10, 'anjanette', 19519); +SET citus.task_executor_type TO 'real-time'; +SET client_min_messages TO 'DEBUG2'; +-- test simple select for a single row +SELECT * FROM articles_hash WHERE author_id = 10 AND id = 50; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 10 + id | author_id | title | word_count +----+-----------+-----------+------------ + 50 | 10 | anjanette | 19519 +(1 row) + +-- get all titles by a single author +SELECT title FROM articles_hash WHERE author_id = 10; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 10 + title +------------ + aggrandize + absentness + andelee + attemper + anjanette +(5 rows) + +-- try ordering them by word count +SELECT title, word_count FROM articles_hash + WHERE author_id = 10 + ORDER BY word_count DESC NULLS LAST; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 10 + title | word_count +------------+------------ + anjanette | 19519 + aggrandize | 17277 + attemper | 14976 + andelee | 6363 + absentness | 1820 +(5 rows) + +-- look at last two articles by an author +SELECT title, id FROM articles_hash + WHERE author_id = 5 + ORDER BY id + LIMIT 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 + title | id +---------+---- + aruru | 5 + adversa | 15 +(2 rows) + +-- find all articles by two authors in same shard +-- but plan is not fast path router plannable due to +-- two distribution columns in the query +SELECT title, author_id FROM articles_hash + WHERE author_id = 7 OR author_id = 8 + ORDER BY author_id ASC, id; +DEBUG: Creating router plan +DEBUG: Plan is router executable + title | author_id +-------------+----------- + aseptic | 7 + auriga | 7 + arsenous | 7 + archduchies | 7 + abeyance | 7 + agatized | 8 + assembly | 8 + aerophyte | 8 + anatine | 8 + alkylic | 8 +(10 rows) + +-- having clause is supported if it goes to a single shard +-- and single dist. key on the query +SELECT author_id, sum(word_count) AS corpus_size FROM articles_hash + WHERE author_id = 1 + GROUP BY author_id + HAVING sum(word_count) > 1000 + ORDER BY sum(word_count) DESC; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + author_id | corpus_size +-----------+------------- + 1 | 35894 +(1 row) + +-- fast path planner only support = operator +SELECT * FROM articles_hash WHERE author_id <= 1; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +SELECT * FROM articles_hash WHERE author_id IN (1, 3); +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 3 | 3 | asternal | 10480 + 11 | 1 | alamo | 1347 + 13 | 3 | aseyev | 2255 + 21 | 1 | arcading | 5890 + 23 | 3 | abhorring | 6799 + 31 | 1 | athwartships | 7271 + 33 | 3 | autochrome | 8180 + 41 | 1 | aznavour | 11814 + 43 | 3 | affixal | 12723 +(10 rows) + +-- queries with CTEs cannot go through fast-path planning +WITH first_author AS ( SELECT id FROM articles_hash WHERE author_id = 1) +SELECT * FROM first_author; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id +---- + 1 + 11 + 21 + 31 + 41 +(5 rows) + +-- two CTE joins also cannot go through fast-path planning +WITH id_author AS ( SELECT id, author_id FROM articles_hash WHERE author_id = 1), +id_title AS (SELECT id, title from articles_hash WHERE author_id = 1) +SELECT * FROM id_author, id_title WHERE id_author.id = id_title.id; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | id | title +----+-----------+----+-------------- + 1 | 1 | 1 | arsenous + 11 | 1 | 11 | alamo + 21 | 1 | 21 | arcading + 31 | 1 | 31 | athwartships + 41 | 1 | 41 | aznavour +(5 rows) + +-- this is a different case where each CTE is recursively planned and those goes +-- through the fast-path router planner, but the top level join is not +WITH id_author AS ( SELECT id, author_id FROM articles_hash WHERE author_id = 1), +id_title AS (SELECT id, title from articles_hash WHERE author_id = 2) +SELECT * FROM id_author, id_title WHERE id_author.id = id_title.id; +DEBUG: generating subplan 12_1 for CTE id_author: SELECT id, author_id FROM fast_path_router_select.articles_hash WHERE (author_id OPERATOR(pg_catalog.=) 1) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +DEBUG: generating subplan 12_2 for CTE id_title: SELECT id, title FROM fast_path_router_select.articles_hash WHERE (author_id OPERATOR(pg_catalog.=) 2) +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 +DEBUG: Plan 12 query after replacing subqueries and CTEs: SELECT id_author.id, id_author.author_id, id_title.id, id_title.title FROM (SELECT intermediate_result.id, intermediate_result.author_id FROM read_intermediate_result('12_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, author_id bigint)) id_author, (SELECT intermediate_result.id, intermediate_result.title FROM read_intermediate_result('12_2'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, title character varying(20))) id_title WHERE (id_author.id OPERATOR(pg_catalog.=) id_title.id) +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | id | title +----+-----------+----+------- +(0 rows) + +CREATE TABLE company_employees (company_id int, employee_id int, manager_id int); +SELECT master_create_distributed_table('company_employees', 'company_id', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('company_employees', 4, 1); +DEBUG: schema "fast_path_router_select" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "fast_path_router_select" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "fast_path_router_select" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "fast_path_router_select" already exists, skipping +DETAIL: NOTICE from localhost:57638 + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO company_employees values(1, 1, 0); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +INSERT INTO company_employees values(1, 2, 1); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +INSERT INTO company_employees values(1, 3, 1); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +INSERT INTO company_employees values(1, 4, 2); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +INSERT INTO company_employees values(1, 5, 4); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +INSERT INTO company_employees values(3, 1, 0); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 3 +INSERT INTO company_employees values(3, 15, 1); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 3 +INSERT INTO company_employees values(3, 3, 1); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 3 +-- recursive CTEs are also cannot go through fast +-- path planning +WITH RECURSIVE hierarchy as ( + SELECT *, 1 AS level + FROM company_employees + WHERE company_id = 1 and manager_id = 0 + UNION + SELECT ce.*, (h.level+1) + FROM hierarchy h JOIN company_employees ce + ON (h.employee_id = ce.manager_id AND + h.company_id = ce.company_id AND + ce.company_id = 1)) +SELECT * FROM hierarchy WHERE LEVEL <= 2; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + company_id | employee_id | manager_id | level +------------+-------------+------------+------- + 1 | 1 | 0 | 1 + 1 | 2 | 1 | 2 + 1 | 3 | 1 | 2 +(3 rows) + +WITH update_article AS ( + UPDATE articles_hash SET word_count = 10 WHERE id = 1 AND word_count = 9 RETURNING * +) +SELECT * FROM update_article; +DEBUG: data-modifying statements are not supported in the WITH clauses of distributed queries +DEBUG: generating subplan 24_1 for CTE update_article: UPDATE fast_path_router_select.articles_hash SET word_count = 10 WHERE ((id OPERATOR(pg_catalog.=) 1) AND (word_count OPERATOR(pg_catalog.=) 9)) RETURNING id, author_id, title, word_count +DEBUG: Creating router plan +DEBUG: Plan is router executable +DEBUG: Plan 24 query after replacing subqueries and CTEs: SELECT id, author_id, title, word_count FROM (SELECT intermediate_result.id, intermediate_result.author_id, intermediate_result.title, intermediate_result.word_count FROM read_intermediate_result('24_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, author_id bigint, title character varying(20), word_count integer)) update_article +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +WITH delete_article AS ( + DELETE FROM articles_hash WHERE id = 1 AND word_count = 10 RETURNING * +) +SELECT * FROM delete_article; +DEBUG: data-modifying statements are not supported in the WITH clauses of distributed queries +DEBUG: generating subplan 26_1 for CTE delete_article: DELETE FROM fast_path_router_select.articles_hash WHERE ((id OPERATOR(pg_catalog.=) 1) AND (word_count OPERATOR(pg_catalog.=) 10)) RETURNING id, author_id, title, word_count +DEBUG: Creating router plan +DEBUG: Plan is router executable +DEBUG: Plan 26 query after replacing subqueries and CTEs: SELECT id, author_id, title, word_count FROM (SELECT intermediate_result.id, intermediate_result.author_id, intermediate_result.title, intermediate_result.word_count FROM read_intermediate_result('26_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, author_id bigint, title character varying(20), word_count integer)) delete_article +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- grouping sets are supported via fast-path +SELECT + id, substring(title, 2, 1) AS subtitle, count(*) + FROM articles_hash + WHERE author_id = 1 + GROUP BY GROUPING SETS ((id),(subtitle)) + ORDER BY id, subtitle; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | subtitle | count +----+----------+------- + 1 | | 1 + 11 | | 1 + 21 | | 1 + 31 | | 1 + 41 | | 1 + | l | 1 + | r | 2 + | t | 1 + | z | 1 +(9 rows) + +-- grouping sets are not supported with multiple quals +SELECT + id, substring(title, 2, 1) AS subtitle, count(*) + FROM articles_hash + WHERE author_id = 1 or author_id = 2 + GROUP BY GROUPING SETS ((id),(subtitle)) + ORDER BY id, subtitle; +ERROR: could not run distributed query with GROUPING SETS, CUBE, or ROLLUP +HINT: Consider using an equality filter on the distributed table's partition column. +-- queries which involve functions in FROM clause are not supported via fast path planning +SELECT * FROM articles_hash, position('om' in 'Thomas') WHERE author_id = 1; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count | position +----+-----------+--------------+------------+---------- + 1 | 1 | arsenous | 9572 | 3 + 11 | 1 | alamo | 1347 | 3 + 21 | 1 | arcading | 5890 | 3 + 31 | 1 | athwartships | 7271 | 3 + 41 | 1 | aznavour | 11814 | 3 +(5 rows) + +-- sublinks are not supported via fast path planning +SELECT * FROM articles_hash +WHERE author_id IN (SELECT author_id FROM articles_hash WHERE author_id = 2) +ORDER BY articles_hash.id; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + id | author_id | title | word_count +----+-----------+------------+------------ + 2 | 2 | abducing | 13642 + 12 | 2 | archiblast | 18185 + 22 | 2 | antipope | 2728 + 32 | 2 | amazon | 11342 + 42 | 2 | ausable | 15885 +(5 rows) + +-- subqueries are not supported via fast path planning +SELECT articles_hash.id,test.word_count +FROM articles_hash, (SELECT id, word_count FROM articles_hash) AS test WHERE test.id = articles_hash.id +ORDER BY test.word_count DESC, articles_hash.id LIMIT 5; +DEBUG: generating subplan 32_1 for subquery SELECT id, word_count FROM fast_path_router_select.articles_hash +DEBUG: Plan 32 query after replacing subqueries and CTEs: SELECT articles_hash.id, test.word_count FROM fast_path_router_select.articles_hash, (SELECT intermediate_result.id, intermediate_result.word_count FROM read_intermediate_result('32_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, word_count integer)) test WHERE (test.id OPERATOR(pg_catalog.=) articles_hash.id) ORDER BY test.word_count DESC, articles_hash.id LIMIT 5 +DEBUG: push down of limit count: 5 + id | word_count +----+------------ + 50 | 19519 + 14 | 19094 + 48 | 18610 + 12 | 18185 + 46 | 17702 +(5 rows) + +SELECT articles_hash.id,test.word_count +FROM articles_hash, (SELECT id, word_count FROM articles_hash) AS test +WHERE test.id = articles_hash.id and articles_hash.author_id = 1 +ORDER BY articles_hash.id; +DEBUG: generating subplan 34_1 for subquery SELECT id, word_count FROM fast_path_router_select.articles_hash +DEBUG: Plan 34 query after replacing subqueries and CTEs: SELECT articles_hash.id, test.word_count FROM fast_path_router_select.articles_hash, (SELECT intermediate_result.id, intermediate_result.word_count FROM read_intermediate_result('34_1'::text, 'binary'::citus_copy_format) intermediate_result(id bigint, word_count integer)) test WHERE ((test.id OPERATOR(pg_catalog.=) articles_hash.id) AND (articles_hash.author_id OPERATOR(pg_catalog.=) 1)) ORDER BY articles_hash.id +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +-- subqueries are not supported in SELECT clause +SELECT a.title AS name, (SELECT a2.id FROM articles_hash a2 WHERE a.id = a2.id LIMIT 1) + AS special_price FROM articles_hash a; +DEBUG: skipping recursive planning for the subquery since it contains references to outer queries +ERROR: could not run distributed query with subquery outside the FROM and WHERE clauses +HINT: Consider using an equality filter on the distributed table's partition column. +-- simple lookup query just works +SELECT * + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- below query hits a single shard but with multiple filters +-- so cannot go via fast-path +SELECT * + FROM articles_hash + WHERE author_id = 1 OR author_id = 17; +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- rename the output columns +SELECT id as article_id, word_count * id as random_value + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + article_id | random_value +------------+-------------- + 1 | 9572 + 11 | 14817 + 21 | 123690 + 31 | 225401 + 41 | 484374 +(5 rows) + +-- joins do not go through fast-path planning +SELECT a.author_id as first_author, b.word_count as second_word_count + FROM articles_hash a, articles_hash b + WHERE a.author_id = 10 and a.author_id = b.author_id + LIMIT 3; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 10 + first_author | second_word_count +--------------+------------------- + 10 | 17277 + 10 | 1820 + 10 | 6363 +(3 rows) + +-- single shard select with limit goes through fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + LIMIT 3; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 +(3 rows) + +-- single shard select with limit + offset goes through fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + LIMIT 2 + OFFSET 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+----------+------------ + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 +(2 rows) + +-- single shard select with limit + offset + order by goes through fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id desc + LIMIT 2 + OFFSET 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 31 | 1 | athwartships | 7271 + 21 | 1 | arcading | 5890 +(2 rows) + + +-- single shard select with group by on non-partition column goes through fast-path planning +SELECT id + FROM articles_hash + WHERE author_id = 1 + GROUP BY id + ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id +---- + 1 + 11 + 21 + 31 + 41 +(5 rows) + +-- single shard select with distinct goes through fast-path planning +SELECT DISTINCT id + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id +---- + 1 + 11 + 21 + 31 + 41 +(5 rows) + +-- single shard aggregate goes through fast-path planning +SELECT avg(word_count) + FROM articles_hash + WHERE author_id = 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + avg +-------------------- + 12356.400000000000 +(1 row) + +-- max, min, sum, count goes through fast-path planning +SELECT max(word_count) as max, min(word_count) as min, + sum(word_count) as sum, count(word_count) as cnt + FROM articles_hash + WHERE author_id = 2; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + max | min | sum | cnt +-------+------+-------+----- + 18185 | 2728 | 61782 | 5 +(1 row) + +-- queries with aggregates and group by goes through fast-path planning +SELECT max(word_count) + FROM articles_hash + WHERE author_id = 1 + GROUP BY author_id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + max +------- + 11814 +(1 row) + +-- set operations are not supported via fast-path planning +SELECT * FROM ( + SELECT * FROM articles_hash WHERE author_id = 1 + UNION + SELECT * FROM articles_hash WHERE author_id = 3 +) AS combination +ORDER BY id; +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 3 | 3 | asternal | 10480 + 11 | 1 | alamo | 1347 + 13 | 3 | aseyev | 2255 + 21 | 1 | arcading | 5890 + 23 | 3 | abhorring | 6799 + 31 | 1 | athwartships | 7271 + 33 | 3 | autochrome | 8180 + 41 | 1 | aznavour | 11814 + 43 | 3 | affixal | 12723 +(10 rows) + +-- function calls in the target list is supported via fast path +SELECT LEFT(title, 1) FROM articles_hash WHERE author_id = 1 +-- top-level union queries are supported through recursive planning +SET client_min_messages to 'NOTICE'; +ERROR: syntax error at or near "SET" +LINE 3: SET client_min_messages to 'NOTICE'; + ^ +-- unions in subqueries are not supported via fast-path planning +SELECT * FROM ( + (SELECT * FROM articles_hash WHERE author_id = 1) + UNION + (SELECT * FROM articles_hash WHERE author_id = 1)) uu +ORDER BY 1, 2 +LIMIT 5; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- Test various filtering options for router plannable check +SET client_min_messages to 'DEBUG2'; +-- cannot go through fast-path if there is +-- explicit coercion +SELECT * + FROM articles_hash + WHERE author_id = 1::bigint; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- can go through fast-path if there is +-- implicit coercion +-- This doesn't work see the related issue +-- reported https://github.com/citusdata/citus/issues/2605 +-- SELECT * +-- FROM articles_hash +-- WHERE author_id = 1.0; +SELECT * + FROM articles_hash + WHERE author_id = 68719476736; -- this is bigint +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 68719476736 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- cannot go through fast-path due to +-- multiple filters on the dist. key +SELECT * + FROM articles_hash + WHERE author_id = 1 and author_id >= 1; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- cannot go through fast-path due to +-- multiple filters on the dist. key +SELECT * + FROM articles_hash + WHERE author_id = 1 or id = 1; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + + +-- goes through fast-path planning because +-- the dist. key is ANDed with the rest of the +-- filters +SELECT * + FROM articles_hash + WHERE author_id = 1 and (id = 1 or id = 41); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 + 41 | 1 | aznavour | 11814 +(2 rows) + +-- this time there is an OR clause which prevents +-- router planning at all +SELECT * + FROM articles_hash + WHERE author_id = 1 and id = 1 or id = 41; + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 + 41 | 1 | aznavour | 11814 +(2 rows) + +-- goes through fast-path planning because +-- the dist. key is ANDed with the rest of the +-- filters +SELECT * + FROM articles_hash + WHERE author_id = 1 and (id = random()::int * 0); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- not router plannable due to function call on the right side +SELECT * + FROM articles_hash + WHERE author_id = (random()::int * 0 + 1); + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + + +-- Citus does not qualify this as a fast-path because +-- dist_key = func() +SELECT * + FROM articles_hash + WHERE author_id = abs(-1); +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- Citus does not qualify this as a fast-path because +-- dist_key = func() +SELECT * + FROM articles_hash + WHERE 1 = abs(author_id); + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- Citus does not qualify this as a fast-path because +-- dist_key = func() +SELECT * + FROM articles_hash + WHERE author_id = abs(author_id - 2); + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- the function is not on the dist. key, so qualify as +-- fast-path +SELECT * + FROM articles_hash + WHERE author_id = 1 and (id = abs(id - 2)); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 +(1 row) + +-- not router plannable due to is true +SELECT * + FROM articles_hash + WHERE (author_id = 1) is true; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- router plannable, (boolean expression) = true is collapsed to (boolean expression) +SELECT * + FROM articles_hash + WHERE (author_id = 1) = true; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- some more complex quals +SELECT count(*) FROM articles_hash WHERE (author_id = 15) AND (id = 1 OR word_count > 5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 15 + count +------- + 0 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (author_id = 15) OR (id = 1 AND word_count > 5); + count +------- + 1 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (id = 15) OR (author_id = 1 AND word_count > 5); + count +------- + 6 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (author_id = 1 OR word_count > 5); + count +------- + 1 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (author_id = 1 AND (word_count > 5 OR id = 2)); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + count +------- + 0 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (title ilike 'a%' AND (word_count > 5 OR author_id = 2)); + count +------- + 1 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (title ilike 'a%' AND (word_count > 5 AND author_id = 2)); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + count +------- + 0 +(1 row) + +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (title ilike 'a%' AND ((word_count > 5 OR title ilike 'b%' ) AND (author_id = 2 AND word_count > 50))); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + count +------- + 0 +(1 row) + +-- fast-path router plannable, between operator is on another column +SELECT * + FROM articles_hash + WHERE (author_id = 1) and id between 0 and 20; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 +(2 rows) + +-- fast-path router plannable, partition column expression is and'ed to rest +SELECT * + FROM articles_hash + WHERE (author_id = 1) and (id = 1 or id = 31) and title like '%s'; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 31 | 1 | athwartships | 7271 +(2 rows) + +-- fast-path router plannable, order is changed +SELECT * + FROM articles_hash + WHERE (id = 1 or id = 31) and title like '%s' and (author_id = 1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 31 | 1 | athwartships | 7271 +(2 rows) + +-- fast-path router plannable +SELECT * + FROM articles_hash + WHERE (title like '%s' or title like 'a%') and (author_id = 1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- fast-path router plannable +SELECT * + FROM articles_hash + WHERE (title like '%s' or title like 'a%') and (author_id = 1) and (word_count < 3000 or word_count > 8000); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 41 | 1 | aznavour | 11814 +(3 rows) + +-- window functions are supported with fast-path router plannable +SELECT LAG(title, 1) over (ORDER BY word_count) prev, title, word_count + FROM articles_hash + WHERE author_id = 5; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 + prev | title | word_count +----------+----------+------------ + | afrasia | 864 + afrasia | adversa | 3164 + adversa | antehall | 7707 + antehall | aminate | 9089 + aminate | aruru | 11389 +(5 rows) + +SELECT LAG(title, 1) over (ORDER BY word_count) prev, title, word_count + FROM articles_hash + WHERE author_id = 5 + ORDER BY word_count DESC; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 + prev | title | word_count +----------+----------+------------ + aminate | aruru | 11389 + antehall | aminate | 9089 + adversa | antehall | 7707 + afrasia | adversa | 3164 + | afrasia | 864 +(5 rows) + +SELECT id, MIN(id) over (order by word_count) + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | min +----+----- + 11 | 11 + 21 | 11 + 31 | 11 + 1 | 1 + 41 | 1 +(5 rows) + +SELECT id, word_count, AVG(word_count) over (order by word_count) + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | word_count | avg +----+------------+----------------------- + 11 | 1347 | 1347.0000000000000000 + 21 | 5890 | 3618.5000000000000000 + 31 | 7271 | 4836.0000000000000000 + 1 | 9572 | 6020.0000000000000000 + 41 | 11814 | 7178.8000000000000000 +(5 rows) + +SELECT word_count, rank() OVER (PARTITION BY author_id ORDER BY word_count) + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + word_count | rank +------------+------ + 1347 | 1 + 5890 | 2 + 7271 | 3 + 9572 | 4 + 11814 | 5 +(5 rows) + +-- some more tests on complex target lists +SELECT DISTINCT ON (author_id, id) author_id, id, + MIN(id) over (order by avg(word_count)) * AVG(id * 5.2 + (1.0/max(word_count))) over (order by max(word_count)) as t1, + count(*) FILTER (WHERE title LIKE 'al%') as cnt_with_filter, + count(*) FILTER (WHERE '0300030' LIKE '%3%') as cnt_with_filter_2, + avg(case when id > 2 then char_length(word_count::text) * (id * strpos(word_count::text, '1')) end) as case_cnt, + COALESCE(strpos(avg(word_count)::text, '1'), 20) + FROM articles_hash as aliased_table + WHERE author_id = 1 + GROUP BY author_id, id + HAVING count(DISTINCT title) > 0 + ORDER BY author_id, id, sum(word_count) - avg(char_length(title)) DESC, COALESCE(array_upper(ARRAY[max(id)],1) * 5,0) DESC; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + author_id | id | t1 | cnt_with_filter | cnt_with_filter_2 | case_cnt | coalesce +-----------+----+------------------------------+-----------------+-------------------+------------------------+---------- + 1 | 1 | 83.20028854345579490574 | 0 | 1 | | 0 + 1 | 11 | 629.20816629547141796586 | 1 | 1 | 44.0000000000000000 | 1 + 1 | 21 | 915.20501693381380745499 | 0 | 1 | 0.00000000000000000000 | 0 + 1 | 31 | 1201.20384890897723321000 | 0 | 1 | 496.0000000000000000 | 4 + 1 | 41 | 109.200247763831844321405335 | 0 | 1 | 205.0000000000000000 | 1 +(5 rows) + +-- where false queries are router plannable but not fast-path +SELECT * + FROM articles_hash + WHERE false; +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- fast-path with false +SELECT * + FROM articles_hash + WHERE author_id = 1 and false; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- fast-path with false +SELECT * + FROM articles_hash + WHERE author_id = 1 and 1=0; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +SELECT * + FROM articles_hash + WHERE null and author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- we cannot qualify dist_key = X operator Y via +-- fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + 1; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + id | author_id | title | word_count +----+-----------+------------+------------ + 2 | 2 | abducing | 13642 + 12 | 2 | archiblast | 18185 + 22 | 2 | antipope | 2728 + 32 | 2 | amazon | 11342 + 42 | 2 | ausable | 15885 +(5 rows) + +-- where false with immutable function returning false +-- goes through fast-path +SELECT * + FROM articles_hash a + WHERE a.author_id = 10 and int4eq(1, 2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 10 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- partition_column is null clause does not prune out any shards, +-- all shards remain after shard pruning, not router plannable +-- not fast-path router either +SELECT * + FROM articles_hash a + WHERE a.author_id is null; + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- partition_column equals to null clause prunes out all shards +-- no shards after shard pruning, router plannable +-- not fast-path router either +SELECT * + FROM articles_hash a + WHERE a.author_id = null; +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- union/difference /intersection with where false +-- this query was not originally router plannable, addition of 1=0 +-- makes it router plannable but not fast-path +SELECT * FROM ( + SELECT * FROM articles_hash WHERE author_id = 1 + UNION + SELECT * FROM articles_hash WHERE author_id = 2 and 1=0 +) AS combination +ORDER BY id; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- same with the above, but with WHERE false +SELECT * FROM ( + SELECT * FROM articles_hash WHERE author_id = 1 + UNION + SELECT * FROM articles_hash WHERE author_id = 2 and 1=0 +) AS combination WHERE false +ORDER BY id; +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +-- window functions with where false +SELECT word_count, rank() OVER (PARTITION BY author_id ORDER BY word_count) + FROM articles_hash + WHERE author_id = 1 and 1=0; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + word_count | rank +------------+------ +(0 rows) + +-- create a dummy function to be used in filtering +CREATE OR REPLACE FUNCTION someDummyFunction(regclass) + RETURNS text AS +$$ +BEGIN + RETURN md5($1::text); +END; +$$ LANGUAGE 'plpgsql' IMMUTABLE; +-- fast path router plannable, but errors +SELECT * FROM articles_hash + WHERE + someDummyFunction('articles_hash') = md5('articles_hash') AND author_id = 1 + ORDER BY + author_id, id + LIMIT 5; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +WARNING: relation "fast_path_router_select.articles_hash" does not exist +CONTEXT: while executing command on localhost:57637 +ERROR: could not receive query results +-- temporarily turn off debug messages before dropping the function +SET client_min_messages TO 'NOTICE'; +DROP FUNCTION someDummyFunction(regclass); +SET client_min_messages TO 'DEBUG2'; +-- complex query hitting a single shard and a fast-path +SELECT + count(DISTINCT CASE + WHEN + word_count > 100 + THEN + id + ELSE + NULL + END) as c + FROM + articles_hash + WHERE + author_id = 5; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 + c +--- + 5 +(1 row) + +-- queries inside transactions can be fast-path router plannable +BEGIN; +SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +END; +-- queries inside read-only transactions can be fast-path router plannable +SET TRANSACTION READ ONLY; +WARNING: SET TRANSACTION can only be used in transaction blocks +SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +END; +WARNING: there is no transaction in progress +-- cursor queries are fast-path router plannable +BEGIN; +DECLARE test_cursor CURSOR FOR + SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +FETCH test_cursor; + id | author_id | title | word_count +----+-----------+----------+------------ + 1 | 1 | arsenous | 9572 +(1 row) + +FETCH ALL test_cursor; + id | author_id | title | word_count +----+-----------+--------------+------------ + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(4 rows) + +FETCH test_cursor; -- fetch one row after the last + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +FETCH BACKWARD test_cursor; + id | author_id | title | word_count +----+-----------+----------+------------ + 41 | 1 | aznavour | 11814 +(1 row) + +END; +-- queries inside copy can be router plannable +COPY ( + SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id) TO STDOUT; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +1 1 arsenous 9572 +11 1 alamo 1347 +21 1 arcading 5890 +31 1 athwartships 7271 +41 1 aznavour 11814 + +-- table creation queries inside can be fast-path router plannable +CREATE TEMP TABLE temp_articles_hash as + SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +-- fast-path router plannable queries may include filter for aggragates +SELECT count(*), count(*) FILTER (WHERE id < 3) + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + count | count +-------+------- + 5 | 1 +(1 row) + +-- prepare queries can be router plannable +PREPARE author_1_articles as + SELECT * + FROM articles_hash + WHERE author_id = 1; +EXECUTE author_1_articles; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_1_articles; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- parametric prepare queries can be router plannable +PREPARE author_articles(int) as + SELECT * + FROM articles_hash + WHERE author_id = $1; +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +EXECUTE author_articles(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- queries inside plpgsql functions could be router plannable +CREATE OR REPLACE FUNCTION author_articles_max_id() RETURNS int AS $$ +DECLARE + max_id integer; +BEGIN + SELECT MAX(id) FROM articles_hash ah + WHERE author_id = 1 + into max_id; + return max_id; +END; +$$ LANGUAGE plpgsql; +-- we don't want too many details. though we're omitting +-- "DETAIL: distribution column value:", we see it acceptable +-- since the query results verifies the correctness +\set VERBOSITY terse +SELECT author_articles_max_id(); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(); + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(); + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(); + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(); + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(); + author_articles_max_id +------------------------ + 41 +(1 row) + +-- queries inside plpgsql functions could be router plannable +CREATE OR REPLACE FUNCTION author_articles_max_id(int) RETURNS int AS $$ +DECLARE + max_id integer; +BEGIN + SELECT MAX(id) FROM articles_hash ah + WHERE author_id = $1 + into max_id; + return max_id; +END; +$$ LANGUAGE plpgsql; +SELECT author_articles_max_id(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +SELECT author_articles_max_id(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + author_articles_max_id +------------------------ + 41 +(1 row) + +-- check that function returning setof query are router plannable +CREATE OR REPLACE FUNCTION author_articles_id_word_count() RETURNS TABLE(id bigint, word_count int) AS $$ +DECLARE +BEGIN + RETURN QUERY + SELECT ah.id, ah.word_count + FROM articles_hash ah + WHERE author_id = 1; + +END; +$$ LANGUAGE plpgsql; +SELECT * FROM author_articles_id_word_count(); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(); + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(); + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(); + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(); + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(); + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +-- check that function returning setof query are router plannable +CREATE OR REPLACE FUNCTION author_articles_id_word_count(int) RETURNS TABLE(id bigint, word_count int) AS $$ +DECLARE +BEGIN + RETURN QUERY + SELECT ah.id, ah.word_count + FROM articles_hash ah + WHERE author_id = $1; + +END; +$$ LANGUAGE plpgsql; +SELECT * FROM author_articles_id_word_count(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +SELECT * FROM author_articles_id_word_count(1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable + id | word_count +----+------------ + 1 | 9572 + 11 | 1347 + 21 | 5890 + 31 | 7271 + 41 | 11814 +(5 rows) + +\set VERBOSITY default +-- insert .. select via coordinator could also +-- use fast-path queries +PREPARE insert_sel(int, int) AS +INSERT INTO articles_hash + SELECT * FROM articles_hash WHERE author_id = $2 AND word_count = $1 OFFSET 0; +EXECUTE insert_sel(1,1); +DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +EXECUTE insert_sel(1,1); +DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +EXECUTE insert_sel(1,1); +DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +EXECUTE insert_sel(1,1); +DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +EXECUTE insert_sel(1,1); +DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +EXECUTE insert_sel(1,1); +DEBUG: OFFSET clauses are not allowed in distributed INSERT ... SELECT queries +DEBUG: Collecting INSERT ... SELECT results on coordinator +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +-- one final interesting preperad statement +-- where one of the filters is on the target list +PREPARE fast_path_agg_filter(int, int) AS + SELECT + count(*) FILTER (WHERE word_count=$1) + FROM + articles_hash + WHERE author_id = $2; +EXECUTE fast_path_agg_filter(1,1); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + count +------- + 0 +(1 row) + +EXECUTE fast_path_agg_filter(2,2); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 2 + count +------- + 0 +(1 row) + +EXECUTE fast_path_agg_filter(3,3); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 3 + count +------- + 0 +(1 row) + +EXECUTE fast_path_agg_filter(4,4); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 0 +(1 row) + +EXECUTE fast_path_agg_filter(5,5); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 5 + count +------- + 0 +(1 row) + +EXECUTE fast_path_agg_filter(6,6); +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 6 + count +------- + 0 +(1 row) + +-- views internally become subqueries, so not fast-path router query +CREATE VIEW test_view AS + SELECT * FROM articles_hash WHERE author_id = 1; +SELECT * FROM test_view; +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- materialized views can be created for fast-path router plannable queries +CREATE MATERIALIZED VIEW mv_articles_hash_empty AS + SELECT * FROM articles_hash WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 +SELECT * FROM mv_articles_hash_empty; + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +-- fast-path router planner/executor is enabled for task-tracker executor +SET citus.task_executor_type to 'task-tracker'; +SELECT id + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id +---- + 1 + 11 + 21 + 31 + 41 +(5 rows) + +-- insert query is router plannable even under task-tracker +INSERT INTO articles_hash VALUES (51, 1, 'amateus', 1814), (52, 1, 'second amateus', 2824); +DEBUG: Creating router plan +DEBUG: Plan is router executable +-- verify insert is successfull (not router plannable and executable) +SELECT id + FROM articles_hash + WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id +---- + 1 + 11 + 21 + 31 + 41 + 51 + 52 +(7 rows) + +SET client_min_messages to 'NOTICE'; +-- finally, some tests with partitioned tables +CREATE TABLE collections_list ( + key bigint, + ts timestamptz, + collection_id integer, + value numeric +) PARTITION BY LIST (collection_id ); +CREATE TABLE collections_list_1 + PARTITION OF collections_list (key, ts, collection_id, value) + FOR VALUES IN ( 1 ); +CREATE TABLE collections_list_2 + PARTITION OF collections_list (key, ts, collection_id, value) + FOR VALUES IN ( 2 ); +-- we don't need many shards +SET citus.shard_count TO 2; +SELECT create_distributed_table('collections_list', 'key'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO collections_list SELECT i % 10, now(), (i % 2) + 1, i*i FROM generate_series(0, 50)i; +SET client_min_messages to 'DEBUG2'; +SELECT count(*) FROM collections_list WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 5 +(1 row) + +SELECT count(*) FROM collections_list_1 WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 5 +(1 row) + +SELECT count(*) FROM collections_list_2 WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 0 +(1 row) + +UPDATE collections_list SET value = 15 WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 +SELECT count(*) FILTER (where value = 15) FROM collections_list WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 5 +(1 row) + +SELECT count(*) FILTER (where value = 15) FROM collections_list_1 WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 5 +(1 row) + +SELECT count(*) FILTER (where value = 15) FROM collections_list_2 WHERE key = 4; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 4 + count +------- + 0 +(1 row) + +SET client_min_messages to 'NOTICE'; +DROP FUNCTION author_articles_max_id(); +DROP FUNCTION author_articles_id_word_count(); +DROP MATERIALIZED VIEW mv_articles_hash_empty; +DROP MATERIALIZED VIEW mv_articles_hash_data; +ERROR: materialized view "mv_articles_hash_data" does not exist +DROP TABLE articles_hash; +ERROR: cannot drop table articles_hash because other objects depend on it +DETAIL: view test_view depends on table articles_hash +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TABLE authors_hash; +DROP TABLE authors_range; +DROP TABLE authors_reference; +DROP TABLE company_employees; +DROP TABLE articles_range; +DROP TABLE articles_append; +DROP TABLE collections_list; +RESET search_path; +DROP SCHEMA fast_path_router_select CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table fast_path_router_select.articles_hash +drop cascades to function fast_path_router_select.author_articles_max_id(integer) +drop cascades to function fast_path_router_select.author_articles_id_word_count(integer) +drop cascades to view fast_path_router_select.test_view diff --git a/src/test/regress/expected/multi_simple_queries.out b/src/test/regress/expected/multi_simple_queries.out index 62e8bac52..27e63935d 100644 --- a/src/test/regress/expected/multi_simple_queries.out +++ b/src/test/regress/expected/multi_simple_queries.out @@ -1,4 +1,8 @@ SET citus.next_shard_id TO 850000; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- =================================================================== -- test end-to-end query functionality -- =================================================================== @@ -658,4 +662,52 @@ DETAIL: distribution column value: 1 41 | 1 | aznavour | 11814 (5 rows) +-- test tablesample with fast path as well +SET citus.enable_fast_path_router_planner TO true; +SELECT * FROM articles TABLESAMPLE SYSTEM (0) WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +SELECT * FROM articles TABLESAMPLE BERNOULLI (0) WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +SELECT * FROM articles TABLESAMPLE SYSTEM (100) WHERE author_id = 1 ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +SELECT * FROM articles TABLESAMPLE BERNOULLI (100) WHERE author_id = 1 ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + SET client_min_messages to 'NOTICE'; diff --git a/src/test/regress/expected/multi_simple_queries_0.out b/src/test/regress/expected/multi_simple_queries_0.out index 3df41d697..ef3390857 100644 --- a/src/test/regress/expected/multi_simple_queries_0.out +++ b/src/test/regress/expected/multi_simple_queries_0.out @@ -1,4 +1,8 @@ SET citus.next_shard_id TO 850000; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- =================================================================== -- test end-to-end query functionality -- =================================================================== @@ -602,4 +606,52 @@ DETAIL: distribution column value: 1 41 | 1 | aznavour | 11814 (5 rows) +-- test tablesample with fast path as well +SET citus.enable_fast_path_router_planner TO true; +SELECT * FROM articles TABLESAMPLE SYSTEM (0) WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +SELECT * FROM articles TABLESAMPLE BERNOULLI (0) WHERE author_id = 1; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+-------+------------ +(0 rows) + +SELECT * FROM articles TABLESAMPLE SYSTEM (100) WHERE author_id = 1 ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + +SELECT * FROM articles TABLESAMPLE BERNOULLI (100) WHERE author_id = 1 ORDER BY id; +DEBUG: Distributed planning for a fast-path router query +DEBUG: Creating router plan +DEBUG: Plan is router executable +DETAIL: distribution column value: 1 + id | author_id | title | word_count +----+-----------+--------------+------------ + 1 | 1 | arsenous | 9572 + 11 | 1 | alamo | 1347 + 21 | 1 | arcading | 5890 + 31 | 1 | athwartships | 7271 + 41 | 1 | aznavour | 11814 +(5 rows) + SET client_min_messages to 'NOTICE'; diff --git a/src/test/regress/expected/multi_task_assignment_policy.out b/src/test/regress/expected/multi_task_assignment_policy.out index cabb5f4ac..9b63c2ad4 100644 --- a/src/test/regress/expected/multi_task_assignment_policy.out +++ b/src/test/regress/expected/multi_task_assignment_policy.out @@ -153,6 +153,7 @@ SELECT create_reference_table('task_assignment_reference_table'); SET LOCAL citus.task_assignment_policy TO 'greedy'; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable QUERY PLAN @@ -162,6 +163,7 @@ DEBUG: Plan is router executable (2 rows) EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable QUERY PLAN @@ -172,6 +174,7 @@ DEBUG: Plan is router executable SET LOCAL citus.task_assignment_policy TO 'first-replica'; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable QUERY PLAN @@ -181,6 +184,7 @@ DEBUG: Plan is router executable (2 rows) EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable QUERY PLAN @@ -192,6 +196,7 @@ DEBUG: Plan is router executable -- here we expect debug output showing two different hosts for subsequent queries SET LOCAL citus.task_assignment_policy TO 'round-robin'; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; +DEBUG: Distributed planning for a fast-path router query DEBUG: assigned task 0 to node localhost:57637 DEBUG: Creating router plan DEBUG: Plan is router executable @@ -202,6 +207,7 @@ DEBUG: Plan is router executable (2 rows) EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; +DEBUG: Distributed planning for a fast-path router query DEBUG: assigned task 0 to node localhost:57638 DEBUG: Creating router plan DEBUG: Plan is router executable diff --git a/src/test/regress/expected/set_operation_and_local_tables.out b/src/test/regress/expected/set_operation_and_local_tables.out index 29a59abe1..daf3e7a4a 100644 --- a/src/test/regress/expected/set_operation_and_local_tables.out +++ b/src/test/regress/expected/set_operation_and_local_tables.out @@ -139,6 +139,7 @@ FROM WHERE test.y = foo.x; DEBUG: generating subplan 19_1 for CTE cte_1: SELECT x FROM recursive_set_local.test DEBUG: generating subplan 19_2 for CTE cte_1: SELECT a FROM recursive_set_local.ref +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable DEBUG: generating subplan 19_3 for subquery SELECT x FROM recursive_set_local.local_test @@ -165,6 +166,7 @@ FROM WHERE ref.a = foo.x; DEBUG: generating subplan 23_1 for CTE cte_1: SELECT x FROM recursive_set_local.test DEBUG: generating subplan 23_2 for CTE cte_1: SELECT a FROM recursive_set_local.ref +DEBUG: Distributed planning for a fast-path router query DEBUG: Creating router plan DEBUG: Plan is router executable DEBUG: generating subplan 23_3 for subquery SELECT x FROM recursive_set_local.local_test diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 9095dabaf..4425b9e96 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -184,8 +184,8 @@ test: multi_transaction_recovery # multi_copy creates hash and range-partitioned tables and performs COPY # multi_router_planner creates hash partitioned tables. # --------- -test: multi_copy -test: multi_router_planner +test: multi_copy fast_path_router_modify +test: multi_router_planner multi_router_planner_fast_path # ---------- # multi_large_shardid loads more lineitem data using high shard identifiers diff --git a/src/test/regress/sql/fast_path_router_modify.sql b/src/test/regress/sql/fast_path_router_modify.sql new file mode 100644 index 000000000..c3f6f9cd2 --- /dev/null +++ b/src/test/regress/sql/fast_path_router_modify.sql @@ -0,0 +1,113 @@ + +CREATE SCHEMA fast_path_router_modify; +SET search_path TO fast_path_router_modify; + +SET citus.next_shard_id TO 1840000; + +-- all the tests in this file is intended for testing fast-path +-- router planner, so we're explicitly enabling itin this file. +-- We've bunch of other tests that triggers non-fast-path-router +-- planner (note this is already true by default) +SET citus.enable_fast_path_router_planner TO true; + +SET citus.shard_replication_factor TO 1; +CREATE TABLE modify_fast_path(key int, value_1 int, value_2 text); +SELECT create_distributed_table('modify_fast_path', 'key'); + +SET citus.shard_replication_factor TO 2; +CREATE TABLE modify_fast_path_replication_2(key int, value_1 int, value_2 text); +SELECT create_distributed_table('modify_fast_path_replication_2', 'key'); + +CREATE TABLE modify_fast_path_reference(key int, value_1 int, value_2 text); +SELECT create_reference_table('modify_fast_path_reference'); + + +-- show the output +SET client_min_messages TO DEBUG; + +-- very simple queries goes through fast-path planning +DELETE FROM modify_fast_path WHERE key = 1; +UPDATE modify_fast_path SET value_1 = 1 WHERE key = 1; +UPDATE modify_fast_path SET value_1 = value_1 + 1 WHERE key = 1; +UPDATE modify_fast_path SET value_1 = value_1 + value_2::int WHERE key = 1; +DELETE FROM modify_fast_path WHERE value_1 = 15 AND (key = 1 AND value_2 = 'citus'); +DELETE FROM modify_fast_path WHERE key = 1 and FALSE; + +-- UPDATE may include complex target entries +UPDATE modify_fast_path SET value_1 = value_1 + 12 * value_1 WHERE key = 1; +UPDATE modify_fast_path SET value_1 = abs(-19) WHERE key = 1; + +-- cannot go through fast-path because there are multiple keys +DELETE FROM modify_fast_path WHERE key = 1 AND key = 2; +DELETE FROM modify_fast_path WHERE key = 1 AND (key = 2 AND value_1 = 15); + +-- cannot go through fast-path because key is not on the top level +DELETE FROM modify_fast_path WHERE value_1 = 15 OR (key = 1 AND value_2 = 'citus'); +DELETE FROM modify_fast_path WHERE value_1 = 15 AND (key = 1 OR value_2 = 'citus'); + +-- goes through fast-path planning even if the key is updated to the same value +UPDATE modify_fast_path SET key = 1 WHERE key = 1; +UPDATE modify_fast_path SET key = 1::float WHERE key = 1; + +-- cannot support if key changes +UPDATE modify_fast_path SET key = 2 WHERE key = 1; +UPDATE modify_fast_path SET key = 2::numeric WHERE key = 1; + +-- returning is not supported via fast-path +DELETE FROM modify_fast_path WHERE key = 1 RETURNING *; + +-- modifying ctes are not supported via fast-path +WITH t1 AS (DELETE FROM modify_fast_path WHERE key = 1), t2 AS (SELECT * FROM modify_fast_path) SELECT * FROM t2; + +-- for update/share is supported via fast-path when replication factor = 1 or reference table +SELECT * FROM modify_fast_path WHERE key = 1 FOR UPDATE; +SELECT * FROM modify_fast_path WHERE key = 1 FOR SHARE; +SELECT * FROM modify_fast_path_reference WHERE key = 1 FOR UPDATE; +SELECT * FROM modify_fast_path_reference WHERE key = 1 FOR SHARE; + +-- for update/share is not supported via fast-path wen replication factor > 1 +SELECT * FROM modify_fast_path_replication_2 WHERE key = 1 FOR UPDATE; +SELECT * FROM modify_fast_path_replication_2 WHERE key = 1 FOR SHARE; + +-- very simple queries on reference tables goes through fast-path planning +DELETE FROM modify_fast_path_reference WHERE key = 1; +UPDATE modify_fast_path_reference SET value_1 = 1 WHERE key = 1; +UPDATE modify_fast_path_reference SET value_1 = value_1 + 1 WHERE key = 1; +UPDATE modify_fast_path_reference SET value_1 = value_1 + value_2::int WHERE key = 1; + + +-- joins are not supported via fast-path +UPDATE modify_fast_path + SET value_1 = 1 + FROM modify_fast_path_reference + WHERE + modify_fast_path.key = modify_fast_path_reference.key AND + modify_fast_path.key = 1 AND + modify_fast_path_reference.key = 1; + +PREPARE p1 (int, int, int) AS + UPDATE modify_fast_path SET value_1 = value_1 + $1 WHERE key = $2 AND value_1 = $3; +EXECUTE p1(1,1,1); +EXECUTE p1(2,2,2); +EXECUTE p1(3,3,3); +EXECUTE p1(4,4,4); +EXECUTE p1(5,5,5); +EXECUTE p1(6,6,6); + +CREATE FUNCTION modify_fast_path_plpsql(int, int) RETURNS void as $$ +BEGIN + DELETE FROM modify_fast_path WHERE key = $1 AND value_1 = $2; +END; +$$ LANGUAGE plpgsql; + +SELECT modify_fast_path_plpsql(1,1); +SELECT modify_fast_path_plpsql(2,2); +SELECT modify_fast_path_plpsql(3,3); +SELECT modify_fast_path_plpsql(4,4); +SELECT modify_fast_path_plpsql(5,5); +SELECT modify_fast_path_plpsql(6,6); +SELECT modify_fast_path_plpsql(6,6); + +RESET client_min_messages; + +DROP SCHEMA fast_path_router_modify CASCADE; \ No newline at end of file diff --git a/src/test/regress/sql/multi_function_evaluation.sql b/src/test/regress/sql/multi_function_evaluation.sql index 4878e8add..b03f1c166 100644 --- a/src/test/regress/sql/multi_function_evaluation.sql +++ b/src/test/regress/sql/multi_function_evaluation.sql @@ -4,6 +4,11 @@ SET citus.next_shard_id TO 1200000; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; + -- nextval() works (no good way to test DEFAULT, or, by extension, SERIAL) CREATE TABLE example (key INT, value INT); diff --git a/src/test/regress/sql/multi_hash_pruning.sql b/src/test/regress/sql/multi_hash_pruning.sql index 81f7e3ab8..5afae0fae 100644 --- a/src/test/regress/sql/multi_hash_pruning.sql +++ b/src/test/regress/sql/multi_hash_pruning.sql @@ -9,6 +9,12 @@ SET citus.next_shard_id TO 630000; SET citus.shard_count to 4; SET citus.shard_replication_factor to 1; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; + + -- Create a table partitioned on integer column and update partition type to -- hash. Then load data into this table and update shard min max values with -- hashed ones. Hash value of 1, 2, 3 and 4 are consecutively -1905060026, diff --git a/src/test/regress/sql/multi_mx_router_planner.sql b/src/test/regress/sql/multi_mx_router_planner.sql index 366f78092..2e744db14 100644 --- a/src/test/regress/sql/multi_mx_router_planner.sql +++ b/src/test/regress/sql/multi_mx_router_planner.sql @@ -70,6 +70,11 @@ INSERT INTO articles_single_shard_hash_mx VALUES (50, 10, 'anjanette', 19519); -- single-shard tests +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; + -- test simple select for a single row SELECT * FROM articles_hash_mx WHERE author_id = 10 AND id = 50; diff --git a/src/test/regress/sql/multi_prepare_plsql.sql b/src/test/regress/sql/multi_prepare_plsql.sql index 8bb7ca587..f61cdb06a 100644 --- a/src/test/regress/sql/multi_prepare_plsql.sql +++ b/src/test/regress/sql/multi_prepare_plsql.sql @@ -6,6 +6,10 @@ -- and converted into both plain SQL and PL/pgsql functions, which -- use prepared statements internally. +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; CREATE FUNCTION plpgsql_test_1() RETURNS TABLE(count bigint) AS $$ DECLARE diff --git a/src/test/regress/sql/multi_prepare_sql.sql b/src/test/regress/sql/multi_prepare_sql.sql index bda1ac5b7..a4f410df0 100644 --- a/src/test/regress/sql/multi_prepare_sql.sql +++ b/src/test/regress/sql/multi_prepare_sql.sql @@ -2,6 +2,11 @@ -- MULTI_PREPARE_SQL -- +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; + -- Tests covering PREPARE statements. Many of the queries are -- taken from other regression test files and converted into -- prepared statements. diff --git a/src/test/regress/sql/multi_router_planner.sql b/src/test/regress/sql/multi_router_planner.sql index c583fdeba..e64ca1e64 100644 --- a/src/test/regress/sql/multi_router_planner.sql +++ b/src/test/regress/sql/multi_router_planner.sql @@ -1,11 +1,15 @@ SET citus.next_shard_id TO 840000; - -- =================================================================== -- test router planner functionality for single shard select queries -- =================================================================== +-- all the tests in this file is intended for testing non-fast-path +-- router planner, so we're disabling it in this file. We've bunch of +-- other tests that triggers fast-path-router planner +SET citus.enable_fast_path_router_planner TO false; + CREATE TABLE articles_hash ( id bigint NOT NULL, author_id bigint NOT NULL, diff --git a/src/test/regress/sql/multi_router_planner_fast_path.sql b/src/test/regress/sql/multi_router_planner_fast_path.sql new file mode 100644 index 000000000..51bd57d68 --- /dev/null +++ b/src/test/regress/sql/multi_router_planner_fast_path.sql @@ -0,0 +1,829 @@ + +CREATE SCHEMA fast_path_router_select; +SET search_path TO fast_path_router_select; + +SET citus.next_shard_id TO 1840000; + +-- all the tests in this file is intended for testing fast-path +-- router planner, so we're explicitly enabling itin this file. +-- We've bunch of other tests that triggers non-fast-path-router +-- planner (note this is already true by default) +SET citus.enable_fast_path_router_planner TO true; + + +-- =================================================================== +-- test router planner functionality for via fast path on +-- single shard select queries +-- =================================================================== + +CREATE TABLE articles_hash ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); + +CREATE TABLE articles_range ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); + +CREATE TABLE articles_append ( + id bigint NOT NULL, + author_id bigint NOT NULL, + title varchar(20) NOT NULL, + word_count integer +); + +-- Check for the existence of line 'DEBUG: Creating router plan' +-- to determine if router planner is used. + +-- this table is used in a CTE test +CREATE TABLE authors_hash ( name varchar(20), id bigint ); +CREATE TABLE authors_range ( name varchar(20), id bigint ); + +SET citus.shard_replication_factor TO 1; + +SET citus.shard_count TO 2; +SELECT create_distributed_table('articles_hash', 'author_id'); + +CREATE TABLE authors_reference ( name varchar(20), id bigint ); +SELECT create_reference_table('authors_reference'); + +-- create a bunch of test data +INSERT INTO articles_hash VALUES (1, 1, 'arsenous', 9572), (2, 2, 'abducing', 13642),( 3, 3, 'asternal', 10480),( 4, 4, 'altdorfer', 14551),( 5, 5, 'aruru', 11389), + (6, 6, 'atlases', 15459),(7, 7, 'aseptic', 12298),( 8, 8, 'agatized', 16368),(9, 9, 'alligate', 438), + (10, 10, 'aggrandize', 17277),(11, 1, 'alamo', 1347),(12, 2, 'archiblast', 18185), + (13, 3, 'aseyev', 2255),(14, 4, 'andesite', 19094),(15, 5, 'adversa', 3164), + (16, 6, 'allonym', 2),(17, 7, 'auriga', 4073),(18, 8, 'assembly', 911),(19, 9, 'aubergiste', 4981), + (20, 10, 'absentness', 1820),(21, 1, 'arcading', 5890),(22, 2, 'antipope', 2728),(23, 3, 'abhorring', 6799), + (24, 4, 'audacious', 3637),(25, 5, 'antehall', 7707),(26, 6, 'abington', 4545),(27, 7, 'arsenous', 8616), + (28, 8, 'aerophyte', 5454),(29, 9, 'amateur', 9524),(30, 10, 'andelee', 6363),(31, 1, 'athwartships', 7271), + (32, 2, 'amazon', 11342),(33, 3, 'autochrome', 8180),(34, 4, 'amnestied', 12250),(35, 5, 'aminate', 9089), + (36, 6, 'ablation', 13159),(37, 7, 'archduchies', 9997),(38, 8, 'anatine', 14067),(39, 9, 'anchises', 10906), + (40, 10, 'attemper', 14976),(41, 1, 'aznavour', 11814),(42, 2, 'ausable', 15885),(43, 3, 'affixal', 12723), + (44, 4, 'anteport', 16793),(45, 5, 'afrasia', 864),(46, 6, 'atlanta', 17702),(47, 7, 'abeyance', 1772), + (48, 8, 'alkylic', 18610),(49, 9, 'anyone', 2681),(50, 10, 'anjanette', 19519); + +SET citus.task_executor_type TO 'real-time'; +SET client_min_messages TO 'DEBUG2'; + +-- test simple select for a single row +SELECT * FROM articles_hash WHERE author_id = 10 AND id = 50; + +-- get all titles by a single author +SELECT title FROM articles_hash WHERE author_id = 10; + +-- try ordering them by word count +SELECT title, word_count FROM articles_hash + WHERE author_id = 10 + ORDER BY word_count DESC NULLS LAST; + +-- look at last two articles by an author +SELECT title, id FROM articles_hash + WHERE author_id = 5 + ORDER BY id + LIMIT 2; + +-- find all articles by two authors in same shard +-- but plan is not fast path router plannable due to +-- two distribution columns in the query +SELECT title, author_id FROM articles_hash + WHERE author_id = 7 OR author_id = 8 + ORDER BY author_id ASC, id; + +-- having clause is supported if it goes to a single shard +-- and single dist. key on the query +SELECT author_id, sum(word_count) AS corpus_size FROM articles_hash + WHERE author_id = 1 + GROUP BY author_id + HAVING sum(word_count) > 1000 + ORDER BY sum(word_count) DESC; + +-- fast path planner only support = operator +SELECT * FROM articles_hash WHERE author_id <= 1; +SELECT * FROM articles_hash WHERE author_id IN (1, 3); + +-- queries with CTEs cannot go through fast-path planning +WITH first_author AS ( SELECT id FROM articles_hash WHERE author_id = 1) +SELECT * FROM first_author; + +-- two CTE joins also cannot go through fast-path planning +WITH id_author AS ( SELECT id, author_id FROM articles_hash WHERE author_id = 1), +id_title AS (SELECT id, title from articles_hash WHERE author_id = 1) +SELECT * FROM id_author, id_title WHERE id_author.id = id_title.id; + +-- this is a different case where each CTE is recursively planned and those goes +-- through the fast-path router planner, but the top level join is not +WITH id_author AS ( SELECT id, author_id FROM articles_hash WHERE author_id = 1), +id_title AS (SELECT id, title from articles_hash WHERE author_id = 2) +SELECT * FROM id_author, id_title WHERE id_author.id = id_title.id; + +CREATE TABLE company_employees (company_id int, employee_id int, manager_id int); +SELECT master_create_distributed_table('company_employees', 'company_id', 'hash'); +SELECT master_create_worker_shards('company_employees', 4, 1); + +INSERT INTO company_employees values(1, 1, 0); +INSERT INTO company_employees values(1, 2, 1); +INSERT INTO company_employees values(1, 3, 1); +INSERT INTO company_employees values(1, 4, 2); +INSERT INTO company_employees values(1, 5, 4); + +INSERT INTO company_employees values(3, 1, 0); +INSERT INTO company_employees values(3, 15, 1); +INSERT INTO company_employees values(3, 3, 1); + +-- recursive CTEs are also cannot go through fast +-- path planning +WITH RECURSIVE hierarchy as ( + SELECT *, 1 AS level + FROM company_employees + WHERE company_id = 1 and manager_id = 0 + UNION + SELECT ce.*, (h.level+1) + FROM hierarchy h JOIN company_employees ce + ON (h.employee_id = ce.manager_id AND + h.company_id = ce.company_id AND + ce.company_id = 1)) +SELECT * FROM hierarchy WHERE LEVEL <= 2; + +WITH update_article AS ( + UPDATE articles_hash SET word_count = 10 WHERE id = 1 AND word_count = 9 RETURNING * +) +SELECT * FROM update_article; + +WITH delete_article AS ( + DELETE FROM articles_hash WHERE id = 1 AND word_count = 10 RETURNING * +) +SELECT * FROM delete_article; + +-- grouping sets are supported via fast-path +SELECT + id, substring(title, 2, 1) AS subtitle, count(*) + FROM articles_hash + WHERE author_id = 1 + GROUP BY GROUPING SETS ((id),(subtitle)) + ORDER BY id, subtitle; + +-- grouping sets are not supported with multiple quals +SELECT + id, substring(title, 2, 1) AS subtitle, count(*) + FROM articles_hash + WHERE author_id = 1 or author_id = 2 + GROUP BY GROUPING SETS ((id),(subtitle)) + ORDER BY id, subtitle; + +-- queries which involve functions in FROM clause are not supported via fast path planning +SELECT * FROM articles_hash, position('om' in 'Thomas') WHERE author_id = 1; + +-- sublinks are not supported via fast path planning +SELECT * FROM articles_hash +WHERE author_id IN (SELECT author_id FROM articles_hash WHERE author_id = 2) +ORDER BY articles_hash.id; + +-- subqueries are not supported via fast path planning +SELECT articles_hash.id,test.word_count +FROM articles_hash, (SELECT id, word_count FROM articles_hash) AS test WHERE test.id = articles_hash.id +ORDER BY test.word_count DESC, articles_hash.id LIMIT 5; + +SELECT articles_hash.id,test.word_count +FROM articles_hash, (SELECT id, word_count FROM articles_hash) AS test +WHERE test.id = articles_hash.id and articles_hash.author_id = 1 +ORDER BY articles_hash.id; + +-- subqueries are not supported in SELECT clause +SELECT a.title AS name, (SELECT a2.id FROM articles_hash a2 WHERE a.id = a2.id LIMIT 1) + AS special_price FROM articles_hash a; + +-- simple lookup query just works +SELECT * + FROM articles_hash + WHERE author_id = 1; + +-- below query hits a single shard but with multiple filters +-- so cannot go via fast-path +SELECT * + FROM articles_hash + WHERE author_id = 1 OR author_id = 17; + +-- rename the output columns +SELECT id as article_id, word_count * id as random_value + FROM articles_hash + WHERE author_id = 1; + +-- joins do not go through fast-path planning +SELECT a.author_id as first_author, b.word_count as second_word_count + FROM articles_hash a, articles_hash b + WHERE a.author_id = 10 and a.author_id = b.author_id + LIMIT 3; + +-- single shard select with limit goes through fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + LIMIT 3; + +-- single shard select with limit + offset goes through fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + LIMIT 2 + OFFSET 1; + +-- single shard select with limit + offset + order by goes through fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id desc + LIMIT 2 + OFFSET 1; + +-- single shard select with group by on non-partition column goes through fast-path planning +SELECT id + FROM articles_hash + WHERE author_id = 1 + GROUP BY id + ORDER BY id; + +-- single shard select with distinct goes through fast-path planning +SELECT DISTINCT id + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; + +-- single shard aggregate goes through fast-path planning +SELECT avg(word_count) + FROM articles_hash + WHERE author_id = 2; + +-- max, min, sum, count goes through fast-path planning +SELECT max(word_count) as max, min(word_count) as min, + sum(word_count) as sum, count(word_count) as cnt + FROM articles_hash + WHERE author_id = 2; + + +-- queries with aggregates and group by goes through fast-path planning +SELECT max(word_count) + FROM articles_hash + WHERE author_id = 1 + GROUP BY author_id; + + +-- set operations are not supported via fast-path planning +SELECT * FROM ( + SELECT * FROM articles_hash WHERE author_id = 1 + UNION + SELECT * FROM articles_hash WHERE author_id = 3 +) AS combination +ORDER BY id; + +-- function calls in the target list is supported via fast path +SELECT LEFT(title, 1) FROM articles_hash WHERE author_id = 1 + + +-- top-level union queries are supported through recursive planning + +SET client_min_messages to 'NOTICE'; + +-- unions in subqueries are not supported via fast-path planning +SELECT * FROM ( + (SELECT * FROM articles_hash WHERE author_id = 1) + UNION + (SELECT * FROM articles_hash WHERE author_id = 1)) uu +ORDER BY 1, 2 +LIMIT 5; + + +-- Test various filtering options for router plannable check +SET client_min_messages to 'DEBUG2'; + +-- cannot go through fast-path if there is +-- explicit coercion +SELECT * + FROM articles_hash + WHERE author_id = 1::bigint; + +-- can go through fast-path if there is +-- implicit coercion +-- This doesn't work see the related issue +-- reported https://github.com/citusdata/citus/issues/2605 +-- SELECT * +-- FROM articles_hash +-- WHERE author_id = 1.0; + +SELECT * + FROM articles_hash + WHERE author_id = 68719476736; -- this is bigint + +-- cannot go through fast-path due to +-- multiple filters on the dist. key +SELECT * + FROM articles_hash + WHERE author_id = 1 and author_id >= 1; + +-- cannot go through fast-path due to +-- multiple filters on the dist. key +SELECT * + FROM articles_hash + WHERE author_id = 1 or id = 1; + +-- goes through fast-path planning because +-- the dist. key is ANDed with the rest of the +-- filters +SELECT * + FROM articles_hash + WHERE author_id = 1 and (id = 1 or id = 41); + +-- this time there is an OR clause which prevents +-- router planning at all +SELECT * + FROM articles_hash + WHERE author_id = 1 and id = 1 or id = 41; + +-- goes through fast-path planning because +-- the dist. key is ANDed with the rest of the +-- filters +SELECT * + FROM articles_hash + WHERE author_id = 1 and (id = random()::int * 0); + +-- not router plannable due to function call on the right side +SELECT * + FROM articles_hash + WHERE author_id = (random()::int * 0 + 1); + +-- Citus does not qualify this as a fast-path because +-- dist_key = func() +SELECT * + FROM articles_hash + WHERE author_id = abs(-1); + +-- Citus does not qualify this as a fast-path because +-- dist_key = func() +SELECT * + FROM articles_hash + WHERE 1 = abs(author_id); + +-- Citus does not qualify this as a fast-path because +-- dist_key = func() +SELECT * + FROM articles_hash + WHERE author_id = abs(author_id - 2); + +-- the function is not on the dist. key, so qualify as +-- fast-path +SELECT * + FROM articles_hash + WHERE author_id = 1 and (id = abs(id - 2)); + +-- not router plannable due to is true +SELECT * + FROM articles_hash + WHERE (author_id = 1) is true; + +-- router plannable, (boolean expression) = true is collapsed to (boolean expression) +SELECT * + FROM articles_hash + WHERE (author_id = 1) = true; + +-- some more complex quals +SELECT count(*) FROM articles_hash WHERE (author_id = 15) AND (id = 1 OR word_count > 5); +SELECT count(*) FROM articles_hash WHERE (author_id = 15) OR (id = 1 AND word_count > 5); +SELECT count(*) FROM articles_hash WHERE (id = 15) OR (author_id = 1 AND word_count > 5); +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (author_id = 1 OR word_count > 5); +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (author_id = 1 AND (word_count > 5 OR id = 2)); +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (title ilike 'a%' AND (word_count > 5 OR author_id = 2)); +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (title ilike 'a%' AND (word_count > 5 AND author_id = 2)); +SELECT count(*) FROM articles_hash WHERE (id = 15) AND (title ilike 'a%' AND ((word_count > 5 OR title ilike 'b%' ) AND (author_id = 2 AND word_count > 50))); + +-- fast-path router plannable, between operator is on another column +SELECT * + FROM articles_hash + WHERE (author_id = 1) and id between 0 and 20; + +-- fast-path router plannable, partition column expression is and'ed to rest +SELECT * + FROM articles_hash + WHERE (author_id = 1) and (id = 1 or id = 31) and title like '%s'; + +-- fast-path router plannable, order is changed +SELECT * + FROM articles_hash + WHERE (id = 1 or id = 31) and title like '%s' and (author_id = 1); + +-- fast-path router plannable +SELECT * + FROM articles_hash + WHERE (title like '%s' or title like 'a%') and (author_id = 1); + +-- fast-path router plannable +SELECT * + FROM articles_hash + WHERE (title like '%s' or title like 'a%') and (author_id = 1) and (word_count < 3000 or word_count > 8000); + +-- window functions are supported with fast-path router plannable +SELECT LAG(title, 1) over (ORDER BY word_count) prev, title, word_count + FROM articles_hash + WHERE author_id = 5; + +SELECT LAG(title, 1) over (ORDER BY word_count) prev, title, word_count + FROM articles_hash + WHERE author_id = 5 + ORDER BY word_count DESC; + +SELECT id, MIN(id) over (order by word_count) + FROM articles_hash + WHERE author_id = 1; + +SELECT id, word_count, AVG(word_count) over (order by word_count) + FROM articles_hash + WHERE author_id = 1; + +SELECT word_count, rank() OVER (PARTITION BY author_id ORDER BY word_count) + FROM articles_hash + WHERE author_id = 1; + +-- some more tests on complex target lists +SELECT DISTINCT ON (author_id, id) author_id, id, + MIN(id) over (order by avg(word_count)) * AVG(id * 5.2 + (1.0/max(word_count))) over (order by max(word_count)) as t1, + count(*) FILTER (WHERE title LIKE 'al%') as cnt_with_filter, + count(*) FILTER (WHERE '0300030' LIKE '%3%') as cnt_with_filter_2, + avg(case when id > 2 then char_length(word_count::text) * (id * strpos(word_count::text, '1')) end) as case_cnt, + COALESCE(strpos(avg(word_count)::text, '1'), 20) + FROM articles_hash as aliased_table + WHERE author_id = 1 + GROUP BY author_id, id + HAVING count(DISTINCT title) > 0 + ORDER BY author_id, id, sum(word_count) - avg(char_length(title)) DESC, COALESCE(array_upper(ARRAY[max(id)],1) * 5,0) DESC; + +-- where false queries are router plannable but not fast-path +SELECT * + FROM articles_hash + WHERE false; + +-- fast-path with false +SELECT * + FROM articles_hash + WHERE author_id = 1 and false; + +-- fast-path with false +SELECT * + FROM articles_hash + WHERE author_id = 1 and 1=0; + +SELECT * + FROM articles_hash + WHERE null and author_id = 1; + +-- we cannot qualify dist_key = X operator Y via +-- fast-path planning +SELECT * + FROM articles_hash + WHERE author_id = 1 + 1; + +-- where false with immutable function returning false +-- goes through fast-path +SELECT * + FROM articles_hash a + WHERE a.author_id = 10 and int4eq(1, 2); + +-- partition_column is null clause does not prune out any shards, +-- all shards remain after shard pruning, not router plannable +-- not fast-path router either +SELECT * + FROM articles_hash a + WHERE a.author_id is null; + +-- partition_column equals to null clause prunes out all shards +-- no shards after shard pruning, router plannable +-- not fast-path router either +SELECT * + FROM articles_hash a + WHERE a.author_id = null; + +-- union/difference /intersection with where false +-- this query was not originally router plannable, addition of 1=0 +-- makes it router plannable but not fast-path +SELECT * FROM ( + SELECT * FROM articles_hash WHERE author_id = 1 + UNION + SELECT * FROM articles_hash WHERE author_id = 2 and 1=0 +) AS combination +ORDER BY id; + +-- same with the above, but with WHERE false +SELECT * FROM ( + SELECT * FROM articles_hash WHERE author_id = 1 + UNION + SELECT * FROM articles_hash WHERE author_id = 2 and 1=0 +) AS combination WHERE false +ORDER BY id; + +-- window functions with where false +SELECT word_count, rank() OVER (PARTITION BY author_id ORDER BY word_count) + FROM articles_hash + WHERE author_id = 1 and 1=0; + +-- create a dummy function to be used in filtering +CREATE OR REPLACE FUNCTION someDummyFunction(regclass) + RETURNS text AS +$$ +BEGIN + RETURN md5($1::text); +END; +$$ LANGUAGE 'plpgsql' IMMUTABLE; + + +-- fast path router plannable, but errors +SELECT * FROM articles_hash + WHERE + someDummyFunction('articles_hash') = md5('articles_hash') AND author_id = 1 + ORDER BY + author_id, id + LIMIT 5; + +-- temporarily turn off debug messages before dropping the function +SET client_min_messages TO 'NOTICE'; +DROP FUNCTION someDummyFunction(regclass); + +SET client_min_messages TO 'DEBUG2'; + +-- complex query hitting a single shard and a fast-path +SELECT + count(DISTINCT CASE + WHEN + word_count > 100 + THEN + id + ELSE + NULL + END) as c + FROM + articles_hash + WHERE + author_id = 5; +-- queries inside transactions can be fast-path router plannable +BEGIN; +SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +END; + +-- queries inside read-only transactions can be fast-path router plannable +SET TRANSACTION READ ONLY; +SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +END; + +-- cursor queries are fast-path router plannable +BEGIN; +DECLARE test_cursor CURSOR FOR + SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; +FETCH test_cursor; +FETCH ALL test_cursor; +FETCH test_cursor; -- fetch one row after the last +FETCH BACKWARD test_cursor; +END; + +-- queries inside copy can be router plannable +COPY ( + SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id) TO STDOUT; + +-- table creation queries inside can be fast-path router plannable +CREATE TEMP TABLE temp_articles_hash as + SELECT * + FROM articles_hash + WHERE author_id = 1 + ORDER BY id; + +-- fast-path router plannable queries may include filter for aggragates +SELECT count(*), count(*) FILTER (WHERE id < 3) + FROM articles_hash + WHERE author_id = 1; + +-- prepare queries can be router plannable +PREPARE author_1_articles as + SELECT * + FROM articles_hash + WHERE author_id = 1; + +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; +EXECUTE author_1_articles; + +-- parametric prepare queries can be router plannable +PREPARE author_articles(int) as + SELECT * + FROM articles_hash + WHERE author_id = $1; + +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); +EXECUTE author_articles(1); + +-- queries inside plpgsql functions could be router plannable +CREATE OR REPLACE FUNCTION author_articles_max_id() RETURNS int AS $$ +DECLARE + max_id integer; +BEGIN + SELECT MAX(id) FROM articles_hash ah + WHERE author_id = 1 + into max_id; + return max_id; +END; +$$ LANGUAGE plpgsql; + +-- we don't want too many details. though we're omitting +-- "DETAIL: distribution column value:", we see it acceptable +-- since the query results verifies the correctness +\set VERBOSITY terse + +SELECT author_articles_max_id(); +SELECT author_articles_max_id(); +SELECT author_articles_max_id(); +SELECT author_articles_max_id(); +SELECT author_articles_max_id(); +SELECT author_articles_max_id(); + +-- queries inside plpgsql functions could be router plannable +CREATE OR REPLACE FUNCTION author_articles_max_id(int) RETURNS int AS $$ +DECLARE + max_id integer; +BEGIN + SELECT MAX(id) FROM articles_hash ah + WHERE author_id = $1 + into max_id; + return max_id; +END; +$$ LANGUAGE plpgsql; +SELECT author_articles_max_id(1); +SELECT author_articles_max_id(1); +SELECT author_articles_max_id(1); +SELECT author_articles_max_id(1); +SELECT author_articles_max_id(1); +SELECT author_articles_max_id(1); + +-- check that function returning setof query are router plannable +CREATE OR REPLACE FUNCTION author_articles_id_word_count() RETURNS TABLE(id bigint, word_count int) AS $$ +DECLARE +BEGIN + RETURN QUERY + SELECT ah.id, ah.word_count + FROM articles_hash ah + WHERE author_id = 1; + +END; +$$ LANGUAGE plpgsql; + +SELECT * FROM author_articles_id_word_count(); +SELECT * FROM author_articles_id_word_count(); +SELECT * FROM author_articles_id_word_count(); +SELECT * FROM author_articles_id_word_count(); +SELECT * FROM author_articles_id_word_count(); +SELECT * FROM author_articles_id_word_count(); + +-- check that function returning setof query are router plannable +CREATE OR REPLACE FUNCTION author_articles_id_word_count(int) RETURNS TABLE(id bigint, word_count int) AS $$ +DECLARE +BEGIN + RETURN QUERY + SELECT ah.id, ah.word_count + FROM articles_hash ah + WHERE author_id = $1; + +END; +$$ LANGUAGE plpgsql; +SELECT * FROM author_articles_id_word_count(1); +SELECT * FROM author_articles_id_word_count(1); +SELECT * FROM author_articles_id_word_count(1); +SELECT * FROM author_articles_id_word_count(1); +SELECT * FROM author_articles_id_word_count(1); +SELECT * FROM author_articles_id_word_count(1); + +\set VERBOSITY default + +-- insert .. select via coordinator could also +-- use fast-path queries +PREPARE insert_sel(int, int) AS +INSERT INTO articles_hash + SELECT * FROM articles_hash WHERE author_id = $2 AND word_count = $1 OFFSET 0; + +EXECUTE insert_sel(1,1); +EXECUTE insert_sel(1,1); +EXECUTE insert_sel(1,1); +EXECUTE insert_sel(1,1); +EXECUTE insert_sel(1,1); +EXECUTE insert_sel(1,1); + +-- one final interesting preperad statement +-- where one of the filters is on the target list +PREPARE fast_path_agg_filter(int, int) AS + SELECT + count(*) FILTER (WHERE word_count=$1) + FROM + articles_hash + WHERE author_id = $2; + +EXECUTE fast_path_agg_filter(1,1); +EXECUTE fast_path_agg_filter(2,2); +EXECUTE fast_path_agg_filter(3,3); +EXECUTE fast_path_agg_filter(4,4); +EXECUTE fast_path_agg_filter(5,5); +EXECUTE fast_path_agg_filter(6,6); + +-- views internally become subqueries, so not fast-path router query +CREATE VIEW test_view AS + SELECT * FROM articles_hash WHERE author_id = 1; +SELECT * FROM test_view; + +-- materialized views can be created for fast-path router plannable queries +CREATE MATERIALIZED VIEW mv_articles_hash_empty AS + SELECT * FROM articles_hash WHERE author_id = 1; +SELECT * FROM mv_articles_hash_empty; + + +-- fast-path router planner/executor is enabled for task-tracker executor +SET citus.task_executor_type to 'task-tracker'; +SELECT id + FROM articles_hash + WHERE author_id = 1; + +-- insert query is router plannable even under task-tracker +INSERT INTO articles_hash VALUES (51, 1, 'amateus', 1814), (52, 1, 'second amateus', 2824); + +-- verify insert is successfull (not router plannable and executable) +SELECT id + FROM articles_hash + WHERE author_id = 1; + +SET client_min_messages to 'NOTICE'; + +-- finally, some tests with partitioned tables +CREATE TABLE collections_list ( + key bigint, + ts timestamptz, + collection_id integer, + value numeric +) PARTITION BY LIST (collection_id ); + +CREATE TABLE collections_list_1 + PARTITION OF collections_list (key, ts, collection_id, value) + FOR VALUES IN ( 1 ); + +CREATE TABLE collections_list_2 + PARTITION OF collections_list (key, ts, collection_id, value) + FOR VALUES IN ( 2 ); + +-- we don't need many shards +SET citus.shard_count TO 2; + +SELECT create_distributed_table('collections_list', 'key'); +INSERT INTO collections_list SELECT i % 10, now(), (i % 2) + 1, i*i FROM generate_series(0, 50)i; + +SET client_min_messages to 'DEBUG2'; + +SELECT count(*) FROM collections_list WHERE key = 4; +SELECT count(*) FROM collections_list_1 WHERE key = 4; +SELECT count(*) FROM collections_list_2 WHERE key = 4; +UPDATE collections_list SET value = 15 WHERE key = 4; +SELECT count(*) FILTER (where value = 15) FROM collections_list WHERE key = 4; +SELECT count(*) FILTER (where value = 15) FROM collections_list_1 WHERE key = 4; +SELECT count(*) FILTER (where value = 15) FROM collections_list_2 WHERE key = 4; + +SET client_min_messages to 'NOTICE'; + +DROP FUNCTION author_articles_max_id(); +DROP FUNCTION author_articles_id_word_count(); + +DROP MATERIALIZED VIEW mv_articles_hash_empty; +DROP MATERIALIZED VIEW mv_articles_hash_data; + +DROP TABLE articles_hash; +DROP TABLE authors_hash; +DROP TABLE authors_range; +DROP TABLE authors_reference; +DROP TABLE company_employees; +DROP TABLE articles_range; +DROP TABLE articles_append; +DROP TABLE collections_list; + +RESET search_path; +DROP SCHEMA fast_path_router_select CASCADE; diff --git a/src/test/regress/sql/multi_simple_queries.sql b/src/test/regress/sql/multi_simple_queries.sql index 1ba8e63c1..8a03f06c9 100644 --- a/src/test/regress/sql/multi_simple_queries.sql +++ b/src/test/regress/sql/multi_simple_queries.sql @@ -1,6 +1,10 @@ SET citus.next_shard_id TO 850000; +-- many of the tests in this file is intended for testing non-fast-path +-- router planner, so we're explicitly disabling it in this file. +-- We've bunch of other tests that triggers fast-path-router +SET citus.enable_fast_path_router_planner TO false; -- =================================================================== -- test end-to-end query functionality @@ -302,4 +306,12 @@ SELECT * FROM articles TABLESAMPLE BERNOULLI (0) WHERE author_id = 1; SELECT * FROM articles TABLESAMPLE SYSTEM (100) WHERE author_id = 1 ORDER BY id; SELECT * FROM articles TABLESAMPLE BERNOULLI (100) WHERE author_id = 1 ORDER BY id; +-- test tablesample with fast path as well +SET citus.enable_fast_path_router_planner TO true; +SELECT * FROM articles TABLESAMPLE SYSTEM (0) WHERE author_id = 1; +SELECT * FROM articles TABLESAMPLE BERNOULLI (0) WHERE author_id = 1; +SELECT * FROM articles TABLESAMPLE SYSTEM (100) WHERE author_id = 1 ORDER BY id; +SELECT * FROM articles TABLESAMPLE BERNOULLI (100) WHERE author_id = 1 ORDER BY id; + + SET client_min_messages to 'NOTICE'; From 407d0e30f5af22fb3e7a751fe7a701bf49e52e4a Mon Sep 17 00:00:00 2001 From: Onder Kalaci Date: Thu, 21 Feb 2019 18:21:41 +0300 Subject: [PATCH 07/20] Fix selectForUpdate bug --- .../distributed/planner/fast_path_router_planner.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/backend/distributed/planner/fast_path_router_planner.c b/src/backend/distributed/planner/fast_path_router_planner.c index f795ef480..efb1488ce 100644 --- a/src/backend/distributed/planner/fast_path_router_planner.c +++ b/src/backend/distributed/planner/fast_path_router_planner.c @@ -213,14 +213,10 @@ FastPathRouterQuery(Query *query) * hasForUpdate is tricky because Citus does support only when * replication = 1 or reference tables. */ - if (query->hasForUpdate) + if (query->hasForUpdate && + !(cacheEntry->partitionMethod == DISTRIBUTE_BY_NONE || + SingleReplicatedTable(distributedTableId))) { - if (cacheEntry->partitionMethod == DISTRIBUTE_BY_NONE || - SingleReplicatedTable(distributedTableId)) - { - return true; - } - return false; } From e521e7e39cc0b8093fadcae767e8f934edefecae Mon Sep 17 00:00:00 2001 From: Onder Kalaci Date: Fri, 22 Feb 2019 18:14:30 +0300 Subject: [PATCH 08/20] Apply feedback --- .../planner/fast_path_router_planner.c | 11 ---------- .../planner/multi_router_planner.c | 20 +++++++++---------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/backend/distributed/planner/fast_path_router_planner.c b/src/backend/distributed/planner/fast_path_router_planner.c index efb1488ce..ebba58214 100644 --- a/src/backend/distributed/planner/fast_path_router_planner.c +++ b/src/backend/distributed/planner/fast_path_router_planner.c @@ -209,17 +209,6 @@ FastPathRouterQuery(Query *query) return false; } - /* - * hasForUpdate is tricky because Citus does support only when - * replication = 1 or reference tables. - */ - if (query->hasForUpdate && - !(cacheEntry->partitionMethod == DISTRIBUTE_BY_NONE || - SingleReplicatedTable(distributedTableId))) - { - return false; - } - /* WHERE clause should not be empty for distributed tables */ if (joinTree == NULL || (cacheEntry->partitionMethod != DISTRIBUTE_BY_NONE && joinTree->quals == NULL)) diff --git a/src/backend/distributed/planner/multi_router_planner.c b/src/backend/distributed/planner/multi_router_planner.c index 666c5a207..8694e44ed 100644 --- a/src/backend/distributed/planner/multi_router_planner.c +++ b/src/backend/distributed/planner/multi_router_planner.c @@ -137,8 +137,7 @@ static void NormalizeMultiRowInsertTargetList(Query *query); static List * BuildRoutesForInsert(Query *query, DeferredErrorMessage **planningError); static List * GroupInsertValuesByShardId(List *insertValuesList); static List * ExtractInsertValuesList(Query *query, Var *partitionColumn); -static bool MultiRouterPlannableQuery(Query *query, - RelationRestrictionContext *restrictionContext); +static bool MultiRouterPlannableQuery(Query *query); static DeferredErrorMessage * ErrorIfQueryHasModifyingCTE(Query *queryTree); static RangeTblEntry * GetUpdateOrDeleteRTE(Query *query); static bool SelectsFromDistributedTable(List *rangeTableList, Query *query); @@ -169,8 +168,7 @@ DistributedPlan * CreateRouterPlan(Query *originalQuery, Query *query, PlannerRestrictionContext *plannerRestrictionContext) { - if (MultiRouterPlannableQuery(query, - plannerRestrictionContext->relationRestrictionContext)) + if (MultiRouterPlannableQuery(query)) { return CreateSingleTaskRouterPlan(originalQuery, query, plannerRestrictionContext); @@ -2133,7 +2131,7 @@ TargetShardIntervalForFastPathQuery(Query *query, Const **partitionValueConst, *isMultiShardQuery = true; } else if (list_length(prunedShardIntervalList) == 1 && - queryPartitionValueConst != NULL) + partitionValueConst != NULL) { /* set the outgoing partition column value if requested */ *partitionValueConst = queryPartitionValueConst; @@ -2910,10 +2908,11 @@ ExtractInsertPartitionKeyValue(Query *query) * flag to false. */ static bool -MultiRouterPlannableQuery(Query *query, RelationRestrictionContext *restrictionContext) +MultiRouterPlannableQuery(Query *query) { CmdType commandType = query->commandType; - ListCell *relationRestrictionContextCell = NULL; + List *rangeTableRelationList = NIL; + ListCell *rangeTableRelationCell = NULL; if (commandType == CMD_INSERT || commandType == CMD_UPDATE || commandType == CMD_DELETE) @@ -2928,11 +2927,10 @@ MultiRouterPlannableQuery(Query *query, RelationRestrictionContext *restrictionC return false; } - foreach(relationRestrictionContextCell, restrictionContext->relationRestrictionList) + ExtractRangeTableRelationWalker((Node *) query, &rangeTableRelationList); + foreach(rangeTableRelationCell, rangeTableRelationList) { - RelationRestriction *relationRestriction = - (RelationRestriction *) lfirst(relationRestrictionContextCell); - RangeTblEntry *rte = relationRestriction->rte; + RangeTblEntry *rte = (RangeTblEntry *) lfirst(rangeTableRelationCell); if (rte->rtekind == RTE_RELATION) { /* only hash partitioned tables are supported */ From f706772b2f465959a5b95bd8056efd78f2019410 Mon Sep 17 00:00:00 2001 From: Onder Kalaci Date: Thu, 21 Feb 2019 18:02:58 +0300 Subject: [PATCH 09/20] Round-robin task assignment policy relies on local transaction id Before this commit, round-robin task assignment policy was relying on the taskId. Thus, even inside a transaction, the tasks were assigned to different nodes. This was especially problematic while reading from reference tables within transaction blocks. Because, we had to expand the distributed transaction to many nodes that are not necessarily already in the distributed transaction. --- .../planner/multi_physical_planner.c | 17 +- .../distributed/transaction/backend_data.c | 11 ++ src/include/distributed/backend_data.h | 1 + .../distributed/multi_physical_planner.h | 1 + .../expected/multi_task_assignment_policy.out | 183 +++++++++++------- .../sql/multi_task_assignment_policy.sql | 141 +++++++++++--- 6 files changed, 263 insertions(+), 91 deletions(-) diff --git a/src/backend/distributed/planner/multi_physical_planner.c b/src/backend/distributed/planner/multi_physical_planner.c index eaecbb8e7..8e359239f 100644 --- a/src/backend/distributed/planner/multi_physical_planner.c +++ b/src/backend/distributed/planner/multi_physical_planner.c @@ -29,6 +29,7 @@ #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/sequence.h" +#include "distributed/backend_data.h" #include "distributed/listutils.h" #include "distributed/citus_nodefuncs.h" #include "distributed/citus_nodes.h" @@ -5076,14 +5077,24 @@ RoundRobinAssignTaskList(List *taskList) /* * RoundRobinReorder implements the core of the round-robin assignment policy. * It takes a task and placement list and rotates a copy of the placement list - * based on the task's jobId. The rotated copy is returned. + * based on the latest stable transaction id provided by PostgreSQL. + * + * We prefer to use transactionId as the seed for the rotation to use the replicas + * in the same worker node within the same transaction. This becomes more important + * when we're reading from (the same or multiple) reference tables within a + * transaction. With this approach, we can prevent reads to expand the worker nodes + * that participate in a distributed transaction. + * + * Note that we prefer PostgreSQL's transactionId over distributed transactionId that + * Citus generates since the distributed transactionId is generated during the execution + * where as task-assignment happens duing the planning. */ static List * RoundRobinReorder(Task *task, List *placementList) { - uint64 jobId = task->jobId; + TransactionId transactionId = GetMyProcLocalTransactionId(); uint32 activePlacementCount = list_length(placementList); - uint32 roundRobinIndex = (jobId % activePlacementCount); + uint32 roundRobinIndex = (transactionId % activePlacementCount); placementList = LeftRotateList(placementList, roundRobinIndex); diff --git a/src/backend/distributed/transaction/backend_data.c b/src/backend/distributed/transaction/backend_data.c index 0ffb6d175..e790bd80b 100644 --- a/src/backend/distributed/transaction/backend_data.c +++ b/src/backend/distributed/transaction/backend_data.c @@ -993,3 +993,14 @@ ActiveDistributedTransactionNumbers(void) return activeTransactionNumberList; } + + +/* + * GetMyProcLocalTransactionId() is a wrapper for + * getting lxid of MyProc. + */ +LocalTransactionId +GetMyProcLocalTransactionId(void) +{ + return MyProc->lxid; +} diff --git a/src/include/distributed/backend_data.h b/src/include/distributed/backend_data.h index cc0f04ca8..17e896f61 100644 --- a/src/include/distributed/backend_data.h +++ b/src/include/distributed/backend_data.h @@ -67,5 +67,6 @@ extern void GetBackendDataForProc(PGPROC *proc, BackendData *result); extern void CancelTransactionDueToDeadlock(PGPROC *proc); extern bool MyBackendGotCancelledDueToDeadlock(void); extern List * ActiveDistributedTransactionNumbers(void); +LocalTransactionId GetMyProcLocalTransactionId(void); #endif /* BACKEND_DATA_H */ diff --git a/src/include/distributed/multi_physical_planner.h b/src/include/distributed/multi_physical_planner.h index c4d662d5f..68831294a 100644 --- a/src/include/distributed/multi_physical_planner.h +++ b/src/include/distributed/multi_physical_planner.h @@ -351,6 +351,7 @@ extern List * TaskListDifference(const List *list1, const List *list2); extern List * AssignAnchorShardTaskList(List *taskList); extern List * FirstReplicaAssignTaskList(List *taskList); extern List * RoundRobinAssignTaskList(List *taskList); +extern List * RoundRobinPerTransactionAssignTaskList(List *taskList); extern int CompareTasksByTaskId(const void *leftElement, const void *rightElement); /* function declaration for creating Task */ diff --git a/src/test/regress/expected/multi_task_assignment_policy.out b/src/test/regress/expected/multi_task_assignment_policy.out index 9b63c2ad4..686b1f9cc 100644 --- a/src/test/regress/expected/multi_task_assignment_policy.out +++ b/src/test/regress/expected/multi_task_assignment_policy.out @@ -10,6 +10,27 @@ SELECT substring(:'server_version', '\d+')::int > 9 AS version_above_nine; t (1 row) +-- the function simply parses the results and returns 'shardId@worker' +-- for all the explain task outputs +CREATE OR REPLACE FUNCTION parse_explain_output(in qry text, in table_name text, out r text) +RETURNS SETOF TEXT AS $$ +DECLARE + portOfTheTask text; + shardOfTheTask text; +begin + for r in execute qry loop + IF r LIKE '%port%' THEN + portOfTheTask = substring(r, '([0-9]{1,10})'); + END IF; + + IF r LIKE '%' || table_name || '%' THEN + shardOfTheTask = substring(r, '([0-9]{1,10})'); + return QUERY SELECT shardOfTheTask || '@' || portOfTheTask ; + END IF; + + end loop; + return; +end; $$ language plpgsql; SET citus.explain_distributed_queries TO off; -- Check that our policies for assigning tasks to worker nodes run as expected. -- To test this, we first create a shell table, and then manually insert shard @@ -102,48 +123,7 @@ DEBUG: assigned task 1 to node localhost:57638 explain statements for distributed queries are not enabled (3 rows) --- Finally test the round-robin task assignment policy -SET citus.task_assignment_policy TO 'round-robin'; -EXPLAIN SELECT count(*) FROM task_assignment_test_table; -DEBUG: assigned task 3 to node localhost:57638 -DEBUG: assigned task 2 to node localhost:57638 -DEBUG: assigned task 1 to node localhost:57637 - QUERY PLAN ------------------------------------------------------------------------ - Aggregate (cost=0.00..0.00 rows=0 width=0) - -> Custom Scan (Citus Real-Time) (cost=0.00..0.00 rows=0 width=0) - explain statements for distributed queries are not enabled -(3 rows) - -EXPLAIN SELECT count(*) FROM task_assignment_test_table; -DEBUG: assigned task 3 to node localhost:57637 -DEBUG: assigned task 2 to node localhost:57637 -DEBUG: assigned task 1 to node localhost:57638 - QUERY PLAN ------------------------------------------------------------------------ - Aggregate (cost=0.00..0.00 rows=0 width=0) - -> Custom Scan (Citus Real-Time) (cost=0.00..0.00 rows=0 width=0) - explain statements for distributed queries are not enabled -(3 rows) - -EXPLAIN SELECT count(*) FROM task_assignment_test_table; -DEBUG: assigned task 3 to node localhost:57638 -DEBUG: assigned task 2 to node localhost:57638 -DEBUG: assigned task 1 to node localhost:57637 - QUERY PLAN ------------------------------------------------------------------------ - Aggregate (cost=0.00..0.00 rows=0 width=0) - -> Custom Scan (Citus Real-Time) (cost=0.00..0.00 rows=0 width=0) - explain statements for distributed queries are not enabled -(3 rows) - -RESET citus.task_assignment_policy; -RESET client_min_messages; COMMIT; -BEGIN; -SET LOCAL client_min_messages TO DEBUG3; -SET LOCAL citus.explain_distributed_queries TO off; --- Check how task_assignment_policy impact planning decisions for reference tables CREATE TABLE task_assignment_reference_table (test_id integer); SELECT create_reference_table('task_assignment_reference_table'); create_reference_table @@ -151,6 +131,10 @@ SELECT create_reference_table('task_assignment_reference_table'); (1 row) +BEGIN; +SET LOCAL client_min_messages TO DEBUG3; +SET LOCAL citus.explain_distributed_queries TO off; +-- Check how task_assignment_policy impact planning decisions for reference tables SET LOCAL citus.task_assignment_policy TO 'greedy'; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; DEBUG: Distributed planning for a fast-path router query @@ -193,31 +177,99 @@ DEBUG: Plan is router executable explain statements for distributed queries are not enabled (2 rows) --- here we expect debug output showing two different hosts for subsequent queries -SET LOCAL citus.task_assignment_policy TO 'round-robin'; -EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; -DEBUG: Distributed planning for a fast-path router query -DEBUG: assigned task 0 to node localhost:57637 -DEBUG: Creating router plan -DEBUG: Plan is router executable - QUERY PLAN --------------------------------------------------------------- - Custom Scan (Citus Router) - explain statements for distributed queries are not enabled -(2 rows) - -EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; -DEBUG: Distributed planning for a fast-path router query -DEBUG: assigned task 0 to node localhost:57638 -DEBUG: Creating router plan -DEBUG: Plan is router executable - QUERY PLAN --------------------------------------------------------------- - Custom Scan (Citus Router) - explain statements for distributed queries are not enabled -(2 rows) - ROLLBACK; +RESET client_min_messages; +-- Now, lets test round-robin policy +-- round-robin policy relies on PostgreSQL's local transactionId, +-- which might change and we don't have any control over it. +-- the important thing that we look for is that round-robin policy +-- should give the same output for executions in the same transaction +-- and different output for executions that are not insdie the +-- same transaction. To ensure that, we define a helper function +BEGIN; +SET LOCAL citus.explain_distributed_queries TO on; +CREATE TEMPORARY TABLE explain_outputs (value text); +SET LOCAL citus.task_assignment_policy TO 'round-robin'; +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); +-- given that we're in the same transaction, the count should be 1 +SELECT count(DISTINCT value) FROM explain_outputs; + count +------- + 1 +(1 row) + +DROP TABLE explain_outputs; +COMMIT; +-- now test round-robin policy outside +-- a transaction, we should see the assignements +-- change on every execution +CREATE TEMPORARY TABLE explain_outputs (value text); +SET citus.task_assignment_policy TO 'round-robin'; +SET citus.explain_distributed_queries TO ON; +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); +-- given that we're in the same transaction, the count should be 2 +-- since there are two different worker nodes +SELECT count(DISTINCT value) FROM explain_outputs; + count +------- + 2 +(1 row) + +TRUNCATE explain_outputs; +-- same test with a distributed table +-- we keep this test because as of this commit, the code +-- paths for reference tables and distributed tables are +-- not the same +SET citus.shard_replication_factor TO 2; +CREATE TABLE task_assignment_replicated_hash (test_id integer); +SELECT create_distributed_table('task_assignment_replicated_hash', 'test_id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; +SET LOCAL citus.explain_distributed_queries TO on; +SET LOCAL citus.task_assignment_policy TO 'round-robin'; +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); +-- given that we're in the same transaction, the count should be 1 +SELECT count(DISTINCT value) FROM explain_outputs; + count +------- + 1 +(1 row) + +DROP TABLE explain_outputs; +COMMIT; +-- now test round-robin policy outside +-- a transaction, we should see the assignements +-- change on every execution +CREATE TEMPORARY TABLE explain_outputs (value text); +SET citus.task_assignment_policy TO 'round-robin'; +SET citus.explain_distributed_queries TO ON; +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); +-- given that we're in the same transaction, the count should be 2 +-- since there are two different worker nodes +SELECT count(DISTINCT value) FROM explain_outputs; + count +------- + 2 +(1 row) + +RESET citus.task_assignment_policy; +RESET client_min_messages; -- we should be able to use round-robin with router queries that -- only contains intermediate results BEGIN; @@ -240,3 +292,4 @@ WITH q1 AS (SELECT * FROM task_assignment_test_table_2) SELECT * FROM q1; (0 rows) ROLLBACK; +DROP TABLE task_assignment_replicated_hash, task_assignment_reference_table; diff --git a/src/test/regress/sql/multi_task_assignment_policy.sql b/src/test/regress/sql/multi_task_assignment_policy.sql index 6b2da3948..ccbc72018 100644 --- a/src/test/regress/sql/multi_task_assignment_policy.sql +++ b/src/test/regress/sql/multi_task_assignment_policy.sql @@ -9,6 +9,28 @@ SET citus.next_shard_id TO 880000; SHOW server_version \gset SELECT substring(:'server_version', '\d+')::int > 9 AS version_above_nine; +-- the function simply parses the results and returns 'shardId@worker' +-- for all the explain task outputs +CREATE OR REPLACE FUNCTION parse_explain_output(in qry text, in table_name text, out r text) +RETURNS SETOF TEXT AS $$ +DECLARE + portOfTheTask text; + shardOfTheTask text; +begin + for r in execute qry loop + IF r LIKE '%port%' THEN + portOfTheTask = substring(r, '([0-9]{1,10})'); + END IF; + + IF r LIKE '%' || table_name || '%' THEN + shardOfTheTask = substring(r, '([0-9]{1,10})'); + return QUERY SELECT shardOfTheTask || '@' || portOfTheTask ; + END IF; + + end loop; + return; +end; $$ language plpgsql; + SET citus.explain_distributed_queries TO off; @@ -80,31 +102,19 @@ EXPLAIN SELECT count(*) FROM task_assignment_test_table; EXPLAIN SELECT count(*) FROM task_assignment_test_table; --- Finally test the round-robin task assignment policy - -SET citus.task_assignment_policy TO 'round-robin'; - -EXPLAIN SELECT count(*) FROM task_assignment_test_table; - -EXPLAIN SELECT count(*) FROM task_assignment_test_table; - -EXPLAIN SELECT count(*) FROM task_assignment_test_table; - -RESET citus.task_assignment_policy; -RESET client_min_messages; - COMMIT; + + +CREATE TABLE task_assignment_reference_table (test_id integer); +SELECT create_reference_table('task_assignment_reference_table'); + BEGIN; SET LOCAL client_min_messages TO DEBUG3; SET LOCAL citus.explain_distributed_queries TO off; -- Check how task_assignment_policy impact planning decisions for reference tables - -CREATE TABLE task_assignment_reference_table (test_id integer); -SELECT create_reference_table('task_assignment_reference_table'); - SET LOCAL citus.task_assignment_policy TO 'greedy'; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; @@ -113,15 +123,100 @@ SET LOCAL citus.task_assignment_policy TO 'first-replica'; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; --- here we expect debug output showing two different hosts for subsequent queries -SET LOCAL citus.task_assignment_policy TO 'round-robin'; -EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; -EXPLAIN (COSTS FALSE) SELECT * FROM task_assignment_reference_table; - ROLLBACK; +RESET client_min_messages; +-- Now, lets test round-robin policy +-- round-robin policy relies on PostgreSQL's local transactionId, +-- which might change and we don't have any control over it. +-- the important thing that we look for is that round-robin policy +-- should give the same output for executions in the same transaction +-- and different output for executions that are not insdie the +-- same transaction. To ensure that, we define a helper function +BEGIN; + +SET LOCAL citus.explain_distributed_queries TO on; + +CREATE TEMPORARY TABLE explain_outputs (value text); +SET LOCAL citus.task_assignment_policy TO 'round-robin'; + +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); + +-- given that we're in the same transaction, the count should be 1 +SELECT count(DISTINCT value) FROM explain_outputs; + +DROP TABLE explain_outputs; +COMMIT; + +-- now test round-robin policy outside +-- a transaction, we should see the assignements +-- change on every execution +CREATE TEMPORARY TABLE explain_outputs (value text); + +SET citus.task_assignment_policy TO 'round-robin'; +SET citus.explain_distributed_queries TO ON; + +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_reference_table;', 'task_assignment_reference_table'); + +-- given that we're in the same transaction, the count should be 2 +-- since there are two different worker nodes +SELECT count(DISTINCT value) FROM explain_outputs; +TRUNCATE explain_outputs; + +-- same test with a distributed table +-- we keep this test because as of this commit, the code +-- paths for reference tables and distributed tables are +-- not the same +SET citus.shard_replication_factor TO 2; + +CREATE TABLE task_assignment_replicated_hash (test_id integer); +SELECT create_distributed_table('task_assignment_replicated_hash', 'test_id'); + +BEGIN; + +SET LOCAL citus.explain_distributed_queries TO on; +SET LOCAL citus.task_assignment_policy TO 'round-robin'; + +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); + +-- given that we're in the same transaction, the count should be 1 +SELECT count(DISTINCT value) FROM explain_outputs; + +DROP TABLE explain_outputs; +COMMIT; + +-- now test round-robin policy outside +-- a transaction, we should see the assignements +-- change on every execution +CREATE TEMPORARY TABLE explain_outputs (value text); + +SET citus.task_assignment_policy TO 'round-robin'; +SET citus.explain_distributed_queries TO ON; + +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); +INSERT INTO explain_outputs + SELECT parse_explain_output('EXPLAIN SELECT count(*) FROM task_assignment_replicated_hash;', 'task_assignment_replicated_hash'); + +-- given that we're in the same transaction, the count should be 2 +-- since there are two different worker nodes +SELECT count(DISTINCT value) FROM explain_outputs; + + +RESET citus.task_assignment_policy; +RESET client_min_messages; + -- we should be able to use round-robin with router queries that -- only contains intermediate results BEGIN; @@ -133,4 +228,4 @@ SET LOCAL citus.task_assignment_policy TO 'round-robin'; WITH q1 AS (SELECT * FROM task_assignment_test_table_2) SELECT * FROM q1; ROLLBACK; - +DROP TABLE task_assignment_replicated_hash, task_assignment_reference_table; From 5db45bac45d9ad83d522278df936bc85f700a69f Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:17:26 -0700 Subject: [PATCH 10/20] Enable CircleCI The configuration for the build is in the YAML file; the changes to the regression runner are backward-compatible with Travis and just add the logic to detect whether our custom (isolation- and vanilla-enabled) pkg is present. --- .circleci/config.yml | 81 ++++++++++++++++++++++++++++ src/test/regress/pg_regress_multi.pl | 27 ++++++++-- 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..41a45855e --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,81 @@ +version: 2.0 + +jobs: + build: + docker: + - {image: 'citusdata/extbuilder:latest'} + steps: + - checkout + - {run: {name: 'Configure, Build, and Install', command: build-ext}} + - {persist_to_workspace: {root: ., paths: [.]}} + test-10_check-multi: + docker: + - {image: 'citusdata/exttester-10:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-multi)', command: 'install-and-test-ext check-multi'}} + test-10_check-tt-van-mx: + docker: + - {image: 'citusdata/exttester-10:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-tt-van-mx)', command: 'install-and-test-ext check-multi-task-tracker-extra check-vanilla check-multi-mx'}} + test-10_check-iso-work-fol: + docker: + - {image: 'citusdata/exttester-10:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-iso-work-fol)', command: 'install-and-test-ext check-isolation check-worker check-follower-cluster'}} + test-10_check-failure: + docker: + - {image: 'citusdata/failtester-10:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-failure)', command: 'install-and-test-ext check-failure'}} + test-11_check-multi: + docker: + - {image: 'citusdata/exttester-11:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-multi)', command: 'install-and-test-ext check-multi'}} + test-11_check-tt-van-mx: + docker: + - {image: 'citusdata/exttester-11:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-tt-van-mx)', command: 'install-and-test-ext check-multi-task-tracker-extra check-vanilla check-multi-mx'}} + test-11_check-iso-work-fol: + docker: + - {image: 'citusdata/exttester-11:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-iso-work-fol)', command: 'install-and-test-ext check-isolation check-worker check-follower-cluster'}} + test-11_check-failure: + docker: + - {image: 'citusdata/failtester-11:latest'} + working_directory: /home/circleci/project + steps: + - {attach_workspace: {at: .}} + - {run: {name: 'Install and Test (check-failure)', command: 'install-and-test-ext check-failure'}} +workflows: + version: 2 + build_and_test: + jobs: + - build + + - {test-10_check-multi: {requires: [build]}} + - {test-10_check-tt-van-mx: {requires: [build]}} + - {test-10_check-iso-work-fol: {requires: [build]}} + - {test-10_check-failure: {requires: [build]}} + + - {test-11_check-multi: {requires: [build]}} + - {test-11_check-tt-van-mx: {requires: [build]}} + - {test-11_check-iso-work-fol: {requires: [build]}} + - {test-11_check-failure: {requires: [build]}} diff --git a/src/test/regress/pg_regress_multi.pl b/src/test/regress/pg_regress_multi.pl index c9846675d..d3eb1b681 100755 --- a/src/test/regress/pg_regress_multi.pl +++ b/src/test/regress/pg_regress_multi.pl @@ -16,6 +16,7 @@ use warnings; use Fcntl; use Getopt::Long; +use File::Basename; use File::Spec::Functions; use File::Path qw(make_path remove_tree); use Config; @@ -151,6 +152,11 @@ else $plainRegress = "$pgxsdir/src/test/regress/pg_regress"; $isolationRegress = "${postgresBuilddir}/src/test/isolation/pg_isolation_regress"; $pgConfig = "$bindir/pg_config"; + + if (-x "$pgxsdir/src/test/isolation/pg_isolation_regress") + { + $isolationRegress = "$pgxsdir/src/test/isolation/pg_isolation_regress"; + } } if ($isolationtester && ! -f "$isolationRegress") @@ -171,7 +177,9 @@ MESSAGE } my $vanillaRegress = catfile("${postgresBuilddir}", "src", "test", "regress", "pg_regress"); -if ($vanillatest && ! -f "$vanillaRegress") +my $vanillaSchedule = catfile(dirname("${pgxsdir}"), "regress", "parallel_schedule"); + +if ($vanillatest && ! (-f "$vanillaRegress" or -f "$vanillaSchedule")) { die <<"MESSAGE"; @@ -796,8 +804,21 @@ if ($vanillatest) $ENV{PGPORT} = $masterPort; $ENV{PGUSER} = $user; - system("make", ("-C", catfile("$postgresBuilddir", "src", "test", "regress"), "installcheck-parallel")) == 0 - or die "Could not run vanilla tests"; + if (-f "$vanillaSchedule") + { + rmdir "./testtablespace"; + mkdir "./testtablespace"; + + my $pgregressdir=catfile(dirname("$pgxsdir"), "regress"); + system("$plainRegress", ("--inputdir", $pgregressdir), + ("--schedule", catfile("$pgregressdir", "parallel_schedule"))) == 0 + or die "Could not run vanilla tests"; + } + else + { + system("make", ("-C", catfile("$postgresBuilddir", "src", "test", "regress"), "installcheck-parallel")) == 0 + or die "Could not run vanilla tests"; + } } elsif ($isolationtester) { From 1b605a61098c42824e98ded974b67e6a7cf401ae Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:20:31 -0700 Subject: [PATCH 11/20] Modernize coverage options These hadn't been looked at in a while, and I'm somewhat certain they actually were running with optimization on, which is pretty bad. Swapped out the lower-level flags for `--coverage`, which will work with both `clang` and `gcc`. On some platforms, linker flags are need- ed as well. --- Makefile.global.in | 2 +- configure | 9 +++++++-- configure.in | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Makefile.global.in b/Makefile.global.in index b3efb54ca..df8eb7cd8 100644 --- a/Makefile.global.in +++ b/Makefile.global.in @@ -69,7 +69,7 @@ endif # Add options passed to configure or computed therein, to CFLAGS/CPPFLAGS/... override CFLAGS += @CFLAGS@ @CITUS_CFLAGS@ -override CPPFLAGS := @CPPFLAGS@ -I '${citus_abs_top_srcdir}/src/include' -I'${citus_top_builddir}/src/include' $(CPPFLAGS) +override CPPFLAGS := @CPPFLAGS@ @CITUS_CPPFLAGS@ -I '${citus_abs_top_srcdir}/src/include' -I'${citus_top_builddir}/src/include' $(CPPFLAGS) override LDFLAGS += @LDFLAGS@ @CITUS_LDFLAGS@ # optional file with user defined, additional, rules diff --git a/configure b/configure index fa5a70cac..c61422438 100755 --- a/configure +++ b/configure @@ -625,6 +625,7 @@ LIBOBJS POSTGRES_BUILDDIR POSTGRES_SRCDIR CITUS_LDFLAGS +CITUS_CPPFLAGS CITUS_CFLAGS EGREP GREP @@ -4052,7 +4053,9 @@ if test "${enable_coverage+set}" = set; then : fi if test "$enable_coverage" = yes; then - CITUS_CFLAGS="$CITUS_CFLAGS -fprofile-arcs -ftest-coverage" + CITUS_CFLAGS="$CITUS_CFLAGS -O0 -g --coverage" + CITUS_CPPFLAGS="$CITUS_CPPFLAGS -DNDEBUG" + CITUS_LDFLAGS="$CITUS_LDFLAGS --coverage" fi # @@ -4183,7 +4186,9 @@ _ACEOF CITUS_CFLAGS="$CITUS_CFLAGS" -CITUS_LDFLAGS="$LIBS" +CITUS_CPPFLAGS="$CITUS_CPPFLAGS" + +CITUS_LDFLAGS="$LIBS $CITUS_LDFLAGS" POSTGRES_SRCDIR="$POSTGRES_SRCDIR" diff --git a/configure.in b/configure.in index 438e46571..315a03393 100644 --- a/configure.in +++ b/configure.in @@ -170,7 +170,9 @@ CITUSAC_PROG_CC_CFLAGS_OPT([-Werror=vla]) # visual studio does not support thes # AC_ARG_ENABLE([coverage], AS_HELP_STRING([--enable-coverage], [build with coverage testing instrumentation])) if test "$enable_coverage" = yes; then - CITUS_CFLAGS="$CITUS_CFLAGS -fprofile-arcs -ftest-coverage" + CITUS_CFLAGS="$CITUS_CFLAGS -O0 -g --coverage" + CITUS_CPPFLAGS="$CITUS_CPPFLAGS -DNDEBUG" + CITUS_LDFLAGS="$CITUS_LDFLAGS --coverage" fi # @@ -201,7 +203,8 @@ AC_DEFINE_UNQUOTED(REPORTS_BASE_URL, "$REPORTS_BASE_URL", [Base URL for statistics collection and update checks]) AC_SUBST(CITUS_CFLAGS, "$CITUS_CFLAGS") -AC_SUBST(CITUS_LDFLAGS, "$LIBS") +AC_SUBST(CITUS_CPPFLAGS, "$CITUS_CPPFLAGS") +AC_SUBST(CITUS_LDFLAGS, "$LIBS $CITUS_LDFLAGS") AC_SUBST(POSTGRES_SRCDIR, "$POSTGRES_SRCDIR") AC_SUBST(POSTGRES_BUILDDIR, "$POSTGRES_BUILDDIR") From 5817bc3cce12a0c4fdc001e7fce70a94ef0eaad1 Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:23:24 -0700 Subject: [PATCH 12/20] Add test-timing script Through some clever stream redirections and options, we can get decent timing data for each of our tests. --- src/test/regress/Makefile | 2 +- src/test/regress/log_test_times | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100755 src/test/regress/log_test_times diff --git a/src/test/regress/Makefile b/src/test/regress/Makefile index 0901defab..1e5ffa8bf 100644 --- a/src/test/regress/Makefile +++ b/src/test/regress/Makefile @@ -15,7 +15,7 @@ endif ## MULTI_INSTALLDIR=$(CURDIR)/tmp_check/install pg_regress_multi_check = $(PERL) $(citus_abs_srcdir)/pg_regress_multi.pl --pgxsdir="$(pgxsdir)" --bindir="$(bindir)" --libdir="$(libdir)" --majorversion="$(MAJORVERSION)" --postgres-builddir="$(postgres_abs_builddir)" --postgres-srcdir="$(postgres_abs_srcdir)" -MULTI_REGRESS_OPTS = --inputdir=$(citus_abs_srcdir) $(pg_regress_locale_flags) +MULTI_REGRESS_OPTS = --inputdir=$(citus_abs_srcdir) $(pg_regress_locale_flags) --launcher="$(citus_abs_srcdir)/log_test_times" # XXX: Can't actually do useful testruns against install - $libdir # etc will point to the directory configured during postgres' diff --git a/src/test/regress/log_test_times b/src/test/regress/log_test_times new file mode 100755 index 000000000..0ceefb10d --- /dev/null +++ b/src/test/regress/log_test_times @@ -0,0 +1,4 @@ +#!/bin/bash +export TIMEFORMAT="${PG_MAJOR}/${PGAPPNAME} %6R" + +{ { time "$@" 1>&3- 2>&4-; } 2>> test_times.log; } 3>&1 4>&2 From 383871af7e8b953cf613ccc17e05525eb4bfbae3 Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:25:06 -0700 Subject: [PATCH 13/20] Upload Codecov results after test runs Our first orb use! --- .circleci/config.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 41a45855e..60274718a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,6 @@ -version: 2.0 +version: 2.1 +orbs: + codecov: codecov/codecov@1.0.4 jobs: build: @@ -15,6 +17,7 @@ jobs: steps: - {attach_workspace: {at: .}} - {run: {name: 'Install and Test (check-multi)', command: 'install-and-test-ext check-multi'}} + - {codecov/upload: {flags: 'test_10,multi'}} test-10_check-tt-van-mx: docker: - {image: 'citusdata/exttester-10:latest'} @@ -22,6 +25,7 @@ jobs: steps: - {attach_workspace: {at: .}} - {run: {name: 'Install and Test (check-tt-van-mx)', command: 'install-and-test-ext check-multi-task-tracker-extra check-vanilla check-multi-mx'}} + - {codecov/upload: {flags: 'test_10,tracker,vanilla,mx'}} test-10_check-iso-work-fol: docker: - {image: 'citusdata/exttester-10:latest'} @@ -29,6 +33,7 @@ jobs: steps: - {attach_workspace: {at: .}} - {run: {name: 'Install and Test (check-iso-work-fol)', command: 'install-and-test-ext check-isolation check-worker check-follower-cluster'}} + - {codecov/upload: {flags: 'test_10,isolation,worker,follower'}} test-10_check-failure: docker: - {image: 'citusdata/failtester-10:latest'} @@ -43,6 +48,7 @@ jobs: steps: - {attach_workspace: {at: .}} - {run: {name: 'Install and Test (check-multi)', command: 'install-and-test-ext check-multi'}} + - {codecov/upload: {flags: 'test_11,multi'}} test-11_check-tt-van-mx: docker: - {image: 'citusdata/exttester-11:latest'} @@ -50,6 +56,7 @@ jobs: steps: - {attach_workspace: {at: .}} - {run: {name: 'Install and Test (check-tt-van-mx)', command: 'install-and-test-ext check-multi-task-tracker-extra check-vanilla check-multi-mx'}} + - {codecov/upload: {flags: 'test_11,tracker,vanilla,mx'}} test-11_check-iso-work-fol: docker: - {image: 'citusdata/exttester-11:latest'} @@ -57,6 +64,7 @@ jobs: steps: - {attach_workspace: {at: .}} - {run: {name: 'Install and Test (check-iso-work-fol)', command: 'install-and-test-ext check-isolation check-worker check-follower-cluster'}} + - {codecov/upload: {flags: 'test_11,isolation,worker,follower'}} test-11_check-failure: docker: - {image: 'citusdata/failtester-11:latest'} From 3df2f51881707d4a9eb047c64e7da41ff7f52a42 Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:26:52 -0700 Subject: [PATCH 14/20] Turn on style-checking, fix lingering violations We'd been ignoring updating uncrustify for some time now because I'd thought these were misclassifications that would require an update in our rules to address. Turns out they're legit, so I'm checking them in. --- .circleci/config.yml | 7 ++++ .../distributed/utils/citus_copyfuncs.c | 4 +- src/backend/distributed/utils/resource_lock.c | 42 +++++++++---------- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 60274718a..0b81c3896 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,6 +10,12 @@ jobs: - checkout - {run: {name: 'Configure, Build, and Install', command: build-ext}} - {persist_to_workspace: {root: ., paths: [.]}} + check-style: + docker: + - {image: 'citusdata/stylechecker:latest'} + steps: + - checkout + - {run: {name: 'Check Style', command: citus_indent --check}} test-10_check-multi: docker: - {image: 'citusdata/exttester-10:latest'} @@ -77,6 +83,7 @@ workflows: build_and_test: jobs: - build + - check-style - {test-10_check-multi: {requires: [build]}} - {test-10_check-tt-van-mx: {requires: [build]}} diff --git a/src/backend/distributed/utils/citus_copyfuncs.c b/src/backend/distributed/utils/citus_copyfuncs.c index 9d02a2b5e..757317851 100644 --- a/src/backend/distributed/utils/citus_copyfuncs.c +++ b/src/backend/distributed/utils/citus_copyfuncs.c @@ -33,8 +33,8 @@ CitusSetTag(Node *node, int tag) #define DECLARE_FROM_AND_NEW_NODE(nodeTypeName) \ - nodeTypeName * newnode = (nodeTypeName *) \ - CitusSetTag((Node *) target_node, T_ ## nodeTypeName); \ + nodeTypeName *newnode = (nodeTypeName *) \ + CitusSetTag((Node *) target_node, T_ ## nodeTypeName); \ nodeTypeName *from = (nodeTypeName *) source_node /* Copy a simple scalar field (int, float, bool, enum, etc) */ diff --git a/src/backend/distributed/utils/resource_lock.c b/src/backend/distributed/utils/resource_lock.c index 36989d0f6..57a4d7ccc 100644 --- a/src/backend/distributed/utils/resource_lock.c +++ b/src/backend/distributed/utils/resource_lock.c @@ -107,7 +107,7 @@ lock_shard_metadata(PG_FUNCTION_ARGS) ereport(ERROR, (errmsg("no locks specified"))); } - /* we don't want random users to block writes */ + /* we don't want random users to block writes */ EnsureSuperUser(); shardIdCount = ArrayObjectCount(shardIdArrayObject); @@ -147,7 +147,7 @@ lock_shard_resources(PG_FUNCTION_ARGS) ereport(ERROR, (errmsg("no locks specified"))); } - /* we don't want random users to block writes */ + /* we don't want random users to block writes */ EnsureSuperUser(); shardIdCount = ArrayObjectCount(shardIdArrayObject); @@ -475,7 +475,7 @@ LockShardListMetadata(List *shardIntervalList, LOCKMODE lockMode) { ListCell *shardIntervalCell = NULL; - /* lock shards in order of shard id to prevent deadlock */ + /* lock shards in order of shard id to prevent deadlock */ shardIntervalList = SortList(shardIntervalList, CompareShardIntervalsById); foreach(shardIntervalCell, shardIntervalList) @@ -497,7 +497,7 @@ LockShardsInPlacementListMetadata(List *shardPlacementList, LOCKMODE lockMode) { ListCell *shardPlacementCell = NULL; - /* lock shards in order of shard id to prevent deadlock */ + /* lock shards in order of shard id to prevent deadlock */ shardPlacementList = SortList(shardPlacementList, CompareShardPlacementsByShardId); @@ -553,7 +553,7 @@ LockShardListResources(List *shardIntervalList, LOCKMODE lockMode) { ListCell *shardIntervalCell = NULL; - /* lock shards in order of shard id to prevent deadlock */ + /* lock shards in order of shard id to prevent deadlock */ shardIntervalList = SortList(shardIntervalList, CompareShardIntervalsById); foreach(shardIntervalCell, shardIntervalList) @@ -575,7 +575,7 @@ LockRelationShardResources(List *relationShardList, LOCKMODE lockMode) { ListCell *relationShardCell = NULL; - /* lock shards in a consistent order to prevent deadlock */ + /* lock shards in a consistent order to prevent deadlock */ relationShardList = SortList(relationShardList, CompareRelationShards); foreach(relationShardCell, relationShardList) @@ -641,11 +641,11 @@ LockPartitionsInRelationList(List *relationIdList, LOCKMODE lockmode) void LockPartitionRelations(Oid relationId, LOCKMODE lockMode) { - /* - * PartitionList function generates partition list in the same order - * as PostgreSQL. Therefore we do not need to sort it before acquiring - * locks. - */ + /* + * PartitionList function generates partition list in the same order + * as PostgreSQL. Therefore we do not need to sort it before acquiring + * locks. + */ List *partitionList = PartitionList(relationId); ListCell *partitionCell = NULL; @@ -678,7 +678,7 @@ LockModeTextToLockMode(const char *lockModeName) } } - /* we could not find the lock mode we are looking for */ + /* we could not find the lock mode we are looking for */ if (lockMode == -1) { ereport(ERROR, @@ -712,7 +712,7 @@ LockModeToLockModeText(LOCKMODE lockMode) } } - /* we could not find the lock mode we are looking for */ + /* we could not find the lock mode we are looking for */ if (lockModeText == NULL) { ereport(ERROR, @@ -747,17 +747,17 @@ lock_relation_if_exists(PG_FUNCTION_ARGS) LOCKMODE lockMode = NoLock; bool relationExists = false; - /* ensure that we're in a transaction block */ + /* ensure that we're in a transaction block */ RequireTransactionBlock(true, "lock_relation_if_exists"); - /* get the lock mode */ + /* get the lock mode */ lockMode = LockModeTextToLockMode(lockModeCString); - /* resolve relationId from passed in schema and relation name */ + /* resolve relationId from passed in schema and relation name */ relationNameList = textToQualifiedNameList(relationName); relation = makeRangeVarFromNameList(relationNameList); - /* lock the relation with the lock mode */ + /* lock the relation with the lock mode */ relationId = RangeVarGetRelidInternal(relation, lockMode, RVR_MISSING_OK, CitusRangeVarCallbackForLockTable, (void *) &lockMode); @@ -783,18 +783,18 @@ CitusRangeVarCallbackForLockTable(const RangeVar *rangeVar, Oid relationId, if (!OidIsValid(relationId)) { - /* table doesn't exist, so no permissions check */ + /* table doesn't exist, so no permissions check */ return; } - /* we only allow tables and views to be locked */ + /* we only allow tables and views to be locked */ if (!RegularTable(relationId)) { ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table", rangeVar->relname))); } - /* check permissions */ + /* check permissions */ aclResult = CitusLockTableAclCheck(relationId, lockmode, GetUserId()); if (aclResult != ACLCHECK_OK) { @@ -822,7 +822,7 @@ CitusLockTableAclCheck(Oid relationId, LOCKMODE lockmode, Oid userId) AclResult aclResult; AclMode aclMask; - /* verify adequate privilege */ + /* verify adequate privilege */ if (lockmode == AccessShareLock) { aclMask = ACL_SELECT; From 6c3f7b665fe036d1260ec9a287653d5521e5cf58 Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:54:16 -0700 Subject: [PATCH 15/20] Squelch indentation errors (uncrustify is old in Travis) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ddb40e124..efda6bcbf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ install: apt-get download "postgresql-${PGVERSION}-topn=2.2.0" sudo dpkg --force-confold --force-confdef --force-all -i *topn*.deb fi -before_script: citus_indent --quiet --check +before_script: citus_indent --quiet --check || echo 'Ignoring indent failures' script: CFLAGS=-Werror pg_travis_multi_test check after_success: - sync_to_enterprise From dceaae3b954bfce21de2ae9e1045045bef075f79 Mon Sep 17 00:00:00 2001 From: Jason Petersen Date: Tue, 26 Feb 2019 22:55:15 -0700 Subject: [PATCH 16/20] Remove codecov push from Travis build --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index efda6bcbf..f70f8207b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,4 +53,3 @@ before_script: citus_indent --quiet --check || echo 'Ignoring indent failures' script: CFLAGS=-Werror pg_travis_multi_test check after_success: - sync_to_enterprise - - bash <(curl -s https://codecov.io/bash) From 26f569abd8a50fad3364cbf0ebcbe2dd8aa706ff Mon Sep 17 00:00:00 2001 From: Onder Kalaci Date: Wed, 27 Feb 2019 18:35:44 +0300 Subject: [PATCH 17/20] Make sure to clear PGresult on few places This leads to a memory leak otherwise. --- src/backend/distributed/master/master_metadata_utility.c | 1 + src/backend/distributed/test/run_from_same_connection.c | 6 +++++- src/backend/distributed/worker/worker_data_fetch_protocol.c | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backend/distributed/master/master_metadata_utility.c b/src/backend/distributed/master/master_metadata_utility.c index f9223cf35..9e1296831 100644 --- a/src/backend/distributed/master/master_metadata_utility.c +++ b/src/backend/distributed/master/master_metadata_utility.c @@ -237,6 +237,7 @@ DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId, char *sizeQ tableSizeString = tableSizeStringInfo->data; tableSize = atol(tableSizeString); + PQclear(result); ClearResults(connection, raiseErrors); return tableSize; diff --git a/src/backend/distributed/test/run_from_same_connection.c b/src/backend/distributed/test/run_from_same_connection.c index 17ce54270..508fc133a 100644 --- a/src/backend/distributed/test/run_from_same_connection.c +++ b/src/backend/distributed/test/run_from_same_connection.c @@ -196,6 +196,7 @@ GetRemoteProcessId(MultiConnection *connection) StringInfo queryStringInfo = makeStringInfo(); PGresult *result = NULL; int64 rowCount = 0; + int64 resultValue = 0; appendStringInfo(queryStringInfo, GET_PROCESS_ID); @@ -208,7 +209,10 @@ GetRemoteProcessId(MultiConnection *connection) PG_RETURN_VOID(); } + resultValue = ParseIntField(result, 0, 0); + + PQclear(result); ClearResults(connection, false); - return ParseIntField(result, 0, 0); + return resultValue; } diff --git a/src/backend/distributed/worker/worker_data_fetch_protocol.c b/src/backend/distributed/worker/worker_data_fetch_protocol.c index bd0b38447..82c50a27c 100644 --- a/src/backend/distributed/worker/worker_data_fetch_protocol.c +++ b/src/backend/distributed/worker/worker_data_fetch_protocol.c @@ -563,6 +563,7 @@ TableDDLCommandList(const char *nodeName, uint32 nodePort, const char *tableName ExecuteOptionalRemoteCommand(connection, queryString->data, &result); ddlCommandList = ReadFirstColumnAsText(result); + PQclear(result); ForgetResults(connection); CloseConnection(connection); From faf50849d70ccf62c15ad851f83f6c48938b25c2 Mon Sep 17 00:00:00 2001 From: velioglu Date: Mon, 21 Jan 2019 16:11:58 +0300 Subject: [PATCH 18/20] Enhance pushdown planning logic to handle full outer joins with using clause Since flattening query may flatten outer joins' columns into coalesce expr that is in the USING part, and that was not expected before this commit, these queries were erroring out. It is fixed by this commit with considering coalesce expression as well. --- .../planner/query_pushdown_planning.c | 158 ++++++++--- src/test/regress/expected/full_join.out | 250 ++++++++++++++++++ src/test/regress/multi_schedule | 2 +- src/test/regress/sql/full_join.sql | 115 ++++++++ 4 files changed, 482 insertions(+), 43 deletions(-) create mode 100644 src/test/regress/expected/full_join.out create mode 100644 src/test/regress/sql/full_join.sql diff --git a/src/backend/distributed/planner/query_pushdown_planning.c b/src/backend/distributed/planner/query_pushdown_planning.c index 4b439a528..219600906 100644 --- a/src/backend/distributed/planner/query_pushdown_planning.c +++ b/src/backend/distributed/planner/query_pushdown_planning.c @@ -79,9 +79,12 @@ static bool IsRecurringRTE(RangeTblEntry *rangeTableEntry, static bool IsRecurringRangeTable(List *rangeTable, RecurringTuplesType *recurType); static bool HasRecurringTuples(Node *node, RecurringTuplesType *recurType); static MultiNode * SubqueryPushdownMultiNodeTree(Query *queryTree); -static void FlattenJoinVars(List *columnList, Query *queryTree); +static List * FlattenJoinVars(List *columnList, Query *queryTree); static void UpdateVarMappingsForExtendedOpNode(List *columnList, + List *flattenedColumnList, List *subqueryTargetEntryList); +static void UpdateColumnToMatchingTargetEntry(Var *column, Node *flattenedExpr, + List *targetEntryList); static MultiTable * MultiSubqueryPushdownTable(Query *subquery); static List * CreateSubqueryTargetEntryList(List *columnList); static bool RelationInfoContainsOnlyRecurringTuples(PlannerInfo *plannerInfo, @@ -1413,6 +1416,7 @@ SubqueryPushdownMultiNodeTree(Query *queryTree) { List *targetEntryList = queryTree->targetList; List *columnList = NIL; + List *flattenedExprList = NIL; List *targetColumnList = NIL; MultiCollect *subqueryCollectNode = CitusMakeNode(MultiCollect); MultiTable *subqueryNode = NULL; @@ -1472,25 +1476,26 @@ SubqueryPushdownMultiNodeTree(Query *queryTree) */ /* - * uniqueColumnList contains all columns returned by subquery. Subquery target + * columnList contains all columns returned by subquery. Subquery target * entry list, subquery range table entry's column name list are derived from - * uniqueColumnList. Columns mentioned in multiProject node and multiExtendedOp - * node are indexed with their respective position in uniqueColumnList. + * columnList. Columns mentioned in multiProject node and multiExtendedOp + * node are indexed with their respective position in columnList. */ targetColumnList = pull_var_clause_default((Node *) targetEntryList); havingClauseColumnList = pull_var_clause_default(queryTree->havingQual); columnList = list_concat(targetColumnList, havingClauseColumnList); - FlattenJoinVars(columnList, queryTree); + flattenedExprList = FlattenJoinVars(columnList, queryTree); /* create a target entry for each unique column */ - subqueryTargetEntryList = CreateSubqueryTargetEntryList(columnList); + subqueryTargetEntryList = CreateSubqueryTargetEntryList(flattenedExprList); /* * Update varno/varattno fields of columns in columnList to * point to corresponding target entry in subquery target entry list. */ - UpdateVarMappingsForExtendedOpNode(columnList, subqueryTargetEntryList); + UpdateVarMappingsForExtendedOpNode(columnList, flattenedExprList, + subqueryTargetEntryList); /* new query only has target entries, join tree, and rtable*/ pushedDownQuery = makeNode(Query); @@ -1553,7 +1558,8 @@ SubqueryPushdownMultiNodeTree(Query *queryTree) /* * FlattenJoinVars iterates over provided columnList to identify * Var's that are referenced from join RTE, and reverts back to their - * original RTEs. + * original RTEs. Then, returns a new list with reverted types. Note that, + * length of the original list and created list must be equal. * * This is required because Postgres allows columns to be referenced using * a join alias. Therefore the same column from a table could be referenced @@ -1568,12 +1574,16 @@ SubqueryPushdownMultiNodeTree(Query *queryTree) * Only exception is that, if a join is given an alias name, we do not want to * flatten those var's. If we do, deparsing fails since it expects to see a join * alias, and cannot access the RTE in the join tree by their names. + * + * Also note that in case of full outer joins, a column could be flattened to a + * coalesce expression if the column appears in the USING clause. */ -static void +static List * FlattenJoinVars(List *columnList, Query *queryTree) { ListCell *columnCell = NULL; List *rteList = queryTree->rtable; + List *flattenedExprList = NIL; foreach(columnCell, columnList) { @@ -1595,7 +1605,7 @@ FlattenJoinVars(List *columnList, Query *queryTree) columnRte = rt_fetch(column->varno, rteList); if (columnRte->rtekind == RTE_JOIN && columnRte->alias == NULL) { - Var *normalizedVar = NULL; + Node *normalizedNode = NULL; if (root == NULL) { @@ -1605,15 +1615,18 @@ FlattenJoinVars(List *columnList, Query *queryTree) root->hasJoinRTEs = true; } - normalizedVar = (Var *) flatten_join_alias_vars(root, (Node *) column); - - /* - * We need to copy values over existing one to make sure it is updated on - * respective places. - */ - memcpy(column, normalizedVar, sizeof(Var)); + normalizedNode = strip_implicit_coercions(flatten_join_alias_vars(root, + (Node *) + column)); + flattenedExprList = lappend(flattenedExprList, copyObject(normalizedNode)); + } + else + { + flattenedExprList = lappend(flattenedExprList, copyObject(column)); } } + + return flattenedExprList; } @@ -1622,28 +1635,28 @@ FlattenJoinVars(List *columnList, Query *queryTree) * in the column list and returns the target entry list. */ static List * -CreateSubqueryTargetEntryList(List *columnList) +CreateSubqueryTargetEntryList(List *exprList) { AttrNumber resNo = 1; - ListCell *columnCell = NULL; - List *uniqueColumnList = NIL; + ListCell *exprCell = NULL; + List *uniqueExprList = NIL; List *subqueryTargetEntryList = NIL; - foreach(columnCell, columnList) + foreach(exprCell, exprList) { - Var *column = (Var *) lfirst(columnCell); - uniqueColumnList = list_append_unique(uniqueColumnList, copyObject(column)); + Node *expr = (Node *) lfirst(exprCell); + uniqueExprList = list_append_unique(uniqueExprList, expr); } - foreach(columnCell, uniqueColumnList) + foreach(exprCell, uniqueExprList) { - Var *column = (Var *) lfirst(columnCell); + Node *expr = (Node *) lfirst(exprCell); TargetEntry *newTargetEntry = makeNode(TargetEntry); - StringInfo columnNameString = makeStringInfo(); + StringInfo exprNameString = makeStringInfo(); - newTargetEntry->expr = (Expr *) copyObject(column); - appendStringInfo(columnNameString, WORKER_COLUMN_FORMAT, resNo); - newTargetEntry->resname = columnNameString->data; + newTargetEntry->expr = (Expr *) copyObject(expr); + appendStringInfo(exprNameString, WORKER_COLUMN_FORMAT, resNo); + newTargetEntry->resname = exprNameString->data; newTargetEntry->resjunk = false; newTargetEntry->resno = resNo; @@ -1661,28 +1674,89 @@ CreateSubqueryTargetEntryList(List *columnList) * list. */ static void -UpdateVarMappingsForExtendedOpNode(List *columnList, List *subqueryTargetEntryList) +UpdateVarMappingsForExtendedOpNode(List *columnList, List *flattenedExprList, + List *subqueryTargetEntryList) { ListCell *columnCell = NULL; - foreach(columnCell, columnList) + ListCell *flattenedExprCell = NULL; + + Assert(list_length(columnList) == list_length(flattenedExprList)); + + forboth(columnCell, columnList, flattenedExprCell, flattenedExprList) { Var *columnOnTheExtendedNode = (Var *) lfirst(columnCell); - ListCell *targetEntryCell = NULL; - foreach(targetEntryCell, subqueryTargetEntryList) - { - TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell); - Var *targetColumn = NULL; + Node *flattenedExpr = (Node *) lfirst(flattenedExprCell); - Assert(IsA(targetEntry->expr, Var)); - targetColumn = (Var *) targetEntry->expr; - if (columnOnTheExtendedNode->varno == targetColumn->varno && - columnOnTheExtendedNode->varattno == targetColumn->varattno) + /* + * As an optimization, subqueryTargetEntryList only consists of + * distinct elements. In other words, any duplicate entries in the + * target list consolidated into a single element to prevent pulling + * unnecessary data from the worker nodes (e.g. SELECT a,a,a,b,b,b FROM x; + * is turned into SELECT a,b FROM x_102008). + * + * Thus, at this point we should iterate on the subqueryTargetEntryList + * and ensure that the column on the extended op node points to the + * correct target entry. + */ + UpdateColumnToMatchingTargetEntry(columnOnTheExtendedNode, flattenedExpr, + subqueryTargetEntryList); + } +} + + +/* + * UpdateColumnToMatchingTargetEntry sets the variable of given column entry to + * the matching entry of the targetEntryList. Since data type of the column can + * be different from the types of the elements of targetEntryList, we use flattenedExpr. + */ +static void +UpdateColumnToMatchingTargetEntry(Var *column, Node *flattenedExpr, List *targetEntryList) +{ + ListCell *targetEntryCell = NULL; + + foreach(targetEntryCell, targetEntryList) + { + TargetEntry *targetEntry = (TargetEntry *) lfirst(targetEntryCell); + + if (IsA(targetEntry->expr, Var)) + { + Var *targetEntryVar = (Var *) targetEntry->expr; + + if (IsA(flattenedExpr, Var) && equal(flattenedExpr, targetEntryVar)) { - columnOnTheExtendedNode->varno = 1; - columnOnTheExtendedNode->varattno = targetEntry->resno; + column->varno = 1; + column->varattno = targetEntry->resno; break; } } + else if (IsA(targetEntry->expr, CoalesceExpr)) + { + /* + * flatten_join_alias_vars() flattens full oter joins' columns that is + * in the USING part into COALESCE(left_col, right_col) + */ + CoalesceExpr *targetCoalesceExpr = (CoalesceExpr *) targetEntry->expr; + + if (IsA(flattenedExpr, CoalesceExpr) && equal(flattenedExpr, + targetCoalesceExpr)) + { + Oid expressionType = exprType(flattenedExpr); + int32 expressionTypmod = exprTypmod(flattenedExpr); + Oid expressionCollation = exprCollation(flattenedExpr); + + column->varno = 1; + column->varattno = targetEntry->resno; + column->vartype = expressionType; + column->vartypmod = expressionTypmod; + column->varcollid = expressionCollation; + break; + } + } + else + { + elog(ERROR, "unrecognized node type on the target list: %d", + nodeTag(targetEntry->expr)); + } } } diff --git a/src/test/regress/expected/full_join.out b/src/test/regress/expected/full_join.out new file mode 100644 index 000000000..522376d78 --- /dev/null +++ b/src/test/regress/expected/full_join.out @@ -0,0 +1,250 @@ +-- +-- Full join with subquery pushdown support +-- +SET citus.next_shard_id TO 9000000; +CREATE SCHEMA full_join; +SET search_path TO full_join, public; +CREATE TABLE test_table_1(id int, val1 int); +CREATE TABLE test_table_2(id bigint, val1 int); +CREATE TABLE test_table_3(id int, val1 bigint); +SELECT create_distributed_table('test_table_1', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('test_table_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES(1,1),(2,2),(3,3); +INSERT INTO test_table_2 VALUES(2,2),(3,3),(4,4); +INSERT INTO test_table_3 VALUES(1,1),(3,3),(4,5); +-- Simple full outer join +SELECT id FROM test_table_1 FULL JOIN test_table_3 using(id) ORDER BY 1; + id +---- + 1 + 2 + 3 + 4 +(4 rows) + +-- Get all columns as the result of the full join +SELECT * FROM test_table_1 FULL JOIN test_table_3 using(id) ORDER BY 1; + id | val1 | val1 +----+------+------ + 1 | 1 | 1 + 2 | 2 | + 3 | 3 | 3 + 4 | | 5 +(4 rows) + +-- Join subqueries using single column +SELECT * FROM + (SELECT test_table_1.id FROM test_table_1 FULL JOIN test_table_3 using(id)) as j1 + FULL JOIN + (SELECT test_table_1.id FROM test_table_1 FULL JOIN test_table_3 using(id)) as j2 + USING(id) + ORDER BY 1; + id +---- + 1 + 2 + 3 + + +(5 rows) + +-- Join subqueries using multiple columns +SELECT * FROM + (SELECT test_table_1.id, test_table_1.val1 FROM test_table_1 FULL JOIN test_table_3 using(id)) as j1 + FULL JOIN + (SELECT test_table_1.id, test_table_1.val1 FROM test_table_1 FULL JOIN test_table_3 using(id)) as j2 + USING(id, val1) + ORDER BY 1; + id | val1 +----+------ + 1 | 1 + 2 | 2 + 3 | 3 + | + | +(5 rows) + +-- Full join using multiple columns +SELECT * FROM test_table_1 FULL JOIN test_table_3 USING(id, val1) ORDER BY 1; + id | val1 +----+------ + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 5 +(4 rows) + +-- Full join with complicated target lists +SELECT count(DISTINCT id), (avg(test_table_1.val1) + id * id)::integer as avg_value, id::numeric IS NOT NULL as not_null +FROM test_table_1 FULL JOIN test_table_3 using(id) +WHERE id::bigint < 55 +GROUP BY id +ORDER BY 2 +ASC LIMIT 3; + count | avg_value | not_null +-------+-----------+---------- + 1 | 2 | t + 1 | 6 | t + 1 | 12 | t +(3 rows) + +SELECT max(val1) +FROM test_table_1 FULL JOIN test_table_3 USING(id, val1) +GROUP BY test_table_1.id +ORDER BY 1; + max +----- + 1 + 2 + 3 + 5 +(4 rows) + +-- Test the left join as well +SELECT max(val1) +FROM test_table_1 LEFT JOIN test_table_3 USING(id, val1) +GROUP BY test_table_1.id +ORDER BY 1; + max +----- + 1 + 2 + 3 +(3 rows) + +-- Full outer join with different distribution column types, should error out +SELECT * FROM test_table_1 full join test_table_2 using(id); +ERROR: cannot push down this subquery +DETAIL: Shards of relations in subquery need to have 1-to-1 shard partitioning +-- Test when the non-distributed column has the value of NULL +INSERT INTO test_table_1 VALUES(7, NULL); +INSERT INTO test_table_2 VALUES(7, NULL); +INSERT INTO test_table_3 VALUES(7, NULL); +-- Get all columns as the result of the full join +SELECT * FROM test_table_1 FULL JOIN test_table_3 using(id) ORDER BY 1; + id | val1 | val1 +----+------+------ + 1 | 1 | 1 + 2 | 2 | + 3 | 3 | 3 + 4 | | 5 + 7 | | +(5 rows) + +-- Get the same result (with multiple id) +SELECT * FROM test_table_1 FULL JOIN test_table_3 ON (test_table_1.id = test_table_3.id) ORDER BY 1; + id | val1 | id | val1 +----+------+----+------ + 1 | 1 | 1 | 1 + 2 | 2 | | + 3 | 3 | 3 | 3 + 7 | | 7 | + | | 4 | 5 +(5 rows) + +-- Full join using multiple columns +SELECT * FROM test_table_1 FULL JOIN test_table_3 USING(id, val1) ORDER BY 1; + id | val1 +----+------ + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 5 + 7 | + 7 | +(6 rows) + +-- In order to make the same test with different data types use text-varchar pair +-- instead of using int-bigint pair. +DROP TABLE test_table_1; +DROP TABLE test_table_2; +DROP TABLE test_table_3; +CREATE TABLE test_table_1(id int, val1 text); +CREATE TABLE test_table_2(id int, val1 varchar(30)); +SELECT create_distributed_table('test_table_1', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES(1,'val_1'),(2,'val_2'),(3,'val_3'), (4, NULL); +INSERT INTO test_table_2 VALUES(2,'val_2'),(3,'val_3'),(4,'val_4'), (5, NULL); +-- Simple full outer join +SELECT id FROM test_table_1 FULL JOIN test_table_2 using(id) ORDER BY 1; + id +---- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +-- Get all columns as the result of the full join +SELECT * FROM test_table_1 FULL JOIN test_table_2 using(id) ORDER BY 1; + id | val1 | val1 +----+-------+------- + 1 | val_1 | + 2 | val_2 | val_2 + 3 | val_3 | val_3 + 4 | | val_4 + 5 | | +(5 rows) + +-- Join subqueries using multiple columns +SELECT * FROM + (SELECT test_table_1.id, test_table_1.val1 FROM test_table_1 FULL JOIN test_table_2 using(id)) as j1 + FULL JOIN + (SELECT test_table_2.id, test_table_2.val1 FROM test_table_1 FULL JOIN test_table_2 using(id)) as j2 + USING(id, val1) + ORDER BY 1,2; + id | val1 +----+------- + 1 | val_1 + 2 | val_2 + 3 | val_3 + 4 | val_4 + 4 | + 5 | + | + | +(8 rows) + +-- Full join using multiple columns +SELECT * FROM test_table_1 FULL JOIN test_table_2 USING(id, val1) ORDER BY 1,2; + id | val1 +----+------- + 1 | val_1 + 2 | val_2 + 3 | val_3 + 4 | val_4 + 4 | + 5 | +(6 rows) + +DROP SCHEMA full_join CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table test_table_1 +drop cascades to table test_table_2 diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 4425b9e96..ddf2ccc92 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -65,7 +65,7 @@ test: multi_explain test: multi_basic_queries multi_complex_expressions multi_subquery multi_subquery_complex_queries multi_subquery_behavioral_analytics test: multi_subquery_complex_reference_clause multi_subquery_window_functions multi_view multi_sql_function multi_prepare_sql test: sql_procedure multi_function_in_join -test: multi_subquery_in_where_reference_clause +test: multi_subquery_in_where_reference_clause full_join test: multi_subquery_union multi_subquery_in_where_clause multi_subquery_misc test: multi_agg_distinct multi_agg_approximate_distinct multi_limit_clause_approximate multi_outer_join_reference multi_single_relation_subquery multi_prepare_plsql test: multi_reference_table multi_select_for_update relation_access_tracking diff --git a/src/test/regress/sql/full_join.sql b/src/test/regress/sql/full_join.sql new file mode 100644 index 000000000..19d06f19d --- /dev/null +++ b/src/test/regress/sql/full_join.sql @@ -0,0 +1,115 @@ +-- +-- Full join with subquery pushdown support +-- + +SET citus.next_shard_id TO 9000000; + +CREATE SCHEMA full_join; +SET search_path TO full_join, public; + +CREATE TABLE test_table_1(id int, val1 int); +CREATE TABLE test_table_2(id bigint, val1 int); +CREATE TABLE test_table_3(id int, val1 bigint); + +SELECT create_distributed_table('test_table_1', 'id'); +SELECT create_distributed_table('test_table_2', 'id'); +SELECT create_distributed_table('test_table_3', 'id'); + +INSERT INTO test_table_1 VALUES(1,1),(2,2),(3,3); +INSERT INTO test_table_2 VALUES(2,2),(3,3),(4,4); +INSERT INTO test_table_3 VALUES(1,1),(3,3),(4,5); + +-- Simple full outer join +SELECT id FROM test_table_1 FULL JOIN test_table_3 using(id) ORDER BY 1; + +-- Get all columns as the result of the full join +SELECT * FROM test_table_1 FULL JOIN test_table_3 using(id) ORDER BY 1; + +-- Join subqueries using single column +SELECT * FROM + (SELECT test_table_1.id FROM test_table_1 FULL JOIN test_table_3 using(id)) as j1 + FULL JOIN + (SELECT test_table_1.id FROM test_table_1 FULL JOIN test_table_3 using(id)) as j2 + USING(id) + ORDER BY 1; + +-- Join subqueries using multiple columns +SELECT * FROM + (SELECT test_table_1.id, test_table_1.val1 FROM test_table_1 FULL JOIN test_table_3 using(id)) as j1 + FULL JOIN + (SELECT test_table_1.id, test_table_1.val1 FROM test_table_1 FULL JOIN test_table_3 using(id)) as j2 + USING(id, val1) + ORDER BY 1; + +-- Full join using multiple columns +SELECT * FROM test_table_1 FULL JOIN test_table_3 USING(id, val1) ORDER BY 1; + +-- Full join with complicated target lists +SELECT count(DISTINCT id), (avg(test_table_1.val1) + id * id)::integer as avg_value, id::numeric IS NOT NULL as not_null +FROM test_table_1 FULL JOIN test_table_3 using(id) +WHERE id::bigint < 55 +GROUP BY id +ORDER BY 2 +ASC LIMIT 3; + +SELECT max(val1) +FROM test_table_1 FULL JOIN test_table_3 USING(id, val1) +GROUP BY test_table_1.id +ORDER BY 1; + +-- Test the left join as well +SELECT max(val1) +FROM test_table_1 LEFT JOIN test_table_3 USING(id, val1) +GROUP BY test_table_1.id +ORDER BY 1; + +-- Full outer join with different distribution column types, should error out +SELECT * FROM test_table_1 full join test_table_2 using(id); + +-- Test when the non-distributed column has the value of NULL +INSERT INTO test_table_1 VALUES(7, NULL); +INSERT INTO test_table_2 VALUES(7, NULL); +INSERT INTO test_table_3 VALUES(7, NULL); + +-- Get all columns as the result of the full join +SELECT * FROM test_table_1 FULL JOIN test_table_3 using(id) ORDER BY 1; + +-- Get the same result (with multiple id) +SELECT * FROM test_table_1 FULL JOIN test_table_3 ON (test_table_1.id = test_table_3.id) ORDER BY 1; + +-- Full join using multiple columns +SELECT * FROM test_table_1 FULL JOIN test_table_3 USING(id, val1) ORDER BY 1; + +-- In order to make the same test with different data types use text-varchar pair +-- instead of using int-bigint pair. +DROP TABLE test_table_1; +DROP TABLE test_table_2; +DROP TABLE test_table_3; + +CREATE TABLE test_table_1(id int, val1 text); +CREATE TABLE test_table_2(id int, val1 varchar(30)); + +SELECT create_distributed_table('test_table_1', 'id'); +SELECT create_distributed_table('test_table_2', 'id'); + +INSERT INTO test_table_1 VALUES(1,'val_1'),(2,'val_2'),(3,'val_3'), (4, NULL); +INSERT INTO test_table_2 VALUES(2,'val_2'),(3,'val_3'),(4,'val_4'), (5, NULL); + +-- Simple full outer join +SELECT id FROM test_table_1 FULL JOIN test_table_2 using(id) ORDER BY 1; + +-- Get all columns as the result of the full join +SELECT * FROM test_table_1 FULL JOIN test_table_2 using(id) ORDER BY 1; + +-- Join subqueries using multiple columns +SELECT * FROM + (SELECT test_table_1.id, test_table_1.val1 FROM test_table_1 FULL JOIN test_table_2 using(id)) as j1 + FULL JOIN + (SELECT test_table_2.id, test_table_2.val1 FROM test_table_1 FULL JOIN test_table_2 using(id)) as j2 + USING(id, val1) + ORDER BY 1,2; + +-- Full join using multiple columns +SELECT * FROM test_table_1 FULL JOIN test_table_2 USING(id, val1) ORDER BY 1,2; + +DROP SCHEMA full_join CASCADE; From f4d3b94e22e5bbad93ab0bbb80ae5b58534ce888 Mon Sep 17 00:00:00 2001 From: Hadi Moshayedi Date: Tue, 5 Mar 2019 12:06:44 -0800 Subject: [PATCH 19/20] Fix some of the casts for groupId (#2609) A small change which partially addresses #2608. --- .../executor/multi_router_executor.c | 8 +++---- .../distributed/master/master_create_shards.c | 2 +- .../master/master_metadata_utility.c | 12 +++++----- .../master/master_stage_protocol.c | 2 +- .../distributed/metadata/metadata_sync.c | 6 ++--- .../transaction/remote_transaction.c | 2 +- .../transaction/transaction_recovery.c | 6 ++--- .../distributed/utils/citus_outfuncs.c | 4 ++-- .../distributed/utils/citus_readfuncs.c | 4 ++-- .../distributed/utils/metadata_cache.c | 24 +++++++++---------- src/backend/distributed/utils/node_metadata.c | 16 ++++++------- .../distributed/utils/reference_table_utils.c | 4 ++-- .../distributed/master_metadata_utility.h | 10 ++++---- src/include/distributed/metadata_cache.h | 4 ++-- src/include/distributed/metadata_sync.h | 2 +- .../distributed/multi_router_executor.h | 4 ++-- .../distributed/reference_table_utils.h | 2 +- src/include/distributed/remote_transaction.h | 2 +- .../distributed/transaction_recovery.h | 2 +- src/include/distributed/worker_manager.h | 4 ++-- 20 files changed, 60 insertions(+), 60 deletions(-) diff --git a/src/backend/distributed/executor/multi_router_executor.c b/src/backend/distributed/executor/multi_router_executor.c index 588dda350..9118e1f8c 100644 --- a/src/backend/distributed/executor/multi_router_executor.c +++ b/src/backend/distributed/executor/multi_router_executor.c @@ -92,7 +92,7 @@ static int64 ExecuteSingleModifyTask(CitusScanState *scanState, Task *task, CmdT operation, bool alwaysThrowErrorOnFailure, bool expectResults); static void ExecuteSingleSelectTask(CitusScanState *scanState, Task *task); -static List * BuildPlacementAccessList(uint32 groupId, List *relationShardList, +static List * BuildPlacementAccessList(int32 groupId, List *relationShardList, ShardPlacementAccessType accessType); static List * GetModifyConnections(Task *task, bool markCritical); static int64 ExecuteModifyTasks(List *taskList, bool expectResults, @@ -890,7 +890,7 @@ ExecuteSingleSelectTask(CitusScanState *scanState, Task *task) * (e.g. in case of a broadcast join) then the shard is skipped. */ List * -BuildPlacementSelectList(uint32 groupId, List *relationShardList) +BuildPlacementSelectList(int32 groupId, List *relationShardList) { return BuildPlacementAccessList(groupId, relationShardList, PLACEMENT_ACCESS_SELECT); } @@ -900,7 +900,7 @@ BuildPlacementSelectList(uint32 groupId, List *relationShardList) * BuildPlacementDDLList is a warpper around BuildPlacementAccessList() for DDL access. */ List * -BuildPlacementDDLList(uint32 groupId, List *relationShardList) +BuildPlacementDDLList(int32 groupId, List *relationShardList) { return BuildPlacementAccessList(groupId, relationShardList, PLACEMENT_ACCESS_DDL); } @@ -911,7 +911,7 @@ BuildPlacementDDLList(uint32 groupId, List *relationShardList) * relationShardList and the access type. */ static List * -BuildPlacementAccessList(uint32 groupId, List *relationShardList, +BuildPlacementAccessList(int32 groupId, List *relationShardList, ShardPlacementAccessType accessType) { ListCell *relationShardCell = NULL; diff --git a/src/backend/distributed/master/master_create_shards.c b/src/backend/distributed/master/master_create_shards.c index 78a4a00df..4e3a25305 100644 --- a/src/backend/distributed/master/master_create_shards.c +++ b/src/backend/distributed/master/master_create_shards.c @@ -316,7 +316,7 @@ CreateColocatedShards(Oid targetRelationId, Oid sourceRelationId, bool { ShardPlacement *sourcePlacement = (ShardPlacement *) lfirst(sourceShardPlacementCell); - uint32 groupId = sourcePlacement->groupId; + int32 groupId = sourcePlacement->groupId; const RelayFileState shardState = FILE_FINALIZED; const uint64 shardSize = 0; uint64 shardPlacementId = 0; diff --git a/src/backend/distributed/master/master_metadata_utility.c b/src/backend/distributed/master/master_metadata_utility.c index 9e1296831..40ff5c007 100644 --- a/src/backend/distributed/master/master_metadata_utility.c +++ b/src/backend/distributed/master/master_metadata_utility.c @@ -250,7 +250,7 @@ DistributedTableSizeOnWorker(WorkerNode *workerNode, Oid relationId, char *sizeQ * on the group. */ List * -GroupShardPlacementsForTableOnGroup(Oid relationId, uint32 groupId) +GroupShardPlacementsForTableOnGroup(Oid relationId, int32 groupId) { DistTableCacheEntry *distTableCacheEntry = DistributedTableCacheEntry(relationId); List *resultList = NIL; @@ -634,7 +634,7 @@ ShardLength(uint64 shardId) * NodeGroupHasShardPlacements returns whether any active shards are placed on the group */ bool -NodeGroupHasShardPlacements(uint32 groupId, bool onlyConsiderActivePlacements) +NodeGroupHasShardPlacements(int32 groupId, bool onlyConsiderActivePlacements) { const int scanKeyCount = (onlyConsiderActivePlacements ? 2 : 1); const bool indexOK = false; @@ -649,7 +649,7 @@ NodeGroupHasShardPlacements(uint32 groupId, bool onlyConsiderActivePlacements) AccessShareLock); ScanKeyInit(&scanKey[0], Anum_pg_dist_placement_groupid, - BTEqualStrategyNumber, F_INT4EQ, UInt32GetDatum(groupId)); + BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(groupId)); if (onlyConsiderActivePlacements) { ScanKeyInit(&scanKey[1], Anum_pg_dist_placement_shardstate, @@ -852,7 +852,7 @@ TupleToGroupShardPlacement(TupleDesc tupleDescriptor, HeapTuple heapTuple) datumArray[Anum_pg_dist_placement_shardlength - 1]); shardPlacement->shardState = DatumGetUInt32( datumArray[Anum_pg_dist_placement_shardstate - 1]); - shardPlacement->groupId = DatumGetUInt32( + shardPlacement->groupId = DatumGetInt32( datumArray[Anum_pg_dist_placement_groupid - 1]); return shardPlacement; @@ -922,7 +922,7 @@ InsertShardRow(Oid relationId, uint64 shardId, char storageType, uint64 InsertShardPlacementRow(uint64 shardId, uint64 placementId, char shardState, uint64 shardLength, - uint32 groupId) + int32 groupId) { Relation pgDistPlacement = NULL; TupleDesc tupleDescriptor = NULL; @@ -942,7 +942,7 @@ InsertShardPlacementRow(uint64 shardId, uint64 placementId, values[Anum_pg_dist_placement_shardid - 1] = Int64GetDatum(shardId); values[Anum_pg_dist_placement_shardstate - 1] = CharGetDatum(shardState); values[Anum_pg_dist_placement_shardlength - 1] = Int64GetDatum(shardLength); - values[Anum_pg_dist_placement_groupid - 1] = Int64GetDatum(groupId); + values[Anum_pg_dist_placement_groupid - 1] = Int32GetDatum(groupId); /* open shard placement relation and insert new tuple */ pgDistPlacement = heap_open(DistPlacementRelationId(), RowExclusiveLock); diff --git a/src/backend/distributed/master/master_stage_protocol.c b/src/backend/distributed/master/master_stage_protocol.c index fa06c4515..95a69a62a 100644 --- a/src/backend/distributed/master/master_stage_protocol.c +++ b/src/backend/distributed/master/master_stage_protocol.c @@ -766,7 +766,7 @@ UpdateShardStatistics(int64 shardId) { ShardPlacement *placement = (ShardPlacement *) lfirst(shardPlacementCell); uint64 placementId = placement->placementId; - uint32 groupId = placement->groupId; + int32 groupId = placement->groupId; DeleteShardPlacementRow(placementId); InsertShardPlacementRow(shardId, placementId, FILE_FINALIZED, shardSize, diff --git a/src/backend/distributed/metadata/metadata_sync.c b/src/backend/distributed/metadata/metadata_sync.c index 418440f09..82d0b8ad0 100644 --- a/src/backend/distributed/metadata/metadata_sync.c +++ b/src/backend/distributed/metadata/metadata_sync.c @@ -51,7 +51,7 @@ #include "utils/tqual.h" -static char * LocalGroupIdUpdateCommand(uint32 groupId); +static char * LocalGroupIdUpdateCommand(int32 groupId); static void MarkNodeHasMetadata(char *nodeName, int32 nodePort, bool hasMetadata); static List * SequenceDDLCommandsForTable(Oid relationId); static void EnsureSupportedSequenceColumnType(Oid sequenceOid); @@ -824,7 +824,7 @@ ColocationIdUpdateCommand(Oid relationId, uint32 colocationId) */ char * PlacementUpsertCommand(uint64 shardId, uint64 placementId, int shardState, - uint64 shardLength, uint32 groupId) + uint64 shardLength, int32 groupId) { StringInfo command = makeStringInfo(); @@ -840,7 +840,7 @@ PlacementUpsertCommand(uint64 shardId, uint64 placementId, int shardState, * of a worker and returns the command in a string. */ static char * -LocalGroupIdUpdateCommand(uint32 groupId) +LocalGroupIdUpdateCommand(int32 groupId) { StringInfo updateCommand = makeStringInfo(); diff --git a/src/backend/distributed/transaction/remote_transaction.c b/src/backend/distributed/transaction/remote_transaction.c index 2a4a6e140..f9028c379 100644 --- a/src/backend/distributed/transaction/remote_transaction.c +++ b/src/backend/distributed/transaction/remote_transaction.c @@ -1323,7 +1323,7 @@ Assign2PCIdentifier(MultiConnection *connection) */ bool ParsePreparedTransactionName(char *preparedTransactionName, - int *groupId, int *procId, + int32 *groupId, int *procId, uint64 *transactionNumber, uint32 *connectionNumber) { diff --git a/src/backend/distributed/transaction/transaction_recovery.c b/src/backend/distributed/transaction/transaction_recovery.c index fde13608e..81746dcd0 100644 --- a/src/backend/distributed/transaction/transaction_recovery.c +++ b/src/backend/distributed/transaction/transaction_recovery.c @@ -78,7 +78,7 @@ recover_prepared_transactions(PG_FUNCTION_ARGS) * prepared transaction should be committed. */ void -LogTransactionRecord(int groupId, char *transactionName) +LogTransactionRecord(int32 groupId, char *transactionName) { Relation pgDistTransaction = NULL; TupleDesc tupleDescriptor = NULL; @@ -141,7 +141,7 @@ RecoverWorkerTransactions(WorkerNode *workerNode) { int recoveredTransactionCount = 0; - int groupId = workerNode->groupId; + int32 groupId = workerNode->groupId; char *nodeName = workerNode->workerName; int nodePort = workerNode->workerPort; @@ -461,7 +461,7 @@ PendingWorkerTransactionList(MultiConnection *connection) static bool IsTransactionInProgress(HTAB *activeTransactionNumberSet, char *preparedTransactionName) { - int groupId = 0; + int32 groupId = 0; int procId = 0; uint32 connectionNumber = 0; uint64 transactionNumber = 0; diff --git a/src/backend/distributed/utils/citus_outfuncs.c b/src/backend/distributed/utils/citus_outfuncs.c index 94f6a4d09..ca8f77d67 100644 --- a/src/backend/distributed/utils/citus_outfuncs.c +++ b/src/backend/distributed/utils/citus_outfuncs.c @@ -402,7 +402,7 @@ OutShardPlacement(OUTFUNC_ARGS) WRITE_UINT64_FIELD(shardId); WRITE_UINT64_FIELD(shardLength); WRITE_ENUM_FIELD(shardState, RelayFileState); - WRITE_UINT_FIELD(groupId); + WRITE_INT_FIELD(groupId); WRITE_STRING_FIELD(nodeName); WRITE_UINT_FIELD(nodePort); /* so we can deal with 0 */ @@ -422,7 +422,7 @@ OutGroupShardPlacement(OUTFUNC_ARGS) WRITE_UINT64_FIELD(shardId); WRITE_UINT64_FIELD(shardLength); WRITE_ENUM_FIELD(shardState, RelayFileState); - WRITE_UINT_FIELD(groupId); + WRITE_INT_FIELD(groupId); } diff --git a/src/backend/distributed/utils/citus_readfuncs.c b/src/backend/distributed/utils/citus_readfuncs.c index bd02f10b9..cbd573142 100644 --- a/src/backend/distributed/utils/citus_readfuncs.c +++ b/src/backend/distributed/utils/citus_readfuncs.c @@ -312,7 +312,7 @@ ReadShardPlacement(READFUNC_ARGS) READ_UINT64_FIELD(shardId); READ_UINT64_FIELD(shardLength); READ_ENUM_FIELD(shardState, RelayFileState); - READ_UINT_FIELD(groupId); + READ_INT_FIELD(groupId); READ_STRING_FIELD(nodeName); READ_UINT_FIELD(nodePort); /* so we can deal with 0 */ @@ -333,7 +333,7 @@ ReadGroupShardPlacement(READFUNC_ARGS) READ_UINT64_FIELD(shardId); READ_UINT64_FIELD(shardLength); READ_ENUM_FIELD(shardState, RelayFileState); - READ_UINT_FIELD(groupId); + READ_INT_FIELD(groupId); READ_DONE(); } diff --git a/src/backend/distributed/utils/metadata_cache.c b/src/backend/distributed/utils/metadata_cache.c index 2973ec709..9d92e748d 100644 --- a/src/backend/distributed/utils/metadata_cache.c +++ b/src/backend/distributed/utils/metadata_cache.c @@ -160,7 +160,7 @@ static int WorkerNodeCount = 0; static bool workerNodeHashValid = false; /* default value is -1, for coordinator it's 0 and for worker nodes > 0 */ -static int LocalGroupId = -1; +static int32 LocalGroupId = -1; /* built first time through in InitializePartitionCache */ static ScanKeyData DistPartitionScanKey[1]; @@ -210,7 +210,7 @@ static ShardInterval * TupleToShardInterval(HeapTuple heapTuple, static void CachedRelationLookup(const char *relationName, Oid *cachedOid); static ShardPlacement * ResolveGroupShardPlacement( GroupShardPlacement *groupShardPlacement, ShardCacheEntry *shardEntry); -static WorkerNode * LookupNodeForGroup(uint32 groupid); +static WorkerNode * LookupNodeForGroup(int32 groupId); static Oid LookupEnumValueId(Oid typeId, char *valueName); static void InvalidateEntireDistCache(void); @@ -474,7 +474,7 @@ LoadShardPlacement(uint64 shardId, uint64 placementId) * on the group. */ ShardPlacement * -FindShardPlacementOnGroup(uint32 groupId, uint64 shardId) +FindShardPlacementOnGroup(int32 groupId, uint64 shardId) { ShardCacheEntry *shardEntry = NULL; DistTableCacheEntry *tableEntry = NULL; @@ -516,7 +516,7 @@ ResolveGroupShardPlacement(GroupShardPlacement *groupShardPlacement, ShardInterval *shardInterval = tableEntry->sortedShardIntervalArray[shardIndex]; ShardPlacement *shardPlacement = CitusMakeNode(ShardPlacement); - uint32 groupId = groupShardPlacement->groupId; + int32 groupId = groupShardPlacement->groupId; WorkerNode *workerNode = LookupNodeForGroup(groupId); /* copy everything into shardPlacement but preserve the header */ @@ -583,7 +583,7 @@ LookupNodeByNodeId(uint32 nodeId) * appropriate error message. */ static WorkerNode * -LookupNodeForGroup(uint32 groupId) +LookupNodeForGroup(int32 groupId) { bool foundAnyNodes = false; int workerNodeIndex = 0; @@ -593,7 +593,7 @@ LookupNodeForGroup(uint32 groupId) for (workerNodeIndex = 0; workerNodeIndex < WorkerNodeCount; workerNodeIndex++) { WorkerNode *workerNode = WorkerNodeArray[workerNodeIndex]; - uint32 workerNodeGroupId = workerNode->groupId; + int32 workerNodeGroupId = workerNode->groupId; if (workerNodeGroupId != groupId) { continue; @@ -609,7 +609,7 @@ LookupNodeForGroup(uint32 groupId) if (!foundAnyNodes) { - ereport(ERROR, (errmsg("there is a shard placement in node group %u but " + ereport(ERROR, (errmsg("there is a shard placement in node group %d but " "there are no nodes in that group", groupId))); } @@ -617,13 +617,13 @@ LookupNodeForGroup(uint32 groupId) { case USE_SECONDARY_NODES_NEVER: { - ereport(ERROR, (errmsg("node group %u does not have a primary node", + ereport(ERROR, (errmsg("node group %d does not have a primary node", groupId))); } case USE_SECONDARY_NODES_ALWAYS: { - ereport(ERROR, (errmsg("node group %u does not have a secondary node", + ereport(ERROR, (errmsg("node group %d does not have a secondary node", groupId))); } @@ -2801,7 +2801,7 @@ RegisterWorkerNodeCacheCallbacks(void) * that pg_dist_local_node_group has exactly one row and has at least one column. * Otherwise, the function errors out. */ -int +int32 GetLocalGroupId(void) { SysScanDesc scanDescriptor = NULL; @@ -2809,7 +2809,7 @@ GetLocalGroupId(void) int scanKeyCount = 0; HeapTuple heapTuple = NULL; TupleDesc tupleDescriptor = NULL; - Oid groupId = InvalidOid; + int32 groupId = 0; Relation pgDistLocalGroupId = NULL; Oid localGroupTableOid = InvalidOid; @@ -2846,7 +2846,7 @@ GetLocalGroupId(void) Anum_pg_dist_local_groupid, tupleDescriptor, &isNull); - groupId = DatumGetUInt32(groupIdDatum); + groupId = DatumGetInt32(groupIdDatum); } else { diff --git a/src/backend/distributed/utils/node_metadata.c b/src/backend/distributed/utils/node_metadata.c index 8d41966ff..435bb0f19 100644 --- a/src/backend/distributed/utils/node_metadata.c +++ b/src/backend/distributed/utils/node_metadata.c @@ -63,7 +63,7 @@ static HeapTuple GetNodeTuple(char *nodeName, int32 nodePort); static Datum GenerateNodeTuple(WorkerNode *workerNode); static int32 GetNextGroupId(void); static int GetNextNodeId(void); -static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport, uint32 groupId, +static void InsertNodeRow(int nodeid, char *nodename, int32 nodeport, int32 groupId, char *nodeRack, bool hasMetadata, bool isActive, Oid nodeRole, char *nodeCluster); static void DeleteNodeRow(char *nodename, int32 nodeport); @@ -395,7 +395,7 @@ WorkerNodeIsReadable(WorkerNode *workerNode) * it will set the bool groupContainsNodes references to true. */ WorkerNode * -PrimaryNodeForGroup(uint32 groupId, bool *groupContainsNodes) +PrimaryNodeForGroup(int32 groupId, bool *groupContainsNodes) { WorkerNode *workerNode = NULL; HASH_SEQ_STATUS status; @@ -405,7 +405,7 @@ PrimaryNodeForGroup(uint32 groupId, bool *groupContainsNodes) while ((workerNode = hash_seq_search(&status)) != NULL) { - uint32 workerNodeGroupId = workerNode->groupId; + int32 workerNodeGroupId = workerNode->groupId; if (workerNodeGroupId != groupId) { continue; @@ -1116,7 +1116,7 @@ GenerateNodeTuple(WorkerNode *workerNode) memset(isNulls, false, sizeof(isNulls)); values[Anum_pg_dist_node_nodeid - 1] = UInt32GetDatum(workerNode->nodeId); - values[Anum_pg_dist_node_groupid - 1] = UInt32GetDatum(workerNode->groupId); + values[Anum_pg_dist_node_groupid - 1] = Int32GetDatum(workerNode->groupId); values[Anum_pg_dist_node_nodename - 1] = CStringGetTextDatum(workerNode->workerName); values[Anum_pg_dist_node_nodeport - 1] = UInt32GetDatum(workerNode->workerPort); values[Anum_pg_dist_node_noderack - 1] = CStringGetTextDatum(workerNode->workerRack); @@ -1166,7 +1166,7 @@ GetNextGroupId() SetUserIdAndSecContext(savedUserId, savedSecurityContext); - groupId = DatumGetUInt32(groupIdDatum); + groupId = DatumGetInt32(groupIdDatum); return groupId; } @@ -1232,7 +1232,7 @@ EnsureCoordinator(void) * an existing group. If you don't it's possible for the metadata to become inconsistent. */ static void -InsertNodeRow(int nodeid, char *nodeName, int32 nodePort, uint32 groupId, char *nodeRack, +InsertNodeRow(int nodeid, char *nodeName, int32 nodePort, int32 groupId, char *nodeRack, bool hasMetadata, bool isActive, Oid nodeRole, char *nodeCluster) { Relation pgDistNode = NULL; @@ -1249,7 +1249,7 @@ InsertNodeRow(int nodeid, char *nodeName, int32 nodePort, uint32 groupId, char * memset(isNulls, false, sizeof(isNulls)); values[Anum_pg_dist_node_nodeid - 1] = UInt32GetDatum(nodeid); - values[Anum_pg_dist_node_groupid - 1] = UInt32GetDatum(groupId); + values[Anum_pg_dist_node_groupid - 1] = Int32GetDatum(groupId); values[Anum_pg_dist_node_nodename - 1] = CStringGetTextDatum(nodeName); values[Anum_pg_dist_node_nodeport - 1] = UInt32GetDatum(nodePort); values[Anum_pg_dist_node_noderack - 1] = CStringGetTextDatum(nodeRack); @@ -1500,7 +1500,7 @@ TupleToWorkerNode(TupleDesc tupleDescriptor, HeapTuple heapTuple) workerNode = (WorkerNode *) palloc0(sizeof(WorkerNode)); workerNode->nodeId = DatumGetUInt32(datumArray[Anum_pg_dist_node_nodeid - 1]); workerNode->workerPort = DatumGetUInt32(datumArray[Anum_pg_dist_node_nodeport - 1]); - workerNode->groupId = DatumGetUInt32(datumArray[Anum_pg_dist_node_groupid - 1]); + workerNode->groupId = DatumGetInt32(datumArray[Anum_pg_dist_node_groupid - 1]); strlcpy(workerNode->workerName, TextDatumGetCString(nodeName), WORKER_LENGTH); strlcpy(workerNode->workerRack, TextDatumGetCString(nodeRack), WORKER_LENGTH); workerNode->hasMetadata = DatumGetBool(datumArray[Anum_pg_dist_node_hasmetadata - 1]); diff --git a/src/backend/distributed/utils/reference_table_utils.c b/src/backend/distributed/utils/reference_table_utils.c index f5b1608a2..2661eaf71 100644 --- a/src/backend/distributed/utils/reference_table_utils.c +++ b/src/backend/distributed/utils/reference_table_utils.c @@ -306,7 +306,7 @@ ReplicateShardToNode(ShardInterval *shardInterval, char *nodeName, int nodePort) if (targetPlacement == NULL || targetPlacement->shardState != FILE_FINALIZED) { uint64 placementId = 0; - uint32 groupId = 0; + int32 groupId = 0; ereport(NOTICE, (errmsg("Replicating reference table \"%s\" to the node %s:%d", get_rel_name(shardInterval->relationId), nodeName, @@ -410,7 +410,7 @@ CreateReferenceTableColocationId() * group of reference tables. It is caller's responsibility to do that if it is necessary. */ void -DeleteAllReferenceTablePlacementsFromNodeGroup(uint32 groupId) +DeleteAllReferenceTablePlacementsFromNodeGroup(int32 groupId) { List *referenceTableList = ReferenceTableOidList(); List *referenceShardIntervalList = NIL; diff --git a/src/include/distributed/master_metadata_utility.h b/src/include/distributed/master_metadata_utility.h index 125f17cb4..3fba2b449 100644 --- a/src/include/distributed/master_metadata_utility.h +++ b/src/include/distributed/master_metadata_utility.h @@ -79,7 +79,7 @@ typedef struct GroupShardPlacement uint64 shardId; uint64 shardLength; RelayFileState shardState; - uint32 groupId; + int32 groupId; } GroupShardPlacement; @@ -94,7 +94,7 @@ typedef struct ShardPlacement uint64 shardId; uint64 shardLength; RelayFileState shardState; - uint32 groupId; + int32 groupId; /* the rest of the fields aren't from pg_dist_placement */ char *nodeName; @@ -122,13 +122,13 @@ extern void CopyShardInterval(ShardInterval *srcInterval, ShardInterval *destInt extern void CopyShardPlacement(ShardPlacement *srcPlacement, ShardPlacement *destPlacement); extern uint64 ShardLength(uint64 shardId); -extern bool NodeGroupHasShardPlacements(uint32 groupId, +extern bool NodeGroupHasShardPlacements(int32 groupId, bool onlyConsiderActivePlacements); extern List * FinalizedShardPlacementList(uint64 shardId); extern ShardPlacement * FinalizedShardPlacement(uint64 shardId, bool missingOk); extern List * BuildShardPlacementList(ShardInterval *shardInterval); extern List * AllShardPlacementsOnNodeGroup(int32 groupId); -extern List * GroupShardPlacementsForTableOnGroup(Oid relationId, uint32 groupId); +extern List * GroupShardPlacementsForTableOnGroup(Oid relationId, int32 groupId); /* Function declarations to modify shard and shard placement data */ extern void InsertShardRow(Oid relationId, uint64 shardId, char storageType, @@ -136,7 +136,7 @@ extern void InsertShardRow(Oid relationId, uint64 shardId, char storageType, extern void DeleteShardRow(uint64 shardId); extern uint64 InsertShardPlacementRow(uint64 shardId, uint64 placementId, char shardState, uint64 shardLength, - uint32 groupId); + int32 groupId); extern void InsertIntoPgDistPartition(Oid relationId, char distributionMethod, Var *distributionColumn, uint32 colocationId, char replicationModel); diff --git a/src/include/distributed/metadata_cache.h b/src/include/distributed/metadata_cache.h index bd410116d..6ade86ed8 100644 --- a/src/include/distributed/metadata_cache.h +++ b/src/include/distributed/metadata_cache.h @@ -93,11 +93,11 @@ extern List * DistributedTableList(void); extern ShardInterval * LoadShardInterval(uint64 shardId); extern Oid RelationIdForShard(uint64 shardId); extern bool ReferenceTableShardId(uint64 shardId); -extern ShardPlacement * FindShardPlacementOnGroup(uint32 groupId, uint64 shardId); +extern ShardPlacement * FindShardPlacementOnGroup(int32 groupId, uint64 shardId); extern GroupShardPlacement * LoadGroupShardPlacement(uint64 shardId, uint64 placementId); extern ShardPlacement * LoadShardPlacement(uint64 shardId, uint64 placementId); extern DistTableCacheEntry * DistributedTableCacheEntry(Oid distributedRelationId); -extern int GetLocalGroupId(void); +extern int32 GetLocalGroupId(void); extern List * DistTableOidList(void); extern Oid LookupShardRelation(int64 shardId, bool missing_ok); extern List * ShardPlacementList(uint64 shardId); diff --git a/src/include/distributed/metadata_sync.h b/src/include/distributed/metadata_sync.h index 05f569a05..98e7b3733 100644 --- a/src/include/distributed/metadata_sync.h +++ b/src/include/distributed/metadata_sync.h @@ -35,7 +35,7 @@ extern char * NodeStateUpdateCommand(uint32 nodeId, bool isActive); extern char * ColocationIdUpdateCommand(Oid relationId, uint32 colocationId); extern char * CreateSchemaDDLCommand(Oid schemaId); extern char * PlacementUpsertCommand(uint64 shardId, uint64 placementId, int shardState, - uint64 shardLength, uint32 groupId); + uint64 shardLength, int32 groupId); extern void CreateTableMetadataOnWorkers(Oid relationId); diff --git a/src/include/distributed/multi_router_executor.h b/src/include/distributed/multi_router_executor.h index 86106c211..003d205f3 100644 --- a/src/include/distributed/multi_router_executor.h +++ b/src/include/distributed/multi_router_executor.h @@ -53,7 +53,7 @@ extern int64 ExecuteModifyTasksSequentiallyWithoutResults(List *taskList, /* helper functions */ extern bool TaskListRequires2PC(List *taskList); -extern List * BuildPlacementSelectList(uint32 groupId, List *relationShardList); -extern List * BuildPlacementDDLList(uint32 groupId, List *relationShardList); +extern List * BuildPlacementSelectList(int32 groupId, List *relationShardList); +extern List * BuildPlacementDDLList(int32 groupId, List *relationShardList); #endif /* MULTI_ROUTER_EXECUTOR_H_ */ diff --git a/src/include/distributed/reference_table_utils.h b/src/include/distributed/reference_table_utils.h index 3e1365844..480e77a1b 100644 --- a/src/include/distributed/reference_table_utils.h +++ b/src/include/distributed/reference_table_utils.h @@ -14,7 +14,7 @@ extern uint32 CreateReferenceTableColocationId(void); extern void ReplicateAllReferenceTablesToNode(char *nodeName, int nodePort); -extern void DeleteAllReferenceTablePlacementsFromNodeGroup(uint32 groupId); +extern void DeleteAllReferenceTablePlacementsFromNodeGroup(int32 groupId); extern List * ReferenceTableOidList(void); extern int CompareOids(const void *leftElement, const void *rightElement); diff --git a/src/include/distributed/remote_transaction.h b/src/include/distributed/remote_transaction.h index 92a065e11..021fb80d5 100644 --- a/src/include/distributed/remote_transaction.h +++ b/src/include/distributed/remote_transaction.h @@ -81,7 +81,7 @@ typedef struct RemoteTransaction /* utility functions for dealing with remote transactions */ -extern bool ParsePreparedTransactionName(char *preparedTransactionName, int *groupId, +extern bool ParsePreparedTransactionName(char *preparedTransactionName, int32 *groupId, int *procId, uint64 *transactionNumber, uint32 *connectionNumber); diff --git a/src/include/distributed/transaction_recovery.h b/src/include/distributed/transaction_recovery.h index 9f359fc18..410f24315 100644 --- a/src/include/distributed/transaction_recovery.h +++ b/src/include/distributed/transaction_recovery.h @@ -17,7 +17,7 @@ extern int Recover2PCInterval; /* Functions declarations for worker transactions */ -extern void LogTransactionRecord(int groupId, char *transactionName); +extern void LogTransactionRecord(int32 groupId, char *transactionName); extern int RecoverTwoPhaseCommits(void); diff --git a/src/include/distributed/worker_manager.h b/src/include/distributed/worker_manager.h index 888d2d7ef..ffa5210c5 100644 --- a/src/include/distributed/worker_manager.h +++ b/src/include/distributed/worker_manager.h @@ -41,7 +41,7 @@ typedef struct WorkerNode uint32 nodeId; /* node's unique id, key of the hash table */ uint32 workerPort; /* node's port */ char workerName[WORKER_LENGTH]; /* node's name */ - uint32 groupId; /* node's groupId; same for the nodes that are in the same group */ + int32 groupId; /* node's groupId; same for the nodes that are in the same group */ char workerRack[WORKER_LENGTH]; /* node's network location */ bool hasMetadata; /* node gets metadata changes */ bool isActive; /* node's state */ @@ -72,7 +72,7 @@ extern WorkerNode * FindWorkerNodeAnyCluster(char *nodeName, int32 nodePort); extern List * ReadWorkerNodes(bool includeNodesFromOtherClusters); extern void EnsureCoordinator(void); extern uint32 GroupForNode(char *nodeName, int32 nodePorT); -extern WorkerNode * PrimaryNodeForGroup(uint32 groupId, bool *groupContainsNodes); +extern WorkerNode * PrimaryNodeForGroup(int32 groupId, bool *groupContainsNodes); extern bool WorkerNodeIsPrimary(WorkerNode *worker); extern bool WorkerNodeIsSecondary(WorkerNode *worker); extern bool WorkerNodeIsReadable(WorkerNode *worker); From 2681231c98405da81978b2dca49926e00c1ff389 Mon Sep 17 00:00:00 2001 From: Murat Tuncer Date: Fri, 1 Feb 2019 10:32:50 +0300 Subject: [PATCH 20/20] Create column aliases for shard tables in worker queries when requested --- src/backend/distributed/utils/ruleutils_10.c | 6 ++ src/backend/distributed/utils/ruleutils_11.c | 6 ++ .../multi_subquery_complex_queries.out | 59 +++++++++++++++++++ .../sql/multi_subquery_complex_queries.sql | 28 +++++++++ 4 files changed, 99 insertions(+) diff --git a/src/backend/distributed/utils/ruleutils_10.c b/src/backend/distributed/utils/ruleutils_10.c index b522ab5ac..07c1df33c 100644 --- a/src/backend/distributed/utils/ruleutils_10.c +++ b/src/backend/distributed/utils/ruleutils_10.c @@ -7134,6 +7134,12 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* Else print column aliases as needed */ get_column_alias_list(colinfo, context); } + /* check if column's are given aliases in distributed tables */ + else if (colinfo->parentUsing != NIL) + { + Assert(colinfo->printaliases); + get_column_alias_list(colinfo, context); + } /* Tablesample clause must go after any alias */ if ((rteKind == CITUS_RTE_RELATION || rteKind == CITUS_RTE_SHARD) && diff --git a/src/backend/distributed/utils/ruleutils_11.c b/src/backend/distributed/utils/ruleutils_11.c index cad31e7c8..4255a5836 100644 --- a/src/backend/distributed/utils/ruleutils_11.c +++ b/src/backend/distributed/utils/ruleutils_11.c @@ -7151,6 +7151,12 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* Else print column aliases as needed */ get_column_alias_list(colinfo, context); } + /* check if column's are given aliases in distributed tables */ + else if (colinfo->parentUsing != NIL) + { + Assert(colinfo->printaliases); + get_column_alias_list(colinfo, context); + } /* Tablesample clause must go after any alias */ if ((rteKind == CITUS_RTE_RELATION || rteKind == CITUS_RTE_SHARD) && diff --git a/src/test/regress/expected/multi_subquery_complex_queries.out b/src/test/regress/expected/multi_subquery_complex_queries.out index f1f2422bc..7efdde87a 100644 --- a/src/test/regress/expected/multi_subquery_complex_queries.out +++ b/src/test/regress/expected/multi_subquery_complex_queries.out @@ -2690,3 +2690,62 @@ ORDER BY 1; 5 | (4 rows) +-- queries where column aliases are used +-- the query is not very complex. join is given an alias with aliases +-- for each output column +SELECT k1 +FROM ( + SELECT k1, random() + FROM (users_table JOIN events_table USING (user_id)) k (k1, k2, k3)) l +ORDER BY k1 +LIMIT 5; + k1 +---- + 1 + 1 + 1 + 1 + 1 +(5 rows) + +SELECT DISTINCT k1 +FROM ( + SELECT k1, random() + FROM (users_table JOIN events_table USING (user_id)) k (k1, k2, k3)) l +ORDER BY k1 +LIMIT 5; + k1 +---- + 1 + 2 + 3 + 4 + 5 +(5 rows) + +SELECT x1, x3, value_2 +FROM (users_table u FULL JOIN events_table e ON (u.user_id = e.user_id)) k(x1, x2, x3, x4, x5) +ORDER BY 1, 2, 3 +LIMIT 5; + x1 | x3 | value_2 +----+----+--------- + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 2 + 1 | 1 | 2 +(5 rows) + +SELECT x1, x3, value_2 +FROM (users_table u FULL JOIN events_table e USING (user_id)) k(x1, x2, x3, x4, x5) +ORDER BY 1, 2, 3 +LIMIT 5; + x1 | x3 | value_2 +----+----+--------- + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 1 + 1 | 1 | 2 + 1 | 1 | 2 +(5 rows) + diff --git a/src/test/regress/sql/multi_subquery_complex_queries.sql b/src/test/regress/sql/multi_subquery_complex_queries.sql index ae89eb166..c44640178 100644 --- a/src/test/regress/sql/multi_subquery_complex_queries.sql +++ b/src/test/regress/sql/multi_subquery_complex_queries.sql @@ -2400,3 +2400,31 @@ FROM USING (user_id) GROUP BY a.user_id ORDER BY 1; + +-- queries where column aliases are used +-- the query is not very complex. join is given an alias with aliases +-- for each output column +SELECT k1 +FROM ( + SELECT k1, random() + FROM (users_table JOIN events_table USING (user_id)) k (k1, k2, k3)) l +ORDER BY k1 +LIMIT 5; + +SELECT DISTINCT k1 +FROM ( + SELECT k1, random() + FROM (users_table JOIN events_table USING (user_id)) k (k1, k2, k3)) l +ORDER BY k1 +LIMIT 5; + +SELECT x1, x3, value_2 +FROM (users_table u FULL JOIN events_table e ON (u.user_id = e.user_id)) k(x1, x2, x3, x4, x5) +ORDER BY 1, 2, 3 +LIMIT 5; + +SELECT x1, x3, value_2 +FROM (users_table u FULL JOIN events_table e USING (user_id)) k(x1, x2, x3, x4, x5) +ORDER BY 1, 2, 3 +LIMIT 5; +