diff --git a/src/backend/distributed/planner/multi_router_planner.c b/src/backend/distributed/planner/multi_router_planner.c index b77eada52..11d62b7fb 100644 --- a/src/backend/distributed/planner/multi_router_planner.c +++ b/src/backend/distributed/planner/multi_router_planner.c @@ -24,6 +24,7 @@ #include "distributed/citus_nodes.h" #include "distributed/citus_nodefuncs.h" #include "distributed/deparse_shard_query.h" +#include "distributed/distribution_column.h" #include "distributed/master_metadata_utility.h" #include "distributed/metadata_cache.h" #include "distributed/multi_join_order.h" @@ -1665,8 +1666,10 @@ TargetShardIntervalForModify(Query *query) DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(distributedTableId); char partitionMethod = cacheEntry->partitionMethod; bool fastShardPruningPossible = false; + CmdType commandType = query->commandType; + bool updateOrDelete = (commandType == CMD_UPDATE || commandType == CMD_DELETE); - Assert(query->commandType != CMD_SELECT); + Assert(commandType != CMD_SELECT); /* error out if no shards exist for the table */ shardCount = cacheEntry->shardIntervalArrayLength; @@ -1710,9 +1713,60 @@ TargetShardIntervalForModify(Query *query) prunedShardCount = list_length(prunedShardList); if (prunedShardCount != 1) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("distributed modifications must target exactly one " - "shard"))); + Oid relationId = cacheEntry->relationId; + char *partitionKeyString = cacheEntry->partitionKeyString; + char *partitionColumnName = ColumnNameToColumn(relationId, partitionKeyString); + StringInfo errorHint = makeStringInfo(); + char *errorDetail = NULL; + + if (prunedShardCount == 0) + { + errorDetail = "This command modifies no shards."; + } + else if (prunedShardCount == shardCount) + { + errorDetail = "This command modifies all shards."; + } + + if (updateOrDelete) + { + appendStringInfo(errorHint, + "Consider using an equality filter on partition column " + "\"%s\". You can use master_modify_multiple_shards() to " + "perform multi-shard delete or update operations.", + partitionColumnName); + } + else + { + appendStringInfo(errorHint, + "Make sure the value for partition column \"%s\" falls into " + "a single shard.", partitionColumnName); + } + + + if (commandType == CMD_DELETE && partitionMethod == DISTRIBUTE_BY_APPEND) + { + appendStringInfo(errorHint, + " You can also use master_apply_delete_command() to drop " + "all shards satisfying delete criteria."); + } + + + if (errorDetail == NULL) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("distributed modifications must target exactly one " + "shard"), + errhint("%s", errorHint->data))); + } + else + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("distributed modifications must target exactly one " + "shard"), + errdetail("%s", errorDetail), + errhint("%s", errorHint->data))); + } } return (ShardInterval *) linitial(prunedShardList); diff --git a/src/test/regress/expected/multi_modifications.out b/src/test/regress/expected/multi_modifications.out index 821bbecbf..5e412d88e 100644 --- a/src/test/regress/expected/multi_modifications.out +++ b/src/test/regress/expected/multi_modifications.out @@ -108,6 +108,8 @@ INSERT INTO limit_orders VALUES (32744, 'AAPL', 9580, '2004-10-19 10:23:54', 'bu INSERT INTO insufficient_shards VALUES (32743, 'AAPL', 9580, '2004-10-19 10:23:54', 'buy', 20.69); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies no shards. +HINT: Make sure the value for partition column "id" falls into a single shard. -- try an insert to a range-partitioned table INSERT INTO range_partitioned VALUES (32743, 'AAPL', 9580, '2004-10-19 10:23:54', 'buy', 20.69); @@ -139,10 +141,14 @@ SET citus.task_executor_type TO DEFAULT; INSERT INTO range_partitioned VALUES (999999, 'AAPL', 9580, '2004-10-19 10:23:54', 'buy', 20.69); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies no shards. +HINT: Make sure the value for partition column "id" falls into a single shard. -- and insert into an append-partitioned table with a value that spans shards: INSERT INTO append_partitioned VALUES (500000, 'AAPL', 9580, '2004-10-19 10:23:54', 'buy', 20.69); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Make sure the value for partition column "id" falls into a single shard. -- INSERT with DEFAULT in the target list INSERT INTO limit_orders VALUES (12756, 'MSFT', 10959, '2013-05-08 07:29:23', 'sell', DEFAULT); @@ -259,6 +265,8 @@ SELECT COUNT(*) FROM limit_orders WHERE id = 246; -- commands with no constraints on the partition key are not supported DELETE FROM limit_orders WHERE bidder_id = 162; ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "id". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. -- commands with a USING clause are unsupported CREATE TABLE bidders ( name text, id bigint ); DELETE FROM limit_orders USING bidders WHERE limit_orders.id = 246 AND @@ -273,6 +281,8 @@ DETAIL: Common table expressions are not supported in distributed modifications -- cursors are not supported DELETE FROM limit_orders WHERE CURRENT OF cursor_name; ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "id". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. INSERT INTO limit_orders VALUES (246, 'TSLA', 162, '2007-07-02 16:32:15', 'sell', 20.69); -- simple UPDATE UPDATE limit_orders SET symbol = 'GM' WHERE id = 246; @@ -392,6 +402,8 @@ ALTER TABLE renamed_orders RENAME TO limit_orders_750000; -- commands with no constraints on the partition key are not supported UPDATE limit_orders SET limit_price = 0.00; ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "id". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. -- attempting to change the partition key is unsupported UPDATE limit_orders SET id = 0 WHERE id = 246; ERROR: modifying the partition value of rows is not allowed @@ -491,6 +503,8 @@ ERROR: non-IMMUTABLE functions are not allowed in the RETURNING clause -- cursors are not supported UPDATE limit_orders SET symbol = 'GM' WHERE CURRENT OF cursor_name; ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "id". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. -- check that multi-row UPDATE/DELETEs with RETURNING work INSERT INTO multiple_hash VALUES ('0', '1'); INSERT INTO multiple_hash VALUES ('0', '2'); diff --git a/src/test/regress/expected/multi_modifying_xacts.out b/src/test/regress/expected/multi_modifying_xacts.out index 44e683db3..477551d68 100644 --- a/src/test/regress/expected/multi_modifying_xacts.out +++ b/src/test/regress/expected/multi_modifying_xacts.out @@ -602,6 +602,8 @@ BEGIN; INSERT INTO append_researchers VALUES (1, 1, 'John McCarthy'); INSERT INTO append_researchers VALUES (500000, 500000, 'Tony Hoare'); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Make sure the value for partition column "id" falls into a single shard. ROLLBACK; SELECT * FROM append_researchers; id | lab_id | name diff --git a/src/test/regress/expected/multi_prepare_plsql.out b/src/test/regress/expected/multi_prepare_plsql.out index 5cc965ab2..3490beed7 100644 --- a/src/test/regress/expected/multi_prepare_plsql.out +++ b/src/test/regress/expected/multi_prepare_plsql.out @@ -874,6 +874,8 @@ SELECT partition_parameter_update(5, 51); -- This fails with an unexpected error message SELECT partition_parameter_update(5, 52); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "key". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. CONTEXT: SQL statement "UPDATE plpgsql_table SET value = $2 WHERE key = $1" PL/pgSQL function partition_parameter_update(integer,integer) line 3 at SQL statement CREATE FUNCTION non_partition_parameter_update(int, int) RETURNS void as $$ @@ -985,6 +987,8 @@ SELECT partition_parameter_delete(5, 51); -- This fails with an unexpected error message SELECT partition_parameter_delete(0, 10); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "key". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. CONTEXT: SQL statement "DELETE FROM plpgsql_table WHERE key = $1 AND value = $2" PL/pgSQL function partition_parameter_delete(integer,integer) line 3 at SQL statement CREATE FUNCTION non_partition_parameter_delete(int) RETURNS void as $$ diff --git a/src/test/regress/expected/multi_prepare_sql.out b/src/test/regress/expected/multi_prepare_sql.out index 7ae77b3e9..5bac00df5 100644 --- a/src/test/regress/expected/multi_prepare_sql.out +++ b/src/test/regress/expected/multi_prepare_sql.out @@ -751,6 +751,8 @@ EXECUTE prepared_partition_parameter_update(5, 51); -- This fails with an unexpected error message EXECUTE prepared_partition_parameter_update(5, 52); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "key". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. PREPARE prepared_non_partition_parameter_update(int, int) AS UPDATE prepare_table SET value = $2 WHERE key = 0 AND value = $1; -- execute 6 times to trigger prepared statement usage @@ -799,6 +801,8 @@ EXECUTE prepared_partition_parameter_delete(5, 51); -- This fails with an unexpected error message EXECUTE prepared_partition_parameter_delete(0, 10); ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "key". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. PREPARE prepared_non_partition_parameter_delete(int) AS DELETE FROM prepare_table WHERE key = 0 AND value = $1; -- execute 6 times to trigger prepared statement usage diff --git a/src/test/regress/expected/multi_simple_queries.out b/src/test/regress/expected/multi_simple_queries.out index 7c460dcee..7351d0983 100644 --- a/src/test/regress/expected/multi_simple_queries.out +++ b/src/test/regress/expected/multi_simple_queries.out @@ -93,8 +93,12 @@ INSERT INTO articles_single_shard VALUES (50, 10, 'anjanette', 19519); -- zero-shard modifications should fail UPDATE articles SET title = '' WHERE author_id = 1 AND author_id = 2; ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies no shards. +HINT: Consider using an equality filter on partition column "author_id". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. DELETE FROM articles WHERE author_id = 1 AND author_id = 2; ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies no shards. +HINT: Consider using an equality filter on partition column "author_id". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. -- single-shard tests -- test simple select for a single row SELECT * FROM articles WHERE author_id = 10 AND id = 50; diff --git a/src/test/regress/input/multi_master_delete_protocol.source b/src/test/regress/input/multi_master_delete_protocol.source index 378985b63..06fb1cdf3 100644 --- a/src/test/regress/input/multi_master_delete_protocol.source +++ b/src/test/regress/input/multi_master_delete_protocol.source @@ -28,9 +28,9 @@ SELECT master_create_distributed_table('customer_delete_protocol', 'c_custkey', SELECT master_apply_delete_command('DELETE FROM customer_delete_protocol WHERE c_acctbal > 0.0'); - +-- Check that free-form deletes are not supported. +DELETE FROM customer_delete_protocol WHERE c_custkey > 100; -- Check that we delete a shard if and only if all rows in the shard satisfy the condition. - SELECT master_apply_delete_command('DELETE FROM customer_delete_protocol WHERE c_custkey > 6500'); SELECT count(*) from customer_delete_protocol; diff --git a/src/test/regress/output/multi_master_delete_protocol.source b/src/test/regress/output/multi_master_delete_protocol.source index cf52bd2fa..10eafe8ad 100644 --- a/src/test/regress/output/multi_master_delete_protocol.source +++ b/src/test/regress/output/multi_master_delete_protocol.source @@ -28,6 +28,11 @@ SELECT master_apply_delete_command('DELETE FROM customer_delete_protocol WHERE c_acctbal > 0.0'); ERROR: cannot delete from distributed table DETAIL: Where clause includes a column other than partition column +-- Check that free-form deletes are not supported. +DELETE FROM customer_delete_protocol WHERE c_custkey > 100; +ERROR: distributed modifications must target exactly one shard +DETAIL: This command modifies all shards. +HINT: Consider using an equality filter on partition column "c_custkey". You can use master_modify_multiple_shards() to perform multi-shard delete or update operations. You can also use master_apply_delete_command() to drop all shards satisfying delete criteria. -- Check that we delete a shard if and only if all rows in the shard satisfy the condition. SELECT master_apply_delete_command('DELETE FROM customer_delete_protocol WHERE c_custkey > 6500');