Thread
Commits
-
Refactor get_partition_for_tuple a bit.
- f0a0c17c1b12 11.0 landed
-
Adopt Bob Jenkins' improved hash function for hash_any(). This changes the
- 8205258fa675 8.4.0 cited
-
Improve hash_any() to use word-wide fetches when hashing suitably aligned
- 2604359251d3 8.4.0 cited
-
[POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-02-28T14:33:13Z
Hi all, Now we have a declarative partitioning, but hash partitioning is not implemented yet. Attached is a POC patch to add the hash partitioning feature. I know we will need more discussions about the syntax and other specifications before going ahead the project, but I think this runnable code might help to discuss what and how we implement this. * Description In this patch, the hash partitioning implementation is basically based on the list partitioning mechanism. However, partition bounds cannot be specified explicitly, but this is used internally as hash partition index, which is calculated when a partition is created or attached. The tentative syntax to create a partitioned table is as bellow; CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; The number of partitions is specified by PARTITIONS, which is currently constant and cannot be changed, but I think this is needed to be changed in some manner. A hash function is specified by USING. Maybe, specifying hash function may be ommitted, and in this case, a default hash function corresponding to key type will be used. A partition table can be create as bellow; CREATE TABLE h1 PARTITION OF h; CREATE TABLE h2 PARTITION OF h; CREATE TABLE h3 PARTITION OF h; FOR VALUES clause cannot be used, and the partition bound is calclulated automatically as partition index of single integer value. When trying create partitions more than the number specified by PARTITIONS, it gets an error. postgres=# create table h4 partition of h; ERROR: cannot create hash partition more than 3 for h An inserted record is stored in a partition whose index equals abs(hashfunc(key)) % <number_of_partitions>. In the above example, this is abs(hashint4(i))%3. postgres=# insert into h (select generate_series(0,20)); INSERT 0 21 postgres=# select *,tableoid::regclass from h; i | tableoid ----+---------- 0 | h1 1 | h1 2 | h1 4 | h1 8 | h1 10 | h1 11 | h1 14 | h1 15 | h1 17 | h1 20 | h1 5 | h2 12 | h2 13 | h2 16 | h2 19 | h2 3 | h3 6 | h3 7 | h3 9 | h3 18 | h3 (21 rows) * Todo / discussions In this patch, we cannot change the number of partitions specified by PARTITIONS. I we can change this, the partitioning rule (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) is also changed and then we need reallocatiing records between partitions. In this patch, user can specify a hash function USING. However, we migth need default hash functions which are useful and proper for hash partitioning. Currently, even when we issue SELECT query with a condition, postgres looks into all partitions regardless of each partition's constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". postgres=# explain select * from h where i = 10; QUERY PLAN ---------------------------------------------------------- Append (cost=0.00..125.62 rows=40 width=4) -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) Filter: (i = 10) -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) Filter: (i = 10) -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) Filter: (i = 10) -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) Filter: (i = 10) (9 rows) However, if we modify a condition into a same expression as the partitions constraint, postgres can exclude unrelated table from search targets. So, we might avoid the problem by converting the qual properly before calling predicate_refuted_by(). postgres=# explain select * from h where abs(hashint4(i))%3 = abs(hashint4(10))%3; QUERY PLAN ---------------------------------------------------------- Append (cost=0.00..61.00 rows=14 width=4) -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) Filter: ((abs(hashint4(i)) % 3) = 2) -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) Filter: ((abs(hashint4(i)) % 3) = 2) (5 rows) Best regards, Yugo Nagata -- Yugo Nagata <nagata@sraoss.co.jp> -
Re: [POC] hash partitioning
Aleksander Alekseev <a.alekseev@postgrespro.ru> — 2017-02-28T15:05:36Z
Hi, Yugo. Looks like a great feature! I'm going to take a closer look on your code and write a feedback shortly. For now I can only tell that you forgot to include some documentation in the patch. I've added a corresponding entry to current commitfest [1]. Hope you don't mind. If it's not too much trouble could you please register on a commitfest site and add yourself to this entry as an author? I'm pretty sure someone is using this information for writing release notes or something like this. [1] https://commitfest.postgresql.org/13/1059/ On Tue, Feb 28, 2017 at 11:33:13PM +0900, Yugo Nagata wrote: > Hi all, > > Now we have a declarative partitioning, but hash partitioning is not > implemented yet. Attached is a POC patch to add the hash partitioning > feature. I know we will need more discussions about the syntax and other > specifications before going ahead the project, but I think this runnable > code might help to discuss what and how we implement this. > > * Description > > In this patch, the hash partitioning implementation is basically based > on the list partitioning mechanism. However, partition bounds cannot be > specified explicitly, but this is used internally as hash partition > index, which is calculated when a partition is created or attached. > > The tentative syntax to create a partitioned table is as bellow; > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > The number of partitions is specified by PARTITIONS, which is currently > constant and cannot be changed, but I think this is needed to be changed in > some manner. A hash function is specified by USING. Maybe, specifying hash > function may be ommitted, and in this case, a default hash function > corresponding to key type will be used. > > A partition table can be create as bellow; > > CREATE TABLE h1 PARTITION OF h; > CREATE TABLE h2 PARTITION OF h; > CREATE TABLE h3 PARTITION OF h; > > FOR VALUES clause cannot be used, and the partition bound is > calclulated automatically as partition index of single integer value. > > When trying create partitions more than the number specified > by PARTITIONS, it gets an error. > > postgres=# create table h4 partition of h; > ERROR: cannot create hash partition more than 3 for h > > An inserted record is stored in a partition whose index equals > abs(hashfunc(key)) % <number_of_partitions>. In the above > example, this is abs(hashint4(i))%3. > > postgres=# insert into h (select generate_series(0,20)); > INSERT 0 21 > > postgres=# select *,tableoid::regclass from h; > i | tableoid > ----+---------- > 0 | h1 > 1 | h1 > 2 | h1 > 4 | h1 > 8 | h1 > 10 | h1 > 11 | h1 > 14 | h1 > 15 | h1 > 17 | h1 > 20 | h1 > 5 | h2 > 12 | h2 > 13 | h2 > 16 | h2 > 19 | h2 > 3 | h3 > 6 | h3 > 7 | h3 > 9 | h3 > 18 | h3 > (21 rows) > > * Todo / discussions > > In this patch, we cannot change the number of partitions specified > by PARTITIONS. I we can change this, the partitioning rule > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > is also changed and then we need reallocatiing records between > partitions. > > In this patch, user can specify a hash function USING. However, > we migth need default hash functions which are useful and > proper for hash partitioning. > > Currently, even when we issue SELECT query with a condition, > postgres looks into all partitions regardless of each partition's > constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". > > postgres=# explain select * from h where i = 10; > QUERY PLAN > ---------------------------------------------------------- > Append (cost=0.00..125.62 rows=40 width=4) > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > Filter: (i = 10) > -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) > Filter: (i = 10) > -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) > Filter: (i = 10) > -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) > Filter: (i = 10) > (9 rows) > > However, if we modify a condition into a same expression > as the partitions constraint, postgres can exclude unrelated > table from search targets. So, we might avoid the problem > by converting the qual properly before calling predicate_refuted_by(). > > postgres=# explain select * from h where abs(hashint4(i))%3 = abs(hashint4(10))%3; > QUERY PLAN > ---------------------------------------------------------- > Append (cost=0.00..61.00 rows=14 width=4) > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > Filter: ((abs(hashint4(i)) % 3) = 2) > -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) > Filter: ((abs(hashint4(i)) % 3) = 2) > (5 rows) > > Best regards, > Yugo Nagata > > -- > Yugo Nagata <nagata@sraoss.co.jp> > diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c > index 41c0056..3820920 100644 > --- a/src/backend/catalog/heap.c > +++ b/src/backend/catalog/heap.c > @@ -3074,7 +3074,7 @@ StorePartitionKey(Relation rel, > AttrNumber *partattrs, > List *partexprs, > Oid *partopclass, > - Oid *partcollation) > + Oid *partcollation, int16 partnparts, Oid hashfunc) > { > int i; > int2vector *partattrs_vec; > @@ -3121,6 +3121,8 @@ StorePartitionKey(Relation rel, > values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); > values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy); > values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts); > + values[Anum_pg_partitioned_table_partnparts - 1] = Int16GetDatum(partnparts); > + values[Anum_pg_partitioned_table_parthashfunc - 1] = ObjectIdGetDatum(hashfunc); > values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec); > values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec); > values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec); > diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c > index 4bcef58..24e69c6 100644 > --- a/src/backend/catalog/partition.c > +++ b/src/backend/catalog/partition.c > @@ -36,6 +36,8 @@ > #include "optimizer/clauses.h" > #include "optimizer/planmain.h" > #include "optimizer/var.h" > +#include "parser/parse_func.h" > +#include "parser/parse_oper.h" > #include "rewrite/rewriteManip.h" > #include "storage/lmgr.h" > #include "utils/array.h" > @@ -120,6 +122,7 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b, > > static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); > static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); > +static List *get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec); > static Oid get_partition_operator(PartitionKey key, int col, > StrategyNumber strategy, bool *need_relabel); > static List *generate_partition_qual(Relation rel); > @@ -236,7 +239,8 @@ RelationBuildPartitionDesc(Relation rel) > oids[i++] = lfirst_oid(cell); > > /* Convert from node to the internal representation */ > - if (key->strategy == PARTITION_STRATEGY_LIST) > + if (key->strategy == PARTITION_STRATEGY_LIST || > + key->strategy == PARTITION_STRATEGY_HASH) > { > List *non_null_values = NIL; > > @@ -251,7 +255,7 @@ RelationBuildPartitionDesc(Relation rel) > ListCell *c; > PartitionBoundSpec *spec = lfirst(cell); > > - if (spec->strategy != PARTITION_STRATEGY_LIST) > + if (spec->strategy != key->strategy) > elog(ERROR, "invalid strategy in partition bound spec"); > > foreach(c, spec->listdatums) > @@ -464,6 +468,7 @@ RelationBuildPartitionDesc(Relation rel) > switch (key->strategy) > { > case PARTITION_STRATEGY_LIST: > + case PARTITION_STRATEGY_HASH: > { > boundinfo->has_null = found_null; > boundinfo->indexes = (int *) palloc(ndatums * sizeof(int)); > @@ -829,6 +834,18 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) > break; > } > > + case PARTITION_STRATEGY_HASH: > + { > + Assert(spec->strategy == PARTITION_STRATEGY_HASH); > + > + if (partdesc->nparts + 1 > key->partnparts) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), > + errmsg("cannot create hash partition more than %d for %s", > + key->partnparts, RelationGetRelationName(parent)))); > + break; > + } > + > default: > elog(ERROR, "unexpected partition strategy: %d", > (int) key->strategy); > @@ -916,6 +933,11 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) > my_qual = get_qual_for_range(key, spec); > break; > > + case PARTITION_STRATEGY_HASH: > + Assert(spec->strategy == PARTITION_STRATEGY_LIST); > + my_qual = get_qual_for_hash(key, spec); > + break; > + > default: > elog(ERROR, "unexpected partition strategy: %d", > (int) key->strategy); > @@ -1146,6 +1168,84 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode, > return pd; > } > > +/* > + * convert_expr_for_hash > + * > + * Converts a expr for a hash partition's constraint. > + * expr is converted into 'abs(hashfunc(expr)) % npart". > + * > + * npart: number of partitions > + * hashfunc: OID of hash function > + */ > +Expr * > +convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc) > +{ > + FuncExpr *func, > + *abs; > + Expr *modexpr; > + Oid modoid; > + Oid int4oid[1] = {INT4OID}; > + > + ParseState *pstate = make_parsestate(NULL); > + Value *val_npart = makeInteger(npart); > + Node *const_npart = (Node *) make_const(pstate, val_npart, -1); > + > + /* hash function */ > + func = makeFuncExpr(hashfunc, > + INT4OID, > + list_make1(expr), > + 0, > + 0, > + COERCE_EXPLICIT_CALL); > + > + /* Abs */ > + abs = makeFuncExpr(LookupFuncName(list_make1(makeString("abs")), 1, int4oid, false), > + INT4OID, > + list_make1(func), > + 0, > + 0, > + COERCE_EXPLICIT_CALL); > + > + /* modulo by npart */ > + modoid = LookupOperName(pstate, list_make1(makeString("%")), INT4OID, INT4OID, false, -1); > + modexpr = make_opclause(modoid, INT4OID, false, (Expr*)abs, (Expr*)const_npart, 0, 0); > + > + return modexpr; > +} > + > + > +/* > + * get_next_hash_partition_index > + * > + * Returns the minimal index which is not used for hash partition. > + */ > +int > +get_next_hash_partition_index(Relation parent) > +{ > + PartitionKey key = RelationGetPartitionKey(parent); > + PartitionDesc partdesc = RelationGetPartitionDesc(parent); > + > + int i; > + bool *used = palloc0(sizeof(int) * key->partnparts); > + > + /* mark used for existing partition indexs */ > + for (i = 0; i < partdesc->boundinfo->ndatums; i++) > + { > + Datum* datum = partdesc->boundinfo->datums[i]; > + int idx = DatumGetInt16(datum[0]); > + > + if (!used[idx]) > + used[idx] = true; > + } > + > + /* find the minimal unused index */ > + for (i = 0; i < key->partnparts; i++) > + if (!used[i]) > + break; > + > + return i; > +} > + > /* Module-local functions */ > > /* > @@ -1467,6 +1567,43 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) > } > > /* > + * get_qual_for_hash > + * > + * Returns a list of expressions to use as a hash partition's constraint. > + */ > +static List * > +get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec) > +{ > + List *result; > + Expr *keyCol; > + Expr *expr; > + Expr *opexpr; > + Oid operoid; > + ParseState *pstate = make_parsestate(NULL); > + > + /* Left operand */ > + if (key->partattrs[0] != 0) > + keyCol = (Expr *) makeVar(1, > + key->partattrs[0], > + key->parttypid[0], > + key->parttypmod[0], > + key->parttypcoll[0], > + 0); > + else > + keyCol = (Expr *) copyObject(linitial(key->partexprs)); > + > + expr = convert_expr_for_hash(keyCol, key->partnparts, key->parthashfunc); > + > + /* equals the listdaums value */ > + operoid = LookupOperName(pstate, list_make1(makeString("=")), INT4OID, INT4OID, false, -1); > + opexpr = make_opclause(operoid, BOOLOID, false, expr, linitial(spec->listdatums), 0, 0); > + > + result = list_make1(opexpr); > + > + return result; > +} > + > +/* > * get_partition_operator > * > * Return oid of the operator of given strategy for a given partition key > @@ -1730,6 +1867,11 @@ get_partition_for_tuple(PartitionDispatch *pd, > (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), > errmsg("range partition key of row contains null"))); > } > + else if (key->strategy == PARTITION_STRATEGY_HASH) > + { > + values[0] = OidFunctionCall1(key->parthashfunc, values[0]); > + values[0] = Int16GetDatum(Abs(DatumGetInt16(values[0])) % key->partnparts); > + } > > if (partdesc->boundinfo->has_null && isnull[0]) > /* Tuple maps to the null-accepting list partition */ > @@ -1744,6 +1886,7 @@ get_partition_for_tuple(PartitionDispatch *pd, > switch (key->strategy) > { > case PARTITION_STRATEGY_LIST: > + case PARTITION_STRATEGY_HASH: > if (cur_offset >= 0 && equal) > cur_index = partdesc->boundinfo->indexes[cur_offset]; > else > @@ -1968,6 +2111,7 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo, > switch (key->strategy) > { > case PARTITION_STRATEGY_LIST: > + case PARTITION_STRATEGY_HASH: > cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], > key->partcollation[0], > bound_datums[0], > diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c > index 3cea220..5a28cc0 100644 > --- a/src/backend/commands/tablecmds.c > +++ b/src/backend/commands/tablecmds.c > @@ -41,6 +41,7 @@ > #include "catalog/pg_inherits_fn.h" > #include "catalog/pg_namespace.h" > #include "catalog/pg_opclass.h" > +#include "catalog/pg_proc.h" > #include "catalog/pg_tablespace.h" > #include "catalog/pg_trigger.h" > #include "catalog/pg_type.h" > @@ -77,6 +78,7 @@ > #include "parser/parse_oper.h" > #include "parser/parse_relation.h" > #include "parser/parse_type.h" > +#include "parser/parse_func.h" > #include "parser/parse_utilcmd.h" > #include "parser/parser.h" > #include "pgstat.h" > @@ -450,7 +452,7 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, > Oid oldrelid, void *arg); > static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr); > static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy); > -static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > +static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > List **partexprs, Oid *partopclass, Oid *partcollation); > static void CreateInheritance(Relation child_rel, Relation parent_rel); > static void RemoveInheritance(Relation child_rel, Relation parent_rel); > @@ -799,8 +801,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > AttrNumber partattrs[PARTITION_MAX_KEYS]; > Oid partopclass[PARTITION_MAX_KEYS]; > Oid partcollation[PARTITION_MAX_KEYS]; > + Oid partatttypes[PARTITION_MAX_KEYS]; > List *partexprs = NIL; > List *cmds = NIL; > + Oid hashfuncOid = InvalidOid; > > /* > * We need to transform the raw parsetrees corresponding to partition > @@ -811,15 +815,40 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > stmt->partspec = transformPartitionSpec(rel, stmt->partspec, > &strategy); > ComputePartitionAttrs(rel, stmt->partspec->partParams, > - partattrs, &partexprs, partopclass, > + partattrs, partatttypes, &partexprs, partopclass, > partcollation); > > partnatts = list_length(stmt->partspec->partParams); > + > + if (strategy == PARTITION_STRATEGY_HASH) > + { > + Oid funcrettype; > + > + if (partnatts != 1) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("number of partition key must be 1 for hash partition"))); > + > + hashfuncOid = LookupFuncName(stmt->partspec->hashfunc, 1, partatttypes, false); > + funcrettype = get_func_rettype(hashfuncOid); > + if (funcrettype != INT4OID) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("hash function for partitioning must return integer"))); > + > + if (func_volatile(hashfuncOid) != PROVOLATILE_IMMUTABLE) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("hash function for partitioning must be marked IMMUTABLE"))); > + > + } > + > StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs, > - partopclass, partcollation); > + partopclass, partcollation, stmt->partspec->partnparts, hashfuncOid); > > - /* Force key columns to be NOT NULL when using range partitioning */ > - if (strategy == PARTITION_STRATEGY_RANGE) > + /* Force key columns to be NOT NULL when using range or hash partitioning */ > + if (strategy == PARTITION_STRATEGY_RANGE || > + strategy == PARTITION_STRATEGY_HASH) > { > for (i = 0; i < partnatts; i++) > { > @@ -12783,18 +12812,51 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > newspec->strategy = partspec->strategy; > newspec->location = partspec->location; > newspec->partParams = NIL; > + newspec->partnparts = partspec->partnparts; > + newspec->hashfunc = partspec->hashfunc; > > /* Parse partitioning strategy name */ > if (!pg_strcasecmp(partspec->strategy, "list")) > *strategy = PARTITION_STRATEGY_LIST; > else if (!pg_strcasecmp(partspec->strategy, "range")) > *strategy = PARTITION_STRATEGY_RANGE; > + else if (!pg_strcasecmp(partspec->strategy, "hash")) > + *strategy = PARTITION_STRATEGY_HASH; > else > ereport(ERROR, > (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > errmsg("unrecognized partitioning strategy \"%s\"", > partspec->strategy))); > > + if (*strategy == PARTITION_STRATEGY_HASH) > + { > + if (partspec->partnparts < 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("number of partitions must be specified for hash partition"))); > + else if (partspec->partnparts == 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("number of partitions must be greater than 0"))); > + > + if (list_length(partspec->hashfunc) == 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("hash function must be specified for hash partition"))); > + } > + else > + { > + if (partspec->partnparts >= 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("number of partitions can be specified only for hash partition"))); > + > + if (list_length(partspec->hashfunc) > 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > + errmsg("hash function can be specified only for hash partition"))); > + } > + > /* > * Create a dummy ParseState and insert the target relation as its sole > * rangetable entry. We need a ParseState for transformExpr. > @@ -12843,7 +12905,7 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > * Compute per-partition-column information from a list of PartitionElem's > */ > static void > -ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > +ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > List **partexprs, Oid *partopclass, Oid *partcollation) > { > int attn; > @@ -13010,6 +13072,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > "btree", > BTREE_AM_OID); > > + partatttypes[attn] = atttype; > attn++; > } > } > diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c > index 05d8538..f4febc9 100644 > --- a/src/backend/nodes/copyfuncs.c > +++ b/src/backend/nodes/copyfuncs.c > @@ -4232,6 +4232,8 @@ _copyPartitionSpec(const PartitionSpec *from) > > COPY_STRING_FIELD(strategy); > COPY_NODE_FIELD(partParams); > + COPY_SCALAR_FIELD(partnparts); > + COPY_NODE_FIELD(hashfunc); > COPY_LOCATION_FIELD(location); > > return newnode; > diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c > index d595cd7..d589eac 100644 > --- a/src/backend/nodes/equalfuncs.c > +++ b/src/backend/nodes/equalfuncs.c > @@ -2725,6 +2725,8 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b) > { > COMPARE_STRING_FIELD(strategy); > COMPARE_NODE_FIELD(partParams); > + COMPARE_SCALAR_FIELD(partnparts); > + COMPARE_NODE_FIELD(hashfunc); > COMPARE_LOCATION_FIELD(location); > > return true; > diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c > index b3802b4..d6db80e 100644 > --- a/src/backend/nodes/outfuncs.c > +++ b/src/backend/nodes/outfuncs.c > @@ -3318,6 +3318,8 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node) > > WRITE_STRING_FIELD(strategy); > WRITE_NODE_FIELD(partParams); > + WRITE_INT_FIELD(partnparts); > + WRITE_NODE_FIELD(hashfunc); > WRITE_LOCATION_FIELD(location); > } > > diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y > index e833b2e..b67140d 100644 > --- a/src/backend/parser/gram.y > +++ b/src/backend/parser/gram.y > @@ -574,6 +574,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > %type <list> partbound_datum_list > %type <partrange_datum> PartitionRangeDatum > %type <list> range_datum_list > +%type <ival> hash_partitions > +%type <list> hash_function > > /* > * Non-keyword token types. These are hard-wired into the "flex" lexer. > @@ -627,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING > > - HANDLER HAVING HEADER_P HOLD HOUR_P > + HANDLER HASH HAVING HEADER_P HOLD HOUR_P > > IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P > INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P > @@ -651,7 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR > ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER > > - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY > + PARALLEL PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PLACING PLANS POLICY > POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY > PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM PUBLICATION > > @@ -2587,6 +2589,16 @@ ForValues: > > $$ = (Node *) n; > } > + > + /* a HASH partition */ > + | /*EMPTY*/ > + { > + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); > + > + n->strategy = PARTITION_STRATEGY_HASH; > + > + $$ = (Node *) n; > + } > ; > > partbound_datum: > @@ -3666,7 +3678,7 @@ OptPartitionSpec: PartitionSpec { $$ = $1; } > | /*EMPTY*/ { $$ = NULL; } > ; > > -PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > +PartitionSpec: PARTITION BY part_strategy '(' part_params ')' hash_partitions hash_function > { > PartitionSpec *n = makeNode(PartitionSpec); > > @@ -3674,10 +3686,21 @@ PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > n->partParams = $5; > n->location = @1; > > + n->partnparts = $7; > + n->hashfunc = $8; > + > $$ = n; > } > ; > > +hash_partitions: PARTITIONS Iconst { $$ = $2; } > + | /*EMPTY*/ { $$ = -1; } > + ; > + > +hash_function: USING handler_name { $$ = $2; } > + | /*EMPTY*/ { $$ = NULL; } > + ; > + > part_strategy: IDENT { $$ = $1; } > | unreserved_keyword { $$ = pstrdup($1); } > ; > @@ -14377,6 +14400,7 @@ unreserved_keyword: > | GLOBAL > | GRANTED > | HANDLER > + | HASH > | HEADER_P > | HOLD > | HOUR_P > @@ -14448,6 +14472,7 @@ unreserved_keyword: > | PARSER > | PARTIAL > | PARTITION > + | PARTITIONS > | PASSING > | PASSWORD > | PLANS > diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c > index ff2bab6..8e1be31 100644 > --- a/src/backend/parser/parse_utilcmd.c > +++ b/src/backend/parser/parse_utilcmd.c > @@ -40,6 +40,7 @@ > #include "catalog/pg_opclass.h" > #include "catalog/pg_operator.h" > #include "catalog/pg_type.h" > +#include "catalog/partition.h" > #include "commands/comment.h" > #include "commands/defrem.h" > #include "commands/tablecmds.h" > @@ -3252,6 +3253,24 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound) > ++i; > } > } > + else if (strategy == PARTITION_STRATEGY_HASH) > + { > + Value *conval; > + Node *value; > + int index; > + > + if (spec->strategy != PARTITION_STRATEGY_HASH) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), > + errmsg("invalid bound specification for a hash partition"))); > + > + index = get_next_hash_partition_index(parent); > + > + /* store the partition index as a listdatums value */ > + conval = makeInteger(index); > + value = (Node *) make_const(pstate, conval, -1); > + result_spec->listdatums = list_make1(value); > + } > else > elog(ERROR, "unexpected partition strategy: %d", (int) strategy); > > diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c > index b27b77d..fab6eea 100644 > --- a/src/backend/utils/adt/ruleutils.c > +++ b/src/backend/utils/adt/ruleutils.c > @@ -1423,7 +1423,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, > * > * Returns the partition key specification, ie, the following: > * > - * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) > + * PARTITION BY { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...]) > */ > Datum > pg_get_partkeydef(PG_FUNCTION_ARGS) > @@ -1513,6 +1513,9 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags) > case PARTITION_STRATEGY_RANGE: > appendStringInfo(&buf, "RANGE"); > break; > + case PARTITION_STRATEGY_HASH: > + appendStringInfo(&buf, "HASH"); > + break; > default: > elog(ERROR, "unexpected partition strategy: %d", > (int) form->partstrat); > @@ -8520,6 +8523,9 @@ get_rule_expr(Node *node, deparse_context *context, > appendStringInfoString(buf, ")"); > break; > > + case PARTITION_STRATEGY_HASH: > + break; > + > default: > elog(ERROR, "unrecognized partition strategy: %d", > (int) spec->strategy); > diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c > index 9001e20..829e4d2 100644 > --- a/src/backend/utils/cache/relcache.c > +++ b/src/backend/utils/cache/relcache.c > @@ -855,6 +855,9 @@ RelationBuildPartitionKey(Relation relation) > key->strategy = form->partstrat; > key->partnatts = form->partnatts; > > + key->partnparts = form->partnparts; > + key->parthashfunc = form->parthashfunc; > + > /* > * We can rely on the first variable-length attribute being mapped to the > * relevant field of the catalog's C struct, because all previous > @@ -999,6 +1002,9 @@ copy_partition_key(PartitionKey fromkey) > newkey->strategy = fromkey->strategy; > newkey->partnatts = n = fromkey->partnatts; > > + newkey->partnparts = fromkey->partnparts; > + newkey->parthashfunc = fromkey->parthashfunc; > + > newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber)); > memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber)); > > diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h > index 1187797..367e2f8 100644 > --- a/src/include/catalog/heap.h > +++ b/src/include/catalog/heap.h > @@ -141,7 +141,7 @@ extern void StorePartitionKey(Relation rel, > AttrNumber *partattrs, > List *partexprs, > Oid *partopclass, > - Oid *partcollation); > + Oid *partcollation, int16 partnparts, Oid hashfunc); > extern void RemovePartitionKeyByRelId(Oid relid); > extern void StorePartitionBound(Relation rel, Relation parent, Node *bound); > > diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h > index b195d1a..80f4b0e 100644 > --- a/src/include/catalog/partition.h > +++ b/src/include/catalog/partition.h > @@ -89,4 +89,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd, > TupleTableSlot *slot, > EState *estate, > Oid *failed_at); > +extern Expr *convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc); > +extern int get_next_hash_partition_index(Relation parent); > #endif /* PARTITION_H */ > diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h > index bdff36a..69e509c 100644 > --- a/src/include/catalog/pg_partitioned_table.h > +++ b/src/include/catalog/pg_partitioned_table.h > @@ -33,6 +33,9 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > char partstrat; /* partitioning strategy */ > int16 partnatts; /* number of partition key columns */ > > + int16 partnparts; > + Oid parthashfunc; > + > /* > * variable-length fields start here, but we allow direct access to > * partattrs via the C struct. That's because the first variable-length > @@ -49,6 +52,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > pg_node_tree partexprs; /* list of expressions in the partition key; > * one item for each zero entry in partattrs[] */ > #endif > + > + > } FormData_pg_partitioned_table; > > /* ---------------- > @@ -62,13 +67,15 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table; > * compiler constants for pg_partitioned_table > * ---------------- > */ > -#define Natts_pg_partitioned_table 7 > +#define Natts_pg_partitioned_table 9 > #define Anum_pg_partitioned_table_partrelid 1 > #define Anum_pg_partitioned_table_partstrat 2 > #define Anum_pg_partitioned_table_partnatts 3 > -#define Anum_pg_partitioned_table_partattrs 4 > -#define Anum_pg_partitioned_table_partclass 5 > -#define Anum_pg_partitioned_table_partcollation 6 > -#define Anum_pg_partitioned_table_partexprs 7 > +#define Anum_pg_partitioned_table_partnparts 4 > +#define Anum_pg_partitioned_table_parthashfunc 5 > +#define Anum_pg_partitioned_table_partattrs 6 > +#define Anum_pg_partitioned_table_partclass 7 > +#define Anum_pg_partitioned_table_partcollation 8 > +#define Anum_pg_partitioned_table_partexprs 9 > > #endif /* PG_PARTITIONED_TABLE_H */ > diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h > index 5afc3eb..1c3474f 100644 > --- a/src/include/nodes/parsenodes.h > +++ b/src/include/nodes/parsenodes.h > @@ -730,11 +730,14 @@ typedef struct PartitionSpec > NodeTag type; > char *strategy; /* partitioning strategy ('list' or 'range') */ > List *partParams; /* List of PartitionElems */ > + int partnparts; > + List *hashfunc; > int location; /* token location, or -1 if unknown */ > } PartitionSpec; > > #define PARTITION_STRATEGY_LIST 'l' > #define PARTITION_STRATEGY_RANGE 'r' > +#define PARTITION_STRATEGY_HASH 'h' > > /* > * PartitionBoundSpec - a partition bound specification > diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h > index 985d650..0597939 100644 > --- a/src/include/parser/kwlist.h > +++ b/src/include/parser/kwlist.h > @@ -180,6 +180,7 @@ PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD) > PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD) > PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD) > PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD) > +PG_KEYWORD("hash", HASH, UNRESERVED_KEYWORD) > PG_KEYWORD("having", HAVING, RESERVED_KEYWORD) > PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD) > PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) > @@ -291,6 +292,7 @@ PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) > PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) > PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) > PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) > +PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD) > PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) > PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) > PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) > diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h > index a617a7c..660adfb 100644 > --- a/src/include/utils/rel.h > +++ b/src/include/utils/rel.h > @@ -62,6 +62,9 @@ typedef struct PartitionKeyData > Oid *partopcintype; /* OIDs of opclass declared input data types */ > FmgrInfo *partsupfunc; /* lookup info for support funcs */ > > + int16 partnparts; /* number of hash partitions */ > + Oid parthashfunc; /* OID of hash function */ > + > /* Partitioning collation per attribute */ > Oid *partcollation; > > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers -- Best regards, Aleksander Alekseev -
Re: [POC] hash partitioning
Amit Langote <langote_amit_f8@lab.ntt.co.jp> — 2017-03-01T02:14:15Z
Nagata-san, On 2017/02/28 23:33, Yugo Nagata wrote: > Hi all, > > Now we have a declarative partitioning, but hash partitioning is not > implemented yet. Attached is a POC patch to add the hash partitioning > feature. I know we will need more discussions about the syntax and other > specifications before going ahead the project, but I think this runnable > code might help to discuss what and how we implement this. Great! > * Description > > In this patch, the hash partitioning implementation is basically based > on the list partitioning mechanism. However, partition bounds cannot be > specified explicitly, but this is used internally as hash partition > index, which is calculated when a partition is created or attached. > > The tentative syntax to create a partitioned table is as bellow; > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > The number of partitions is specified by PARTITIONS, which is currently > constant and cannot be changed, but I think this is needed to be changed in > some manner. A hash function is specified by USING. Maybe, specifying hash > function may be ommitted, and in this case, a default hash function > corresponding to key type will be used. > > A partition table can be create as bellow; > > CREATE TABLE h1 PARTITION OF h; > CREATE TABLE h2 PARTITION OF h; > CREATE TABLE h3 PARTITION OF h; > > FOR VALUES clause cannot be used, and the partition bound is > calclulated automatically as partition index of single integer value. > > When trying create partitions more than the number specified > by PARTITIONS, it gets an error. > > postgres=# create table h4 partition of h; > ERROR: cannot create hash partition more than 3 for h Instead of having to create each partition individually, wouldn't it be better if the following command CREATE TABLE h (i int) PARTITION BY HASH (i) PARTITIONS 3; created the partitions *automatically*? It makes sense to provide a way to create individual list and range partitions separately, because users can specify custom bounds for each. We don't need that for hash partitions, so why make users run separate commands (without the FOR VALUES clause) anyway? We may perhaps need to offer a way to optionally specify a user-defined name for each partition in the same command, along with tablespace, storage options, etc. By default, the names would be generated internally and the user can ALTER individual partitions after the fact to specify tablespace, etc. Thanks, Amit
-
Re: [POC] hash partitioning
Rushabh Lathia <rushabh.lathia@gmail.com> — 2017-03-01T05:00:09Z
On Tue, Feb 28, 2017 at 8:03 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > Hi all, > > Now we have a declarative partitioning, but hash partitioning is not > implemented yet. Attached is a POC patch to add the hash partitioning > feature. I know we will need more discussions about the syntax and other > specifications before going ahead the project, but I think this runnable > code might help to discuss what and how we implement this. > > * Description > > In this patch, the hash partitioning implementation is basically based > on the list partitioning mechanism. However, partition bounds cannot be > specified explicitly, but this is used internally as hash partition > index, which is calculated when a partition is created or attached. > > The tentative syntax to create a partitioned table is as bellow; > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > The number of partitions is specified by PARTITIONS, which is currently > constant and cannot be changed, but I think this is needed to be changed in > some manner. A hash function is specified by USING. Maybe, specifying hash > function may be ommitted, and in this case, a default hash function > corresponding to key type will be used. > > A partition table can be create as bellow; > > CREATE TABLE h1 PARTITION OF h; > CREATE TABLE h2 PARTITION OF h; > CREATE TABLE h3 PARTITION OF h; > > FOR VALUES clause cannot be used, and the partition bound is > calclulated automatically as partition index of single integer value. > > When trying create partitions more than the number specified > by PARTITIONS, it gets an error. > > postgres=# create table h4 partition of h; > ERROR: cannot create hash partition more than 3 for h > > An inserted record is stored in a partition whose index equals > abs(hashfunc(key)) % <number_of_partitions>. In the above > example, this is abs(hashint4(i))%3. > > postgres=# insert into h (select generate_series(0,20)); > INSERT 0 21 > > postgres=# select *,tableoid::regclass from h; > i | tableoid > ----+---------- > 0 | h1 > 1 | h1 > 2 | h1 > 4 | h1 > 8 | h1 > 10 | h1 > 11 | h1 > 14 | h1 > 15 | h1 > 17 | h1 > 20 | h1 > 5 | h2 > 12 | h2 > 13 | h2 > 16 | h2 > 19 | h2 > 3 | h3 > 6 | h3 > 7 | h3 > 9 | h3 > 18 | h3 > (21 rows) > > This is good, I will have closer look into the patch, but here are few quick comments. - CREATE HASH partition syntax adds two new keywords and ideally we should try to avoid adding additional keywords. Also I can see that HASH keyword been added, but I don't see any use of newly added keyword in gram.y. - Also I didn't like the idea of fixing number of partitions during the CREATE TABLE syntax. Thats something that needs to be able to changes. > * Todo / discussions > > In this patch, we cannot change the number of partitions specified > by PARTITIONS. I we can change this, the partitioning rule > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > is also changed and then we need reallocatiing records between > partitions. > > In this patch, user can specify a hash function USING. However, > we migth need default hash functions which are useful and > proper for hash partitioning. > +1 - With fixing default hash function and not specifying number of partitions during CREATE TABLE - don't need two new additional columns into pg_partitioned_table catalog. > Currently, even when we issue SELECT query with a condition, > postgres looks into all partitions regardless of each partition's > constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". > > postgres=# explain select * from h where i = 10; > QUERY PLAN > ---------------------------------------------------------- > Append (cost=0.00..125.62 rows=40 width=4) > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > Filter: (i = 10) > -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) > Filter: (i = 10) > -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) > Filter: (i = 10) > -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) > Filter: (i = 10) > (9 rows) > > However, if we modify a condition into a same expression > as the partitions constraint, postgres can exclude unrelated > table from search targets. So, we might avoid the problem > by converting the qual properly before calling predicate_refuted_by(). > > postgres=# explain select * from h where abs(hashint4(i))%3 = > abs(hashint4(10))%3; > QUERY PLAN > ---------------------------------------------------------- > Append (cost=0.00..61.00 rows=14 width=4) > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > Filter: ((abs(hashint4(i)) % 3) = 2) > -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) > Filter: ((abs(hashint4(i)) % 3) = 2) > (5 rows) > > Best regards, > Yugo Nagata > > -- > Yugo Nagata <nagata@sraoss.co.jp> > > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers > > Regards, Rushabh Lathia
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-03-01T05:22:58Z
On Tue, Feb 28, 2017 at 8:03 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > Hi all, > > Now we have a declarative partitioning, but hash partitioning is not > implemented yet. Attached is a POC patch to add the hash partitioning > feature. I know we will need more discussions about the syntax and other > specifications before going ahead the project, but I think this runnable > code might help to discuss what and how we implement this. > Great. > * Description > > In this patch, the hash partitioning implementation is basically based > on the list partitioning mechanism. However, partition bounds cannot be > specified explicitly, but this is used internally as hash partition > index, which is calculated when a partition is created or attached. > > The tentative syntax to create a partitioned table is as bellow; > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > The number of partitions is specified by PARTITIONS, which is currently > constant and cannot be changed, but I think this is needed to be changed in > some manner. A hash function is specified by USING. Maybe, specifying hash > function may be ommitted, and in this case, a default hash function > corresponding to key type will be used. > > A partition table can be create as bellow; > > CREATE TABLE h1 PARTITION OF h; > CREATE TABLE h2 PARTITION OF h; > CREATE TABLE h3 PARTITION OF h; > > FOR VALUES clause cannot be used, and the partition bound is > calclulated automatically as partition index of single integer value. > > When trying create partitions more than the number specified > by PARTITIONS, it gets an error. > > postgres=# create table h4 partition of h; > ERROR: cannot create hash partition more than 3 for h > > An inserted record is stored in a partition whose index equals > abs(hashfunc(key)) % <number_of_partitions>. In the above > example, this is abs(hashint4(i))%3. > > postgres=# insert into h (select generate_series(0,20)); > INSERT 0 21 > > postgres=# select *,tableoid::regclass from h; > i | tableoid > ----+---------- > 0 | h1 > 1 | h1 > 2 | h1 > 4 | h1 > 8 | h1 > 10 | h1 > 11 | h1 > 14 | h1 > 15 | h1 > 17 | h1 > 20 | h1 > 5 | h2 > 12 | h2 > 13 | h2 > 16 | h2 > 19 | h2 > 3 | h3 > 6 | h3 > 7 | h3 > 9 | h3 > 18 | h3 > (21 rows) > > * Todo / discussions > > In this patch, we cannot change the number of partitions specified > by PARTITIONS. I we can change this, the partitioning rule > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > is also changed and then we need reallocatiing records between > partitions. > > In this patch, user can specify a hash function USING. However, > we migth need default hash functions which are useful and > proper for hash partitioning. > IMHO, we should try to keep create partition syntax simple and aligned with other partition strategy. For e.g: CREATE TABLE h (i int) PARTITION BY HASH(i); I Agree that it is unavoidable partitions number in modulo hashing, but we can do in other hashing technique. Have you had thought about Linear hashing[1] or Consistent hashing[2]? This will allow us to add/drop partition with minimal row moment. +1 for the default hash function corresponding to partitioning key type. Regards, Amul [1] https://en.wikipedia.org/wiki/Linear_hashing [2] https://en.wikipedia.org/wiki/Consistent_hashing
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-01T09:10:10Z
Hi Aleksander, On Tue, 28 Feb 2017 18:05:36 +0300 Aleksander Alekseev <a.alekseev@postgrespro.ru> wrote: > Hi, Yugo. > > Looks like a great feature! I'm going to take a closer look on your code > and write a feedback shortly. For now I can only tell that you forgot > to include some documentation in the patch. Thank you for looking into it. I'm forward to your feedback. This is a proof of concept patch and additional documentation is not included. I'll add this after reaching a consensus on the specification of the feature. > > I've added a corresponding entry to current commitfest [1]. Hope you > don't mind. If it's not too much trouble could you please register on a > commitfest site and add yourself to this entry as an author? I'm pretty > sure someone is using this information for writing release notes or > something like this. Thank you for registering it to the commitfest. I have added me as an auther. > > [1] https://commitfest.postgresql.org/13/1059/ > > On Tue, Feb 28, 2017 at 11:33:13PM +0900, Yugo Nagata wrote: > > Hi all, > > > > Now we have a declarative partitioning, but hash partitioning is not > > implemented yet. Attached is a POC patch to add the hash partitioning > > feature. I know we will need more discussions about the syntax and other > > specifications before going ahead the project, but I think this runnable > > code might help to discuss what and how we implement this. > > > > * Description > > > > In this patch, the hash partitioning implementation is basically based > > on the list partitioning mechanism. However, partition bounds cannot be > > specified explicitly, but this is used internally as hash partition > > index, which is calculated when a partition is created or attached. > > > > The tentative syntax to create a partitioned table is as bellow; > > > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > > > The number of partitions is specified by PARTITIONS, which is currently > > constant and cannot be changed, but I think this is needed to be changed in > > some manner. A hash function is specified by USING. Maybe, specifying hash > > function may be ommitted, and in this case, a default hash function > > corresponding to key type will be used. > > > > A partition table can be create as bellow; > > > > CREATE TABLE h1 PARTITION OF h; > > CREATE TABLE h2 PARTITION OF h; > > CREATE TABLE h3 PARTITION OF h; > > > > FOR VALUES clause cannot be used, and the partition bound is > > calclulated automatically as partition index of single integer value. > > > > When trying create partitions more than the number specified > > by PARTITIONS, it gets an error. > > > > postgres=# create table h4 partition of h; > > ERROR: cannot create hash partition more than 3 for h > > > > An inserted record is stored in a partition whose index equals > > abs(hashfunc(key)) % <number_of_partitions>. In the above > > example, this is abs(hashint4(i))%3. > > > > postgres=# insert into h (select generate_series(0,20)); > > INSERT 0 21 > > > > postgres=# select *,tableoid::regclass from h; > > i | tableoid > > ----+---------- > > 0 | h1 > > 1 | h1 > > 2 | h1 > > 4 | h1 > > 8 | h1 > > 10 | h1 > > 11 | h1 > > 14 | h1 > > 15 | h1 > > 17 | h1 > > 20 | h1 > > 5 | h2 > > 12 | h2 > > 13 | h2 > > 16 | h2 > > 19 | h2 > > 3 | h3 > > 6 | h3 > > 7 | h3 > > 9 | h3 > > 18 | h3 > > (21 rows) > > > > * Todo / discussions > > > > In this patch, we cannot change the number of partitions specified > > by PARTITIONS. I we can change this, the partitioning rule > > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > > is also changed and then we need reallocatiing records between > > partitions. > > > > In this patch, user can specify a hash function USING. However, > > we migth need default hash functions which are useful and > > proper for hash partitioning. > > > > Currently, even when we issue SELECT query with a condition, > > postgres looks into all partitions regardless of each partition's > > constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". > > > > postgres=# explain select * from h where i = 10; > > QUERY PLAN > > ---------------------------------------------------------- > > Append (cost=0.00..125.62 rows=40 width=4) > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > Filter: (i = 10) > > -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) > > Filter: (i = 10) > > -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) > > Filter: (i = 10) > > -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) > > Filter: (i = 10) > > (9 rows) > > > > However, if we modify a condition into a same expression > > as the partitions constraint, postgres can exclude unrelated > > table from search targets. So, we might avoid the problem > > by converting the qual properly before calling predicate_refuted_by(). > > > > postgres=# explain select * from h where abs(hashint4(i))%3 = abs(hashint4(10))%3; > > QUERY PLAN > > ---------------------------------------------------------- > > Append (cost=0.00..61.00 rows=14 width=4) > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > Filter: ((abs(hashint4(i)) % 3) = 2) > > -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) > > Filter: ((abs(hashint4(i)) % 3) = 2) > > (5 rows) > > > > Best regards, > > Yugo Nagata > > > > -- > > Yugo Nagata <nagata@sraoss.co.jp> > > > diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c > > index 41c0056..3820920 100644 > > --- a/src/backend/catalog/heap.c > > +++ b/src/backend/catalog/heap.c > > @@ -3074,7 +3074,7 @@ StorePartitionKey(Relation rel, > > AttrNumber *partattrs, > > List *partexprs, > > Oid *partopclass, > > - Oid *partcollation) > > + Oid *partcollation, int16 partnparts, Oid hashfunc) > > { > > int i; > > int2vector *partattrs_vec; > > @@ -3121,6 +3121,8 @@ StorePartitionKey(Relation rel, > > values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); > > values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy); > > values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts); > > + values[Anum_pg_partitioned_table_partnparts - 1] = Int16GetDatum(partnparts); > > + values[Anum_pg_partitioned_table_parthashfunc - 1] = ObjectIdGetDatum(hashfunc); > > values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec); > > values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec); > > values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec); > > diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c > > index 4bcef58..24e69c6 100644 > > --- a/src/backend/catalog/partition.c > > +++ b/src/backend/catalog/partition.c > > @@ -36,6 +36,8 @@ > > #include "optimizer/clauses.h" > > #include "optimizer/planmain.h" > > #include "optimizer/var.h" > > +#include "parser/parse_func.h" > > +#include "parser/parse_oper.h" > > #include "rewrite/rewriteManip.h" > > #include "storage/lmgr.h" > > #include "utils/array.h" > > @@ -120,6 +122,7 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b, > > > > static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); > > static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); > > +static List *get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec); > > static Oid get_partition_operator(PartitionKey key, int col, > > StrategyNumber strategy, bool *need_relabel); > > static List *generate_partition_qual(Relation rel); > > @@ -236,7 +239,8 @@ RelationBuildPartitionDesc(Relation rel) > > oids[i++] = lfirst_oid(cell); > > > > /* Convert from node to the internal representation */ > > - if (key->strategy == PARTITION_STRATEGY_LIST) > > + if (key->strategy == PARTITION_STRATEGY_LIST || > > + key->strategy == PARTITION_STRATEGY_HASH) > > { > > List *non_null_values = NIL; > > > > @@ -251,7 +255,7 @@ RelationBuildPartitionDesc(Relation rel) > > ListCell *c; > > PartitionBoundSpec *spec = lfirst(cell); > > > > - if (spec->strategy != PARTITION_STRATEGY_LIST) > > + if (spec->strategy != key->strategy) > > elog(ERROR, "invalid strategy in partition bound spec"); > > > > foreach(c, spec->listdatums) > > @@ -464,6 +468,7 @@ RelationBuildPartitionDesc(Relation rel) > > switch (key->strategy) > > { > > case PARTITION_STRATEGY_LIST: > > + case PARTITION_STRATEGY_HASH: > > { > > boundinfo->has_null = found_null; > > boundinfo->indexes = (int *) palloc(ndatums * sizeof(int)); > > @@ -829,6 +834,18 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) > > break; > > } > > > > + case PARTITION_STRATEGY_HASH: > > + { > > + Assert(spec->strategy == PARTITION_STRATEGY_HASH); > > + > > + if (partdesc->nparts + 1 > key->partnparts) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), > > + errmsg("cannot create hash partition more than %d for %s", > > + key->partnparts, RelationGetRelationName(parent)))); > > + break; > > + } > > + > > default: > > elog(ERROR, "unexpected partition strategy: %d", > > (int) key->strategy); > > @@ -916,6 +933,11 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) > > my_qual = get_qual_for_range(key, spec); > > break; > > > > + case PARTITION_STRATEGY_HASH: > > + Assert(spec->strategy == PARTITION_STRATEGY_LIST); > > + my_qual = get_qual_for_hash(key, spec); > > + break; > > + > > default: > > elog(ERROR, "unexpected partition strategy: %d", > > (int) key->strategy); > > @@ -1146,6 +1168,84 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode, > > return pd; > > } > > > > +/* > > + * convert_expr_for_hash > > + * > > + * Converts a expr for a hash partition's constraint. > > + * expr is converted into 'abs(hashfunc(expr)) % npart". > > + * > > + * npart: number of partitions > > + * hashfunc: OID of hash function > > + */ > > +Expr * > > +convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc) > > +{ > > + FuncExpr *func, > > + *abs; > > + Expr *modexpr; > > + Oid modoid; > > + Oid int4oid[1] = {INT4OID}; > > + > > + ParseState *pstate = make_parsestate(NULL); > > + Value *val_npart = makeInteger(npart); > > + Node *const_npart = (Node *) make_const(pstate, val_npart, -1); > > + > > + /* hash function */ > > + func = makeFuncExpr(hashfunc, > > + INT4OID, > > + list_make1(expr), > > + 0, > > + 0, > > + COERCE_EXPLICIT_CALL); > > + > > + /* Abs */ > > + abs = makeFuncExpr(LookupFuncName(list_make1(makeString("abs")), 1, int4oid, false), > > + INT4OID, > > + list_make1(func), > > + 0, > > + 0, > > + COERCE_EXPLICIT_CALL); > > + > > + /* modulo by npart */ > > + modoid = LookupOperName(pstate, list_make1(makeString("%")), INT4OID, INT4OID, false, -1); > > + modexpr = make_opclause(modoid, INT4OID, false, (Expr*)abs, (Expr*)const_npart, 0, 0); > > + > > + return modexpr; > > +} > > + > > + > > +/* > > + * get_next_hash_partition_index > > + * > > + * Returns the minimal index which is not used for hash partition. > > + */ > > +int > > +get_next_hash_partition_index(Relation parent) > > +{ > > + PartitionKey key = RelationGetPartitionKey(parent); > > + PartitionDesc partdesc = RelationGetPartitionDesc(parent); > > + > > + int i; > > + bool *used = palloc0(sizeof(int) * key->partnparts); > > + > > + /* mark used for existing partition indexs */ > > + for (i = 0; i < partdesc->boundinfo->ndatums; i++) > > + { > > + Datum* datum = partdesc->boundinfo->datums[i]; > > + int idx = DatumGetInt16(datum[0]); > > + > > + if (!used[idx]) > > + used[idx] = true; > > + } > > + > > + /* find the minimal unused index */ > > + for (i = 0; i < key->partnparts; i++) > > + if (!used[i]) > > + break; > > + > > + return i; > > +} > > + > > /* Module-local functions */ > > > > /* > > @@ -1467,6 +1567,43 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) > > } > > > > /* > > + * get_qual_for_hash > > + * > > + * Returns a list of expressions to use as a hash partition's constraint. > > + */ > > +static List * > > +get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec) > > +{ > > + List *result; > > + Expr *keyCol; > > + Expr *expr; > > + Expr *opexpr; > > + Oid operoid; > > + ParseState *pstate = make_parsestate(NULL); > > + > > + /* Left operand */ > > + if (key->partattrs[0] != 0) > > + keyCol = (Expr *) makeVar(1, > > + key->partattrs[0], > > + key->parttypid[0], > > + key->parttypmod[0], > > + key->parttypcoll[0], > > + 0); > > + else > > + keyCol = (Expr *) copyObject(linitial(key->partexprs)); > > + > > + expr = convert_expr_for_hash(keyCol, key->partnparts, key->parthashfunc); > > + > > + /* equals the listdaums value */ > > + operoid = LookupOperName(pstate, list_make1(makeString("=")), INT4OID, INT4OID, false, -1); > > + opexpr = make_opclause(operoid, BOOLOID, false, expr, linitial(spec->listdatums), 0, 0); > > + > > + result = list_make1(opexpr); > > + > > + return result; > > +} > > + > > +/* > > * get_partition_operator > > * > > * Return oid of the operator of given strategy for a given partition key > > @@ -1730,6 +1867,11 @@ get_partition_for_tuple(PartitionDispatch *pd, > > (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), > > errmsg("range partition key of row contains null"))); > > } > > + else if (key->strategy == PARTITION_STRATEGY_HASH) > > + { > > + values[0] = OidFunctionCall1(key->parthashfunc, values[0]); > > + values[0] = Int16GetDatum(Abs(DatumGetInt16(values[0])) % key->partnparts); > > + } > > > > if (partdesc->boundinfo->has_null && isnull[0]) > > /* Tuple maps to the null-accepting list partition */ > > @@ -1744,6 +1886,7 @@ get_partition_for_tuple(PartitionDispatch *pd, > > switch (key->strategy) > > { > > case PARTITION_STRATEGY_LIST: > > + case PARTITION_STRATEGY_HASH: > > if (cur_offset >= 0 && equal) > > cur_index = partdesc->boundinfo->indexes[cur_offset]; > > else > > @@ -1968,6 +2111,7 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo, > > switch (key->strategy) > > { > > case PARTITION_STRATEGY_LIST: > > + case PARTITION_STRATEGY_HASH: > > cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], > > key->partcollation[0], > > bound_datums[0], > > diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c > > index 3cea220..5a28cc0 100644 > > --- a/src/backend/commands/tablecmds.c > > +++ b/src/backend/commands/tablecmds.c > > @@ -41,6 +41,7 @@ > > #include "catalog/pg_inherits_fn.h" > > #include "catalog/pg_namespace.h" > > #include "catalog/pg_opclass.h" > > +#include "catalog/pg_proc.h" > > #include "catalog/pg_tablespace.h" > > #include "catalog/pg_trigger.h" > > #include "catalog/pg_type.h" > > @@ -77,6 +78,7 @@ > > #include "parser/parse_oper.h" > > #include "parser/parse_relation.h" > > #include "parser/parse_type.h" > > +#include "parser/parse_func.h" > > #include "parser/parse_utilcmd.h" > > #include "parser/parser.h" > > #include "pgstat.h" > > @@ -450,7 +452,7 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, > > Oid oldrelid, void *arg); > > static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr); > > static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy); > > -static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > +static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > > List **partexprs, Oid *partopclass, Oid *partcollation); > > static void CreateInheritance(Relation child_rel, Relation parent_rel); > > static void RemoveInheritance(Relation child_rel, Relation parent_rel); > > @@ -799,8 +801,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > > AttrNumber partattrs[PARTITION_MAX_KEYS]; > > Oid partopclass[PARTITION_MAX_KEYS]; > > Oid partcollation[PARTITION_MAX_KEYS]; > > + Oid partatttypes[PARTITION_MAX_KEYS]; > > List *partexprs = NIL; > > List *cmds = NIL; > > + Oid hashfuncOid = InvalidOid; > > > > /* > > * We need to transform the raw parsetrees corresponding to partition > > @@ -811,15 +815,40 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > > stmt->partspec = transformPartitionSpec(rel, stmt->partspec, > > &strategy); > > ComputePartitionAttrs(rel, stmt->partspec->partParams, > > - partattrs, &partexprs, partopclass, > > + partattrs, partatttypes, &partexprs, partopclass, > > partcollation); > > > > partnatts = list_length(stmt->partspec->partParams); > > + > > + if (strategy == PARTITION_STRATEGY_HASH) > > + { > > + Oid funcrettype; > > + > > + if (partnatts != 1) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("number of partition key must be 1 for hash partition"))); > > + > > + hashfuncOid = LookupFuncName(stmt->partspec->hashfunc, 1, partatttypes, false); > > + funcrettype = get_func_rettype(hashfuncOid); > > + if (funcrettype != INT4OID) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("hash function for partitioning must return integer"))); > > + > > + if (func_volatile(hashfuncOid) != PROVOLATILE_IMMUTABLE) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("hash function for partitioning must be marked IMMUTABLE"))); > > + > > + } > > + > > StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs, > > - partopclass, partcollation); > > + partopclass, partcollation, stmt->partspec->partnparts, hashfuncOid); > > > > - /* Force key columns to be NOT NULL when using range partitioning */ > > - if (strategy == PARTITION_STRATEGY_RANGE) > > + /* Force key columns to be NOT NULL when using range or hash partitioning */ > > + if (strategy == PARTITION_STRATEGY_RANGE || > > + strategy == PARTITION_STRATEGY_HASH) > > { > > for (i = 0; i < partnatts; i++) > > { > > @@ -12783,18 +12812,51 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > > newspec->strategy = partspec->strategy; > > newspec->location = partspec->location; > > newspec->partParams = NIL; > > + newspec->partnparts = partspec->partnparts; > > + newspec->hashfunc = partspec->hashfunc; > > > > /* Parse partitioning strategy name */ > > if (!pg_strcasecmp(partspec->strategy, "list")) > > *strategy = PARTITION_STRATEGY_LIST; > > else if (!pg_strcasecmp(partspec->strategy, "range")) > > *strategy = PARTITION_STRATEGY_RANGE; > > + else if (!pg_strcasecmp(partspec->strategy, "hash")) > > + *strategy = PARTITION_STRATEGY_HASH; > > else > > ereport(ERROR, > > (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > errmsg("unrecognized partitioning strategy \"%s\"", > > partspec->strategy))); > > > > + if (*strategy == PARTITION_STRATEGY_HASH) > > + { > > + if (partspec->partnparts < 0) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("number of partitions must be specified for hash partition"))); > > + else if (partspec->partnparts == 0) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("number of partitions must be greater than 0"))); > > + > > + if (list_length(partspec->hashfunc) == 0) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("hash function must be specified for hash partition"))); > > + } > > + else > > + { > > + if (partspec->partnparts >= 0) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("number of partitions can be specified only for hash partition"))); > > + > > + if (list_length(partspec->hashfunc) > 0) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > + errmsg("hash function can be specified only for hash partition"))); > > + } > > + > > /* > > * Create a dummy ParseState and insert the target relation as its sole > > * rangetable entry. We need a ParseState for transformExpr. > > @@ -12843,7 +12905,7 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > > * Compute per-partition-column information from a list of PartitionElem's > > */ > > static void > > -ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > +ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > > List **partexprs, Oid *partopclass, Oid *partcollation) > > { > > int attn; > > @@ -13010,6 +13072,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > "btree", > > BTREE_AM_OID); > > > > + partatttypes[attn] = atttype; > > attn++; > > } > > } > > diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c > > index 05d8538..f4febc9 100644 > > --- a/src/backend/nodes/copyfuncs.c > > +++ b/src/backend/nodes/copyfuncs.c > > @@ -4232,6 +4232,8 @@ _copyPartitionSpec(const PartitionSpec *from) > > > > COPY_STRING_FIELD(strategy); > > COPY_NODE_FIELD(partParams); > > + COPY_SCALAR_FIELD(partnparts); > > + COPY_NODE_FIELD(hashfunc); > > COPY_LOCATION_FIELD(location); > > > > return newnode; > > diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c > > index d595cd7..d589eac 100644 > > --- a/src/backend/nodes/equalfuncs.c > > +++ b/src/backend/nodes/equalfuncs.c > > @@ -2725,6 +2725,8 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b) > > { > > COMPARE_STRING_FIELD(strategy); > > COMPARE_NODE_FIELD(partParams); > > + COMPARE_SCALAR_FIELD(partnparts); > > + COMPARE_NODE_FIELD(hashfunc); > > COMPARE_LOCATION_FIELD(location); > > > > return true; > > diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c > > index b3802b4..d6db80e 100644 > > --- a/src/backend/nodes/outfuncs.c > > +++ b/src/backend/nodes/outfuncs.c > > @@ -3318,6 +3318,8 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node) > > > > WRITE_STRING_FIELD(strategy); > > WRITE_NODE_FIELD(partParams); > > + WRITE_INT_FIELD(partnparts); > > + WRITE_NODE_FIELD(hashfunc); > > WRITE_LOCATION_FIELD(location); > > } > > > > diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y > > index e833b2e..b67140d 100644 > > --- a/src/backend/parser/gram.y > > +++ b/src/backend/parser/gram.y > > @@ -574,6 +574,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > %type <list> partbound_datum_list > > %type <partrange_datum> PartitionRangeDatum > > %type <list> range_datum_list > > +%type <ival> hash_partitions > > +%type <list> hash_function > > > > /* > > * Non-keyword token types. These are hard-wired into the "flex" lexer. > > @@ -627,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > > GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING > > > > - HANDLER HAVING HEADER_P HOLD HOUR_P > > + HANDLER HASH HAVING HEADER_P HOLD HOUR_P > > > > IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P > > INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P > > @@ -651,7 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR > > ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER > > > > - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY > > + PARALLEL PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PLACING PLANS POLICY > > POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY > > PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM PUBLICATION > > > > @@ -2587,6 +2589,16 @@ ForValues: > > > > $$ = (Node *) n; > > } > > + > > + /* a HASH partition */ > > + | /*EMPTY*/ > > + { > > + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); > > + > > + n->strategy = PARTITION_STRATEGY_HASH; > > + > > + $$ = (Node *) n; > > + } > > ; > > > > partbound_datum: > > @@ -3666,7 +3678,7 @@ OptPartitionSpec: PartitionSpec { $$ = $1; } > > | /*EMPTY*/ { $$ = NULL; } > > ; > > > > -PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > > +PartitionSpec: PARTITION BY part_strategy '(' part_params ')' hash_partitions hash_function > > { > > PartitionSpec *n = makeNode(PartitionSpec); > > > > @@ -3674,10 +3686,21 @@ PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > > n->partParams = $5; > > n->location = @1; > > > > + n->partnparts = $7; > > + n->hashfunc = $8; > > + > > $$ = n; > > } > > ; > > > > +hash_partitions: PARTITIONS Iconst { $$ = $2; } > > + | /*EMPTY*/ { $$ = -1; } > > + ; > > + > > +hash_function: USING handler_name { $$ = $2; } > > + | /*EMPTY*/ { $$ = NULL; } > > + ; > > + > > part_strategy: IDENT { $$ = $1; } > > | unreserved_keyword { $$ = pstrdup($1); } > > ; > > @@ -14377,6 +14400,7 @@ unreserved_keyword: > > | GLOBAL > > | GRANTED > > | HANDLER > > + | HASH > > | HEADER_P > > | HOLD > > | HOUR_P > > @@ -14448,6 +14472,7 @@ unreserved_keyword: > > | PARSER > > | PARTIAL > > | PARTITION > > + | PARTITIONS > > | PASSING > > | PASSWORD > > | PLANS > > diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c > > index ff2bab6..8e1be31 100644 > > --- a/src/backend/parser/parse_utilcmd.c > > +++ b/src/backend/parser/parse_utilcmd.c > > @@ -40,6 +40,7 @@ > > #include "catalog/pg_opclass.h" > > #include "catalog/pg_operator.h" > > #include "catalog/pg_type.h" > > +#include "catalog/partition.h" > > #include "commands/comment.h" > > #include "commands/defrem.h" > > #include "commands/tablecmds.h" > > @@ -3252,6 +3253,24 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound) > > ++i; > > } > > } > > + else if (strategy == PARTITION_STRATEGY_HASH) > > + { > > + Value *conval; > > + Node *value; > > + int index; > > + > > + if (spec->strategy != PARTITION_STRATEGY_HASH) > > + ereport(ERROR, > > + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), > > + errmsg("invalid bound specification for a hash partition"))); > > + > > + index = get_next_hash_partition_index(parent); > > + > > + /* store the partition index as a listdatums value */ > > + conval = makeInteger(index); > > + value = (Node *) make_const(pstate, conval, -1); > > + result_spec->listdatums = list_make1(value); > > + } > > else > > elog(ERROR, "unexpected partition strategy: %d", (int) strategy); > > > > diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c > > index b27b77d..fab6eea 100644 > > --- a/src/backend/utils/adt/ruleutils.c > > +++ b/src/backend/utils/adt/ruleutils.c > > @@ -1423,7 +1423,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, > > * > > * Returns the partition key specification, ie, the following: > > * > > - * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) > > + * PARTITION BY { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...]) > > */ > > Datum > > pg_get_partkeydef(PG_FUNCTION_ARGS) > > @@ -1513,6 +1513,9 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags) > > case PARTITION_STRATEGY_RANGE: > > appendStringInfo(&buf, "RANGE"); > > break; > > + case PARTITION_STRATEGY_HASH: > > + appendStringInfo(&buf, "HASH"); > > + break; > > default: > > elog(ERROR, "unexpected partition strategy: %d", > > (int) form->partstrat); > > @@ -8520,6 +8523,9 @@ get_rule_expr(Node *node, deparse_context *context, > > appendStringInfoString(buf, ")"); > > break; > > > > + case PARTITION_STRATEGY_HASH: > > + break; > > + > > default: > > elog(ERROR, "unrecognized partition strategy: %d", > > (int) spec->strategy); > > diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c > > index 9001e20..829e4d2 100644 > > --- a/src/backend/utils/cache/relcache.c > > +++ b/src/backend/utils/cache/relcache.c > > @@ -855,6 +855,9 @@ RelationBuildPartitionKey(Relation relation) > > key->strategy = form->partstrat; > > key->partnatts = form->partnatts; > > > > + key->partnparts = form->partnparts; > > + key->parthashfunc = form->parthashfunc; > > + > > /* > > * We can rely on the first variable-length attribute being mapped to the > > * relevant field of the catalog's C struct, because all previous > > @@ -999,6 +1002,9 @@ copy_partition_key(PartitionKey fromkey) > > newkey->strategy = fromkey->strategy; > > newkey->partnatts = n = fromkey->partnatts; > > > > + newkey->partnparts = fromkey->partnparts; > > + newkey->parthashfunc = fromkey->parthashfunc; > > + > > newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber)); > > memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber)); > > > > diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h > > index 1187797..367e2f8 100644 > > --- a/src/include/catalog/heap.h > > +++ b/src/include/catalog/heap.h > > @@ -141,7 +141,7 @@ extern void StorePartitionKey(Relation rel, > > AttrNumber *partattrs, > > List *partexprs, > > Oid *partopclass, > > - Oid *partcollation); > > + Oid *partcollation, int16 partnparts, Oid hashfunc); > > extern void RemovePartitionKeyByRelId(Oid relid); > > extern void StorePartitionBound(Relation rel, Relation parent, Node *bound); > > > > diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h > > index b195d1a..80f4b0e 100644 > > --- a/src/include/catalog/partition.h > > +++ b/src/include/catalog/partition.h > > @@ -89,4 +89,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd, > > TupleTableSlot *slot, > > EState *estate, > > Oid *failed_at); > > +extern Expr *convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc); > > +extern int get_next_hash_partition_index(Relation parent); > > #endif /* PARTITION_H */ > > diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h > > index bdff36a..69e509c 100644 > > --- a/src/include/catalog/pg_partitioned_table.h > > +++ b/src/include/catalog/pg_partitioned_table.h > > @@ -33,6 +33,9 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > > char partstrat; /* partitioning strategy */ > > int16 partnatts; /* number of partition key columns */ > > > > + int16 partnparts; > > + Oid parthashfunc; > > + > > /* > > * variable-length fields start here, but we allow direct access to > > * partattrs via the C struct. That's because the first variable-length > > @@ -49,6 +52,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > > pg_node_tree partexprs; /* list of expressions in the partition key; > > * one item for each zero entry in partattrs[] */ > > #endif > > + > > + > > } FormData_pg_partitioned_table; > > > > /* ---------------- > > @@ -62,13 +67,15 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table; > > * compiler constants for pg_partitioned_table > > * ---------------- > > */ > > -#define Natts_pg_partitioned_table 7 > > +#define Natts_pg_partitioned_table 9 > > #define Anum_pg_partitioned_table_partrelid 1 > > #define Anum_pg_partitioned_table_partstrat 2 > > #define Anum_pg_partitioned_table_partnatts 3 > > -#define Anum_pg_partitioned_table_partattrs 4 > > -#define Anum_pg_partitioned_table_partclass 5 > > -#define Anum_pg_partitioned_table_partcollation 6 > > -#define Anum_pg_partitioned_table_partexprs 7 > > +#define Anum_pg_partitioned_table_partnparts 4 > > +#define Anum_pg_partitioned_table_parthashfunc 5 > > +#define Anum_pg_partitioned_table_partattrs 6 > > +#define Anum_pg_partitioned_table_partclass 7 > > +#define Anum_pg_partitioned_table_partcollation 8 > > +#define Anum_pg_partitioned_table_partexprs 9 > > > > #endif /* PG_PARTITIONED_TABLE_H */ > > diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h > > index 5afc3eb..1c3474f 100644 > > --- a/src/include/nodes/parsenodes.h > > +++ b/src/include/nodes/parsenodes.h > > @@ -730,11 +730,14 @@ typedef struct PartitionSpec > > NodeTag type; > > char *strategy; /* partitioning strategy ('list' or 'range') */ > > List *partParams; /* List of PartitionElems */ > > + int partnparts; > > + List *hashfunc; > > int location; /* token location, or -1 if unknown */ > > } PartitionSpec; > > > > #define PARTITION_STRATEGY_LIST 'l' > > #define PARTITION_STRATEGY_RANGE 'r' > > +#define PARTITION_STRATEGY_HASH 'h' > > > > /* > > * PartitionBoundSpec - a partition bound specification > > diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h > > index 985d650..0597939 100644 > > --- a/src/include/parser/kwlist.h > > +++ b/src/include/parser/kwlist.h > > @@ -180,6 +180,7 @@ PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD) > > PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD) > > PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD) > > PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD) > > +PG_KEYWORD("hash", HASH, UNRESERVED_KEYWORD) > > PG_KEYWORD("having", HAVING, RESERVED_KEYWORD) > > PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD) > > PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) > > @@ -291,6 +292,7 @@ PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) > > PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) > > PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) > > PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) > > +PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD) > > PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) > > PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) > > PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) > > diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h > > index a617a7c..660adfb 100644 > > --- a/src/include/utils/rel.h > > +++ b/src/include/utils/rel.h > > @@ -62,6 +62,9 @@ typedef struct PartitionKeyData > > Oid *partopcintype; /* OIDs of opclass declared input data types */ > > FmgrInfo *partsupfunc; /* lookup info for support funcs */ > > > > + int16 partnparts; /* number of hash partitions */ > > + Oid parthashfunc; /* OID of hash function */ > > + > > /* Partitioning collation per attribute */ > > Oid *partcollation; > > > > > > > -- > > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > > To make changes to your subscription: > > http://www.postgresql.org/mailpref/pgsql-hackers > > > -- > Best regards, > Aleksander Alekseev -- Yugo Nagata <nagata@sraoss.co.jp> -
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-01T09:45:38Z
Hi Ammit, On Wed, 1 Mar 2017 11:14:15 +0900 Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote: > Nagata-san, > > On 2017/02/28 23:33, Yugo Nagata wrote: > > Hi all, > > > > Now we have a declarative partitioning, but hash partitioning is not > > implemented yet. Attached is a POC patch to add the hash partitioning > > feature. I know we will need more discussions about the syntax and other > > specifications before going ahead the project, but I think this runnable > > code might help to discuss what and how we implement this. > > Great! Thank you! > > > * Description > > > > In this patch, the hash partitioning implementation is basically based > > on the list partitioning mechanism. However, partition bounds cannot be > > specified explicitly, but this is used internally as hash partition > > index, which is calculated when a partition is created or attached. > > > > The tentative syntax to create a partitioned table is as bellow; > > > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > > > The number of partitions is specified by PARTITIONS, which is currently > > constant and cannot be changed, but I think this is needed to be changed in > > some manner. A hash function is specified by USING. Maybe, specifying hash > > function may be ommitted, and in this case, a default hash function > > corresponding to key type will be used. > > > > A partition table can be create as bellow; > > > > CREATE TABLE h1 PARTITION OF h; > > CREATE TABLE h2 PARTITION OF h; > > CREATE TABLE h3 PARTITION OF h; > > > > FOR VALUES clause cannot be used, and the partition bound is > > calclulated automatically as partition index of single integer value. > > > > When trying create partitions more than the number specified > > by PARTITIONS, it gets an error. > > > > postgres=# create table h4 partition of h; > > ERROR: cannot create hash partition more than 3 for h > > Instead of having to create each partition individually, wouldn't it be > better if the following command > > CREATE TABLE h (i int) PARTITION BY HASH (i) PARTITIONS 3; > > created the partitions *automatically*? > > It makes sense to provide a way to create individual list and range > partitions separately, because users can specify custom bounds for each. > We don't need that for hash partitions, so why make users run separate > commands (without the FOR VALUES clause) anyway? We may perhaps need to > offer a way to optionally specify a user-defined name for each partition > in the same command, along with tablespace, storage options, etc. By > default, the names would be generated internally and the user can ALTER > individual partitions after the fact to specify tablespace, etc. I though that creating each partition individually is needed because some user will want to specify a tablespce to each partition. However, as you say, that isn't need for many cases because use can move a partition to other tablespaces afterward by ALTER. Thanks, Yugo Nagata > > Thanks, > Amit > > > > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-01T10:07:30Z
On Wed, 1 Mar 2017 10:30:09 +0530 Rushabh Lathia <rushabh.lathia@gmail.com> wrote: > On Tue, Feb 28, 2017 at 8:03 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > > > Hi all, > > > > Now we have a declarative partitioning, but hash partitioning is not > > implemented yet. Attached is a POC patch to add the hash partitioning > > feature. I know we will need more discussions about the syntax and other > > specifications before going ahead the project, but I think this runnable > > code might help to discuss what and how we implement this. > > > > * Description > > > > In this patch, the hash partitioning implementation is basically based > > on the list partitioning mechanism. However, partition bounds cannot be > > specified explicitly, but this is used internally as hash partition > > index, which is calculated when a partition is created or attached. > > > > The tentative syntax to create a partitioned table is as bellow; > > > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > > > The number of partitions is specified by PARTITIONS, which is currently > > constant and cannot be changed, but I think this is needed to be changed in > > some manner. A hash function is specified by USING. Maybe, specifying hash > > function may be ommitted, and in this case, a default hash function > > corresponding to key type will be used. > > > > A partition table can be create as bellow; > > > > CREATE TABLE h1 PARTITION OF h; > > CREATE TABLE h2 PARTITION OF h; > > CREATE TABLE h3 PARTITION OF h; > > > > FOR VALUES clause cannot be used, and the partition bound is > > calclulated automatically as partition index of single integer value. > > > > When trying create partitions more than the number specified > > by PARTITIONS, it gets an error. > > > > postgres=# create table h4 partition of h; > > ERROR: cannot create hash partition more than 3 for h > > > > An inserted record is stored in a partition whose index equals > > abs(hashfunc(key)) % <number_of_partitions>. In the above > > example, this is abs(hashint4(i))%3. > > > > postgres=# insert into h (select generate_series(0,20)); > > INSERT 0 21 > > > > postgres=# select *,tableoid::regclass from h; > > i | tableoid > > ----+---------- > > 0 | h1 > > 1 | h1 > > 2 | h1 > > 4 | h1 > > 8 | h1 > > 10 | h1 > > 11 | h1 > > 14 | h1 > > 15 | h1 > > 17 | h1 > > 20 | h1 > > 5 | h2 > > 12 | h2 > > 13 | h2 > > 16 | h2 > > 19 | h2 > > 3 | h3 > > 6 | h3 > > 7 | h3 > > 9 | h3 > > 18 | h3 > > (21 rows) > > > > > This is good, I will have closer look into the patch, but here are > few quick comments. Thanks. I'm looking forward to your comments. > > - CREATE HASH partition syntax adds two new keywords and ideally > we should try to avoid adding additional keywords. Also I can see that > HASH keyword been added, but I don't see any use of newly added > keyword in gram.y. Yes, you are right. HASH keyword is not necessary. I'll remove it from the patch. > > - Also I didn't like the idea of fixing number of partitions during the > CREATE > TABLE syntax. Thats something that needs to be able to changes. I agree. The number specified by PARTIONS should be the *initial* number of partitions and this should be abelt to be changed. I'm investigating the way. > > > > > * Todo / discussions > > > > In this patch, we cannot change the number of partitions specified > > by PARTITIONS. I we can change this, the partitioning rule > > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > > is also changed and then we need reallocatiing records between > > partitions. > > > > In this patch, user can specify a hash function USING. However, > > we migth need default hash functions which are useful and > > proper for hash partitioning. > > > > +1 > > - With fixing default hash function and not specifying number of partitions > during CREATE TABLE - don't need two new additional columns into > pg_partitioned_table catalog. I think the option to specify a hash function is needed because user may want to use a user-defined hash function for some reasons, for example, when a user-defined type is used as a partition key. > > > > Currently, even when we issue SELECT query with a condition, > > postgres looks into all partitions regardless of each partition's > > constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". > > > > postgres=# explain select * from h where i = 10; > > QUERY PLAN > > ---------------------------------------------------------- > > Append (cost=0.00..125.62 rows=40 width=4) > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > Filter: (i = 10) > > -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) > > Filter: (i = 10) > > -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) > > Filter: (i = 10) > > -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) > > Filter: (i = 10) > > (9 rows) > > > > However, if we modify a condition into a same expression > > as the partitions constraint, postgres can exclude unrelated > > table from search targets. So, we might avoid the problem > > by converting the qual properly before calling predicate_refuted_by(). > > > > postgres=# explain select * from h where abs(hashint4(i))%3 = > > abs(hashint4(10))%3; > > QUERY PLAN > > ---------------------------------------------------------- > > Append (cost=0.00..61.00 rows=14 width=4) > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > Filter: ((abs(hashint4(i)) % 3) = 2) > > -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) > > Filter: ((abs(hashint4(i)) % 3) = 2) > > (5 rows) > > > > Best regards, > > Yugo Nagata > > > > -- > > Yugo Nagata <nagata@sraoss.co.jp> > > > > > > -- > > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > > To make changes to your subscription: > > http://www.postgresql.org/mailpref/pgsql-hackers > > > > > > > Regards, > > Rushabh Lathia -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-01T10:20:25Z
On Wed, 1 Mar 2017 10:52:58 +0530 amul sul <sulamul@gmail.com> wrote: > On Tue, Feb 28, 2017 at 8:03 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > > Hi all, > > > > Now we have a declarative partitioning, but hash partitioning is not > > implemented yet. Attached is a POC patch to add the hash partitioning > > feature. I know we will need more discussions about the syntax and other > > specifications before going ahead the project, but I think this runnable > > code might help to discuss what and how we implement this. > > > > Great. Thanks. > > > * Description > > > > In this patch, the hash partitioning implementation is basically based > > on the list partitioning mechanism. However, partition bounds cannot be > > specified explicitly, but this is used internally as hash partition > > index, which is calculated when a partition is created or attached. > > > > The tentative syntax to create a partitioned table is as bellow; > > > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > > > The number of partitions is specified by PARTITIONS, which is currently > > constant and cannot be changed, but I think this is needed to be changed > in > > some manner. A hash function is specified by USING. Maybe, specifying hash > > function may be ommitted, and in this case, a default hash function > > corresponding to key type will be used. > > > > A partition table can be create as bellow; > > > > CREATE TABLE h1 PARTITION OF h; > > CREATE TABLE h2 PARTITION OF h; > > CREATE TABLE h3 PARTITION OF h; > > > > FOR VALUES clause cannot be used, and the partition bound is > > calclulated automatically as partition index of single integer value. > > > > When trying create partitions more than the number specified > > by PARTITIONS, it gets an error. > > > > postgres=# create table h4 partition of h; > > ERROR: cannot create hash partition more than 3 for h > > > > An inserted record is stored in a partition whose index equals > > abs(hashfunc(key)) % <number_of_partitions>. In the above > > example, this is abs(hashint4(i))%3. > > > > postgres=# insert into h (select generate_series(0,20)); > > INSERT 0 21 > > > > postgres=# select *,tableoid::regclass from h; > > i | tableoid > > ----+---------- > > 0 | h1 > > 1 | h1 > > 2 | h1 > > 4 | h1 > > 8 | h1 > > 10 | h1 > > 11 | h1 > > 14 | h1 > > 15 | h1 > > 17 | h1 > > 20 | h1 > > 5 | h2 > > 12 | h2 > > 13 | h2 > > 16 | h2 > > 19 | h2 > > 3 | h3 > > 6 | h3 > > 7 | h3 > > 9 | h3 > > 18 | h3 > > (21 rows) > > > > * Todo / discussions > > > > In this patch, we cannot change the number of partitions specified > > by PARTITIONS. I we can change this, the partitioning rule > > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > > is also changed and then we need reallocatiing records between > > partitions. > > > > In this patch, user can specify a hash function USING. However, > > we migth need default hash functions which are useful and > > proper for hash partitioning. > > > IMHO, we should try to keep create partition syntax simple and aligned > with other partition strategy. For e.g: > CREATE TABLE h (i int) PARTITION BY HASH(i); > > I Agree that it is unavoidable partitions number in modulo hashing, > but we can do in other hashing technique. Have you had thought about > Linear hashing[1] or Consistent hashing[2]? This will allow us to > add/drop > partition with minimal row moment. Thank you for your information of hash technique. I'll see them and try to allowing the number of partitions to be changed. Thanks, Yugo Nagata > > +1 for the default hash function corresponding to partitioning key type. > > Regards, > Amul > > > [1] https://en.wikipedia.org/wiki/Linear_hashing > [2] https://en.wikipedia.org/wiki/Consistent_hashing -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Aleksander Alekseev <a.alekseev@postgrespro.ru> — 2017-03-01T14:08:49Z
Hi, Yugo. Today I've had an opportunity to take a closer look on this patch. Here are a few things that bother me. 1a) There are missing commends here: ``` --- a/src/include/catalog/pg_partitioned_table.h +++ b/src/include/catalog/pg_partitioned_table.h @@ -33,6 +33,9 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS char partstrat; /* partitioning strategy */ int16 partnatts; /* number of partition key columns */ + int16 partnparts; + Oid parthashfunc; + ``` 1b) ... and here: ``` --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -730,11 +730,14 @@ typedef struct PartitionSpec NodeTag type; char *strategy; /* partitioning strategy ('list' or 'range') */ List *partParams; /* List of PartitionElems */ + int partnparts; + List *hashfunc; int location; /* token location, or -1 if unknown */ } PartitionSpec; ``` 2) I believe new empty lines in patches are generally not welcomed by community: ``` @@ -49,6 +52,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS pg_node_tree partexprs; /* list of expressions in the partition key; * one item for each zero entry in partattrs[] */ #endif + + } FormData_pg_partitioned_table; ``` 3) One test fails on my laptop (Arch Linux, x64) [1]: ``` *************** *** 344,350 **** CREATE TABLE partitioned ( a int ) PARTITION BY HASH (a); ! ERROR: unrecognized partitioning strategy "hash" -- specified column must be present in the table CREATE TABLE partitioned ( a int --- 344,350 ---- CREATE TABLE partitioned ( a int ) PARTITION BY HASH (a); ! ERROR: number of partitions must be specified for hash partition -- specified column must be present in the table CREATE TABLE partitioned ( a int ``` Exact script I'm using for building and testing PostgreSQL could be found here [2]. 4) As I already mentioned - missing documentation. In general patch looks quite good to me. I personally believe it has all the changes to be accepted in current commitfest. Naturally if community will come to a consensus regarding keywords, whether all partitions should be created automatically, etc :) [1] http://afiskon.ru/s/dd/20cbe21934_regression.diffs.txt [2] http://afiskon.ru/s/76/a4fb71739c_full-build.sh.txt On Wed, Mar 01, 2017 at 06:10:10PM +0900, Yugo Nagata wrote: > Hi Aleksander, > > On Tue, 28 Feb 2017 18:05:36 +0300 > Aleksander Alekseev <a.alekseev@postgrespro.ru> wrote: > > > Hi, Yugo. > > > > Looks like a great feature! I'm going to take a closer look on your code > > and write a feedback shortly. For now I can only tell that you forgot > > to include some documentation in the patch. > > Thank you for looking into it. I'm forward to your feedback. > This is a proof of concept patch and additional documentation > is not included. I'll add this after reaching a consensus > on the specification of the feature. > > > > > I've added a corresponding entry to current commitfest [1]. Hope you > > don't mind. If it's not too much trouble could you please register on a > > commitfest site and add yourself to this entry as an author? I'm pretty > > sure someone is using this information for writing release notes or > > something like this. > > Thank you for registering it to the commitfest. I have added me as an auther. > > > > > [1] https://commitfest.postgresql.org/13/1059/ > > > > On Tue, Feb 28, 2017 at 11:33:13PM +0900, Yugo Nagata wrote: > > > Hi all, > > > > > > Now we have a declarative partitioning, but hash partitioning is not > > > implemented yet. Attached is a POC patch to add the hash partitioning > > > feature. I know we will need more discussions about the syntax and other > > > specifications before going ahead the project, but I think this runnable > > > code might help to discuss what and how we implement this. > > > > > > * Description > > > > > > In this patch, the hash partitioning implementation is basically based > > > on the list partitioning mechanism. However, partition bounds cannot be > > > specified explicitly, but this is used internally as hash partition > > > index, which is calculated when a partition is created or attached. > > > > > > The tentative syntax to create a partitioned table is as bellow; > > > > > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > > > > > The number of partitions is specified by PARTITIONS, which is currently > > > constant and cannot be changed, but I think this is needed to be changed in > > > some manner. A hash function is specified by USING. Maybe, specifying hash > > > function may be ommitted, and in this case, a default hash function > > > corresponding to key type will be used. > > > > > > A partition table can be create as bellow; > > > > > > CREATE TABLE h1 PARTITION OF h; > > > CREATE TABLE h2 PARTITION OF h; > > > CREATE TABLE h3 PARTITION OF h; > > > > > > FOR VALUES clause cannot be used, and the partition bound is > > > calclulated automatically as partition index of single integer value. > > > > > > When trying create partitions more than the number specified > > > by PARTITIONS, it gets an error. > > > > > > postgres=# create table h4 partition of h; > > > ERROR: cannot create hash partition more than 3 for h > > > > > > An inserted record is stored in a partition whose index equals > > > abs(hashfunc(key)) % <number_of_partitions>. In the above > > > example, this is abs(hashint4(i))%3. > > > > > > postgres=# insert into h (select generate_series(0,20)); > > > INSERT 0 21 > > > > > > postgres=# select *,tableoid::regclass from h; > > > i | tableoid > > > ----+---------- > > > 0 | h1 > > > 1 | h1 > > > 2 | h1 > > > 4 | h1 > > > 8 | h1 > > > 10 | h1 > > > 11 | h1 > > > 14 | h1 > > > 15 | h1 > > > 17 | h1 > > > 20 | h1 > > > 5 | h2 > > > 12 | h2 > > > 13 | h2 > > > 16 | h2 > > > 19 | h2 > > > 3 | h3 > > > 6 | h3 > > > 7 | h3 > > > 9 | h3 > > > 18 | h3 > > > (21 rows) > > > > > > * Todo / discussions > > > > > > In this patch, we cannot change the number of partitions specified > > > by PARTITIONS. I we can change this, the partitioning rule > > > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > > > is also changed and then we need reallocatiing records between > > > partitions. > > > > > > In this patch, user can specify a hash function USING. However, > > > we migth need default hash functions which are useful and > > > proper for hash partitioning. > > > > > > Currently, even when we issue SELECT query with a condition, > > > postgres looks into all partitions regardless of each partition's > > > constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". > > > > > > postgres=# explain select * from h where i = 10; > > > QUERY PLAN > > > ---------------------------------------------------------- > > > Append (cost=0.00..125.62 rows=40 width=4) > > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > > Filter: (i = 10) > > > -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) > > > Filter: (i = 10) > > > -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) > > > Filter: (i = 10) > > > -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) > > > Filter: (i = 10) > > > (9 rows) > > > > > > However, if we modify a condition into a same expression > > > as the partitions constraint, postgres can exclude unrelated > > > table from search targets. So, we might avoid the problem > > > by converting the qual properly before calling predicate_refuted_by(). > > > > > > postgres=# explain select * from h where abs(hashint4(i))%3 = abs(hashint4(10))%3; > > > QUERY PLAN > > > ---------------------------------------------------------- > > > Append (cost=0.00..61.00 rows=14 width=4) > > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > > Filter: ((abs(hashint4(i)) % 3) = 2) > > > -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) > > > Filter: ((abs(hashint4(i)) % 3) = 2) > > > (5 rows) > > > > > > Best regards, > > > Yugo Nagata > > > > > > -- > > > Yugo Nagata <nagata@sraoss.co.jp> > > > > > diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c > > > index 41c0056..3820920 100644 > > > --- a/src/backend/catalog/heap.c > > > +++ b/src/backend/catalog/heap.c > > > @@ -3074,7 +3074,7 @@ StorePartitionKey(Relation rel, > > > AttrNumber *partattrs, > > > List *partexprs, > > > Oid *partopclass, > > > - Oid *partcollation) > > > + Oid *partcollation, int16 partnparts, Oid hashfunc) > > > { > > > int i; > > > int2vector *partattrs_vec; > > > @@ -3121,6 +3121,8 @@ StorePartitionKey(Relation rel, > > > values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); > > > values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy); > > > values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts); > > > + values[Anum_pg_partitioned_table_partnparts - 1] = Int16GetDatum(partnparts); > > > + values[Anum_pg_partitioned_table_parthashfunc - 1] = ObjectIdGetDatum(hashfunc); > > > values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec); > > > values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec); > > > values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec); > > > diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c > > > index 4bcef58..24e69c6 100644 > > > --- a/src/backend/catalog/partition.c > > > +++ b/src/backend/catalog/partition.c > > > @@ -36,6 +36,8 @@ > > > #include "optimizer/clauses.h" > > > #include "optimizer/planmain.h" > > > #include "optimizer/var.h" > > > +#include "parser/parse_func.h" > > > +#include "parser/parse_oper.h" > > > #include "rewrite/rewriteManip.h" > > > #include "storage/lmgr.h" > > > #include "utils/array.h" > > > @@ -120,6 +122,7 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b, > > > > > > static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); > > > static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); > > > +static List *get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec); > > > static Oid get_partition_operator(PartitionKey key, int col, > > > StrategyNumber strategy, bool *need_relabel); > > > static List *generate_partition_qual(Relation rel); > > > @@ -236,7 +239,8 @@ RelationBuildPartitionDesc(Relation rel) > > > oids[i++] = lfirst_oid(cell); > > > > > > /* Convert from node to the internal representation */ > > > - if (key->strategy == PARTITION_STRATEGY_LIST) > > > + if (key->strategy == PARTITION_STRATEGY_LIST || > > > + key->strategy == PARTITION_STRATEGY_HASH) > > > { > > > List *non_null_values = NIL; > > > > > > @@ -251,7 +255,7 @@ RelationBuildPartitionDesc(Relation rel) > > > ListCell *c; > > > PartitionBoundSpec *spec = lfirst(cell); > > > > > > - if (spec->strategy != PARTITION_STRATEGY_LIST) > > > + if (spec->strategy != key->strategy) > > > elog(ERROR, "invalid strategy in partition bound spec"); > > > > > > foreach(c, spec->listdatums) > > > @@ -464,6 +468,7 @@ RelationBuildPartitionDesc(Relation rel) > > > switch (key->strategy) > > > { > > > case PARTITION_STRATEGY_LIST: > > > + case PARTITION_STRATEGY_HASH: > > > { > > > boundinfo->has_null = found_null; > > > boundinfo->indexes = (int *) palloc(ndatums * sizeof(int)); > > > @@ -829,6 +834,18 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) > > > break; > > > } > > > > > > + case PARTITION_STRATEGY_HASH: > > > + { > > > + Assert(spec->strategy == PARTITION_STRATEGY_HASH); > > > + > > > + if (partdesc->nparts + 1 > key->partnparts) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), > > > + errmsg("cannot create hash partition more than %d for %s", > > > + key->partnparts, RelationGetRelationName(parent)))); > > > + break; > > > + } > > > + > > > default: > > > elog(ERROR, "unexpected partition strategy: %d", > > > (int) key->strategy); > > > @@ -916,6 +933,11 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) > > > my_qual = get_qual_for_range(key, spec); > > > break; > > > > > > + case PARTITION_STRATEGY_HASH: > > > + Assert(spec->strategy == PARTITION_STRATEGY_LIST); > > > + my_qual = get_qual_for_hash(key, spec); > > > + break; > > > + > > > default: > > > elog(ERROR, "unexpected partition strategy: %d", > > > (int) key->strategy); > > > @@ -1146,6 +1168,84 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode, > > > return pd; > > > } > > > > > > +/* > > > + * convert_expr_for_hash > > > + * > > > + * Converts a expr for a hash partition's constraint. > > > + * expr is converted into 'abs(hashfunc(expr)) % npart". > > > + * > > > + * npart: number of partitions > > > + * hashfunc: OID of hash function > > > + */ > > > +Expr * > > > +convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc) > > > +{ > > > + FuncExpr *func, > > > + *abs; > > > + Expr *modexpr; > > > + Oid modoid; > > > + Oid int4oid[1] = {INT4OID}; > > > + > > > + ParseState *pstate = make_parsestate(NULL); > > > + Value *val_npart = makeInteger(npart); > > > + Node *const_npart = (Node *) make_const(pstate, val_npart, -1); > > > + > > > + /* hash function */ > > > + func = makeFuncExpr(hashfunc, > > > + INT4OID, > > > + list_make1(expr), > > > + 0, > > > + 0, > > > + COERCE_EXPLICIT_CALL); > > > + > > > + /* Abs */ > > > + abs = makeFuncExpr(LookupFuncName(list_make1(makeString("abs")), 1, int4oid, false), > > > + INT4OID, > > > + list_make1(func), > > > + 0, > > > + 0, > > > + COERCE_EXPLICIT_CALL); > > > + > > > + /* modulo by npart */ > > > + modoid = LookupOperName(pstate, list_make1(makeString("%")), INT4OID, INT4OID, false, -1); > > > + modexpr = make_opclause(modoid, INT4OID, false, (Expr*)abs, (Expr*)const_npart, 0, 0); > > > + > > > + return modexpr; > > > +} > > > + > > > + > > > +/* > > > + * get_next_hash_partition_index > > > + * > > > + * Returns the minimal index which is not used for hash partition. > > > + */ > > > +int > > > +get_next_hash_partition_index(Relation parent) > > > +{ > > > + PartitionKey key = RelationGetPartitionKey(parent); > > > + PartitionDesc partdesc = RelationGetPartitionDesc(parent); > > > + > > > + int i; > > > + bool *used = palloc0(sizeof(int) * key->partnparts); > > > + > > > + /* mark used for existing partition indexs */ > > > + for (i = 0; i < partdesc->boundinfo->ndatums; i++) > > > + { > > > + Datum* datum = partdesc->boundinfo->datums[i]; > > > + int idx = DatumGetInt16(datum[0]); > > > + > > > + if (!used[idx]) > > > + used[idx] = true; > > > + } > > > + > > > + /* find the minimal unused index */ > > > + for (i = 0; i < key->partnparts; i++) > > > + if (!used[i]) > > > + break; > > > + > > > + return i; > > > +} > > > + > > > /* Module-local functions */ > > > > > > /* > > > @@ -1467,6 +1567,43 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) > > > } > > > > > > /* > > > + * get_qual_for_hash > > > + * > > > + * Returns a list of expressions to use as a hash partition's constraint. > > > + */ > > > +static List * > > > +get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec) > > > +{ > > > + List *result; > > > + Expr *keyCol; > > > + Expr *expr; > > > + Expr *opexpr; > > > + Oid operoid; > > > + ParseState *pstate = make_parsestate(NULL); > > > + > > > + /* Left operand */ > > > + if (key->partattrs[0] != 0) > > > + keyCol = (Expr *) makeVar(1, > > > + key->partattrs[0], > > > + key->parttypid[0], > > > + key->parttypmod[0], > > > + key->parttypcoll[0], > > > + 0); > > > + else > > > + keyCol = (Expr *) copyObject(linitial(key->partexprs)); > > > + > > > + expr = convert_expr_for_hash(keyCol, key->partnparts, key->parthashfunc); > > > + > > > + /* equals the listdaums value */ > > > + operoid = LookupOperName(pstate, list_make1(makeString("=")), INT4OID, INT4OID, false, -1); > > > + opexpr = make_opclause(operoid, BOOLOID, false, expr, linitial(spec->listdatums), 0, 0); > > > + > > > + result = list_make1(opexpr); > > > + > > > + return result; > > > +} > > > + > > > +/* > > > * get_partition_operator > > > * > > > * Return oid of the operator of given strategy for a given partition key > > > @@ -1730,6 +1867,11 @@ get_partition_for_tuple(PartitionDispatch *pd, > > > (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), > > > errmsg("range partition key of row contains null"))); > > > } > > > + else if (key->strategy == PARTITION_STRATEGY_HASH) > > > + { > > > + values[0] = OidFunctionCall1(key->parthashfunc, values[0]); > > > + values[0] = Int16GetDatum(Abs(DatumGetInt16(values[0])) % key->partnparts); > > > + } > > > > > > if (partdesc->boundinfo->has_null && isnull[0]) > > > /* Tuple maps to the null-accepting list partition */ > > > @@ -1744,6 +1886,7 @@ get_partition_for_tuple(PartitionDispatch *pd, > > > switch (key->strategy) > > > { > > > case PARTITION_STRATEGY_LIST: > > > + case PARTITION_STRATEGY_HASH: > > > if (cur_offset >= 0 && equal) > > > cur_index = partdesc->boundinfo->indexes[cur_offset]; > > > else > > > @@ -1968,6 +2111,7 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo, > > > switch (key->strategy) > > > { > > > case PARTITION_STRATEGY_LIST: > > > + case PARTITION_STRATEGY_HASH: > > > cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], > > > key->partcollation[0], > > > bound_datums[0], > > > diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c > > > index 3cea220..5a28cc0 100644 > > > --- a/src/backend/commands/tablecmds.c > > > +++ b/src/backend/commands/tablecmds.c > > > @@ -41,6 +41,7 @@ > > > #include "catalog/pg_inherits_fn.h" > > > #include "catalog/pg_namespace.h" > > > #include "catalog/pg_opclass.h" > > > +#include "catalog/pg_proc.h" > > > #include "catalog/pg_tablespace.h" > > > #include "catalog/pg_trigger.h" > > > #include "catalog/pg_type.h" > > > @@ -77,6 +78,7 @@ > > > #include "parser/parse_oper.h" > > > #include "parser/parse_relation.h" > > > #include "parser/parse_type.h" > > > +#include "parser/parse_func.h" > > > #include "parser/parse_utilcmd.h" > > > #include "parser/parser.h" > > > #include "pgstat.h" > > > @@ -450,7 +452,7 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, > > > Oid oldrelid, void *arg); > > > static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr); > > > static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy); > > > -static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > > +static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > > > List **partexprs, Oid *partopclass, Oid *partcollation); > > > static void CreateInheritance(Relation child_rel, Relation parent_rel); > > > static void RemoveInheritance(Relation child_rel, Relation parent_rel); > > > @@ -799,8 +801,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > > > AttrNumber partattrs[PARTITION_MAX_KEYS]; > > > Oid partopclass[PARTITION_MAX_KEYS]; > > > Oid partcollation[PARTITION_MAX_KEYS]; > > > + Oid partatttypes[PARTITION_MAX_KEYS]; > > > List *partexprs = NIL; > > > List *cmds = NIL; > > > + Oid hashfuncOid = InvalidOid; > > > > > > /* > > > * We need to transform the raw parsetrees corresponding to partition > > > @@ -811,15 +815,40 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > > > stmt->partspec = transformPartitionSpec(rel, stmt->partspec, > > > &strategy); > > > ComputePartitionAttrs(rel, stmt->partspec->partParams, > > > - partattrs, &partexprs, partopclass, > > > + partattrs, partatttypes, &partexprs, partopclass, > > > partcollation); > > > > > > partnatts = list_length(stmt->partspec->partParams); > > > + > > > + if (strategy == PARTITION_STRATEGY_HASH) > > > + { > > > + Oid funcrettype; > > > + > > > + if (partnatts != 1) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("number of partition key must be 1 for hash partition"))); > > > + > > > + hashfuncOid = LookupFuncName(stmt->partspec->hashfunc, 1, partatttypes, false); > > > + funcrettype = get_func_rettype(hashfuncOid); > > > + if (funcrettype != INT4OID) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("hash function for partitioning must return integer"))); > > > + > > > + if (func_volatile(hashfuncOid) != PROVOLATILE_IMMUTABLE) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("hash function for partitioning must be marked IMMUTABLE"))); > > > + > > > + } > > > + > > > StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs, > > > - partopclass, partcollation); > > > + partopclass, partcollation, stmt->partspec->partnparts, hashfuncOid); > > > > > > - /* Force key columns to be NOT NULL when using range partitioning */ > > > - if (strategy == PARTITION_STRATEGY_RANGE) > > > + /* Force key columns to be NOT NULL when using range or hash partitioning */ > > > + if (strategy == PARTITION_STRATEGY_RANGE || > > > + strategy == PARTITION_STRATEGY_HASH) > > > { > > > for (i = 0; i < partnatts; i++) > > > { > > > @@ -12783,18 +12812,51 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > > > newspec->strategy = partspec->strategy; > > > newspec->location = partspec->location; > > > newspec->partParams = NIL; > > > + newspec->partnparts = partspec->partnparts; > > > + newspec->hashfunc = partspec->hashfunc; > > > > > > /* Parse partitioning strategy name */ > > > if (!pg_strcasecmp(partspec->strategy, "list")) > > > *strategy = PARTITION_STRATEGY_LIST; > > > else if (!pg_strcasecmp(partspec->strategy, "range")) > > > *strategy = PARTITION_STRATEGY_RANGE; > > > + else if (!pg_strcasecmp(partspec->strategy, "hash")) > > > + *strategy = PARTITION_STRATEGY_HASH; > > > else > > > ereport(ERROR, > > > (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > errmsg("unrecognized partitioning strategy \"%s\"", > > > partspec->strategy))); > > > > > > + if (*strategy == PARTITION_STRATEGY_HASH) > > > + { > > > + if (partspec->partnparts < 0) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("number of partitions must be specified for hash partition"))); > > > + else if (partspec->partnparts == 0) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("number of partitions must be greater than 0"))); > > > + > > > + if (list_length(partspec->hashfunc) == 0) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("hash function must be specified for hash partition"))); > > > + } > > > + else > > > + { > > > + if (partspec->partnparts >= 0) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("number of partitions can be specified only for hash partition"))); > > > + > > > + if (list_length(partspec->hashfunc) > 0) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > + errmsg("hash function can be specified only for hash partition"))); > > > + } > > > + > > > /* > > > * Create a dummy ParseState and insert the target relation as its sole > > > * rangetable entry. We need a ParseState for transformExpr. > > > @@ -12843,7 +12905,7 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > > > * Compute per-partition-column information from a list of PartitionElem's > > > */ > > > static void > > > -ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > > +ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > > > List **partexprs, Oid *partopclass, Oid *partcollation) > > > { > > > int attn; > > > @@ -13010,6 +13072,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > > "btree", > > > BTREE_AM_OID); > > > > > > + partatttypes[attn] = atttype; > > > attn++; > > > } > > > } > > > diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c > > > index 05d8538..f4febc9 100644 > > > --- a/src/backend/nodes/copyfuncs.c > > > +++ b/src/backend/nodes/copyfuncs.c > > > @@ -4232,6 +4232,8 @@ _copyPartitionSpec(const PartitionSpec *from) > > > > > > COPY_STRING_FIELD(strategy); > > > COPY_NODE_FIELD(partParams); > > > + COPY_SCALAR_FIELD(partnparts); > > > + COPY_NODE_FIELD(hashfunc); > > > COPY_LOCATION_FIELD(location); > > > > > > return newnode; > > > diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c > > > index d595cd7..d589eac 100644 > > > --- a/src/backend/nodes/equalfuncs.c > > > +++ b/src/backend/nodes/equalfuncs.c > > > @@ -2725,6 +2725,8 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b) > > > { > > > COMPARE_STRING_FIELD(strategy); > > > COMPARE_NODE_FIELD(partParams); > > > + COMPARE_SCALAR_FIELD(partnparts); > > > + COMPARE_NODE_FIELD(hashfunc); > > > COMPARE_LOCATION_FIELD(location); > > > > > > return true; > > > diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c > > > index b3802b4..d6db80e 100644 > > > --- a/src/backend/nodes/outfuncs.c > > > +++ b/src/backend/nodes/outfuncs.c > > > @@ -3318,6 +3318,8 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node) > > > > > > WRITE_STRING_FIELD(strategy); > > > WRITE_NODE_FIELD(partParams); > > > + WRITE_INT_FIELD(partnparts); > > > + WRITE_NODE_FIELD(hashfunc); > > > WRITE_LOCATION_FIELD(location); > > > } > > > > > > diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y > > > index e833b2e..b67140d 100644 > > > --- a/src/backend/parser/gram.y > > > +++ b/src/backend/parser/gram.y > > > @@ -574,6 +574,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > %type <list> partbound_datum_list > > > %type <partrange_datum> PartitionRangeDatum > > > %type <list> range_datum_list > > > +%type <ival> hash_partitions > > > +%type <list> hash_function > > > > > > /* > > > * Non-keyword token types. These are hard-wired into the "flex" lexer. > > > @@ -627,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > > > > GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING > > > > > > - HANDLER HAVING HEADER_P HOLD HOUR_P > > > + HANDLER HASH HAVING HEADER_P HOLD HOUR_P > > > > > > IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P > > > INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P > > > @@ -651,7 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR > > > ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER > > > > > > - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY > > > + PARALLEL PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PLACING PLANS POLICY > > > POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY > > > PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM PUBLICATION > > > > > > @@ -2587,6 +2589,16 @@ ForValues: > > > > > > $$ = (Node *) n; > > > } > > > + > > > + /* a HASH partition */ > > > + | /*EMPTY*/ > > > + { > > > + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); > > > + > > > + n->strategy = PARTITION_STRATEGY_HASH; > > > + > > > + $$ = (Node *) n; > > > + } > > > ; > > > > > > partbound_datum: > > > @@ -3666,7 +3678,7 @@ OptPartitionSpec: PartitionSpec { $$ = $1; } > > > | /*EMPTY*/ { $$ = NULL; } > > > ; > > > > > > -PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > > > +PartitionSpec: PARTITION BY part_strategy '(' part_params ')' hash_partitions hash_function > > > { > > > PartitionSpec *n = makeNode(PartitionSpec); > > > > > > @@ -3674,10 +3686,21 @@ PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > > > n->partParams = $5; > > > n->location = @1; > > > > > > + n->partnparts = $7; > > > + n->hashfunc = $8; > > > + > > > $$ = n; > > > } > > > ; > > > > > > +hash_partitions: PARTITIONS Iconst { $$ = $2; } > > > + | /*EMPTY*/ { $$ = -1; } > > > + ; > > > + > > > +hash_function: USING handler_name { $$ = $2; } > > > + | /*EMPTY*/ { $$ = NULL; } > > > + ; > > > + > > > part_strategy: IDENT { $$ = $1; } > > > | unreserved_keyword { $$ = pstrdup($1); } > > > ; > > > @@ -14377,6 +14400,7 @@ unreserved_keyword: > > > | GLOBAL > > > | GRANTED > > > | HANDLER > > > + | HASH > > > | HEADER_P > > > | HOLD > > > | HOUR_P > > > @@ -14448,6 +14472,7 @@ unreserved_keyword: > > > | PARSER > > > | PARTIAL > > > | PARTITION > > > + | PARTITIONS > > > | PASSING > > > | PASSWORD > > > | PLANS > > > diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c > > > index ff2bab6..8e1be31 100644 > > > --- a/src/backend/parser/parse_utilcmd.c > > > +++ b/src/backend/parser/parse_utilcmd.c > > > @@ -40,6 +40,7 @@ > > > #include "catalog/pg_opclass.h" > > > #include "catalog/pg_operator.h" > > > #include "catalog/pg_type.h" > > > +#include "catalog/partition.h" > > > #include "commands/comment.h" > > > #include "commands/defrem.h" > > > #include "commands/tablecmds.h" > > > @@ -3252,6 +3253,24 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound) > > > ++i; > > > } > > > } > > > + else if (strategy == PARTITION_STRATEGY_HASH) > > > + { > > > + Value *conval; > > > + Node *value; > > > + int index; > > > + > > > + if (spec->strategy != PARTITION_STRATEGY_HASH) > > > + ereport(ERROR, > > > + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), > > > + errmsg("invalid bound specification for a hash partition"))); > > > + > > > + index = get_next_hash_partition_index(parent); > > > + > > > + /* store the partition index as a listdatums value */ > > > + conval = makeInteger(index); > > > + value = (Node *) make_const(pstate, conval, -1); > > > + result_spec->listdatums = list_make1(value); > > > + } > > > else > > > elog(ERROR, "unexpected partition strategy: %d", (int) strategy); > > > > > > diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c > > > index b27b77d..fab6eea 100644 > > > --- a/src/backend/utils/adt/ruleutils.c > > > +++ b/src/backend/utils/adt/ruleutils.c > > > @@ -1423,7 +1423,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, > > > * > > > * Returns the partition key specification, ie, the following: > > > * > > > - * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) > > > + * PARTITION BY { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...]) > > > */ > > > Datum > > > pg_get_partkeydef(PG_FUNCTION_ARGS) > > > @@ -1513,6 +1513,9 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags) > > > case PARTITION_STRATEGY_RANGE: > > > appendStringInfo(&buf, "RANGE"); > > > break; > > > + case PARTITION_STRATEGY_HASH: > > > + appendStringInfo(&buf, "HASH"); > > > + break; > > > default: > > > elog(ERROR, "unexpected partition strategy: %d", > > > (int) form->partstrat); > > > @@ -8520,6 +8523,9 @@ get_rule_expr(Node *node, deparse_context *context, > > > appendStringInfoString(buf, ")"); > > > break; > > > > > > + case PARTITION_STRATEGY_HASH: > > > + break; > > > + > > > default: > > > elog(ERROR, "unrecognized partition strategy: %d", > > > (int) spec->strategy); > > > diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c > > > index 9001e20..829e4d2 100644 > > > --- a/src/backend/utils/cache/relcache.c > > > +++ b/src/backend/utils/cache/relcache.c > > > @@ -855,6 +855,9 @@ RelationBuildPartitionKey(Relation relation) > > > key->strategy = form->partstrat; > > > key->partnatts = form->partnatts; > > > > > > + key->partnparts = form->partnparts; > > > + key->parthashfunc = form->parthashfunc; > > > + > > > /* > > > * We can rely on the first variable-length attribute being mapped to the > > > * relevant field of the catalog's C struct, because all previous > > > @@ -999,6 +1002,9 @@ copy_partition_key(PartitionKey fromkey) > > > newkey->strategy = fromkey->strategy; > > > newkey->partnatts = n = fromkey->partnatts; > > > > > > + newkey->partnparts = fromkey->partnparts; > > > + newkey->parthashfunc = fromkey->parthashfunc; > > > + > > > newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber)); > > > memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber)); > > > > > > diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h > > > index 1187797..367e2f8 100644 > > > --- a/src/include/catalog/heap.h > > > +++ b/src/include/catalog/heap.h > > > @@ -141,7 +141,7 @@ extern void StorePartitionKey(Relation rel, > > > AttrNumber *partattrs, > > > List *partexprs, > > > Oid *partopclass, > > > - Oid *partcollation); > > > + Oid *partcollation, int16 partnparts, Oid hashfunc); > > > extern void RemovePartitionKeyByRelId(Oid relid); > > > extern void StorePartitionBound(Relation rel, Relation parent, Node *bound); > > > > > > diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h > > > index b195d1a..80f4b0e 100644 > > > --- a/src/include/catalog/partition.h > > > +++ b/src/include/catalog/partition.h > > > @@ -89,4 +89,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd, > > > TupleTableSlot *slot, > > > EState *estate, > > > Oid *failed_at); > > > +extern Expr *convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc); > > > +extern int get_next_hash_partition_index(Relation parent); > > > #endif /* PARTITION_H */ > > > diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h > > > index bdff36a..69e509c 100644 > > > --- a/src/include/catalog/pg_partitioned_table.h > > > +++ b/src/include/catalog/pg_partitioned_table.h > > > @@ -33,6 +33,9 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > > > char partstrat; /* partitioning strategy */ > > > int16 partnatts; /* number of partition key columns */ > > > > > > + int16 partnparts; > > > + Oid parthashfunc; > > > + > > > /* > > > * variable-length fields start here, but we allow direct access to > > > * partattrs via the C struct. That's because the first variable-length > > > @@ -49,6 +52,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > > > pg_node_tree partexprs; /* list of expressions in the partition key; > > > * one item for each zero entry in partattrs[] */ > > > #endif > > > + > > > + > > > } FormData_pg_partitioned_table; > > > > > > /* ---------------- > > > @@ -62,13 +67,15 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table; > > > * compiler constants for pg_partitioned_table > > > * ---------------- > > > */ > > > -#define Natts_pg_partitioned_table 7 > > > +#define Natts_pg_partitioned_table 9 > > > #define Anum_pg_partitioned_table_partrelid 1 > > > #define Anum_pg_partitioned_table_partstrat 2 > > > #define Anum_pg_partitioned_table_partnatts 3 > > > -#define Anum_pg_partitioned_table_partattrs 4 > > > -#define Anum_pg_partitioned_table_partclass 5 > > > -#define Anum_pg_partitioned_table_partcollation 6 > > > -#define Anum_pg_partitioned_table_partexprs 7 > > > +#define Anum_pg_partitioned_table_partnparts 4 > > > +#define Anum_pg_partitioned_table_parthashfunc 5 > > > +#define Anum_pg_partitioned_table_partattrs 6 > > > +#define Anum_pg_partitioned_table_partclass 7 > > > +#define Anum_pg_partitioned_table_partcollation 8 > > > +#define Anum_pg_partitioned_table_partexprs 9 > > > > > > #endif /* PG_PARTITIONED_TABLE_H */ > > > diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h > > > index 5afc3eb..1c3474f 100644 > > > --- a/src/include/nodes/parsenodes.h > > > +++ b/src/include/nodes/parsenodes.h > > > @@ -730,11 +730,14 @@ typedef struct PartitionSpec > > > NodeTag type; > > > char *strategy; /* partitioning strategy ('list' or 'range') */ > > > List *partParams; /* List of PartitionElems */ > > > + int partnparts; > > > + List *hashfunc; > > > int location; /* token location, or -1 if unknown */ > > > } PartitionSpec; > > > > > > #define PARTITION_STRATEGY_LIST 'l' > > > #define PARTITION_STRATEGY_RANGE 'r' > > > +#define PARTITION_STRATEGY_HASH 'h' > > > > > > /* > > > * PartitionBoundSpec - a partition bound specification > > > diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h > > > index 985d650..0597939 100644 > > > --- a/src/include/parser/kwlist.h > > > +++ b/src/include/parser/kwlist.h > > > @@ -180,6 +180,7 @@ PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD) > > > PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD) > > > PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD) > > > PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD) > > > +PG_KEYWORD("hash", HASH, UNRESERVED_KEYWORD) > > > PG_KEYWORD("having", HAVING, RESERVED_KEYWORD) > > > PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD) > > > PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) > > > @@ -291,6 +292,7 @@ PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) > > > PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) > > > PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) > > > PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) > > > +PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD) > > > PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) > > > PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) > > > PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) > > > diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h > > > index a617a7c..660adfb 100644 > > > --- a/src/include/utils/rel.h > > > +++ b/src/include/utils/rel.h > > > @@ -62,6 +62,9 @@ typedef struct PartitionKeyData > > > Oid *partopcintype; /* OIDs of opclass declared input data types */ > > > FmgrInfo *partsupfunc; /* lookup info for support funcs */ > > > > > > + int16 partnparts; /* number of hash partitions */ > > > + Oid parthashfunc; /* OID of hash function */ > > > + > > > /* Partitioning collation per attribute */ > > > Oid *partcollation; > > > > > > > > > > > -- > > > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > > > To make changes to your subscription: > > > http://www.postgresql.org/mailpref/pgsql-hackers > > > > > > -- > > Best regards, > > Aleksander Alekseev > > > -- > Yugo Nagata <nagata@sraoss.co.jp> -- Best regards, Aleksander Alekseev -
Re: [POC] hash partitioning
Maksim Milyutin <m.milyutin@postgrespro.ru> — 2017-03-01T14:10:34Z
On 01.03.2017 05:14, Amit Langote wrote: > Nagata-san, > >> A partition table can be create as bellow; >> >> CREATE TABLE h1 PARTITION OF h; >> CREATE TABLE h2 PARTITION OF h; >> CREATE TABLE h3 PARTITION OF h; >> >> FOR VALUES clause cannot be used, and the partition bound is >> calclulated automatically as partition index of single integer value. >> >> When trying create partitions more than the number specified >> by PARTITIONS, it gets an error. >> >> postgres=# create table h4 partition of h; >> ERROR: cannot create hash partition more than 3 for h > > Instead of having to create each partition individually, wouldn't it be > better if the following command > > CREATE TABLE h (i int) PARTITION BY HASH (i) PARTITIONS 3; > > created the partitions *automatically*? It's a good idea but in this case we can't create hash-partition that is also partitioned table, and as a consequence we are unable to create subpartitions. My understanding is that the table can be partitioned only using CREATE TABLE statement, not ALTER TABLE. For this reason the new created partitions are only regular tables. We can achieve desired result through creating a separate partitioned table and making the DETACH/ATTACH manipulation, though. But IMO it's not flexible case. It would be a good thing if a regular table could be partitioned through separate command. Then your idea would not be restrictive. -- Maksim Milyutin Postgres Professional: http://www.postgrespro.com Russian Postgres Company
-
Re: [POC] hash partitioning
Aleksander Alekseev <a.alekseev@postgrespro.ru> — 2017-03-01T14:23:46Z
> We can achieve desired result through creating a separate partitioned table > and making the DETACH/ATTACH manipulation, though. But IMO it's not flexible > case. I think it would be great to allow end user to decide. If user is not interested in subpartitions he or she can use syntax like 'CREATE TABLE ... PARTITION BY HAHS(i) PARTITIONS 3 CREATE AUTOMATICALLY;' or maybe a build-in procedure for this. Otherwise there is also ATTACH/DETACH syntax available. Anyway all of this is something that could be discussed infinitely and not necessarily should be included in this concrete patch. We could probably agree that 3 or 4 separately discussed, reviewed and tested patches are better than one huge patch that will be moved to the next commitfest because of disagreements regarding a syntax. On Wed, Mar 01, 2017 at 05:10:34PM +0300, Maksim Milyutin wrote: > On 01.03.2017 05:14, Amit Langote wrote: > > Nagata-san, > > > > > A partition table can be create as bellow; > > > > > > CREATE TABLE h1 PARTITION OF h; > > > CREATE TABLE h2 PARTITION OF h; > > > CREATE TABLE h3 PARTITION OF h; > > > > > > FOR VALUES clause cannot be used, and the partition bound is > > > calclulated automatically as partition index of single integer value. > > > > > > When trying create partitions more than the number specified > > > by PARTITIONS, it gets an error. > > > > > > postgres=# create table h4 partition of h; > > > ERROR: cannot create hash partition more than 3 for h > > > > Instead of having to create each partition individually, wouldn't it be > > better if the following command > > > > CREATE TABLE h (i int) PARTITION BY HASH (i) PARTITIONS 3; > > > > created the partitions *automatically*? > > It's a good idea but in this case we can't create hash-partition that is > also partitioned table, and as a consequence we are unable to create > subpartitions. My understanding is that the table can be partitioned only > using CREATE TABLE statement, not ALTER TABLE. For this reason the new > created partitions are only regular tables. > > We can achieve desired result through creating a separate partitioned table > and making the DETACH/ATTACH manipulation, though. But IMO it's not flexible > case. > > It would be a good thing if a regular table could be partitioned through > separate command. Then your idea would not be restrictive. > > > -- > Maksim Milyutin > Postgres Professional: http://www.postgrespro.com > Russian Postgres Company > > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers -- Best regards, Aleksander Alekseev
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-02T08:45:38Z
Hi Aleksander , Thank you for reviewing the patch. On Wed, 1 Mar 2017 17:08:49 +0300 Aleksander Alekseev <a.alekseev@postgrespro.ru> wrote: > Hi, Yugo. > > Today I've had an opportunity to take a closer look on this patch. Here are > a few things that bother me. > > 1a) There are missing commends here: > > ``` > --- a/src/include/catalog/pg_partitioned_table.h > +++ b/src/include/catalog/pg_partitioned_table.h > @@ -33,6 +33,9 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > char partstrat; /* partitioning strategy */ > int16 partnatts; /* number of partition key columns */ > > + int16 partnparts; > + Oid parthashfunc; > + > ``` > > 1b) ... and here: > > ``` > --- a/src/include/nodes/parsenodes.h > +++ b/src/include/nodes/parsenodes.h > @@ -730,11 +730,14 @@ typedef struct PartitionSpec > NodeTag type; > char *strategy; /* partitioning strategy ('list' or 'range') */ > List *partParams; /* List of PartitionElems */ > + int partnparts; > + List *hashfunc; > int location; /* token location, or -1 if unknown */ > } PartitionSpec; > ``` ok, I'll add comments for these members; > > 2) I believe new empty lines in patches are generally not welcomed by > community: > > ``` > @@ -49,6 +52,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > pg_node_tree partexprs; /* list of expressions in the partition key; > * one item for each zero entry in partattrs[] */ > #endif > + > + > } FormData_pg_partitioned_table; > ``` I'll remove it from the patch. > > 3) One test fails on my laptop (Arch Linux, x64) [1]: > > ``` > *************** > *** 344,350 **** > CREATE TABLE partitioned ( > a int > ) PARTITION BY HASH (a); > ! ERROR: unrecognized partitioning strategy "hash" > -- specified column must be present in the table > CREATE TABLE partitioned ( > a int > --- 344,350 ---- > CREATE TABLE partitioned ( > a int > ) PARTITION BY HASH (a); > ! ERROR: number of partitions must be specified for hash partition > -- specified column must be present in the table > CREATE TABLE partitioned ( > a int > ``` These are expected behaviors in the current patch. However, there are some discussions on the specification about CREATE TABLE, so it may be changed. > > Exact script I'm using for building and testing PostgreSQL could be > found here [2]. > > 4) As I already mentioned - missing documentation. I think writing the documentation should be waited fo the specification getting a consensus. > > In general patch looks quite good to me. I personally believe it has all > the changes to be accepted in current commitfest. Naturally if community > will come to a consensus regarding keywords, whether all partitions > should be created automatically, etc :) > > [1] http://afiskon.ru/s/dd/20cbe21934_regression.diffs.txt > [2] http://afiskon.ru/s/76/a4fb71739c_full-build.sh.txt > > On Wed, Mar 01, 2017 at 06:10:10PM +0900, Yugo Nagata wrote: > > Hi Aleksander, > > > > On Tue, 28 Feb 2017 18:05:36 +0300 > > Aleksander Alekseev <a.alekseev@postgrespro.ru> wrote: > > > > > Hi, Yugo. > > > > > > Looks like a great feature! I'm going to take a closer look on your code > > > and write a feedback shortly. For now I can only tell that you forgot > > > to include some documentation in the patch. > > > > Thank you for looking into it. I'm forward to your feedback. > > This is a proof of concept patch and additional documentation > > is not included. I'll add this after reaching a consensus > > on the specification of the feature. > > > > > > > > I've added a corresponding entry to current commitfest [1]. Hope you > > > don't mind. If it's not too much trouble could you please register on a > > > commitfest site and add yourself to this entry as an author? I'm pretty > > > sure someone is using this information for writing release notes or > > > something like this. > > > > Thank you for registering it to the commitfest. I have added me as an auther. > > > > > > > > [1] https://commitfest.postgresql.org/13/1059/ > > > > > > On Tue, Feb 28, 2017 at 11:33:13PM +0900, Yugo Nagata wrote: > > > > Hi all, > > > > > > > > Now we have a declarative partitioning, but hash partitioning is not > > > > implemented yet. Attached is a POC patch to add the hash partitioning > > > > feature. I know we will need more discussions about the syntax and other > > > > specifications before going ahead the project, but I think this runnable > > > > code might help to discuss what and how we implement this. > > > > > > > > * Description > > > > > > > > In this patch, the hash partitioning implementation is basically based > > > > on the list partitioning mechanism. However, partition bounds cannot be > > > > specified explicitly, but this is used internally as hash partition > > > > index, which is calculated when a partition is created or attached. > > > > > > > > The tentative syntax to create a partitioned table is as bellow; > > > > > > > > CREATE TABLE h (i int) PARTITION BY HASH(i) PARTITIONS 3 USING hashint4; > > > > > > > > The number of partitions is specified by PARTITIONS, which is currently > > > > constant and cannot be changed, but I think this is needed to be changed in > > > > some manner. A hash function is specified by USING. Maybe, specifying hash > > > > function may be ommitted, and in this case, a default hash function > > > > corresponding to key type will be used. > > > > > > > > A partition table can be create as bellow; > > > > > > > > CREATE TABLE h1 PARTITION OF h; > > > > CREATE TABLE h2 PARTITION OF h; > > > > CREATE TABLE h3 PARTITION OF h; > > > > > > > > FOR VALUES clause cannot be used, and the partition bound is > > > > calclulated automatically as partition index of single integer value. > > > > > > > > When trying create partitions more than the number specified > > > > by PARTITIONS, it gets an error. > > > > > > > > postgres=# create table h4 partition of h; > > > > ERROR: cannot create hash partition more than 3 for h > > > > > > > > An inserted record is stored in a partition whose index equals > > > > abs(hashfunc(key)) % <number_of_partitions>. In the above > > > > example, this is abs(hashint4(i))%3. > > > > > > > > postgres=# insert into h (select generate_series(0,20)); > > > > INSERT 0 21 > > > > > > > > postgres=# select *,tableoid::regclass from h; > > > > i | tableoid > > > > ----+---------- > > > > 0 | h1 > > > > 1 | h1 > > > > 2 | h1 > > > > 4 | h1 > > > > 8 | h1 > > > > 10 | h1 > > > > 11 | h1 > > > > 14 | h1 > > > > 15 | h1 > > > > 17 | h1 > > > > 20 | h1 > > > > 5 | h2 > > > > 12 | h2 > > > > 13 | h2 > > > > 16 | h2 > > > > 19 | h2 > > > > 3 | h3 > > > > 6 | h3 > > > > 7 | h3 > > > > 9 | h3 > > > > 18 | h3 > > > > (21 rows) > > > > > > > > * Todo / discussions > > > > > > > > In this patch, we cannot change the number of partitions specified > > > > by PARTITIONS. I we can change this, the partitioning rule > > > > (<partition index> = abs(hashfunc(key)) % <number_of_partitions>) > > > > is also changed and then we need reallocatiing records between > > > > partitions. > > > > > > > > In this patch, user can specify a hash function USING. However, > > > > we migth need default hash functions which are useful and > > > > proper for hash partitioning. > > > > > > > > Currently, even when we issue SELECT query with a condition, > > > > postgres looks into all partitions regardless of each partition's > > > > constraint, because this is complicated such like "abs(hashint4(i))%3 = 0". > > > > > > > > postgres=# explain select * from h where i = 10; > > > > QUERY PLAN > > > > ---------------------------------------------------------- > > > > Append (cost=0.00..125.62 rows=40 width=4) > > > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > > > Filter: (i = 10) > > > > -> Seq Scan on h1 (cost=0.00..41.88 rows=13 width=4) > > > > Filter: (i = 10) > > > > -> Seq Scan on h2 (cost=0.00..41.88 rows=13 width=4) > > > > Filter: (i = 10) > > > > -> Seq Scan on h3 (cost=0.00..41.88 rows=13 width=4) > > > > Filter: (i = 10) > > > > (9 rows) > > > > > > > > However, if we modify a condition into a same expression > > > > as the partitions constraint, postgres can exclude unrelated > > > > table from search targets. So, we might avoid the problem > > > > by converting the qual properly before calling predicate_refuted_by(). > > > > > > > > postgres=# explain select * from h where abs(hashint4(i))%3 = abs(hashint4(10))%3; > > > > QUERY PLAN > > > > ---------------------------------------------------------- > > > > Append (cost=0.00..61.00 rows=14 width=4) > > > > -> Seq Scan on h (cost=0.00..0.00 rows=1 width=4) > > > > Filter: ((abs(hashint4(i)) % 3) = 2) > > > > -> Seq Scan on h3 (cost=0.00..61.00 rows=13 width=4) > > > > Filter: ((abs(hashint4(i)) % 3) = 2) > > > > (5 rows) > > > > > > > > Best regards, > > > > Yugo Nagata > > > > > > > > -- > > > > Yugo Nagata <nagata@sraoss.co.jp> > > > > > > > diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c > > > > index 41c0056..3820920 100644 > > > > --- a/src/backend/catalog/heap.c > > > > +++ b/src/backend/catalog/heap.c > > > > @@ -3074,7 +3074,7 @@ StorePartitionKey(Relation rel, > > > > AttrNumber *partattrs, > > > > List *partexprs, > > > > Oid *partopclass, > > > > - Oid *partcollation) > > > > + Oid *partcollation, int16 partnparts, Oid hashfunc) > > > > { > > > > int i; > > > > int2vector *partattrs_vec; > > > > @@ -3121,6 +3121,8 @@ StorePartitionKey(Relation rel, > > > > values[Anum_pg_partitioned_table_partrelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel)); > > > > values[Anum_pg_partitioned_table_partstrat - 1] = CharGetDatum(strategy); > > > > values[Anum_pg_partitioned_table_partnatts - 1] = Int16GetDatum(partnatts); > > > > + values[Anum_pg_partitioned_table_partnparts - 1] = Int16GetDatum(partnparts); > > > > + values[Anum_pg_partitioned_table_parthashfunc - 1] = ObjectIdGetDatum(hashfunc); > > > > values[Anum_pg_partitioned_table_partattrs - 1] = PointerGetDatum(partattrs_vec); > > > > values[Anum_pg_partitioned_table_partclass - 1] = PointerGetDatum(partopclass_vec); > > > > values[Anum_pg_partitioned_table_partcollation - 1] = PointerGetDatum(partcollation_vec); > > > > diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c > > > > index 4bcef58..24e69c6 100644 > > > > --- a/src/backend/catalog/partition.c > > > > +++ b/src/backend/catalog/partition.c > > > > @@ -36,6 +36,8 @@ > > > > #include "optimizer/clauses.h" > > > > #include "optimizer/planmain.h" > > > > #include "optimizer/var.h" > > > > +#include "parser/parse_func.h" > > > > +#include "parser/parse_oper.h" > > > > #include "rewrite/rewriteManip.h" > > > > #include "storage/lmgr.h" > > > > #include "utils/array.h" > > > > @@ -120,6 +122,7 @@ static int32 qsort_partition_rbound_cmp(const void *a, const void *b, > > > > > > > > static List *get_qual_for_list(PartitionKey key, PartitionBoundSpec *spec); > > > > static List *get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec); > > > > +static List *get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec); > > > > static Oid get_partition_operator(PartitionKey key, int col, > > > > StrategyNumber strategy, bool *need_relabel); > > > > static List *generate_partition_qual(Relation rel); > > > > @@ -236,7 +239,8 @@ RelationBuildPartitionDesc(Relation rel) > > > > oids[i++] = lfirst_oid(cell); > > > > > > > > /* Convert from node to the internal representation */ > > > > - if (key->strategy == PARTITION_STRATEGY_LIST) > > > > + if (key->strategy == PARTITION_STRATEGY_LIST || > > > > + key->strategy == PARTITION_STRATEGY_HASH) > > > > { > > > > List *non_null_values = NIL; > > > > > > > > @@ -251,7 +255,7 @@ RelationBuildPartitionDesc(Relation rel) > > > > ListCell *c; > > > > PartitionBoundSpec *spec = lfirst(cell); > > > > > > > > - if (spec->strategy != PARTITION_STRATEGY_LIST) > > > > + if (spec->strategy != key->strategy) > > > > elog(ERROR, "invalid strategy in partition bound spec"); > > > > > > > > foreach(c, spec->listdatums) > > > > @@ -464,6 +468,7 @@ RelationBuildPartitionDesc(Relation rel) > > > > switch (key->strategy) > > > > { > > > > case PARTITION_STRATEGY_LIST: > > > > + case PARTITION_STRATEGY_HASH: > > > > { > > > > boundinfo->has_null = found_null; > > > > boundinfo->indexes = (int *) palloc(ndatums * sizeof(int)); > > > > @@ -829,6 +834,18 @@ check_new_partition_bound(char *relname, Relation parent, Node *bound) > > > > break; > > > > } > > > > > > > > + case PARTITION_STRATEGY_HASH: > > > > + { > > > > + Assert(spec->strategy == PARTITION_STRATEGY_HASH); > > > > + > > > > + if (partdesc->nparts + 1 > key->partnparts) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), > > > > + errmsg("cannot create hash partition more than %d for %s", > > > > + key->partnparts, RelationGetRelationName(parent)))); > > > > + break; > > > > + } > > > > + > > > > default: > > > > elog(ERROR, "unexpected partition strategy: %d", > > > > (int) key->strategy); > > > > @@ -916,6 +933,11 @@ get_qual_from_partbound(Relation rel, Relation parent, Node *bound) > > > > my_qual = get_qual_for_range(key, spec); > > > > break; > > > > > > > > + case PARTITION_STRATEGY_HASH: > > > > + Assert(spec->strategy == PARTITION_STRATEGY_LIST); > > > > + my_qual = get_qual_for_hash(key, spec); > > > > + break; > > > > + > > > > default: > > > > elog(ERROR, "unexpected partition strategy: %d", > > > > (int) key->strategy); > > > > @@ -1146,6 +1168,84 @@ RelationGetPartitionDispatchInfo(Relation rel, int lockmode, > > > > return pd; > > > > } > > > > > > > > +/* > > > > + * convert_expr_for_hash > > > > + * > > > > + * Converts a expr for a hash partition's constraint. > > > > + * expr is converted into 'abs(hashfunc(expr)) % npart". > > > > + * > > > > + * npart: number of partitions > > > > + * hashfunc: OID of hash function > > > > + */ > > > > +Expr * > > > > +convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc) > > > > +{ > > > > + FuncExpr *func, > > > > + *abs; > > > > + Expr *modexpr; > > > > + Oid modoid; > > > > + Oid int4oid[1] = {INT4OID}; > > > > + > > > > + ParseState *pstate = make_parsestate(NULL); > > > > + Value *val_npart = makeInteger(npart); > > > > + Node *const_npart = (Node *) make_const(pstate, val_npart, -1); > > > > + > > > > + /* hash function */ > > > > + func = makeFuncExpr(hashfunc, > > > > + INT4OID, > > > > + list_make1(expr), > > > > + 0, > > > > + 0, > > > > + COERCE_EXPLICIT_CALL); > > > > + > > > > + /* Abs */ > > > > + abs = makeFuncExpr(LookupFuncName(list_make1(makeString("abs")), 1, int4oid, false), > > > > + INT4OID, > > > > + list_make1(func), > > > > + 0, > > > > + 0, > > > > + COERCE_EXPLICIT_CALL); > > > > + > > > > + /* modulo by npart */ > > > > + modoid = LookupOperName(pstate, list_make1(makeString("%")), INT4OID, INT4OID, false, -1); > > > > + modexpr = make_opclause(modoid, INT4OID, false, (Expr*)abs, (Expr*)const_npart, 0, 0); > > > > + > > > > + return modexpr; > > > > +} > > > > + > > > > + > > > > +/* > > > > + * get_next_hash_partition_index > > > > + * > > > > + * Returns the minimal index which is not used for hash partition. > > > > + */ > > > > +int > > > > +get_next_hash_partition_index(Relation parent) > > > > +{ > > > > + PartitionKey key = RelationGetPartitionKey(parent); > > > > + PartitionDesc partdesc = RelationGetPartitionDesc(parent); > > > > + > > > > + int i; > > > > + bool *used = palloc0(sizeof(int) * key->partnparts); > > > > + > > > > + /* mark used for existing partition indexs */ > > > > + for (i = 0; i < partdesc->boundinfo->ndatums; i++) > > > > + { > > > > + Datum* datum = partdesc->boundinfo->datums[i]; > > > > + int idx = DatumGetInt16(datum[0]); > > > > + > > > > + if (!used[idx]) > > > > + used[idx] = true; > > > > + } > > > > + > > > > + /* find the minimal unused index */ > > > > + for (i = 0; i < key->partnparts; i++) > > > > + if (!used[i]) > > > > + break; > > > > + > > > > + return i; > > > > +} > > > > + > > > > /* Module-local functions */ > > > > > > > > /* > > > > @@ -1467,6 +1567,43 @@ get_qual_for_range(PartitionKey key, PartitionBoundSpec *spec) > > > > } > > > > > > > > /* > > > > + * get_qual_for_hash > > > > + * > > > > + * Returns a list of expressions to use as a hash partition's constraint. > > > > + */ > > > > +static List * > > > > +get_qual_for_hash(PartitionKey key, PartitionBoundSpec *spec) > > > > +{ > > > > + List *result; > > > > + Expr *keyCol; > > > > + Expr *expr; > > > > + Expr *opexpr; > > > > + Oid operoid; > > > > + ParseState *pstate = make_parsestate(NULL); > > > > + > > > > + /* Left operand */ > > > > + if (key->partattrs[0] != 0) > > > > + keyCol = (Expr *) makeVar(1, > > > > + key->partattrs[0], > > > > + key->parttypid[0], > > > > + key->parttypmod[0], > > > > + key->parttypcoll[0], > > > > + 0); > > > > + else > > > > + keyCol = (Expr *) copyObject(linitial(key->partexprs)); > > > > + > > > > + expr = convert_expr_for_hash(keyCol, key->partnparts, key->parthashfunc); > > > > + > > > > + /* equals the listdaums value */ > > > > + operoid = LookupOperName(pstate, list_make1(makeString("=")), INT4OID, INT4OID, false, -1); > > > > + opexpr = make_opclause(operoid, BOOLOID, false, expr, linitial(spec->listdatums), 0, 0); > > > > + > > > > + result = list_make1(opexpr); > > > > + > > > > + return result; > > > > +} > > > > + > > > > +/* > > > > * get_partition_operator > > > > * > > > > * Return oid of the operator of given strategy for a given partition key > > > > @@ -1730,6 +1867,11 @@ get_partition_for_tuple(PartitionDispatch *pd, > > > > (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), > > > > errmsg("range partition key of row contains null"))); > > > > } > > > > + else if (key->strategy == PARTITION_STRATEGY_HASH) > > > > + { > > > > + values[0] = OidFunctionCall1(key->parthashfunc, values[0]); > > > > + values[0] = Int16GetDatum(Abs(DatumGetInt16(values[0])) % key->partnparts); > > > > + } > > > > > > > > if (partdesc->boundinfo->has_null && isnull[0]) > > > > /* Tuple maps to the null-accepting list partition */ > > > > @@ -1744,6 +1886,7 @@ get_partition_for_tuple(PartitionDispatch *pd, > > > > switch (key->strategy) > > > > { > > > > case PARTITION_STRATEGY_LIST: > > > > + case PARTITION_STRATEGY_HASH: > > > > if (cur_offset >= 0 && equal) > > > > cur_index = partdesc->boundinfo->indexes[cur_offset]; > > > > else > > > > @@ -1968,6 +2111,7 @@ partition_bound_cmp(PartitionKey key, PartitionBoundInfo boundinfo, > > > > switch (key->strategy) > > > > { > > > > case PARTITION_STRATEGY_LIST: > > > > + case PARTITION_STRATEGY_HASH: > > > > cmpval = DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0], > > > > key->partcollation[0], > > > > bound_datums[0], > > > > diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c > > > > index 3cea220..5a28cc0 100644 > > > > --- a/src/backend/commands/tablecmds.c > > > > +++ b/src/backend/commands/tablecmds.c > > > > @@ -41,6 +41,7 @@ > > > > #include "catalog/pg_inherits_fn.h" > > > > #include "catalog/pg_namespace.h" > > > > #include "catalog/pg_opclass.h" > > > > +#include "catalog/pg_proc.h" > > > > #include "catalog/pg_tablespace.h" > > > > #include "catalog/pg_trigger.h" > > > > #include "catalog/pg_type.h" > > > > @@ -77,6 +78,7 @@ > > > > #include "parser/parse_oper.h" > > > > #include "parser/parse_relation.h" > > > > #include "parser/parse_type.h" > > > > +#include "parser/parse_func.h" > > > > #include "parser/parse_utilcmd.h" > > > > #include "parser/parser.h" > > > > #include "pgstat.h" > > > > @@ -450,7 +452,7 @@ static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, > > > > Oid oldrelid, void *arg); > > > > static bool is_partition_attr(Relation rel, AttrNumber attnum, bool *used_in_expr); > > > > static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy); > > > > -static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > > > +static void ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > > > > List **partexprs, Oid *partopclass, Oid *partcollation); > > > > static void CreateInheritance(Relation child_rel, Relation parent_rel); > > > > static void RemoveInheritance(Relation child_rel, Relation parent_rel); > > > > @@ -799,8 +801,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > > > > AttrNumber partattrs[PARTITION_MAX_KEYS]; > > > > Oid partopclass[PARTITION_MAX_KEYS]; > > > > Oid partcollation[PARTITION_MAX_KEYS]; > > > > + Oid partatttypes[PARTITION_MAX_KEYS]; > > > > List *partexprs = NIL; > > > > List *cmds = NIL; > > > > + Oid hashfuncOid = InvalidOid; > > > > > > > > /* > > > > * We need to transform the raw parsetrees corresponding to partition > > > > @@ -811,15 +815,40 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, > > > > stmt->partspec = transformPartitionSpec(rel, stmt->partspec, > > > > &strategy); > > > > ComputePartitionAttrs(rel, stmt->partspec->partParams, > > > > - partattrs, &partexprs, partopclass, > > > > + partattrs, partatttypes, &partexprs, partopclass, > > > > partcollation); > > > > > > > > partnatts = list_length(stmt->partspec->partParams); > > > > + > > > > + if (strategy == PARTITION_STRATEGY_HASH) > > > > + { > > > > + Oid funcrettype; > > > > + > > > > + if (partnatts != 1) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("number of partition key must be 1 for hash partition"))); > > > > + > > > > + hashfuncOid = LookupFuncName(stmt->partspec->hashfunc, 1, partatttypes, false); > > > > + funcrettype = get_func_rettype(hashfuncOid); > > > > + if (funcrettype != INT4OID) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("hash function for partitioning must return integer"))); > > > > + > > > > + if (func_volatile(hashfuncOid) != PROVOLATILE_IMMUTABLE) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("hash function for partitioning must be marked IMMUTABLE"))); > > > > + > > > > + } > > > > + > > > > StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs, > > > > - partopclass, partcollation); > > > > + partopclass, partcollation, stmt->partspec->partnparts, hashfuncOid); > > > > > > > > - /* Force key columns to be NOT NULL when using range partitioning */ > > > > - if (strategy == PARTITION_STRATEGY_RANGE) > > > > + /* Force key columns to be NOT NULL when using range or hash partitioning */ > > > > + if (strategy == PARTITION_STRATEGY_RANGE || > > > > + strategy == PARTITION_STRATEGY_HASH) > > > > { > > > > for (i = 0; i < partnatts; i++) > > > > { > > > > @@ -12783,18 +12812,51 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > > > > newspec->strategy = partspec->strategy; > > > > newspec->location = partspec->location; > > > > newspec->partParams = NIL; > > > > + newspec->partnparts = partspec->partnparts; > > > > + newspec->hashfunc = partspec->hashfunc; > > > > > > > > /* Parse partitioning strategy name */ > > > > if (!pg_strcasecmp(partspec->strategy, "list")) > > > > *strategy = PARTITION_STRATEGY_LIST; > > > > else if (!pg_strcasecmp(partspec->strategy, "range")) > > > > *strategy = PARTITION_STRATEGY_RANGE; > > > > + else if (!pg_strcasecmp(partspec->strategy, "hash")) > > > > + *strategy = PARTITION_STRATEGY_HASH; > > > > else > > > > ereport(ERROR, > > > > (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > errmsg("unrecognized partitioning strategy \"%s\"", > > > > partspec->strategy))); > > > > > > > > + if (*strategy == PARTITION_STRATEGY_HASH) > > > > + { > > > > + if (partspec->partnparts < 0) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("number of partitions must be specified for hash partition"))); > > > > + else if (partspec->partnparts == 0) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("number of partitions must be greater than 0"))); > > > > + > > > > + if (list_length(partspec->hashfunc) == 0) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("hash function must be specified for hash partition"))); > > > > + } > > > > + else > > > > + { > > > > + if (partspec->partnparts >= 0) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("number of partitions can be specified only for hash partition"))); > > > > + > > > > + if (list_length(partspec->hashfunc) > 0) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), > > > > + errmsg("hash function can be specified only for hash partition"))); > > > > + } > > > > + > > > > /* > > > > * Create a dummy ParseState and insert the target relation as its sole > > > > * rangetable entry. We need a ParseState for transformExpr. > > > > @@ -12843,7 +12905,7 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy) > > > > * Compute per-partition-column information from a list of PartitionElem's > > > > */ > > > > static void > > > > -ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > > > +ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, Oid *partatttypes, > > > > List **partexprs, Oid *partopclass, Oid *partcollation) > > > > { > > > > int attn; > > > > @@ -13010,6 +13072,7 @@ ComputePartitionAttrs(Relation rel, List *partParams, AttrNumber *partattrs, > > > > "btree", > > > > BTREE_AM_OID); > > > > > > > > + partatttypes[attn] = atttype; > > > > attn++; > > > > } > > > > } > > > > diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c > > > > index 05d8538..f4febc9 100644 > > > > --- a/src/backend/nodes/copyfuncs.c > > > > +++ b/src/backend/nodes/copyfuncs.c > > > > @@ -4232,6 +4232,8 @@ _copyPartitionSpec(const PartitionSpec *from) > > > > > > > > COPY_STRING_FIELD(strategy); > > > > COPY_NODE_FIELD(partParams); > > > > + COPY_SCALAR_FIELD(partnparts); > > > > + COPY_NODE_FIELD(hashfunc); > > > > COPY_LOCATION_FIELD(location); > > > > > > > > return newnode; > > > > diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c > > > > index d595cd7..d589eac 100644 > > > > --- a/src/backend/nodes/equalfuncs.c > > > > +++ b/src/backend/nodes/equalfuncs.c > > > > @@ -2725,6 +2725,8 @@ _equalPartitionSpec(const PartitionSpec *a, const PartitionSpec *b) > > > > { > > > > COMPARE_STRING_FIELD(strategy); > > > > COMPARE_NODE_FIELD(partParams); > > > > + COMPARE_SCALAR_FIELD(partnparts); > > > > + COMPARE_NODE_FIELD(hashfunc); > > > > COMPARE_LOCATION_FIELD(location); > > > > > > > > return true; > > > > diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c > > > > index b3802b4..d6db80e 100644 > > > > --- a/src/backend/nodes/outfuncs.c > > > > +++ b/src/backend/nodes/outfuncs.c > > > > @@ -3318,6 +3318,8 @@ _outPartitionSpec(StringInfo str, const PartitionSpec *node) > > > > > > > > WRITE_STRING_FIELD(strategy); > > > > WRITE_NODE_FIELD(partParams); > > > > + WRITE_INT_FIELD(partnparts); > > > > + WRITE_NODE_FIELD(hashfunc); > > > > WRITE_LOCATION_FIELD(location); > > > > } > > > > > > > > diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y > > > > index e833b2e..b67140d 100644 > > > > --- a/src/backend/parser/gram.y > > > > +++ b/src/backend/parser/gram.y > > > > @@ -574,6 +574,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > > %type <list> partbound_datum_list > > > > %type <partrange_datum> PartitionRangeDatum > > > > %type <list> range_datum_list > > > > +%type <ival> hash_partitions > > > > +%type <list> hash_function > > > > > > > > /* > > > > * Non-keyword token types. These are hard-wired into the "flex" lexer. > > > > @@ -627,7 +629,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > > > > > > GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING > > > > > > > > - HANDLER HAVING HEADER_P HOLD HOUR_P > > > > + HANDLER HASH HAVING HEADER_P HOLD HOUR_P > > > > > > > > IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P > > > > INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P > > > > @@ -651,7 +653,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); > > > > OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR > > > > ORDER ORDINALITY OUT_P OUTER_P OVER OVERLAPS OVERLAY OWNED OWNER > > > > > > > > - PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY > > > > + PARALLEL PARSER PARTIAL PARTITION PARTITIONS PASSING PASSWORD PLACING PLANS POLICY > > > > POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY > > > > PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROGRAM PUBLICATION > > > > > > > > @@ -2587,6 +2589,16 @@ ForValues: > > > > > > > > $$ = (Node *) n; > > > > } > > > > + > > > > + /* a HASH partition */ > > > > + | /*EMPTY*/ > > > > + { > > > > + PartitionBoundSpec *n = makeNode(PartitionBoundSpec); > > > > + > > > > + n->strategy = PARTITION_STRATEGY_HASH; > > > > + > > > > + $$ = (Node *) n; > > > > + } > > > > ; > > > > > > > > partbound_datum: > > > > @@ -3666,7 +3678,7 @@ OptPartitionSpec: PartitionSpec { $$ = $1; } > > > > | /*EMPTY*/ { $$ = NULL; } > > > > ; > > > > > > > > -PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > > > > +PartitionSpec: PARTITION BY part_strategy '(' part_params ')' hash_partitions hash_function > > > > { > > > > PartitionSpec *n = makeNode(PartitionSpec); > > > > > > > > @@ -3674,10 +3686,21 @@ PartitionSpec: PARTITION BY part_strategy '(' part_params ')' > > > > n->partParams = $5; > > > > n->location = @1; > > > > > > > > + n->partnparts = $7; > > > > + n->hashfunc = $8; > > > > + > > > > $$ = n; > > > > } > > > > ; > > > > > > > > +hash_partitions: PARTITIONS Iconst { $$ = $2; } > > > > + | /*EMPTY*/ { $$ = -1; } > > > > + ; > > > > + > > > > +hash_function: USING handler_name { $$ = $2; } > > > > + | /*EMPTY*/ { $$ = NULL; } > > > > + ; > > > > + > > > > part_strategy: IDENT { $$ = $1; } > > > > | unreserved_keyword { $$ = pstrdup($1); } > > > > ; > > > > @@ -14377,6 +14400,7 @@ unreserved_keyword: > > > > | GLOBAL > > > > | GRANTED > > > > | HANDLER > > > > + | HASH > > > > | HEADER_P > > > > | HOLD > > > > | HOUR_P > > > > @@ -14448,6 +14472,7 @@ unreserved_keyword: > > > > | PARSER > > > > | PARTIAL > > > > | PARTITION > > > > + | PARTITIONS > > > > | PASSING > > > > | PASSWORD > > > > | PLANS > > > > diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c > > > > index ff2bab6..8e1be31 100644 > > > > --- a/src/backend/parser/parse_utilcmd.c > > > > +++ b/src/backend/parser/parse_utilcmd.c > > > > @@ -40,6 +40,7 @@ > > > > #include "catalog/pg_opclass.h" > > > > #include "catalog/pg_operator.h" > > > > #include "catalog/pg_type.h" > > > > +#include "catalog/partition.h" > > > > #include "commands/comment.h" > > > > #include "commands/defrem.h" > > > > #include "commands/tablecmds.h" > > > > @@ -3252,6 +3253,24 @@ transformPartitionBound(ParseState *pstate, Relation parent, Node *bound) > > > > ++i; > > > > } > > > > } > > > > + else if (strategy == PARTITION_STRATEGY_HASH) > > > > + { > > > > + Value *conval; > > > > + Node *value; > > > > + int index; > > > > + > > > > + if (spec->strategy != PARTITION_STRATEGY_HASH) > > > > + ereport(ERROR, > > > > + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), > > > > + errmsg("invalid bound specification for a hash partition"))); > > > > + > > > > + index = get_next_hash_partition_index(parent); > > > > + > > > > + /* store the partition index as a listdatums value */ > > > > + conval = makeInteger(index); > > > > + value = (Node *) make_const(pstate, conval, -1); > > > > + result_spec->listdatums = list_make1(value); > > > > + } > > > > else > > > > elog(ERROR, "unexpected partition strategy: %d", (int) strategy); > > > > > > > > diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c > > > > index b27b77d..fab6eea 100644 > > > > --- a/src/backend/utils/adt/ruleutils.c > > > > +++ b/src/backend/utils/adt/ruleutils.c > > > > @@ -1423,7 +1423,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, > > > > * > > > > * Returns the partition key specification, ie, the following: > > > > * > > > > - * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) > > > > + * PARTITION BY { RANGE | LIST | HASH } (column opt_collation opt_opclass [, ...]) > > > > */ > > > > Datum > > > > pg_get_partkeydef(PG_FUNCTION_ARGS) > > > > @@ -1513,6 +1513,9 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags) > > > > case PARTITION_STRATEGY_RANGE: > > > > appendStringInfo(&buf, "RANGE"); > > > > break; > > > > + case PARTITION_STRATEGY_HASH: > > > > + appendStringInfo(&buf, "HASH"); > > > > + break; > > > > default: > > > > elog(ERROR, "unexpected partition strategy: %d", > > > > (int) form->partstrat); > > > > @@ -8520,6 +8523,9 @@ get_rule_expr(Node *node, deparse_context *context, > > > > appendStringInfoString(buf, ")"); > > > > break; > > > > > > > > + case PARTITION_STRATEGY_HASH: > > > > + break; > > > > + > > > > default: > > > > elog(ERROR, "unrecognized partition strategy: %d", > > > > (int) spec->strategy); > > > > diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c > > > > index 9001e20..829e4d2 100644 > > > > --- a/src/backend/utils/cache/relcache.c > > > > +++ b/src/backend/utils/cache/relcache.c > > > > @@ -855,6 +855,9 @@ RelationBuildPartitionKey(Relation relation) > > > > key->strategy = form->partstrat; > > > > key->partnatts = form->partnatts; > > > > > > > > + key->partnparts = form->partnparts; > > > > + key->parthashfunc = form->parthashfunc; > > > > + > > > > /* > > > > * We can rely on the first variable-length attribute being mapped to the > > > > * relevant field of the catalog's C struct, because all previous > > > > @@ -999,6 +1002,9 @@ copy_partition_key(PartitionKey fromkey) > > > > newkey->strategy = fromkey->strategy; > > > > newkey->partnatts = n = fromkey->partnatts; > > > > > > > > + newkey->partnparts = fromkey->partnparts; > > > > + newkey->parthashfunc = fromkey->parthashfunc; > > > > + > > > > newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber)); > > > > memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber)); > > > > > > > > diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h > > > > index 1187797..367e2f8 100644 > > > > --- a/src/include/catalog/heap.h > > > > +++ b/src/include/catalog/heap.h > > > > @@ -141,7 +141,7 @@ extern void StorePartitionKey(Relation rel, > > > > AttrNumber *partattrs, > > > > List *partexprs, > > > > Oid *partopclass, > > > > - Oid *partcollation); > > > > + Oid *partcollation, int16 partnparts, Oid hashfunc); > > > > extern void RemovePartitionKeyByRelId(Oid relid); > > > > extern void StorePartitionBound(Relation rel, Relation parent, Node *bound); > > > > > > > > diff --git a/src/include/catalog/partition.h b/src/include/catalog/partition.h > > > > index b195d1a..80f4b0e 100644 > > > > --- a/src/include/catalog/partition.h > > > > +++ b/src/include/catalog/partition.h > > > > @@ -89,4 +89,6 @@ extern int get_partition_for_tuple(PartitionDispatch *pd, > > > > TupleTableSlot *slot, > > > > EState *estate, > > > > Oid *failed_at); > > > > +extern Expr *convert_expr_for_hash(Expr *expr, int npart, Oid hashfunc); > > > > +extern int get_next_hash_partition_index(Relation parent); > > > > #endif /* PARTITION_H */ > > > > diff --git a/src/include/catalog/pg_partitioned_table.h b/src/include/catalog/pg_partitioned_table.h > > > > index bdff36a..69e509c 100644 > > > > --- a/src/include/catalog/pg_partitioned_table.h > > > > +++ b/src/include/catalog/pg_partitioned_table.h > > > > @@ -33,6 +33,9 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > > > > char partstrat; /* partitioning strategy */ > > > > int16 partnatts; /* number of partition key columns */ > > > > > > > > + int16 partnparts; > > > > + Oid parthashfunc; > > > > + > > > > /* > > > > * variable-length fields start here, but we allow direct access to > > > > * partattrs via the C struct. That's because the first variable-length > > > > @@ -49,6 +52,8 @@ CATALOG(pg_partitioned_table,3350) BKI_WITHOUT_OIDS > > > > pg_node_tree partexprs; /* list of expressions in the partition key; > > > > * one item for each zero entry in partattrs[] */ > > > > #endif > > > > + > > > > + > > > > } FormData_pg_partitioned_table; > > > > > > > > /* ---------------- > > > > @@ -62,13 +67,15 @@ typedef FormData_pg_partitioned_table *Form_pg_partitioned_table; > > > > * compiler constants for pg_partitioned_table > > > > * ---------------- > > > > */ > > > > -#define Natts_pg_partitioned_table 7 > > > > +#define Natts_pg_partitioned_table 9 > > > > #define Anum_pg_partitioned_table_partrelid 1 > > > > #define Anum_pg_partitioned_table_partstrat 2 > > > > #define Anum_pg_partitioned_table_partnatts 3 > > > > -#define Anum_pg_partitioned_table_partattrs 4 > > > > -#define Anum_pg_partitioned_table_partclass 5 > > > > -#define Anum_pg_partitioned_table_partcollation 6 > > > > -#define Anum_pg_partitioned_table_partexprs 7 > > > > +#define Anum_pg_partitioned_table_partnparts 4 > > > > +#define Anum_pg_partitioned_table_parthashfunc 5 > > > > +#define Anum_pg_partitioned_table_partattrs 6 > > > > +#define Anum_pg_partitioned_table_partclass 7 > > > > +#define Anum_pg_partitioned_table_partcollation 8 > > > > +#define Anum_pg_partitioned_table_partexprs 9 > > > > > > > > #endif /* PG_PARTITIONED_TABLE_H */ > > > > diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h > > > > index 5afc3eb..1c3474f 100644 > > > > --- a/src/include/nodes/parsenodes.h > > > > +++ b/src/include/nodes/parsenodes.h > > > > @@ -730,11 +730,14 @@ typedef struct PartitionSpec > > > > NodeTag type; > > > > char *strategy; /* partitioning strategy ('list' or 'range') */ > > > > List *partParams; /* List of PartitionElems */ > > > > + int partnparts; > > > > + List *hashfunc; > > > > int location; /* token location, or -1 if unknown */ > > > > } PartitionSpec; > > > > > > > > #define PARTITION_STRATEGY_LIST 'l' > > > > #define PARTITION_STRATEGY_RANGE 'r' > > > > +#define PARTITION_STRATEGY_HASH 'h' > > > > > > > > /* > > > > * PartitionBoundSpec - a partition bound specification > > > > diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h > > > > index 985d650..0597939 100644 > > > > --- a/src/include/parser/kwlist.h > > > > +++ b/src/include/parser/kwlist.h > > > > @@ -180,6 +180,7 @@ PG_KEYWORD("greatest", GREATEST, COL_NAME_KEYWORD) > > > > PG_KEYWORD("group", GROUP_P, RESERVED_KEYWORD) > > > > PG_KEYWORD("grouping", GROUPING, COL_NAME_KEYWORD) > > > > PG_KEYWORD("handler", HANDLER, UNRESERVED_KEYWORD) > > > > +PG_KEYWORD("hash", HASH, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("having", HAVING, RESERVED_KEYWORD) > > > > PG_KEYWORD("header", HEADER_P, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("hold", HOLD, UNRESERVED_KEYWORD) > > > > @@ -291,6 +292,7 @@ PG_KEYWORD("parallel", PARALLEL, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("parser", PARSER, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD) > > > > +PG_KEYWORD("partitions", PARTITIONS, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD) > > > > PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD) > > > > diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h > > > > index a617a7c..660adfb 100644 > > > > --- a/src/include/utils/rel.h > > > > +++ b/src/include/utils/rel.h > > > > @@ -62,6 +62,9 @@ typedef struct PartitionKeyData > > > > Oid *partopcintype; /* OIDs of opclass declared input data types */ > > > > FmgrInfo *partsupfunc; /* lookup info for support funcs */ > > > > > > > > + int16 partnparts; /* number of hash partitions */ > > > > + Oid parthashfunc; /* OID of hash function */ > > > > + > > > > /* Partitioning collation per attribute */ > > > > Oid *partcollation; > > > > > > > > > > > > > > > -- > > > > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > > > > To make changes to your subscription: > > > > http://www.postgresql.org/mailpref/pgsql-hackers > > > > > > > > > -- > > > Best regards, > > > Aleksander Alekseev > > > > > > -- > > Yugo Nagata <nagata@sraoss.co.jp> > > -- > Best regards, > Aleksander Alekseev -- Yugo Nagata <nagata@sraoss.co.jp> -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-03-02T13:03:42Z
On Wed, Mar 1, 2017 at 3:50 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > [....] > > I Agree that it is unavoidable partitions number in modulo hashing, > > but we can do in other hashing technique. Have you had thought about > > Linear hashing[1] or Consistent hashing[2]? This will allow us to > > add/drop > > partition with minimal row moment. > > Thank you for your information of hash technique. I'll see them > and try to allowing the number of partitions to be changed. > > Thanks for showing interest, I was also talking about this with Robert Haas and hacking on this, here is what we came up with this. If we want to introduce hash partitioning without syntax contort and minimal movement while changing hash partitions (ADD-DROP/ATTACH-DETACH operation), at start I thought we could pick up linear hashing, because of in both the hashing we might need to move approx tot_num_of_tuple/tot_num_of_partitions tuples at adding new partition and no row moment required at dropping partitioning. With further thinking and talking through the idea of using linear hashing with my team, we realized that has some problems specially during pg_dump and pg_upgrade. Both a regular pg_dump and the binary-upgrade version of pg_dump which is used by pg_restore need to maintain the identity of the partitions. We can't rely on things like OID order which may be unstable, or name order which might not match the order in which partitions were added. So somehow the partition position would need to be specified explicitly. So later we came up with some syntax like this (just fyi, this doesn't add any new keywords): create table foo (a integer, b text) partition by hash (a); create table foo1 partition of foo with (modulus 4, remainder 0); create table foo2 partition of foo with (modulus 8, remainder 1); -- legal, modulus doesn't need to match create table foo3 partition of foo with (modulus 8, remainder 4); -- illegal, overlaps foo1 Here we need to enforce a rule that every modulus must be a factor of the next larger modulus. So, for example, if you have a bunch of partitions that all have modulus 5, you can add a new partition with modulus 10 or a new partition with modulus 15, but you cannot add both a partition with modulus 10 and a partition with modulus 15, because 10 is not a factor of 15. However, you could simultaneously use modulus 4, modulus 8, modulus 16, and modulus 32 if you wished, because each modulus is a factor of the next larger one. You could also use modulus 10, modulus 20, and modulus 60. But you could not use modulus 10, modulus 15, and modulus 60, because while both of the smaller module are factors of 60, it is not true that each is a factor of the next. Other advantages with this rule are: 1. Dropping (or detaching) and adding (or attaching) a partition can never cause the rule to be violated. 2. We can easily build a tuple-routing data structure based on the largest modulus. For example: If the user has partition 1 with (modulus 2, remainder 1), partition 2 with (modulus 4, remainder 2), partition 3 with (modulus 8, remainder 0) and partition 4 with (modulus 8, remainder 4), then we can build the following tuple routing array in the relcache: == lookup table for hashvalue % 8 == 0 => p3 1 => p1 2 => p2 3 => p1 4 => p4 5 => p1 6 => p2 7 => p1 3. It's also quite easy to test with a proposed new partition overlaps with any existing partition. Just build the mapping array and see if you ever end up trying to assign a partition to a slot that's already been assigned to some other partition. We can still work on the proposed syntax - and I am open for suggestions. One more thought is to use FOR VALUES HAVING like: CREATE TABLE foo1 PARTITION OF foo FOR VALUES HAVING (modulus 2, remainder 1); But still more thoughts/inputs welcome here. Attached patch implements former syntax, here is quick demonstration: 1.CREATE : create table foo (a integer, b text) partition by hash (a); create table foo1 partition of foo with (modulus 2, remainder 1); create table foo2 partition of foo with (modulus 4, remainder 2); create table foo3 partition of foo with (modulus 8, remainder 0); create table foo4 partition of foo with (modulus 8, remainder 4); 2. Display parent table info: postgres=# \d+ foo Table "public.foo" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- a | integer | | | | plain | | b | text | | | | extended | | Partition key: HASH (a) Partitions: foo1 WITH (modulus 2, remainder 1), foo2 WITH (modulus 4, remainder 2), foo3 WITH (modulus 8, remainder 0), foo4 WITH (modulus 8, remainder 4) 3. Display child table info: postgres=# \d+ foo1 Table "public.foo1" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+---------+-----------+----------+---------+----------+--------------+------------- a | integer | | | | plain | | b | text | | | | extended | | Partition of: foo WITH (modulus 2, remainder 1) 4. INSERT: postgres=# insert into foo select i, 'abc' from generate_series(1,10) i; INSERT 0 10 postgres=# select tableoid::regclass as part, * from foo; part | a | b ------+----+----- foo1 | 3 | abc foo1 | 4 | abc foo1 | 7 | abc foo1 | 10 | abc foo2 | 1 | abc foo2 | 2 | abc foo2 | 9 | abc foo3 | 6 | abc foo4 | 5 | abc foo4 | 8 | abc (10 rows) TODOs. 1. Maybe need some work in the CREATE TABLE .. PARTITION OF .. syntax. 2. Trim regression tests (if require). 3. Documentation Thoughts/Comments? -
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-03T11:01:57Z
On Thu, 2 Mar 2017 18:33:42 +0530 amul sul <sulamul@gmail.com> wrote: Thank you for the patch. This is very interesting. I'm going to look into your code and write a feedback later. > On Wed, Mar 1, 2017 at 3:50 PM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > > > [....] > > > > I Agree that it is unavoidable partitions number in modulo hashing, > > > but we can do in other hashing technique. Have you had thought about > > > Linear hashing[1] or Consistent hashing[2]? This will allow us to > > > add/drop > > > partition with minimal row moment. > > > > Thank you for your information of hash technique. I'll see them > > and try to allowing the number of partitions to be changed. > > > > > Thanks for showing interest, I was also talking about this with Robert Haas > and > hacking on this, here is what we came up with this. > > If we want to introduce hash partitioning without syntax contort and minimal > movement while changing hash partitions (ADD-DROP/ATTACH-DETACH operation), > at start I thought we could pick up linear hashing, because of in both the > hashing we might need to move approx tot_num_of_tuple/tot_num_of_partitions > tuples at adding new partition and no row moment required at dropping > partitioning. > > With further thinking and talking through the idea of using linear hashing > with my team, we realized that has some problems specially during pg_dump > and pg_upgrade. Both a regular pg_dump and the binary-upgrade version of > pg_dump which is used by pg_restore need to maintain the identity of the > partitions. We can't rely on things like OID order which may be unstable, or > name order which might not match the order in which partitions were added. > So > somehow the partition position would need to be specified explicitly. > > So later we came up with some syntax like this (just fyi, this doesn't add > any new keywords): > > create table foo (a integer, b text) partition by hash (a); > create table foo1 partition of foo with (modulus 4, remainder 0); > create table foo2 partition of foo with (modulus 8, remainder 1); -- > legal, modulus doesn't need to match > create table foo3 partition of foo with (modulus 8, remainder 4); -- > illegal, overlaps foo1 > > Here we need to enforce a rule that every modulus must be a factor of the > next > larger modulus. So, for example, if you have a bunch of partitions that all > have > modulus 5, you can add a new partition with modulus 10 or a new partition > with > modulus 15, but you cannot add both a partition with modulus 10 and a > partition > with modulus 15, because 10 is not a factor of 15. However, you could > simultaneously use modulus 4, modulus 8, modulus 16, and modulus 32 if you > wished, because each modulus is a factor of the next larger one. You could > also use modulus 10, modulus 20, and modulus 60. But you could not use > modulus > 10, modulus 15, and modulus 60, because while both of the smaller module are > factors of 60, it is not true that each is a factor of the next. > > Other advantages with this rule are: > > 1. Dropping (or detaching) and adding (or attaching) a partition can never > cause the rule to be violated. > > 2. We can easily build a tuple-routing data structure based on the largest > modulus. > > For example: If the user has > partition 1 with (modulus 2, remainder 1), > partition 2 with (modulus 4, remainder 2), > partition 3 with (modulus 8, remainder 0) and > partition 4 with (modulus 8, remainder 4), > > then we can build the following tuple routing array in the relcache: > > == lookup table for hashvalue % 8 == > 0 => p3 > 1 => p1 > 2 => p2 > 3 => p1 > 4 => p4 > 5 => p1 > 6 => p2 > 7 => p1 > > 3. It's also quite easy to test with a proposed new partition overlaps with > any > existing partition. Just build the mapping array and see if you ever end up > trying to assign a partition to a slot that's already been assigned to some > other partition. > > We can still work on the proposed syntax - and I am open for suggestions. > One > more thought is to use FOR VALUES HAVING like: > CREATE TABLE foo1 PARTITION OF foo FOR VALUES HAVING (modulus 2, remainder > 1); > > But still more thoughts/inputs welcome here. > > Attached patch implements former syntax, here is quick demonstration: > > 1.CREATE : > create table foo (a integer, b text) partition by hash (a); > create table foo1 partition of foo with (modulus 2, remainder 1); > create table foo2 partition of foo with (modulus 4, remainder 2); > create table foo3 partition of foo with (modulus 8, remainder 0); > create table foo4 partition of foo with (modulus 8, remainder 4); > > 2. Display parent table info: > postgres=# \d+ foo > Table "public.foo" > Column | Type | Collation | Nullable | Default | Storage | Stats > target | Description > --------+---------+-----------+----------+---------+----------+--------------+------------- > a | integer | | | | plain | > | > b | text | | | | extended | > | > Partition key: HASH (a) > Partitions: foo1 WITH (modulus 2, remainder 1), > foo2 WITH (modulus 4, remainder 2), > foo3 WITH (modulus 8, remainder 0), > foo4 WITH (modulus 8, remainder 4) > > 3. Display child table info: > postgres=# \d+ foo1 > Table "public.foo1" > Column | Type | Collation | Nullable | Default | Storage | Stats > target | Description > --------+---------+-----------+----------+---------+----------+--------------+------------- > a | integer | | | | plain | > | > b | text | | | | extended | > | > Partition of: foo WITH (modulus 2, remainder 1) > > 4. INSERT: > postgres=# insert into foo select i, 'abc' from generate_series(1,10) i; > INSERT 0 10 > > postgres=# select tableoid::regclass as part, * from foo; > part | a | b > ------+----+----- > foo1 | 3 | abc > foo1 | 4 | abc > foo1 | 7 | abc > foo1 | 10 | abc > foo2 | 1 | abc > foo2 | 2 | abc > foo2 | 9 | abc > foo3 | 6 | abc > foo4 | 5 | abc > foo4 | 8 | abc > (10 rows) > > TODOs. > 1. Maybe need some work in the CREATE TABLE .. PARTITION OF .. syntax. > 2. Trim regression tests (if require). > 3. Documentation > > Thoughts/Comments? -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Greg Stark <stark@mit.edu> — 2017-03-03T11:30:17Z
On 2 March 2017 at 13:03, amul sul <sulamul@gmail.com> wrote: > create table foo (a integer, b text) partition by hash (a); > create table foo1 partition of foo with (modulus 4, remainder 0); > create table foo2 partition of foo with (modulus 8, remainder 1); -- legal, > modulus doesn't need to match > create table foo3 partition of foo with (modulus 8, remainder 4); -- > illegal, overlaps foo1 Instead of using modulus, why not just divide up the range of hash keys using ranges? That should be just as good for a good hash function (effectively using the high bits instead of the low bits of the hash value). And it would mean you could reuse the machinery for list partitioning for partition exclusion. It also has the advantage that it's easier to see how to add more partitions. You just split all the ranges and (and migrate the data...). There's even the possibility of having uneven partitions if you have a data distribution skew -- which can happen even if you have a good hash function. In a degenerate case you could have a partition for a single hash of a particularly common value then a reasonable number of partitions for the remaining hash ranges. -- greg
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-03-03T13:33:22Z
On Fri, Mar 3, 2017 at 5:00 PM, Greg Stark <stark@mit.edu> wrote: > On 2 March 2017 at 13:03, amul sul <sulamul@gmail.com> wrote: > > create table foo (a integer, b text) partition by hash (a); > > create table foo1 partition of foo with (modulus 4, remainder 0); > > create table foo2 partition of foo with (modulus 8, remainder 1); -- > legal, > > modulus doesn't need to match > > create table foo3 partition of foo with (modulus 8, remainder 4); -- > > illegal, overlaps foo1 > > > Instead of using modulus, why not just divide up the range of hash > keys using ranges? > > That should be just as good for a good hash function (effectively using the high bits instead of the low bits of > the hash value). And it would mean you could reuse the machinery for > list partitioning for partition exclusion. > It also has the advantage that it's easier to see how to add more > partitions. You just split all the ranges and (and migrate the > data...). There's even the possibility of having uneven partitions if > you have a data distribution skew -- which can happen even if you have > a good hash function. In a degenerate case you could have a partition > for a single hash of a particularly common value then a reasonable > number of partitions for the remaining hash ranges. > Initially we had to have somewhat similar thought to make a range of hash values for each partition, using the same half-open interval syntax we use in general: create table foo (a integer, b text) partition by hash (a); create table foo1 partition of foo for values from (0) to (1073741824); create table foo2 partition of foo for values from (1073741824) to (-2147483648); create table foo3 partition of foo for values from (-2147483648) to (-1073741824); create table foo4 partition of foo for values from (-1073741824) to (0); That's really nice for the system, but not so much for the users. The system can now generate each partition constraint correctly immediately upon seeing the SQL statement for the corresponding table, which is very desirable. However, users are not likely to know that the magic numbers to distribute keys equally across four partitions are 1073741824, -2147483648, and -1073741824. So it's pretty user-unfriendly. Regards, Amul
-
Re: [POC] hash partitioning
David Steele <david@pgmasters.net> — 2017-03-14T14:08:14Z
On 3/3/17 8:33 AM, amul sul wrote: > On Fri, Mar 3, 2017 at 5:00 PM, Greg Stark <stark@mit.edu > > It also has the advantage that it's easier to see how to add more > partitions. You just split all the ranges and (and migrate the > data...). There's even the possibility of having uneven partitions if > you have a data distribution skew -- which can happen even if you have > a good hash function. In a degenerate case you could have a partition > for a single hash of a particularly common value then a reasonable > number of partitions for the remaining hash ranges. > > Initially > we > had > to have > somewhat similar thought to make a range of hash > values for > > each partition, using the same half-open interval syntax we use in general: > <...> > So it's pretty > > user-unfriendly. This patch is marked as POC and after a read-through I agree that's exactly what it is. As such, I'm not sure it belongs in the last commitfest. Furthermore, there has not been any activity or a new patch in a while and we are halfway through the CF. Please post an explanation for the delay and a schedule for the new patch. If no patch or explanation is posted by 2017-03-17 AoE I will mark this submission "Returned with Feedback". -- -David david@pgmasters.net
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-03-15T16:25:23Z
On Tue, Mar 14, 2017 at 10:08 AM, David Steele <david@pgmasters.net> wrote: > This patch is marked as POC and after a read-through I agree that's > exactly what it is. Just out of curiosity, were you looking at Nagata-san's patch, or Amul's? > As such, I'm not sure it belongs in the last > commitfest. Furthermore, there has not been any activity or a new patch > in a while and we are halfway through the CF. > > Please post an explanation for the delay and a schedule for the new > patch. If no patch or explanation is posted by 2017-03-17 AoE I will > mark this submission "Returned with Feedback". Regrettably, I do think it's too late to squeeze hash partitioning into v10, but I plan to try to get something committed for v11. I was heavily involved in the design of Amul's patch, and I think that design solves several problems that would be an issue for us if we did as Nagata-san is proposing. For example, he proposed this: CREATE TABLE h1 PARTITION OF h; CREATE TABLE h2 PARTITION OF h; CREATE TABLE h3 PARTITION OF h; That looks OK if you are thinking of typing this in interactively, but if you're doing a pg_dump, maybe with --binary-upgrade, you don't want the meaning of a series of nearly-identical SQL commands to depend on the dump ordering. You want it to be explicit in the SQL command which partition is which, and Amul's patch solves that problem. Also, Nagata-san's proposal doesn't provide any way to increase the number of partitions later, and Amul's approach gives you some options there. I'm not sure those options are as good as we'd like them to be, and if not then we may need to revise the approach, but I'm pretty sure having no strategy at all for changing the partition count is not good enough. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
David Steele <david@pgmasters.net> — 2017-03-15T16:39:12Z
On 3/15/17 12:25 PM, Robert Haas wrote: > On Tue, Mar 14, 2017 at 10:08 AM, David Steele <david@pgmasters.net> wrote: >> This patch is marked as POC and after a read-through I agree that's >> exactly what it is. > > Just out of curiosity, were you looking at Nagata-san's patch, or Amul's? Both - what I was looking for was some kind of reconciliation between the two patches and I didn't find that. It seemed from the thread that Yugo intended to pull Amul's changes/idea into his patch. >> As such, I'm not sure it belongs in the last >> commitfest. Furthermore, there has not been any activity or a new patch >> in a while and we are halfway through the CF. >> >> Please post an explanation for the delay and a schedule for the new >> patch. If no patch or explanation is posted by 2017-03-17 AoE I will >> mark this submission "Returned with Feedback". > > Regrettably, I do think it's too late to squeeze hash partitioning > into v10, but I plan to try to get something committed for v11. It would certainly be a nice feature to have. > I was > heavily involved in the design of Amul's patch, and I think that > design solves several problems that would be an issue for us if we did > as Nagata-san is proposing. For example, he proposed this: > > CREATE TABLE h1 PARTITION OF h; > CREATE TABLE h2 PARTITION OF h; > CREATE TABLE h3 PARTITION OF h; > > That looks OK if you are thinking of typing this in interactively, but > if you're doing a pg_dump, maybe with --binary-upgrade, you don't want > the meaning of a series of nearly-identical SQL commands to depend on > the dump ordering. You want it to be explicit in the SQL command > which partition is which, and Amul's patch solves that problem. OK, it wasn't clear to me that this was the case because of the stated user-unfriendliness. > Also, > Nagata-san's proposal doesn't provide any way to increase the number > of partitions later, and Amul's approach gives you some options there. > I'm not sure those options are as good as we'd like them to be, and if > not then we may need to revise the approach, but I'm pretty sure > having no strategy at all for changing the partition count is not good > enough. Agreed. Perhaps both types of syntax should be supported, one that is friendly to users and one that is precise for dump tools and those who care get in the weeds. -- -David david@pgmasters.net
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-03-15T17:01:30Z
On Wed, Mar 15, 2017 at 12:39 PM, David Steele <david@pgmasters.net> wrote: > Agreed. Perhaps both types of syntax should be supported, one that is > friendly to users and one that is precise for dump tools and those who > care get in the weeds. Eventually, sure. For the first version, I want to skip the friendly syntax and just add the necessary syntax. That makes it easier to make sure that pg_dump and everything are working the way you want. Range and list partitioning could potentially grow convenience syntax around partition creation, too, but that wasn't essential for the first patch, so we cut it. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-03-17T11:57:23Z
On Tue, 14 Mar 2017 10:08:14 -0400 David Steele <david@pgmasters.net> wrote: > Please post an explanation for the delay and a schedule for the new > patch. If no patch or explanation is posted by 2017-03-17 AoE I will > mark this submission "Returned with Feedback". I am sorry for my late response. I had not a enough time because I had a business trip and was busy for other works. I agree that fixing the number of partitions is bad and a way to increase or decrease partitions should be provided. I also think using linear hashing would be good as Amul is mentioning, but I have not implemented it in my patch yet. I also understanded that my design has a problem during pg_dump and pg_upgrade, and that some information to identify the partition is required not depending the command order. However, I feel that Amul's design is a bit complicated with the rule to specify modulus. I think we can use simpler syntax, for example, as below. CREATE TABLE h1 PARTITION OF h FOR (0); CREATE TABLE h2 PARTITION OF h FOR (1); CREATE TABLE h3 PARTITION OF h FOR (2); If user want to user any complicated partitioning rule, it can be defined by specifying a user-defined hash function at creating partitioned table. If the hash function is omitted, we will be able to use default hash operator class as well as in Amul's patch. Attached is the updated patch taking the comments from Aleksander and Rushabh. HASH keyword and unnecessary spaces are removed, and some comments are added. Thanks, -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Tatsuo Ishii <ishii@sraoss.co.jp> — 2017-03-28T01:06:46Z
> Please post an explanation for the delay and a schedule for the new > patch. If no patch or explanation is posted by 2017-03-17 AoE I will > mark this submission "Returned with Feedback". Depite the fact that Yugo has posted a new patch on 2017-03-17, this item had been marked as "Returned with Feedback". I don't know why. -- Tatsuo Ishii SRA OSS, Inc. Japan English: http://www.sraoss.co.jp/index_en.php Japanese:http://www.sraoss.co.jp
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-04-13T20:40:29Z
On Fri, Mar 17, 2017 at 7:57 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > I also understanded that my design has a problem during pg_dump and > pg_upgrade, and that some information to identify the partition > is required not depending the command order. However, I feel that > Amul's design is a bit complicated with the rule to specify modulus. > > I think we can use simpler syntax, for example, as below. > > CREATE TABLE h1 PARTITION OF h FOR (0); > CREATE TABLE h2 PARTITION OF h FOR (1); > CREATE TABLE h3 PARTITION OF h FOR (2); I don't see how that can possibly work. Until you see all the table partitions, you don't know what the partitioning constraint for any given partition should be, which seems to me to be a fatal problem. I agree that Amul's syntax - really, I proposed it to him - is not the simplest, but I think all the details needed to reconstruct the partitioning constraint need to be explicit. Otherwise, I'm pretty sure things we're going to have lots of problems that we can't really solve cleanly. We can later invent convenience syntax that makes common configurations easier to set up, but we should invent the syntax that spells out all the details first. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-04-14T08:23:00Z
On Thu, 13 Apr 2017 16:40:29 -0400 Robert Haas <robertmhaas@gmail.com> wrote: > On Fri, Mar 17, 2017 at 7:57 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > > I also understanded that my design has a problem during pg_dump and > > pg_upgrade, and that some information to identify the partition > > is required not depending the command order. However, I feel that > > Amul's design is a bit complicated with the rule to specify modulus. > > > > I think we can use simpler syntax, for example, as below. > > > > CREATE TABLE h1 PARTITION OF h FOR (0); > > CREATE TABLE h2 PARTITION OF h FOR (1); > > CREATE TABLE h3 PARTITION OF h FOR (2); > > I don't see how that can possibly work. Until you see all the table > partitions, you don't know what the partitioning constraint for any > given partition should be, which seems to me to be a fatal problem. If a partition has an id, the partitioning constraint can be written as hash_func(hash_key) % N = id wehre N is the number of paritions. Doesn't it work? > I agree that Amul's syntax - really, I proposed it to him - is not the > simplest, but I think all the details needed to reconstruct the > partitioning constraint need to be explicit. Otherwise, I'm pretty > sure things we're going to have lots of problems that we can't really > solve cleanly. We can later invent convenience syntax that makes > common configurations easier to set up, but we should invent the > syntax that spells out all the details first. I have a question about Amul's syntax. After we create partitions as followings, create table foo (a integer, b text) partition by hash (a); create table foo1 partition of foo with (modulus 2, remainder 0); create table foo2 partition of foo with (modulus 2, remainder 1); we cannot create any additional partitions for the partition. Then, after inserting records into foo1 and foo2, how we can increase the number of partitions? > > -- > Robert Haas > EnterpriseDB: http://www.enterprisedb.com > The Enterprise PostgreSQL Company -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-04-14T13:05:14Z
On Fri, Apr 14, 2017 at 4:23 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > On Thu, 13 Apr 2017 16:40:29 -0400 > Robert Haas <robertmhaas@gmail.com> wrote: >> On Fri, Mar 17, 2017 at 7:57 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: >> > I also understanded that my design has a problem during pg_dump and >> > pg_upgrade, and that some information to identify the partition >> > is required not depending the command order. However, I feel that >> > Amul's design is a bit complicated with the rule to specify modulus. >> > >> > I think we can use simpler syntax, for example, as below. >> > >> > CREATE TABLE h1 PARTITION OF h FOR (0); >> > CREATE TABLE h2 PARTITION OF h FOR (1); >> > CREATE TABLE h3 PARTITION OF h FOR (2); >> >> I don't see how that can possibly work. Until you see all the table >> partitions, you don't know what the partitioning constraint for any >> given partition should be, which seems to me to be a fatal problem. > > If a partition has an id, the partitioning constraint can be written as > > hash_func(hash_key) % N = id > > wehre N is the number of paritions. Doesn't it work? Only if you know the number of partitions. But with your syntax, after seeing only the first of the CREATE TABLE .. PARTITION OF commands, what should the partition constraint be? It depends on how many more such commands appear later in the dump file, which you do not know at that point. >> I agree that Amul's syntax - really, I proposed it to him - is not the >> simplest, but I think all the details needed to reconstruct the >> partitioning constraint need to be explicit. Otherwise, I'm pretty >> sure things we're going to have lots of problems that we can't really >> solve cleanly. We can later invent convenience syntax that makes >> common configurations easier to set up, but we should invent the >> syntax that spells out all the details first. > > I have a question about Amul's syntax. After we create partitions > as followings, > > create table foo (a integer, b text) partition by hash (a); > create table foo1 partition of foo with (modulus 2, remainder 0); > create table foo2 partition of foo with (modulus 2, remainder 1); > > we cannot create any additional partitions for the partition. > > Then, after inserting records into foo1 and foo2, how we can > increase the number of partitions? You can detach foo1, create two new partitions with modulus 4 and remainders 0 and 2, and move the data over from the old partition. I realize that's not as automated as you might like, but it's no worse than what is currently required for list and range partitioning when you split a partition. Someday we might build in tools to do that kind of data migration automatically, but right now we have none. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-04-17T08:50:42Z
On Fri, 14 Apr 2017 09:05:14 -0400 Robert Haas <robertmhaas@gmail.com> wrote: > On Fri, Apr 14, 2017 at 4:23 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > > On Thu, 13 Apr 2017 16:40:29 -0400 > > Robert Haas <robertmhaas@gmail.com> wrote: > >> On Fri, Mar 17, 2017 at 7:57 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > >> > I also understanded that my design has a problem during pg_dump and > >> > pg_upgrade, and that some information to identify the partition > >> > is required not depending the command order. However, I feel that > >> > Amul's design is a bit complicated with the rule to specify modulus. > >> > > >> > I think we can use simpler syntax, for example, as below. > >> > > >> > CREATE TABLE h1 PARTITION OF h FOR (0); > >> > CREATE TABLE h2 PARTITION OF h FOR (1); > >> > CREATE TABLE h3 PARTITION OF h FOR (2); > >> > >> I don't see how that can possibly work. Until you see all the table > >> partitions, you don't know what the partitioning constraint for any > >> given partition should be, which seems to me to be a fatal problem. > > > > If a partition has an id, the partitioning constraint can be written as > > > > hash_func(hash_key) % N = id > > > > wehre N is the number of paritions. Doesn't it work? > > Only if you know the number of partitions. But with your syntax, > after seeing only the first of the CREATE TABLE .. PARTITION OF > commands, what should the partition constraint be? It depends on how > many more such commands appear later in the dump file, which you do > not know at that point. I thought that the partition constraint could be decided every time a new partition is created or attached, and that it woule be needed to relocate records automatically when the partition configuration changes. However, I have come to think that the automatic relocation might not be needed at this point. > > >> I agree that Amul's syntax - really, I proposed it to him - is not the > >> simplest, but I think all the details needed to reconstruct the > >> partitioning constraint need to be explicit. Otherwise, I'm pretty > >> sure things we're going to have lots of problems that we can't really > >> solve cleanly. We can later invent convenience syntax that makes > >> common configurations easier to set up, but we should invent the > >> syntax that spells out all the details first. > > > > I have a question about Amul's syntax. After we create partitions > > as followings, > > > > create table foo (a integer, b text) partition by hash (a); > > create table foo1 partition of foo with (modulus 2, remainder 0); > > create table foo2 partition of foo with (modulus 2, remainder 1); > > > > we cannot create any additional partitions for the partition. > > > > Then, after inserting records into foo1 and foo2, how we can > > increase the number of partitions? > > You can detach foo1, create two new partitions with modulus 4 and > remainders 0 and 2, and move the data over from the old partition. > > I realize that's not as automated as you might like, but it's no worse > than what is currently required for list and range partitioning when > you split a partition. Someday we might build in tools to do that > kind of data migration automatically, but right now we have none. Thanks. I understood it. The automatic data migration feature would be better to be implemented separately. > > -- > Robert Haas > EnterpriseDB: http://www.enterprisedb.com > The Enterprise PostgreSQL Company -- Yugo Nagata <nagata@sraoss.co.jp>
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-04-20T20:27:57Z
On Mon, Apr 17, 2017 at 10:50 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > I thought that the partition constraint could be decided every > time a new partition is created or attached, and that it woule be > needed to relocate records automatically when the partition configuration > changes. However, I have come to think that the automatic relocation > might not be needed at this point. Great! I am glad that we are in agreement about this point. However, actually I think the problem is worse than you are supposing. If you're restoring from a database dump created by pg_dump, then we will try to load data into each individual partition using COPY. Direct insertions into individual partitions are not subject to tuple routing -- that only affects inserts into the parent table. So if the partition constraint is not correct immediately after creating the table, the COPY which tries to repopulate that partition will probably fail with an ERROR, because there will likely be at least one row (probably many) which match the "final" partition constraint but not the "interim" partition constraint that we'd have after recreating some but not all of the hash partitions. For example, if we had created 2 partitions so far out of a total of 3, we'd think the constraint ought to be (hashvalue % 2) == 1 rather than (hashvalue % 3) == 1, which obviously will likely lead to the dump failing to restore properly. So, I think we really need something like the syntax in Amul's patch in order for this to work at all. Of course, the details can be changed according to what seems best but I think the overall picture is about right. There is another point that I think also needs thought; not sure if either your patch or Amit's patch handles it: constraint exclusion will not work for hash partitioning. For example, if the partitioning constraint for each partition is of the form (hash(partcol) % 6) == SOME_VALUE_BETWEEN_0_AND_5, and the query contains the predicate partcol == 37, constraint exclusion will not be able to prove anything about which partitions need to be scanned. Amit Langote has noted a few times that partitioning relies on constraint exclusion *for now*, which implies, I think, that he's thought about changing it to work differently. I think that would be a good idea. For range partitioning or list partitioning, a special-purpose mechanism for partitioning could be much faster than constraint exclusion, since it knows that partcol == 37 can only be true for one partition and can reuse the tuple-routing infrastructure to figure out which one it is. And that approach can also work for hash partitioning, where constraint exclusion is useless. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-04-26T20:12:21Z
On Thu, Apr 20, 2017 at 4:27 PM, Robert Haas <robertmhaas@gmail.com> wrote: > So, I think we really need something like the syntax in Amul's patch > in order for this to work at all. Of course, the details can be > changed according to what seems best but I think the overall picture > is about right. I spent some time today looking at these patches. It seems like there is some more work still needed here to produce something committable regardless of which way we go, but I am inclined to think that Amul's patch is a better basis for work going forward than Nagata-san's patch. Here are some general comments on the two patches: - As noted above, the syntax implemented by Amul's patch allows us to know the final partition constraint right away. Nagata-san's proposed syntax does not do that. Also, Amul's syntax allows for a way to split partitions (awkwardly, but we can improve it later); Nagata-san's doesn't provide any method at all. - Amul's patch derives the hash function to be used from the relevant hash opclass, whereas Nagata-san's patch requires the user to specify it explicitly. I think that there is no real use case for a user providing a custom hash function, and that using the opclass machinery to derive the function to be used is better. If a user DOES want to provide their own, they can always create a custom opclass with the appropriate support function and specify that it should be used when creating a hash-partitioned table, but most users will be happy for the system to supply the appropriate function automatically. - In Nagata-san's patch, convert_expr_for_hash() looks up a function called "abs" and an operator called "%" by name, which is not a good idea. We don't want to just find whatever is in the current search path; we want to make sure we're using the system-defined operators that we intend to be using. Amul's patch builds the constraint using a hard-coded internal function OID, F_SATISFIES_HASH_PARTITION. That's a lot more robust, and it's also likely to be faster because, in Amul's patch, we only call one function at the SQL level (satisfies_hash_partition), whereas in Nagata-san's patch, we'll end up calling three (abs, %, =). Nagata-san's version of get_qual_for_hash is implicated in this problem, too: it's looking up the operator to use based on the operator name (=) rather than the opclass properties. Note that the existing get_qual_for_list() and get_qual_for_range() use opclass properties, as does Amul's patch. - Nagata-san's patch only supports hash partitioning based on a single column, and that column must be NOT NULL. Amul's patch does not have these restrictions. - Neither patch contains any documentation updates, which is bad. Nagata-san's patch also contains no regression tests. Amul's patch does, but they need to be rebased, since they no longer apply, and I think some other improvements are possible as well. It's probably not necessary to re-test things like whether temp and non-temp tables can be mixed within a partitioning hierarchy, but there should be tests that tuple routing actually works. The case where it fails because no matching partition exists should be tested as well. Also, the tests should validate not only that FOR VALUES isn't accept when creating a hash partition (which they do) but also that WITH (...) isn't accepted for a range or list partition (which they do not). - When I try to do even something pretty trivial with Nagata-san's patches, it crashes: rhaas=# create table foo (a int, b text) partition by hash (a) partitions 7 using hashint4; CREATE TABLE rhaas=# create table foo1 partition of foo; <server crash> The ruleutils.c support in Nagata-san's patch is broken. If you execute the non-crashing statement from the above example and then run pg_dump, it doesn't dump "partitions 7 using hashint4", which means that the syntax in the dump is invalid. - Neither patch does anything about the fact that constraint exclusion won't work for hash partitioning. I mentioned this issue upthread in the last paragraph of http://postgr.es/m/CA+Tgmob7RsN5A=ehgYbLPx--c5CmptrK-dB=Y-v--o+TKyfteA@mail.gmail.com and I think it's imperative that we fix it in some way before we think about committing any of this. I think that needs to be done by extending relation_excluded_by_constraints() to have some specific smarts about hash partitioning, and maybe other kinds of partitioning as well (because it could probably be made much faster for list and range partitioning, too). - Amul's patch should perhaps update tab completion support: create table foo1 partition of foo <tab> completes with "for values", but now "with" will be another option. - Amul's patch probably needs to validate the WITH () clause more thoroughly. I bet you get a not-very-great error message if you leave out "modulus" and no error at all if you leave out "remainder". This is not yet a detailed review - I may be missing things, and review and commentary from others is welcome. If there is no major disagreement with the idea of moving forward using Amul's patch as a base, then I will do a more detailed review of that patch (or, hopefully, an updated version that addresses the above comments). -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Jeff Davis <pgsql@j-davis.com> — 2017-05-03T01:01:31Z
On Tue, Feb 28, 2017 at 6:33 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > In this patch, user can specify a hash function USING. However, > we migth need default hash functions which are useful and > proper for hash partitioning. I suggest that we consider the hash functions more carefully. This is (effectively) an on-disk format so it can't be changed easily later. 1. Consider a partition-wise join of two hash-partitioned tables. If that's a hash join, and we just use the hash opclass, we immediately lose some useful bits of the hash function. Same for hash aggregation where the grouping key is the partition key. To fix this, I think we need to include a salt in the hash API. Each level of hashing can choose a random salt. 2. Consider a partition-wise join where the join keys are varchar(10) and char(10). We can't do that join if we just use the existing hash strategy, because 'foo' = 'foo ' should match, but those values have different hashes when using the standard hash opclass. To fix this, we need to be smarter about normalizing values at a logical level before hashing. We can take this to varying degrees, perhaps even normalizing an integer to a numeric before hashing so that you can do a cross-type join on int=numeric. Furthermore, we need catalog metadata to indicate which hash functions are suitable for which cross-type comparisons. Or, to put it the other way, which typecasts preserve the partitioning. 3. We might want to use a hash function that is a little slower that is more resistant to collisions. We may even want to use a 64-bit hash. My opinion is that we should work on this hashing infrastructure first, and then support the DDL. If we get the hash functions right, that frees us up to create better plans, with better push-downs, which will be good for parallel query. Regards, Jeff Davis -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-03T02:01:07Z
On Tue, May 2, 2017 at 9:01 PM, Jeff Davis <pgsql@j-davis.com> wrote: > On Tue, Feb 28, 2017 at 6:33 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: >> In this patch, user can specify a hash function USING. However, >> we migth need default hash functions which are useful and >> proper for hash partitioning. > > I suggest that we consider the hash functions more carefully. This is > (effectively) an on-disk format so it can't be changed easily later. > > 1. Consider a partition-wise join of two hash-partitioned tables. If > that's a hash join, and we just use the hash opclass, we immediately > lose some useful bits of the hash function. Same for hash aggregation > where the grouping key is the partition key. Hmm, that could be a problem in some cases. I think there's probably much less of a problem if the modulus isn't a power of two? > To fix this, I think we need to include a salt in the hash API. Each > level of hashing can choose a random salt. Do you mean that we'd salt partitioning hashing differently from grouping hashing which would be salted different from aggregation hashing which, I suppose, would be salted differently from hash index hashing? Or do you mean that you'd have to specify a salt when creating a hash-partitioned table, and make sure it's the same across all compatibly partitioned tables you might want to hash-join? That latter sounds unappealing. > 2. Consider a partition-wise join where the join keys are varchar(10) > and char(10). We can't do that join if we just use the existing hash > strategy, because 'foo' = 'foo ' should match, but those values > have different hashes when using the standard hash opclass. > > To fix this, we need to be smarter about normalizing values at a > logical level before hashing. We can take this to varying degrees, > perhaps even normalizing an integer to a numeric before hashing so > that you can do a cross-type join on int=numeric. > > Furthermore, we need catalog metadata to indicate which hash functions > are suitable for which cross-type comparisons. Or, to put it the other > way, which typecasts preserve the partitioning. You're basically describing what a hash opfamily already does, except that we don't have a single opfamily that covers both varchar(10) and char(10), nor do we have one that covers both int and numeric. We have one that covers int2, int4, and int8, though. If somebody wanted to make the ones you're suggesting, there's nothing preventing it, although I'm not sure exactly how we'd encourage people to start using the new one and deprecating the old one. We don't seem to have a good infrastructure for that. > 3. We might want to use a hash function that is a little slower that > is more resistant to collisions. We may even want to use a 64-bit > hash. > > My opinion is that we should work on this hashing infrastructure > first, and then support the DDL. If we get the hash functions right, > that frees us up to create better plans, with better push-downs, which > will be good for parallel query. I am opposed to linking the fate of this patch to multiple independent, possibly large, possibly difficult, possibly controversial enhancements to the hashing mechanism. If there are simple things that can reasonably be done in this patch to make hash partitioning better, great. If you want to work on improving the hashing mechanism as an independent project, also great. But I think that most people would rather have hash partitioning in v11 than wait for v12 or v13 so that other hashing improvements can be completed; I know I would. If we say "we shouldn't implement hash partitioning because some day we might make incompatible changes to the hashing mechanism" then we'll never implement it, because that will always be true. Even the day after we change it, there still may come a future day when we change it again. The stakes have already been raised by making hash indexes durable; that too is arguably making future changes to the hashing infrastructure harder. But I still think it was the right thing to proceed with that work. If we get 64-bit hash codes in the next release, and we want hash indexes to use them, then we will have to invalidate existing hash indexes (again). That's sad, but not as sad as it would have been to not commit the work to make hash indexes durable. There's a chicken-and-egg problem here: without durable hash indexes and hash partitioning, there's not much incentive to make hashing better, but once we have them, changes create a backward compatibility issue. Such is life; nothing we do is infinitely future-proof. The last significant overhaul of the hashing mechanism that I know about was in 2009, cf. 2604359251d34177a14ef58250d8b4a51d83103b and 8205258fa675115439017b626c4932d5fefe2ea8. Until this email, I haven't seen any complaints about the quality of that hash function either in terms of speed or collision properties - what makes you think those things are serious problems? I *have* heard some interest in widening the output to 64 bits, and also in finding a way to combine multiple hash values in some smarter way than we do at present. Seeding has come up, too. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-03T13:09:15Z
On Thu, Apr 27, 2017 at 1:42 AM, Robert Haas <robertmhaas@gmail.com> wrote: >I spent some time today looking at these patches. It seems like there >is some more work still needed here to produce something committable >regardless of which way we go, but I am inclined to think that Amul's >patch is a better basis for work going forward than Nagata-san's >patch. Here are some general comments on the two patches: Thanks for your time. [...] > - Neither patch contains any documentation updates, which is bad. Fixed in the attached version. > > Nagata-san's patch also contains no regression tests. Amul's patch > does, but they need to be rebased, since they no longer apply, and I > think some other improvements are possible as well. It's probably not > necessary to re-test things like whether temp and non-temp tables can > be mixed within a partitioning hierarchy, but there should be tests > that tuple routing actually works. The case where it fails because no > matching partition exists should be tested as well. Also, the tests > should validate not only that FOR VALUES isn't accept when creating a > hash partition (which they do) but also that WITH (...) isn't accepted > for a range or list partition (which they do not). > Fixed in the attached version. [...] > - Amul's patch should perhaps update tab completion support: create > table foo1 partition of foo <tab> completes with "for values", but now > "with" will be another option. > Fixed in the attached version. > > - Amul's patch probably needs to validate the WITH () clause more > thoroughly. I bet you get a not-very-great error message if you leave > out "modulus" and no error at all if you leave out "remainder". > Thats not true, there will be syntax error if you leave modulus or remainder, see this: postgres=# CREATE TABLE hpart_2 PARTITION OF hash_parted WITH(modulus 4); ERROR: syntax error at or near ")" LINE 1: ...hpart_2 PARTITION OF hash_parted WITH(modulus 4); > > This is not yet a detailed review - I may be missing things, and > review and commentary from others is welcome. If there is no major > disagreement with the idea of moving forward using Amul's patch as a > base, then I will do a more detailed review of that patch (or, > hopefully, an updated version that addresses the above comments). > I have made a smaller change in earlier proposed syntax to create partition to be aligned with current range and list partition syntax, new syntax will be as follow: CREATE TABLE p1 PARTITION OF hash_parted FOR VALUES WITH (modulus 10, remainder 1); Regards, Amul
-
Re: [POC] hash partitioning
Jeff Davis <pgsql@j-davis.com> — 2017-05-04T05:44:54Z
On Tue, May 2, 2017 at 7:01 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Tue, May 2, 2017 at 9:01 PM, Jeff Davis <pgsql@j-davis.com> wrote: >> 1. Consider a partition-wise join of two hash-partitioned tables. If >> that's a hash join, and we just use the hash opclass, we immediately >> lose some useful bits of the hash function. Same for hash aggregation >> where the grouping key is the partition key. > > Hmm, that could be a problem in some cases. I think there's probably > much less of a problem if the modulus isn't a power of two? That's true, but it's awkward to describe that to users. And I think most people would be inclined to use a power-of-two number of partitions, perhaps coming from other systems. >> To fix this, I think we need to include a salt in the hash API. Each >> level of hashing can choose a random salt. > > Do you mean that we'd salt partitioning hashing differently from > grouping hashing which would be salted different from aggregation > hashing which, I suppose, would be salted differently from hash index > hashing? Yes. The way I think about it is that choosing a new random salt is an easy way to get a new hash function. > Or do you mean that you'd have to specify a salt when > creating a hash-partitioned table, and make sure it's the same across > all compatibly partitioned tables you might want to hash-join? That > latter sounds unappealing. I don't see a reason to expose the salt to users. If we found a reason in the future, we could, but it would create all of the problems you are thinking about. >> 2. Consider a partition-wise join where the join keys are varchar(10) >> and char(10). We can't do that join if we just use the existing hash >> strategy, because 'foo' = 'foo ' should match, but those values >> have different hashes when using the standard hash opclass. ... > You're basically describing what a hash opfamily already does, except > that we don't have a single opfamily that covers both varchar(10) and > char(10), nor do we have one that covers both int and numeric. We > have one that covers int2, int4, and int8, though. If somebody wanted > to make the ones you're suggesting, there's nothing preventing it, > although I'm not sure exactly how we'd encourage people to start using > the new one and deprecating the old one. We don't seem to have a good > infrastructure for that. OK. I will propose new hash opfamilies for varchar/bpchar/text, int2/4/8/numeric, and timestamptz/date. One approach is to promote the narrower type to the wider type, and then hash. The problem is that would substantially slow down the hashing of integers, so then we'd need to use one hash opfamily for partitioning and one for hashjoin, and it gets messy. The other approach is to check if the wider type is within the domain of the narrower type, and if so, *demote* the value and then hash. For instance, '4.2'::numeric would hash the same as it does today, but '4'::numeric would hash as an int2. I prefer this approach, and int8 already does something resembling it. For timestamptz/date, it's not nearly as important. >> My opinion is that we should work on this hashing infrastructure >> first, and then support the DDL. If we get the hash functions right, >> that frees us up to create better plans, with better push-downs, which >> will be good for parallel query. > > I am opposed to linking the fate of this patch to multiple > independent, possibly large, possibly difficult, possibly > controversial enhancements to the hashing mechanism. It's a little early in the v11 cycle to be having this argument. Really what I'm saying is that a small effort now may save us a lot of headache later. Regards, Jeff Davis -
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-10T12:34:38Z
On Wed, May 3, 2017 at 6:39 PM, amul sul <sulamul@gmail.com> wrote: > On Thu, Apr 27, 2017 at 1:42 AM, Robert Haas <robertmhaas@gmail.com> wrote: > >> >> This is not yet a detailed review - I may be missing things, and >> review and commentary from others is welcome. If there is no major >> disagreement with the idea of moving forward using Amul's patch as a >> base, then I will do a more detailed review of that patch (or, >> hopefully, an updated version that addresses the above comments). > I agree that Amul's approach makes dump/restore feasible whereas Nagata-san's approach makes that difficult. That is a major plus point about Amul's patch. Also, it makes it possible to implement Nagata-san's syntax, which is more user-friendly in future. Here are some review comments after my initial reading of Amul's patch: Hash partitioning will partition the data based on the hash value of the partition key. Does that require collation? Should we throw an error/warning if collation is specified in PARTITION BY clause? + int *indexes; /* Partition indexes; in case of hash + * partitioned table array length will be + * value of largest modulus, and for others + * one entry per member of the datums array + * (plus one if range partitioned table) */ This may be rewritten as "Partition indexes: For hash partitioned table the number of indexes will be same as the largest modulus. For list partitioned table the number of indexes will be same as the number of datums. For range partitioned table the number of indexes will be number of datums plus one.". You may be able to reword it to a shorter version, but essentially we will have separate description for each strategy. I guess, we need to change the comments for the other members too. For example "datums" does not contain tuples with key->partnatts attributes for hash partitions. It contains a tuple with two attributes, modulus and remainder. We may not want to track null_index separately since rows with NULL partition key will fit in the partition corresponding to the hash value of NULL. OR may be we want to set null_index to partition which contains NULL values, if there is a partition created for corresponding remainder, modulus pair and set has_null accordingly. Accordingly we will need to update the comments. cal_hash_value() may be renamed as calc_has_value() or compute_hash_value()? Should we change the if .. else if .. construct in RelationBuildPartitionDesc() to a switch case? There's very less chance that we will support a fourth partitioning strategy, so if .. else if .. may be fine. + int mod = hbounds[i]->modulus, + place = hbounds[i]->remainder; Although there are places in the code where we separate variable declaration with same type by comma, most of the code declares each variable with the data type on separate line. Should variable "place" be renamed as "remainder" since that's what it is ultimately? RelationBuildPartitionDesc() fills up mapping array but never uses it. In this code the index into mapping array itself is the mapping so it doesn't need to be maintained separately like list partiioning case. Similary next_index usage looks unnecessary, although that probably improves readability, so may be fine. + * for p_p1: satisfies_hash_partition(2, 1, pkey, value) + * for p_p2: satisfies_hash_partition(4, 2, pkey, value) + * for p_p3: satisfies_hash_partition(8, 0, pkey, value) + * for p_p4: satisfies_hash_partition(8, 4, pkey, value) What the function builds is satisfies_hash_partition(2, 1, pkey). I don't see code to add value as an argument to the function. Is that correct? + int modulus = DatumGetInt32(datum); May be you want to rename this variable to greatest_modulus like in the other places. + Assert(spec->modulus > 0 && spec->remainder >= 0); I liked this assertion. Do you want to add spec->modulus > spec->reminder also here? + char *strategy; /* partitioning strategy + ('hash', 'list' or 'range') */ We need the second line to start with '*' +-- check validation when attaching list partitions Do you want to say "hash" instead of "list" here? I think we need to explain the reasoning behind this syntax somewhere as a README or in the documentation or in the comments. Otherwise it's difficult to understand how various pieces of code are related. This is not full review. I am still trying to understand how the hash partitioning implementation fits with list and range partitioning. I am going to continue to review this patch further. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-10T16:38:54Z
On Thu, May 4, 2017 at 1:44 AM, Jeff Davis <pgsql@j-davis.com> wrote: >> Hmm, that could be a problem in some cases. I think there's probably >> much less of a problem if the modulus isn't a power of two? > > That's true, but it's awkward to describe that to users. And I think > most people would be inclined to use a power-of-two number of > partitions, perhaps coming from other systems. Yeah, true. >>> To fix this, I think we need to include a salt in the hash API. Each >>> level of hashing can choose a random salt. >> >> Do you mean that we'd salt partitioning hashing differently from >> grouping hashing which would be salted different from aggregation >> hashing which, I suppose, would be salted differently from hash index >> hashing? > > Yes. The way I think about it is that choosing a new random salt is an > easy way to get a new hash function. OK. One problem, though, is we don't quite have the opclass infrastructure for this. A hash opclass's support function is expected to take one argument, a value of the data type at issue. The first idea that occurred to me was to allow an optional second argument which would be a seed, but that seems like it would require extensive changes to all of the datatype-specific hash functions and some of them would probably emerge noticeably slower. If a function is just calling hash_uint32 right now then I don't see how we're going to replace that with something more complex that folds in a salt without causing performance to drop. Even just the cost of unpacking the extra argument might be noticeable. Another alternative would be to be to add one additional, optional hash opclass support function which takes a value of the type in question as one argument and a seed as a second argument. That seems like it might work OK. Existing code can use the existing support function 1 with no change, and hash partitioning can use support function 2. >> Or do you mean that you'd have to specify a salt when >> creating a hash-partitioned table, and make sure it's the same across >> all compatibly partitioned tables you might want to hash-join? That >> latter sounds unappealing. > > I don't see a reason to expose the salt to users. If we found a reason > in the future, we could, but it would create all of the problems you > are thinking about. Right, OK. >> You're basically describing what a hash opfamily already does, except >> that we don't have a single opfamily that covers both varchar(10) and >> char(10), nor do we have one that covers both int and numeric. We >> have one that covers int2, int4, and int8, though. If somebody wanted >> to make the ones you're suggesting, there's nothing preventing it, >> although I'm not sure exactly how we'd encourage people to start using >> the new one and deprecating the old one. We don't seem to have a good >> infrastructure for that. > > OK. I will propose new hash opfamilies for varchar/bpchar/text, > int2/4/8/numeric, and timestamptz/date. Cool! I have no idea how we'll convert from the old ones to the new ones without breaking things but I agree that it would be nicer if it were like that rather than the way it is now. > One approach is to promote the narrower type to the wider type, and > then hash. The problem is that would substantially slow down the > hashing of integers, so then we'd need to use one hash opfamily for > partitioning and one for hashjoin, and it gets messy. Yes, that sounds messy. > The other approach is to check if the wider type is within the domain > of the narrower type, and if so, *demote* the value and then hash. For > instance, '4.2'::numeric would hash the same as it does today, but > '4'::numeric would hash as an int2. I prefer this approach, and int8 > already does something resembling it. Sounds reasonable. > It's a little early in the v11 cycle to be having this argument. > Really what I'm saying is that a small effort now may save us a lot of > headache later. Well, that's fair enough. My concern is basically that it may the other way around: a large effort to save a small headache later. I agree that it's probably a good idea to figure out a way to salt the hash function so that we don't end up with this and partitionwise join interacting badly, but I don't see the other issues as being very critical. I don't have any evidence that there's a big need to replace our hash functions with new ones, and over on the partitionwise join thread we gave up on the idea of a cross-type partitionwise join. It wouldn't be particularly common (or sensible, really) even if we ended up supporting it. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-10T16:43:20Z
On Wed, May 10, 2017 at 8:34 AM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > Hash partitioning will partition the data based on the hash value of the > partition key. Does that require collation? Should we throw an error/warning if > collation is specified in PARTITION BY clause? Collation is only relevant for ordering, not equality. Since hash opclasses provide only equality, not ordering, it's not relevant here. I'm not sure whether we should error out if it's specified or just silently ignore it. Maybe an ERROR is a good idea? But not sure. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-10T18:09:04Z
On Wed, May 3, 2017 at 9:09 AM, amul sul <sulamul@gmail.com> wrote: > Fixed in the attached version. +[ PARTITION BY { HASH | RANGE | LIST } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable In the department of severe nitpicking, I would have expected this to either use alphabetical order (HASH | LIST | RANGE) or to add the new method at the end on the theory that we probably did the important ones first (RANGE | LIST | HASH). + WITH ( MODULUS <replaceable class="PARAMETER">value</replaceable>, REMAINDER <replaceable class="PARAMETER">value</replaceable> ) } Maybe value -> modulus and value -> remainder? <para> + When creating a hash partition, <literal>MODULUS</literal> should be + greater than zero and <literal>REMAINDER</literal> should be greater than + or equal to zero. Every <literal>MODULUS</literal> must be a factor of + the next larger modulus. [ ... and it goes on from there ... ] This paragraph is fairly terrible, because it's a design spec that I wrote, not an explanation intended for users. Here's an attempt to improve it: === When creating a hash partition, a modulus and remainder must be specified. The modulus must be a positive integer, and the remainder must a non-negative integer less than the modulus. Typically, when initially setting up a hash-partitioned table, you should choose a modulus equal to the number of partitions and assign every table the same modulus and a different remainder (see examples, below). However, it is not required that every partition have the same modulus, only that every modulus which occurs among the children of a hash-partitioned table is a factor of the next larger modulus. This allows the number of partitions to be increased incrementally without needing to move all the data at once. For example, suppose you have a hash-partitioned table with 8 children, each of which has modulus 8, but find it necessary to increase the number of partitions to 16. You can detach one of the modulus-8 partitions, create two new modulus-16 partitions covering the same portion of the key space (one with a remainder equal to the remainder of the detached partition, and the other with a remainder equal to that value plus 8), and repopulate them with data. You can then repeat this -- perhaps at a later time -- for each modulus-8 partition until none remain. While this may still involve a large amount of data movement at each step, it is still better than having to create a whole new table and move all the data at once. === +CREATE TABLE postal_code ( + code int not null, + city_id bigint not null, + address text +) PARTITION BY HASH (code); It would be fairly silly to hash-partition the postal_code table, because there aren't enough postal codes to justify it. Maybe make this a lineitem or order table, and partition on the order number. Also, extend the example to show creating 4 partitions with modulus 4. + if (spec->strategy != PARTITION_STRATEGY_HASH) + elog(ERROR, "invalid strategy in partition bound spec"); I think this should be an ereport() if it can happen or an Assert() if it's supposed to be prevented by the grammar. + if (!(datumIsEqual(b1->datums[i][0], b2->datums[i][0], + true, sizeof(int)) && It doesn't seem necessary to use datumIsEqual() here. You know the datums are pass-by-value, so why not just use == ? I'd include a comment but I don't think using datumIsEqual() adds anything here except unnecessary complexity. More broadly, I wonder why we're cramming this into the datums arrays instead of just adding another field to PartitionBoundInfoData that is only used by hash partitioning. /* + * Check rule that every modulus must be a factor of the + * next larger modulus. For example, if you have a bunch + * of partitions that all have modulus 5, you can add a new + * new partition with modulus 10 or a new partition with + * modulus 15, but you cannot add both a partition with + * modulus 10 and a partition with modulus 15, because 10 + * is not a factor of 15. However, you could simultaneously + * use modulus 4, modulus 8, modulus 16, and modulus 32 if + * you wished, because each modulus is a factor of the next + * larger one. You could also use modulus 10, modulus 20, + * and modulus 60. But you could not use modulus 10, + * modulus 15, and modulus 60 for the same reason. + */ I think just the first sentence is fine here; I'd nuke the rest of this. The block that follows could be merged into the surrounding block. There's no need to increase the indentation level here, so let's not. I also suspect that the code itself is wrong. There are two ways a modulus can be invalid: it can either fail to be a multiple of the next lower-modulus, or it can fail to be a factor of the next-higher modulus. I think your code only checks the latter. So for example, if the current modulus list is (4, 36), your code would correctly disallow 3 because it's not a factor of 4 and would correctly disallow 23 because it's not a factor of 36, but it looks to me like it would allow 9 because that's a factor of 36. However, then the list would be (4, 9, 36), and 4 is not a factor of 9. + greatest_modulus = DatumGetInt32(datums[ndatums - 1][0]); Here, insert: /* Normally, the lowest remainder that could conflict with the new partition is equal to the remainder specified for the new partition, but when the new partition has a modulus higher than any used so far, we need to adjust. */ + place = spec->remainder; + if (place >= greatest_modulus) + place = place % greatest_modulus; Here, insert: /* Check every potentially-conflicting remainder. */ + do + { + if (boundinfo->indexes[place] != -1) + { + overlap = true; + with = boundinfo->indexes[place]; + break; + } + place = place + spec->modulus; Maybe use += ? + } while (place < greatest_modulus); + * Used when sorting hash bounds across all hash modulus + * for hash partitioning This is not a very descriptive comment. Maybe /* We sort hash bounds by modulus, then by remainder. */ +cal_hash_value(FmgrInfo *partsupfunc, int nkeys, Datum *values, bool *isnull) I agree with Ashutosh's critique of this name. + /* + * Cache hash function information, similar to how record_eq() caches + * equality operator information. (Perhaps no SQL syntax could cause + * PG_NARGS()/nkeys to change between calls through the same FmgrInfo. + * Checking nkeys here is just defensiveness.) + */ Unless I'm missing something, this comment does not actually describe what the code does. Each call to the function repeats the same TypeCacheEntry lookups. I'm not actually sure whether caching here can actually help - is there any situation in which the same FmgrInfo will get used repeatedly here? But if it is possible then this code fails to achieve its intended objective. Another problem with this code is that, unless I'm missing something, it completely ignores the opclass the user specified and just looks up the default hash opclass. I think you should create a non-default hash opclass for some data type -- maybe create one for int4 that just returns the input value unchanged -- and test that the specifying default hash opclass routes tuples according to hash_uint32(val) % modulus while specifying your customer opclass routes tuples according to val % modulus. Unless I'm severely misunderstanding the situation this code is seriously undertested. + * Identify a btree opclass to use. Currently, we use only btree + * operators, which seems enough for list and range partitioning. This comment is false, right? + appendStringInfoString(buf, "FOR VALUES"); + appendStringInfo(buf, " WITH (modulus %d, remainder %d)", + spec->modulus, spec->remainder); You could combine these. +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 0, remainder 1); +ERROR: invalid bound specification for a hash partition +HINT: modulus must be greater than zero +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 8, remainder 8); +ERROR: invalid bound specification for a hash partition +HINT: modulus must be greater than remainder +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 3, remainder 2); +ERROR: invalid bound specification for a hash partition +HINT: every modulus must be factor of next largest modulus It seems like you could merge the hint back into the error: ERROR: hash partition modulus must be greater than 0 ERROR: hash partition remainder must be less than modulus ERROR: every hash partition modulus must be a factor of the next larger modulus +DETAIL: Partition key of the failing row contains (HASHa, b) = (c, 5). That's obviously garbled somehow. +hash_partbound_elem: + NonReservedWord Iconst + { + $$ = makeDefElem($1, (Node *)makeInteger($2), @1); + } + ; + +hash_partbound: + hash_partbound_elem ',' hash_partbound_elem + { + $$ = list_make2($1, $3); + } + ; I don't think that it's the grammar's job to enforce that exactly two options are present. It should allow any number of options, and some later code, probably during parse analysis, should check that the ones you need are present and that there are no invalid ones. See the code for EXPLAIN, VACUUM, etc. Regarding the test cases, I think that you've got a lot of tests for failure scenarios (which is good) but not enough for success scenarios. For example, you test that inserting a row into the wrong hash partition fails, but not (unless I missed it) that tuple routing succeeds. I think it would be good to have a test where you insert 1000 or so rows into a hash partitioned table just to see it all work. Also, you haven't done anything about the fact that constraint exclusion doesn't work for hash partitioned tables, a point I raised in http://postgr.es/m/CA+Tgmob7RsN5A=ehgYbLPx--c5CmptrK-dB=Y-v--o+TKyfteA@mail.gmail.com and which I still think is quite important. I think that to have a committable patch for this feature that would have to be addressed. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company -
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-05-11T16:02:11Z
On Wed, May 3, 2017 at 6:39 PM, amul sul <sulamul@gmail.com> wrote: > On Thu, Apr 27, 2017 at 1:42 AM, Robert Haas <robertmhaas@gmail.com> wrote: > >>I spent some time today looking at these patches. It seems like there >>is some more work still needed here to produce something committable >>regardless of which way we go, but I am inclined to think that Amul's >>patch is a better basis for work going forward than Nagata-san's >>patch. Here are some general comments on the two patches: > > Thanks for your time. > > [...] > >> - Neither patch contains any documentation updates, which is bad. > > Fixed in the attached version. I have done an intial review of the patch and I have some comments. I will continue the review and testing and report the results soon ----- Patch need to be rebased ---- if (key->strategy == PARTITION_STRATEGY_RANGE) { /* Disallow nulls in the range partition key of the tuple */ for (i = 0; i < key->partnatts; i++) if (isnull[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("range partition key of row contains null"))); } We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL for hash also, right? ---- RangeDatumContent **content;/* what's contained in each range bound datum? * (see the above enum); NULL for list * partitioned tables */ This will be NULL for hash as well we need to change the comments. ----- bool has_null; /* Is there a null-accepting partition? false * for range partitioned tables */ int null_index; /* Index of the null-accepting partition; -1 Comments needs to be changed for these two members as well ---- +/* One bound of a hash partition */ +typedef struct PartitionHashBound +{ + int modulus; + int remainder; + int index; +} PartitionHashBound; It will good to add some comments to explain the structure members -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-12T01:42:32Z
On Thu, May 11, 2017 at 12:02 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL > for hash also, right? I think it should. Actually, I think that not supporting nulls for range partitioning may have been a fairly bad decision. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Amit Langote <langote_amit_f8@lab.ntt.co.jp> — 2017-05-12T02:15:51Z
On 2017/05/12 10:42, Robert Haas wrote: > On Thu, May 11, 2017 at 12:02 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >> We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL >> for hash also, right? > > I think it should. > > Actually, I think that not supporting nulls for range partitioning may > have been a fairly bad decision. I think the relevant discussion concluded [1] that way, because we couldn't decide which interface to provide for specifying where NULLs are placed or because we decided to think about it later. Thanks, Amit [1] https://www.postgresql.org/message-id/CA%2BTgmoZN_Zf7MBb48O66FAJgFe0S9_NkLVeQNBz6hsxb6Og93w%40mail.gmail.com
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-12T02:20:48Z
On Thu, May 11, 2017 at 10:15 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote: > On 2017/05/12 10:42, Robert Haas wrote: >> On Thu, May 11, 2017 at 12:02 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >>> We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL >>> for hash also, right? >> >> I think it should. >> >> Actually, I think that not supporting nulls for range partitioning may >> have been a fairly bad decision. > > I think the relevant discussion concluded [1] that way, because we > couldn't decide which interface to provide for specifying where NULLs are > placed or because we decided to think about it later. Yeah, but I have a feeling that marking the columns NOT NULL is going to make it really hard to support that in the future when we get the syntax hammered out. If it had only affected the partition constraints that'd be different. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Amit Langote <langote_amit_f8@lab.ntt.co.jp> — 2017-05-12T02:38:11Z
On 2017/05/12 11:20, Robert Haas wrote: > On Thu, May 11, 2017 at 10:15 PM, Amit Langote > <Langote_Amit_f8@lab.ntt.co.jp> wrote: >> On 2017/05/12 10:42, Robert Haas wrote: >>> On Thu, May 11, 2017 at 12:02 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >>>> We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL >>>> for hash also, right? >>> >>> I think it should. >>> >>> Actually, I think that not supporting nulls for range partitioning may >>> have been a fairly bad decision. >> >> I think the relevant discussion concluded [1] that way, because we >> couldn't decide which interface to provide for specifying where NULLs are >> placed or because we decided to think about it later. > > Yeah, but I have a feeling that marking the columns NOT NULL is going > to make it really hard to support that in the future when we get the > syntax hammered out. If it had only affected the partition > constraints that'd be different. So, adding keycol IS NOT NULL (like we currently do for expressions) in the implicit partition constraint would be more future-proof than generating an actual catalogued NOT NULL constraint on the keycol? I now tend to think it would be better. Directly inserting into a range partition with a NULL value for a column currently generates a "null value in column \"%s\" violates not-null constraint" instead of perhaps more relevant "new row for relation \"%s\" violates partition constraint". That said, we *do* document the fact that a NOT NULL constraint is added on range key columns, but we might as well document instead that we don't currently support routing tuples with NULL values in the partition key through a range-partitioned table and so NULL values cause error. Can we still decide to do that instead? Thanks, Amit
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-12T05:14:46Z
On Fri, May 12, 2017 at 7:12 AM, Robert Haas <robertmhaas@gmail.com> wrote: > On Thu, May 11, 2017 at 12:02 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >> We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL >> for hash also, right? > > I think it should. > +1 As long as we can hash a NULL value, we should place a value with NULL key in the corresponding partition, most probably the one with remainder 0. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-12T05:24:46Z
On Fri, May 12, 2017 at 8:08 AM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote: > On 2017/05/12 11:20, Robert Haas wrote: >> On Thu, May 11, 2017 at 10:15 PM, Amit Langote >> <Langote_Amit_f8@lab.ntt.co.jp> wrote: >>> On 2017/05/12 10:42, Robert Haas wrote: >>>> On Thu, May 11, 2017 at 12:02 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >>>>> We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL >>>>> for hash also, right? >>>> >>>> I think it should. >>>> >>>> Actually, I think that not supporting nulls for range partitioning may >>>> have been a fairly bad decision. >>> >>> I think the relevant discussion concluded [1] that way, because we >>> couldn't decide which interface to provide for specifying where NULLs are >>> placed or because we decided to think about it later. >> >> Yeah, but I have a feeling that marking the columns NOT NULL is going >> to make it really hard to support that in the future when we get the >> syntax hammered out. If it had only affected the partition >> constraints that'd be different. > > So, adding keycol IS NOT NULL (like we currently do for expressions) in > the implicit partition constraint would be more future-proof than > generating an actual catalogued NOT NULL constraint on the keycol? I now > tend to think it would be better. Directly inserting into a range > partition with a NULL value for a column currently generates a "null value > in column \"%s\" violates not-null constraint" instead of perhaps more > relevant "new row for relation \"%s\" violates partition constraint". > That said, we *do* document the fact that a NOT NULL constraint is added > on range key columns, but we might as well document instead that we don't > currently support routing tuples with NULL values in the partition key > through a range-partitioned table and so NULL values cause error. in get_partition_for_tuple() we have if (key->strategy == PARTITION_STRATEGY_RANGE) { /* Disallow nulls in the range partition key of the tuple */ for (i = 0; i < key->partnatts; i++) if (isnull[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("range partition key of row contains null"))); } Instead of throwing an error here, we should probably return -1 and let the error be ""no partition of relation \"%s\" found for row", which is the real error, not having a partition which can accept NULL. If in future we decide to support NULL values in partition keys, we need to just remove above code from get_partition_for_tuple() and everything will work as is. I am assuming that we don't add any implicit/explicit NOT NULL constraint right now. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Amit Langote <langote_amit_f8@lab.ntt.co.jp> — 2017-05-12T08:16:24Z
On 2017/05/12 14:24, Ashutosh Bapat wrote: > On Fri, May 12, 2017 at 8:08 AM, Amit Langote > <Langote_Amit_f8@lab.ntt.co.jp> wrote: >> On 2017/05/12 11:20, Robert Haas wrote: >>> Yeah, but I have a feeling that marking the columns NOT NULL is going >>> to make it really hard to support that in the future when we get the >>> syntax hammered out. If it had only affected the partition >>> constraints that'd be different. >> >> So, adding keycol IS NOT NULL (like we currently do for expressions) in >> the implicit partition constraint would be more future-proof than >> generating an actual catalogued NOT NULL constraint on the keycol? I now >> tend to think it would be better. Directly inserting into a range >> partition with a NULL value for a column currently generates a "null value >> in column \"%s\" violates not-null constraint" instead of perhaps more >> relevant "new row for relation \"%s\" violates partition constraint". >> That said, we *do* document the fact that a NOT NULL constraint is added >> on range key columns, but we might as well document instead that we don't >> currently support routing tuples with NULL values in the partition key >> through a range-partitioned table and so NULL values cause error. > > in get_partition_for_tuple() we have > if (key->strategy == PARTITION_STRATEGY_RANGE) > { > /* Disallow nulls in the range partition key of the tuple */ > for (i = 0; i < key->partnatts; i++) > if (isnull[i]) > ereport(ERROR, > (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), > errmsg("range partition key of row contains null"))); > } > > Instead of throwing an error here, we should probably return -1 and > let the error be ""no partition of relation \"%s\" found for row", > which is the real error, not having a partition which can accept NULL. > If in future we decide to support NULL values in partition keys, we > need to just remove above code from get_partition_for_tuple() and > everything will work as is. I am assuming that we don't add any > implicit/explicit NOT NULL constraint right now. We *do* actually, for real columns: create table p (a int) partition by range (a); \d p Table "public.p" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- a | integer | | not null | Partition key: RANGE (a) For expression keys, we emit IS NOT NULL as part of the implicit partition constraint. The above check for NULL is really for the expressions, because if any simple columns of the key contain NULL, they will fail the NOT NULL constraint itself (with that error message). As I said in my previous message, I'm thinking that emitting IS NOT NULL as part of the implicit partition constraint might be better instead of adding it as a NOT NULL constraint, that is, for the simple column keys; we already do that for the expression keys for which we cannot add the NOT NULL constraint anyway. The way things are currently, error messages generated when a row with NULL in the range partition key is *directly* into the partition looks a bit inconsistent, depending on whether the target key is a simple column or expression: create table p (a int, b int) partition by range (a, abs(b)); create table p1 partition of p for values from (1, 1) to (1, 10); insert into p1 values (NULL, NULL); ERROR: null value in column "a" violates not-null constraint DETAIL: Failing row contains (null, null). insert into p1 values (1, NULL); ERROR: new row for relation "p1" violates partition constraint DETAIL: Failing row contains (1, null). It would be nice if both said "violates partition constraint". BTW, note that this is independent of your suggestion to emit "partition not found" message instead of the "no NULLs allowed in the range partition key" message, which seems fine to me to implement. Thanks, Amit -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-12T10:34:23Z
On Wed, May 10, 2017 at 6:04 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > On Wed, May 3, 2017 at 6:39 PM, amul sul <sulamul@gmail.com> wrote: >> On Thu, Apr 27, 2017 at 1:42 AM, Robert Haas <robertmhaas@gmail.com> wrote: >> >>> >>> This is not yet a detailed review - I may be missing things, and >>> review and commentary from others is welcome. If there is no major >>> disagreement with the idea of moving forward using Amul's patch as a >>> base, then I will do a more detailed review of that patch (or, >>> hopefully, an updated version that addresses the above comments). >> > > I agree that Amul's approach makes dump/restore feasible whereas > Nagata-san's approach makes that difficult. That is a major plus point > about Amul's patch. Also, it makes it possible to implement > Nagata-san's syntax, which is more user-friendly in future. > > Here are some review comments after my initial reading of Amul's patch: > > Hash partitioning will partition the data based on the hash value of the > partition key. Does that require collation? Should we throw an error/warning if > collation is specified in PARTITION BY clause? > > + int *indexes; /* Partition indexes; in case of hash > + * partitioned table array length will be > + * value of largest modulus, and for others > + * one entry per member of the datums array > + * (plus one if range partitioned table) */ > This may be rewritten as "Partition indexes: For hash partitioned table the > number of indexes will be same as the largest modulus. For list partitioned > table the number of indexes will be same as the number of datums. For range > partitioned table the number of indexes will be number of datums plus one.". > You may be able to reword it to a shorter version, but essentially we will have > separate description for each strategy. > Okay, will fix this. > I guess, we need to change the comments for the other members too. For example > "datums" does not contain tuples with key->partnatts attributes for hash > partitions. It contains a tuple with two attributes, modulus and remainder. We > may not want to track null_index separately since rows with NULL partition key > will fit in the partition corresponding to the hash value of NULL. OR may be we > want to set null_index to partition which contains NULL values, if there is a > partition created for corresponding remainder, modulus pair and set has_null > accordingly. Accordingly we will need to update the comments. > > cal_hash_value() may be renamed as calc_has_value() or compute_hash_value()? > Okay, will rename to compute_hash_value(). > Should we change the if .. else if .. construct in RelationBuildPartitionDesc() > to a switch case? There's very less chance that we will support a fourth > partitioning strategy, so if .. else if .. may be fine. > > + int mod = hbounds[i]->modulus, > + place = hbounds[i]->remainder; > Although there are places in the code where we separate variable declaration > with same type by comma, most of the code declares each variable with the data > type on separate line. Should variable "place" be renamed as "remainder" since > that's what it is ultimately? > Okay, will rename "place" to "remainder". > RelationBuildPartitionDesc() fills up mapping array but never uses it. In this Agreed, mapping array is not that much useful but not useless, it required at the end of RelationBuildPartitionDesc() while assigning OIDs to result->oids, see for-loop just before releasing mapping memory. > code the index into mapping array itself is the mapping so it doesn't need to > be maintained separately like list partiioning case. Similary next_index usage > looks unnecessary, although that probably improves readability, so may be fine. > Anyway, will remove uses of "next_index". > + * for p_p1: satisfies_hash_partition(2, 1, pkey, value) > + * for p_p2: satisfies_hash_partition(4, 2, pkey, value) > + * for p_p3: satisfies_hash_partition(8, 0, pkey, value) > + * for p_p4: satisfies_hash_partition(8, 4, pkey, value) > What the function builds is satisfies_hash_partition(2, 1, pkey). I don't see > code to add value as an argument to the function. Is that correct? > Sorry for confusion, "pkey" & "value" are the column of table in the give example. Renamed those column name to "a" & "b". > + int modulus = DatumGetInt32(datum); > May be you want to rename this variable to greatest_modulus like in the other > places. > Okay, will fix this. > + Assert(spec->modulus > 0 && spec->remainder >= 0); > I liked this assertion. Do you want to add spec->modulus > spec->reminder also > here? > Okay, will add this too. > + char *strategy; /* partitioning strategy > + ('hash', 'list' or 'range') */ > > We need the second line to start with '*' > > +-- check validation when attaching list partitions > Do you want to say "hash" instead of "list" here? > You are correct, will fix this too. > I think we need to explain the reasoning behind this syntax somewhere > as a README or in the documentation or in the comments. Otherwise it's > difficult to understand how various pieces of code are related. > Not sure about README, I think we should focus on documentation & code comments first, and then think about developer perspective README if hash partitioning logic is too difficult to understand . > This is not full review. I am still trying to understand how the hash > partitioning implementation fits with list and range partitioning. I > am going to continue to review this patch further. > Thanks a lots for your help. Regards, Amul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-12T12:38:36Z
Hi, Please find the following updated patches attached: 0001-Cleanup.patch : Does some cleanup and code refactoring required for hash partition patch. Otherwise, there will be unnecessary diff in 0002 patch 0002-hash-partitioning_another_design-v3.patch: Addressed review comments given by Ashutosh and Robert. On Wed, May 10, 2017 at 11:39 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Wed, May 3, 2017 at 9:09 AM, amul sul <sulamul@gmail.com> wrote: >> Fixed in the attached version. > > +[ PARTITION BY { HASH | RANGE | LIST } ( { <replaceable > class="parameter">column_name</replaceable> | ( <replaceable > class="parameter">expression</replaceable> ) } [ COLLATE <replaceable > > In the department of severe nitpicking, I would have expected this to > either use alphabetical order (HASH | LIST | RANGE) or to add the new > method at the end on the theory that we probably did the important > ones first (RANGE | LIST | HASH). > Fixed in the attached version. > + WITH ( MODULUS <replaceable class="PARAMETER">value</replaceable>, > REMAINDER <replaceable class="PARAMETER">value</replaceable> ) } > > Maybe value -> modulus and value -> remainder? > Fixed in the attached version. > <para> > + When creating a hash partition, <literal>MODULUS</literal> should be > + greater than zero and <literal>REMAINDER</literal> should be greater than > + or equal to zero. Every <literal>MODULUS</literal> must be a factor of > + the next larger modulus. > [ ... and it goes on from there ... ] > > This paragraph is fairly terrible, because it's a design spec that I > wrote, not an explanation intended for users. Here's an attempt to > improve it: > > === > When creating a hash partition, a modulus and remainder must be > specified. The modulus must be a positive integer, and the remainder > must a non-negative integer less than the modulus. Typically, when > initially setting up a hash-partitioned table, you should choose a > modulus equal to the number of partitions and assign every table the > same modulus and a different remainder (see examples, below). > However, it is not required that every partition have the same > modulus, only that every modulus which occurs among the children of a > hash-partitioned table is a factor of the next larger modulus. This > allows the number of partitions to be increased incrementally without > needing to move all the data at once. For example, suppose you have a > hash-partitioned table with 8 children, each of which has modulus 8, > but find it necessary to increase the number of partitions to 16. You > can detach one of the modulus-8 partitions, create two new modulus-16 > partitions covering the same portion of the key space (one with a > remainder equal to the remainder of the detached partition, and the > other with a remainder equal to that value plus 8), and repopulate > them with data. You can then repeat this -- perhaps at a later time > -- for each modulus-8 partition until none remain. While this may > still involve a large amount of data movement at each step, it is > still better than having to create a whole new table and move all the > data at once. > === > Thanks a lot, added in attached version. > +CREATE TABLE postal_code ( > + code int not null, > + city_id bigint not null, > + address text > +) PARTITION BY HASH (code); > > It would be fairly silly to hash-partition the postal_code table, > because there aren't enough postal codes to justify it. Maybe make > this a lineitem or order table, and partition on the order number. > Also, extend the example to show creating 4 partitions with modulus 4. > Understood, added order table example. > + if (spec->strategy != PARTITION_STRATEGY_HASH) > + elog(ERROR, "invalid strategy in partition bound spec"); > > I think this should be an ereport() if it can happen or an Assert() if > it's supposed to be prevented by the grammar. > Used Assert() in the attach version patch, also changed same for RANGE and LIST in 0001- cleanup patch. > + if (!(datumIsEqual(b1->datums[i][0], b2->datums[i][0], > + true, sizeof(int)) && > > It doesn't seem necessary to use datumIsEqual() here. You know the > datums are pass-by-value, so why not just use == ? I'd include a > comment but I don't think using datumIsEqual() adds anything here > except unnecessary complexity. More broadly, I wonder why we're > cramming this into the datums arrays instead of just adding another > field to PartitionBoundInfoData that is only used by hash > partitioning. > Fixed in the attached version. > /* > + * Check rule that every modulus must be a factor of the > + * next larger modulus. For example, if you have a bunch > + * of partitions that all have modulus 5, you can add a new > + * new partition with modulus 10 or a new partition with > + * modulus 15, but you cannot add both a partition with > + * modulus 10 and a partition with modulus 15, because 10 > + * is not a factor of 15. However, you could > simultaneously > + * use modulus 4, modulus 8, modulus 16, and modulus 32 if > + * you wished, because each modulus is a factor of the next > + * larger one. You could also use modulus 10, modulus 20, > + * and modulus 60. But you could not use modulus 10, > + * modulus 15, and modulus 60 for the same reason. > + */ > > I think just the first sentence is fine here; I'd nuke the rest of this. > Fixed in the attached version. > The block that follows could be merged into the surrounding block. > There's no need to increase the indentation level here, so let's not. > I also suspect that the code itself is wrong. There are two ways a > modulus can be invalid: it can either fail to be a multiple of the > next lower-modulus, or it can fail to be a factor of the next-higher > modulus. I think your code only checks the latter. So for example, > if the current modulus list is (4, 36), your code would correctly > disallow 3 because it's not a factor of 4 and would correctly disallow > 23 because it's not a factor of 36, but it looks to me like it would > allow 9 because that's a factor of 36. However, then the list would be > (4, 9, 36), and 4 is not a factor of 9. > This case is already handled in previous patch and similar regression test does exists in create_table.sql, see this in v2 patch. +-- check partition bound syntax for the hash partition +CREATE TABLE hash_parted ( + a int +) PARTITION BY HASH (a); +CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (modulus 10, remainder 1); +CREATE TABLE hpart_2 PARTITION OF hash_parted FOR VALUES WITH (modulus 50, remainder 0); +-- modulus 25 is factor of modulus of 50 but 10 is not factor of 25. +CREATE TABLE fail_part PARTITION OF hash_parted FOR VALUES WITH (modulus 25, remainder 2); > + greatest_modulus = DatumGetInt32(datums[ndatums - 1][0]); > > Here, insert: /* Normally, the lowest remainder that could conflict > with the new partition is equal to the remainder specified for the new > partition, but when the new partition has a modulus higher than any > used so far, we need to adjust. */ > > + place = spec->remainder; > + if (place >= greatest_modulus) > + place = place % greatest_modulus; > Fixed in the attached version. > Here, insert: /* Check every potentially-conflicting remainder. */ > > + do > + { > + if (boundinfo->indexes[place] != -1) > + { > + overlap = true; > + with = boundinfo->indexes[place]; > + break; > + } > + place = place + spec->modulus; > > Maybe use += ? > Fixed. > + } while (place < greatest_modulus); > > + * Used when sorting hash bounds across all hash modulus > + * for hash partitioning > > This is not a very descriptive comment. Maybe /* We sort hash bounds > by modulus, then by remainder. */ > Fixed. > +cal_hash_value(FmgrInfo *partsupfunc, int nkeys, Datum *values, bool *isnull) > > I agree with Ashutosh's critique of this name. > Fixed. > + /* > + * Cache hash function information, similar to how record_eq() caches > + * equality operator information. (Perhaps no SQL syntax could cause > + * PG_NARGS()/nkeys to change between calls through the same FmgrInfo. > + * Checking nkeys here is just defensiveness.) > + */ > > Unless I'm missing something, this comment does not actually describe > what the code does. Each call to the function repeats the same > TypeCacheEntry lookups. I'm not actually sure whether caching here > can actually help - is there any situation in which the same FmgrInfo > will get used repeatedly here? But if it is possible then this code > fails to achieve its intended objective. > This code is no longer exists in new satisfies_hash_partition() code. > Another problem with this code is that, unless I'm missing something, > it completely ignores the opclass the user specified and just looks up > the default hash opclass. I think you should create a non-default > hash opclass for some data type -- maybe create one for int4 that just > returns the input value unchanged -- and test that the specifying > default hash opclass routes tuples according to hash_uint32(val) % > modulus while specifying your customer opclass routes tuples according > to val % modulus. > > Unless I'm severely misunderstanding the situation this code is > seriously undertested. > You are correct, I've missed to opclass handling. Fixed in the attached version, and added same case regression test. > + * Identify a btree opclass to use. Currently, we use only btree > + * operators, which seems enough for list and range partitioning. > > This comment is false, right? > Not really, this has been re-added due to indentation change. > + appendStringInfoString(buf, "FOR VALUES"); > + appendStringInfo(buf, " WITH (modulus %d, > remainder %d)", > + spec->modulus, spec->remainder); > > You could combine these. > I am not sure about this, I've used same code style exist in get_rule_expr() for range and list. Do you want me to change this for other partitioning as well? > +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH > (modulus 0, remainder 1); > +ERROR: invalid bound specification for a hash partition > +HINT: modulus must be greater than zero > +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH > (modulus 8, remainder 8); > +ERROR: invalid bound specification for a hash partition > +HINT: modulus must be greater than remainder > +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH > (modulus 3, remainder 2); > +ERROR: invalid bound specification for a hash partition > +HINT: every modulus must be factor of next largest modulus > > It seems like you could merge the hint back into the error: > > ERROR: hash partition modulus must be greater than 0 > ERROR: hash partition remainder must be less than modulus > ERROR: every hash partition modulus must be a factor of the next larger modulus > Added same in the attached version. Thanks again. > +DETAIL: Partition key of the failing row contains (HASHa, b) = (c, 5). > > That's obviously garbled somehow. > Oops. Fixed in the attached version. > +hash_partbound_elem: > + NonReservedWord Iconst > + { > + $$ = makeDefElem($1, (Node *)makeInteger($2), @1); > + } > + ; > + > +hash_partbound: > + hash_partbound_elem ',' hash_partbound_elem > + { > + $$ = list_make2($1, $3); > + } > + ; > > I don't think that it's the grammar's job to enforce that exactly two > options are present. It should allow any number of options, and some > later code, probably during parse analysis, should check that the ones > you need are present and that there are no invalid ones. See the code > for EXPLAIN, VACUUM, etc. > Tried to fixed in the attached version. > Regarding the test cases, I think that you've got a lot of tests for > failure scenarios (which is good) but not enough for success > scenarios. For example, you test that inserting a row into the wrong > hash partition fails, but not (unless I missed it) that tuple routing > succeeds. I think it would be good to have a test where you insert > 1000 or so rows into a hash partitioned table just to see it all work. > I am quite unsure about this test, now sure how can we verify correct tuple routing? > Also, you haven't done anything about the fact that constraint > exclusion doesn't work for hash partitioned tables, a point I raised > in http://postgr.es/m/CA+Tgmob7RsN5A=ehgYbLPx--c5CmptrK-dB=Y-v--o+TKyfteA@mail.gmail.com > and which I still think is quite important. I think that to have a > committable patch for this feature that would have to be addressed. > Do you mean, we should come up with special handling(pre-pruning) for hash partitioning or modify constraints exclusion so that it will handle hash partition expression and cases that you have discussed in thread[1] as well? I was under the impression that we might going to have this as a separate feature proposal. 1]. https://www.postgresql.org/message-id/CA%2BTgmoaE9NZ_RiqZQLp2aJXPO4E78QxkQYL-FR2zCDop96Ahdg%40mail.gmail.com Regards, Amul Sul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-12T12:46:33Z
On Thu, May 11, 2017 at 9:32 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > On Wed, May 3, 2017 at 6:39 PM, amul sul <sulamul@gmail.com> wrote: >> On Thu, Apr 27, 2017 at 1:42 AM, Robert Haas <robertmhaas@gmail.com> wrote: >> >>>I spent some time today looking at these patches. It seems like there >>>is some more work still needed here to produce something committable >>>regardless of which way we go, but I am inclined to think that Amul's >>>patch is a better basis for work going forward than Nagata-san's >>>patch. Here are some general comments on the two patches: >> >> Thanks for your time. >> >> [...] >> >>> - Neither patch contains any documentation updates, which is bad. >> >> Fixed in the attached version. > > I have done an intial review of the patch and I have some comments. I > will continue the review > and testing and report the results soon > > ----- > Patch need to be rebased > > ---- > > if (key->strategy == PARTITION_STRATEGY_RANGE) > { > /* Disallow nulls in the range partition key of the tuple */ > for (i = 0; i < key->partnatts; i++) > if (isnull[i]) > ereport(ERROR, > (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), > errmsg("range partition key of row contains null"))); > } > > We need to add PARTITION_STRATEGY_HASH as well, we don't support NULL > for hash also, right? > ---- We do. > > RangeDatumContent **content;/* what's contained in each range bound datum? > * (see the above enum); NULL for list > * partitioned tables */ > > This will be NULL for hash as well we need to change the comments. > ----- Fixed in previously posted patch(v3). > > bool has_null; /* Is there a null-accepting partition? false > * for range partitioned tables */ > int null_index; /* Index of the null-accepting partition; -1 > > Comments needs to be changed for these two members as well > ---- Fixed in previously posted patch(v3). > > +/* One bound of a hash partition */ > +typedef struct PartitionHashBound > +{ > + int modulus; > + int remainder; > + int index; > +} PartitionHashBound; > > It will good to add some comments to explain the structure members > I think we don't really need that, variable names are ample to explain its purpose. Regards, Amul -
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-12T16:57:01Z
On Wed, May 10, 2017 at 11:39 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Wed, May 3, 2017 at 9:09 AM, amul sul <sulamul@gmail.com> wrote: >> Fixed in the attached version. > > +[ PARTITION BY { HASH | RANGE | LIST } ( { <replaceable > class="parameter">column_name</replaceable> | ( <replaceable > class="parameter">expression</replaceable> ) } [ COLLATE <replaceable > > In the department of severe nitpicking, I would have expected this to > either use alphabetical order (HASH | LIST | RANGE) or to add the new > method at the end on the theory that we probably did the important > ones first (RANGE | LIST | HASH). Importance is subjective, so may be we should arrange them in alphabetical order, to keep the list in some order and be consistent everywhere in the code and documentation. > More broadly, I wonder why we're > cramming this into the datums arrays instead of just adding another > field to PartitionBoundInfoData that is only used by hash > partitioning. It would be good if we store datums corresponding to partition bounds in the same place. So that we don't have to handle hash partition specially in all the places where we handle partition bound datums. We already do that for list and range partitions. May be we want to continue doing so for hash as well. In my comments to Amul's latest patch, I have described a possibility that partition_bounds_equal() need not compare all entries in the datums array. It can just compare greated modulus and the indexes array from given two partition bounds to check whether they are equal. If that works well, we will probably address your complaint about DatumIsEqual() in a different manner. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-12T17:09:19Z
On Thu, May 11, 2017 at 10:38 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote: > So, adding keycol IS NOT NULL (like we currently do for expressions) in > the implicit partition constraint would be more future-proof than > generating an actual catalogued NOT NULL constraint on the keycol? I now > tend to think it would be better. Directly inserting into a range > partition with a NULL value for a column currently generates a "null value > in column \"%s\" violates not-null constraint" instead of perhaps more > relevant "new row for relation \"%s\" violates partition constraint". > That said, we *do* document the fact that a NOT NULL constraint is added > on range key columns, but we might as well document instead that we don't > currently support routing tuples with NULL values in the partition key > through a range-partitioned table and so NULL values cause error. > > Can we still decide to do that instead? I suggest you start a new thread on that topic. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-12T17:09:34Z
On Fri, May 12, 2017 at 6:08 PM, amul sul <sulamul@gmail.com> wrote: > Hi, > > Please find the following updated patches attached: > > 0001-Cleanup.patch : Does some cleanup and code refactoring required > for hash partition patch. Otherwise, there will be unnecessary diff in > 0002 patch Thanks for splitting the patch. + if (isnull[0]) + cur_index = partdesc->boundinfo->null_index; This code assumes that null_index will be set to -1 when has_null is false. Per RelationBuildPartitionDesc() this is true. But probably we should write this code as if (isnull[0]) { if (partdesc->boundinfo->has_null) cur_index = partdesc->boundinfo->null_index; } That way we are certain that when has_null is false, cur_index = -1 similar to the original code. Additional arguement to ComputePartitionAttrs() isn't used anywhere in this patch, so may be this better be part of 0002. If we do this the only change that will remain in patch is the refactoring of RelationBuildPartitionDesc(), so we may consider merging it into 0002, unless we find that some more refactoring is needed. But for now, having it as a separate patch helps. Here's some more comments on 0002 + * In the case of hash partitioning, datums is a 2-D array, stores modulus and + * remainder values at datums[x][0] and datums[x][1] respectively for each + * partition in the ascending order. This comment about datums should appear in a paragraph of itself and may be rephrased as in the attached patch. May be we could also add a line about ndatums for hash partitioned tables as in the attached patch. + * (see the above enum); NULL for has and list typo s/has/hash + if (key->strategy == PARTITION_STRATEGY_HASH) + { + ndatums = nparts; + hbounds = (PartitionHashBound **) palloc(nparts * + sizeof(PartitionHashBound *)); + i = 0; + foreach (cell, boundspecs) + { + PartitionBoundSpec *spec = lfirst(cell); + [ clipped ] + hbounds[i]->index = i; + i++; + } For list and range partitioned table we order the bounds so that two partitioned tables have them in the same order irrespective of order in which they are specified by the user or hence stored in the catalogs. The partitions then get indexes according the order in which their bounds appear in ordered arrays of bounds. Thus any two partitioned tables with same partition specification always have same PartitionBoundInfoData. This helps in partition-wise join to match partition bounds of two given tables. Above code assigns the indexes to the partitions as they appear in the catalogs. This means that two partitioned tables with same partition specification but different order for partition bound specification will have different PartitionBoundInfoData represenation. If we do that, probably partition_bounds_equal() would reduce to just matching indexes and the last element of datums array i.e. the greatest modulus datum. If ordered datums array of two partitioned table do not match exactly, the mismatch can be because missing datums or different datums. If it's a missing datum it will change the greatest modulus or have corresponding entry in indexes array as -1. If the entry differs it will cause mismatching indexes in the index arrays. + * is not a factor of 15. + * + * + * Get greatest bound in array boundinfo->datums which is An extra line here. + if (offset < 0) + { + nmod = DatumGetInt32(datums[0][0]); + valid_bound = (nmod % spec->modulus) == 0; + } + else + { + pmod = DatumGetInt32(datums[offset][0]); + valid_bound = (spec->modulus % pmod) == 0; + + if (valid_bound && (offset + 1) < ndatums) + { + nmod = DatumGetInt32(datums[offset + 1][0]); + valid_bound = (nmod % spec->modulus) == 0; + } + } May be name the variables as prev_mod(ulus) and next_mod(ulus) for better readability. + * for p_p1: satisfies_hash_partition(2, 1, hash_fn(a), hash_fn(b)) + * for p_p2: satisfies_hash_partition(4, 2, hash_fn(a), hash_fn(b)) + * for p_p3: satisfies_hash_partition(8, 0, hash_fn(a), hash_fn(b)) + * for p_p4: satisfies_hash_partition(8, 4, hash_fn(a), hash_fn(b)) The description here may be read as if we are calling the same hash function for both a and b, but that's not true. So, you may want to clarify that in hash_fn(a) hash_fn means hash function specified for key a. + if (key->partattrs[i] != 0) + { + keyCol = (Node *) makeVar(1, + key->partattrs[i], + key->parttypid[i], + key->parttypmod[i], + key->parttypcoll[i], + 0); + + /* Form hash_fn(value) expression */ + keyCol = (Node *) makeFuncExpr(key->partsupfunc[i].fn_oid, + get_fn_expr_rettype(&key->partsupfunc[i]), + list_make1(keyCol), + InvalidOid, + InvalidOid, + COERCE_EXPLICIT_CALL); + } + else + { + keyCol = (Node *) copyObject(lfirst(partexprs_item)); + partexprs_item = lnext(partexprs_item); + } I think we should add FuncExpr for column Vars as well as expressions. The logic to compare two bounds is duplicated in qsort_partition_hbound_cmp() and partition_bound_cmp(). Should we separate it into a separate function accepting moduli and remainders. That way in case we change it in future, we have to change only one place. I think we need more comments for compute_hash_value(), mix_hash_value() and satisfies_hash_partition() as to what each of them accepts and what it computes. + /* key's hash values start from third argument of function. */ + if (!PG_ARGISNULL(i + 2)) + { + values[i] = PG_GETARG_DATUM(i + 2); + isnull[i] = false; + } + else + isnull[i] = true; You could write this as isnull[i] = PG_ARGISNULL(i + 2); if (isnull[i]) values[i] = PG_GETARG_DATUM(i + 2); + * Identify a btree or hash opclass to use. Currently, we use only + * btree operators, which seems enough for list and range partitioning, + * and hash operators for hash partitioning. The wording, if not read carefully, might be read as "we use only btree operators". I suggest we rephrase it as "Identify opclass to use. For list and range partitioning we use only btree operators, which seems enough for those. For hash partitioning, we use hash operators." for clarity. + foreach (lc, $5) + { + DefElem *opt = (DefElem *) lfirst(lc); A search on WITH in gram.y shows that we do not handle WITH options in gram.y. Usually they are handled at the transformation stage. Why is this an exception? If you do that, we can have all the error handling in transformPartitionBound(). +DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 2276 0 f f f f f f i s 3 0 16 "23 23 2276" _null_ _null_ _null_ _null_ _null_ satisfies_hash_partition _null_ _null_ _null_ )); Why is third argument to this function ANY? Shouldn't it be INT4ARRAY (variadic INT4)? I am yet to review the testcases and thumb through all the places using PARTITION_STRATEGY_RANGE/LIST to make sure that we are handling PARTITION_STRATEGY_HASH in all those places. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-05-13T06:41:09Z
On Fri, May 12, 2017 at 6:08 PM, amul sul <sulamul@gmail.com> wrote: > Hi, > > Please find the following updated patches attached: I have done some testing with the new patch, most of the cases worked as per the expectation except below I expect the planner to select only "Seq Scan on t1" whereas it's scanning both the partitions? create table t (a int, b varchar) partition by hash(a); create table t1 partition of t for values with (modulus 8, remainder 0); create table t2 partition of t for values with (modulus 8, remainder 1); postgres=# explain select * from t where a=8; QUERY PLAN ---------------------------------------------------------- Append (cost=0.00..51.75 rows=12 width=36) -> Seq Scan on t1 (cost=0.00..25.88 rows=6 width=36) Filter: (a = 8) -> Seq Scan on t2 (cost=0.00..25.88 rows=6 width=36) Filter: (a = 8) (5 rows) Some cosmetic comments. ----------------------------------- + RangeVar *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg)); + Useless Hunk. /* - * Build a CREATE SEQUENCE command to create the sequence object, and - * add it to the list of things to be done before this CREATE/ALTER - * TABLE. + * Build a CREATE SEQUENCE command to create the sequence object, and add + * it to the list of things to be done before this CREATE/ALTER TABLE. */ Seems like, in src/backend/parser/parse_utilcmd.c, you have changed the existing code with pgindent. I think it's not a good idea to mix pgindent changes with your patch. -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-14T07:00:58Z
On Fri, May 12, 2017 at 10:39 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > On Fri, May 12, 2017 at 6:08 PM, amul sul <sulamul@gmail.com> wrote: >> Hi, >> >> Please find the following updated patches attached: >> >> 0001-Cleanup.patch : Does some cleanup and code refactoring required >> for hash partition patch. Otherwise, there will be unnecessary diff in >> 0002 patch > > Thanks for splitting the patch. > > + if (isnull[0]) > + cur_index = partdesc->boundinfo->null_index; > This code assumes that null_index will be set to -1 when has_null is false. Per > RelationBuildPartitionDesc() this is true. But probably we should write this > code as > if (isnull[0]) > { > if (partdesc->boundinfo->has_null) > cur_index = partdesc->boundinfo->null_index; > } > That way we are certain that when has_null is false, cur_index = -1 similar to > the original code. > Okay will add this. I still don't understood point of having has_null variable, if no null accepting partition exists then null_index is alway set to -1 in RelationBuildPartitionDesc. Anyway, let not change the original code. > Additional arguement to ComputePartitionAttrs() isn't used anywhere in this > patch, so may be this better be part of 0002. If we do this the only change > that will remain in patch is the refactoring of RelationBuildPartitionDesc(), > so we may consider merging it into 0002, unless we find that some more > refactoring is needed. But for now, having it as a separate patch helps. > Okay. > Here's some more comments on 0002 > > + * In the case of hash partitioning, datums is a 2-D array, stores modulus and > + * remainder values at datums[x][0] and datums[x][1] respectively for each > + * partition in the ascending order. > > This comment about datums should appear in a paragraph of itself and may be > rephrased as in the attached patch. May be we could also add a line about > ndatums for hash partitioned tables as in the attached patch. > Thanks, looks good to me; will include this. [...] > > + if (key->strategy == PARTITION_STRATEGY_HASH) > + { > + ndatums = nparts; > + hbounds = (PartitionHashBound **) palloc(nparts * > + > sizeof(PartitionHashBound *)); > + i = 0; > + foreach (cell, boundspecs) > + { > + PartitionBoundSpec *spec = lfirst(cell); > + > [ clipped ] > + hbounds[i]->index = i; > + i++; > + } > For list and range partitioned table we order the bounds so that two > partitioned tables have them in the same order irrespective of order in which > they are specified by the user or hence stored in the catalogs. The partitions > then get indexes according the order in which their bounds appear in ordered > arrays of bounds. Thus any two partitioned tables with same partition > specification always have same PartitionBoundInfoData. This helps in > partition-wise join to match partition bounds of two given tables. Above code > assigns the indexes to the partitions as they appear in the catalogs. This > means that two partitioned tables with same partition specification but > different order for partition bound specification will have different > PartitionBoundInfoData represenation. > > If we do that, probably partition_bounds_equal() would reduce to just matching > indexes and the last element of datums array i.e. the greatest modulus datum. > If ordered datums array of two partitioned table do not match exactly, the > mismatch can be because missing datums or different datums. If it's a missing > datum it will change the greatest modulus or have corresponding entry in > indexes array as -1. If the entry differs it will cause mismatching indexes in > the index arrays. > Make sense, will fix this. [...] > > + if (offset < 0) > + { > + nmod = DatumGetInt32(datums[0][0]); > + valid_bound = (nmod % spec->modulus) == 0; > + } > + else > + { > + pmod = DatumGetInt32(datums[offset][0]); > + valid_bound = (spec->modulus % pmod) == 0; > + > + if (valid_bound && (offset + 1) < ndatums) > + { > + nmod = DatumGetInt32(datums[offset + 1][0]); > + valid_bound = (nmod % spec->modulus) == 0; > + } > + } > May be name the variables as prev_mod(ulus) and next_mod(ulus) for better > readability. > Okay, will rename to prev_modulus and next_modulus resp. > + * for p_p1: satisfies_hash_partition(2, 1, hash_fn(a), hash_fn(b)) > + * for p_p2: satisfies_hash_partition(4, 2, hash_fn(a), hash_fn(b)) > + * for p_p3: satisfies_hash_partition(8, 0, hash_fn(a), hash_fn(b)) > + * for p_p4: satisfies_hash_partition(8, 4, hash_fn(a), hash_fn(b)) > The description here may be read as if we are calling the same hash function > for both a and b, but that's not true. So, you may want to clarify that > in hash_fn(a) hash_fn means hash function specified for key a. > Okay. > > + if (key->partattrs[i] != 0) > + { > + keyCol = (Node *) makeVar(1, > + key->partattrs[i], > + key->parttypid[i], > + key->parttypmod[i], > + key->parttypcoll[i], > + 0); > + > + /* Form hash_fn(value) expression */ > + keyCol = (Node *) makeFuncExpr(key->partsupfunc[i].fn_oid, > + get_fn_expr_rettype(&key->partsupfunc[i]), > + list_make1(keyCol), > + InvalidOid, > + InvalidOid, > + COERCE_EXPLICIT_CALL); > + } > + else > + { > + keyCol = (Node *) copyObject(lfirst(partexprs_item)); > + partexprs_item = lnext(partexprs_item); > + } > I think we should add FuncExpr for column Vars as well as expressions. > Okay, will fix this. > The logic to compare two bounds is duplicated in qsort_partition_hbound_cmp() > and partition_bound_cmp(). Should we separate it into a separate function > accepting moduli and remainders. That way in case we change it in future, we > have to change only one place. > Okay. > I think we need more comments for compute_hash_value(), mix_hash_value() and > satisfies_hash_partition() as to what each of them accepts and what it > computes. > > + /* key's hash values start from third argument of function. */ > + if (!PG_ARGISNULL(i + 2)) > + { > + values[i] = PG_GETARG_DATUM(i + 2); > + isnull[i] = false; > + } > + else > + isnull[i] = true; > You could write this as > isnull[i] = PG_ARGISNULL(i + 2); > if (isnull[i]) > values[i] = PG_GETARG_DATUM(i + 2); > Okay. > > + * Identify a btree or hash opclass to use. Currently, we use only > + * btree operators, which seems enough for list and range partitioning, > + * and hash operators for hash partitioning. > > The wording, if not read carefully, might be read as "we use only btree > operators". I suggest we rephrase it as "Identify opclass to use. For > list and range > partitioning we use only btree operators, which seems enough for those. For > hash partitioning, we use hash operators." for clarity. > Okay > + foreach (lc, $5) > + { > + DefElem *opt = (DefElem *) lfirst(lc); > A search on WITH in gram.y shows that we do not handle WITH options in gram.y. > Usually they are handled at the transformation stage. Why is this an exception? > If you do that, we can have all the error handling in > transformPartitionBound(). > If so, ForValues need to return list for hash and PartitionBoundSpec for other two; wouldn't that break code consistency? And such validation is not new in gram.y see xmltable_column_el. > +DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 > 2276 0 f f f f f f i s 3 0 16 "23 23 2276" _null_ _null_ _null_ _null_ > _null_ satisfies_hash_partition _null_ _null_ _null_ )); > Why is third argument to this function ANY? Shouldn't it be INT4ARRAY (variadic > INT4)? > Will use INT4ARRAY in next patch, but I am little sceptical of it. we need an unsigned int32, but unfortunately there is not variadic uint32 support. How about INT8ARRAY? Regards, Amul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-14T08:00:24Z
On Sat, May 13, 2017 at 12:11 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > On Fri, May 12, 2017 at 6:08 PM, amul sul <sulamul@gmail.com> wrote: >> Hi, >> >> Please find the following updated patches attached: > > I have done some testing with the new patch, most of the cases worked > as per the expectation except below > > I expect the planner to select only "Seq Scan on t1" whereas it's > scanning both the partitions? > > create table t (a int, b varchar) partition by hash(a); > create table t1 partition of t for values with (modulus 8, remainder 0); > create table t2 partition of t for values with (modulus 8, remainder 1); > > postgres=# explain select * from t where a=8; > QUERY PLAN > ---------------------------------------------------------- > Append (cost=0.00..51.75 rows=12 width=36) > -> Seq Scan on t1 (cost=0.00..25.88 rows=6 width=36) > Filter: (a = 8) > -> Seq Scan on t2 (cost=0.00..25.88 rows=6 width=36) > Filter: (a = 8) > (5 rows) > You are correct. As of now constraint exclusion doesn't work on partition constraint involves function call[1], and hash partition constraint does have satisfies_hash_partition() function call. > > Some cosmetic comments. > ----------------------------------- > + RangeVar *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg)); > + > > Useless Hunk. > > /* > - * Build a CREATE SEQUENCE command to create the sequence object, and > - * add it to the list of things to be done before this CREATE/ALTER > - * TABLE. > + * Build a CREATE SEQUENCE command to create the sequence object, and add > + * it to the list of things to be done before this CREATE/ALTER TABLE. > */ > > Seems like, in src/backend/parser/parse_utilcmd.c, you have changed > the existing code with > pgindent. I think it's not a good idea to mix pgindent changes with your patch. > Oops, my silly mistake, sorry about that. Fixed in attached version. Regards, Amul 1] https://www.postgresql.org/message-id/CA%2BTgmoaE9NZ_RiqZQLp2aJXPO4E78QxkQYL-FR2zCDop96Ahdg%40mail.gmail.com
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-15T10:57:13Z
On Wed, May 10, 2017 at 10:13 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Wed, May 10, 2017 at 8:34 AM, Ashutosh Bapat > <ashutosh.bapat@enterprisedb.com> wrote: >> Hash partitioning will partition the data based on the hash value of the >> partition key. Does that require collation? Should we throw an error/warning if >> collation is specified in PARTITION BY clause? > > Collation is only relevant for ordering, not equality. Since hash > opclasses provide only equality, not ordering, it's not relevant here. > I'm not sure whether we should error out if it's specified or just > silently ignore it. Maybe an ERROR is a good idea? But not sure. > IMHO, we could simply have a WARNING, and ignore collation, thoughts? Updated patches attached. Regards, Amul
-
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-05-15T15:36:35Z
On Mon, May 15, 2017 at 4:27 PM, amul sul <sulamul@gmail.com> wrote: > Updated patches attached. While testing latest patch I found a strange behaviour. test1: postgres=# create table x (a int) partition by hash(a); CREATE TABLE postgres=# create table x1 partition of x for values with (modulus 4, remainder 0); CREATE TABLE postgres=# create table x2 partition of x for values with (modulus 4, remainder 1); CREATE TABLE postgres=# insert into x values(1); 2017-05-15 20:55:20.446 IST [28045] ERROR: no partition of relation "x" found for row 2017-05-15 20:55:20.446 IST [28045] DETAIL: Partition key of the failing row contains (a) = (1). 2017-05-15 20:55:20.446 IST [28045] STATEMENT: insert into x values(1); ERROR: no partition of relation "x" found for row DETAIL: Partition key of the failing row contains (a) = (1). Test2: postgres=# insert into x2 values(100); -- it should violates partition constraint INSERT 0 1 Seems like a bug or am I missing something completely? -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-15T15:43:47Z
On Mon, May 15, 2017 at 6:57 AM, amul sul <sulamul@gmail.com> wrote: >> Collation is only relevant for ordering, not equality. Since hash >> opclasses provide only equality, not ordering, it's not relevant here. >> I'm not sure whether we should error out if it's specified or just >> silently ignore it. Maybe an ERROR is a good idea? But not sure. >> > IMHO, we could simply have a WARNING, and ignore collation, thoughts? > > Updated patches attached. I think that WARNING is rarely a good compromise between ERROR and nothing. I think we should just decide whether this is legal (and then allow it without a WARNING) or not legal (and then ERROR). Telling the user that it's allowed but we don't like it doesn't really help much. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-05-15T15:43:58Z
On Mon, May 15, 2017 at 9:06 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > Test2: > postgres=# insert into x2 values(100); -- it should violates > partition constraint > INSERT 0 1 > > Seems like a bug or am I missing something completely? Sorry, my bad. It's modulus on the hashvalue, not the column. -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-16T03:45:43Z
On Mon, May 15, 2017 at 9:13 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Mon, May 15, 2017 at 6:57 AM, amul sul <sulamul@gmail.com> wrote: >>> Collation is only relevant for ordering, not equality. Since hash >>> opclasses provide only equality, not ordering, it's not relevant here. >>> I'm not sure whether we should error out if it's specified or just >>> silently ignore it. Maybe an ERROR is a good idea? But not sure. >>> >> IMHO, we could simply have a WARNING, and ignore collation, thoughts? >> >> Updated patches attached. > > I think that WARNING is rarely a good compromise between ERROR and > nothing. I think we should just decide whether this is legal (and > then allow it without a WARNING) or not legal (and then ERROR). > Telling the user that it's allowed but we don't like it doesn't really > help much. +1. We should throw an error and add a line in documentation that collation should not be specified for hash partitioned table. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-16T04:33:06Z
On Mon, May 15, 2017 at 9:13 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Mon, May 15, 2017 at 6:57 AM, amul sul <sulamul@gmail.com> wrote: >>> Collation is only relevant for ordering, not equality. Since hash >>> opclasses provide only equality, not ordering, it's not relevant here. >>> I'm not sure whether we should error out if it's specified or just >>> silently ignore it. Maybe an ERROR is a good idea? But not sure. >>> >> IMHO, we could simply have a WARNING, and ignore collation, thoughts? >> >> Updated patches attached. > > I think that WARNING is rarely a good compromise between ERROR and > nothing. I think we should just decide whether this is legal (and > then allow it without a WARNING) or not legal (and then ERROR). > Telling the user that it's allowed but we don't like it doesn't really > help much. Understood, will throw an ERROR instead. Thank you. Regards, Amul
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-16T07:19:30Z
On Tue, May 16, 2017 at 10:03 AM, amul sul <sulamul@gmail.com> wrote: > On Mon, May 15, 2017 at 9:13 PM, Robert Haas <robertmhaas@gmail.com> wrote: >>>> Collation is only relevant for ordering, not equality. While earlier, I thought the same, I am wondering whether this is true. Don't different collations deem different strings equal e.g one collation may deem 'aa' and 'AA' as same but other may not. Or is that encoding problem being discussed in hash functions thread? Sorry for the confusion. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-16T07:32:08Z
On Sun, May 14, 2017 at 12:30 PM, amul sul <sulamul@gmail.com> wrote: > On Fri, May 12, 2017 at 10:39 PM, Ashutosh Bapat > <ashutosh.bapat@enterprisedb.com> wrote: >> On Fri, May 12, 2017 at 6:08 PM, amul sul <sulamul@gmail.com> wrote: >>> Hi, >>> >>> Please find the following updated patches attached: >>> >>> 0001-Cleanup.patch : Does some cleanup and code refactoring required >>> for hash partition patch. Otherwise, there will be unnecessary diff in >>> 0002 patch >> >> Thanks for splitting the patch. >> >> + if (isnull[0]) >> + cur_index = partdesc->boundinfo->null_index; >> This code assumes that null_index will be set to -1 when has_null is false. Per >> RelationBuildPartitionDesc() this is true. But probably we should write this >> code as >> if (isnull[0]) >> { >> if (partdesc->boundinfo->has_null) >> cur_index = partdesc->boundinfo->null_index; >> } >> That way we are certain that when has_null is false, cur_index = -1 similar to >> the original code. >> > Okay will add this. Thanks. > I still don't understood point of having has_null > variable, if no null accepting partition exists then null_index is > alway set to -1 in RelationBuildPartitionDesc. Anyway, let not change > the original code. I agree. has_null might have been folded as null_index == -1. But that's not the problem of this patch. 0001 looks good to me now. > > [...] >> >> + if (key->strategy == PARTITION_STRATEGY_HASH) >> + { >> + ndatums = nparts; >> + hbounds = (PartitionHashBound **) palloc(nparts * >> + >> sizeof(PartitionHashBound *)); >> + i = 0; >> + foreach (cell, boundspecs) >> + { >> + PartitionBoundSpec *spec = lfirst(cell); >> + >> [ clipped ] >> + hbounds[i]->index = i; >> + i++; >> + } >> For list and range partitioned table we order the bounds so that two >> partitioned tables have them in the same order irrespective of order in which >> they are specified by the user or hence stored in the catalogs. The partitions >> then get indexes according the order in which their bounds appear in ordered >> arrays of bounds. Thus any two partitioned tables with same partition >> specification always have same PartitionBoundInfoData. This helps in >> partition-wise join to match partition bounds of two given tables. Above code >> assigns the indexes to the partitions as they appear in the catalogs. This >> means that two partitioned tables with same partition specification but >> different order for partition bound specification will have different >> PartitionBoundInfoData represenation. >> >> If we do that, probably partition_bounds_equal() would reduce to just matching >> indexes and the last element of datums array i.e. the greatest modulus datum. >> If ordered datums array of two partitioned table do not match exactly, the >> mismatch can be because missing datums or different datums. If it's a missing >> datum it will change the greatest modulus or have corresponding entry in >> indexes array as -1. If the entry differs it will cause mismatching indexes in >> the index arrays. >> > Make sense, will fix this. I don't see this being addressed in the patches attached in the reply to Dilip. > >> >> + if (key->partattrs[i] != 0) >> + { >> + keyCol = (Node *) makeVar(1, >> + key->partattrs[i], >> + key->parttypid[i], >> + key->parttypmod[i], >> + key->parttypcoll[i], >> + 0); >> + >> + /* Form hash_fn(value) expression */ >> + keyCol = (Node *) makeFuncExpr(key->partsupfunc[i].fn_oid, >> + get_fn_expr_rettype(&key->partsupfunc[i]), >> + list_make1(keyCol), >> + InvalidOid, >> + InvalidOid, >> + COERCE_EXPLICIT_CALL); >> + } >> + else >> + { >> + keyCol = (Node *) copyObject(lfirst(partexprs_item)); >> + partexprs_item = lnext(partexprs_item); >> + } >> I think we should add FuncExpr for column Vars as well as expressions. >> > Okay, will fix this. Here, please add a check similar to get_quals_for_range() 1840 if (partexprs_item == NULL) 1841 elog(ERROR, "wrong number of partition key expressions"); > >> I think we need more comments for compute_hash_value(), mix_hash_value() and >> satisfies_hash_partition() as to what each of them accepts and what it >> computes. >> >> + /* key's hash values start from third argument of function. */ >> + if (!PG_ARGISNULL(i + 2)) >> + { >> + values[i] = PG_GETARG_DATUM(i + 2); >> + isnull[i] = false; >> + } >> + else >> + isnull[i] = true; >> You could write this as >> isnull[i] = PG_ARGISNULL(i + 2); >> if (isnull[i]) >> values[i] = PG_GETARG_DATUM(i + 2); >> > Okay. If we have used this technique somewhere else in PG code, please mention that function/place. /* * Rotate hash left 1 bit before mixing in the next column. This * prevents equal values in different keys from cancelling each other. */ > >> + foreach (lc, $5) >> + { >> + DefElem *opt = (DefElem *) lfirst(lc); >> A search on WITH in gram.y shows that we do not handle WITH options in gram.y. >> Usually they are handled at the transformation stage. Why is this an exception? >> If you do that, we can have all the error handling in >> transformPartitionBound(). >> > If so, ForValues need to return list for hash and PartitionBoundSpec > for other two; wouldn't that break code consistency? And such > validation is not new in gram.y see xmltable_column_el. Thanks for pointing that out. Ok, then may be leave it in gram.y. But may be we should move the error handling in transform function. > >> +DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 >> 2276 0 f f f f f f i s 3 0 16 "23 23 2276" _null_ _null_ _null_ _null_ >> _null_ satisfies_hash_partition _null_ _null_ _null_ )); >> Why is third argument to this function ANY? Shouldn't it be INT4ARRAY (variadic >> INT4)? >> > Will use INT4ARRAY in next patch, but I am little sceptical of it. we > need an unsigned int32, but unfortunately there is not variadic uint32 > support. How about INT8ARRAY? Hmm, I think as long as the binary representation of given unsigned integer doesn't change in the function call, we could cast an INT32 datums into unsigned int32, so spending extra 4 bytes per partition key doesn't look like worth the effort. A related question is, all hash functions have return type as "integer" but internally they return uint32. Why not to do the same for this function as well? -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-16T07:47:26Z
Hi, Here's patch with some cosmetic fixes to 0002, to be applied on top of 0002. On Tue, May 16, 2017 at 1:02 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > On Sun, May 14, 2017 at 12:30 PM, amul sul <sulamul@gmail.com> wrote: >> On Fri, May 12, 2017 at 10:39 PM, Ashutosh Bapat >> <ashutosh.bapat@enterprisedb.com> wrote: >>> On Fri, May 12, 2017 at 6:08 PM, amul sul <sulamul@gmail.com> wrote: >>>> Hi, >>>> >>>> Please find the following updated patches attached: >>>> >>>> 0001-Cleanup.patch : Does some cleanup and code refactoring required >>>> for hash partition patch. Otherwise, there will be unnecessary diff in >>>> 0002 patch >>> >>> Thanks for splitting the patch. >>> >>> + if (isnull[0]) >>> + cur_index = partdesc->boundinfo->null_index; >>> This code assumes that null_index will be set to -1 when has_null is false. Per >>> RelationBuildPartitionDesc() this is true. But probably we should write this >>> code as >>> if (isnull[0]) >>> { >>> if (partdesc->boundinfo->has_null) >>> cur_index = partdesc->boundinfo->null_index; >>> } >>> That way we are certain that when has_null is false, cur_index = -1 similar to >>> the original code. >>> >> Okay will add this. > > Thanks. > >> I still don't understood point of having has_null >> variable, if no null accepting partition exists then null_index is >> alway set to -1 in RelationBuildPartitionDesc. Anyway, let not change >> the original code. > > I agree. has_null might have been folded as null_index == -1. But > that's not the problem of this patch. > > 0001 looks good to me now. > > >> >> [...] >>> >>> + if (key->strategy == PARTITION_STRATEGY_HASH) >>> + { >>> + ndatums = nparts; >>> + hbounds = (PartitionHashBound **) palloc(nparts * >>> + >>> sizeof(PartitionHashBound *)); >>> + i = 0; >>> + foreach (cell, boundspecs) >>> + { >>> + PartitionBoundSpec *spec = lfirst(cell); >>> + >>> [ clipped ] >>> + hbounds[i]->index = i; >>> + i++; >>> + } >>> For list and range partitioned table we order the bounds so that two >>> partitioned tables have them in the same order irrespective of order in which >>> they are specified by the user or hence stored in the catalogs. The partitions >>> then get indexes according the order in which their bounds appear in ordered >>> arrays of bounds. Thus any two partitioned tables with same partition >>> specification always have same PartitionBoundInfoData. This helps in >>> partition-wise join to match partition bounds of two given tables. Above code >>> assigns the indexes to the partitions as they appear in the catalogs. This >>> means that two partitioned tables with same partition specification but >>> different order for partition bound specification will have different >>> PartitionBoundInfoData represenation. >>> >>> If we do that, probably partition_bounds_equal() would reduce to just matching >>> indexes and the last element of datums array i.e. the greatest modulus datum. >>> If ordered datums array of two partitioned table do not match exactly, the >>> mismatch can be because missing datums or different datums. If it's a missing >>> datum it will change the greatest modulus or have corresponding entry in >>> indexes array as -1. If the entry differs it will cause mismatching indexes in >>> the index arrays. >>> >> Make sense, will fix this. > > I don't see this being addressed in the patches attached in the reply to Dilip. > >> >>> >>> + if (key->partattrs[i] != 0) >>> + { >>> + keyCol = (Node *) makeVar(1, >>> + key->partattrs[i], >>> + key->parttypid[i], >>> + key->parttypmod[i], >>> + key->parttypcoll[i], >>> + 0); >>> + >>> + /* Form hash_fn(value) expression */ >>> + keyCol = (Node *) makeFuncExpr(key->partsupfunc[i].fn_oid, >>> + get_fn_expr_rettype(&key->partsupfunc[i]), >>> + list_make1(keyCol), >>> + InvalidOid, >>> + InvalidOid, >>> + COERCE_EXPLICIT_CALL); >>> + } >>> + else >>> + { >>> + keyCol = (Node *) copyObject(lfirst(partexprs_item)); >>> + partexprs_item = lnext(partexprs_item); >>> + } >>> I think we should add FuncExpr for column Vars as well as expressions. >>> >> Okay, will fix this. > > Here, please add a check similar to get_quals_for_range() > 1840 if (partexprs_item == NULL) > 1841 elog(ERROR, "wrong number of partition key expressions"); > > >> >>> I think we need more comments for compute_hash_value(), mix_hash_value() and >>> satisfies_hash_partition() as to what each of them accepts and what it >>> computes. >>> >>> + /* key's hash values start from third argument of function. */ >>> + if (!PG_ARGISNULL(i + 2)) >>> + { >>> + values[i] = PG_GETARG_DATUM(i + 2); >>> + isnull[i] = false; >>> + } >>> + else >>> + isnull[i] = true; >>> You could write this as >>> isnull[i] = PG_ARGISNULL(i + 2); >>> if (isnull[i]) >>> values[i] = PG_GETARG_DATUM(i + 2); >>> >> Okay. > > If we have used this technique somewhere else in PG code, please > mention that function/place. > /* > * Rotate hash left 1 bit before mixing in the next column. This > * prevents equal values in different keys from cancelling each other. > */ > > >> >>> + foreach (lc, $5) >>> + { >>> + DefElem *opt = (DefElem *) lfirst(lc); >>> A search on WITH in gram.y shows that we do not handle WITH options in gram.y. >>> Usually they are handled at the transformation stage. Why is this an exception? >>> If you do that, we can have all the error handling in >>> transformPartitionBound(). >>> >> If so, ForValues need to return list for hash and PartitionBoundSpec >> for other two; wouldn't that break code consistency? And such >> validation is not new in gram.y see xmltable_column_el. > > Thanks for pointing that out. Ok, then may be leave it in gram.y. But > may be we should move the error handling in transform function. > > >> >>> +DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 >>> 2276 0 f f f f f f i s 3 0 16 "23 23 2276" _null_ _null_ _null_ _null_ >>> _null_ satisfies_hash_partition _null_ _null_ _null_ )); >>> Why is third argument to this function ANY? Shouldn't it be INT4ARRAY (variadic >>> INT4)? >>> >> Will use INT4ARRAY in next patch, but I am little sceptical of it. we >> need an unsigned int32, but unfortunately there is not variadic uint32 >> support. How about INT8ARRAY? > > Hmm, I think as long as the binary representation of given unsigned > integer doesn't change in the function call, we could cast an INT32 > datums into unsigned int32, so spending extra 4 bytes per partition > key doesn't look like worth the effort. > > A related question is, all hash functions have return type as > "integer" but internally they return uint32. Why not to do the same > for this function as well? > > -- > Best Wishes, > Ashutosh Bapat > EnterpriseDB Corporation > The Postgres Database Company -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-16T10:00:00Z
On Tue, May 16, 2017 at 1:02 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: [...] >>> >>> + if (key->strategy == PARTITION_STRATEGY_HASH) >>> + { >>> + ndatums = nparts; >>> + hbounds = (PartitionHashBound **) palloc(nparts * >>> + >>> sizeof(PartitionHashBound *)); >>> + i = 0; >>> + foreach (cell, boundspecs) >>> + { >>> + PartitionBoundSpec *spec = lfirst(cell); >>> + >>> [ clipped ] >>> + hbounds[i]->index = i; >>> + i++; >>> + } >>> For list and range partitioned table we order the bounds so that two >>> partitioned tables have them in the same order irrespective of order in which >>> they are specified by the user or hence stored in the catalogs. The partitions >>> then get indexes according the order in which their bounds appear in ordered >>> arrays of bounds. Thus any two partitioned tables with same partition >>> specification always have same PartitionBoundInfoData. This helps in >>> partition-wise join to match partition bounds of two given tables. Above code >>> assigns the indexes to the partitions as they appear in the catalogs. This >>> means that two partitioned tables with same partition specification but >>> different order for partition bound specification will have different >>> PartitionBoundInfoData represenation. >>> >>> If we do that, probably partition_bounds_equal() would reduce to just matching >>> indexes and the last element of datums array i.e. the greatest modulus datum. >>> If ordered datums array of two partitioned table do not match exactly, the >>> mismatch can be because missing datums or different datums. If it's a missing >>> datum it will change the greatest modulus or have corresponding entry in >>> indexes array as -1. If the entry differs it will cause mismatching indexes in >>> the index arrays. >>> >> Make sense, will fix this. > > I don't see this being addressed in the patches attached in the reply to Dilip. > Fixed in the attached version. >> >>> >>> + if (key->partattrs[i] != 0) >>> + { >>> + keyCol = (Node *) makeVar(1, >>> + key->partattrs[i], >>> + key->parttypid[i], >>> + key->parttypmod[i], >>> + key->parttypcoll[i], >>> + 0); >>> + >>> + /* Form hash_fn(value) expression */ >>> + keyCol = (Node *) makeFuncExpr(key->partsupfunc[i].fn_oid, >>> + get_fn_expr_rettype(&key->partsupfunc[i]), >>> + list_make1(keyCol), >>> + InvalidOid, >>> + InvalidOid, >>> + COERCE_EXPLICIT_CALL); >>> + } >>> + else >>> + { >>> + keyCol = (Node *) copyObject(lfirst(partexprs_item)); >>> + partexprs_item = lnext(partexprs_item); >>> + } >>> I think we should add FuncExpr for column Vars as well as expressions. >>> >> Okay, will fix this. > > Here, please add a check similar to get_quals_for_range() > 1840 if (partexprs_item == NULL) > 1841 elog(ERROR, "wrong number of partition key expressions"); > > Fixed in the attached version. >> >>> I think we need more comments for compute_hash_value(), mix_hash_value() and >>> satisfies_hash_partition() as to what each of them accepts and what it >>> computes. >>> >>> + /* key's hash values start from third argument of function. */ >>> + if (!PG_ARGISNULL(i + 2)) >>> + { >>> + values[i] = PG_GETARG_DATUM(i + 2); >>> + isnull[i] = false; >>> + } >>> + else >>> + isnull[i] = true; >>> You could write this as >>> isnull[i] = PG_ARGISNULL(i + 2); >>> if (isnull[i]) >>> values[i] = PG_GETARG_DATUM(i + 2); >>> >> Okay. > > If we have used this technique somewhere else in PG code, please > mention that function/place. > /* > * Rotate hash left 1 bit before mixing in the next column. This > * prevents equal values in different keys from cancelling each other. > */ > Fixed in the attached version. > >> >>> + foreach (lc, $5) >>> + { >>> + DefElem *opt = (DefElem *) lfirst(lc); >>> A search on WITH in gram.y shows that we do not handle WITH options in gram.y. >>> Usually they are handled at the transformation stage. Why is this an exception? >>> If you do that, we can have all the error handling in >>> transformPartitionBound(). >>> >> If so, ForValues need to return list for hash and PartitionBoundSpec >> for other two; wouldn't that break code consistency? And such >> validation is not new in gram.y see xmltable_column_el. > > Thanks for pointing that out. Ok, then may be leave it in gram.y. But > may be we should move the error handling in transform function. > IMO, let it be there for readability. It will be easier to understand why do we have set -1 for modulus and remainder. > >> >>> +DATA(insert OID = 5028 ( satisfies_hash_partition PGNSP PGUID 12 1 0 >>> 2276 0 f f f f f f i s 3 0 16 "23 23 2276" _null_ _null_ _null_ _null_ >>> _null_ satisfies_hash_partition _null_ _null_ _null_ )); >>> Why is third argument to this function ANY? Shouldn't it be INT4ARRAY (variadic >>> INT4)? >>> >> Will use INT4ARRAY in next patch, but I am little sceptical of it. we >> need an unsigned int32, but unfortunately there is not variadic uint32 >> support. How about INT8ARRAY? > > Hmm, I think as long as the binary representation of given unsigned > integer doesn't change in the function call, we could cast an INT32 > datums into unsigned int32, so spending extra 4 bytes per partition > key doesn't look like worth the effort. > > A related question is, all hash functions have return type as > "integer" but internally they return uint32. Why not to do the same > for this function as well? I see. IIUC, there is no harm to use INT4ARRAY, thanks for explanation. Regards, Amul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-16T10:09:53Z
On Tue, May 16, 2017 at 1:17 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > Hi, > Here's patch with some cosmetic fixes to 0002, to be applied on top of 0002. > Thank you, included in v6 patch. Regards, Amul
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-16T10:52:07Z
On Tue, May 16, 2017 at 3:30 PM, amul sul <sulamul@gmail.com> wrote: > On Tue, May 16, 2017 at 1:02 PM, Ashutosh Bapat > <ashutosh.bapat@enterprisedb.com> wrote: > [...] >>>> >>>> + if (key->strategy == PARTITION_STRATEGY_HASH) >>>> + { >>>> + ndatums = nparts; >>>> + hbounds = (PartitionHashBound **) palloc(nparts * >>>> + >>>> sizeof(PartitionHashBound *)); >>>> + i = 0; >>>> + foreach (cell, boundspecs) >>>> + { >>>> + PartitionBoundSpec *spec = lfirst(cell); >>>> + >>>> [ clipped ] >>>> + hbounds[i]->index = i; >>>> + i++; >>>> + } >>>> For list and range partitioned table we order the bounds so that two >>>> partitioned tables have them in the same order irrespective of order in which >>>> they are specified by the user or hence stored in the catalogs. The partitions >>>> then get indexes according the order in which their bounds appear in ordered >>>> arrays of bounds. Thus any two partitioned tables with same partition >>>> specification always have same PartitionBoundInfoData. This helps in >>>> partition-wise join to match partition bounds of two given tables. Above code >>>> assigns the indexes to the partitions as they appear in the catalogs. This >>>> means that two partitioned tables with same partition specification but >>>> different order for partition bound specification will have different >>>> PartitionBoundInfoData represenation. >>>> >>>> If we do that, probably partition_bounds_equal() would reduce to just matching >>>> indexes and the last element of datums array i.e. the greatest modulus datum. >>>> If ordered datums array of two partitioned table do not match exactly, the >>>> mismatch can be because missing datums or different datums. If it's a missing >>>> datum it will change the greatest modulus or have corresponding entry in >>>> indexes array as -1. If the entry differs it will cause mismatching indexes in >>>> the index arrays. >>>> >>> Make sense, will fix this. >> >> I don't see this being addressed in the patches attached in the reply to Dilip. >> > > Fixed in the attached version. > v6 patch has bug in partition oid mapping and indexing, fixed in the attached version. Now partition oids will be arranged in the ascending order of hash partition bound (i.e. modulus and remainder sorting order) Regards, Amul -
Re: [POC] hash partitioning
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> — 2017-05-16T13:20:43Z
On 5/15/17 23:45, Ashutosh Bapat wrote: > +1. We should throw an error and add a line in documentation that > collation should not be specified for hash partitioned table. Why is it even allowed in the parser then? -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
-
Re: [POC] hash partitioning
Peter Eisentraut <peter.eisentraut@2ndquadrant.com> — 2017-05-16T13:30:21Z
On 5/16/17 03:19, Ashutosh Bapat wrote: > On Tue, May 16, 2017 at 10:03 AM, amul sul <sulamul@gmail.com> wrote: >> On Mon, May 15, 2017 at 9:13 PM, Robert Haas <robertmhaas@gmail.com> wrote: >>>>> Collation is only relevant for ordering, not equality. > > While earlier, I thought the same, I am wondering whether this is > true. Don't different collations deem different strings equal e.g one > collation may deem 'aa' and 'AA' as same but other may not. Or is that > encoding problem being discussed in hash functions thread? The collations we currently support don't do that, unless someone made a custom one. However, we might want to support that in the future. Also, text/varchar comparisons always use strcmp() as a tie-breaker. Again, this might be something to review at some point. But you currently have the citext type that would indeed consider 'aa' and 'AA' equal. But citext also has a hash function in the hash operator class that handles that. So you could look into using that approach. -- Peter Eisentraut http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
-
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-05-16T16:30:57Z
On Tue, May 16, 2017 at 4:22 PM, amul sul <sulamul@gmail.com> wrote: > v6 patch has bug in partition oid mapping and indexing, fixed in the > attached version. > > Now partition oids will be arranged in the ascending order of hash > partition bound (i.e. modulus and remainder sorting order) Thanks for the update patch. I have some more comments. ------------ + if (spec->remainder < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("hash partition remainder must be less than modulus"))); I think this error message is not correct, you might want to change it to "hash partition remainder must be non-negative integer" ------- + The table is partitioned by specifying remainder and modulus for each + partition. Each partition holds rows for which the hash value of Wouldn't it be better to say "modulus and remainder" instead of "remainder and modulus" then it will be consistent? ------- + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because fails, because -> fails because ------- Wouldn't it be a good idea to document how to increase the number of hash partitions, I think we can document it somewhere with an example, something like Robert explained upthread? create table foo (a integer, b text) partition by hash (a); create table foo1 partition of foo with (modulus 2, remainder 0); create table foo2 partition of foo with (modulus 2, remainder 1); You can detach foo1, create two new partitions with modulus 4 and remainders 0 and 2, and move the data over from the old partition I think it will be good information for a user to have? or it's already documented and I missed it? -------- -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-16T17:23:13Z
On Tue, May 16, 2017 at 3:19 AM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > While earlier, I thought the same, I am wondering whether this is > true. Don't different collations deem different strings equal e.g one > collation may deem 'aa' and 'AA' as same but other may not. No, that's not allowed. This has been discussed many times on this mailing list. See varstr_cmp(), which you will notice refuses to return 0 unless the strings are bytewise identical. > Or is that > encoding problem being discussed in hash functions thread? No, that's something else entirely. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-16T18:34:50Z
On Tue, May 16, 2017 at 10:00 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > On Tue, May 16, 2017 at 4:22 PM, amul sul <sulamul@gmail.com> wrote: >> v6 patch has bug in partition oid mapping and indexing, fixed in the >> attached version. >> >> Now partition oids will be arranged in the ascending order of hash >> partition bound (i.e. modulus and remainder sorting order) > > Thanks for the update patch. I have some more comments. > > ------------ > + if (spec->remainder < 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), > + errmsg("hash partition remainder must be less than modulus"))); > > I think this error message is not correct, you might want to change it > to "hash partition remainder must be non-negative integer" > Fixed in the attached version; used "hash partition remainder must be greater than or equal to 0" instead. > ------- > > + The table is partitioned by specifying remainder and modulus for each > + partition. Each partition holds rows for which the hash value of > > Wouldn't it be better to say "modulus and remainder" instead of > "remainder and modulus" then it will be consistent? > You are correct, fixed in the attached version. > ------- > + An <command>UPDATE</> that causes a row to move from one partition to > + another fails, because > > fails, because -> fails because > This hunk is no longer exists in the attached patch, that was mistaken copied, sorry about that. > ------- > > Wouldn't it be a good idea to document how to increase the number of > hash partitions, I think we can document it somewhere with an example, > something like Robert explained upthread? > > create table foo (a integer, b text) partition by hash (a); > create table foo1 partition of foo with (modulus 2, remainder 0); > create table foo2 partition of foo with (modulus 2, remainder 1); > > You can detach foo1, create two new partitions with modulus 4 and > remainders 0 and 2, and move the data over from the old partition > > I think it will be good information for a user to have? or it's > already documented and I missed it? > I think, we should, but not sure about it. Regards, Amul -
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-17T03:34:56Z
On Tue, May 16, 2017 at 6:50 PM, Peter Eisentraut <peter.eisentraut@2ndquadrant.com> wrote: > On 5/15/17 23:45, Ashutosh Bapat wrote: >> +1. We should throw an error and add a line in documentation that >> collation should not be specified for hash partitioned table. > > Why is it even allowed in the parser then? That grammar is common to all the partitioning strategies. It looks like it's easy to handle collation for hash partitions in transformation than in grammar. But, if we could handle it in grammar, I don't have any objection to it. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Tom Lane <tgl@sss.pgh.pa.us> — 2017-05-17T04:08:17Z
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> writes: > On Tue, May 16, 2017 at 6:50 PM, Peter Eisentraut > <peter.eisentraut@2ndquadrant.com> wrote: >> On 5/15/17 23:45, Ashutosh Bapat wrote: >>> +1. We should throw an error and add a line in documentation that >>> collation should not be specified for hash partitioned table. >> Why is it even allowed in the parser then? > That grammar is common to all the partitioning strategies. It looks > like it's easy to handle collation for hash partitions in > transformation than in grammar. But, if we could handle it in grammar, > I don't have any objection to it. If you disallow something in the grammar, the error message is unlikely to be better than "syntax error". That's not very desirable. regards, tom lane
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-17T04:14:04Z
On Wed, May 17, 2017 at 9:38 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote: > Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> writes: >> On Tue, May 16, 2017 at 6:50 PM, Peter Eisentraut >> <peter.eisentraut@2ndquadrant.com> wrote: >>> On 5/15/17 23:45, Ashutosh Bapat wrote: >>>> +1. We should throw an error and add a line in documentation that >>>> collation should not be specified for hash partitioned table. > >>> Why is it even allowed in the parser then? > >> That grammar is common to all the partitioning strategies. It looks >> like it's easy to handle collation for hash partitions in >> transformation than in grammar. But, if we could handle it in grammar, >> I don't have any objection to it. > > If you disallow something in the grammar, the error message is unlikely to > be better than "syntax error". That's not very desirable. Right +1. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-17T05:41:41Z
On Wed, May 17, 2017 at 12:04 AM, amul sul <sulamul@gmail.com> wrote: > On Tue, May 16, 2017 at 10:00 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >> On Tue, May 16, 2017 at 4:22 PM, amul sul <sulamul@gmail.com> wrote: >>> v6 patch has bug in partition oid mapping and indexing, fixed in the >>> attached version. >>> >>> Now partition oids will be arranged in the ascending order of hash >>> partition bound (i.e. modulus and remainder sorting order) >> >> Thanks for the update patch. I have some more comments. >> >> ------------ >> + if (spec->remainder < 0) >> + ereport(ERROR, >> + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), >> + errmsg("hash partition remainder must be less than modulus"))); >> >> I think this error message is not correct, you might want to change it >> to "hash partition remainder must be non-negative integer" >> > > Fixed in the attached version; used "hash partition remainder must be > greater than or equal to 0" instead. I would suggest "non-zero positive", since that's what we are using in the documentation. > >> ------- >> >> + The table is partitioned by specifying remainder and modulus for each >> + partition. Each partition holds rows for which the hash value of >> >> Wouldn't it be better to say "modulus and remainder" instead of >> "remainder and modulus" then it will be consistent? >> > > You are correct, fixed in the attached version. > >> ------- >> + An <command>UPDATE</> that causes a row to move from one partition to >> + another fails, because >> >> fails, because -> fails because >> > > This hunk is no longer exists in the attached patch, that was mistaken > copied, sorry about that. > >> ------- >> >> Wouldn't it be a good idea to document how to increase the number of >> hash partitions, I think we can document it somewhere with an example, >> something like Robert explained upthread? >> >> create table foo (a integer, b text) partition by hash (a); >> create table foo1 partition of foo with (modulus 2, remainder 0); >> create table foo2 partition of foo with (modulus 2, remainder 1); >> >> You can detach foo1, create two new partitions with modulus 4 and >> remainders 0 and 2, and move the data over from the old partition >> >> I think it will be good information for a user to have? or it's >> already documented and I missed it? >> This is already part of documentation contained in the patch. Here are some more comments @@ -3296,6 +3311,14 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02 not the partitioned table. </para> </listitem> + + <listitem> + <para> + An <command>UPDATE</> that causes a row to move from one partition to + another fails, because the new value of the row fails to satisfy the + implicit partition constraint of the original partition. + </para> + </listitem> </itemizedlist> </para> </sect3> The description in this chunk is applicable to all the kinds of partitioning. Why should it be part of a patch implementing hash partitioning? + Declarative partitioning only supports hash, list and range + partitioning, whereas table inheritance allows data to be + divided in a manner of the user's choosing. (Note, however, + that if constraint exclusion is unable to prune partitions + effectively, query performance will be very poor.) Looks like the line width is less than 80 characters. In partition_bounds_equal(), please add comments explaining why is it safe to check just the indexes? May be we should add code under assertion to make sure that the datums are equal as well. The comment could be something like, "If two partitioned tables have different greatest moduli, their partition schemes don't match. If they have same greatest moduli, and all remainders have different indexes, they all have same modulus specified and the partitions are ordered by remainders, thus indexes array will be an identity i.e. index[i] = i. If the partition corresponding to a given remainder exists, it will have same index entry for both partitioned tables or if it's missing it will be -1. Thus if indexes array matches, corresponding datums array matches. If there are multiple remainders corresponding to a given partition, their partitions are ordered by the lowest of the remainders, thus if indexes array matches, both of the tables have same indexes arrays, in both the tables remainders corresponding to multiple partitions all have same indexes and thus same modulus. Thus again if the indexes are same, datums are same.". In the same function if (key->strategy == PARTITION_STRATEGY_HASH) { int greatest_modulus; /* * Compare greatest modulus of hash partition bound which * is the last element of datums array. */ if (b1->datums[b1->ndatums - 1][0] != b2->datums[b2->ndatums - 1][0]) return false; /* Compare indexes */ greatest_modulus = DatumGetInt32(b1->datums[b1->ndatums - 1][0]); for (i = 0; i < greatest_modulus; i++) if (b1->indexes[i] != b2->indexes[i]) return false; } if we return true from where this block ends, we will save one indenation level for rest of the code and also FWIW extra diffs in this patch because of this indentation change. + /* + * Hash operator classes provide only equality, not ordering. + * Collation, which is relevant for ordering and not equality is + * irrelevant for hash partitioning. + */ A comma is missing after "equality", and may be we need "for" before "equality". * Collation, which is relevant for ordering and not equality, is + * we use hash operator class. */ */ should be on new line. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-17T08:37:44Z
On Wed, May 17, 2017 at 11:11 AM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > On Wed, May 17, 2017 at 12:04 AM, amul sul <sulamul@gmail.com> wrote: >> On Tue, May 16, 2017 at 10:00 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: >>> On Tue, May 16, 2017 at 4:22 PM, amul sul <sulamul@gmail.com> wrote: >>>> v6 patch has bug in partition oid mapping and indexing, fixed in the >>>> attached version. >>>> >>>> Now partition oids will be arranged in the ascending order of hash >>>> partition bound (i.e. modulus and remainder sorting order) >>> >>> Thanks for the update patch. I have some more comments. >>> >>> ------------ >>> + if (spec->remainder < 0) >>> + ereport(ERROR, >>> + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), >>> + errmsg("hash partition remainder must be less than modulus"))); >>> >>> I think this error message is not correct, you might want to change it >>> to "hash partition remainder must be non-negative integer" >>> >> >> Fixed in the attached version; used "hash partition remainder must be >> greater than or equal to 0" instead. > > I would suggest "non-zero positive", since that's what we are using in > the documentation. > Understood, Fixed in the attached version. >> >>> ------- >>> >>> + The table is partitioned by specifying remainder and modulus for each >>> + partition. Each partition holds rows for which the hash value of >>> >>> Wouldn't it be better to say "modulus and remainder" instead of >>> "remainder and modulus" then it will be consistent? >>> >> >> You are correct, fixed in the attached version. >> >>> ------- >>> + An <command>UPDATE</> that causes a row to move from one partition to >>> + another fails, because >>> >>> fails, because -> fails because >>> >> >> This hunk is no longer exists in the attached patch, that was mistaken >> copied, sorry about that. >> >>> ------- >>> >>> Wouldn't it be a good idea to document how to increase the number of >>> hash partitions, I think we can document it somewhere with an example, >>> something like Robert explained upthread? >>> >>> create table foo (a integer, b text) partition by hash (a); >>> create table foo1 partition of foo with (modulus 2, remainder 0); >>> create table foo2 partition of foo with (modulus 2, remainder 1); >>> >>> You can detach foo1, create two new partitions with modulus 4 and >>> remainders 0 and 2, and move the data over from the old partition >>> >>> I think it will be good information for a user to have? or it's >>> already documented and I missed it? >>> > > This is already part of documentation contained in the patch. > > Here are some more comments > @@ -3296,6 +3311,14 @@ ALTER TABLE measurement ATTACH PARTITION > measurement_y2008m02 > not the partitioned table. > </para> > </listitem> > + > + <listitem> > + <para> > + An <command>UPDATE</> that causes a row to move from one partition to > + another fails, because the new value of the row fails to satisfy the > + implicit partition constraint of the original partition. > + </para> > + </listitem> > </itemizedlist> > </para> > </sect3> > The description in this chunk is applicable to all the kinds of partitioning. > Why should it be part of a patch implementing hash partitioning? > This was already addressed in the previous patch(v8). > + Declarative partitioning only supports hash, list and range > + partitioning, whereas table inheritance allows data to be > + divided in a manner of the user's choosing. (Note, however, > + that if constraint exclusion is unable to prune partitions > + effectively, query performance will be very poor.) > Looks like the line width is less than 80 characters. > Fixed in the attached version. > In partition_bounds_equal(), please add comments explaining why is it safe to > check just the indexes? May be we should add code under assertion to make sure > that the datums are equal as well. Added assert in the attached version. > The comment could be something > like, "If two partitioned tables have different greatest moduli, their > partition schemes don't match. If they have same greatest moduli, and > all remainders have different indexes, they all have same modulus > specified and the partitions are ordered by remainders, thus indexes > array will be an identity i.e. index[i] = i. If the partition > corresponding to a given remainder exists, it will have same index > entry for both partitioned tables or if it's missing it will be -1. > Thus if indexes array matches, corresponding datums array matches. If > there are multiple remainders corresponding to a given partition, > their partitions are ordered by the lowest of the remainders, thus if > indexes array matches, both of the tables have same indexes arrays, in > both the tables remainders corresponding to multiple partitions all > have same indexes and thus same modulus. Thus again if the indexes are > same, datums are same.". > Thanks, added with minor modification. > In the same function > if (key->strategy == PARTITION_STRATEGY_HASH) > { > int greatest_modulus; > > /* > * Compare greatest modulus of hash partition bound which > * is the last element of datums array. > */ > if (b1->datums[b1->ndatums - 1][0] != b2->datums[b2->ndatums - 1][0]) > return false; > > /* Compare indexes */ > greatest_modulus = DatumGetInt32(b1->datums[b1->ndatums - 1][0]); > for (i = 0; i < greatest_modulus; i++) > if (b1->indexes[i] != b2->indexes[i]) > return false; > } > if we return true from where this block ends, we will save one indenation level > for rest of the code and also FWIW extra diffs in this patch because of this > indentation change. > I still do believe having this code in the IF - ELSE block will be better for longterm, rather having code clutter to avoid diff that unpleasant for now. > + /* > + * Hash operator classes provide only equality, not ordering. > + * Collation, which is relevant for ordering and not equality is > + * irrelevant for hash partitioning. > + */ > A comma is missing after "equality", and may be we need "for" before > "equality". > * Collation, which is relevant for ordering and not equality, is > > + * we use hash operator class. */ > */ should be on new line. > Fixed. Regards, Amul -
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-17T13:24:46Z
On Wed, May 17, 2017 at 2:07 PM, amul sul <sulamul@gmail.com> wrote: > >> In partition_bounds_equal(), please add comments explaining why is it safe to >> check just the indexes? May be we should add code under assertion to make sure >> that the datums are equal as well. > > Added assert in the attached version. > >> The comment could be something >> like, "If two partitioned tables have different greatest moduli, their >> partition schemes don't match. If they have same greatest moduli, and >> all remainders have different indexes, they all have same modulus >> specified and the partitions are ordered by remainders, thus indexes >> array will be an identity i.e. index[i] = i. If the partition >> corresponding to a given remainder exists, it will have same index >> entry for both partitioned tables or if it's missing it will be -1. >> Thus if indexes array matches, corresponding datums array matches. If >> there are multiple remainders corresponding to a given partition, >> their partitions are ordered by the lowest of the remainders, thus if >> indexes array matches, both of the tables have same indexes arrays, in >> both the tables remainders corresponding to multiple partitions all >> have same indexes and thus same modulus. Thus again if the indexes are >> same, datums are same.". >> > > Thanks, added with minor modification. I have reworded this slightly better. See the attached patch as diff of 0002. > >> In the same function >> if (key->strategy == PARTITION_STRATEGY_HASH) >> { >> int greatest_modulus; >> >> /* >> * Compare greatest modulus of hash partition bound which >> * is the last element of datums array. >> */ >> if (b1->datums[b1->ndatums - 1][0] != b2->datums[b2->ndatums - 1][0]) >> return false; >> >> /* Compare indexes */ >> greatest_modulus = DatumGetInt32(b1->datums[b1->ndatums - 1][0]); >> for (i = 0; i < greatest_modulus; i++) >> if (b1->indexes[i] != b2->indexes[i]) >> return false; >> } >> if we return true from where this block ends, we will save one indenation level >> for rest of the code and also FWIW extra diffs in this patch because of this >> indentation change. >> > > I still do believe having this code in the IF - ELSE block will be > better for longterm, rather having code clutter to avoid diff that > unpleasant for now. Ok, I will leave it to the committer to judge. Comments on the tests +#ifdef USE_ASSERT_CHECKING + { + /* + * Hash partition bound stores modulus and remainder at + * b1->datums[i][0] and b1->datums[i][1] position respectively. + */ + for (i = 0; i < b1->ndatums; i++) + Assert((b1->datums[i][0] == b2->datums[i][0] && + b1->datums[i][1] == b2->datums[i][1])); + } +#endif Why do we need extra {} here? Comments on testcases +CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH (modulus 8, remainder 0); +CREATE TABLE fail_part (LIKE hpart_1 INCLUDING CONSTRAINTS); +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH (modulus 4, remainder 0); Probably you should also test the other-way round case i.e. create modulus 4, remainder 0 partition and then try to add partitions with modulus 8, remainder 4 and modulus 8, remainder 0. That should fail. Why to create two tables hash_parted and hash_parted2, you should be able to test with only a single table. +INSERT INTO hpart_2 VALUES (3, 'a'); +DELETE FROM hpart_2; +INSERT INTO hpart_5_a (a, b) VALUES (6, 'a'); This is slightly tricky. On different platforms the row may map to different partitions depending upon how the values are hashed. So, this test may not be portable on all the platforms. Probably you should add such testcases with a custom hash operator class which is identity function as suggested by Robert. This also applies to the tests in insert.sql and update.sql for partitioned table without custom opclass. +-- delete the faulting row and also add a constraint to skip the scan +ALTER TABLE hpart_5 ADD CONSTRAINT hcheck_a CHECK (a IN (5)), ALTER a SET NOT NULL; The constraint is not same as the implicit constraint added for that partition. I am not sure whether it's really going to avoid the scan. Did you verify it? If yes, then how? +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH (modulus 3, remainder 2); +ERROR: every hash partition modulus must be a factor of the next larger modulus We should add this test with at least two partitions in there so that we can check lower and upper modulus. Also, testing with some interesting bounds discussed earlier in this mail e.g. adding modulus 15 when 5, 10, 60 exist will be better than testing with 3, 4 and 8. +ERROR: cannot use collation for hash partition key column "a" This seems to indicate that we can not specify collation for hash partition key column, which isn't true. Column a here can have its collation. What's not allowed is specifying collation in PARTITION BY clause. May be reword the error as "cannot use collation for hash partitioning". or plain "cannot use collation in PARTITION BY clause for hash partitioning". +ERROR: invalid bound specification for a list partition +LINE 1: CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES W... + ^ Should the location for this error be that of WITH clause like in case of range and list partitioned table. +select tableoid::regclass as part, a from hash_parted order by part; May be add a % 4 to show clearly that the data really goes to the partitioning with that remainder. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-17T18:21:48Z
On Wed, May 17, 2017 at 1:41 AM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: >> Fixed in the attached version; used "hash partition remainder must be >> greater than or equal to 0" instead. > > I would suggest "non-zero positive", since that's what we are using in > the documentation. Well, that's not very good terminology, because zero is not a positive number. Existing error messages seem to use phrasing such as "THING must be a positive integer" when zero is not allowed or "THING must be a non-negative integer" when zero is allowed. For examples, do git grep errmsg.*positive or git grep errmsg.*negative. > In partition_bounds_equal(), please add comments explaining why is it safe to > check just the indexes? May be we should add code under assertion to make sure > that the datums are equal as well. The comment could be something > like, "If two partitioned tables have different greatest moduli, their > partition schemes don't match. If they have same greatest moduli, and > all remainders have different indexes, they all have same modulus > specified and the partitions are ordered by remainders, thus indexes > array will be an identity i.e. index[i] = i. If the partition > corresponding to a given remainder exists, it will have same index > entry for both partitioned tables or if it's missing it will be -1. > Thus if indexes array matches, corresponding datums array matches. If > there are multiple remainders corresponding to a given partition, > their partitions are ordered by the lowest of the remainders, thus if > indexes array matches, both of the tables have same indexes arrays, in > both the tables remainders corresponding to multiple partitions all > have same indexes and thus same modulus. Thus again if the indexes are > same, datums are same.". That seems quite long. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-18T03:58:22Z
On Wed, May 17, 2017 at 11:51 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Wed, May 17, 2017 at 1:41 AM, Ashutosh Bapat > <ashutosh.bapat@enterprisedb.com> wrote: >>> Fixed in the attached version; used "hash partition remainder must be >>> greater than or equal to 0" instead. >> >> I would suggest "non-zero positive", since that's what we are using in >> the documentation. > > Well, that's not very good terminology, because zero is not a positive > number. Existing error messages seem to use phrasing such as "THING > must be a positive integer" when zero is not allowed or "THING must be > a non-negative integer" when zero is allowed. For examples, do git > grep errmsg.*positive or git grep errmsg.*negative. Ok. We need to change all the usages in the documentation and in the comments to non-negative. The point is to use same phrases consistently. > >> In partition_bounds_equal(), please add comments explaining why is it safe to >> check just the indexes? May be we should add code under assertion to make sure >> that the datums are equal as well. The comment could be something >> like, "If two partitioned tables have different greatest moduli, their >> partition schemes don't match. If they have same greatest moduli, and >> all remainders have different indexes, they all have same modulus >> specified and the partitions are ordered by remainders, thus indexes >> array will be an identity i.e. index[i] = i. If the partition >> corresponding to a given remainder exists, it will have same index >> entry for both partitioned tables or if it's missing it will be -1. >> Thus if indexes array matches, corresponding datums array matches. If >> there are multiple remainders corresponding to a given partition, >> their partitions are ordered by the lowest of the remainders, thus if >> indexes array matches, both of the tables have same indexes arrays, in >> both the tables remainders corresponding to multiple partitions all >> have same indexes and thus same modulus. Thus again if the indexes are >> same, datums are same.". > > That seems quite long. I have shared a patch containing a denser explanation with my last set of comments. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-05-18T16:09:03Z
On Wed, May 17, 2017 at 2:07 PM, amul sul <sulamul@gmail.com> wrote: >> I would suggest "non-zero positive", since that's what we are using in >> the documentation. >> > > Understood, Fixed in the attached version. Why non-zero positive? We do support zero for the remainder right? -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com
-
Re: [POC] hash partitioning
Amit Langote <langote_amit_f8@lab.ntt.co.jp> — 2017-05-19T05:01:39Z
On 2017/05/19 1:09, Dilip Kumar wrote: > On Wed, May 17, 2017 at 2:07 PM, amul sul <sulamul@gmail.com> wrote: >>> I would suggest "non-zero positive", since that's what we are using in >>> the documentation. >>> >> >> Understood, Fixed in the attached version. > > Why non-zero positive? We do support zero for the remainder right? Using "non-negative integers" (for remainders) was suggested upthread. Thanks, Amit
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-19T09:32:51Z
On Wed, May 17, 2017 at 6:54 PM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: [...] > > Comments on the tests > +#ifdef USE_ASSERT_CHECKING > + { > + /* > + * Hash partition bound stores modulus and remainder at > + * b1->datums[i][0] and b1->datums[i][1] position respectively. > + */ > + for (i = 0; i < b1->ndatums; i++) > + Assert((b1->datums[i][0] == b2->datums[i][0] && > + b1->datums[i][1] == b2->datums[i][1])); > + } > +#endif > Why do we need extra {} here? > Okay, removed in the attached version. > Comments on testcases > +CREATE TABLE hpart_1 PARTITION OF hash_parted FOR VALUES WITH > (modulus 8, remainder 0); > +CREATE TABLE fail_part (LIKE hpart_1 INCLUDING CONSTRAINTS); > +ALTER TABLE hash_parted ATTACH PARTITION fail_part FOR VALUES WITH > (modulus 4, remainder 0); > Probably you should also test the other-way round case i.e. create modulus 4, > remainder 0 partition and then try to add partitions with modulus 8, remainder > 4 and modulus 8, remainder 0. That should fail. > Fixed. > Why to create two tables hash_parted and hash_parted2, you should be able to > test with only a single table. > Fixed. > +INSERT INTO hpart_2 VALUES (3, 'a'); > +DELETE FROM hpart_2; > +INSERT INTO hpart_5_a (a, b) VALUES (6, 'a'); > This is slightly tricky. On different platforms the row may map to different > partitions depending upon how the values are hashed. So, this test may not be > portable on all the platforms. Probably you should add such testcases with a > custom hash operator class which is identity function as suggested by Robert. > This also applies to the tests in insert.sql and update.sql for partitioned > table without custom opclass. > Yes, you are correct. Fixed in the attached version. > +-- delete the faulting row and also add a constraint to skip the scan > +ALTER TABLE hpart_5 ADD CONSTRAINT hcheck_a CHECK (a IN (5)), ALTER a > SET NOT NULL; > The constraint is not same as the implicit constraint added for that partition. > I am not sure whether it's really going to avoid the scan. Did you verify it? > If yes, then how? > I haven't tested that, may be I've copied blindly, sorry about that. I don't think this test is needed again for hash partitioning, so removed. > +ALTER TABLE hash_parted2 ATTACH PARTITION fail_part FOR VALUES WITH > (modulus 3, remainder 2); > +ERROR: every hash partition modulus must be a factor of the next > larger modulus > We should add this test with at least two partitions in there so that we can > check lower and upper modulus. Also, testing with some interesting > bounds discussed earlier > in this mail e.g. adding modulus 15 when 5, 10, 60 exist will be better than > testing with 3, 4 and 8. > Similar test do exists in create_table.sql file. > +ERROR: cannot use collation for hash partition key column "a" > This seems to indicate that we can not specify collation for hash partition key > column, which isn't true. Column a here can have its collation. What's not > allowed is specifying collation in PARTITION BY clause. > May be reword the error as "cannot use collation for hash partitioning". or > plain "cannot use collation in PARTITION BY clause for hash partitioning". > > +ERROR: invalid bound specification for a list partition > +LINE 1: CREATE TABLE fail_part PARTITION OF list_parted FOR VALUES W... > + ^ > Should the location for this error be that of WITH clause like in case of range > and list partitioned table. > Fixed. > +select tableoid::regclass as part, a from hash_parted order by part; > May be add a % 4 to show clearly that the data really goes to the partitioning > with that remainder. > Fixed. Updated patch attached. 0001-patch rebased against latest head. 0002-patch also incorporates code comments and error message changes as per Robert's & your suggestions. Thanks ! Regards, Amul -
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-19T17:05:50Z
On Fri, May 19, 2017 at 5:32 AM, amul sul <sulamul@gmail.com> wrote: > Updated patch attached. 0001-patch rebased against latest head. > 0002-patch also incorporates code comments and error message changes > as per Robert's & your suggestions. Thanks ! - if (spec->strategy != PARTITION_STRATEGY_LIST) - elog(ERROR, "invalid strategy in partition bound spec"); + Assert(spec->strategy == PARTITION_STRATEGY_LIST); Let's just drop these hunks. I realize this is a response to a review comment I made, but I take it back. If the existing code is already doing it this way, there's no real need to revise it. The patch doesn't even make it consistent anyway, since elsewhere you elog() for a similar case. Perhaps elog() is best anyway. - partitioning methods include range and list, where each partition is - assigned a range of keys and a list of keys, respectively. + partitioning methods include hash, range and list, where each partition is + assigned a modulus and remainder of keys, a range of keys and a list of + keys, respectively. I think this sentence has become too long and unwieldy, and is more unclear than helpful. I'd just write "The currently supported partitioning methods are list, range, and hash." The use of the word include is actually wrong here, because it implies that there are more not mentioned here, which is false. - expression. If no btree operator class is specified when creating a - partitioned table, the default btree operator class for the datatype will - be used. If there is none, an error will be reported. + expression. List and range partitioning uses only btree operator class. + Hash partitioning uses only hash operator class. If no operator class is + specified when creating a partitioned table, the default operator class + for the datatype will be used. If there is none, an error will be + reported. + </para> I suggest: If no operator class is specified when creating a partitioned table, the default operator class of the appropriate type (btree for list and range partitioning, hash for hash partitioning) will be used. If there is none, an error will be reported. + <para> + Since hash partitiong operator class, provide only equality, not ordering, + collation is not relevant in hash partition key column. An error will be + reported if collation is specified. partitiong -> partitioning. Also, remove the comma after "operator class" and change "not relevant in hash partition key column" to "not relevant for hash partitioning". Also change "if collation is specified" to "if a collation is specified". + Create a hash partitioned table: +<programlisting> +CREATE TABLE orders ( + order_id bigint not null, + cust_id bigint not null, + status text +) PARTITION BY HASH (order_id); +</programlisting></para> Move this down so it's just above the example of creating partitions. + * For range and list partitioned tables, datums is an array of datum-tuples + * with key->partnatts datums each. + * For hash partitioned tables, it is an array of datum-tuples with 2 datums, + * modulus and remainder, corresponding to a given partition. Second line is very short; reflow as one paragraph. * In case of range partitioning, it stores one entry per distinct range * datum, which is the index of the partition for which a given datum * is an upper bound. + * In the case of hash partitioning, the number of the entries in the indexes + * array is same as the greatest modulus amongst all partitions. For a given + * partition key datum-tuple, the index of the partition which would accept that + * datum-tuple would be given by the entry pointed by remainder produced when + * hash value of the datum-tuple is divided by the greatest modulus. Insert line break before the new text as a paragraph break. + char strategy; /* hash, list or range bounds? */ Might be clearer to just write /* hash, list, or range? */ or /* bounds for hash, list, or range? */ +static uint32 compute_hash_value(PartitionKey key, Datum *values, bool *isnull); + I think there should be a blank line after this but not before it. I don't really see why hash partitioning needs to touch partition_bounds_equal() at all. Why can't the existing logic work for hash partitioning without change? + valid_bound = true; valid_modulus, maybe? - errmsg("data type %s has no default btree operator class", - format_type_be(atttype)), - errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); + errmsg("data type %s has no default %s operator class", + format_type_be(atttype), am_method), + errhint("You must specify a %s operator class or define a default %s operator class for the data type.", + am_method, am_method))); Let's use this existing wording from typecmds.c: errmsg("data type %s has no default operator class for access method \"%s\"", and for the hint, maybe: You must specify an operator class or define a default operator class for the data type. Leave out the %s, in other words. + /* + * Hash operator classes provide only equality, not ordering. + * Collation, which is relevant for ordering and not for equality, is + * irrelevant for hash partitioning. + */ + if (*strategy == PARTITION_STRATEGY_HASH && pelem->collation != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use collation for hash partitioning"), + parser_errposition(pstate, pelem->location))); This error message is not very informative, and it requires propagating information about the partitioning type into parts of the code that otherwise don't require it. I was waffling before on whether to ERROR here; I think now I'm in favor of ignoring the problem. The collation won't do any harm; it just won't affect the behavior. + * Identify opclass to use. For list and range partitioning we use + * only btree operator class, which seems enough for those. For hash + * partitioning, we use hash operator class. Strange wording. Suggest: Identify the appropriate operator class. For list and range partitioning, we use a btree operator class; hash partitioning uses a hash operator class. + FOR VALUES WITH '(' hash_partbound ')' /*TODO: syntax is not finalised*/ Remove the comment. + foreach (lc, $5) + { + DefElem *opt = (DefElem *) lfirst(lc); + + if (strcmp(opt->defname, "modulus") == 0) + n->modulus = defGetInt32(opt); + else if (strcmp(opt->defname, "remainder") == 0) + n->remainder = defGetInt32(opt); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized hash partition bound specification \"%s\"", + opt->defname), + parser_errposition(opt->location))); + } This logic doesn't complain if the same option is specified more than once. I suggest adding a check for that, and also pushing this logic out into a helper function that gets called here instead of including it inline. + errmsg("hash partition modulus must be a positive integer"))); modulus for hash partition + errmsg("hash partition remainder must be a non-negative integer"))); remainder for hash partition + errmsg("hash partition modulus must be greater than remainder"))); modulus for hash partition must be greater than remainder +-- values are hashed, row may map to different partitions, which result in the row +-- regression failure. To avoid this, let's create non-default hash function create a non-default -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company -
Re: [POC] hash partitioning
Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> — 2017-05-22T05:49:30Z
On Fri, May 19, 2017 at 10:35 PM, Robert Haas <robertmhaas@gmail.com> wrote: > > + * For range and list partitioned tables, datums is an array of datum-tuples > + * with key->partnatts datums each. > + * For hash partitioned tables, it is an array of datum-tuples with 2 datums, > + * modulus and remainder, corresponding to a given partition. > > Second line is very short; reflow as one paragraph. > > * In case of range partitioning, it stores one entry per distinct range > * datum, which is the index of the partition for which a given datum > * is an upper bound. > + * In the case of hash partitioning, the number of the entries in the indexes > + * array is same as the greatest modulus amongst all partitions. For a given > + * partition key datum-tuple, the index of the partition which would > accept that > + * datum-tuple would be given by the entry pointed by remainder produced when > + * hash value of the datum-tuple is divided by the greatest modulus. > > Insert line break before the new text as a paragraph break. The prologue is arranged as one paragraph (with a new line) per member. Within each paragraph explanation for each partitioning strategy starts on its own line. One paragraph per member is more readable than separate paragraphs for each member and strategy. > > I don't really see why hash partitioning needs to touch > partition_bounds_equal() at all. Why can't the existing logic work > for hash partitioning without change? Right now, it compares partnatts datums values for list and range. For hash it requires to compare 2 datums remainder and modulus. So, the difference? Further, I suggested that we use the fact that equality of indexes array implies equality of bounds for hash partitioning. -- Best Wishes, Ashutosh Bapat EnterpriseDB Corporation The Postgres Database Company
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-22T08:53:31Z
On Fri, May 19, 2017 at 10:35 PM, Robert Haas <robertmhaas@gmail.com> wrote: > On Fri, May 19, 2017 at 5:32 AM, amul sul <sulamul@gmail.com> wrote: >> Updated patch attached. 0001-patch rebased against latest head. >> 0002-patch also incorporates code comments and error message changes >> as per Robert's & your suggestions. Thanks ! > > - if (spec->strategy != PARTITION_STRATEGY_LIST) > - elog(ERROR, "invalid strategy in partition bound spec"); > + Assert(spec->strategy == PARTITION_STRATEGY_LIST); > > Let's just drop these hunks. I realize this is a response to a review > comment I made, but I take it back. If the existing code is already > doing it this way, there's no real need to revise it. The patch > doesn't even make it consistent anyway, since elsewhere you elog() for > a similar case. Perhaps elog() is best anyway. > Done. > - partitioning methods include range and list, where each partition is > - assigned a range of keys and a list of keys, respectively. > + partitioning methods include hash, range and list, where each partition is > + assigned a modulus and remainder of keys, a range of keys and a list of > + keys, respectively. > > I think this sentence has become too long and unwieldy, and is more > unclear than helpful. I'd just write "The currently supported > partitioning methods are list, range, and hash." The use of the word > include is actually wrong here, because it implies that there are more > not mentioned here, which is false. > Done. > - expression. If no btree operator class is specified when creating a > - partitioned table, the default btree operator class for the datatype will > - be used. If there is none, an error will be reported. > + expression. List and range partitioning uses only btree operator class. > + Hash partitioning uses only hash operator class. If no operator class is > + specified when creating a partitioned table, the default operator class > + for the datatype will be used. If there is none, an error will be > + reported. > + </para> > > I suggest: If no operator class is specified when creating a > partitioned table, the default operator class of the appropriate type > (btree for list and range partitioning, hash for hash partitioning) > will be used. If there is none, an error will be reported. > Done. > + <para> > + Since hash partitiong operator class, provide only equality, > not ordering, > + collation is not relevant in hash partition key column. An error will be > + reported if collation is specified. > > partitiong -> partitioning. Also, remove the comma after "operator > class" and change "not relevant in hash partition key column" to "not > relevant for hash partitioning". Also change "if collation is > specified" to "if a collation is specified". > Done. > + Create a hash partitioned table: > +<programlisting> > +CREATE TABLE orders ( > + order_id bigint not null, > + cust_id bigint not null, > + status text > +) PARTITION BY HASH (order_id); > +</programlisting></para> > > Move this down so it's just above the example of creating partitions. > Done. > + * For range and list partitioned tables, datums is an array of datum-tuples > + * with key->partnatts datums each. > + * For hash partitioned tables, it is an array of datum-tuples with 2 datums, > + * modulus and remainder, corresponding to a given partition. > > Second line is very short; reflow as one paragraph. > Done > * In case of range partitioning, it stores one entry per distinct range > * datum, which is the index of the partition for which a given datum > * is an upper bound. > + * In the case of hash partitioning, the number of the entries in the indexes > + * array is same as the greatest modulus amongst all partitions. For a given > + * partition key datum-tuple, the index of the partition which would > accept that > + * datum-tuple would be given by the entry pointed by remainder produced when > + * hash value of the datum-tuple is divided by the greatest modulus. > > Insert line break before the new text as a paragraph break. Will wait for more inputs on Ashutosh's explanation upthread. > > + char strategy; /* hash, list or range bounds? */ > > Might be clearer to just write /* hash, list, or range? */ or /* > bounds for hash, list, or range? */ > Done, used "hash, list, or range?" > > +static uint32 compute_hash_value(PartitionKey key, Datum *values, > bool *isnull); > + > > I think there should be a blank line after this but not before it. > Done. > I don't really see why hash partitioning needs to touch > partition_bounds_equal() at all. Why can't the existing logic work > for hash partitioning without change? > Unlike list and range partition, ndatums does not represents size of the indexes array, also dimension of datums array in not the same as a key->partnatts. > + valid_bound = true; > > valid_modulus, maybe? > Sure, added. > - errmsg("data type %s has no default btree operator class", > - format_type_be(atttype)), > - errhint("You must specify a btree operator > class or define a default btree operator class for the data type."))); > + errmsg("data type %s has no default %s operator class", > + format_type_be(atttype), am_method), > + errhint("You must specify a %s operator > class or define a default %s operator class for the data type.", > + am_method, am_method))); > > Let's use this existing wording from typecmds.c: > > errmsg("data type %s has no default operator > class for access method \"%s\"", > > and for the hint, maybe: You must specify an operator class or define > a default operator class for the data type. Leave out the %s, in > other words. > Done. > + /* > + * Hash operator classes provide only equality, not ordering. > + * Collation, which is relevant for ordering and not for equality, is > + * irrelevant for hash partitioning. > + */ > + if (*strategy == PARTITION_STRATEGY_HASH && pelem->collation != NIL) > + ereport(ERROR, > + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > + errmsg("cannot use collation for hash partitioning"), > + parser_errposition(pstate, pelem->location))); > > This error message is not very informative, and it requires > propagating information about the partitioning type into parts of the > code that otherwise don't require it. I was waffling before on > whether to ERROR here; I think now I'm in favor of ignoring the > problem. The collation won't do any harm; it just won't affect the > behavior. > Removed. > + * Identify opclass to use. For list and range partitioning we use > + * only btree operator class, which seems enough for those. For hash > + * partitioning, we use hash operator class. > > Strange wording. Suggest: Identify the appropriate operator class. > For list and range partitioning, we use a btree operator class; hash > partitioning uses a hash operator class. > Done > + FOR VALUES WITH '(' hash_partbound ')' /*TODO: syntax is > not finalised*/ > > Remove the comment. > Done. > + foreach (lc, $5) > + { > + DefElem *opt = (DefElem *) lfirst(lc); > + > + if (strcmp(opt->defname, "modulus") == 0) > + n->modulus = defGetInt32(opt); > + else if (strcmp(opt->defname, "remainder") == 0) > + n->remainder = defGetInt32(opt); > + else > + ereport(ERROR, > + (errcode(ERRCODE_SYNTAX_ERROR), > + errmsg("unrecognized hash > partition bound specification \"%s\"", > + opt->defname), > + parser_errposition(opt->location))); > + } > > This logic doesn't complain if the same option is specified more than > once. I suggest adding a check for that, and also pushing this logic > out into a helper function that gets called here instead of including > it inline. > Added duplicate error. About separate helper function, can't we have as it is, because, imo, we might not going to use that elsewhere? > + errmsg("hash partition modulus must be a positive > integer"))); > > modulus for hash partition > > + errmsg("hash partition remainder must be a > non-negative integer"))); > > remainder for hash partition > > + errmsg("hash partition modulus must be greater than remainder"))); > > modulus for hash partition must be greater than remainder > Done. Similar changes in gram.y as well. > +-- values are hashed, row may map to different partitions, which result in > > the row > > +-- regression failure. To avoid this, let's create non-default hash function > > create a non-default Done. Updated patch attached. Thanks a lot for review. Regards, Amul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-05-25T04:29:00Z
On Mon, May 22, 2017 at 2:23 PM, amul sul <sulamul@gmail.com> wrote: > > Updated patch attached. Thanks a lot for review. > Minor fix in the document, PFA. Regards, Amul
-
Re: [POC] hash partitioning
Robert Haas <robertmhaas@gmail.com> — 2017-05-25T15:13:25Z
On Mon, May 22, 2017 at 1:49 AM, Ashutosh Bapat <ashutosh.bapat@enterprisedb.com> wrote: > The prologue is arranged as one paragraph (with a new line) per > member. Within each paragraph explanation for each partitioning > strategy starts on its own line. One paragraph per member is more > readable than separate paragraphs for each member and strategy. The point is that you can either make it a separate paragraph or you can make it a single paragraph, but you can't leave it halfway in between. If it's one paragraph, every line should end at around the 80 character mark, without any short lines. If it's multiple paragraphs, they should be separated by blank lines. The only line of a paragraph that can be short is the last one. -- Robert Haas EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
-
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-06-03T13:24:46Z
On Thu, May 25, 2017 at 9:59 AM, amul sul <sulamul@gmail.com> wrote: > On Mon, May 22, 2017 at 2:23 PM, amul sul <sulamul@gmail.com> wrote: >> >> Updated patch attached. Thanks a lot for review. >> > Minor fix in the document, PFA. Patch need rebase ------- Function header is not consistent with other neighbouring functions (some function contains function name in the header but others don't) +/* + * Compute the hash value for given not null partition key values. + */ ------ postgres=# create table t1 partition of t for values with (modulus 2, remainder 1) partition by range(a); CREATE TABLE postgres=# create table t1_1 partition of t1 for values from (8) to (10); CREATE TABLE postgres=# insert into t1 values(8); 2017-06-03 18:41:46.067 IST [5433] ERROR: new row for relation "t1_1" violates partition constraint 2017-06-03 18:41:46.067 IST [5433] DETAIL: Failing row contains (8). 2017-06-03 18:41:46.067 IST [5433] STATEMENT: insert into t1 values(8); ERROR: new row for relation "t1_1" violates partition constraint DETAIL: Failing row contains (8). The value 8 is violating the partition constraint of the t1 and we are trying to insert to value in t1, still, the error is coming from the leaf level table t1_1, that may be fine but from error, it appears that it's violating the constraint of t1_1 whereas it's actually violating the constraint of t1. From Implementation, it appears that based on the key are identifying the leaf partition and it's only failing during ExecInsert while checking the partition constraint. Other than that, patch looks fine to me. -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com
-
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-06-06T07:33:58Z
Hi Dilip, Thanks for review. On Sat, Jun 3, 2017 at 6:54 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > On Thu, May 25, 2017 at 9:59 AM, amul sul <sulamul@gmail.com> wrote: >> On Mon, May 22, 2017 at 2:23 PM, amul sul <sulamul@gmail.com> wrote: >>> >>> Updated patch attached. Thanks a lot for review. >>> >> Minor fix in the document, PFA. > > Patch need rebase > Done. > ------- > Function header is not consistent with other neighbouring functions > (some function contains function name in the header but others don't) > +/* > + * Compute the hash value for given not null partition key values. > + */ > Done. > ------ > postgres=# create table t1 partition of t for values with (modulus 2, > remainder 1) partition by range(a); > CREATE TABLE > postgres=# create table t1_1 partition of t1 for values from (8) to (10); > CREATE TABLE > postgres=# insert into t1 values(8); > 2017-06-03 18:41:46.067 IST [5433] ERROR: new row for relation "t1_1" > violates partition constraint > 2017-06-03 18:41:46.067 IST [5433] DETAIL: Failing row contains (8). > 2017-06-03 18:41:46.067 IST [5433] STATEMENT: insert into t1 values(8); > ERROR: new row for relation "t1_1" violates partition constraint > DETAIL: Failing row contains (8). > > The value 8 is violating the partition constraint of the t1 and we are > trying to insert to value in t1, > still, the error is coming from the leaf level table t1_1, that may be > fine but from error, it appears that > it's violating the constraint of t1_1 whereas it's actually violating > the constraint of t1. > > From Implementation, it appears that based on the key are identifying > the leaf partition and it's only failing during ExecInsert while > checking the partition constraint. > May I ask you, how you sure about 8 is an unfit value for t1 relation? And what if the value other than 8, for e.g. 7? Updated patch attached. Regards, Amul Sul
-
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-06-06T08:50:53Z
On Tue, Jun 6, 2017 at 1:03 PM, amul sul <sulamul@gmail.com> wrote: > May I ask you, how you sure about 8 is an unfit value for t1 relation? > And what if the value other than 8, for e.g. 7? Well, First I created t1 as a leaf relation like below, and I tested insert into t1 with value 8 and it was violating the partition constraint of t1, however, 7 was fine. create table t (a int) partition by hash(a); create table t1 partition of t for values with (modulus 2, remainder 1); Later I dropped this t1 and created 2 level partition with the leaf as a range. drop table t1; create table t1 partition of t for values with (modulus 2, remainder 1) partition by range(a); create table t1_1 partition of t1 for values from (8) to (10); So now, I am sure that t1_1 can accept the value 8 and its parent t1 can't. So I think this can only happen in the case of partitioned by hash that a value is legal for the child but illegal for the parent? Isn't it a good idea that if a user is inserting in the top level relation he should know for which partition exactly the constraint got violated? > > Updated patch attached. Thanks. -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com
-
Re: [POC] hash partitioning
Amit Langote <langote_amit_f8@lab.ntt.co.jp> — 2017-06-06T09:11:18Z
On 2017/06/06 17:50, Dilip Kumar wrote: > On Tue, Jun 6, 2017 at 1:03 PM, amul sul <sulamul@gmail.com> wrote: >> May I ask you, how you sure about 8 is an unfit value for t1 relation? >> And what if the value other than 8, for e.g. 7? > > Well, First I created t1 as a leaf relation like below, and I tested > insert into t1 with value 8 and it was violating the partition > constraint of t1, however, 7 was fine. > > create table t (a int) partition by hash(a); > create table t1 partition of t for values with (modulus 2, remainder 1); > > Later I dropped this t1 and created 2 level partition with the leaf as a range. > > drop table t1; > create table t1 partition of t for values with (modulus 2, remainder > 1) partition by range(a); > create table t1_1 partition of t1 for values from (8) to (10); > > So now, I am sure that t1_1 can accept the value 8 and its parent t1 can't. > > So I think this can only happen in the case of partitioned by hash > that a value is legal for the child but illegal for the parent? Isn't > it a good idea that if a user is inserting in the top level relation > he should know for which partition exactly the constraint got > violated? It's how the original partitioning code around ExecInsert/CopyFrom works, not something that only affects hash partitioning. So, I think that Amul's patch is fine and if we want to change something here, it should be done by an independent patch. See the explanation below: If we insert into a partition directly, we must check its partition constraint. If the partition happens to be itself a partitioned table, the constraint will be checked *after* tuple-routing and ExecConstraints() is passed the leaf partition's ResultRelInfo, so if an error occurs there we will use the leaf partition's name in the message. Since we combine the leaf partition's own constraint with all of the ancestors' into a single expression that is passed to ExecCheck(), it is hard to say exactly which ancestor's constraint is violated. However, if the partition constraint of some intervening ancestor had been violated, we wouldn't be in ExecConstraints() at all; tuple-routing itself would have failed. So it seems that we need worry (if at all) only about partition constraints of the table mentioned in the insert statement. Consider an example using the partition hierarchy: root (a int, b char, c int) partition by range (a) -> level1 from (1) to (10) partition by list (b) -> level2 in ('a') parition by range (c) -> leaf from (1) to (10) Inserting (1, 'b', 1) into level1 will fail, because tuple can't be routed at level1 (no partition defined for b = 'b'). Inserting (1, 'a', 10) into level1 will fail, because tuple can't be routed at level2 (no partition defined for c >= 10). Inserting (10, 'a', 1) into level1 will fail, because, although it was able to get through level1 and level2 into leaf, a = 10 falls out of level1's defined range. We don't check that 1 <= a < 10 before starting the tuple-routing. I wonder if we should... Since we don't allow BR triggers on partitioned tables, there should not be any harm in doing it just before calling ExecFindPartition(). Perhaps, topic for a new thread. Thanks, Amit -
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-06-06T09:46:04Z
On Tue, Jun 6, 2017 at 2:41 PM, Amit Langote <Langote_Amit_f8@lab.ntt.co.jp> wrote: > Consider an example using the partition hierarchy: > > root (a int, b char, c int) partition by range (a) > > -> level1 from (1) to (10) partition by list (b) > > -> level2 in ('a') parition by range (c) > > -> leaf from (1) to (10) > > Inserting (1, 'b', 1) into level1 will fail, because tuple can't be routed > at level1 (no partition defined for b = 'b'). > > Inserting (1, 'a', 10) into level1 will fail, because tuple can't be > routed at level2 (no partition defined for c >= 10). > > Inserting (10, 'a', 1) into level1 will fail, because, although it was > able to get through level1 and level2 into leaf, a = 10 falls out of > level1's defined range. We don't check that 1 <= a < 10 before starting > the tuple-routing. > > I wonder if we should... Since we don't allow BR triggers on partitioned > tables, there should not be any harm in doing it just before calling > ExecFindPartition(). Perhaps, topic for a new thread. Yeah, correct. -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com -
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-06-23T04:41:15Z
On Tue, 6 Jun 2017 13:03:58 +0530 amul sul <sulamul@gmail.com> wrote: > Updated patch attached. I looked into the latest patch (v13) and have some comments althogh they might be trivial. First, I couldn't apply this patch to the latest HEAD due to a documentation fix and pgintend updates. It needes rebase. $ git apply /tmp/0002-hash-partitioning_another_design-v13.patch error: patch failed: doc/src/sgml/ref/create_table.sgml:87 error: doc/src/sgml/ref/create_table.sgml: patch does not apply error: patch failed: src/backend/catalog/partition.c:76 error: src/backend/catalog/partition.c: patch does not apply error: patch failed: src/backend/commands/tablecmds.c:13371 error: src/backend/commands/tablecmds.c: patch does not apply <varlistentry> + <term>Hash Partitioning</term> + + <listitem> + <para> + The table is partitioned by specifying modulus and remainder for each + partition. Each partition holds rows for which the hash value of + partition keys when divided by specified modulus produces specified + remainder. For more clarification on modulus and remainder please refer + <xref linkend="sql-createtable-partition">. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term>Range Partitioning</term> I think this section should be inserted after List Partitioning section because the order of the descriptions is Range, List, then Hash in other places of the documentation. At least, - <firstterm>partition bounds</firstterm>. Currently supported - partitioning methods include range and list, where each partition is - assigned a range of keys and a list of keys, respectively. + <firstterm>partition bounds</firstterm>. The currently supported + partitioning methods are list, range, and hash. </para> Also in this hunk. I think "The currently supported partitioning methods are range, list, and hash." is better. We don't need to change the order of the original description. <listitem> <para> - Declarative partitioning only supports list and range partitioning, - whereas table inheritance allows data to be divided in a manner of - the user's choosing. (Note, however, that if constraint exclusion is - unable to prune partitions effectively, query performance will be very - poor.) + Declarative partitioning only supports hash, list and range + partitioning, whereas table inheritance allows data to be divided in a + manner of the user's choosing. (Note, however, that if constraint + exclusion is unable to prune partitions effectively, query performance + will be very poor.) Similarly, I think "Declarative partitioning only supports range, list and hash partitioning," is better. + + <para> + Create a hash partitioned table: +<programlisting> +CREATE TABLE orders ( + order_id bigint not null, + cust_id bigint not null, + status text +) PARTITION BY HASH (order_id); +</programlisting></para> + This paragraph should be inserted between "Create a list partitioned table:" paragraph and "Ceate partition of a range partitioned table:" paragraph as well as range and list. *strategy = PARTITION_STRATEGY_LIST; else if (pg_strcasecmp(partspec->strategy, "range") == 0) *strategy = PARTITION_STRATEGY_RANGE; + else if (pg_strcasecmp(partspec->strategy, "hash") == 0) + *strategy = PARTITION_STRATEGY_HASH; else ereport(ERROR, In the most of codes, the order is hash, range, then list, but only in transformPartitionSpec(), the order is list, range, then hash, as above. Maybe it is better to be uniform. + { + if (strategy == PARTITION_STRATEGY_HASH) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("data type %s has no default hash operator class", + format_type_be(atttype)), + errhint("You must specify a hash operator class or define a default hash operator class for the data type."))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("data type %s has no default btree operator class", + format_type_be(atttype)), + errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); + + atttype, - "btree", - BTREE_AM_OID); + am_oid == HASH_AM_OID ? "hash" : "btree", + am_oid); How about writing this part as following to reduce code redundancy? + Oid am_oid; + char *am_name; <snip> + if (strategy == PARTITION_STRATEGY_HASH) + { + am_oid = HASH_AM_OID; + am_name = pstrdup("hash"); + } + else + { + am_oid = BTREE_AM_OID; + am_name = pstrdup("btree"); + } + if (!pelem->opclass) { - partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID); + partopclass[attn] = GetDefaultOpClass(atttype, am_oid); if (!OidIsValid(partopclass[attn])) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("data type %s has no default btree operator class", - format_type_be(atttype)), - errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); + errmsg("data type %s has no default %s operator class", + format_type_be(atttype), am_name), + errhint("You must specify a %s operator class or define a default %s operator class for the data type.", + am_name, am_name))); + } else partopclass[attn] = ResolveOpClass(pelem->opclass, atttype, - "btree", - BTREE_AM_OID); + am_name, + am_oid); There is meaningless indentation change. @@ -2021,7 +2370,8 @@ get_partition_for_tuple(PartitionDispatch *pd, /* bsearch in partdesc->boundinfo */ cur_offset = partition_bound_bsearch(key, partdesc->boundinfo, - values, false, &equal); + values, false, &equal); + /* * Offset returned is such that the bound at offset is Fixing the comment of pg_get_partkeydef() is missing. * pg_get_partkeydef * * Returns the partition key specification, ie, the following: * * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) */ Datum pg_get_partkeydef(PG_FUNCTION_ARGS) { Regards, > > Regards, > Amul Sul -- Yugo Nagata <nagata@sraoss.co.jp> -
Re: [POC] hash partitioning
Yugo Nagata <nagata@sraoss.co.jp> — 2017-06-23T05:49:34Z
On Fri, 23 Jun 2017 13:41:15 +0900 Yugo Nagata <nagata@sraoss.co.jp> wrote: > On Tue, 6 Jun 2017 13:03:58 +0530 > amul sul <sulamul@gmail.com> wrote: > > > > Updated patch attached. > > I looked into the latest patch (v13) and have some comments > althogh they might be trivial. One more comment: + if (spec->remainder < 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("remainder for hash partition must be a non-negative integer"))); The value of remainder is defined as Iconst in gram.y, so it never be negative. Hence, I think this check is not necessary or Assert is enough. > > First, I couldn't apply this patch to the latest HEAD due to > a documentation fix and pgintend updates. It needes rebase. > > $ git apply /tmp/0002-hash-partitioning_another_design-v13.patch > error: patch failed: doc/src/sgml/ref/create_table.sgml:87 > error: doc/src/sgml/ref/create_table.sgml: patch does not apply > error: patch failed: src/backend/catalog/partition.c:76 > error: src/backend/catalog/partition.c: patch does not apply > error: patch failed: src/backend/commands/tablecmds.c:13371 > error: src/backend/commands/tablecmds.c: patch does not apply > > > <varlistentry> > + <term>Hash Partitioning</term> > + > + <listitem> > + <para> > + The table is partitioned by specifying modulus and remainder for each > + partition. Each partition holds rows for which the hash value of > + partition keys when divided by specified modulus produces specified > + remainder. For more clarification on modulus and remainder please refer > + <xref linkend="sql-createtable-partition">. > + </para> > + </listitem> > + </varlistentry> > + > + <varlistentry> > <term>Range Partitioning</term> > > I think this section should be inserted after List Partitioning section because > the order of the descriptions is Range, List, then Hash in other places of > the documentation. At least, > > > - <firstterm>partition bounds</firstterm>. Currently supported > - partitioning methods include range and list, where each partition is > - assigned a range of keys and a list of keys, respectively. > + <firstterm>partition bounds</firstterm>. The currently supported > + partitioning methods are list, range, and hash. > </para> > > Also in this hunk. I think "The currently supported partitioning methods are > range, list, and hash." is better. We don't need to change the order of > the original description. > > > <listitem> > <para> > - Declarative partitioning only supports list and range partitioning, > - whereas table inheritance allows data to be divided in a manner of > - the user's choosing. (Note, however, that if constraint exclusion is > - unable to prune partitions effectively, query performance will be very > - poor.) > + Declarative partitioning only supports hash, list and range > + partitioning, whereas table inheritance allows data to be divided in a > + manner of the user's choosing. (Note, however, that if constraint > + exclusion is unable to prune partitions effectively, query performance > + will be very poor.) > > Similarly, I think "Declarative partitioning only supports range, list and hash > partitioning," is better. > > > + > + <para> > + Create a hash partitioned table: > +<programlisting> > +CREATE TABLE orders ( > + order_id bigint not null, > + cust_id bigint not null, > + status text > +) PARTITION BY HASH (order_id); > +</programlisting></para> > + > > This paragraph should be inserted between "Create a list partitioned table:" > paragraph and "Ceate partition of a range partitioned table:" paragraph > as well as range and list. > > > *strategy = PARTITION_STRATEGY_LIST; > else if (pg_strcasecmp(partspec->strategy, "range") == 0) > *strategy = PARTITION_STRATEGY_RANGE; > + else if (pg_strcasecmp(partspec->strategy, "hash") == 0) > + *strategy = PARTITION_STRATEGY_HASH; > else > ereport(ERROR, > > In the most of codes, the order is hash, range, then list, but only > in transformPartitionSpec(), the order is list, range, then hash, > as above. Maybe it is better to be uniform. > > > + { > + if (strategy == PARTITION_STRATEGY_HASH) > + ereport(ERROR, > + (errcode(ERRCODE_UNDEFINED_OBJECT), > + errmsg("data type %s has no default hash operator class", > + format_type_be(atttype)), > + errhint("You must specify a hash operator class or define a default hash operator class for the data type."))); > + else > + ereport(ERROR, > + (errcode(ERRCODE_UNDEFINED_OBJECT), > + errmsg("data type %s has no default btree operator class", > + format_type_be(atttype)), > + errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); > + > + > > atttype, > - "btree", > - BTREE_AM_OID); > + am_oid == HASH_AM_OID ? "hash" : "btree", > + am_oid); > > How about writing this part as following to reduce code redundancy? > > + Oid am_oid; > + char *am_name; > > <snip> > > + if (strategy == PARTITION_STRATEGY_HASH) > + { > + am_oid = HASH_AM_OID; > + am_name = pstrdup("hash"); > + } > + else > + { > + am_oid = BTREE_AM_OID; > + am_name = pstrdup("btree"); > + } > + > if (!pelem->opclass) > { > - partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID); > + partopclass[attn] = GetDefaultOpClass(atttype, am_oid); > > if (!OidIsValid(partopclass[attn])) > ereport(ERROR, > (errcode(ERRCODE_UNDEFINED_OBJECT), > - errmsg("data type %s has no default btree operator class", > - format_type_be(atttype)), > - errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); > + errmsg("data type %s has no default %s operator class", > + format_type_be(atttype), am_name), > + errhint("You must specify a %s operator class or define a default %s operator class for the data type.", > + am_name, am_name))); > + > } > else > partopclass[attn] = ResolveOpClass(pelem->opclass, > atttype, > - "btree", > - BTREE_AM_OID); > + am_name, > + am_oid); > > > There is meaningless indentation change. > > @@ -2021,7 +2370,8 @@ get_partition_for_tuple(PartitionDispatch *pd, > /* bsearch in partdesc->boundinfo */ > cur_offset = partition_bound_bsearch(key, > partdesc->boundinfo, > - values, false, &equal); > + values, false, &equal); > + > /* > * Offset returned is such that the bound at offset is > > > Fixing the comment of pg_get_partkeydef() is missing. > > * pg_get_partkeydef > * > * Returns the partition key specification, ie, the following: > * > * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) > */ > Datum > pg_get_partkeydef(PG_FUNCTION_ARGS) > { > > Regards, > > > > > Regards, > > Amul Sul > > > -- > Yugo Nagata <nagata@sraoss.co.jp> > > > -- > Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) > To make changes to your subscription: > http://www.postgresql.org/mailpref/pgsql-hackers -- Yugo Nagata <nagata@sraoss.co.jp> -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-07-03T11:09:51Z
On Fri, Jun 23, 2017 at 10:11 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > On Tue, 6 Jun 2017 13:03:58 +0530 > amul sul <sulamul@gmail.com> wrote: > > >> Updated patch attached. > > I looked into the latest patch (v13) and have some comments > althogh they might be trivial. > Thanks for your review. > First, I couldn't apply this patch to the latest HEAD due to > a documentation fix and pgintend updates. It needes rebase. > > $ git apply /tmp/0002-hash-partitioning_another_design-v13.patch > error: patch failed: doc/src/sgml/ref/create_table.sgml:87 > error: doc/src/sgml/ref/create_table.sgml: patch does not apply > error: patch failed: src/backend/catalog/partition.c:76 > error: src/backend/catalog/partition.c: patch does not apply > error: patch failed: src/backend/commands/tablecmds.c:13371 > error: src/backend/commands/tablecmds.c: patch does not apply > Fixed. > > <varlistentry> > + <term>Hash Partitioning</term> > + > + <listitem> > + <para> > + The table is partitioned by specifying modulus and remainder for each > + partition. Each partition holds rows for which the hash value of > + partition keys when divided by specified modulus produces specified > + remainder. For more clarification on modulus and remainder please refer > + <xref linkend="sql-createtable-partition">. > + </para> > + </listitem> > + </varlistentry> > + > + <varlistentry> > <term>Range Partitioning</term> > > I think this section should be inserted after List Partitioning section because > the order of the descriptions is Range, List, then Hash in other places of > the documentation. At least, > Fixed in the attached version. > > - <firstterm>partition bounds</firstterm>. Currently supported > - partitioning methods include range and list, where each partition is > - assigned a range of keys and a list of keys, respectively. > + <firstterm>partition bounds</firstterm>. The currently supported > + partitioning methods are list, range, and hash. > </para> > > Also in this hunk. I think "The currently supported partitioning methods are > range, list, and hash." is better. We don't need to change the order of > the original description. > Fixed in the attached version. > > <listitem> > <para> > - Declarative partitioning only supports list and range partitioning, > - whereas table inheritance allows data to be divided in a manner of > - the user's choosing. (Note, however, that if constraint exclusion is > - unable to prune partitions effectively, query performance will be very > - poor.) > + Declarative partitioning only supports hash, list and range > + partitioning, whereas table inheritance allows data to be divided in a > + manner of the user's choosing. (Note, however, that if constraint > + exclusion is unable to prune partitions effectively, query performance > + will be very poor.) > > Similarly, I think "Declarative partitioning only supports range, list and hash > partitioning," is better. > Fixed in the attached version. > > + > + <para> > + Create a hash partitioned table: > +<programlisting> > +CREATE TABLE orders ( > + order_id bigint not null, > + cust_id bigint not null, > + status text > +) PARTITION BY HASH (order_id); > +</programlisting></para> > + > > This paragraph should be inserted between "Create a list partitioned table:" > paragraph and "Ceate partition of a range partitioned table:" paragraph > as well as range and list. > Fixed in the attached version. > > *strategy = PARTITION_STRATEGY_LIST; > else if (pg_strcasecmp(partspec->strategy, "range") == 0) > *strategy = PARTITION_STRATEGY_RANGE; > + else if (pg_strcasecmp(partspec->strategy, "hash") == 0) > + *strategy = PARTITION_STRATEGY_HASH; > else > ereport(ERROR, > > In the most of codes, the order is hash, range, then list, but only > in transformPartitionSpec(), the order is list, range, then hash, > as above. Maybe it is better to be uniform. > Make sense, fixed in the attached version. > > + { > + if (strategy == PARTITION_STRATEGY_HASH) > + ereport(ERROR, > + (errcode(ERRCODE_UNDEFINED_OBJECT), > + errmsg("data type %s has no default hash operator class", > + format_type_be(atttype)), > + errhint("You must specify a hash operator class or define a default hash operator class for the data type."))); > + else > + ereport(ERROR, > + (errcode(ERRCODE_UNDEFINED_OBJECT), > + errmsg("data type %s has no default btree operator class", > + format_type_be(atttype)), > + errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); > + > + > > atttype, > - "btree", > - BTREE_AM_OID); > + am_oid == HASH_AM_OID ? "hash" : "btree", > + am_oid); > > How about writing this part as following to reduce code redundancy? > > + Oid am_oid; > + char *am_name; > > <snip> > > + if (strategy == PARTITION_STRATEGY_HASH) > + { > + am_oid = HASH_AM_OID; > + am_name = pstrdup("hash"); > + } > + else > + { > + am_oid = BTREE_AM_OID; > + am_name = pstrdup("btree"); > + } > + > if (!pelem->opclass) > { > - partopclass[attn] = GetDefaultOpClass(atttype, BTREE_AM_OID); > + partopclass[attn] = GetDefaultOpClass(atttype, am_oid); > > if (!OidIsValid(partopclass[attn])) > ereport(ERROR, > (errcode(ERRCODE_UNDEFINED_OBJECT), > - errmsg("data type %s has no default btree operator class", > - format_type_be(atttype)), > - errhint("You must specify a btree operator class or define a default btree operator class for the data type."))); > + errmsg("data type %s has no default %s operator class", > + format_type_be(atttype), am_name), > + errhint("You must specify a %s operator class or define a default %s operator class for the data type.", > + am_name, am_name))); > + > } > else > partopclass[attn] = ResolveOpClass(pelem->opclass, > atttype, > - "btree", > - BTREE_AM_OID); > + am_name, > + am_oid); > I had to have same thoughts before (see v12 patch & before), but change due to review comments upthread. > > There is meaningless indentation change. > > @@ -2021,7 +2370,8 @@ get_partition_for_tuple(PartitionDispatch *pd, > /* bsearch in partdesc->boundinfo */ > cur_offset = partition_bound_bsearch(key, > partdesc->boundinfo, > - values, false, &equal); > + values, false, &equal); > + > /* > * Offset returned is such that the bound at offset is > Fixed in the attached version. > > Fixing the comment of pg_get_partkeydef() is missing. > > * pg_get_partkeydef > * > * Returns the partition key specification, ie, the following: > * > * PARTITION BY { RANGE | LIST } (column opt_collation opt_opclass [, ...]) > */ > Datum > pg_get_partkeydef(PG_FUNCTION_ARGS) > { > Thanks to catching this, fixed in the attached version. Regards, Amul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-07-03T11:12:11Z
On Fri, Jun 23, 2017 at 11:19 AM, Yugo Nagata <nagata@sraoss.co.jp> wrote: > On Fri, 23 Jun 2017 13:41:15 +0900 > Yugo Nagata <nagata@sraoss.co.jp> wrote: > >> On Tue, 6 Jun 2017 13:03:58 +0530 >> amul sul <sulamul@gmail.com> wrote: >> >> >> > Updated patch attached. >> >> I looked into the latest patch (v13) and have some comments >> althogh they might be trivial. > > One more comment: > > + if (spec->remainder < 0) > + ereport(ERROR, > + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), > + errmsg("remainder for hash partition must be a non-negative integer"))); > > The value of remainder is defined as Iconst in gram.y, so it never be negative. > Hence, I think this check is not necessary or Assert is enough. > Make sense, fixed this as well in the v14 patch. Thanks again. Regards, Amul -
Re: [POC] hash partitioning
Dilip Kumar <dilipbalaut@gmail.com> — 2017-07-05T11:20:43Z
On Mon, Jul 3, 2017 at 4:39 PM, amul sul <sulamul@gmail.com> wrote: > Thanks to catching this, fixed in the attached version. Few comments on the latest version. 0001 looks fine, for 0002 I have some comments. 1. + hbounds = (PartitionHashBound * *) palloc(nparts * + sizeof(PartitionHashBound *)); /s/(PartitionHashBound * *)/(PartitionHashBound **)/g 2. RelationBuildPartitionDesc { .... * catalog scan that retrieved them, whereas that in the latter is * defined by canonicalized representation of the list values or the * range bounds. */ for (i = 0; i < nparts; i++) result->oids[mapping[i]] = oids[i]; Should this comments mention about hash as well? 3. if (b1->datums[b1->ndatums - 1][0] != b2->datums[b2->ndatums - 1][0]) return false; if (b1->ndatums != b2->ndatums) return false; If ndatums itself is different then no need to access datum memory, so better to check ndatum first. 4. + * next larger modulus. For example, if you have a bunch + * of partitions that all have modulus 5, you can add a + * new new partition with modulus 10 or a new partition Typo, "new new partition" -> "new partition" -- Regards, Dilip Kumar EnterpriseDB: http://www.enterprisedb.com -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-07-05T12:38:00Z
On Wed, Jul 5, 2017 at 4:50 PM, Dilip Kumar <dilipbalaut@gmail.com> wrote: > On Mon, Jul 3, 2017 at 4:39 PM, amul sul <sulamul@gmail.com> wrote: >> Thanks to catching this, fixed in the attached version. > > Few comments on the latest version. > Thanks for your review, please find my comment inline: > 0001 looks fine, for 0002 I have some comments. > > 1. > + hbounds = (PartitionHashBound * *) palloc(nparts * > + sizeof(PartitionHashBound *)); > > /s/(PartitionHashBound * *)/(PartitionHashBound **)/g > Fixed in the attached version. > 2. > RelationBuildPartitionDesc > { > .... > > > * catalog scan that retrieved them, whereas that in the latter is > * defined by canonicalized representation of the list values or the > * range bounds. > */ > for (i = 0; i < nparts; i++) > result->oids[mapping[i]] = oids[i]; > > Should this comments mention about hash as well? > Instead, I have generalised this comment in the attached patch > 3. > > if (b1->datums[b1->ndatums - 1][0] != b2->datums[b2->ndatums - 1][0]) > return false; > > if (b1->ndatums != b2->ndatums) > return false; > > If ndatums itself is different then no need to access datum memory, so > better to check ndatum first. > You are correct, we already doing this in the partition_bounds_equal(). This is a redundant code, removed in the attached version. > 4. > + * next larger modulus. For example, if you have a bunch > + * of partitions that all have modulus 5, you can add a > + * new new partition with modulus 10 or a new partition > > Typo, "new new partition" -> "new partition" > Fixed in the attached version. Regards, Amul -
Re: [POC] hash partitioning
Amul Sul <sulamul@gmail.com> — 2017-07-27T11:41:01Z
Attaching newer patches rebased against the latest master head. Thanks ! Regards, Amul