diff -cprN head/doc/src/sgml/catalogs.sgml work/doc/src/sgml/catalogs.sgml *** head/doc/src/sgml/catalogs.sgml 2010-01-12 09:08:27.585112000 +0900 --- work/doc/src/sgml/catalogs.sgml 2010-01-14 15:12:13.197458520 +0900 *************** *** 194,199 **** --- 194,204 ---- + pg_partition + key definitions for partitioned tables + + + pg_pltemplate template data for procedural languages *************** *** 2989,2994 **** --- 2994,3010 ---- inherited columns are to be arranged. The count starts at 1 + + + inhvalues + anyarray + + + An array of partition values. + An empty array for overflow partitions. + NULL for non-partitioned tables. + + *************** *** 3698,3703 **** --- 3714,3780 ---- + + <structname>pg_partition</structname> + + + pg_partition + + + + The catalog pg_partition stores + key definitions for partitioned tables. + + + + <structname>pg_partition</> Columns + + + + + Name + Type + References + Description + + + + + + partrelid + oid + pg_class.oid + Partitioned table oid + + + + partopclass + oid + pg_opclass.oid + Operator class to compare keys + + + + partkind + char + + Kind of partition: RANGE or LIST + + + + partkey + text + + Partition key expression + + + + +
+ +
+ + <structname>pg_pltemplate</structname> diff -cprN head/doc/src/sgml/ref/allfiles.sgml work/doc/src/sgml/ref/allfiles.sgml *** head/doc/src/sgml/ref/allfiles.sgml 2009-12-11 12:39:49.829461000 +0900 --- work/doc/src/sgml/ref/allfiles.sgml 2010-01-14 15:16:42.613363000 +0900 *************** Complete list of usable sgml source file *** 58,63 **** --- 58,64 ---- + diff -cprN head/doc/src/sgml/ref/alter_table.sgml work/doc/src/sgml/ref/alter_table.sgml *** head/doc/src/sgml/ref/alter_table.sgml 2009-09-28 09:25:40.046261000 +0900 --- work/doc/src/sgml/ref/alter_table.sgml 2010-01-14 15:12:13.199410861 +0900 *************** ALTER TABLE parent_table OWNER TO new_owner SET TABLESPACE new_tablespace + PARTITION BY { RANGE | LIST } ( key ) [ opclass ] [ (...) ] + NO PARTITION + ATTACH PARTITION child_table VALUES ... + DETACH PARTITION child_table *************** ALTER TABLE for more information. + + + + + + NO PARTITION + + + This form removes the partition key from the table. If the table has + some partitions, partition values are also removed but inheritance and + check constraints are kept. + + + + + + ATTACH PARTITION + + + This form attaches an existing table as a partition with specified partition values. + Inheritance and check constraints are internally added to the partitions. + See for more information. + + + + + + DETACH PARTITION + + + This form removes the specified partition the list of partitions of + the target table. The removed partition can be accessed as a normal table. + + + + diff -cprN head/doc/src/sgml/ref/create_partition.sgml work/doc/src/sgml/ref/create_partition.sgml *** head/doc/src/sgml/ref/create_partition.sgml 1970-01-01 09:00:00.000000000 +0900 --- work/doc/src/sgml/ref/create_partition.sgml 2010-01-14 15:53:10.298023000 +0900 *************** *** 0 **** --- 1,164 ---- + + + CREATE PARTITION + 7 + SQL - Language Statements + + + + CREATE PARTITION + define a new table + + + + CREATE PARTITION + + + + + CREATE PARTITION partition_name + ON table_name + { + VALUES LESS THAN { value | MAXVALUE } + | VALUES [ IN ] [ ( ] { value [, ...] | DEFAULT } [ ) ] + } + [ WITH ( storage_parameter [= value] [, ... ] ) | WITH OIDS | WITHOUT OIDS ] + [ TABLESPACE tablespace ] + + + + + + Description + + + CREATE PARTITION will create a new, initially empty + partition which has the same definitions with the parent table, and add + it as a partition to the parent table. The parent table must be + partitioned before the command. The partition will be owned by the user + issuing the command. + + + + If a schema name is given (for example, CREATE PARTITION + myschema.mypartition ...) then the partition is created in the + specified schema. Otherwise it is created in the same schema with + the parent. The name of the partition must be distinct from the + name of any other partitions, table, sequence, index, or view + in the same schema. + + + + CREATE PARTITION is a syntactic sugar for + CREATE TABLE partition_name (LIKE parent_table) and + ALTER TABLE parent_table ATTACH PARTITION partition_name + See and + for more information. + + + + + Parameters + + + + + partition_name + + + The name (optionally schema-qualified) of the table to be created. + If schema is not specified, use the same schema with the parent table. + + + + + + table_name + + + The name (optionally schema-qualified) of the parent table. + + + + + + WITH ( storage_parameter [= value] [, ... ] ) + + + This clause specifies optional storage parameters for a table or index; + see for more + information. + + + + + + TABLESPACE tablespace + + + The tablespace is the name + of the tablespace in which the new table is to be created. + If not specified, + is consulted, or + if the table is temporary. + + + + + + + + + + Examples + + + Create partitions for each year on parent table sales: + + + CREATE TABLE sales ( + salesman_id integer PRIMARY KEY, + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ) + PARTITION BY RANGE (sales_date); + + CREATE PARTITION sales_2008 ON sales VALUES LESS THAN ('2009-01-01'); + CREATE PARTITION sales_2009 ON sales VALUES LESS THAN ('2010-01-01'); + CREATE PARTITION sales_2010 ON sales VALUES LESS THAN ('2011-01-01'); + + + + + + Create a table with a 2-dimensional array: + + + CREATE TABLE array_int ( + vector int[][] + ); + + + + + + + Compatibility + + + There is no CREATE PARTITION statement in the SQL standard. + + + + + See Also + + + + + + + + diff -cprN head/doc/src/sgml/ref/create_table.sgml work/doc/src/sgml/ref/create_table.sgml *** head/doc/src/sgml/ref/create_table.sgml 2009-12-09 13:45:22.745455000 +0900 --- work/doc/src/sgml/ref/create_table.sgml 2010-01-14 15:56:39.517686000 +0900 *************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY *** 31,36 **** --- 31,42 ---- [ WITH ( storage_parameter [= value] [, ... ] ) | WITH OIDS | WITHOUT OIDS ] [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ] [ TABLESPACE tablespace ] + [ PARTITION BY { RANGE | LIST } ( key ) [ opclass ] + [ ( { + PARTITION partition VALUES LESS THAN { value | MAXVALUE } + | PARTITION partition VALUES [ IN ] [ ( ] { value [, ...] | DEFAULT } [ ) ] + } ) ] + ] where column_constraint is: *************** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY *** 762,767 **** --- 768,784 ---- + + PARTITION BY + + + This clause adds a RANGE or LIST + partition key and optional partitions into the table. + When a named partition exist as a table, it inherits this table. + If not exists, a new table is automatically created and used. + + + *************** CREATE TABLE cinemas ( *** 1182,1187 **** --- 1199,1224 ---- + + Create a table films with partitions for each + released year: + + + CREATE TABLE films ( + code char(5) PRIMARY KEY, + title varchar(40), + released date, + kind varchar(10), + len interval hour to minute + ) + PARTITION BY RANGE (released) + ( + PARTITION films_2008 VALUES LESS THAN ('2009-01-01'), + PARTITION films_2009 VALUES LESS THAN ('2010-01-01'), + PARTITION films_2010 VALUES LESS THAN ('2011-01-01') + ); + + diff -cprN head/src/backend/catalog/Makefile work/src/backend/catalog/Makefile *** head/src/backend/catalog/Makefile 2010-01-07 09:24:42.729068000 +0900 --- work/src/backend/catalog/Makefile 2010-01-14 15:12:13.201468184 +0900 *************** POSTGRES_BKI_SRCS = $(addprefix $(top_sr *** 37,43 **** pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ ! pg_default_acl.h \ toasting.h indexing.h \ ) --- 37,43 ---- pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ ! pg_default_acl.h pg_partition.h \ toasting.h indexing.h \ ) diff -cprN head/src/backend/catalog/dependency.c work/src/backend/catalog/dependency.c *** head/src/backend/catalog/dependency.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/backend/catalog/dependency.c 2010-01-14 15:12:13.202482525 +0900 *************** *** 42,47 **** --- 42,48 ---- #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" + #include "catalog/pg_partition.h" #include "catalog/pg_proc.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_tablespace.h" *************** static const Oid object_classes[MAX_OCLA *** 151,157 **** ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclRelationId /* OCLASS_DEFACL */ }; --- 152,159 ---- ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclRelationId, /* OCLASS_DEFACL */ ! PartitionRelationId /* OCLASS_PARTITION */ }; *************** doDeletion(const ObjectAddress *object) *** 1149,1154 **** --- 1151,1160 ---- RemoveDefaultACLById(object->objectId); break; + case OCLASS_PARTITION: + RemovePartition(object->objectId); + break; + default: elog(ERROR, "unrecognized object class: %u", object->classId); *************** getObjectClass(const ObjectAddress *obje *** 2076,2081 **** --- 2082,2091 ---- case DefaultAclRelationId: Assert(object->objectSubId == 0); return OCLASS_DEFACL; + + case PartitionRelationId: + Assert(object->objectSubId == 0); + return OCLASS_PARTITION; } /* shouldn't get here */ *************** getObjectDescription(const ObjectAddress *** 2685,2690 **** --- 2695,2744 ---- break; } + case OCLASS_PARTITION: + { + Relation rel; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_partition partition; + + rel = heap_open(PartitionRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_partition_partrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + rcscan = systable_beginscan(rel, PartitionRelidIndexId, + true, SnapshotNow, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for partition %u", + object->objectId); + + partition = (Form_pg_partition) GETSTRUCT(tup); + + switch (partition->partkind) + { + case PARTITION_BY_RANGE: + appendStringInfo(&buffer, _("range partition")); + break; + case PARTITION_BY_LIST: + appendStringInfo(&buffer, _("list partition")); + break; + default: + appendStringInfo(&buffer, _("partition")); + break; + } + + systable_endscan(rcscan); + heap_close(rel, AccessShareLock); + break; + } + default: appendStringInfo(&buffer, "unrecognized object %u %u %d", object->classId, diff -cprN head/src/backend/catalog/heap.c work/src/backend/catalog/heap.c *** head/src/backend/catalog/heap.c 2010-01-06 14:42:58.290140000 +0900 --- work/src/backend/catalog/heap.c 2010-01-14 15:12:13.203126729 +0900 *************** *** 43,48 **** --- 43,49 ---- #include "catalog/pg_constraint.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" + #include "catalog/pg_partition.h" #include "catalog/pg_statistic.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_type.h" *************** static void StoreConstraints(Relation re *** 91,99 **** static bool MergeWithExistingConstraint(Relation rel, char *ccname, Node *expr, bool allow_merge, bool is_local); static void SetRelationNumChecks(Relation rel, int numchecks); - static Node *cookConstraint(ParseState *pstate, - Node *raw_constraint, - char *relname); static List *insert_ordered_unique_oid(List *list, Oid datum); Oid binary_upgrade_next_heap_relfilenode = InvalidOid; --- 92,97 ---- *************** cookDefault(ParseState *pstate, *** 2283,2289 **** * Parse state must be set up to recognize any vars that might appear * in the expression. */ ! static Node * cookConstraint(ParseState *pstate, Node *raw_constraint, char *relname) --- 2281,2287 ---- * Parse state must be set up to recognize any vars that might appear * in the expression. */ ! Node * cookConstraint(ParseState *pstate, Node *raw_constraint, char *relname) *************** RemoveStatistics(Oid relid, AttrNumber a *** 2376,2381 **** --- 2374,2444 ---- /* + * Remove a pg_partition entry + */ + void + RemovePartition(Oid relid) + { + Relation rel; + HeapScanDesc scan; + ScanKeyData key; + HeapTuple tup; + + /* DELETE FROM pg_partition WHERE partrelid = :relid */ + rel = heap_open(PartitionRelationId, RowExclusiveLock); + tup = SearchSysCache(PARTITIONKEY, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for partition %u", relid); + simple_heap_delete(rel, &tup->t_self); + ReleaseSysCache(tup); + heap_close(rel, RowExclusiveLock); + + /* UPDATE pg_inherits SET inhvalues = NULL WHERE inhparent = :relid */ + rel = heap_open(InheritsRelationId, RowExclusiveLock); + ScanKeyInit(&key, + Anum_pg_inherits_inhparent, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + scan = heap_beginscan(rel, SnapshotNow, 1, &key); + + while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection))) + { + Datum datum; + bool isnull; + + datum = heap_getattr(tup, Anum_pg_inherits_inhvalues, + RelationGetDescr(rel), &isnull); + if (!isnull) + { + Datum values[Natts_pg_inherits]; + bool nulls[Natts_pg_inherits]; + bool replaces[Natts_pg_inherits]; + HeapTuple newtup; + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + nulls[Anum_pg_inherits_inhvalues - 1] = true; + replaces[Anum_pg_inherits_inhvalues - 1] = true; + + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), + values, nulls, replaces); + simple_heap_update(rel, &newtup->t_self, newtup); + CatalogUpdateIndexes(rel, newtup); + + heap_freetuple(newtup); + } + } + heap_endscan(scan); + + heap_close(rel, RowExclusiveLock); + } + + + /* * RelationTruncateIndexes - truncate all indexes associated * with the heap relation to zero tuples. * diff -cprN head/src/backend/catalog/pg_inherits.c work/src/backend/catalog/pg_inherits.c *** head/src/backend/catalog/pg_inherits.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/backend/catalog/pg_inherits.c 2010-01-14 15:12:13.203126729 +0900 *************** *** 21,37 **** --- 21,46 ---- #include "access/genam.h" #include "access/heapam.h" + #include "access/nbtree.h" #include "catalog/indexing.h" #include "catalog/pg_class.h" #include "catalog/pg_inherits.h" #include "catalog/pg_inherits_fn.h" + #include "catalog/pg_opclass.h" + #include "catalog/pg_partition.h" + #include "nodes/nodeFuncs.h" #include "parser/parse_type.h" #include "storage/lmgr.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/datum.h" #include "utils/fmgroids.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/tqual.h" static int oid_cmp(const void *p1, const void *p2); + static int partition_cmp(const void *a, const void *b, void *arg); /* *************** oid_cmp(const void *p1, const void *p2) *** 341,343 **** --- 350,513 ---- return 1; return 0; } + + + /* + * get_partitions - Gather information of a partition. + * + * Returns a list of Partition. + */ + List * + get_partitions(Oid parentrelId, char *kind, Node **key, Oid *opclass) + { + Relation inhrel; + SysScanDesc scan; + ScanKeyData skey[1]; + HeapTuple tp; + Datum datum; + bool isnull; + List *partitions = NIL; + Form_pg_opclass form; + Oid opfamily; + Oid opcintype; + Oid typid; + int16 typlen; + bool typbyval; + char typalign; + int i, + n; + + *kind = 0; + *key = NULL; + *opclass = InvalidOid; + + /* Get partition key and operator. */ + tp = SearchSysCache(PARTITIONKEY, ObjectIdGetDatum(parentrelId), 0, 0, 0); + if (!HeapTupleIsValid(tp)) + return NIL; /* not partitioned */ + + *kind = DatumGetChar(SysCacheGetAttr(PARTITIONKEY, tp, + Anum_pg_partition_partkind, &isnull)); + *key = stringToNode(TextDatumGetCString(SysCacheGetAttr( + PARTITIONKEY, tp, Anum_pg_partition_partkey, &isnull))); + *opclass = DatumGetObjectId(SysCacheGetAttr(PARTITIONKEY, tp, + Anum_pg_partition_partopclass, &isnull)); + ReleaseSysCache(tp); + + tp = SearchSysCache(CLAOID, ObjectIdGetDatum(*opclass), 0, 0, 0); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for opclass %u", *opclass); + form = (Form_pg_opclass) GETSTRUCT(tp); + opfamily = form->opcfamily; + opcintype = form->opcintype; + ReleaseSysCache(tp); + + typid = exprType(*key); + get_typlenbyvalalign(typid, &typlen, &typbyval, &typalign); + + if (!has_subclass(parentrelId)) + return NIL; /* has no partitions */ + + /* Gather values from existing paritition. */ + inhrel = heap_open(InheritsRelationId, AccessShareLock); + ScanKeyInit(&skey[0], + Anum_pg_inherits_inhparent, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(parentrelId)); + scan = systable_beginscan(inhrel, InheritsParentIndexId, true, + SnapshotNow, 1, skey); + + while ((tp = systable_getnext(scan)) != NULL) + { + Form_pg_inherits inh = (Form_pg_inherits) GETSTRUCT(tp); + Partition *p; + + datum = heap_getattr(tp, Anum_pg_inherits_inhvalues, + RelationGetDescr(inhrel), &isnull); + if (isnull) + continue; /* non-partition inheritance */ + + p = (Partition *) palloc(sizeof(Partition)); + p->relid = inh->inhrelid; + deconstruct_array(DatumGetArrayTypeP(datum), typid, typlen, + typbyval, typalign, &p->values, NULL, &p->nvalues); + + /* copy array elements if passed-by-reference */ + if (!typbyval) + { + for (i = 0; i < p->nvalues; i++) + { + p->values[i] = (typlen == -1 + ? PointerGetDatum(PG_DETOAST_DATUM_COPY(p->values[i])) + : datumCopy(p->values[i], false, typlen)); + } + } + + partitions = lappend(partitions, p); + } + + systable_endscan(scan); + heap_close(inhrel, AccessShareLock); + + /* + * Sort partitions compared with each first value. They are the upper range + * of the partition for range partitions. List partitions don't always need + * to sort whole of the list, but overflow partitions should be placed at + * end of the list. + */ + n = list_length(partitions); + if (n >= 2) + { + Oid cmptype = (opcintype != InvalidOid ? opcintype : typid); + Oid cmpid; + FmgrInfo cmpfn; + Partition **sorted; + ListCell *cell; + + cmpid = get_opfamily_proc(opfamily, cmptype, cmptype, BTORDER_PROC); + if (!OidIsValid(cmpid)) + elog(ERROR, "missing support function %d(%u,%u) in opfamily %u", + BTORDER_PROC, cmptype, cmptype, opfamily); + fmgr_info(cmpid, &cmpfn); + + /* + * We flatten list to array, sort items in array, and rebuild a list. + */ + sorted = (Partition **) palloc(sizeof(Partition *) * n); + i = 0; + foreach(cell, partitions) + sorted[i++] = (Partition *) lfirst(cell); + list_free(partitions); + + qsort_arg(sorted, n, sizeof(Partition *), partition_cmp, &cmpfn); + + partitions = NIL; + for (i = 0; i < n; i++) + partitions = lappend(partitions, sorted[i]); + pfree(sorted); + } + + return partitions; + } + + + /* + * qsort function to compare partitions with the first value. Overflow + * partitions are always larger than normal partitions. + */ + static int + partition_cmp(const void *a, const void *b, void *arg) + { + const Partition *lhs = *(const Partition **) a; + const Partition *rhs = *(const Partition **) b; + FmgrInfo *cmpfn = (FmgrInfo *) arg; + + if (lhs->nvalues == 0 && rhs->nvalues == 0) + return 0; /* should not ocuur */ + else if (rhs->nvalues == 0) + return -1; /* rhs is an overflow partition */ + else if (lhs->nvalues == 0) + return +1; /* lhs is an overflow partition */ + + return DatumGetInt32(FunctionCall2(cmpfn, lhs->values[0], rhs->values[0])); + } diff -cprN head/src/backend/commands/indexcmds.c work/src/backend/commands/indexcmds.c *** head/src/backend/commands/indexcmds.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/backend/commands/indexcmds.c 2010-01-14 15:12:13.204484688 +0900 *************** static void ComputeIndexAttrs(IndexInfo *** 65,72 **** char *accessMethodName, Oid accessMethodId, bool amcanorder, bool isconstraint); - static Oid GetIndexOpClass(List *opclass, Oid attrType, - char *accessMethodName, Oid accessMethodId); static char *ChooseIndexNameAddition(List *colnames); static bool relationHasPrimaryKey(Relation rel); --- 65,70 ---- *************** ComputeIndexAttrs(IndexInfo *indexInfo, *** 1044,1052 **** /* * Resolve possibly-defaulted operator class specification */ ! static Oid GetIndexOpClass(List *opclass, Oid attrType, ! char *accessMethodName, Oid accessMethodId) { char *schemaname; char *opcname; --- 1042,1050 ---- /* * Resolve possibly-defaulted operator class specification */ ! Oid GetIndexOpClass(List *opclass, Oid attrType, ! const char *accessMethodName, Oid accessMethodId) { char *schemaname; char *opcname; diff -cprN head/src/backend/commands/tablecmds.c work/src/backend/commands/tablecmds.c *** head/src/backend/commands/tablecmds.c 2010-01-06 12:51:20.335548000 +0900 --- work/src/backend/commands/tablecmds.c 2010-01-14 17:31:09.760104405 +0900 *************** *** 32,37 **** --- 32,39 ---- #include "catalog/pg_inherits_fn.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" + #include "catalog/pg_operator.h" + #include "catalog/pg_partition.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" *************** *** 65,72 **** --- 67,76 ---- #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/smgr.h" + #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" + #include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" *************** static void MergeAttributesIntoExisting( *** 230,236 **** static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); static void StoreCatalogInheritance(Oid relationId, List *supers); static void StoreCatalogInheritance1(Oid relationId, Oid parentOid, ! int16 seqNumber, Relation inhRelation); static int findAttrByName(const char *attributeName, List *schema); static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass); static void AlterIndexNamespaces(Relation classRel, Relation rel, --- 234,240 ---- static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); static void StoreCatalogInheritance(Oid relationId, List *supers); static void StoreCatalogInheritance1(Oid relationId, Oid parentOid, ! int16 seqNumber, ArrayType *inhvalues, Relation inhRelation); static int findAttrByName(const char *attributeName, List *schema); static void setRelhassubclassInRelation(Oid relationId, bool relhassubclass); static void AlterIndexNamespaces(Relation classRel, Relation rel, *************** static void ATExecEnableDisableRule(Rela *** 331,340 **** char fires_when); static void ATExecAddInherit(Relation rel, RangeVar *parent); static void ATExecDropInherit(Relation rel, RangeVar *parent); static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, ForkNumber forkNum, bool istemp); static const char *storage_name(char c); ! /* ---------------------------------------------------------------- * DefineRelation --- 335,360 ---- char fires_when); static void ATExecAddInherit(Relation rel, RangeVar *parent); static void ATExecDropInherit(Relation rel, RangeVar *parent); + static void ATExecPartitionBy(Relation rel, PartitionBy *defs); + static void ATExecAttachPartition(List **wqueue, AlteredTableInfo *tab, Relation rel, PartitionDef *def); + static void ATExecDetachPartition(Relation rel, RangeVar *child); static void copy_relation_data(SMgrRelation rel, SMgrRelation dst, ForkNumber forkNum, bool istemp); static const char *storage_name(char c); ! static void addInherit(Relation child_rel, Relation parent_rel, ! List **wqueue, AlteredTableInfo *tab, PartitionDef *def); ! static void dropInherit(Relation rel, Relation parent_rel); ! static ArrayType *addPartitionConstraint(List **wqueue, AlteredTableInfo *tab, ! PartitionDef *def, Relation parent, Relation child); ! static void findRangePartition(Datum value, List *partitions, FmgrInfo *ltfn, ! Partition **left, Partition **right); ! static Partition *findOverlappedListPartition(const Datum *values, ! int nvalues, List *partitions, FmgrInfo *eqfn); ! static List *gatherPartitionValues(List *partitions, Oid elmtype, int elmlen, ! bool elmbyval); ! static Datum *evaluateValues(Oid typid, int typlen, bool typbyval, ! List *values, int *length); ! static const char *getPartitionKindName(char partkind); /* ---------------------------------------------------------------- * DefineRelation *************** StoreCatalogInheritance(Oid relationId, *** 1777,1783 **** { Oid parentOid = lfirst_oid(entry); ! StoreCatalogInheritance1(relationId, parentOid, seqNumber, relation); seqNumber++; } --- 1797,1803 ---- { Oid parentOid = lfirst_oid(entry); ! StoreCatalogInheritance1(relationId, parentOid, seqNumber, NULL, relation); seqNumber++; } *************** StoreCatalogInheritance(Oid relationId, *** 1789,1796 **** * of parentOid. inhRelation is the already-opened pg_inherits catalog. */ static void ! StoreCatalogInheritance1(Oid relationId, Oid parentOid, ! int16 seqNumber, Relation inhRelation) { TupleDesc desc = RelationGetDescr(inhRelation); Datum values[Natts_pg_inherits]; --- 1809,1816 ---- * of parentOid. inhRelation is the already-opened pg_inherits catalog. */ static void ! StoreCatalogInheritance1(Oid relationId, Oid parentOid, int16 seqNumber, ! ArrayType *inhvalues, Relation inhRelation) { TupleDesc desc = RelationGetDescr(inhRelation); Datum values[Natts_pg_inherits]; *************** StoreCatalogInheritance1(Oid relationId, *** 1805,1812 **** --- 1825,1834 ---- values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId); values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid); values[Anum_pg_inherits_inhseqno - 1] = Int16GetDatum(seqNumber); + values[Anum_pg_inherits_inhvalues - 1] = PointerGetDatum(inhvalues); memset(nulls, 0, sizeof(nulls)); + nulls[Anum_pg_inherits_inhvalues - 1] = (inhvalues == NULL); tuple = heap_form_tuple(desc, values, nulls); *************** RenameRelationInternal(Oid myrelid, cons *** 2174,2179 **** --- 2196,2220 ---- } /* + * CREATE PARTITION partition ON table + */ + void + CreatePartition(CreatePartitionStmt *stmt, const char *queryString) + { + List *stmts; + ListCell *cell; + + stmts = transformCreatePartition(stmt->def, stmt->parent); + + foreach(cell, stmts) + { + ProcessUtility((Node *) lfirst(cell), + queryString, NULL, false, NULL, NULL); + CommandCounterIncrement(); + } + } + + /* * Disallow ALTER TABLE (and similar commands) when the current backend has * any open reference to the target table besides the one just acquired by * the calling command; this implies there's an open cursor or active plan. *************** ATPrepCmd(List **wqueue, Relation rel, A *** 2533,2543 **** --- 2574,2590 ---- case AT_DisableRule: case AT_AddInherit: /* INHERIT / NO INHERIT */ case AT_DropInherit: + case AT_DetachPartition: /* DETACH PARTITION */ ATSimplePermissions(rel, false); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_PartitionBy: /* PARTITION BY / NO PARTITION */ + case AT_AttachPartition: /* ATTACH PARTITION */ + ATSimplePermissions(rel, false); + pass = AT_PASS_ADD_CONSTR; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); *************** ATExecCmd(List **wqueue, AlteredTableInf *** 2777,2782 **** --- 2824,2838 ---- case AT_DropInherit: ATExecDropInherit(rel, (RangeVar *) cmd->def); break; + case AT_PartitionBy: /* PARTITION BY / NO PARTITION */ + ATExecPartitionBy(rel, (PartitionBy *) cmd->def); + break; + case AT_AttachPartition: + ATExecAttachPartition(wqueue, tab, rel, (PartitionDef *) cmd->def); + break; + case AT_DetachPartition: + ATExecDetachPartition(rel, (RangeVar *) cmd->def); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); *************** ATExecAlterColumnType(AlteredTableInfo * *** 6143,6148 **** --- 6199,6211 ---- getObjectDescription(&foundObject)); break; + case OCLASS_PARTITION: + /* TODO: recreate all partitions */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type of a column used in partition keys"))); + break; + default: elog(ERROR, "unrecognized object class: %u", foundObject.classId); *************** ATExecEnableDisableRule(Relation rel, ch *** 7110,7116 **** } /* ! * ALTER TABLE INHERIT * * Add a parent to the child's parents. This verifies that all the columns and * check constraints of the parent appear in the child and that they have the --- 7173,7179 ---- } /* ! * ALTER TABLE INHERIT / ATTACH PARTITION * * Add a parent to the child's parents. This verifies that all the columns and * check constraints of the parent appear in the child and that they have the *************** ATExecEnableDisableRule(Relation rel, ch *** 7119,7131 **** static void ATExecAddInherit(Relation child_rel, RangeVar *parent) { ! Relation parent_rel, ! catalogRelation; ! SysScanDesc scan; ! ScanKeyData key; ! HeapTuple inheritsTuple; ! int32 inhseqno; ! List *children; /* * AccessShareLock on the parent is what's obtained during normal CREATE --- 7182,7188 ---- static void ATExecAddInherit(Relation child_rel, RangeVar *parent) { ! Relation parent_rel; /* * AccessShareLock on the parent is what's obtained during normal CREATE *************** ATExecAddInherit(Relation child_rel, Ran *** 7133,7138 **** --- 7190,7231 ---- */ parent_rel = heap_openrv(parent, AccessShareLock); + addInherit(child_rel, parent_rel, NULL, NULL, NULL); + + /* keep our lock on the parent relation until commit */ + heap_close(parent_rel, NoLock); + } + + static void + ATExecAttachPartition(List **wqueue, AlteredTableInfo *tab, + Relation parent_rel, PartitionDef *def) + { + Relation child_rel; + + /* + * AccessShareLock on the parent is what's obtained during normal CREATE + * TABLE ... INHERITS ..., so should be enough here. + */ + child_rel = heap_openrv(def->name, AccessShareLock); + + addInherit(child_rel, parent_rel, wqueue, tab, def); + + /* keep our lock on the parent relation until commit */ + heap_close(child_rel, NoLock); + } + + static void + addInherit(Relation child_rel, Relation parent_rel, + List **wqueue, AlteredTableInfo *tab, PartitionDef *def) + { + Relation catalogRelation; + SysScanDesc scan; + ScanKeyData key; + HeapTuple inheritsTuple; + int32 inhseqno; + List *children; + ArrayType *inhvalues; + /* * Must be owner of both parent and child -- child was checked by * ATSimplePermissions call in ATPrepCmd *************** ATExecAddInherit(Relation child_rel, Ran *** 7200,7206 **** (errcode(ERRCODE_DUPLICATE_TABLE), errmsg("circular inheritance not allowed"), errdetail("\"%s\" is already a child of \"%s\".", ! parent->relname, RelationGetRelationName(child_rel)))); /* If parent has OIDs then child must have OIDs */ --- 7293,7299 ---- (errcode(ERRCODE_DUPLICATE_TABLE), errmsg("circular inheritance not allowed"), errdetail("\"%s\" is already a child of \"%s\".", ! RelationGetRelationName(parent_rel), RelationGetRelationName(child_rel)))); /* If parent has OIDs then child must have OIDs */ *************** ATExecAddInherit(Relation child_rel, Ran *** 7217,7235 **** /* Match up the constraints and bump coninhcount as needed */ MergeConstraintsIntoExisting(child_rel, parent_rel); /* * OK, it looks valid. Make the catalog entries that show inheritance. */ StoreCatalogInheritance1(RelationGetRelid(child_rel), RelationGetRelid(parent_rel), inhseqno + 1, catalogRelation); /* Now we're done with pg_inherits */ heap_close(catalogRelation, RowExclusiveLock); ! /* keep our lock on the parent relation until commit */ ! heap_close(parent_rel, NoLock); } /* --- 7310,7735 ---- /* Match up the constraints and bump coninhcount as needed */ MergeConstraintsIntoExisting(child_rel, parent_rel); + /* Add CHECK constraint for partition */ + if (def != NULL) + inhvalues = addPartitionConstraint( + wqueue, tab, def, parent_rel, child_rel); + else + inhvalues = NULL; + /* * OK, it looks valid. Make the catalog entries that show inheritance. */ StoreCatalogInheritance1(RelationGetRelid(child_rel), RelationGetRelid(parent_rel), inhseqno + 1, + inhvalues, catalogRelation); /* Now we're done with pg_inherits */ heap_close(catalogRelation, RowExclusiveLock); + } ! /* ! * addPartitionConstraint - Add check constraint for partition. ! */ ! static ArrayType * ! addPartitionConstraint(List **wqueue, AlteredTableInfo *tab, PartitionDef *def, ! Relation parent, Relation child) ! { ! HeapTuple tp; ! Form_pg_opclass opclassTup; ! char partkind; ! Node *partkey; ! Oid opclass; ! Oid opfamily; ! Oid opcintype; ! List *partitions; ! Oid elmtype; ! int16 elmlen; ! bool elmbyval; ! char elmalign; ! int16 strategy; ! Oid oprid; ! List *opr; ! FmgrInfo opfn; ! Node *check_expr; ! Datum *values; ! int nvalues; ! ! /* Retrieve partition information. */ ! partitions = get_partitions(RelationGetRelid(parent), ! &partkind, &partkey, &opclass); ! if (partkind == 0) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_TABLE_DEFINITION), ! errmsg("table \"%s\" is not partitioned", ! RelationGetRelationName(parent)))); ! if (partkind != def->kind) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_TABLE_DEFINITION), ! errmsg("cannot add %s partition \"%s\" to %s partitioned table \"%s\"", ! getPartitionKindName(def->kind), ! RelationGetRelationName(child), ! getPartitionKindName(partkind), ! RelationGetRelationName(parent)))); ! ! /* Check for duplicated overflow partitions */ ! if (list_length(partitions) > 0 && ! ((Partition *) llast(partitions))->nvalues == 0) ! { ! if (def->values == NIL) ! ereport(ERROR, ! (errcode(ERRCODE_DUPLICATE_TABLE), ! errmsg("duplicated overflow partition"))); ! else ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot add partition to \"%s\", that has an overflow partition", ! RelationGetRelationName(parent)))); ! } ! ! /* Retrieve type information and convert expressions into datum array. */ ! elmtype = exprType(partkey); ! get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign); ! values = evaluateValues(elmtype, elmlen, elmbyval, def->values, &nvalues); ! ! /* Retrieve operator information. */ ! tp = SearchSysCache(CLAOID, ObjectIdGetDatum(opclass), 0, 0, 0); ! if (!HeapTupleIsValid(tp)) ! elog(ERROR, "cache lookup failed for opclass %u", opclass); ! opclassTup = (Form_pg_opclass) GETSTRUCT(tp); ! opfamily = opclassTup->opcfamily; ! opcintype = opclassTup->opcintype; ! ReleaseSysCache(tp); ! ! if (!OidIsValid(opcintype)) ! opcintype = elmtype; ! ! switch (partkind) ! { ! case PARTITION_BY_RANGE: ! strategy = BTLessStrategyNumber; ! break; ! case PARTITION_BY_LIST: ! strategy = BTEqualStrategyNumber; ! break; ! default: ! strategy = 0; ! } ! oprid = get_opfamily_member(opfamily, opcintype, opcintype, strategy); ! if (!OidIsValid(oprid)) ! elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", ! strategy, opcintype, opcintype, opfamily); ! opr = get_opfullname(oprid); ! fmgr_info(get_opcode(oprid), &opfn); ! ! /* ALTER TABLE ... ADD CHECK */ ! switch (partkind) ! { ! case PARTITION_BY_RANGE: ! { ! Partition *left; ! Partition *right; ! Node *lt = NULL; ! Node *ge = NULL; ! ! /* Check for overlapped list partition values. */ ! if (nvalues > 0) ! { ! Assert(nvalues == 1); ! lt = (Node *) makeA_Expr( ! AEXPR_OP, opr, partkey, linitial(def->values), -1); ! findRangePartition(values[0], partitions, &opfn, &left, &right); ! } ! else ! { ! left = (list_length(partitions) > 0 ? llast(partitions) : NULL); ! right = NULL; ! } ! ! /* TODO: split overlapped partition */ ! if (right != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("cannot split partition \"%s\"", ! get_rel_name(right->relid)))); ! ! if (left) ! { ! Oid oprle; ! Node *lower; ! ! oprle = get_opfamily_member(opfamily, opcintype, opcintype, ! BTLessEqualStrategyNumber); ! if (!OidIsValid(oprid)) ! elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", ! BTLessEqualStrategyNumber, opcintype, opcintype, opfamily); ! lower = (Node *) makeConst(elmtype, -1, elmlen, left->values[0], ! false, elmbyval); ! /* lower <= key */ ! ge = (Node *) makeA_Expr(AEXPR_OP, get_opfullname(oprle), ! lower, partkey, -1); ! } ! ! if (ge == NULL) /* key < upper */ ! check_expr = lt; ! else if (lt == NULL) /* key >= lower */ ! check_expr = ge; ! else /* key >= lower AND key < upper */ ! check_expr = (Node *) makeA_Expr(AEXPR_AND, NIL, ge, lt, -1); ! break; ! } ! case PARTITION_BY_LIST: ! { ! if (nvalues > 0) ! { ! Partition *overlapped; ! ! /* Check for overlapped list partition values. */ ! overlapped = findOverlappedListPartition(values, nvalues, ! partitions, &opfn); ! ! /* TODO: split overlapped partition */ ! if (overlapped != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ! errmsg("partition values overlapped with \"%s\"", ! get_rel_name(overlapped->relid)))); ! ! /* CHECK ( key = ANY ( values ) ) */ ! check_expr = (Node *) makeA_Expr(AEXPR_IN, opr, partkey, ! (Node *) def->values, -1); ! } ! else ! { ! List *all_values = gatherPartitionValues(partitions, elmtype, ! elmlen, elmbyval); ! ! /* CHECK ( NOT (key = ANY ( values ) ) ) */ ! if (all_values == NIL) ! check_expr = NULL; ! else ! check_expr = (Node *) makeA_Expr(AEXPR_NOT, NIL, NULL, ! (Node *) makeA_Expr(AEXPR_IN, opr, partkey, ! (Node *) all_values, -1), -1); ! } ! ! break; ! } ! default: ! elog(ERROR, "unknown partition kind: %d", partkind); ! check_expr = NULL; /* keep compiler quiet */ ! } ! ! /* Add partition constraint if there are no same check constraint. */ ! if (check_expr != NULL) ! { ! TupleConstr *constr; ! Node *cooked_expr = NULL; ! bool found = false; ! ! if ((constr = RelationGetDescr(child)->constr) != NULL) ! { ! ParseState *pstate; ! RangeTblEntry *rte; ! int i; ! ! pstate = make_parsestate(NULL); ! rte = addRangeTableEntryForRelation(pstate, child, NULL, false, true); ! addRTEtoQuery(pstate, rte, true, true, true); ! cooked_expr = cookConstraint(pstate, check_expr, ! RelationGetRelationName(child)); ! free_parsestate(pstate); ! ! for (i = 0; i < constr->num_check; i++) ! { ! if (equal(cooked_expr, stringToNode(constr->check[i].ccbin))) ! { ! found = true; ! break; ! } ! } ! } ! ! /* Add a new check constraint only when not found. */ ! if (!found) ! { ! Constraint *check; ! ! check = makeNode(Constraint); ! check->contype = CONSTR_CHECK; ! check->location = -1; ! check->raw_expr = (cooked_expr ? NULL : check_expr); ! check->cooked_expr = (cooked_expr ? nodeToString(cooked_expr) : NULL); ! ATAddCheckConstraint(wqueue, tab, child, check, false, false); ! } ! } ! ! /* overflow partition has an empty array. */ ! return construct_array(values, nvalues, elmtype, elmlen, elmbyval, elmalign); ! } ! ! /* ! * Return left and right range partitions. ! */ ! static void ! findRangePartition(Datum value, List *partitions, FmgrInfo *ltfn, ! Partition **left, Partition **right) ! { ! ListCell *cell; ! ! Assert(left != NULL); ! Assert(right != NULL); ! ! *left = *right = NULL; ! ! foreach(cell, partitions) ! { ! Partition *p = (Partition *) lfirst(cell); ! ! if (p->nvalues > 0 && ! DatumGetBool(FunctionCall2(ltfn, p->values[0], value))) ! { ! /* left < p < value */ ! if (*left == NULL || DatumGetBool(FunctionCall2(ltfn, ! (*left)->values[0], p->values[0]))) ! *left = p; ! } ! else ! { ! /* value <= p < right */ ! if (*right == NULL || (*right)->nvalues == 0 || ! (p->nvalues > 0 && DatumGetBool(FunctionCall2(ltfn, ! p->values[0], (*right)->values[0])))) ! *right = p; ! } ! } ! } ! ! /* ! * Return an overlapped list partition, or NULL if not found. ! */ ! static Partition * ! findOverlappedListPartition(const Datum *values, int nvalues, ! List *partitions, FmgrInfo *eqfn) ! { ! ListCell *cell; ! int i; ! int j; ! ! foreach(cell, partitions) ! { ! Partition *p = (Partition *) lfirst(cell); ! ! for (i = 0; i < p->nvalues; i++) ! for (j = 0; j < nvalues; j++) ! if (DatumGetBool(FunctionCall2(eqfn, p->values[i], values[j]))) ! return p; ! } ! ! return NULL; ! } ! ! /* ! * Gather partition values as a list of Const nodes. ! */ ! static List * ! gatherPartitionValues(List *partitions, Oid elmtype, int elmlen, bool elmbyval) ! { ! List *all_values = NIL; ! ListCell *cell; ! int i; ! ! foreach(cell, partitions) ! { ! Partition *p = (Partition *) lfirst(cell); ! ! for (i = 0; i < p->nvalues; i++) ! { ! Const *value = makeConst(elmtype, -1, elmlen, ! p->values[i], false, elmbyval); ! all_values = lappend(all_values, value); ! } ! } ! ! return all_values; ! } ! ! /* ! * evaluateValues - evaluate a list of expressions to build a datum array. ! */ ! static Datum * ! evaluateValues(Oid typid, int typlen, bool typbyval, List *values, int *length) ! { ! ListCell *cell; ! ParseState *pstate; ! EState *estate; ! ExprContext *econtext; ! int i; ! Datum *datum; ! ! *length = list_length(values); ! if (*length < 1) ! return NULL; ! ! datum = (Datum *) palloc(*length * sizeof(Datum)); ! pstate = make_parsestate(NULL); ! estate = CreateExecutorState(); ! econtext = GetPerTupleExprContext(estate); ! ! i = 0; ! foreach(cell, values) ! { ! Node *value = (Node *) lfirst(cell); ! bool isnull; ! ExprState *expr; ! MemoryContext oldcxt; ! ! oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate)); ! ! value = transformExpr(pstate, value); ! value = coerce_to_specific_type(pstate, value, typid, "PARTITION"); ! expr = ExecPrepareExpr((Expr *) value, estate); ! ! datum[i] = ExecEvalExpr(expr, econtext, &isnull, NULL); ! if (isnull) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_TABLE_DEFINITION), ! errmsg("partition key must not be NULL"))); ! ! MemoryContextSwitchTo(oldcxt); ! ! if (!typbyval) ! { ! if (typlen == -1) ! datum[i] = PointerGetDatum(PG_DETOAST_DATUM_COPY(datum[i])); ! else ! datum[i] = datumCopy(datum[i], false, typlen); ! } ! ! ResetPerTupleExprContext(estate); ! i++; ! } ! ! FreeExecutorState(estate); ! free_parsestate(pstate); ! ! return datum; ! } ! ! static const char * ! getPartitionKindName(char partkind) ! { ! switch (partkind) ! { ! case PARTITION_BY_RANGE: ! return "RANGE"; ! case PARTITION_BY_LIST: ! return "LIST"; ! default: ! return "???"; ! } } /* *************** MergeConstraintsIntoExisting(Relation ch *** 7462,7468 **** } /* ! * ALTER TABLE NO INHERIT * * Drop a parent from the child's parents. This just adjusts the attinhcount * and attislocal of the columns and removes the pg_inherit and pg_depend --- 7962,7968 ---- } /* ! * ALTER TABLE NO INHERIT / DETACH PARTITION * * Drop a parent from the child's parents. This just adjusts the attinhcount * and attislocal of the columns and removes the pg_inherit and pg_depend *************** static void *** 7481,7486 **** --- 7981,8016 ---- ATExecDropInherit(Relation rel, RangeVar *parent) { Relation parent_rel; + + /* + * AccessShareLock on the parent is probably enough, seeing that DROP + * TABLE doesn't lock parent tables at all. We need some lock since we'll + * be inspecting the parent's schema. + */ + parent_rel = heap_openrv(parent, AccessShareLock); + + dropInherit(rel, parent_rel); + + /* close rel, but keep lock until commit */ + relation_close(parent_rel, NoLock); + } + + static void + ATExecDetachPartition(Relation rel, RangeVar *child) + { + Relation child_rel; + + child_rel = heap_openrv(child, AccessExclusiveLock); + + dropInherit(child_rel, rel); + + /* close rel, but keep lock until commit */ + relation_close(child_rel, NoLock); + } + + static void + dropInherit(Relation rel, Relation parent_rel) + { Relation catalogRelation; SysScanDesc scan; ScanKeyData key[3]; *************** ATExecDropInherit(Relation rel, RangeVar *** 7492,7504 **** bool found = false; /* - * AccessShareLock on the parent is probably enough, seeing that DROP - * TABLE doesn't lock parent tables at all. We need some lock since we'll - * be inspecting the parent's schema. - */ - parent_rel = heap_openrv(parent, AccessShareLock); - - /* * We don't bother to check ownership of the parent table --- ownership of * the child is presumed enough rights. */ --- 8022,8027 ---- *************** ATExecDropInherit(Relation rel, RangeVar *** 7689,7697 **** systable_endscan(scan); heap_close(catalogRelation, RowExclusiveLock); ! /* keep our lock on the parent relation until commit */ ! heap_close(parent_rel, NoLock); } --- 8212,8312 ---- systable_endscan(scan); heap_close(catalogRelation, RowExclusiveLock); + } ! ! /* ! * ALTER TABLE PARTITION BY / NO PARTITION ! */ ! static void ! ATExecPartitionBy(Relation rel, PartitionBy *defs) ! { ! Oid relid = RelationGetRelid(rel); ! HeapTuple tp; ! ObjectAddress myself; ! ! tp = SearchSysCache(PARTITIONKEY, ObjectIdGetDatum(relid), 0, 0, 0); ! if (HeapTupleIsValid(tp)) ! { ! if (defs != NULL) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_TABLE_DEFINITION), ! errmsg("multiple partition keys for table \"%s\" are not allowed", ! RelationGetRelationName(rel)))); ! ReleaseSysCache(tp); ! } ! else if (defs == NULL) ! { ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_TABLE_DEFINITION), ! errmsg("table \"%s\" has no partition key", ! RelationGetRelationName(rel)))); ! } ! ! myself.classId = PartitionRelationId; ! myself.objectId = relid; ! myself.objectSubId = 0; ! ! if (defs == NULL) ! { ! /* ALTER TABLE NO PARTITION */ ! performDeletion(&myself, DROP_RESTRICT); ! } ! else ! { ! /* ALTER TABLE PARTITION BY ... */ ! Relation catalogRel; ! TupleDesc catalogDesc; ! Datum datum[Natts_pg_partition]; ! bool nulls[Natts_pg_partition]; ! HeapTuple tuple; ! Node *partkey; ! Oid keytype; ! Oid opclass; ! ParseState *pstate; ! RangeTblEntry *rte; ! ObjectAddress opclassObject; ! ! Assert(defs->key != NULL); ! ! /* Transform the expression of partition key. */ ! pstate = make_parsestate(NULL); ! rte = addRangeTableEntryForRelation(pstate, rel, NULL, false, true); ! addRTEtoQuery(pstate, rte, true, true, true); ! partkey = transformExpr(pstate, defs->key); ! free_parsestate(pstate); ! ! /* Extract operator oid to compare partition keys */ ! keytype = exprType(partkey); ! opclass = GetIndexOpClass(defs->opclass, keytype, "btree", BTREE_AM_OID); ! ! /* ! * Make the pg_partition entry ! */ ! memset(nulls, 0, sizeof(nulls)); ! datum[0] = ObjectIdGetDatum(relid); /* partrelid */ ! datum[1] = ObjectIdGetDatum(opclass); /* partopclass */ ! datum[2] = CharGetDatum(defs->kind); /* partkind */ ! datum[3] = CStringGetTextDatum(nodeToString(partkey)); /* partkey */ ! ! catalogRel = heap_open(PartitionRelationId, RowExclusiveLock); ! catalogDesc = RelationGetDescr(catalogRel); ! ! tuple = heap_form_tuple(catalogDesc, datum, nulls); ! simple_heap_insert(catalogRel, tuple); ! CatalogUpdateIndexes(catalogRel, tuple); ! heap_freetuple(tuple); ! ! /* Store a dependency */ ! recordDependencyOnSingleRelExpr(&myself, partkey, relid, ! DEPENDENCY_NORMAL, DEPENDENCY_AUTO); ! opclassObject.classId = OperatorClassRelationId; ! opclassObject.objectId = opclass; ! opclassObject.objectSubId = 0; ! recordDependencyOn(&myself, &opclassObject, DEPENDENCY_NORMAL); ! ! heap_close(catalogRel, RowExclusiveLock); ! } } diff -cprN head/src/backend/nodes/copyfuncs.c work/src/backend/nodes/copyfuncs.c *** head/src/backend/nodes/copyfuncs.c 2010-01-06 09:46:25.748744000 +0900 --- work/src/backend/nodes/copyfuncs.c 2010-01-14 15:12:13.208483632 +0900 *************** _copyCreateStmt(CreateStmt *from) *** 2511,2516 **** --- 2511,2517 ---- COPY_NODE_FIELD(options); COPY_SCALAR_FIELD(oncommit); COPY_STRING_FIELD(tablespacename); + COPY_NODE_FIELD(partitions); return newnode; } *************** _copyAlterTSConfigurationStmt(AlterTSCon *** 3469,3474 **** --- 3470,3513 ---- return newnode; } + static PartitionDef * + _copyPartitionDef(PartitionDef *from) + { + PartitionDef *newnode = makeNode(PartitionDef); + + COPY_SCALAR_FIELD(kind); + COPY_NODE_FIELD(name); + COPY_NODE_FIELD(values); + COPY_NODE_FIELD(options); + COPY_STRING_FIELD(tablespacename); + + return newnode; + } + + static PartitionBy * + _copyPartitionBy(PartitionBy *from) + { + PartitionBy *newnode = makeNode(PartitionBy); + + COPY_SCALAR_FIELD(kind); + COPY_NODE_FIELD(key); + COPY_NODE_FIELD(opclass); + COPY_NODE_FIELD(defs); + + return newnode; + } + + static CreatePartitionStmt * + _copyCreatePartitionStmt(CreatePartitionStmt *from) + { + CreatePartitionStmt *newnode = makeNode(CreatePartitionStmt); + + COPY_NODE_FIELD(parent); + COPY_NODE_FIELD(def); + + return newnode; + } + /* **************************************************************** * pg_list.h copy functions * **************************************************************** *************** copyObject(void *from) *** 4139,4144 **** --- 4178,4192 ---- case T_AlterTSConfigurationStmt: retval = _copyAlterTSConfigurationStmt(from); break; + case T_PartitionDef: + retval = _copyPartitionDef(from); + break; + case T_PartitionBy: + retval = _copyPartitionBy(from); + break; + case T_CreatePartitionStmt: + retval = _copyCreatePartitionStmt(from); + break; case T_A_Expr: retval = _copyAExpr(from); diff -cprN head/src/backend/nodes/equalfuncs.c work/src/backend/nodes/equalfuncs.c *** head/src/backend/nodes/equalfuncs.c 2010-01-06 09:46:25.748744000 +0900 --- work/src/backend/nodes/equalfuncs.c 2010-01-14 15:12:13.209413335 +0900 *************** _equalCreateStmt(CreateStmt *a, CreateSt *** 1103,1108 **** --- 1103,1109 ---- COMPARE_NODE_FIELD(options); COMPARE_SCALAR_FIELD(oncommit); COMPARE_STRING_FIELD(tablespacename); + COMPARE_NODE_FIELD(partitions); return true; } *************** _equalAlterTSConfigurationStmt(AlterTSCo *** 1912,1917 **** --- 1913,1950 ---- } static bool + _equalPartitionDef(PartitionDef *a, PartitionDef *b) + { + COMPARE_SCALAR_FIELD(kind); + COMPARE_NODE_FIELD(name); + COMPARE_NODE_FIELD(values); + COMPARE_NODE_FIELD(options); + COMPARE_STRING_FIELD(tablespacename); + + return true; + } + + static bool + _equalPartitionBy(PartitionBy *a, PartitionBy *b) + { + COMPARE_SCALAR_FIELD(kind); + COMPARE_NODE_FIELD(key); + COMPARE_NODE_FIELD(opclass); + COMPARE_NODE_FIELD(defs); + + return true; + } + + static bool + _equalCreatePartitionStmt(CreatePartitionStmt *a, CreatePartitionStmt *b) + { + COMPARE_NODE_FIELD(parent); + COMPARE_NODE_FIELD(def); + + return true; + } + + static bool _equalAExpr(A_Expr *a, A_Expr *b) { COMPARE_SCALAR_FIELD(kind); *************** equal(void *a, void *b) *** 2830,2835 **** --- 2863,2877 ---- case T_AlterTSConfigurationStmt: retval = _equalAlterTSConfigurationStmt(a, b); break; + case T_PartitionDef: + retval = _equalPartitionDef(a, b); + break; + case T_PartitionBy: + retval = _equalPartitionBy(a, b); + break; + case T_CreatePartitionStmt: + retval = _equalCreatePartitionStmt(a, b); + break; case T_A_Expr: retval = _equalAExpr(a, b); diff -cprN head/src/backend/nodes/outfuncs.c work/src/backend/nodes/outfuncs.c *** head/src/backend/nodes/outfuncs.c 2010-01-06 09:46:25.748744000 +0900 --- work/src/backend/nodes/outfuncs.c 2010-01-14 15:12:13.209413335 +0900 *************** _outCreateStmt(StringInfo str, CreateStm *** 1788,1793 **** --- 1788,1794 ---- WRITE_NODE_FIELD(options); WRITE_ENUM_FIELD(oncommit, OnCommitAction); WRITE_STRING_FIELD(tablespacename); + WRITE_NODE_FIELD(partitions); } static void *************** _outConstraint(StringInfo str, Constrain *** 2439,2444 **** --- 2440,2468 ---- } } + static void + _outPartition(StringInfo str, PartitionDef *node) + { + WRITE_NODE_TYPE("PARTITIONDEF"); + + WRITE_CHAR_FIELD(kind); + WRITE_NODE_FIELD(name); + WRITE_NODE_FIELD(values); + WRITE_NODE_FIELD(options); + WRITE_STRING_FIELD(tablespacename); + } + + static void + _outPartitionBy(StringInfo str, PartitionBy *node) + { + WRITE_NODE_TYPE("PARTITIONBY"); + + WRITE_CHAR_FIELD(kind); + WRITE_NODE_FIELD(key); + WRITE_NODE_FIELD(opclass); + WRITE_NODE_FIELD(defs); + } + /* * _outNode - *************** _outNode(StringInfo str, void *obj) *** 2892,2897 **** --- 2916,2927 ---- case T_XmlSerialize: _outXmlSerialize(str, obj); break; + case T_PartitionDef: + _outPartition(str, obj); + break; + case T_PartitionBy: + _outPartitionBy(str, obj); + break; default: diff -cprN head/src/backend/parser/gram.y work/src/backend/parser/gram.y *** head/src/backend/parser/gram.y 2010-01-06 14:42:58.290140000 +0900 --- work/src/backend/parser/gram.y 2010-01-14 15:13:50.204110961 +0900 *************** static TypeName *TableFuncTypeName(List *** 178,183 **** --- 178,185 ---- AccessPriv *accesspriv; InsertStmt *istmt; VariableSetStmt *vsetstmt; + PartitionDef *partition; + PartitionBy *partitionby; } %type stmt schema_stmt *************** static TypeName *TableFuncTypeName(List *** 193,199 **** CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt ! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt --- 195,201 ---- CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt ! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePartitionStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt *************** static TypeName *TableFuncTypeName(List *** 436,441 **** --- 438,447 ---- %type opt_existing_window_name %type opt_frame_clause frame_extent frame_bound + %type RangePartition ListPartition AnyPartition + %type PartitionBy OptPartition + %type OptRangePartitions RangePartitions RangeUpper + OptListPartitions ListPartitions ListValues ConstValues /* * Non-keyword token types. These are hard-wired into the "flex" lexer. *************** static TypeName *TableFuncTypeName(List *** 460,466 **** /* ordinary key words in alphabetical order */ %token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC ! ASSERTION ASSIGNMENT ASYMMETRIC AT AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BOOLEAN_P BOTH BY --- 466,472 ---- /* ordinary key words in alphabetical order */ %token ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC ! ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH AUTHORIZATION BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT BOOLEAN_P BOTH BY *************** static TypeName *TableFuncTypeName(List *** 475,481 **** CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS ! DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT --- 481,487 ---- CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURSOR CYCLE DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS ! DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT *************** static TypeName *TableFuncTypeName(List *** 498,504 **** KEY LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING ! LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGIN_P MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE --- 504,510 ---- KEY LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING ! LEAST LEFT LESS LEVEL LIKE LIMIT LIST LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOGIN_P MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE *************** static TypeName *TableFuncTypeName(List *** 526,532 **** STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P SYMMETRIC SYSID SYSTEM_P ! TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P --- 532,538 ---- STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SUPERUSER_P SYMMETRIC SYSID SYSTEM_P ! TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THAN THEN TIME TIMESTAMP TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P TRUNCATE TRUSTED TYPE_P *************** stmt : *** 680,685 **** --- 686,692 ---- | CreateRoleStmt | CreateUserStmt | CreateUserMappingStmt + | CreatePartitionStmt | CreatedbStmt | DeallocateStmt | DeclareCursorStmt *************** schema_stmt: *** 1138,1143 **** --- 1145,1151 ---- | CreateTrigStmt | GrantStmt | ViewStmt + | CreatePartitionStmt ; *************** alter_table_cmd: *** 1894,1899 **** --- 1902,1939 ---- n->def = (Node *)$2; $$ = (Node *)n; } + /* ALTER TABLE PARTITION BY ... */ + | PartitionBy + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_PartitionBy; + n->def = (Node *)$1; + $$ = (Node *)n; + } + /* ALTER TABLE NO PARTITION */ + | NO PARTITION + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_PartitionBy; + n->def = NULL; + $$ = (Node *)n; + } + /* ALTER TABLE ATTACH PARTITION */ + | ATTACH AnyPartition + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AttachPartition; + n->def = (Node *) $2; + $$ = (Node *)n; + } + /* ALTER TABLE DETACH PARTITION */ + | DETACH PARTITION qualified_name + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DetachPartition; + n->def = (Node *) $3; + $$ = (Node *)n; + } ; alter_column_default: *************** copy_generic_opt_arg_list_item: *** 2181,2187 **** *****************************************************************************/ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' ! OptInherit OptWith OnCommitOption OptTableSpace { CreateStmt *n = makeNode(CreateStmt); $4->istemp = $2; --- 2221,2227 ---- *****************************************************************************/ CreateStmt: CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')' ! OptInherit OptWith OnCommitOption OptTableSpace OptPartition { CreateStmt *n = makeNode(CreateStmt); $4->istemp = $2; *************** CreateStmt: CREATE OptTemp TABLE qualifi *** 2192,2201 **** n->options = $9; n->oncommit = $10; n->tablespacename = $11; $$ = (Node *)n; } | CREATE OptTemp TABLE qualified_name OF qualified_name ! '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace { /* SQL99 CREATE TABLE OF (cols) seems to be satisfied * by our inheritance capabilities. Let's try it... --- 2232,2242 ---- n->options = $9; n->oncommit = $10; n->tablespacename = $11; + n->partitions = $12; $$ = (Node *)n; } | CREATE OptTemp TABLE qualified_name OF qualified_name ! '(' OptTableElementList ')' OptWith OnCommitOption OptTableSpace OptPartition { /* SQL99 CREATE TABLE OF (cols) seems to be satisfied * by our inheritance capabilities. Let's try it... *************** CreateStmt: CREATE OptTemp TABLE qualifi *** 2209,2218 **** --- 2250,2375 ---- n->options = $10; n->oncommit = $11; n->tablespacename = $12; + n->partitions = $13; $$ = (Node *)n; } ; + OptPartition: + PartitionBy { $$ = $1; } + | /*EMPTY*/ { $$ = NULL; } + ; + + PartitionBy: + PARTITION BY RANGE '(' a_expr ')' opt_class OptRangePartitions + { + PartitionBy *n = makeNode(PartitionBy); + + n->kind = PARTITION_BY_RANGE; + n->key = $5; + n->opclass = $7; + n->defs = $8; + $$ = n; + } + | PARTITION BY LIST '(' a_expr ')' opt_class OptListPartitions + { + PartitionBy *n = makeNode(PartitionBy); + n->kind = PARTITION_BY_LIST; + n->key = $5; + n->opclass = $7; + n->defs = $8; + $$ = n; + } + ; + + OptRangePartitions: + '(' RangePartitions ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + + RangePartitions: + RangePartition { $$ = list_make1($1); } + | RangePartitions ',' RangePartition { $$ = lappend($1, $3); } + ; + + RangePartition: + PARTITION qualified_name VALUES LESS THAN RangeUpper OptWith OptTableSpace + { + PartitionDef *n = makeNode(PartitionDef); + n->kind = PARTITION_BY_RANGE; + n->name = $2; + n->values = $6; + n->options = $7; + n->tablespacename = $8; + $$ = n; + } + ; + + RangeUpper: + AexprConst { $$ = list_make1($1); } + | '(' AexprConst ')' { $$ = list_make1($2); } + | MAXVALUE { $$ = NIL; } + | '(' MAXVALUE ')' { $$ = NIL; } + ; + + OptListPartitions: + '(' ListPartitions ')' { $$ = $2; } + | /*EMPTY*/ { $$ = NIL; } + ; + + ListPartitions: + ListPartition { $$ = list_make1($1); } + | ListPartitions ',' ListPartition { $$ = lappend($1, $3); } + ; + + ListPartition: + PARTITION qualified_name VALUES opt_in ListValues OptWith OptTableSpace + { + PartitionDef *n = makeNode(PartitionDef); + n->kind = PARTITION_BY_LIST; + n->name = $2; + n->values = $5; + n->options = $6; + n->tablespacename = $7; + $$ = n; + } + ; + + opt_in: IN_P {} + | /*EMPTY*/ {} + ; + + ListValues: + DEFAULT { $$ = NIL; } + | '(' DEFAULT ')' { $$ = NIL; } + | '(' ConstValues ')' { $$ = $2; } + ; + + ConstValues: + AexprConst { $$ = list_make1($1); } + | ConstValues ',' AexprConst { $$ = lappend($1, $3); } + ; + + /* for ALTER TABLE ATTACH PARTITION. No WITH and TABLESPACE supported. */ + AnyPartition: + PARTITION qualified_name VALUES LESS THAN RangeUpper + { + PartitionDef *n = makeNode(PartitionDef); + n->kind = PARTITION_BY_RANGE; + n->name = $2; + n->values = $6; + $$ = n; + } + | PARTITION qualified_name VALUES opt_in ListValues + { + PartitionDef *n = makeNode(PartitionDef); + n->kind = PARTITION_BY_LIST; + n->name = $2; + n->values = $5; + $$ = n; + } + ; + /* * Redundancy here is needed to avoid shift/reduce conflicts, * since TEMP is not a reserved word. See also OptTempTableName. *************** opt_with_data: *** 2732,2737 **** --- 2889,2930 ---- /***************************************************************************** * * QUERY : + * CREATE PARTITION name + * + *****************************************************************************/ + + CreatePartitionStmt: + CREATE PARTITION qualified_name ON qualified_name + VALUES LESS THAN RangeUpper OptWith OptTableSpace + { + CreatePartitionStmt *n = makeNode(CreatePartitionStmt); + n->parent = $5; + n->def = makeNode(PartitionDef); + n->def->kind = PARTITION_BY_RANGE; + n->def->name = $3; + n->def->values = $9; + n->def->options = $10; + n->def->tablespacename = $11; + $$ = (Node *)n; + } + | CREATE PARTITION qualified_name ON qualified_name + VALUES opt_in ListValues OptWith OptTableSpace + { + CreatePartitionStmt *n = makeNode(CreatePartitionStmt); + n->parent = $5; + n->def = makeNode(PartitionDef); + n->def->kind = PARTITION_BY_LIST; + n->def->name = $3; + n->def->values = $8; + n->def->options = $9; + n->def->tablespacename = $10; + $$ = (Node *)n; + } + ; + + /***************************************************************************** + * + * QUERY : * CREATE SEQUENCE seqname * ALTER SEQUENCE seqname * *************** DropStmt: DROP drop_type IF_P EXISTS any *** 3989,3994 **** --- 4182,4188 ---- drop_type: TABLE { $$ = OBJECT_TABLE; } + | PARTITION { $$ = OBJECT_TABLE; } | SEQUENCE { $$ = OBJECT_SEQUENCE; } | VIEW { $$ = OBJECT_VIEW; } | INDEX { $$ = OBJECT_INDEX; } *************** unreserved_keyword: *** 10693,10698 **** --- 10887,10893 ---- | ASSERTION | ASSIGNMENT | AT + | ATTACH | BACKWARD | BEFORE | BEGIN_P *************** unreserved_keyword: *** 10738,10743 **** --- 10933,10939 ---- | DELETE_P | DELIMITER | DELIMITERS + | DETACH | DICTIONARY | DISABLE_P | DISCARD *************** unreserved_keyword: *** 10795,10801 **** --- 10991,10999 ---- | LAST_P | LC_COLLATE_P | LC_CTYPE_P + | LESS | LEVEL + | LIST | LISTEN | LOAD | LOCAL *************** unreserved_keyword: *** 10901,10906 **** --- 11099,11105 ---- | TEMPLATE | TEMPORARY | TEXT_P + | THAN | TRANSACTION | TRIGGER | TRUNCATE diff -cprN head/src/backend/parser/parse_utilcmd.c work/src/backend/parser/parse_utilcmd.c *** head/src/backend/parser/parse_utilcmd.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/backend/parser/parse_utilcmd.c 2010-01-14 15:12:13.212148207 +0900 *************** static void transformFKConstraints(Parse *** 116,121 **** --- 116,122 ---- CreateStmtContext *cxt, bool skipValidation, bool isAddConstraint); + static void transformPartitionBy(ParseState *pstate, CreateStmtContext *cxt, PartitionBy *defs); static void transformConstraintAttrs(ParseState *pstate, List *constraintList); static void transformColumnType(ParseState *pstate, ColumnDef *column); static void setSchemaName(char *context_schema, char **stmt_schema_name); *************** transformCreateStmt(CreateStmt *stmt, co *** 235,240 **** --- 236,259 ---- transformFKConstraints(pstate, &cxt, true, false); /* + * Postprocess partition related information. + */ + if (stmt->partitions) + { + AlterTableStmt *alterstmt = makeNode(AlterTableStmt); + AlterTableCmd *cmd = makeNode(AlterTableCmd); + + cmd->subtype = AT_PartitionBy; + cmd->def = (Node *) stmt->partitions; + + alterstmt->relation = cxt.relation; + alterstmt->relkind = OBJECT_TABLE; + alterstmt->cmds = list_make1(cmd); + + cxt.alist = lappend(cxt.alist, alterstmt); + } + + /* * Output results. */ stmt->tableElts = cxt.columns; *************** transformFKConstraints(ParseState *pstat *** 1482,1487 **** --- 1501,1574 ---- } } + /* CREATE PARTITION */ + List * + transformCreatePartition(PartitionDef *def, RangeVar *parent) + { + List *result = NIL; + CreateStmt *create; + InhRelation *like; + AlterTableStmt *alter; + AlterTableCmd *attach; + PartitionDef *partition; + + /* Use the same schema as the parent if not specified. */ + if (def->name->schemaname == NULL) + def->name->schemaname = parent->schemaname; + def->name->istemp = parent->istemp; + + /* CREATE TABLE partition (LIKE parent INCLUDING ALL) */ + like = makeNode(InhRelation); + like->relation = parent; + like->options = CREATE_TABLE_LIKE_ALL; + create = makeNode(CreateStmt); + create->relation = def->name; + create->tableElts = list_make1(like); + create->inhRelations = NIL; + create->constraints = NIL; + create->options = def->options; + create->oncommit = ONCOMMIT_NOOP; + create->tablespacename = def->tablespacename; + create->partitions = NULL; + result = lappend(result, create); + + /* ALTER TABLE partition INHERIT parent (AS PARTITION) */ + partition = makeNode(PartitionDef); + partition->kind = def->kind;; + partition->name = def->name; + partition->values = def->values; + attach = makeNode(AlterTableCmd); + attach->subtype = AT_AttachPartition; + attach->def = (Node *) partition; + alter = makeNode(AlterTableStmt); + alter->relation = parent; + alter->cmds = list_make1(attach); + alter->relkind = OBJECT_TABLE; + result = lappend(result, alter); + + return result; + } + + /* PARTITION BY / NO PARTITION */ + static void + transformPartitionBy(ParseState *pstate, CreateStmtContext *cxt, + PartitionBy *partitions) + { + ListCell *cell; + + if (partitions == NULL) + return; + + /* Expand partition clauses to CREATE TABLE. */ + foreach (cell, partitions->defs) + { + PartitionDef *def = (PartitionDef *) lfirst(cell); + + cxt->alist = list_concat(cxt->alist, + transformCreatePartition(def, cxt->relation)); + } + } + /* * transformIndexStmt - parse analysis for CREATE INDEX * *************** transformAlterTableStmt(AlterTableStmt * *** 1971,1976 **** --- 2058,2068 ---- newcmds = lappend(newcmds, cmd); break; + case AT_PartitionBy: + newcmds = lappend(newcmds, cmd); + transformPartitionBy(pstate, &cxt, (PartitionBy *) cmd->def); + break; + default: newcmds = lappend(newcmds, cmd); break; diff -cprN head/src/backend/tcop/utility.c work/src/backend/tcop/utility.c *** head/src/backend/tcop/utility.c 2010-01-06 12:51:20.335548000 +0900 --- work/src/backend/tcop/utility.c 2010-01-14 15:12:13.213124791 +0900 *************** check_xact_readonly(Node *parsetree) *** 219,224 **** --- 219,225 ---- case T_AlterUserMappingStmt: case T_DropUserMappingStmt: case T_AlterTableSpaceOptionsStmt: + case T_CreatePartitionStmt: ereport(ERROR, (errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION), errmsg("transaction is read-only"))); *************** standard_ProcessUtility(Node *parsetree, *** 565,570 **** --- 566,575 ---- RemoveUserMapping((DropUserMappingStmt *) parsetree); break; + case T_CreatePartitionStmt: + CreatePartition((CreatePartitionStmt *) parsetree, queryString); + break; + case T_DropStmt: { DropStmt *stmt = (DropStmt *) parsetree; *************** CreateCommandTag(Node *parsetree) *** 1497,1502 **** --- 1502,1511 ---- tag = "DROP USER MAPPING"; break; + case T_CreatePartitionStmt: + tag = "CREATE PARTITION"; + break; + case T_DropStmt: switch (((DropStmt *) parsetree)->removeType) { *************** GetCommandLogLevel(Node *parsetree) *** 2256,2261 **** --- 2265,2271 ---- case T_CreateUserMappingStmt: case T_AlterUserMappingStmt: case T_DropUserMappingStmt: + case T_CreatePartitionStmt: lev = LOGSTMT_DDL; break; diff -cprN head/src/backend/utils/adt/ruleutils.c work/src/backend/utils/adt/ruleutils.c *** head/src/backend/utils/adt/ruleutils.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/backend/utils/adt/ruleutils.c 2010-01-14 15:12:13.214409672 +0900 *************** *** 26,31 **** --- 26,32 ---- #include "catalog/pg_constraint.h" #include "catalog/pg_depend.h" #include "catalog/pg_language.h" + #include "catalog/pg_inherits_fn.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" *************** pg_get_function_result(PG_FUNCTION_ARGS) *** 1873,1878 **** --- 1874,2014 ---- } /* + * pg_get_partitiondef - Get the definition of a partition + */ + Datum + pg_get_partitiondef(PG_FUNCTION_ARGS) + { + Oid parentOid = PG_GETARG_OID(0); + bool alter_fmt = PG_GETARG_OID(1); + + Oid parentNsp = get_rel_namespace(parentOid); + char *parentName; + char partkind; + Node *partkey; + Oid partopclass; + List *partitions; + Oid typid; + const char *prefix; + const char *suffix; + const char *overflow; + StringInfoData buf; + + partitions = get_partitions(parentOid, &partkind, &partkey, &partopclass); + if (partkind == 0) + PG_RETURN_NULL(); /* not partitioned */ + + typid = exprType(partkey); + + initStringInfo(&buf); + + /* Append definition of partition key. */ + if (alter_fmt) + { + parentName = quote_qualified_identifier( + get_namespace_name(parentNsp), get_rel_name(parentOid)); + + appendStringInfo(&buf, "ALTER TABLE %s PARTITION BY ", parentName); + } + else + parentName = NULL; + + switch (partkind) + { + case PARTITION_BY_RANGE: + appendStringInfoString(&buf, "RANGE"); + prefix = "LESS THAN "; + suffix = ""; + overflow = "MAXVALUE"; + break; + case PARTITION_BY_LIST: + appendStringInfoString(&buf, "LIST"); + prefix = "("; + suffix = ")"; + overflow = "DEFAULT"; + break; + default: + elog(ERROR, "unknown partition kind: %d", partkind); + prefix = suffix = overflow = NULL; + break; + } + appendStringInfoString(&buf, " ("); + appendStringInfoString(&buf, deparse_expression_pretty( + partkey, deparse_context_for(get_rel_name(parentOid), parentOid), + false, false, 0, 0)); + appendStringInfoChar(&buf, ')'); + get_opclass_name(partopclass, typid, &buf); + + /* Append definitions of partitions. */ + if (list_length(partitions) > 0) + { + ListCell *cell; + Oid typoutput; + bool typIsVarlena; + FmgrInfo outfn; + bool needcomma = false; + + getTypeOutputInfo(typid, &typoutput, &typIsVarlena); + fmgr_info(typoutput, &outfn); + + if (alter_fmt) + appendStringInfoString(&buf, ";\n"); + else + appendStringInfoString(&buf, "\n(\n"); + + foreach(cell, partitions) + { + Partition *p = (Partition *) lfirst(cell); + Oid childNsp; + char *nspname; + char *name; + + /* Hide namespace if parent and child are in the same namespace. */ + if ((childNsp = get_rel_namespace(p->relid)) != parentNsp) + nspname = get_namespace_name(childNsp); + else + nspname = NULL; + name = quote_qualified_identifier(nspname, get_rel_name(p->relid)); + + if (needcomma) + appendStringInfoString(&buf, ",\n"); + + if (alter_fmt) + appendStringInfo(&buf, "ALTER TABLE %s ATTACH ", parentName); + else + appendStringInfoString(&buf, " "); + appendStringInfo(&buf, "PARTITION %s VALUES ", name); + + appendStringInfoString(&buf, prefix); + if (p->nvalues > 0) + { + int i; + + for (i = 0; i < p->nvalues; i++) + { + if (i > 0) + appendStringInfo(&buf, ", "); + simple_quote_literal(&buf, DatumGetCString( + OutputFunctionCall(&outfn, p->values[i]))); + } + } + else + appendStringInfoString(&buf, overflow); + appendStringInfoString(&buf, suffix); + + if (alter_fmt) + appendStringInfoString(&buf, ";\n"); + else + needcomma = true; + } + if (!alter_fmt) + appendStringInfoString(&buf, "\n)"); + } + + PG_RETURN_TEXT_P(string_to_text(buf.data)); + } + + /* * Guts of pg_get_function_result: append the function's return type * to the specified buffer. */ diff -cprN head/src/backend/utils/cache/lsyscache.c work/src/backend/utils/cache/lsyscache.c *** head/src/backend/utils/cache/lsyscache.c 2010-01-05 09:17:58.505600000 +0900 --- work/src/backend/utils/cache/lsyscache.c 2010-01-14 15:12:13.215164172 +0900 *************** *** 32,39 **** --- 32,41 ---- #include "utils/array.h" #include "utils/builtins.h" #include "utils/datum.h" + #include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/syscache.h" + #include "utils/tqual.h" /* Hook for plugins to get control in get_attavgwidth() */ get_attavgwidth_hook_type get_attavgwidth_hook = NULL; *************** get_opname(Oid opno) *** 1058,1063 **** --- 1060,1088 ---- return NULL; } + List * + get_opfullname(Oid opno) + { + HeapTuple tp; + + tp = SearchSysCache(OPEROID, + ObjectIdGetDatum(opno), + 0, 0, 0); + if (HeapTupleIsValid(tp)) + { + Form_pg_operator optup = (Form_pg_operator) GETSTRUCT(tp); + List *result; + + result = list_make2( + makeString(get_namespace_name(optup->oprnamespace)), + makeString(pstrdup(NameStr(optup->oprname)))); + ReleaseSysCache(tp); + return result; + } + else + return NULL; + } + /* * op_input_types * *************** get_roleid_checked(const char *rolname) *** 2785,2787 **** --- 2810,2813 ---- errmsg("role \"%s\" does not exist", rolname))); return roleid; } + diff -cprN head/src/backend/utils/cache/syscache.c work/src/backend/utils/cache/syscache.c *** head/src/backend/utils/cache/syscache.c 2010-01-06 09:46:25.748744000 +0900 --- work/src/backend/utils/cache/syscache.c 2010-01-14 15:12:13.215164172 +0900 *************** *** 40,45 **** --- 40,46 ---- #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" + #include "catalog/pg_partition.h" #include "catalog/pg_proc.h" #include "catalog/pg_rewrite.h" #include "catalog/pg_statistic.h" *************** static const struct cachedesc cacheinfo[ *** 777,787 **** 0 }, 128 } }; ! static CatCache *SysCache[ ! lengthof(cacheinfo)]; static int SysCacheSize = lengthof(cacheinfo); static bool CacheInitialized = false; --- 778,799 ---- 0 }, 128 + }, + {PartitionRelationId, /* PARTITIONKEY */ + PartitionRelidIndexId, + Anum_pg_partition_partrelid, + 1, + { + Anum_pg_partition_partrelid, + 0, + 0, + 0 + }, + 64 } }; ! static CatCache *SysCache[lengthof(cacheinfo)]; static int SysCacheSize = lengthof(cacheinfo); static bool CacheInitialized = false; diff -cprN head/src/bin/pg_dump/common.c work/src/bin/pg_dump/common.c *** head/src/bin/pg_dump/common.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/bin/pg_dump/common.c 2010-01-14 15:12:13.215164172 +0900 *************** getSchemaData(int *numTablesPtr) *** 222,227 **** --- 222,231 ---- write_msg(NULL, "reading triggers\n"); getTriggers(tblinfo, numTables); + if (g_verbose) + write_msg(NULL, "reading partitions\n"); + getPartitions(tblinfo, numTables); + *numTablesPtr = numTables; return tblinfo; } diff -cprN head/src/bin/pg_dump/pg_dump.c work/src/bin/pg_dump/pg_dump.c *** head/src/bin/pg_dump/pg_dump.c 2010-01-06 14:42:58.290140000 +0900 --- work/src/bin/pg_dump/pg_dump.c 2010-01-14 15:12:13.217479614 +0900 *************** static void dumpUserMappings(Archive *fo *** 165,170 **** --- 165,171 ---- const char *servername, const char *namespace, const char *owner, CatalogId catalogId, DumpId dumpId); static void dumpDefaultACL(Archive *fout, DefaultACLInfo *daclinfo); + static void dumpPartition(Archive *fout, PartitionInfo *partinfo); static void dumpACL(Archive *fout, CatalogId objCatId, DumpId objDumpId, const char *type, const char *name, const char *subname, *************** getInherits(int *numInherits) *** 3853,3859 **** /* find all the inheritance information */ ! appendPQExpBuffer(query, "SELECT inhrelid, inhparent FROM pg_inherits"); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 3854,3864 ---- /* find all the inheritance information */ ! if (g_fout->remoteVersion >= 80500) ! appendPQExpBuffer(query, "SELECT inhrelid, inhparent FROM pg_inherits " ! "WHERE inhvalues IS NULL"); ! else ! appendPQExpBuffer(query, "SELECT inhrelid, inhparent FROM pg_inherits"); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** getDefaultACLs(int *numDefaultACLs) *** 6063,6068 **** --- 6068,6141 ---- } /* + * getTriggers + * get information about every partition on a dumpable table + * + * Note: partition data is not returned directly to the caller, but it + * does get entered into the DumpableObject tables. + */ + void + getPartitions(TableInfo tblinfo[], int numTables) + { + int i, + j; + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + PartitionInfo *partinfo; + int i_partrelid, + i_partdef; + int ntups; + + if (g_fout->remoteVersion < 80500) + return; + + appendPQExpBuffer(query, + "SELECT " + "partrelid, " + "pg_get_partitiondef(partrelid, true) AS partdef " + "FROM pg_partition"); + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + i_partrelid = PQfnumber(res, "partrelid"); + i_partdef = PQfnumber(res, "partdef"); + + ntups = PQntuples(res); + partinfo = (PartitionInfo *) malloc(ntups * sizeof(PartitionInfo)); + + for (j = 0; j < ntups; j++) + { + Oid relid = atooid(PQgetvalue(res, j, i_partrelid)); + + for (i = 0; i < numTables; i++) + { + TableInfo *tbinfo = &tblinfo[i]; + + if (tbinfo->dobj.catId.oid == relid) + { + if (g_verbose) + write_msg(NULL, "reading partition for table \"%s\"\n", + tbinfo->dobj.name); + + partinfo[j].dobj.objType = DO_PARTITION; + partinfo[j].dobj.catId.tableoid = 0; + partinfo[j].dobj.catId.oid = tbinfo->dobj.catId.oid; + AssignDumpId(&partinfo[j].dobj); + partinfo[j].dobj.name = tbinfo->dobj.name; + partinfo[j].dobj.namespace = tbinfo->dobj.namespace; + partinfo[j].dobj.dump = tbinfo->dobj.dump; + partinfo[j].parttable = tbinfo; + partinfo[j].partdef = strdup(PQgetvalue(res, j, i_partdef)); + break; + } + } + } + + PQclear(res); + destroyPQExpBuffer(query); + } + + /* * dumpComment -- * * This routine is used to dump any comments associated with the *************** dumpDumpableObject(Archive *fout, Dumpab *** 6494,6499 **** --- 6567,6575 ---- dobj->dependencies, dobj->nDeps, dumpBlobComments, NULL); break; + case DO_PARTITION: + dumpPartition(fout, (PartitionInfo *) dobj); + break; } } *************** dumpRule(Archive *fout, RuleInfo *rinfo) *** 11931,11936 **** --- 12007,12047 ---- } /* + * dumpPartition + * Dump a partition + */ + static void + dumpPartition(Archive *fout, PartitionInfo *partinfo) + { + TableInfo *tbinfo = partinfo->parttable; + PQExpBuffer cmd; + + /* Skip if not to be dumped */ + if (!partinfo->dobj.dump || dataOnly) + return; + + cmd = createPQExpBuffer(); + + appendPQExpBufferStr(cmd, partinfo->partdef); + + /* + * DROP must be fully qualified in case same name appears in pg_catalog + */ + ArchiveEntry(fout, partinfo->dobj.catId, partinfo->dobj.dumpId, + partinfo->dobj.name, + tbinfo->dobj.namespace->dobj.name, + NULL, + tbinfo->rolname, false, + "PARTITION", SECTION_POST_DATA, + cmd->data, "", NULL, + partinfo->dobj.dependencies, partinfo->dobj.nDeps, + NULL, NULL); + + destroyPQExpBuffer(cmd); + } + + + /* * getDependencies --- obtain available dependency data */ static void diff -cprN head/src/bin/pg_dump/pg_dump.h work/src/bin/pg_dump/pg_dump.h *** head/src/bin/pg_dump/pg_dump.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/bin/pg_dump/pg_dump.h 2010-01-14 15:12:13.218158396 +0900 *************** typedef enum *** 116,122 **** DO_FOREIGN_SERVER, DO_DEFAULT_ACL, DO_BLOBS, ! DO_BLOB_COMMENTS } DumpableObjectType; typedef struct _dumpableObject --- 116,123 ---- DO_FOREIGN_SERVER, DO_DEFAULT_ACL, DO_BLOBS, ! DO_BLOB_COMMENTS, ! DO_PARTITION } DumpableObjectType; typedef struct _dumpableObject *************** typedef struct _defaultACLInfo *** 442,447 **** --- 443,455 ---- char *defaclacl; } DefaultACLInfo; + typedef struct _partitionInfo + { + DumpableObject dobj; + TableInfo *parttable; /* link to table the partition is for */ + char *partdef; + } PartitionInfo; + /* global decls */ extern bool force_quotes; /* double-quotes for identifiers flag */ extern bool g_verbose; /* verbose flag */ *************** extern TSConfigInfo *getTSConfigurations *** 527,531 **** --- 535,540 ---- extern FdwInfo *getForeignDataWrappers(int *numForeignDataWrappers); extern ForeignServerInfo *getForeignServers(int *numForeignServers); extern DefaultACLInfo *getDefaultACLs(int *numDefaultACLs); + extern void getPartitions(TableInfo tblinfo[], int numTables); #endif /* PG_DUMP_H */ diff -cprN head/src/bin/pg_dump/pg_dump_sort.c work/src/bin/pg_dump/pg_dump_sort.c *** head/src/bin/pg_dump/pg_dump_sort.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/bin/pg_dump/pg_dump_sort.c 2010-01-14 15:12:13.218158396 +0900 *************** static const int oldObjectTypePriority[] *** 56,62 **** 4, /* DO_FOREIGN_SERVER */ 17, /* DO_DEFAULT_ACL */ 10, /* DO_BLOBS */ ! 11 /* DO_BLOB_COMMENTS */ }; /* --- 56,63 ---- 4, /* DO_FOREIGN_SERVER */ 17, /* DO_DEFAULT_ACL */ 10, /* DO_BLOBS */ ! 11, /* DO_BLOB_COMMENTS */ ! 18 /* DO_PARTITION */ }; /* *************** static const int newObjectTypePriority[] *** 76,89 **** 9, /* DO_CONVERSION */ 16, /* DO_TABLE */ 18, /* DO_ATTRDEF */ ! 23, /* DO_INDEX */ ! 24, /* DO_RULE */ ! 25, /* DO_TRIGGER */ ! 22, /* DO_CONSTRAINT */ ! 26, /* DO_FK_CONSTRAINT */ 2, /* DO_PROCLANG */ 8, /* DO_CAST */ ! 19, /* DO_TABLE_DATA */ 17, /* DO_DUMMY_TYPE */ 10, /* DO_TSPARSER */ 12, /* DO_TSDICT */ --- 77,90 ---- 9, /* DO_CONVERSION */ 16, /* DO_TABLE */ 18, /* DO_ATTRDEF */ ! 24, /* DO_INDEX */ ! 25, /* DO_RULE */ ! 26, /* DO_TRIGGER */ ! 23, /* DO_CONSTRAINT */ ! 27, /* DO_FK_CONSTRAINT */ 2, /* DO_PROCLANG */ 8, /* DO_CAST */ ! 20, /* DO_TABLE_DATA */ 17, /* DO_DUMMY_TYPE */ 10, /* DO_TSPARSER */ 12, /* DO_TSDICT */ *************** static const int newObjectTypePriority[] *** 91,99 **** 13, /* DO_TSCONFIG */ 14, /* DO_FDW */ 15, /* DO_FOREIGN_SERVER */ ! 27, /* DO_DEFAULT_ACL */ ! 20, /* DO_BLOBS */ ! 21 /* DO_BLOB_COMMENTS */ }; --- 92,101 ---- 13, /* DO_TSCONFIG */ 14, /* DO_FDW */ 15, /* DO_FOREIGN_SERVER */ ! 28, /* DO_DEFAULT_ACL */ ! 21, /* DO_BLOBS */ ! 22, /* DO_BLOB_COMMENTS */ ! 19 /* DO_PARTITION */ }; *************** describeDumpableObject(DumpableObject *o *** 1156,1161 **** --- 1158,1168 ---- "BLOB COMMENTS (ID %d)", obj->dumpId); return; + case DO_PARTITION: + snprintf(buf, bufsize, + "PARTITION %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; } /* shouldn't get here */ snprintf(buf, bufsize, diff -cprN head/src/bin/psql/describe.c work/src/bin/psql/describe.c *** head/src/bin/psql/describe.c 2010-01-04 09:10:26.638773000 +0900 --- work/src/bin/psql/describe.c 2010-01-14 15:12:13.219142582 +0900 *************** describeOneTableDetails(const char *sche *** 1985,1992 **** } PQclear(result); /* print child tables */ ! if (pset.sversion >= 80300) printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid); else printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid); --- 1985,2008 ---- } PQclear(result); + /* print partitions */ + if (pset.sversion >= 80500) + { + printfPQExpBuffer(&buf, "SELECT pg_catalog.pg_get_partitiondef('%s', false);", oid); + result = PSQLexec(buf.data, false); + if (!result) + goto error_return; + if (PQntuples(result) > 0 && !PQgetisnull(result, 0, 0)) + { + printfPQExpBuffer(&buf, _("Partitions: %s"), PQgetvalue(result, 0, 0)); + printTableAddFooter(&cont, buf.data); + } + } + /* print child tables */ ! if (pset.sversion >= 80500) ! printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' AND i.inhvalues IS NULL ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid); ! else if (pset.sversion >= 80300) printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.oid::pg_catalog.regclass::pg_catalog.text;", oid); else printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhrelid AND i.inhparent = '%s' ORDER BY c.relname;", oid); diff -cprN head/src/include/catalog/dependency.h work/src/include/catalog/dependency.h *** head/src/include/catalog/dependency.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/catalog/dependency.h 2010-01-14 15:12:13.220153520 +0900 *************** typedef enum ObjectClass *** 148,153 **** --- 148,154 ---- OCLASS_FOREIGN_SERVER, /* pg_foreign_server */ OCLASS_USER_MAPPING, /* pg_user_mapping */ OCLASS_DEFACL, /* pg_default_acl */ + OCLASS_PARTITION, /* pg_partition */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; diff -cprN head/src/include/catalog/heap.h work/src/include/catalog/heap.h *** head/src/include/catalog/heap.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/catalog/heap.h 2010-01-14 15:12:13.220153520 +0900 *************** extern Node *cookDefault(ParseState *pst *** 93,98 **** --- 93,101 ---- Oid atttypid, int32 atttypmod, char *attname); + extern Node *cookConstraint(ParseState *pstate, + Node *raw_constraint, + char *relname); extern void DeleteRelationTuple(Oid relid); extern void DeleteAttributeTuples(Oid relid); *************** extern void RemoveAttrDefault(Oid relid, *** 101,106 **** --- 104,110 ---- DropBehavior behavior, bool complain); extern void RemoveAttrDefaultById(Oid attrdefId); extern void RemoveStatistics(Oid relid, AttrNumber attnum); + extern void RemovePartition(Oid relid); extern Form_pg_attribute SystemAttributeDefinition(AttrNumber attno, bool relhasoids); diff -cprN head/src/include/catalog/indexing.h work/src/include/catalog/indexing.h *** head/src/include/catalog/indexing.h 2010-01-05 10:25:20.891034000 +0900 --- work/src/include/catalog/indexing.h 2010-01-14 15:12:13.221141962 +0900 *************** DECLARE_UNIQUE_INDEX(pg_default_acl_oid_ *** 281,286 **** --- 281,289 ---- DECLARE_UNIQUE_INDEX(pg_db_role_setting_databaseid_rol_index, 2965, on pg_db_role_setting using btree(setdatabase oid_ops, setrole oid_ops)); #define DbRoleSettingDatidRolidIndexId 2965 + DECLARE_UNIQUE_INDEX(pg_partition_relid_index, 2998, on pg_partition using btree(partrelid oid_ops)); + #define PartitionRelidIndexId 2998 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES diff -cprN head/src/include/catalog/pg_inherits.h work/src/include/catalog/pg_inherits.h *** head/src/include/catalog/pg_inherits.h 2010-01-05 10:25:20.891034000 +0900 --- work/src/include/catalog/pg_inherits.h 2010-01-14 15:12:13.221141962 +0900 *************** *** 21,26 **** --- 21,29 ---- #include "catalog/genbki.h" + /* See comments in pg_statistic.h. */ + #define anyarray int + /* ---------------- * pg_inherits definition. cpp turns this into * typedef struct FormData_pg_inherits *************** CATALOG(pg_inherits,2611) BKI_WITHOUT_OI *** 33,38 **** --- 36,42 ---- Oid inhrelid; Oid inhparent; int4 inhseqno; + anyarray inhvalues; /* values for partition */ } FormData_pg_inherits; /* ---------------- *************** typedef FormData_pg_inherits *Form_pg_in *** 46,55 **** * compiler constants for pg_inherits * ---------------- */ ! #define Natts_pg_inherits 3 #define Anum_pg_inherits_inhrelid 1 #define Anum_pg_inherits_inhparent 2 #define Anum_pg_inherits_inhseqno 3 /* ---------------- * pg_inherits has no initial contents --- 50,60 ---- * compiler constants for pg_inherits * ---------------- */ ! #define Natts_pg_inherits 4 #define Anum_pg_inherits_inhrelid 1 #define Anum_pg_inherits_inhparent 2 #define Anum_pg_inherits_inhseqno 3 + #define Anum_pg_inherits_inhvalues 4 /* ---------------- * pg_inherits has no initial contents diff -cprN head/src/include/catalog/pg_inherits_fn.h work/src/include/catalog/pg_inherits_fn.h *** head/src/include/catalog/pg_inherits_fn.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/catalog/pg_inherits_fn.h 2010-01-14 15:12:13.221141962 +0900 *************** *** 17,24 **** --- 17,36 ---- #include "nodes/pg_list.h" #include "storage/lock.h" + /* + * Partition - store partition values. + */ + typedef struct Partition + { + Oid relid; + int nvalues; + Datum *values; + } Partition; + extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode); extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode); + extern List *get_partitions(Oid parentrelId, + char *kind, Node **key, Oid *opclass); extern bool has_subclass(Oid relationId); extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId); diff -cprN head/src/include/catalog/pg_partition.h work/src/include/catalog/pg_partition.h *** head/src/include/catalog/pg_partition.h 1970-01-01 09:00:00.000000000 +0900 --- work/src/include/catalog/pg_partition.h 2010-01-14 15:12:13.222411292 +0900 *************** *** 0 **** --- 1,55 ---- + /*------------------------------------------------------------------------- + * + * pg_partition.h + * definition of the system "partition" relation (pg_partition) + * along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * + * $PostgreSQL: pgsql/src/include/catalog/pg_partition.h $ + * + * NOTES + * the genbki.sh script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_PARTITION_H + #define PG_PARTITION_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_partition definition. cpp turns this into + * typedef struct FormData_pg_partitions + * ---------------- + */ + #define PartitionRelationId 2997 + + CATALOG(pg_partition,2997) BKI_WITHOUT_OIDS + { + Oid partrelid; /* partitioned table oid */ + Oid partopclass; /* operator class to compare keys */ + char partkind; /* kind of partition: RANGE or LIST */ + text partkey; /* partition key expression */ + } FormData_pg_partition; + + /* ---------------- + * Form_pg_partitions corresponds to a pointer to a tuple with + * the format of pg_partitions relation. + * ---------------- + */ + typedef FormData_pg_partition *Form_pg_partition; + + /* ---------------- + * compiler constants for pg_partitions + * ---------------- + */ + #define Natts_pg_partition 4 + #define Anum_pg_partition_partrelid 1 + #define Anum_pg_partition_partopclass 2 + #define Anum_pg_partition_partkind 3 + #define Anum_pg_partition_partkey 4 + + #endif /* PG_PARTITIONS_H */ diff -cprN head/src/include/catalog/pg_proc.h work/src/include/catalog/pg_proc.h *** head/src/include/catalog/pg_proc.h 2010-01-08 09:22:26.272759000 +0900 --- work/src/include/catalog/pg_proc.h 2010-01-14 15:12:13.223156072 +0900 *************** DATA(insert OID = 2232 ( pg_get_functio *** 2313,2318 **** --- 2313,2320 ---- DESCR("identity argument list of a function"); DATA(insert OID = 2165 ( pg_get_function_result PGNSP PGUID 12 1 0 0 f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_get_function_result _null_ _null_ _null_ )); DESCR("result type of a function"); + DATA(insert OID = 2179 ( pg_get_partitiondef PGNSP PGUID 12 1 0 0 f f f t f s 2 0 25 "26 16" _null_ _null_ _null_ _null_ pg_get_partitiondef _null_ _null_ _null_ )); + DESCR("definition of a partition"); DATA(insert OID = 1686 ( pg_get_keywords PGNSP PGUID 12 10 400 0 f f f t t s 0 0 2249 "" "{25,18,25}" "{o,o,o}" "{word,catcode,catdesc}" _null_ pg_get_keywords _null_ _null_ _null_ )); DESCR("list of SQL keywords"); diff -cprN head/src/include/commands/defrem.h work/src/include/commands/defrem.h *** head/src/include/commands/defrem.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/commands/defrem.h 2010-01-14 15:12:13.224121500 +0900 *************** extern char *ChooseIndexName(const char *** 50,55 **** --- 50,57 ---- bool primary, bool isconstraint); extern List *ChooseIndexColumnNames(List *indexElems); extern Oid GetDefaultOpClass(Oid type_id, Oid am_id); + extern Oid GetIndexOpClass(List *opclass, Oid attrType, + const char *accessMethodName, Oid accessMethodId); /* commands/functioncmds.c */ extern void CreateFunction(CreateFunctionStmt *stmt, const char *queryString); diff -cprN head/src/include/commands/tablecmds.h work/src/include/commands/tablecmds.h *** head/src/include/commands/tablecmds.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/commands/tablecmds.h 2010-01-14 15:12:13.224121500 +0900 *************** extern void RenameRelationInternal(Oid m *** 53,58 **** --- 53,60 ---- const char *newrelname, Oid namespaceId); + extern void CreatePartition(CreatePartitionStmt *stmt, const char *queryString); + extern void find_composite_type_dependencies(Oid typeOid, const char *origTblName, const char *origTypeName); diff -cprN head/src/include/nodes/nodes.h work/src/include/nodes/nodes.h *** head/src/include/nodes/nodes.h 2010-01-06 09:46:25.748744000 +0900 --- work/src/include/nodes/nodes.h 2010-01-14 15:12:13.225409881 +0900 *************** typedef enum NodeTag *** 347,352 **** --- 347,353 ---- T_AlterUserMappingStmt, T_DropUserMappingStmt, T_AlterTableSpaceOptionsStmt, + T_CreatePartitionStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *************** typedef enum NodeTag *** 385,390 **** --- 386,394 ---- T_XmlSerialize, T_WithClause, T_CommonTableExpr, + T_PartitionDef, + T_PartitionBy, + T_AddInherit, /* * TAGS FOR RANDOM OTHER STUFF diff -cprN head/src/include/nodes/parsenodes.h work/src/include/nodes/parsenodes.h *** head/src/include/nodes/parsenodes.h 2010-01-06 14:42:58.290140000 +0900 --- work/src/include/nodes/parsenodes.h 2010-01-14 15:12:13.225409881 +0900 *************** typedef enum AlterTableType *** 1135,1141 **** AT_EnableReplicaRule, /* ENABLE REPLICA RULE name */ AT_DisableRule, /* DISABLE RULE name */ AT_AddInherit, /* INHERIT parent */ ! AT_DropInherit /* NO INHERIT parent */ } AlterTableType; typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ --- 1135,1144 ---- AT_EnableReplicaRule, /* ENABLE REPLICA RULE name */ AT_DisableRule, /* DISABLE RULE name */ AT_AddInherit, /* INHERIT parent */ ! AT_DropInherit, /* NO INHERIT parent */ ! AT_PartitionBy, /* PARTITION BY / NO PARTITION */ ! AT_AttachPartition, /* ATTACH PARTITION */ ! AT_DetachPartition /* DETACH PARTITION */ } AlterTableType; typedef struct AlterTableCmd /* one subcommand of an ALTER TABLE */ *************** typedef struct VariableShowStmt *** 1337,1342 **** --- 1340,1379 ---- char *name; } VariableShowStmt; + /* ---------- + * Partitioning definitions + * ---------- + */ + + #define PARTITION_BY_RANGE 'r' + #define PARTITION_BY_LIST 'l' + + typedef struct PartitionDef + { + NodeTag type; + char kind; /* PARTITION_BY_xxx */ + RangeVar *name; /* name of partition */ + List *values; /* for RANGE and LIST */ + List *options; /* options from WITH clause */ + char *tablespacename; /* table space to use, or NULL */ + } PartitionDef; + + typedef struct PartitionBy + { + NodeTag type; + char kind; /* PARTITION_BY_xxx */ + Node *key; /* partition key expr */ + List *opclass; /* partition key operator class */ + List *defs; /* list of PartitionDef */ + } PartitionBy; + + typedef struct CreatePartitionStmt + { + NodeTag type; + RangeVar *parent; /* parent of the partition */ + PartitionDef *def; + } CreatePartitionStmt; + /* ---------------------- * Create Table Statement * *************** typedef struct CreateStmt *** 1359,1364 **** --- 1396,1402 ---- List *options; /* options from WITH clause */ OnCommitAction oncommit; /* what do we do at COMMIT? */ char *tablespacename; /* table space to use, or NULL */ + PartitionBy *partitions; /* partitioning definition, or NULL */ } CreateStmt; /* ---------- diff -cprN head/src/include/parser/kwlist.h work/src/include/parser/kwlist.h *** head/src/include/parser/kwlist.h 2010-01-06 14:42:58.290140000 +0900 --- work/src/include/parser/kwlist.h 2010-01-14 15:12:13.225409881 +0900 *************** PG_KEYWORD("assertion", ASSERTION, UNRES *** 49,54 **** --- 49,55 ---- PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD) PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD) PG_KEYWORD("at", AT, UNRESERVED_KEYWORD) + PG_KEYWORD("attach", ATTACH, UNRESERVED_KEYWORD) PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD) PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD) *************** PG_KEYWORD("delete", DELETE_P, UNRESERVE *** 125,130 **** --- 126,132 ---- PG_KEYWORD("delimiter", DELIMITER, UNRESERVED_KEYWORD) PG_KEYWORD("delimiters", DELIMITERS, UNRESERVED_KEYWORD) PG_KEYWORD("desc", DESC, RESERVED_KEYWORD) + PG_KEYWORD("detach", DETACH, UNRESERVED_KEYWORD) PG_KEYWORD("dictionary", DICTIONARY, UNRESERVED_KEYWORD) PG_KEYWORD("disable", DISABLE_P, UNRESERVED_KEYWORD) PG_KEYWORD("discard", DISCARD, UNRESERVED_KEYWORD) *************** PG_KEYWORD("lc_ctype", LC_CTYPE_P, UNRES *** 217,225 **** --- 219,229 ---- PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD) PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD) PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD) + PG_KEYWORD("less", LESS, UNRESERVED_KEYWORD) PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD) PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD) + PG_KEYWORD("list", LIST, UNRESERVED_KEYWORD) PG_KEYWORD("listen", LISTEN, UNRESERVED_KEYWORD) PG_KEYWORD("load", LOAD, UNRESERVED_KEYWORD) PG_KEYWORD("local", LOCAL, UNRESERVED_KEYWORD) *************** PG_KEYWORD("temp", TEMP, UNRESERVED_KEYW *** 365,370 **** --- 369,375 ---- PG_KEYWORD("template", TEMPLATE, UNRESERVED_KEYWORD) PG_KEYWORD("temporary", TEMPORARY, UNRESERVED_KEYWORD) PG_KEYWORD("text", TEXT_P, UNRESERVED_KEYWORD) + PG_KEYWORD("than", THAN, UNRESERVED_KEYWORD) PG_KEYWORD("then", THEN, RESERVED_KEYWORD) PG_KEYWORD("time", TIME, COL_NAME_KEYWORD) PG_KEYWORD("timestamp", TIMESTAMP, COL_NAME_KEYWORD) diff -cprN head/src/include/parser/parse_utilcmd.h work/src/include/parser/parse_utilcmd.h *** head/src/include/parser/parse_utilcmd.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/parser/parse_utilcmd.h 2010-01-14 15:12:13.226474907 +0900 *************** extern IndexStmt *transformIndexStmt(Ind *** 24,28 **** --- 24,29 ---- extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt); + extern List *transformCreatePartition(PartitionDef *def, RangeVar *parent); #endif /* PARSE_UTILCMD_H */ diff -cprN head/src/include/utils/builtins.h work/src/include/utils/builtins.h *** head/src/include/utils/builtins.h 2010-01-04 09:10:26.638773000 +0900 --- work/src/include/utils/builtins.h 2010-01-14 15:12:13.227412036 +0900 *************** extern Datum pg_get_functiondef(PG_FUNCT *** 601,606 **** --- 601,607 ---- extern Datum pg_get_function_arguments(PG_FUNCTION_ARGS); extern Datum pg_get_function_identity_arguments(PG_FUNCTION_ARGS); extern Datum pg_get_function_result(PG_FUNCTION_ARGS); + extern Datum pg_get_partitiondef(PG_FUNCTION_ARGS); extern char *deparse_expression(Node *expr, List *dpcontext, bool forceprefix, bool showimplicit); extern List *deparse_context_for(const char *aliasname, Oid relid); diff -cprN head/src/include/utils/lsyscache.h work/src/include/utils/lsyscache.h *** head/src/include/utils/lsyscache.h 2010-01-05 09:17:58.505600000 +0900 --- work/src/include/utils/lsyscache.h 2010-01-14 15:12:13.228424734 +0900 *************** extern Oid get_opclass_family(Oid opclas *** 66,71 **** --- 66,72 ---- extern Oid get_opclass_input_type(Oid opclass); extern RegProcedure get_opcode(Oid opno); extern char *get_opname(Oid opno); + extern List *get_opfullname(Oid opno); extern void op_input_types(Oid opno, Oid *lefttype, Oid *righttype); extern bool op_mergejoinable(Oid opno); extern bool op_hashjoinable(Oid opno); diff -cprN head/src/include/utils/syscache.h work/src/include/utils/syscache.h *** head/src/include/utils/syscache.h 2010-01-06 09:46:25.748744000 +0900 --- work/src/include/utils/syscache.h 2010-01-14 15:12:13.228424734 +0900 *************** enum SysCacheIdentifier *** 84,90 **** TYPENAMENSP, TYPEOID, USERMAPPINGOID, ! USERMAPPINGUSERSERVER }; extern void InitCatalogCache(void); --- 84,91 ---- TYPENAMENSP, TYPEOID, USERMAPPINGOID, ! USERMAPPINGUSERSERVER, ! PARTITIONKEY }; extern void InitCatalogCache(void); diff -cprN head/src/test/regress/expected/partition.out work/src/test/regress/expected/partition.out *** head/src/test/regress/expected/partition.out 1970-01-01 09:00:00.000000000 +0900 --- work/src/test/regress/expected/partition.out 2010-01-14 17:58:17.375101000 +0900 *************** *** 0 **** --- 1,458 ---- + -- + -- RANGE PARTITION with timestamp + -- + CREATE TABLE sales_range ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ) + PARTITION BY RANGE (sales_date) + ( + PARTITION sales_2006 VALUES LESS THAN ('2007-01-01'), + PARTITION sales_2007 VALUES LESS THAN ('2008-01-01'), + PARTITION sales_2008 VALUES LESS THAN ('2009-01-01'), + PARTITION sales_max VALUES LESS THAN (MAXVALUE) + ); + \d+ sales_range + Table "public.sales_range" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Partitions: RANGE (sales_date) + ( + PARTITION sales_2006 VALUES LESS THAN 'Mon Jan 01 00:00:00 2007', + PARTITION sales_2007 VALUES LESS THAN 'Tue Jan 01 00:00:00 2008', + PARTITION sales_2008 VALUES LESS THAN 'Thu Jan 01 00:00:00 2009', + PARTITION sales_max VALUES LESS THAN MAXVALUE + ) + Has OIDs: no + + \d+ sales_2006 + Table "public.sales_2006" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_2006_sales_date_check" CHECK (sales_date < 'Mon Jan 01 00:00:00 2007'::timestamp without time zone) + Inherits: sales_range + Has OIDs: no + + \d+ sales_2007 + Table "public.sales_2007" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_2007_sales_date_check" CHECK ('Mon Jan 01 00:00:00 2007'::timestamp without time zone <= sales_date AND sales_date < 'Tue Jan 01 00:00:00 2008'::timestamp without time zone) + Inherits: sales_range + Has OIDs: no + + \d+ sales_2008 + Table "public.sales_2008" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_2008_sales_date_check" CHECK ('Tue Jan 01 00:00:00 2008'::timestamp without time zone <= sales_date AND sales_date < 'Thu Jan 01 00:00:00 2009'::timestamp without time zone) + Inherits: sales_range + Has OIDs: no + + \d+ sales_max + Table "public.sales_max" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_max_sales_date_check" CHECK ('Thu Jan 01 00:00:00 2009'::timestamp without time zone <= sales_date) + Inherits: sales_range + Has OIDs: no + + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_range'::regclass ORDER BY 1; + inhrelid | inhseqno | inhvalues + ------------+----------+------------------------------ + sales_2006 | 1 | {"Mon Jan 01 00:00:00 2007"} + sales_2007 | 1 | {"Tue Jan 01 00:00:00 2008"} + sales_2008 | 1 | {"Thu Jan 01 00:00:00 2009"} + sales_max | 1 | {} + (4 rows) + + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + partrelid | partkind | pg_get_expr + -------------+----------+------------- + sales_range | r | sales_date + (1 row) + + DROP TABLE sales_range CASCADE; + NOTICE: drop cascades to 4 other objects + DETAIL: drop cascades to table sales_2006 + drop cascades to table sales_2007 + drop cascades to table sales_2008 + drop cascades to table sales_max + -- + -- CREATE TABLE + ALTER TABLE PARTITION BY RANGE + ATTACH PARTITION + -- + CREATE TABLE sales_range ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ); + ALTER TABLE sales_range PARTITION BY RANGE (sales_date) timestamp_ops + ( + PARTITION sales_2006 VALUES LESS THAN '2007-01-01', + PARTITION sales_2007 VALUES LESS THAN '2008-01-01' + ); + CREATE TABLE sales_2008 (LIKE sales_range INCLUDING ALL); + ALTER TABLE sales_range ATTACH PARTITION sales_2008 VALUES LESS THAN '2009-01-01'; + -- ERROR: cannot split partition "sales_2008" + CREATE PARTITION sales_error ON sales_range VALUES LESS THAN '2008-06-06'; + ERROR: cannot split partition "sales_2008" + CREATE TABLE sales_max (LIKE sales_range INCLUDING ALL); + ALTER TABLE sales_range ATTACH PARTITION sales_max VALUES LESS THAN MAXVALUE; + \d+ sales_range + Table "public.sales_range" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Partitions: RANGE (sales_date) + ( + PARTITION sales_2006 VALUES LESS THAN 'Mon Jan 01 00:00:00 2007', + PARTITION sales_2007 VALUES LESS THAN 'Tue Jan 01 00:00:00 2008', + PARTITION sales_2008 VALUES LESS THAN 'Thu Jan 01 00:00:00 2009', + PARTITION sales_max VALUES LESS THAN MAXVALUE + ) + Has OIDs: no + + \d+ sales_2006 + Table "public.sales_2006" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_2006_sales_date_check" CHECK (sales_date < 'Mon Jan 01 00:00:00 2007'::timestamp without time zone) + Inherits: sales_range + Has OIDs: no + + \d+ sales_2007 + Table "public.sales_2007" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_2007_sales_date_check" CHECK ('Mon Jan 01 00:00:00 2007'::timestamp without time zone <= sales_date AND sales_date < 'Tue Jan 01 00:00:00 2008'::timestamp without time zone) + Inherits: sales_range + Has OIDs: no + + \d+ sales_2008 + Table "public.sales_2008" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_2008_sales_date_check" CHECK ('Tue Jan 01 00:00:00 2008'::timestamp without time zone <= sales_date AND sales_date < 'Thu Jan 01 00:00:00 2009'::timestamp without time zone) + Inherits: sales_range + Has OIDs: no + + \d+ sales_max + Table "public.sales_max" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_max_sales_date_check" CHECK ('Thu Jan 01 00:00:00 2009'::timestamp without time zone <= sales_date) + Inherits: sales_range + Has OIDs: no + + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_range'::regclass ORDER BY 1; + inhrelid | inhseqno | inhvalues + ------------+----------+------------------------------ + sales_2006 | 1 | {"Mon Jan 01 00:00:00 2007"} + sales_2007 | 1 | {"Tue Jan 01 00:00:00 2008"} + sales_2008 | 1 | {"Thu Jan 01 00:00:00 2009"} + sales_max | 1 | {} + (4 rows) + + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + partrelid | partkind | pg_get_expr + -------------+----------+------------- + sales_range | r | sales_date + (1 row) + + -- ERROR: cannot add partition to "sales_range", that has an overflow partition + CREATE PARTITION sales_error ON sales_range VALUES LESS THAN '2008-06-06'; + ERROR: cannot add partition to "sales_range", that has an overflow partition + -- ERROR: multiple partition keys for table "sales_range" are not allowed + ALTER TABLE sales_range PARTITION BY RANGE (salesman_id); + ERROR: multiple partition keys for table "sales_range" are not allowed + -- ERROR: cannot add LIST partition "sales_error" to RANGE partitioned table "sales_range" + CREATE PARTITION sales_error ON sales_range VALUES IN ('list'); + ERROR: cannot add LIST partition "sales_error" to RANGE partitioned table "sales_range" + -- NO PARTITION will drop all inhvalues. + ALTER TABLE sales_range NO PARTITION; + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_range'::regclass ORDER BY 1; + inhrelid | inhseqno | inhvalues + ------------+----------+----------- + sales_2006 | 1 | + sales_2007 | 1 | + sales_2008 | 1 | + sales_max | 1 | + (4 rows) + + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + partrelid | partkind | pg_get_expr + -----------+----------+------------- + (0 rows) + + DROP TABLE sales_range CASCADE; + NOTICE: drop cascades to 4 other objects + DETAIL: drop cascades to table sales_2006 + drop cascades to table sales_2007 + drop cascades to table sales_2008 + drop cascades to table sales_max + -- + -- 1000 RANGE PARTITIONS + -- + CREATE TABLE sales_range ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ); + ALTER TABLE sales_range PARTITION BY RANGE (sales_date); + DO + $$ + BEGIN + FOR i IN 1..1000 LOOP + EXECUTE 'CREATE PARTITION sales_' || i + || ' ON sales_range VALUES LESS THAN ' + || quote_literal('2000-01-01'::timestamp + i * '1 day'::interval); + END LOOP; + END; + $$; + CREATE PARTITION sales_max ON sales_range VALUES LESS THAN MAXVALUE; + SET client_min_messages = WARNING; + DROP TABLE sales_range CASCADE; + RESET client_min_messages; + -- + -- LIST PARTITION with varchar + -- + CREATE TABLE sales_list ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ) + PARTITION BY LIST (sales_state) varchar_ops + ( + PARTITION sales_asia VALUES ('asia'), + PARTITION sales_euro VALUES ('eu'), + PARTITION sales_us VALUES ('usa', 'canada'), + PARTITION sales_other VALUES (DEFAULT) + ); + \d+ sales_list + Table "public.sales_list" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Partitions: LIST (sales_state) varchar_ops + ( + PARTITION sales_asia VALUES ('asia'), + PARTITION sales_euro VALUES ('eu'), + PARTITION sales_us VALUES ('usa', 'canada'), + PARTITION sales_other VALUES (DEFAULT) + ) + Has OIDs: no + + \d+ sales_asia + Table "public.sales_asia" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_asia_sales_state_check" CHECK (sales_state::text = 'asia'::text) + Inherits: sales_list + Has OIDs: no + + \d+ sales_euro + Table "public.sales_euro" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_euro_sales_state_check" CHECK (sales_state::text = 'eu'::text) + Inherits: sales_list + Has OIDs: no + + \d+ sales_us + Table "public.sales_us" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_us_sales_state_check" CHECK (sales_state::text = ANY (ARRAY['usa'::character varying, 'canada'::character varying]::text[])) + Inherits: sales_list + Has OIDs: no + + \d+ sales_other + Table "public.sales_other" + Column | Type | Modifiers | Storage | Description + ---------------+-----------------------------+-----------+----------+------------- + salesman_id | numeric(5,0) | | main | + salesman_name | character varying(30) | | extended | + sales_state | character varying(20) | | extended | + sales_date | timestamp without time zone | | plain | + Check constraints: + "sales_other_sales_state_check" CHECK (NOT (sales_state::text = ANY (ARRAY['asia'::character varying, 'eu'::character varying, 'usa'::character varying, 'canada'::character varying]::text[]))) + Inherits: sales_list + Has OIDs: no + + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_list'::regclass ORDER BY 1; + inhrelid | inhseqno | inhvalues + -------------+----------+-------------- + sales_asia | 1 | {asia} + sales_euro | 1 | {eu} + sales_us | 1 | {usa,canada} + sales_other | 1 | {} + (4 rows) + + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + partrelid | partkind | pg_get_expr + ------------+----------+------------- + sales_list | l | sales_state + (1 row) + + DROP TABLE sales_list CASCADE; + NOTICE: drop cascades to 4 other objects + DETAIL: drop cascades to table sales_asia + drop cascades to table sales_euro + drop cascades to table sales_us + drop cascades to table sales_other + -- + -- 1000 LIST PARTITIONS + -- + CREATE TABLE sales_list ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ); + ALTER TABLE sales_list PARTITION BY LIST (lower(sales_state)) text_ops; + DO + $$ + BEGIN + FOR i IN 1..1000 LOOP + EXECUTE 'CREATE PARTITION sales_' || i + || ' ON sales_list VALUES (' || quote_literal(i) || ')'; + END LOOP; + END; + $$; + -- ERROR: partition values overlapped with "sales_123" + CREATE PARTITION sales_error ON sales_list VALUES ('1001', '123'); + ERROR: partition values overlapped with "sales_123" + CREATE PARTITION sales_other ON sales_list VALUES DEFAULT; + SET client_min_messages = WARNING; + DROP TABLE sales_list CASCADE; + RESET client_min_messages; + -- + -- LIST PARTITION with boolean + -- + CREATE TABLE answers ( + id integer, + answer boolean + ) + PARTITION BY LIST (answer) + ( + PARTITION ans_yes VALUES (true), + PARTITION ans_no VALUES (false) + ); + -- ERROR: partition key must not be NULL + CREATE PARTITION ans_unknown ON answers VALUES (NULL); + ERROR: partition key must not be NULL + CREATE PARTITION ans_unknown ON answers VALUES (DEFAULT); + \d+ answers + Table "public.answers" + Column | Type | Modifiers | Storage | Description + --------+---------+-----------+---------+------------- + id | integer | | plain | + answer | boolean | | plain | + Partitions: LIST (answer) + ( + PARTITION ans_no VALUES ('f'), + PARTITION ans_yes VALUES ('t'), + PARTITION ans_unknown VALUES (DEFAULT) + ) + Has OIDs: no + + -- INSERTs with conditional triggers + CREATE FUNCTION insert_into() RETURNS trigger AS + $$ + BEGIN + EXECUTE 'INSERT INTO ' || quote_ident(TG_ARGV[0]) || ' VALUES($1.*)' USING NEW; + RETURN NULL; + END; + $$ + LANGUAGE plpgsql; + CREATE TRIGGER insert_yes BEFORE INSERT ON answers FOR EACH ROW + WHEN (NEW.answer) EXECUTE PROCEDURE insert_into('ans_yes'); + CREATE TRIGGER insert_no BEFORE INSERT ON answers FOR EACH ROW + WHEN (NOT NEW.answer) EXECUTE PROCEDURE insert_into('ans_no'); + CREATE TRIGGER insert_unknown BEFORE INSERT ON answers FOR EACH ROW + WHEN (NEW.answer IS NULL) EXECUTE PROCEDURE insert_into('ans_unknown'); + INSERT INTO answers VALUES(1, true), (2, false), (3, NULL); + SELECT tableoid::regclass, * FROM answers ORDER BY id; + tableoid | id | answer + -------------+----+-------- + ans_yes | 1 | t + ans_no | 2 | f + ans_unknown | 3 | + (3 rows) + + DROP TABLE answers CASCADE; + NOTICE: drop cascades to 3 other objects + DETAIL: drop cascades to table ans_yes + drop cascades to table ans_no + drop cascades to table ans_unknown + DROP FUNCTION insert_into(); diff -cprN head/src/test/regress/expected/sanity_check.out work/src/test/regress/expected/sanity_check.out *** head/src/test/regress/expected/sanity_check.out 2009-12-11 12:39:49.829461000 +0900 --- work/src/test/regress/expected/sanity_check.out 2010-01-14 15:12:13.229507210 +0900 *************** SELECT relname, relhasindex *** 112,117 **** --- 112,118 ---- pg_opclass | t pg_operator | t pg_opfamily | t + pg_partition | t pg_pltemplate | t pg_proc | t pg_rewrite | t *************** SELECT relname, relhasindex *** 154,160 **** timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (143 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 155,161 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (144 rows) -- -- another sanity check: every system catalog that has OIDs should have diff -cprN head/src/test/regress/parallel_schedule work/src/test/regress/parallel_schedule *** head/src/test/regress/parallel_schedule 2009-08-25 09:55:55.804564000 +0900 --- work/src/test/regress/parallel_schedule 2010-01-14 15:12:13.229507210 +0900 *************** test: copy copyselect *** 52,58 **** # ---------- # Another group of parallel tests # ---------- ! test: constraints triggers create_misc create_aggregate create_operator inherit vacuum drop_if_exists create_cast # Depends on the above test: create_index create_view --- 52,58 ---- # ---------- # Another group of parallel tests # ---------- ! test: constraints triggers create_misc create_aggregate create_operator inherit partition vacuum drop_if_exists create_cast # Depends on the above test: create_index create_view diff -cprN head/src/test/regress/serial_schedule work/src/test/regress/serial_schedule *** head/src/test/regress/serial_schedule 2009-08-25 09:55:55.804564000 +0900 --- work/src/test/regress/serial_schedule 2010-01-14 15:12:13.229507210 +0900 *************** test: create_operator *** 60,65 **** --- 60,66 ---- test: create_index test: drop_if_exists test: inherit + test: partition test: vacuum test: create_view test: sanity_check diff -cprN head/src/test/regress/sql/partition.sql work/src/test/regress/sql/partition.sql *** head/src/test/regress/sql/partition.sql 1970-01-01 09:00:00.000000000 +0900 --- work/src/test/regress/sql/partition.sql 2010-01-14 17:55:02.639624000 +0900 *************** *** 0 **** --- 1,188 ---- + -- + -- RANGE PARTITION with timestamp + -- + CREATE TABLE sales_range ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ) + PARTITION BY RANGE (sales_date) + ( + PARTITION sales_2006 VALUES LESS THAN ('2007-01-01'), + PARTITION sales_2007 VALUES LESS THAN ('2008-01-01'), + PARTITION sales_2008 VALUES LESS THAN ('2009-01-01'), + PARTITION sales_max VALUES LESS THAN (MAXVALUE) + ); + \d+ sales_range + \d+ sales_2006 + \d+ sales_2007 + \d+ sales_2008 + \d+ sales_max + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_range'::regclass ORDER BY 1; + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + DROP TABLE sales_range CASCADE; + + -- + -- CREATE TABLE + ALTER TABLE PARTITION BY RANGE + ATTACH PARTITION + -- + CREATE TABLE sales_range ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ); + ALTER TABLE sales_range PARTITION BY RANGE (sales_date) timestamp_ops + ( + PARTITION sales_2006 VALUES LESS THAN '2007-01-01', + PARTITION sales_2007 VALUES LESS THAN '2008-01-01' + ); + CREATE TABLE sales_2008 (LIKE sales_range INCLUDING ALL); + ALTER TABLE sales_range ATTACH PARTITION sales_2008 VALUES LESS THAN '2009-01-01'; + + -- ERROR: cannot split partition "sales_2008" + CREATE PARTITION sales_error ON sales_range VALUES LESS THAN '2008-06-06'; + + CREATE TABLE sales_max (LIKE sales_range INCLUDING ALL); + ALTER TABLE sales_range ATTACH PARTITION sales_max VALUES LESS THAN MAXVALUE; + + \d+ sales_range + \d+ sales_2006 + \d+ sales_2007 + \d+ sales_2008 + \d+ sales_max + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_range'::regclass ORDER BY 1; + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + + -- ERROR: cannot add partition to "sales_range", that has an overflow partition + CREATE PARTITION sales_error ON sales_range VALUES LESS THAN '2008-06-06'; + -- ERROR: multiple partition keys for table "sales_range" are not allowed + ALTER TABLE sales_range PARTITION BY RANGE (salesman_id); + -- ERROR: cannot add LIST partition "sales_error" to RANGE partitioned table "sales_range" + CREATE PARTITION sales_error ON sales_range VALUES IN ('list'); + + -- NO PARTITION will drop all inhvalues. + ALTER TABLE sales_range NO PARTITION; + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_range'::regclass ORDER BY 1; + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + DROP TABLE sales_range CASCADE; + + -- + -- 1000 RANGE PARTITIONS + -- + CREATE TABLE sales_range ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ); + ALTER TABLE sales_range PARTITION BY RANGE (sales_date); + DO + $$ + BEGIN + FOR i IN 1..1000 LOOP + EXECUTE 'CREATE PARTITION sales_' || i + || ' ON sales_range VALUES LESS THAN ' + || quote_literal('2000-01-01'::timestamp + i * '1 day'::interval); + END LOOP; + END; + $$; + CREATE PARTITION sales_max ON sales_range VALUES LESS THAN MAXVALUE; + SET client_min_messages = WARNING; + DROP TABLE sales_range CASCADE; + RESET client_min_messages; + + -- + -- LIST PARTITION with varchar + -- + CREATE TABLE sales_list ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ) + PARTITION BY LIST (sales_state) varchar_ops + ( + PARTITION sales_asia VALUES ('asia'), + PARTITION sales_euro VALUES ('eu'), + PARTITION sales_us VALUES ('usa', 'canada'), + PARTITION sales_other VALUES (DEFAULT) + ); + \d+ sales_list + \d+ sales_asia + \d+ sales_euro + \d+ sales_us + \d+ sales_other + SELECT inhrelid::regclass, inhseqno, inhvalues FROM pg_inherits WHERE inhparent = 'sales_list'::regclass ORDER BY 1; + SELECT partrelid::regclass, partkind, pg_get_expr(partkey, partrelid) FROM pg_partition; + DROP TABLE sales_list CASCADE; + + -- + -- 1000 LIST PARTITIONS + -- + CREATE TABLE sales_list ( + salesman_id numeric(5), + salesman_name varchar(30), + sales_state varchar(20), + sales_date timestamp + ); + ALTER TABLE sales_list PARTITION BY LIST (lower(sales_state)) text_ops; + DO + $$ + BEGIN + FOR i IN 1..1000 LOOP + EXECUTE 'CREATE PARTITION sales_' || i + || ' ON sales_list VALUES (' || quote_literal(i) || ')'; + END LOOP; + END; + $$; + + -- ERROR: partition values overlapped with "sales_123" + CREATE PARTITION sales_error ON sales_list VALUES ('1001', '123'); + + CREATE PARTITION sales_other ON sales_list VALUES DEFAULT; + SET client_min_messages = WARNING; + DROP TABLE sales_list CASCADE; + RESET client_min_messages; + + -- + -- LIST PARTITION with boolean + -- + CREATE TABLE answers ( + id integer, + answer boolean + ) + PARTITION BY LIST (answer) + ( + PARTITION ans_yes VALUES (true), + PARTITION ans_no VALUES (false) + ); + + -- ERROR: partition key must not be NULL + CREATE PARTITION ans_unknown ON answers VALUES (NULL); + + CREATE PARTITION ans_unknown ON answers VALUES (DEFAULT); + \d+ answers + + -- INSERTs with conditional triggers + CREATE FUNCTION insert_into() RETURNS trigger AS + $$ + BEGIN + EXECUTE 'INSERT INTO ' || quote_ident(TG_ARGV[0]) || ' VALUES($1.*)' USING NEW; + RETURN NULL; + END; + $$ + LANGUAGE plpgsql; + + CREATE TRIGGER insert_yes BEFORE INSERT ON answers FOR EACH ROW + WHEN (NEW.answer) EXECUTE PROCEDURE insert_into('ans_yes'); + CREATE TRIGGER insert_no BEFORE INSERT ON answers FOR EACH ROW + WHEN (NOT NEW.answer) EXECUTE PROCEDURE insert_into('ans_no'); + CREATE TRIGGER insert_unknown BEFORE INSERT ON answers FOR EACH ROW + WHEN (NEW.answer IS NULL) EXECUTE PROCEDURE insert_into('ans_unknown'); + + INSERT INTO answers VALUES(1, true), (2, false), (3, NULL); + SELECT tableoid::regclass, * FROM answers ORDER BY id; + + DROP TABLE answers CASCADE; + DROP FUNCTION insert_into();