diff --git a/src/backend/distributed/Makefile b/src/backend/distributed/Makefile index cf6e3e341..b69f842c4 100644 --- a/src/backend/distributed/Makefile +++ b/src/backend/distributed/Makefile @@ -8,7 +8,7 @@ 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-1 6.0-2 # All citus--*.sql files in the source directory DATA = $(patsubst $(citus_abs_srcdir)/%.sql,%.sql,$(wildcard $(citus_abs_srcdir)/$(EXTENSION)--*--*.sql)) @@ -60,6 +60,8 @@ $(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 $^ > $@ NO_PGXS = 1 diff --git a/src/backend/distributed/citus--6.0-1--6.0-2.sql b/src/backend/distributed/citus--6.0-1--6.0-2.sql new file mode 100644 index 000000000..ae469d9de --- /dev/null +++ b/src/backend/distributed/citus--6.0-1--6.0-2.sql @@ -0,0 +1,9 @@ +/* citus--6.0-1--6.0-2.sql */ + +CREATE FUNCTION pg_catalog.shard_name(object_name regclass, shard_id bigint) + RETURNS text + LANGUAGE C STABLE + AS 'MODULE_PATHNAME', $$shard_name$$; +COMMENT ON FUNCTION pg_catalog.shard_name(object_name regclass, shard_id bigint) + IS 'returns shard-extended version of object name'; + diff --git a/src/backend/distributed/citus.control b/src/backend/distributed/citus.control index 1fdf421af..07ef50699 100644 --- a/src/backend/distributed/citus.control +++ b/src/backend/distributed/citus.control @@ -1,6 +1,6 @@ # Citus extension comment = 'Citus distributed database' -default_version = '6.0-1' +default_version = '6.0-2' module_pathname = '$libdir/citus' relocatable = false schema = pg_catalog diff --git a/src/backend/distributed/master/master_delete_protocol.c b/src/backend/distributed/master/master_delete_protocol.c index f25d0a780..399936bb9 100644 --- a/src/backend/distributed/master/master_delete_protocol.c +++ b/src/backend/distributed/master/master_delete_protocol.c @@ -317,14 +317,13 @@ DropShards(Oid relationId, char *schemaName, char *relationName, ShardInterval *shardInterval = (ShardInterval *) lfirst(shardIntervalCell); uint64 shardId = shardInterval->shardId; char *quotedShardName = NULL; - StringInfo shardName = makeStringInfo(); + char *shardRelationName = pnstrdup(relationName, NAMEDATALEN); Assert(shardInterval->relationId == relationId); /* Build shard relation name. */ - appendStringInfoString(shardName, relationName); - AppendShardIdToStringInfo(shardName, shardId); - quotedShardName = quote_qualified_identifier(schemaName, shardName->data); + AppendShardIdToName(&shardRelationName, shardId); + quotedShardName = quote_qualified_identifier(schemaName, shardRelationName); shardPlacementList = ShardPlacementList(shardId); foreach(shardPlacementCell, shardPlacementList) @@ -386,7 +385,7 @@ DropShards(Oid relationId, char *schemaName, char *relationName, workerName, workerPort); ereport(WARNING, (errmsg("could not delete shard \"%s\" on node \"%s:%u\"", - shardName->data, workerName, workerPort), + shardRelationName, workerName, workerPort), errdetail("Marking this shard placement for deletion"))); } diff --git a/src/backend/distributed/master/master_expire_table_cache.c b/src/backend/distributed/master/master_expire_table_cache.c index f40a3eec3..f0d3f3e53 100644 --- a/src/backend/distributed/master/master_expire_table_cache.c +++ b/src/backend/distributed/master/master_expire_table_cache.c @@ -144,7 +144,6 @@ DropShardsFromWorker(WorkerNode *workerNode, Oid relationId, List *shardInterval char *schemaName = get_namespace_name(schemaId); char *relationName = get_rel_name(relationId); char relationKind = get_rel_relkind(relationId); - StringInfo shardName = makeStringInfo(); StringInfo workerCommand = makeStringInfo(); ListCell *shardIntervalCell = NULL; @@ -170,12 +169,11 @@ DropShardsFromWorker(WorkerNode *workerNode, Oid relationId, List *shardInterval foreach(shardIntervalCell, shardIntervalList) { ShardInterval *shardInterval = (ShardInterval *) lfirst(shardIntervalCell); + char *shardName = pnstrdup(relationName, NAMEDATALEN); char *quotedShardName = NULL; - resetStringInfo(shardName); - appendStringInfo(shardName, "%s", relationName); - AppendShardIdToStringInfo(shardName, shardInterval->shardId); - quotedShardName = quote_qualified_identifier(schemaName, shardName->data); + AppendShardIdToName(&shardName, shardInterval->shardId); + quotedShardName = quote_qualified_identifier(schemaName, shardName); appendStringInfo(workerCommand, "%s", quotedShardName); /* append a comma after the shard name if there are more shards */ diff --git a/src/backend/distributed/relay/relay_event_utility.c b/src/backend/distributed/relay/relay_event_utility.c index 3dda412f8..3a7f57daf 100644 --- a/src/backend/distributed/relay/relay_event_utility.c +++ b/src/backend/distributed/relay/relay_event_utility.c @@ -21,6 +21,7 @@ #include "access/genam.h" #include "access/heapam.h" #include "access/htup_details.h" +#include "access/hash.h" #include "access/htup.h" #include "access/skey.h" #include "access/stratnum.h" @@ -29,6 +30,7 @@ #include "catalog/pg_constraint.h" #include "distributed/relay_utility.h" #include "lib/stringinfo.h" +#include "mb/pg_wchar.h" #include "nodes/nodes.h" #include "nodes/nodeFuncs.h" #include "nodes/parsenodes.h" @@ -36,9 +38,11 @@ #include "nodes/primnodes.h" #include "nodes/value.h" #include "storage/lock.h" +#include "utils/builtins.h" #include "utils/elog.h" #include "utils/errcodes.h" #include "utils/fmgroids.h" +#include "utils/lsyscache.h" #include "utils/palloc.h" #include "utils/relcache.h" @@ -50,6 +54,9 @@ static void AppendShardIdToConstraintName(AlterTableCmd *command, uint64 shardId static void SetSchemaNameIfNotExist(char **schemaName, char *newSchemaName); static bool UpdateWholeRowColumnReferencesWalker(Node *node, uint64 *shardId); +/* exports for SQL callable functions */ +PG_FUNCTION_INFO_V1(shard_name); + /* * RelayEventExtendNames extends relation names in the given parse tree for * certain utility commands. The function more specifically extends table and @@ -624,32 +631,120 @@ AppendShardIdToName(char **name, uint64 shardId) { char extendedName[NAMEDATALEN]; uint32 extendedNameLength = 0; + int nameLength = strlen(*name); + char shardIdAndSeparator[NAMEDATALEN]; + int shardIdAndSeparatorLength; + uint32 longNameHash = 0; + int multiByteClipLength = 0; - snprintf(extendedName, NAMEDATALEN, "%s%c" UINT64_FORMAT, - (*name), SHARD_NAME_SEPARATOR, shardId); + snprintf(shardIdAndSeparator, NAMEDATALEN, "%c" UINT64_FORMAT, + SHARD_NAME_SEPARATOR, shardId); + shardIdAndSeparatorLength = strlen(shardIdAndSeparator); /* - * Parser should have already checked that the table name has enough space - * reserved for appending shardIds. Nonetheless, we perform an additional - * check here to verify that the appended name does not overflow. + * If *name strlen is < (NAMEDATALEN - shardIdAndSeparatorLength), + * it is safe merely to append the separator and shardId. */ - extendedNameLength = strlen(extendedName) + 1; - if (extendedNameLength >= NAMEDATALEN) + + if (nameLength < (NAMEDATALEN - shardIdAndSeparatorLength)) { - ereport(ERROR, (errmsg("shard name too long to extend: \"%s\"", (*name)))); + snprintf(extendedName, NAMEDATALEN, "%s%s", (*name), shardIdAndSeparator); } + /* + * Otherwise, we need to truncate the name further to accommodate + * a sufficient hash value. The resulting name will avoid collision + * with other hashed names such that for any given schema with + * 90 distinct object names that are long enough to require hashing + * (typically 57-63 characters), the chance of a collision existing is: + * + * If randomly generated UTF8 names: + * (1e-6) * (9.39323783788e-114) ~= (9.39e-120) + * If random case-insensitive ASCII names (letter first, 37 useful characters): + * (1e-6) * (2.80380202421e-74) ~= (2.8e-80) + * If names sharing only N distinct 45- to 47-character prefixes: + * (1e-6) * (1/N) = (1e-6/N) + * 1e-7 for 10 distinct prefixes + * 5e-8 for 20 distinct prefixes + * + * In practice, since shard IDs are globally unique, the risk of name collision + * exists only amongst objects that pertain to a single distributed table + * and are created for each shard: the table name and the names of any indexes + * or index-backed constraints. Since there are typically less than five such + * names, and almost never more than ten, the expected collision rate even in + * the worst case (ten names share same 45- to 47-character prefix) is roughly + * 1e-8: one in 100 million schemas will experience a name collision only if ALL + * 100 million schemas present the worst-case scenario. + */ + else + { + longNameHash = hash_any((unsigned char *) (*name), nameLength); + multiByteClipLength = pg_mbcliplen(*name, nameLength, (NAMEDATALEN - + shardIdAndSeparatorLength - + 10)); + snprintf(extendedName, NAMEDATALEN, "%.*s%c%.8x%s", + multiByteClipLength, (*name), + SHARD_NAME_SEPARATOR, longNameHash, + shardIdAndSeparator); + } + extendedNameLength = strlen(extendedName) + 1; + Assert(extendedNameLength <= NAMEDATALEN); + (*name) = (char *) repalloc((*name), extendedNameLength); snprintf((*name), extendedNameLength, "%s", extendedName); } /* - * AppendShardIdToStringInfo appends shardId to the given name, represented - * by a StringInfo. + * shard_name() provides a PG function interface to AppendShardNameToId above. */ -void -AppendShardIdToStringInfo(StringInfo name, uint64 shardId) +Datum +shard_name(PG_FUNCTION_ARGS) { - appendStringInfo(name, "%c" UINT64_FORMAT, SHARD_NAME_SEPARATOR, shardId); + Oid relationId = InvalidOid; + int64 shardId = 0; + char *relationName = NULL; + + /* + * Have to check arguments for NULLness as it can't be declared STRICT + * because of min/max arguments, which have to be NULLable for new shards. + */ + if (PG_ARGISNULL(0)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("object_name cannot be null"))); + } + if (PG_ARGISNULL(1)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("shard_id cannot be null"))); + } + + + relationId = PG_GETARG_OID(0); + shardId = PG_GETARG_INT64(1); + + if (shardId <= 0) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("shard_id cannot be zero or negative value"))); + } + + + if (!OidIsValid(relationId)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("object_name does not reference a valid relation"))); + } + + relationName = get_rel_name(relationId); + + if (relationName == NULL) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("object_name does not reference a valid relation"))); + } + + AppendShardIdToName(&relationName, shardId); + PG_RETURN_TEXT_P(cstring_to_text(relationName)); } diff --git a/src/include/distributed/relay_utility.h b/src/include/distributed/relay_utility.h index bb504bfa0..ba93ad429 100644 --- a/src/include/distributed/relay_utility.h +++ b/src/include/distributed/relay_utility.h @@ -15,6 +15,7 @@ #ifndef RELAY_UTILITY_H #define RELAY_UTILITY_H +#include "fmgr.h" #include "lib/stringinfo.h" #include "nodes/nodes.h" @@ -41,7 +42,5 @@ typedef enum /* Function declarations to extend names in DDL commands */ extern void RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId); extern void AppendShardIdToName(char **name, uint64 shardId); -extern void AppendShardIdToStringInfo(StringInfo name, uint64 shardId); - #endif /* RELAY_UTILITY_H */ diff --git a/src/test/regress/expected/multi_name_lengths.out b/src/test/regress/expected/multi_name_lengths.out new file mode 100644 index 000000000..fdf9f8822 --- /dev/null +++ b/src/test/regress/expected/multi_name_lengths.out @@ -0,0 +1,398 @@ +-- +-- MULTI_NAME_LENGTHS +-- +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 225000; +ALTER SEQUENCE pg_catalog.pg_dist_jobid_seq RESTART 225000; +SET citus.multi_shard_commit_protocol = '2pc'; +-- Verify that a table name > 56 characters gets hashed properly. +CREATE TABLE too_long_12345678901234567890123456789012345678901234567890 ( + col1 integer not null, + col2 integer not null); +SELECT master_create_distributed_table('too_long_12345678901234567890123456789012345678901234567890', 'col1', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('too_long_12345678901234567890123456789012345678901234567890', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +\c - - - :worker_1_port +\d too_long_* +Table "public.too_long_12345678901234567890123456789012345678_e0119164_225000" + Column | Type | Modifiers +--------+---------+----------- + col1 | integer | not null + col2 | integer | not null + +Table "public.too_long_12345678901234567890123456789012345678_e0119164_225001" + Column | Type | Modifiers +--------+---------+----------- + col1 | integer | not null + col2 | integer | not null + +\c - - - :master_port +-- Verify that the UDF works and rejects bad arguments. +SELECT shard_name(NULL, 666666); +ERROR: object_name cannot be null +SELECT shard_name(0, 666666); +ERROR: object_name does not reference a valid relation +SELECT shard_name('too_long_12345678901234567890123456789012345678901234567890'::regclass, 666666); + shard_name +----------------------------------------------------------------- + too_long_12345678901234567890123456789012345678_e0119164_666666 +(1 row) + +SELECT shard_name('too_long_12345678901234567890123456789012345678901234567890'::regclass, NULL); +ERROR: shard_id cannot be null +SELECT shard_name('too_long_12345678901234567890123456789012345678901234567890'::regclass, -21); +ERROR: shard_id cannot be zero or negative value +DROP TABLE too_long_12345678901234567890123456789012345678901234567890 CASCADE; +-- Table to use for rename checks. +CREATE TABLE name_lengths ( + col1 integer not null, + col2 integer not null, + constraint constraint_a UNIQUE (col1) + ); +SELECT master_create_distributed_table('name_lengths', 'col1', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('name_lengths', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +-- Verify that we CAN add columns with "too-long names", because +-- the columns' names are not extended in the corresponding shard tables. +ALTER TABLE name_lengths ADD COLUMN float_col_12345678901234567890123456789012345678901234567890 FLOAT; +NOTICE: using one-phase commit for distributed DDL commands +HINT: You can enable two-phase commit for extra safety with: SET citus.multi_shard_commit_protocol TO '2pc' +ALTER TABLE name_lengths ADD COLUMN date_col_12345678901234567890123456789012345678901234567890 DATE; +ALTER TABLE name_lengths ADD COLUMN int_col_12345678901234567890123456789012345678901234567890 INTEGER DEFAULT 1; +-- Placeholders for unsupported ALTER TABLE to add constraints with implicit names that are likely too long +ALTER TABLE name_lengths ADD UNIQUE (float_col_12345678901234567890123456789012345678901234567890); +ERROR: alter table command is currently supported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT and TYPE subcommands are supported. +ALTER TABLE name_lengths ADD EXCLUDE (int_col_12345678901234567890123456789012345678901234567890 WITH =); +ERROR: alter table command is currently supported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT and TYPE subcommands are supported. +ALTER TABLE name_lengths ADD CHECK (date_col_12345678901234567890123456789012345678901234567890 > '2014-01-01'::date); +ERROR: alter table command is currently supported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT and TYPE subcommands are supported. +\c - - - :worker_1_port +\d name_lengths_* + Table "public.name_lengths_225002" + Column | Type | Modifiers +--------------------------------------------------------------+------------------+----------- + col1 | integer | not null + col2 | integer | not null + float_col_12345678901234567890123456789012345678901234567890 | double precision | + date_col_12345678901234567890123456789012345678901234567890 | date | + int_col_12345678901234567890123456789012345678901234567890 | integer | default 1 +Indexes: + "constraint_a_225002" UNIQUE CONSTRAINT, btree (col1) + + Table "public.name_lengths_225003" + Column | Type | Modifiers +--------------------------------------------------------------+------------------+----------- + col1 | integer | not null + col2 | integer | not null + float_col_12345678901234567890123456789012345678901234567890 | double precision | + date_col_12345678901234567890123456789012345678901234567890 | date | + int_col_12345678901234567890123456789012345678901234567890 | integer | default 1 +Indexes: + "constraint_a_225003" UNIQUE CONSTRAINT, btree (col1) + +\c - - - :master_port +-- Placeholders for unsupported add constraints with EXPLICIT names that are too long +ALTER TABLE name_lengths ADD CONSTRAINT nl_unique_12345678901234567890123456789012345678901234567890 UNIQUE (float_col_12345678901234567890123456789012345678901234567890); +ERROR: alter table command is currently supported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT and TYPE subcommands are supported. +ALTER TABLE name_lengths ADD CONSTRAINT nl_exclude_12345678901234567890123456789012345678901234567890 EXCLUDE (int_col_12345678901234567890123456789012345678901234567890 WITH =); +ERROR: alter table command is currently supported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT and TYPE subcommands are supported. +ALTER TABLE name_lengths ADD CONSTRAINT nl_checky_12345678901234567890123456789012345678901234567890 CHECK (date_col_12345678901234567890123456789012345678901234567890 >= '2014-01-01'::date); +ERROR: alter table command is currently supported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT and TYPE subcommands are supported. +\c - - - :worker_1_port +\d nl_* +\c - - - :master_port +-- Placeholders for RENAME operations +ALTER TABLE name_lengths RENAME TO name_len_12345678901234567890123456789012345678901234567890; +ERROR: renaming distributed tables or their objects is currently unsupported +ALTER TABLE name_lengths RENAME CONSTRAINT unique_12345678901234567890123456789012345678901234567890 TO unique2_12345678901234567890123456789012345678901234567890; +ERROR: renaming distributed tables or their objects is currently unsupported +-- Verify that CREATE INDEX on already distributed table has proper shard names. +CREATE INDEX tmp_idx_12345678901234567890123456789012345678901234567890 ON name_lengths(col2); +NOTICE: using one-phase commit for distributed DDL commands +HINT: You can enable two-phase commit for extra safety with: SET citus.multi_shard_commit_protocol TO '2pc' +\c - - - :worker_1_port +\d tmp_idx_* +Index "public.tmp_idx_123456789012345678901234567890123456789_5e470afa_225002" + Column | Type | Definition +--------+---------+------------ + col2 | integer | col2 +btree, for table "public.name_lengths_225002" + +Index "public.tmp_idx_123456789012345678901234567890123456789_5e470afa_225003" + Column | Type | Definition +--------+---------+------------ + col2 | integer | col2 +btree, for table "public.name_lengths_225003" + +\c - - - :master_port +-- Verify that a new index name > 63 characters is auto-truncated +-- by the parser/rewriter before further processing, just as in Postgres. +CREATE INDEX tmp_idx_123456789012345678901234567890123456789012345678901234567890 ON name_lengths(col2); +NOTICE: identifier "tmp_idx_123456789012345678901234567890123456789012345678901234567890" will be truncated to "tmp_idx_1234567890123456789012345678901234567890123456789012345" +NOTICE: using one-phase commit for distributed DDL commands +HINT: You can enable two-phase commit for extra safety with: SET citus.multi_shard_commit_protocol TO '2pc' +\c - - - :worker_1_port +\d tmp_idx_* +Index "public.tmp_idx_123456789012345678901234567890123456789_599636aa_225002" + Column | Type | Definition +--------+---------+------------ + col2 | integer | col2 +btree, for table "public.name_lengths_225002" + +Index "public.tmp_idx_123456789012345678901234567890123456789_599636aa_225003" + Column | Type | Definition +--------+---------+------------ + col2 | integer | col2 +btree, for table "public.name_lengths_225003" + +Index "public.tmp_idx_123456789012345678901234567890123456789_5e470afa_225002" + Column | Type | Definition +--------+---------+------------ + col2 | integer | col2 +btree, for table "public.name_lengths_225002" + +Index "public.tmp_idx_123456789012345678901234567890123456789_5e470afa_225003" + Column | Type | Definition +--------+---------+------------ + col2 | integer | col2 +btree, for table "public.name_lengths_225003" + +\c - - - :master_port +-- Verify that distributed tables with too-long names +-- for CHECK constraints are no trouble. +CREATE TABLE sneaky_name_lengths ( + col1 integer not null, + col2 integer not null, + int_col_12345678901234567890123456789012345678901234567890 integer not null, + CHECK (int_col_12345678901234567890123456789012345678901234567890 > 100) + ); +SELECT master_create_distributed_table('sneaky_name_lengths', 'col1', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('sneaky_name_lengths', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +DROP TABLE sneaky_name_lengths CASCADE; +CREATE TABLE sneaky_name_lengths ( + int_col_123456789012345678901234567890123456789012345678901234 integer UNIQUE not null, + col2 integer not null, + CONSTRAINT checky_12345678901234567890123456789012345678901234567890 CHECK (int_col_123456789012345678901234567890123456789012345678901234 > 100) + ); +\d sneaky_name_lengths* + Table "public.sneaky_name_lengths" + Column | Type | Modifiers +----------------------------------------------------------------+---------+----------- + int_col_123456789012345678901234567890123456789012345678901234 | integer | not null + col2 | integer | not null +Indexes: + "sneaky_name_lengths_int_col_1234567890123456789012345678901_key" UNIQUE CONSTRAINT, btree (int_col_123456789012345678901234567890123456789012345678901234) +Check constraints: + "checky_12345678901234567890123456789012345678901234567890" CHECK (int_col_123456789012345678901234567890123456789012345678901234 > 100) + + Index "public.sneaky_name_lengths_int_col_1234567890123456789012345678901_key" + Column | Type | Definition +----------------------------------------------------------------+---------+---------------------------------------------------------------- + int_col_123456789012345678901234567890123456789012345678901234 | integer | int_col_123456789012345678901234567890123456789012345678901234 +unique, btree, for table "public.sneaky_name_lengths" + +SELECT master_create_distributed_table('sneaky_name_lengths', 'int_col_123456789012345678901234567890123456789012345678901234', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('sneaky_name_lengths', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +\c - - - :worker_1_port +\d sneaky_name_lengths* + Table "public.sneaky_name_lengths_225006" + Column | Type | Modifiers +----------------------------------------------------------------+---------+----------- + int_col_123456789012345678901234567890123456789012345678901234 | integer | not null + col2 | integer | not null +Indexes: + "sneaky_name_lengths_int_col_1234567890123456789_6402d2cd_225006" UNIQUE CONSTRAINT, btree (int_col_123456789012345678901234567890123456789012345678901234) +Check constraints: + "checky_12345678901234567890123456789012345678901234567890" CHECK (int_col_123456789012345678901234567890123456789012345678901234 > 100) + + Table "public.sneaky_name_lengths_225007" + Column | Type | Modifiers +----------------------------------------------------------------+---------+----------- + int_col_123456789012345678901234567890123456789012345678901234 | integer | not null + col2 | integer | not null +Indexes: + "sneaky_name_lengths_int_col_1234567890123456789_6402d2cd_225007" UNIQUE CONSTRAINT, btree (int_col_123456789012345678901234567890123456789012345678901234) +Check constraints: + "checky_12345678901234567890123456789012345678901234567890" CHECK (int_col_123456789012345678901234567890123456789012345678901234 > 100) + + Index "public.sneaky_name_lengths_int_col_1234567890123456789_6402d2cd_225006" + Column | Type | Definition +----------------------------------------------------------------+---------+---------------------------------------------------------------- + int_col_123456789012345678901234567890123456789012345678901234 | integer | int_col_123456789012345678901234567890123456789012345678901234 +unique, btree, for table "public.sneaky_name_lengths_225006" + + Index "public.sneaky_name_lengths_int_col_1234567890123456789_6402d2cd_225007" + Column | Type | Definition +----------------------------------------------------------------+---------+---------------------------------------------------------------- + int_col_123456789012345678901234567890123456789012345678901234 | integer | int_col_123456789012345678901234567890123456789012345678901234 +unique, btree, for table "public.sneaky_name_lengths_225007" + +\c - - - :master_port +DROP TABLE sneaky_name_lengths CASCADE; +-- verify that named constraint with too-long name gets hashed properly +CREATE TABLE sneaky_name_lengths ( + col1 integer not null, + col2 integer not null, + int_col_12345678901234567890123456789012345678901234567890 integer not null, + constraint unique_12345678901234567890123456789012345678901234567890 UNIQUE (col1) + ); +SELECT master_create_distributed_table('sneaky_name_lengths', 'col1', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('sneaky_name_lengths', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +\c - - - :worker_1_port +\d sneaky_name_lengths* + Table "public.sneaky_name_lengths_225008" + Column | Type | Modifiers +------------------------------------------------------------+---------+----------- + col1 | integer | not null + col2 | integer | not null + int_col_12345678901234567890123456789012345678901234567890 | integer | not null +Indexes: + "unique_1234567890123456789012345678901234567890_a5986f27_225008" UNIQUE CONSTRAINT, btree (col1) + + Table "public.sneaky_name_lengths_225009" + Column | Type | Modifiers +------------------------------------------------------------+---------+----------- + col1 | integer | not null + col2 | integer | not null + int_col_12345678901234567890123456789012345678901234567890 | integer | not null +Indexes: + "unique_1234567890123456789012345678901234567890_a5986f27_225009" UNIQUE CONSTRAINT, btree (col1) + +\c - - - :master_port +DROP TABLE sneaky_name_lengths CASCADE; +-- Verify that much larger shardIds are handled properly +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 2250000000000; +CREATE TABLE too_long_12345678901234567890123456789012345678901234567890 ( + col1 integer not null, + col2 integer not null); +SELECT master_create_distributed_table('too_long_12345678901234567890123456789012345678901234567890', 'col1', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('too_long_12345678901234567890123456789012345678901234567890', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +\c - - - :worker_1_port +\d too_long_* +Table "public.too_long_1234567890123456789012345678901_e0119164_2250000000000" + Column | Type | Modifiers +--------+---------+----------- + col1 | integer | not null + col2 | integer | not null + +Table "public.too_long_1234567890123456789012345678901_e0119164_2250000000001" + Column | Type | Modifiers +--------+---------+----------- + col1 | integer | not null + col2 | integer | not null + +\c - - - :master_port +DROP TABLE too_long_12345678901234567890123456789012345678901234567890 CASCADE; +-- Verify that multi-byte boundaries are respected for databases with UTF8 encoding. +CREATE TABLE U&"elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D" UESCAPE '!' ( + col1 integer not null PRIMARY KEY, + col2 integer not null); +SELECT master_create_distributed_table(U&'elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D' UESCAPE '!', 'col1', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards(U&'elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D' UESCAPE '!', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +\c - - - :worker_1_port +\d elephant_* +Index "public.elephant_слонслонслонсло_14d34928_2250000000002" + Column | Type | Definition +--------+---------+------------ + col1 | integer | col1 +primary key, btree, for table "public.elephant_слонслонслонсло_c8b737c2_2250000000002" + +Index "public.elephant_слонслонслонсло_14d34928_2250000000003" + Column | Type | Definition +--------+---------+------------ + col1 | integer | col1 +primary key, btree, for table "public.elephant_слонслонслонсло_c8b737c2_2250000000003" + +Table "public.elephant_слонслонслонсло_c8b737c2_2250000000002" + Column | Type | Modifiers +--------+---------+----------- + col1 | integer | not null + col2 | integer | not null +Indexes: + "elephant_слонслонслонсло_14d34928_2250000000002" PRIMARY KEY, btree (col1) + +Table "public.elephant_слонслонслонсло_c8b737c2_2250000000003" + Column | Type | Modifiers +--------+---------+----------- + col1 | integer | not null + col2 | integer | not null +Indexes: + "elephant_слонслонслонсло_14d34928_2250000000003" PRIMARY KEY, btree (col1) + +\c - - - :master_port +-- Clean up. +DROP TABLE name_lengths CASCADE; +DROP TABLE U&"elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D" UESCAPE '!' CASCADE; diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index bb58fdb72..1233063e7 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -17,6 +17,7 @@ # --- test: multi_extension test: multi_table_ddl +test: multi_name_lengths # ---------- # The following distributed tests depend on creating a partitioned table and diff --git a/src/test/regress/sql/multi_name_lengths.sql b/src/test/regress/sql/multi_name_lengths.sql new file mode 100644 index 000000000..35ecd9506 --- /dev/null +++ b/src/test/regress/sql/multi_name_lengths.sql @@ -0,0 +1,157 @@ +-- +-- MULTI_NAME_LENGTHS +-- + +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 225000; +ALTER SEQUENCE pg_catalog.pg_dist_jobid_seq RESTART 225000; + +SET citus.multi_shard_commit_protocol = '2pc'; + +-- Verify that a table name > 56 characters gets hashed properly. +CREATE TABLE too_long_12345678901234567890123456789012345678901234567890 ( + col1 integer not null, + col2 integer not null); +SELECT master_create_distributed_table('too_long_12345678901234567890123456789012345678901234567890', 'col1', 'hash'); +SELECT master_create_worker_shards('too_long_12345678901234567890123456789012345678901234567890', '2', '2'); + +\c - - - :worker_1_port +\d too_long_* +\c - - - :master_port + +-- Verify that the UDF works and rejects bad arguments. +SELECT shard_name(NULL, 666666); +SELECT shard_name(0, 666666); +SELECT shard_name('too_long_12345678901234567890123456789012345678901234567890'::regclass, 666666); +SELECT shard_name('too_long_12345678901234567890123456789012345678901234567890'::regclass, NULL); +SELECT shard_name('too_long_12345678901234567890123456789012345678901234567890'::regclass, -21); + +DROP TABLE too_long_12345678901234567890123456789012345678901234567890 CASCADE; + + +-- Table to use for rename checks. +CREATE TABLE name_lengths ( + col1 integer not null, + col2 integer not null, + constraint constraint_a UNIQUE (col1) + ); + +SELECT master_create_distributed_table('name_lengths', 'col1', 'hash'); +SELECT master_create_worker_shards('name_lengths', '2', '2'); + +-- Verify that we CAN add columns with "too-long names", because +-- the columns' names are not extended in the corresponding shard tables. + +ALTER TABLE name_lengths ADD COLUMN float_col_12345678901234567890123456789012345678901234567890 FLOAT; +ALTER TABLE name_lengths ADD COLUMN date_col_12345678901234567890123456789012345678901234567890 DATE; +ALTER TABLE name_lengths ADD COLUMN int_col_12345678901234567890123456789012345678901234567890 INTEGER DEFAULT 1; + +-- Placeholders for unsupported ALTER TABLE to add constraints with implicit names that are likely too long +ALTER TABLE name_lengths ADD UNIQUE (float_col_12345678901234567890123456789012345678901234567890); +ALTER TABLE name_lengths ADD EXCLUDE (int_col_12345678901234567890123456789012345678901234567890 WITH =); +ALTER TABLE name_lengths ADD CHECK (date_col_12345678901234567890123456789012345678901234567890 > '2014-01-01'::date); + +\c - - - :worker_1_port +\d name_lengths_* +\c - - - :master_port + +-- Placeholders for unsupported add constraints with EXPLICIT names that are too long +ALTER TABLE name_lengths ADD CONSTRAINT nl_unique_12345678901234567890123456789012345678901234567890 UNIQUE (float_col_12345678901234567890123456789012345678901234567890); +ALTER TABLE name_lengths ADD CONSTRAINT nl_exclude_12345678901234567890123456789012345678901234567890 EXCLUDE (int_col_12345678901234567890123456789012345678901234567890 WITH =); +ALTER TABLE name_lengths ADD CONSTRAINT nl_checky_12345678901234567890123456789012345678901234567890 CHECK (date_col_12345678901234567890123456789012345678901234567890 >= '2014-01-01'::date); + +\c - - - :worker_1_port +\d nl_* +\c - - - :master_port + +-- Placeholders for RENAME operations +ALTER TABLE name_lengths RENAME TO name_len_12345678901234567890123456789012345678901234567890; +ALTER TABLE name_lengths RENAME CONSTRAINT unique_12345678901234567890123456789012345678901234567890 TO unique2_12345678901234567890123456789012345678901234567890; + +-- Verify that CREATE INDEX on already distributed table has proper shard names. + +CREATE INDEX tmp_idx_12345678901234567890123456789012345678901234567890 ON name_lengths(col2); + +\c - - - :worker_1_port +\d tmp_idx_* +\c - - - :master_port + +-- Verify that a new index name > 63 characters is auto-truncated +-- by the parser/rewriter before further processing, just as in Postgres. +CREATE INDEX tmp_idx_123456789012345678901234567890123456789012345678901234567890 ON name_lengths(col2); + +\c - - - :worker_1_port +\d tmp_idx_* +\c - - - :master_port + +-- Verify that distributed tables with too-long names +-- for CHECK constraints are no trouble. +CREATE TABLE sneaky_name_lengths ( + col1 integer not null, + col2 integer not null, + int_col_12345678901234567890123456789012345678901234567890 integer not null, + CHECK (int_col_12345678901234567890123456789012345678901234567890 > 100) + ); +SELECT master_create_distributed_table('sneaky_name_lengths', 'col1', 'hash'); +SELECT master_create_worker_shards('sneaky_name_lengths', '2', '2'); +DROP TABLE sneaky_name_lengths CASCADE; + +CREATE TABLE sneaky_name_lengths ( + int_col_123456789012345678901234567890123456789012345678901234 integer UNIQUE not null, + col2 integer not null, + CONSTRAINT checky_12345678901234567890123456789012345678901234567890 CHECK (int_col_123456789012345678901234567890123456789012345678901234 > 100) + ); +\d sneaky_name_lengths* + +SELECT master_create_distributed_table('sneaky_name_lengths', 'int_col_123456789012345678901234567890123456789012345678901234', 'hash'); +SELECT master_create_worker_shards('sneaky_name_lengths', '2', '2'); + +\c - - - :worker_1_port +\d sneaky_name_lengths* +\c - - - :master_port + +DROP TABLE sneaky_name_lengths CASCADE; + +-- verify that named constraint with too-long name gets hashed properly +CREATE TABLE sneaky_name_lengths ( + col1 integer not null, + col2 integer not null, + int_col_12345678901234567890123456789012345678901234567890 integer not null, + constraint unique_12345678901234567890123456789012345678901234567890 UNIQUE (col1) + ); +SELECT master_create_distributed_table('sneaky_name_lengths', 'col1', 'hash'); +SELECT master_create_worker_shards('sneaky_name_lengths', '2', '2'); + +\c - - - :worker_1_port +\d sneaky_name_lengths* +\c - - - :master_port + +DROP TABLE sneaky_name_lengths CASCADE; + +-- Verify that much larger shardIds are handled properly +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 2250000000000; +CREATE TABLE too_long_12345678901234567890123456789012345678901234567890 ( + col1 integer not null, + col2 integer not null); +SELECT master_create_distributed_table('too_long_12345678901234567890123456789012345678901234567890', 'col1', 'hash'); +SELECT master_create_worker_shards('too_long_12345678901234567890123456789012345678901234567890', '2', '2'); + +\c - - - :worker_1_port +\d too_long_* +\c - - - :master_port + +DROP TABLE too_long_12345678901234567890123456789012345678901234567890 CASCADE; + +-- Verify that multi-byte boundaries are respected for databases with UTF8 encoding. +CREATE TABLE U&"elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D" UESCAPE '!' ( + col1 integer not null PRIMARY KEY, + col2 integer not null); +SELECT master_create_distributed_table(U&'elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D' UESCAPE '!', 'col1', 'hash'); +SELECT master_create_worker_shards(U&'elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D' UESCAPE '!', '2', '2'); + +\c - - - :worker_1_port +\d elephant_* +\c - - - :master_port + +-- Clean up. +DROP TABLE name_lengths CASCADE; +DROP TABLE U&"elephant_!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D!0441!043B!043E!043D" UESCAPE '!' CASCADE;