From f04d4842d22e18b79a306fa3693bad02106cde2b Mon Sep 17 00:00:00 2001
From: Petr Jelinek <pjmodos@pjmodos.net>
Date: Wed, 7 Jan 2015 23:51:44 +0100
Subject: [PATCH 3/3] tablesample-ddl v4

---
 doc/src/sgml/ref/allfiles.sgml                     |   2 +
 doc/src/sgml/ref/create_tablesamplemethod.sgml     | 173 +++++++++
 doc/src/sgml/ref/drop_tablesamplemethod.sgml       |  87 +++++
 doc/src/sgml/reference.sgml                        |   2 +
 src/backend/catalog/dependency.c                   |  15 +-
 src/backend/catalog/objectaddress.c                |  65 +++-
 src/backend/commands/Makefile                      |   6 +-
 src/backend/commands/dropcmds.c                    |   4 +
 src/backend/commands/event_trigger.c               |   3 +
 src/backend/commands/tablecmds.c                   |   1 +
 src/backend/commands/tablesample.c                 | 422 +++++++++++++++++++++
 src/backend/parser/gram.y                          |  14 +-
 src/backend/tcop/utility.c                         |  12 +
 src/bin/pg_dump/common.c                           |   5 +
 src/bin/pg_dump/pg_dump.c                          | 171 +++++++++
 src/bin/pg_dump/pg_dump.h                          |  10 +-
 src/bin/pg_dump/pg_dump_sort.c                     |  11 +-
 src/include/catalog/dependency.h                   |   1 +
 src/include/catalog/pg_tablesample_method.h        |  11 +
 src/include/nodes/parsenodes.h                     |   1 +
 src/include/parser/kwlist.h                        |   1 +
 src/test/modules/Makefile                          |   3 +-
 src/test/modules/tablesample/.gitignore            |   4 +
 src/test/modules/tablesample/Makefile              |  21 +
 .../modules/tablesample/expected/tablesample.out   |  38 ++
 src/test/modules/tablesample/sql/tablesample.sql   |  14 +
 src/test/modules/tablesample/tsm_test--1.0.sql     |  51 +++
 src/test/modules/tablesample/tsm_test.c            | 228 +++++++++++
 src/test/modules/tablesample/tsm_test.control      |   5 +
 29 files changed, 1370 insertions(+), 11 deletions(-)
 create mode 100644 doc/src/sgml/ref/create_tablesamplemethod.sgml
 create mode 100644 doc/src/sgml/ref/drop_tablesamplemethod.sgml
 create mode 100644 src/backend/commands/tablesample.c
 create mode 100644 src/test/modules/tablesample/.gitignore
 create mode 100644 src/test/modules/tablesample/Makefile
 create mode 100644 src/test/modules/tablesample/expected/tablesample.out
 create mode 100644 src/test/modules/tablesample/sql/tablesample.sql
 create mode 100644 src/test/modules/tablesample/tsm_test--1.0.sql
 create mode 100644 src/test/modules/tablesample/tsm_test.c
 create mode 100644 src/test/modules/tablesample/tsm_test.control

diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 7aa3128..2fad084 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -78,6 +78,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY createServer       SYSTEM "create_server.sgml">
 <!ENTITY createTable        SYSTEM "create_table.sgml">
 <!ENTITY createTableAs      SYSTEM "create_table_as.sgml">
+<!ENTITY createTablesampleMethod SYSTEM "create_tablesamplemethod.sgml">
 <!ENTITY createTableSpace   SYSTEM "create_tablespace.sgml">
 <!ENTITY createTrigger      SYSTEM "create_trigger.sgml">
 <!ENTITY createTSConfig     SYSTEM "create_tsconfig.sgml">
@@ -119,6 +120,7 @@ Complete list of usable sgml source files in this directory.
 <!ENTITY dropSequence       SYSTEM "drop_sequence.sgml">
 <!ENTITY dropServer         SYSTEM "drop_server.sgml">
 <!ENTITY dropTable          SYSTEM "drop_table.sgml">
+<!ENTITY dropTablesampleMethod SYSTEM "drop_tablesamplemethod.sgml">
 <!ENTITY dropTableSpace     SYSTEM "drop_tablespace.sgml">
 <!ENTITY dropTrigger        SYSTEM "drop_trigger.sgml">
 <!ENTITY dropTSConfig       SYSTEM "drop_tsconfig.sgml">
diff --git a/doc/src/sgml/ref/create_tablesamplemethod.sgml b/doc/src/sgml/ref/create_tablesamplemethod.sgml
new file mode 100644
index 0000000..62a8ce4
--- /dev/null
+++ b/doc/src/sgml/ref/create_tablesamplemethod.sgml
@@ -0,0 +1,173 @@
+<!--
+doc/src/sgml/ref/create_tablesamplemethod.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-CREATETABLESAMPLEMETHOD">
+ <indexterm zone="sql-createtablesamplemethod">
+  <primary>CREATE TABLESAMPLE METHOD</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>CREATE TABLESAMPLE METHOD</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>CREATE TABLESAMPLE METHOD</refname>
+  <refpurpose>define custom tablesample method</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+CREATE TABLESAMPLE METHOD <replaceable class="parameter">name</replaceable> (
+    INIT = <replaceable class="parameter">init_function</replaceable> ,
+    NEXTBLOCK = <replaceable class="parameter">nextblock_function</replaceable> ,
+    NEXTTUPLE = <replaceable class="parameter">nexttuple_function</replaceable> ,
+    END = <replaceable class="parameter">end_function</replaceable> ,
+    RESET = <replaceable class="parameter">reset_function</replaceable> ,
+    COST = <replaceable class="parameter">cost_function</replaceable>
+    [ , EXAMINETUPLE = <replaceable class="parameter">examinetuple_function</replaceable> ]
+    [ , SEQSCAN = <replaceable class="parameter">seqscan</replaceable> ]
+)
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>CREATE TABLESAMPLE METHOD</command> creates a tablesample method.
+   A tablesample method provides alrorithm for reading sample part of a table
+   when used in <command>TABLESAMPLE</> clause of a <command>SELECT</>
+   statement.
+  </para>
+
+  <para>
+   You must be a superuser to use <command>CREATE TABLESAMPLE METHOD</command>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of the tablesample method to be created. This name must be
+      unique within the database.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">init_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the init function for the tablesample method.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">nextblock_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the get-next-block function for the tablesample method.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">nexttuple_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the get-next-tuple function for the tablesample method.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">end_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the end function for the tablesample method.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">reset_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the reset function for the tablesample method.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">cost_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the costing function for the tablesample method.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">examinetuple_function</replaceable></term>
+    <listitem>
+     <para>
+      The name of the function for inspecting the tuple contents in order
+      to make decision if it should be returned or not. This parameter
+      is optional.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">seqscan</replaceable></term>
+    <listitem>
+     <para>
+      True if the sampling method will do sequential scan of the whole table.
+      Used for cost estimation and syncscan. The default value if not specified
+      is False.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+
+  <para>
+   The function names can be schema-qualified if necessary.  Argument types
+   are not given, since the argument list for each type of function is
+   predetermined.  All functions are required.
+  </para>
+
+  <para>
+   The arguments can appear in any order, not only the one shown above.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no
+   <command>CREATE TABLESAMPLE METHOD</command> statement in the SQL
+   standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-droptablesamplemethod"></member>
+   <member><xref linkend="sql-select"></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/ref/drop_tablesamplemethod.sgml b/doc/src/sgml/ref/drop_tablesamplemethod.sgml
new file mode 100644
index 0000000..dffd2ec
--- /dev/null
+++ b/doc/src/sgml/ref/drop_tablesamplemethod.sgml
@@ -0,0 +1,87 @@
+<!--
+doc/src/sgml/ref/drop_tablesamplemethod.sgml
+PostgreSQL documentation
+-->
+
+<refentry id="SQL-DROPTABLESAMPLEMETHOD">
+ <indexterm zone="sql-droptablesamplemethod">
+  <primary>DROP TABLESAMPLE METHOD</primary>
+ </indexterm>
+
+ <refmeta>
+  <refentrytitle>DROP TABLESAMPLE METHOD</refentrytitle>
+  <manvolnum>7</manvolnum>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>DROP TABLESAMPLE METHOD</refname>
+  <refpurpose>remove a custom tablesample method</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+<synopsis>
+DROP TABLESAMPLE METHOD [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   <command>DROP TABLESAMPLE METHOD</command> drop an existing tablesample
+   method.
+  </para>
+
+  <para>
+   You must be a superuser to use <command>CREATE TABLESAMPLE METHOD</command>.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Parameters</title>
+
+  <variablelist>
+
+   <varlistentry>
+    <term><literal>IF EXISTS</literal></term>
+    <listitem>
+     <para>
+      Do not throw an error if the tablesample method does not exist.
+      A notice is issued in this case.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><replaceable class="parameter">name</replaceable></term>
+    <listitem>
+     <para>
+      The name of an existing tablesample method to be removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
+  </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   There is no
+   <command>DROP TABLESAMPLE METHOD</command> statement in the SQL
+   standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-createtablesamplemethod"></member>
+   <member><xref linkend="sql-select"></member>
+  </simplelist>
+ </refsect1>
+</refentry>
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 10c9a6d..2c09a3c 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -106,6 +106,7 @@
    &createServer;
    &createTable;
    &createTableAs;
+   &createTablesampleMethod;
    &createTableSpace;
    &createTSConfig;
    &createTSDictionary;
@@ -147,6 +148,7 @@
    &dropSequence;
    &dropServer;
    &dropTable;
+   &dropTablesampleMethod;
    &dropTableSpace;
    &dropTSConfig;
    &dropTSDictionary;
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index bacb242..6acb5b3 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_policy.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_tablesample_method.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -157,7 +158,8 @@ static const Oid object_classes[MAX_OCLASS] = {
 	DefaultAclRelationId,		/* OCLASS_DEFACL */
 	ExtensionRelationId,		/* OCLASS_EXTENSION */
 	EventTriggerRelationId,		/* OCLASS_EVENT_TRIGGER */
-	PolicyRelationId			/* OCLASS_POLICY */
+	PolicyRelationId,			/* OCLASS_POLICY */
+	TableSampleMethodRelationId			/* OCLASS_TABLESAMPLEMETHOD */
 };
 
 
@@ -1265,6 +1267,10 @@ doDeletion(const ObjectAddress *object, int flags)
 			RemovePolicyById(object->objectId);
 			break;
 
+		case OCLASS_TABLESAMPLEMETHOD:
+			RemoveTablesampleMethodById(object->objectId);
+			break;
+
 		default:
 			elog(ERROR, "unrecognized object class: %u",
 				 object->classId);
@@ -1794,6 +1800,10 @@ find_expr_references_walker(Node *node,
 				case RTE_RELATION:
 					add_object_address(OCLASS_CLASS, rte->relid, 0,
 									   context->addrs);
+					if (rte->tablesample)
+						add_object_address(OCLASS_TABLESAMPLEMETHOD,
+										   rte->tablesample->tsmid, 0,
+										   context->addrs);
 					break;
 				default:
 					break;
@@ -2373,6 +2383,9 @@ getObjectClass(const ObjectAddress *object)
 
 		case PolicyRelationId:
 			return OCLASS_POLICY;
+
+		case TableSampleMethodRelationId:
+			return OCLASS_TABLESAMPLEMETHOD;
 	}
 
 	/* shouldn't get here */
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 825d8b2..02edc0a 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_policy.h"
 #include "catalog/pg_rewrite.h"
+#include "catalog/pg_tablesample_method.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_ts_config.h"
@@ -429,7 +430,19 @@ static const ObjectPropertyType ObjectProperty[] =
 		Anum_pg_type_typacl,
 		ACL_KIND_TYPE,
 		true
-	}
+	},
+	{
+		TableSampleMethodRelationId,
+		TableSampleMethodOidIndexId,
+		TABLESAMPLEMETHODOID,
+		TABLESAMPLEMETHODNAME,
+		Anum_pg_tablesample_method_tsmname,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		InvalidAttrNumber,
+		-1,
+		true
+	},
 };
 
 /*
@@ -528,7 +541,9 @@ ObjectTypeMap[] =
 	/* OCLASS_EVENT_TRIGGER */
 	{ "event trigger", OBJECT_EVENT_TRIGGER },
 	/* OCLASS_POLICY */
-	{ "policy", OBJECT_POLICY }
+	{ "policy", OBJECT_POLICY },
+	/* OCLASS_TABLESAMPLEMETHOD */
+	{ "tablesample method", OBJECT_TABLESAMPLEMETHOD }
 };
 
 
@@ -670,6 +685,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
 			case OBJECT_FDW:
 			case OBJECT_FOREIGN_SERVER:
 			case OBJECT_EVENT_TRIGGER:
+			case OBJECT_TABLESAMPLEMETHOD:
 				address = get_object_address_unqualified(objtype,
 														 objname, missing_ok);
 				break;
@@ -896,6 +912,9 @@ get_object_address_unqualified(ObjectType objtype,
 			case OBJECT_EVENT_TRIGGER:
 				msg = gettext_noop("event trigger name cannot be qualified");
 				break;
+			case OBJECT_TABLESAMPLEMETHOD:
+				msg = gettext_noop("tablesample method name cannot be qualified");
+				break;
 			default:
 				elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 				msg = NULL;		/* placate compiler */
@@ -956,6 +975,11 @@ get_object_address_unqualified(ObjectType objtype,
 			address.objectId = get_event_trigger_oid(name, missing_ok);
 			address.objectSubId = 0;
 			break;
+		case OBJECT_TABLESAMPLEMETHOD:
+			address.classId = TableSampleMethodRelationId;
+			address.objectId = get_tablesample_method_oid(name, missing_ok);
+			address.objectSubId = 0;
+			break;
 		default:
 			elog(ERROR, "unrecognized objtype: %d", (int) objtype);
 			/* placate compiler, which doesn't know elog won't return */
@@ -1720,6 +1744,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
 			break;
 		case OBJECT_TSPARSER:
 		case OBJECT_TSTEMPLATE:
+		case OBJECT_TABLESAMPLEMETHOD:
 			/* We treat these object types as being owned by superusers */
 			if (!superuser_arg(roleid))
 				ereport(ERROR,
@@ -2654,6 +2679,21 @@ getObjectDescription(const ObjectAddress *object)
 				break;
 			}
 
+		case OCLASS_TABLESAMPLEMETHOD:
+			{
+				HeapTuple	tup;
+
+				tup = SearchSysCache1(TABLESAMPLEMETHODOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for tablesample method %u",
+						 object->objectId);
+				appendStringInfo(&buffer, _("tablesample method %s"),
+					 NameStr(((Form_pg_tablesample_method) GETSTRUCT(tup))->tsmname));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
@@ -3131,6 +3171,10 @@ getObjectTypeDescription(const ObjectAddress *object)
 			appendStringInfoString(&buffer, "policy");
 			break;
 
+		case OCLASS_TABLESAMPLEMETHOD:
+			appendStringInfoString(&buffer, "tablesample method");
+			break;
+
 		default:
 			appendStringInfo(&buffer, "unrecognized %u", object->classId);
 			break;
@@ -4025,6 +4069,23 @@ getObjectIdentityParts(const ObjectAddress *object,
 				break;
 			}
 
+		case OCLASS_TABLESAMPLEMETHOD:
+			{
+				HeapTuple	tup;
+				Form_pg_tablesample_method tsmForm;
+
+				tup = SearchSysCache1(TABLESAMPLEMETHODOID,
+									  ObjectIdGetDatum(object->objectId));
+				if (!HeapTupleIsValid(tup))
+					elog(ERROR, "cache lookup failed for tablesample method %u",
+						 object->objectId);
+				tsmForm = (Form_pg_tablesample_method) GETSTRUCT(tup);
+				appendStringInfoString(&buffer,
+							   quote_identifier(NameStr(tsmForm->tsmname)));
+				ReleaseSysCache(tup);
+				break;
+			}
+
 		default:
 			appendStringInfo(&buffer, "unrecognized object %u %u %d",
 							 object->classId,
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index b1ac704..04fcd8c 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,8 +18,8 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o  \
 	event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
 	indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
 	policy.o portalcmds.o prepare.o proclang.o \
-	schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
-	tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
-	variable.o view.o
+	schemacmds.o seclabel.o sequence.o tablecmds.o tablesample.o \
+	tablespace.o trigger.o tsearchcmds.o typecmds.o user.o vacuum.o \
+	vacuumlazy.o variable.o view.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index e5185ba..04d29a2 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -421,6 +421,10 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
 				args = strVal(linitial(objargs));
 			}
 			break;
+		case OBJECT_TABLESAMPLEMETHOD:
+			msg = gettext_noop("tablesample method \"%s\" does not exist, skipping");
+			name = NameListToString(objname);
+			break;
 		default:
 			elog(ERROR, "unexpected object type (%d)", (int) objtype);
 			break;
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index a33a5ad..f20e9f7 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -97,6 +97,7 @@ static event_trigger_support_data event_trigger_support[] = {
 	{"SEQUENCE", true},
 	{"SERVER", true},
 	{"TABLE", true},
+	{"TABLESAMPLE METHOD", true},
 	{"TABLESPACE", false},
 	{"TRIGGER", true},
 	{"TEXT SEARCH CONFIGURATION", true},
@@ -1078,6 +1079,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
 		case OBJECT_SEQUENCE:
 		case OBJECT_TABCONSTRAINT:
 		case OBJECT_TABLE:
+		case OBJECT_TABLESAMPLEMETHOD:
 		case OBJECT_TRIGGER:
 		case OBJECT_TSCONFIGURATION:
 		case OBJECT_TSDICTIONARY:
@@ -1134,6 +1136,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
 		case OCLASS_DEFACL:
 		case OCLASS_EXTENSION:
 		case OCLASS_POLICY:
+		case OCLASS_TABLESAMPLEMETHOD:
 			return true;
 
 		case MAX_OCLASS:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 66d5083..b67c560 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8059,6 +8059,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 			case OCLASS_USER_MAPPING:
 			case OCLASS_DEFACL:
 			case OCLASS_EXTENSION:
+			case OCLASS_TABLESAMPLEMETHOD:
 
 				/*
 				 * We don't expect any of these sorts of objects to depend on
diff --git a/src/backend/commands/tablesample.c b/src/backend/commands/tablesample.c
new file mode 100644
index 0000000..ed8102d
--- /dev/null
+++ b/src/backend/commands/tablesample.c
@@ -0,0 +1,422 @@
+/*-------------------------------------------------------------------------
+ *
+ * tablesample.c
+ *	  Commands to manipulate tablesample methods
+ *
+ * Table sampling methods provide algorithms for doing sample scan over
+ * the table.
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/commands/tablesample.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "access/heapam.h"
+#include "access/htup_details.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_tablesample_method.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "miscadmin.h"
+#include "parser/parse_func.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+
+static Datum
+get_tablesample_method_func(DefElem *defel, int attnum)
+{
+	List	   *funcName = defGetQualifiedName(defel);
+	/* Big enough size for our needs. */
+	Oid		   *typeId = palloc0(7 * sizeof(Oid));
+	Oid			retTypeId;
+	int			nargs;
+	Oid			procOid = InvalidOid;
+	FuncCandidateList clist;
+
+	switch (attnum)
+	{
+		case Anum_pg_tablesample_method_tsminit:
+			/*
+			 * tsminit needs special handling because it is defined as function
+			 * with 3 or more arguments and only first two arguments must have
+			 * specific type, the rest is up to the tablesample method creator.
+			 */
+			{
+				nargs = 2;
+				typeId[0] = INTERNALOID;
+				typeId[1] = INT4OID;
+				retTypeId = VOIDOID;
+
+				clist = FuncnameGetCandidates(funcName, -1, NIL, false, false, false);
+
+				while (clist)
+				{
+					if (clist->nargs >= 3 &&
+						memcmp(typeId, clist->args, nargs * sizeof(Oid)) == 0)
+					{
+						procOid = clist->oid;
+						/* Save real function signature for future errors. */
+						nargs = clist->nargs;
+						pfree(typeId);
+						typeId = clist->args;
+						break;
+					}
+					clist = clist->next;
+				}
+
+				if (!OidIsValid(procOid))
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_FUNCTION),
+							 errmsg("function \"%s\" does not exist or does not have valid signature",
+									NameListToString(funcName)),
+							 errhint("The tamplesample method init function "
+									 "must have at least 3 input parameters "
+									 "with first one of type INTERNAL and second of type INTEGER.")));
+			}
+			break;
+
+		case Anum_pg_tablesample_method_tsmnextblock:
+			nargs = 1;
+			typeId[0] = INTERNALOID;
+			retTypeId = INT4OID;
+			break;
+		case Anum_pg_tablesample_method_tsmnexttuple:
+			nargs = 3;
+			typeId[0] = INTERNALOID;
+			typeId[1] = INT4OID;
+			typeId[2] = INT2OID;
+			retTypeId = INT2OID;
+			break;
+		case Anum_pg_tablesample_method_tsmexaminetuple:
+			nargs = 4;
+			typeId[0] = INTERNALOID;
+			typeId[1] = INT4OID;
+			typeId[2] = INTERNALOID;
+			typeId[3] = BOOLOID;
+			retTypeId = BOOLOID;
+			break;
+		case Anum_pg_tablesample_method_tsmend:
+		case Anum_pg_tablesample_method_tsmreset:
+			nargs = 1;
+			typeId[0] = INTERNALOID;
+			retTypeId = VOIDOID;
+			break;
+		case Anum_pg_tablesample_method_tsmcost:
+			nargs = 7;
+			typeId[0] = INTERNALOID;
+			typeId[1] = INTERNALOID;
+			typeId[2] = INTERNALOID;
+			typeId[3] = INTERNALOID;
+			typeId[4] = INTERNALOID;
+			typeId[5] = INTERNALOID;
+			typeId[6] = INTERNALOID;
+			retTypeId = VOIDOID;
+			break;
+		default:
+			/* should not be here */
+			elog(ERROR, "unrecognized attribute for tablesample method: %d",
+				 attnum);
+			nargs = 0;			/* keep compiler quiet */
+	}
+
+	if (!OidIsValid(procOid))
+		procOid = LookupFuncName(funcName, nargs, typeId, false);
+	if (get_func_rettype(procOid) != retTypeId)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("function %s should return type %s",
+						func_signature_string(funcName, nargs, NIL, typeId),
+						format_type_be(retTypeId))));
+
+	return ObjectIdGetDatum(procOid);
+}
+
+/*
+ * make pg_depend entries for a new pg_tablesample_method entry
+ */
+static void
+makeTablesampleMethodDeps(HeapTuple tuple)
+{
+	Form_pg_tablesample_method tsm = (Form_pg_tablesample_method) GETSTRUCT(tuple);
+	ObjectAddress	myself,
+					referenced;
+
+	myself.classId = TableSampleMethodRelationId;
+	myself.objectId = HeapTupleGetOid(tuple);
+	myself.objectSubId = 0;
+
+	/* dependency on extension */
+	recordDependencyOnCurrentExtension(&myself, false);
+
+	/* dependencies on functions */
+	referenced.classId = ProcedureRelationId;
+	referenced.objectSubId = 0;
+
+	referenced.objectId = tsm->tsminit;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	referenced.objectId = tsm->tsmnextblock;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	referenced.objectId = tsm->tsmnexttuple;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	if (OidIsValid(tsm->tsmexaminetuple))
+	{
+		referenced.objectId = tsm->tsmexaminetuple;
+		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+	}
+
+	referenced.objectId = tsm->tsmend;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	referenced.objectId = tsm->tsmreset;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+	referenced.objectId = tsm->tsmcost;
+	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+}
+
+/*
+ * Create a table sampling method
+ *
+ * Only superusers can create a table sampling methods.
+ */
+Oid
+DefineTablesampleMethod(List *names, List *parameters)
+{
+	char	   *tsmname = strVal(linitial(names));
+	Oid			tsmoid;
+	ListCell   *pl;
+	Relation	rel;
+	Datum		values[Natts_pg_tablesample_method];
+	bool		nulls[Natts_pg_tablesample_method];
+	HeapTuple	tuple;
+
+	/* Must be super user. */
+	if (!superuser())
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied to create tablesample method \"%s\"",
+						tsmname),
+				 errhint("Must be superuser to create a tablesample method.")));
+
+	/* Must not already exist. */
+	tsmoid = get_tablesample_method_oid(tsmname, true);
+	if (OidIsValid(tsmoid))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("tablesample method \"%s\" already exists",
+						tsmname)));
+
+	/* Initialize the values. */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+
+	values[Anum_pg_tablesample_method_tsmname - 1] =
+		DirectFunctionCall1(namein, CStringGetDatum(tsmname));
+
+	/*
+	 * loop over the definition list and extract the information we need.
+	 */
+	foreach(pl, parameters)
+	{
+		DefElem    *defel = (DefElem *) lfirst(pl);
+
+		if (pg_strcasecmp(defel->defname, "seqscan") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmseqscan - 1] =
+				BoolGetDatum(defGetBoolean(defel));
+		}
+		else if (pg_strcasecmp(defel->defname, "init") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsminit - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsminit);
+		}
+		else if (pg_strcasecmp(defel->defname, "nextblock") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmnextblock - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmnextblock);
+		}
+		else if (pg_strcasecmp(defel->defname, "nexttuple") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmnexttuple - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmnexttuple);
+		}
+		else if (pg_strcasecmp(defel->defname, "examinetuple") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmexaminetuple - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmexaminetuple);
+		}
+		else if (pg_strcasecmp(defel->defname, "end") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmend - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmend);
+		}
+		else if (pg_strcasecmp(defel->defname, "reset") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmreset - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmreset);
+		}
+		else if (pg_strcasecmp(defel->defname, "cost") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmcost - 1] =
+				get_tablesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmcost);
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("tablesample method parameter \"%s\" not recognized",
+						defel->defname)));
+	}
+
+	/*
+	 * Validation.
+	 */
+	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_tablesample_method_tsminit - 1])))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("tablesample method init function is required")));
+
+	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_tablesample_method_tsmnextblock - 1])))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("tablesample method nextblock function is required")));
+
+	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_tablesample_method_tsmnexttuple - 1])))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("tablesample method nexttuple function is required")));
+
+	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_tablesample_method_tsmend - 1])))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("tablesample method end function is required")));
+
+	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_tablesample_method_tsmreset - 1])))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("tablesample method reset function is required")));
+
+	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_tablesample_method_tsmcost - 1])))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+				 errmsg("tablesample method cost function is required")));
+
+	/*
+	 * Insert tuple into pg_tablesample_method.
+	 */
+	rel = heap_open(TableSampleMethodRelationId, RowExclusiveLock);
+
+	tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+	tsmoid = simple_heap_insert(rel, tuple);
+
+	CatalogUpdateIndexes(rel, tuple);
+
+	makeTablesampleMethodDeps(tuple);
+
+	heap_freetuple(tuple);
+
+	/* Post creation hook for new tablesample method */
+	InvokeObjectPostCreateHook(TableSampleMethodRelationId, tsmoid, 0);
+
+	heap_close(rel, RowExclusiveLock);
+
+	return tsmoid;
+}
+
+/*
+ * Drop a tablesample method.
+ */
+void
+RemoveTablesampleMethodById(Oid tsmoid)
+{
+	Relation	rel;
+	HeapTuple	tuple;
+	Form_pg_tablesample_method tsm;
+
+	/*
+	 * Find the target tuple
+	 */
+	rel = heap_open(TableSampleMethodRelationId, RowExclusiveLock);
+
+	tuple = SearchSysCache1(TABLESAMPLEMETHODOID, ObjectIdGetDatum(tsmoid));
+	if (!HeapTupleIsValid(tuple))
+		elog(ERROR, "cache lookup failed for tablesample method %u",
+			 tsmoid);
+
+	tsm = (Form_pg_tablesample_method) GETSTRUCT(tuple);
+	/* Can't drop builtin tablesample methods. */
+	if (tsmoid == TABLESAMPLE_METHOD_SYSTEM_OID ||
+		tsmoid == TABLESAMPLE_METHOD_BERNOULLI_OID)
+		ereport(ERROR,
+				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				 errmsg("permission denied for tablesample method %s",
+						NameStr(tsm->tsmname))));
+
+	/*
+	 * Remove the pg_tablespace tuple (this will roll back if we fail below)
+	 */
+	simple_heap_delete(rel, &tuple->t_self);
+
+	ReleaseSysCache(tuple);
+
+	heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * get_tablesample_method_oid - given a tablesample method name,
+ * look up the OID
+ *
+ * If missing_ok is false, throw an error if tablesample method name not found.
+ * If true, just return InvalidOid.
+ */
+Oid
+get_tablesample_method_oid(const char *tsmname, bool missing_ok)
+{
+	Oid			result;
+	HeapTuple	tuple;
+
+	tuple = SearchSysCache1(TABLESAMPLEMETHODNAME, PointerGetDatum(tsmname));
+	if (HeapTupleIsValid(tuple))
+	{
+		result = HeapTupleGetOid(tuple);
+		ReleaseSysCache(tuple);
+	}
+	else
+		result = InvalidOid;
+
+	if (!OidIsValid(result) && !missing_ok)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("tablesample method \"%s\" does not exist",
+						tsmname)));
+
+	return result;
+}
+
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ac5e095..4578b5e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -586,7 +586,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
 
-	MAPPING MATCH MATERIALIZED MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
+	MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P
+	MOVE
 
 	NAME_P NAMES NATIONAL NATURAL NCHAR NEXT NO NONE
 	NOT NOTHING NOTIFY NOTNULL NOWAIT NULL_P NULLIF
@@ -5094,6 +5095,15 @@ DefineStmt:
 					n->definition = list_make1(makeDefElem("from", (Node *) $5));
 					$$ = (Node *)n;
 				}
+			| CREATE TABLESAMPLE METHOD name definition
+				{
+					DefineStmt *n = makeNode(DefineStmt);
+					n->kind = OBJECT_TABLESAMPLEMETHOD;
+					n->args = NIL;
+					n->defnames = list_make1(makeString($4));
+					n->definition = $5;
+					$$ = (Node *)n;
+				}
 		;
 
 definition: '(' def_list ')'						{ $$ = $2; }
@@ -5552,6 +5562,7 @@ drop_type:	TABLE									{ $$ = OBJECT_TABLE; }
 			| TEXT_P SEARCH DICTIONARY				{ $$ = OBJECT_TSDICTIONARY; }
 			| TEXT_P SEARCH TEMPLATE				{ $$ = OBJECT_TSTEMPLATE; }
 			| TEXT_P SEARCH CONFIGURATION			{ $$ = OBJECT_TSCONFIGURATION; }
+			| TABLESAMPLE METHOD					{ $$ = OBJECT_TABLESAMPLEMETHOD; }
 		;
 
 any_name_list:
@@ -13313,6 +13324,7 @@ unreserved_keyword:
 			| MATCH
 			| MATERIALIZED
 			| MAXVALUE
+			| METHOD
 			| MINUTE_P
 			| MINVALUE
 			| MODE
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3533cfa..532256d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -23,6 +23,7 @@
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_tablesample_method.h"
 #include "catalog/toasting.h"
 #include "commands/alter.h"
 #include "commands/async.h"
@@ -1106,6 +1107,11 @@ ProcessUtilitySlow(Node *parsetree,
 							Assert(stmt->args == NIL);
 							DefineCollation(stmt->defnames, stmt->definition);
 							break;
+						case OBJECT_TABLESAMPLEMETHOD:
+							Assert(stmt->args == NIL);
+							Assert(list_length(stmt->defnames) == 1);
+							DefineTablesampleMethod(stmt->defnames, stmt->definition);
+							break;
 						default:
 							elog(ERROR, "unrecognized define stmt type: %d",
 								 (int) stmt->kind);
@@ -1960,6 +1966,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_POLICY:
 					tag = "DROP POLICY";
 					break;
+				case OBJECT_TABLESAMPLEMETHOD:
+					tag = "DROP TABLESAMPLE METHOD";
+					break;
 				default:
 					tag = "???";
 			}
@@ -2056,6 +2065,9 @@ CreateCommandTag(Node *parsetree)
 				case OBJECT_COLLATION:
 					tag = "CREATE COLLATION";
 					break;
+				case OBJECT_TABLESAMPLEMETHOD:
+					tag = "CREATE TABLESAMPLE METHOD";
+					break;
 				default:
 					tag = "???";
 			}
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 1a0a587..8a64e4b 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -103,6 +103,7 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
 	int			numForeignServers;
 	int			numDefaultACLs;
 	int			numEventTriggers;
+	int			numTSMs;
 
 	if (g_verbose)
 		write_msg(NULL, "reading schemas\n");
@@ -251,6 +252,10 @@ getSchemaData(Archive *fout, DumpOptions *dopt, int *numTablesPtr)
 		write_msg(NULL, "reading policies\n");
 	getPolicies(fout, tblinfo, numTables);
 
+	if (g_verbose)
+		write_msg(NULL, "reading tablesample methods\n");
+	getTableSampleMethods(fout, &numTSMs);
+
 	*numTablesPtr = numTables;
 	return tblinfo;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 7e92b74..9f19799 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -182,6 +182,7 @@ static void dumpSequenceData(Archive *fout, TableDataInfo *tdinfo);
 static void dumpIndex(Archive *fout, DumpOptions *dopt, IndxInfo *indxinfo);
 static void dumpConstraint(Archive *fout, DumpOptions *dopt, ConstraintInfo *coninfo);
 static void dumpTableConstraintComment(Archive *fout, DumpOptions *dopt, ConstraintInfo *coninfo);
+static void dumpTableSampleMethod(Archive *fout, DumpOptions *dopt, TSMInfo *tbinfo);
 static void dumpTSParser(Archive *fout, DumpOptions *dopt, TSParserInfo *prsinfo);
 static void dumpTSDictionary(Archive *fout, DumpOptions *dopt, TSDictInfo *dictinfo);
 static void dumpTSTemplate(Archive *fout, DumpOptions *dopt, TSTemplateInfo *tmplinfo);
@@ -7174,6 +7175,75 @@ getTableAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tblinfo, int numTable
 }
 
 /*
+ * getTableSampleMethods:
+ *	  read all tablesample methods in the system catalogs and return them
+ *	  in the TSMInfo* structure
+ *
+ *	numTSMs is set to the number of tablesample methods read in
+ */
+TSMInfo *
+getTableSampleMethods(Archive *fout, int *numTSMs)
+{
+	PGresult   *res;
+	int			ntups;
+	int			i;
+	PQExpBuffer query;
+	TSMInfo	   *tsminfo;
+	int			i_tableoid,
+				i_oid,
+				i_tsmname,
+				i_tsmseqscan;
+
+	/* Before 9.5, there were no tablesample methods */
+	if (fout->remoteVersion < 90500)
+	{
+		*numTSMs = 0;
+		return NULL;
+	}
+
+	query = createPQExpBuffer();
+
+	appendPQExpBuffer(query,
+					  "SELECT tableoid, oid, tsmname, tsmseqscan "
+					  "FROM pg_catalog.pg_tablesample_method "
+					  "WHERE oid >= '%u'::pg_catalog.oid",
+					  FirstNormalObjectId);
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+	*numTSMs = ntups;
+
+	tsminfo = (TSMInfo *) pg_malloc(ntups * sizeof(TSMInfo));
+
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_oid = PQfnumber(res, "oid");
+	i_tsmname = PQfnumber(res, "tsmname");
+	i_tsmseqscan = PQfnumber(res, "tsmseqscan");
+
+	for (i = 0; i < ntups; i++)
+	{
+		tsminfo[i].dobj.objType = DO_TABLESAMPLE_METHOD;
+		tsminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		tsminfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+		AssignDumpId(&tsminfo[i].dobj);
+		tsminfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_tsmname));
+		tsminfo[i].dobj.namespace = NULL;
+		tsminfo[i].tsmseqscan = PQgetvalue(res, i, i_tsmseqscan)[0] == 't';
+
+		/* Decide whether we want to dump it */
+		selectDumpableObject(&(tsminfo[i].dobj));
+	}
+
+	PQclear(res);
+
+	destroyPQExpBuffer(query);
+
+	return tsminfo;
+}
+
+
+/*
  * Test whether a column should be printed as part of table's CREATE TABLE.
  * Column number is zero-based.
  *
@@ -8266,6 +8336,9 @@ dumpDumpableObject(Archive *fout, DumpOptions *dopt, DumpableObject *dobj)
 		case DO_POLICY:
 			dumpPolicy(fout, dopt, (PolicyInfo *) dobj);
 			break;
+		case DO_TABLESAMPLE_METHOD:
+			dumpTableSampleMethod(fout, dopt, (TSMInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -12266,6 +12339,103 @@ dumpAgg(Archive *fout, DumpOptions *dopt, AggInfo *agginfo)
 }
 
 /*
+ * dumpTableSampleMethod
+ *	  write the declaration of one user-defined tablesample method
+ */
+static void
+dumpTableSampleMethod(Archive *fout, DumpOptions *dopt, TSMInfo *tsminfo)
+{
+	PGresult   *res;
+	PQExpBuffer q;
+	PQExpBuffer delq;
+	PQExpBuffer labelq;
+	PQExpBuffer query;
+	char	   *tsminit;
+	char	   *tsmnextblock;
+	char	   *tsmnexttuple;
+	char	   *tsmexaminetuple;
+	char	   *tsmend;
+	char	   *tsmreset;
+	char	   *tsmcost;
+
+	/* Skip if not to be dumped */
+	if (!tsminfo->dobj.dump || dopt->dataOnly)
+		return;
+
+	q = createPQExpBuffer();
+	delq = createPQExpBuffer();
+	labelq = createPQExpBuffer();
+	query = createPQExpBuffer();
+
+	/* Make sure we are in proper schema */
+	selectSourceSchema(fout, "pg_catalog");
+
+	appendPQExpBuffer(query, "SELECT tsminit, tsmnextblock, "
+							 "tsmnexttuple, tsmexaminetuple, "
+							 "tsmend, tsmreset, tsmcost "
+							 "FROM pg_catalog.pg_tablesample_method "
+							 "WHERE oid = '%u'::pg_catalog.oid",
+							 tsminfo->dobj.catId.oid);
+
+	res = ExecuteSqlQueryForSingleRow(fout, query->data);
+
+	tsminit = PQgetvalue(res, 0, PQfnumber(res, "tsminit"));
+	tsmnexttuple = PQgetvalue(res, 0, PQfnumber(res, "tsmnexttuple"));
+	tsmnextblock = PQgetvalue(res, 0, PQfnumber(res, "tsmnextblock"));
+	tsmexaminetuple = PQgetvalue(res, 0, PQfnumber(res, "tsmexaminetuple"));
+	tsmend = PQgetvalue(res, 0, PQfnumber(res, "tsmend"));
+	tsmreset = PQgetvalue(res, 0, PQfnumber(res, "tsmreset"));
+	 tsmcost = PQgetvalue(res, 0, PQfnumber(res, "tsmcost"));
+
+	appendPQExpBuffer(q, "CREATE TABLESAMPLE METHOD %s (\n",
+					  fmtId(tsminfo->dobj.name));
+
+	appendPQExpBuffer(q, "    INIT = %s,\n", tsminit);
+	appendPQExpBuffer(q, "    NEXTTUPLE = %s,\n", tsmnexttuple);
+	appendPQExpBuffer(q, "    NEXTBLOCK = %s,\n", tsmnextblock);
+	appendPQExpBuffer(q, "    END = %s,\n", tsmend);
+	appendPQExpBuffer(q, "    RESET = %s,\n", tsmreset);
+	appendPQExpBuffer(q, "    COST = %s", tsmcost);
+
+	if (strcmp(tsmexaminetuple, "-") != 0)
+		appendPQExpBuffer(q, ",\n    EXAMINETUPLE = %s", tsmexaminetuple);
+
+	if (tsminfo->tsmseqscan)
+		appendPQExpBufferStr(q, ",\n    SEQSCAN = true");
+
+	appendPQExpBufferStr(q, "\n);");
+
+	appendPQExpBuffer(delq, "DROP TABLESAMPLE METHOD %s",
+					  fmtId(tsminfo->dobj.name));
+
+	appendPQExpBuffer(labelq, "TABLESAMPLE METHOD %s",
+					  fmtId(tsminfo->dobj.name));
+
+	if (dopt->binary_upgrade)
+		binary_upgrade_extension_member(q, &tsminfo->dobj, labelq->data);
+
+	ArchiveEntry(fout, tsminfo->dobj.catId, tsminfo->dobj.dumpId,
+				 tsminfo->dobj.name,
+				 NULL,
+				 NULL,
+				 "",
+				 false, "TABLESAMPLE METHOD", SECTION_PRE_DATA,
+				 q->data, delq->data, NULL,
+				 NULL, 0,
+				 NULL, NULL);
+
+	/* Dump Parser Comments */
+	dumpComment(fout, dopt, labelq->data,
+				NULL, "",
+				tsminfo->dobj.catId, 0, tsminfo->dobj.dumpId);
+
+	PQclear(res);
+	destroyPQExpBuffer(q);
+	destroyPQExpBuffer(delq);
+	destroyPQExpBuffer(labelq);
+}
+
+/*
  * dumpTSParser
  *	  write out a single text search parser
  */
@@ -15619,6 +15789,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_FDW:
 			case DO_FOREIGN_SERVER:
 			case DO_BLOB:
+			case DO_TABLESAMPLE_METHOD:
 				/* Pre-data objects: must come before the pre-data boundary */
 				addObjectDependency(preDataBound, dobj->dumpId);
 				break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index f42c42d..645c07c 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -76,7 +76,8 @@ typedef enum
 	DO_POST_DATA_BOUNDARY,
 	DO_EVENT_TRIGGER,
 	DO_REFRESH_MATVIEW,
-	DO_POLICY
+	DO_POLICY,
+	DO_TABLESAMPLE_METHOD
 } DumpableObjectType;
 
 typedef struct _dumpableObject
@@ -384,6 +385,12 @@ typedef struct _inhInfo
 	Oid			inhparent;		/* OID of its parent */
 } InhInfo;
 
+typedef struct _tsmInfo
+{
+	DumpableObject dobj;
+	bool		tsmseqscan;
+} TSMInfo;
+
 typedef struct _prsInfo
 {
 	DumpableObject dobj;
@@ -537,6 +544,7 @@ extern ProcLangInfo *getProcLangs(Archive *fout, int *numProcLangs);
 extern CastInfo *getCasts(Archive *fout, DumpOptions *dopt, int *numCasts);
 extern void getTableAttrs(Archive *fout, DumpOptions *dopt, TableInfo *tbinfo, int numTables);
 extern bool shouldPrintColumn(DumpOptions *dopt, TableInfo *tbinfo, int colno);
+extern TSMInfo *getTableSampleMethods(Archive *fout, int *numTSMs);
 extern TSParserInfo *getTSParsers(Archive *fout, int *numTSParsers);
 extern TSDictInfo *getTSDictionaries(Archive *fout, int *numTSDicts);
 extern TSTemplateInfo *getTSTemplates(Archive *fout, int *numTSTemplates);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 4b9bba0..cb009ce 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -73,7 +73,8 @@ static const int oldObjectTypePriority[] =
 	13,							/* DO_POST_DATA_BOUNDARY */
 	20,							/* DO_EVENT_TRIGGER */
 	15,							/* DO_REFRESH_MATVIEW */
-	21							/* DO_POLICY */
+	21,							/* DO_POLICY */
+	5							/* DO_TABLESAMPLE_METHOD */
 };
 
 /*
@@ -122,7 +123,8 @@ static const int newObjectTypePriority[] =
 	25,							/* DO_POST_DATA_BOUNDARY */
 	32,							/* DO_EVENT_TRIGGER */
 	33,							/* DO_REFRESH_MATVIEW */
-	34							/* DO_POLICY */
+	34,							/* DO_POLICY */
+	17							/* DO_TABLESAMPLE_METHOD */
 };
 
 static DumpId preDataBoundId;
@@ -1443,6 +1445,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "POLICY (ID %d OID %u)",
 					 obj->dumpId, obj->catId.oid);
 			return;
+		case DO_TABLESAMPLE_METHOD:
+			snprintf(buf, bufsize,
+					 "TABLESAMPLE METHOD %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 		case DO_PRE_DATA_BOUNDARY:
 			snprintf(buf, bufsize,
 					 "PRE-DATA BOUNDARY  (ID %d)",
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 6481ac8..30653f8 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -148,6 +148,7 @@ typedef enum ObjectClass
 	OCLASS_EXTENSION,			/* pg_extension */
 	OCLASS_EVENT_TRIGGER,		/* pg_event_trigger */
 	OCLASS_POLICY,				/* pg_policy */
+	OCLASS_TABLESAMPLEMETHOD,	/* pg_tablesample_method */
 	MAX_OCLASS					/* MUST BE LAST */
 } ObjectClass;
 
diff --git a/src/include/catalog/pg_tablesample_method.h b/src/include/catalog/pg_tablesample_method.h
index fd76f77..6a55669 100644
--- a/src/include/catalog/pg_tablesample_method.h
+++ b/src/include/catalog/pg_tablesample_method.h
@@ -69,7 +69,18 @@ typedef FormData_pg_tablesample_method *Form_pg_tablesample_method;
 
 DATA(insert OID = 3283 ( system false tsm_system_init tsm_system_nextblock tsm_system_nexttuple - tsm_system_end tsm_system_reset tsm_system_cost ));
 DESCR("SYSTEM table sampling method");
+#define TABLESAMPLE_METHOD_SYSTEM_OID 3283
 DATA(insert OID = 3284 ( bernoulli true tsm_bernoulli_init tsm_bernoulli_nextblock tsm_bernoulli_nexttuple - tsm_bernoulli_end tsm_bernoulli_reset tsm_bernoulli_cost ));
 DESCR("BERNOULLI table sampling method");
+#define TABLESAMPLE_METHOD_BERNOULLI_OID 3284
+
+/* ----------------
+ *		functions for manipulation of pg_tablesample_method
+ * ----------------
+ */
+
+extern Oid DefineTablesampleMethod(List *names, List *parameters);
+extern void RemoveTablesampleMethodById(Oid tsmoid);
+extern Oid get_tablesample_method_oid(const char *tsmname, bool missing_ok);
 
 #endif   /* PG_TABLESAMPLE_METHOD_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2f4df1d..bc1fef2 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1269,6 +1269,7 @@ typedef enum ObjectType
 	OBJECT_SEQUENCE,
 	OBJECT_TABCONSTRAINT,
 	OBJECT_TABLE,
+	OBJECT_TABLESAMPLEMETHOD,
 	OBJECT_TABLESPACE,
 	OBJECT_TRIGGER,
 	OBJECT_TSCONFIGURATION,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 6ff7b44..c3269c0 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -236,6 +236,7 @@ PG_KEYWORD("mapping", MAPPING, UNRESERVED_KEYWORD)
 PG_KEYWORD("match", MATCH, UNRESERVED_KEYWORD)
 PG_KEYWORD("materialized", MATERIALIZED, UNRESERVED_KEYWORD)
 PG_KEYWORD("maxvalue", MAXVALUE, UNRESERVED_KEYWORD)
+PG_KEYWORD("method", METHOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("minute", MINUTE_P, UNRESERVED_KEYWORD)
 PG_KEYWORD("minvalue", MINVALUE, UNRESERVED_KEYWORD)
 PG_KEYWORD("mode", MODE, UNRESERVED_KEYWORD)
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 93d93af..37ea524 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -9,7 +9,8 @@ SUBDIRS = \
 		  worker_spi \
 		  dummy_seclabel \
 		  test_shm_mq \
-		  test_parser
+		  test_parser \
+		  tablesample
 
 all: submake-errcodes
 
diff --git a/src/test/modules/tablesample/.gitignore b/src/test/modules/tablesample/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/src/test/modules/tablesample/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/tablesample/Makefile b/src/test/modules/tablesample/Makefile
new file mode 100644
index 0000000..469b004
--- /dev/null
+++ b/src/test/modules/tablesample/Makefile
@@ -0,0 +1,21 @@
+# src/test/modules/tsm_test/Makefile
+
+MODULE_big = tsm_test
+OBJS = tsm_test.o $(WIN32RES)
+PGFILEDESC = "tsm_test - example of a custom tablesample method"
+
+EXTENSION = tsm_test
+DATA = tsm_test--1.0.sql
+
+REGRESS = tablesample
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/tablesample
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/tablesample/expected/tablesample.out b/src/test/modules/tablesample/expected/tablesample.out
new file mode 100644
index 0000000..3cb3848
--- /dev/null
+++ b/src/test/modules/tablesample/expected/tablesample.out
@@ -0,0 +1,38 @@
+CREATE EXTENSION tsm_test;
+CREATE TABLE test_tsm AS SELECT md5(i::text) a, 0.5::float b FROM generate_series(1,10) g(i);
+SELECT * FROM test_tsm TABLESAMPLE tsm_test('b') REPEATABLE (1);
+                a                 |  b  
+----------------------------------+-----
+ c4ca4238a0b923820dcc509a6f75849b | 0.5
+ c81e728d9d4c2f636f067f89cc14862c | 0.5
+ a87ff679a2f3e71d9181a67b7542122c | 0.5
+ 1679091c5a880faf6fb5e6087eb1b2dc | 0.5
+ 8f14e45fceea167a5a36dedd4bea2543 | 0.5
+ d3d9446802a44259755d38e6d163e820 | 0.5
+(6 rows)
+
+CREATE VIEW test_tsm_v AS SELECT * FROM test_tsm TABLESAMPLE tsm_test('b') REPEATABLE (9999);
+SELECT * FROM test_tsm_v;
+                a                 |  b  
+----------------------------------+-----
+ c4ca4238a0b923820dcc509a6f75849b | 0.5
+ e4da3b7fbbce2345d7772b0674a318d5 | 0.5
+ 1679091c5a880faf6fb5e6087eb1b2dc | 0.5
+ 8f14e45fceea167a5a36dedd4bea2543 | 0.5
+ c9f0f895fb98ab9159f51fd0297e236d | 0.5
+(5 rows)
+
+DROP TABLESAMPLE METHOD tsm_test;
+ERROR:  cannot drop tablesample method tsm_test because extension tsm_test requires it
+HINT:  You can drop extension tsm_test instead.
+DROP EXTENSION tsm_test;
+ERROR:  cannot drop extension tsm_test because other objects depend on it
+DETAIL:  view test_tsm_v depends on tablesample method tsm_test
+HINT:  Use DROP ... CASCADE to drop the dependent objects too.
+DROP EXTENSION tsm_test CASCADE;
+NOTICE:  drop cascades to view test_tsm_v
+SELECT * FROM pg_tablesample_method WHERE tsmname = 'tsm_test';
+ tsmname | tsmseqscan | tsminit | tsmnextblock | tsmnexttuple | tsmexaminetuple | tsmend | tsmreset | tsmcost 
+---------+------------+---------+--------------+--------------+-----------------+--------+----------+---------
+(0 rows)
+
diff --git a/src/test/modules/tablesample/sql/tablesample.sql b/src/test/modules/tablesample/sql/tablesample.sql
new file mode 100644
index 0000000..b1104d6
--- /dev/null
+++ b/src/test/modules/tablesample/sql/tablesample.sql
@@ -0,0 +1,14 @@
+CREATE EXTENSION tsm_test;
+
+CREATE TABLE test_tsm AS SELECT md5(i::text) a, 0.5::float b FROM generate_series(1,10) g(i);
+
+SELECT * FROM test_tsm TABLESAMPLE tsm_test('b') REPEATABLE (1);
+
+CREATE VIEW test_tsm_v AS SELECT * FROM test_tsm TABLESAMPLE tsm_test('b') REPEATABLE (9999);
+SELECT * FROM test_tsm_v;
+
+DROP TABLESAMPLE METHOD tsm_test;
+DROP EXTENSION tsm_test;
+DROP EXTENSION tsm_test CASCADE;
+
+SELECT * FROM pg_tablesample_method WHERE tsmname = 'tsm_test';
diff --git a/src/test/modules/tablesample/tsm_test--1.0.sql b/src/test/modules/tablesample/tsm_test--1.0.sql
new file mode 100644
index 0000000..2280ab0
--- /dev/null
+++ b/src/test/modules/tablesample/tsm_test--1.0.sql
@@ -0,0 +1,51 @@
+/* src/test/modules/tablesample/tsm_test--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION tsm_test" to load this file. \quit
+
+CREATE FUNCTION tsm_test_init(internal, int4, text)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_nextblock(internal)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_nexttuple(internal, int4, int2)
+RETURNS int2
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_examinetuple(internal, int4, internal, bool)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_end(internal)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_reset(internal)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_cost(internal, internal, internal, internal, internal, internal, internal)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+
+CREATE TABLESAMPLE METHOD tsm_test (
+	SEQSCAN		= true,
+	INIT		= tsm_test_init,
+	NEXTBLOCK	= tsm_test_nextblock,
+	NEXTTUPLE	= tsm_test_nexttuple,
+	EXAMINETUPLE	= tsm_test_examinetuple,
+	END			= tsm_test_end,
+	RESET		= tsm_test_reset,
+	COST		= tsm_test_cost
+);
diff --git a/src/test/modules/tablesample/tsm_test.c b/src/test/modules/tablesample/tsm_test.c
new file mode 100644
index 0000000..be4dcb9
--- /dev/null
+++ b/src/test/modules/tablesample/tsm_test.c
@@ -0,0 +1,228 @@
+/*-------------------------------------------------------------------------
+ *
+ * tsm_test.c
+ *	  Simple example of a custom tablesample method
+ *
+ * Copyright (c) 2007-2014, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *	  src/test/modules/tablesample/tsm_test.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+
+#include "access/htup_details.h"
+#include "access/relscan.h"
+#include "access/tupdesc.h"
+#include "catalog/pg_type.h"
+#include "nodes/execnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/clauses.h"
+#include "storage/bufmgr.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/sampling.h"
+#include "utils/tablesample.h"
+
+PG_MODULE_MAGIC;
+
+/* State */
+typedef struct
+{
+	uint32		seed;			/* random seed */
+	AttrNumber	attnum;			/* column to check */
+	TupleDesc	tupDesc;		/* tuple descriptor of table */
+	BlockNumber startblock;		/* starting block, we use ths for syncscan support */
+	BlockNumber nblocks;		/* total blocks in relation */
+	BlockNumber blockno;		/* current block */
+	OffsetNumber lt;			/* last tuple returned from current block */
+	SamplerRandomState randstate; /* random generator state */
+} tsm_test_state;
+
+
+PG_FUNCTION_INFO_V1(tsm_test_init);
+PG_FUNCTION_INFO_V1(tsm_test_nextblock);
+PG_FUNCTION_INFO_V1(tsm_test_nexttuple);
+PG_FUNCTION_INFO_V1(tsm_test_examinetuple);
+PG_FUNCTION_INFO_V1(tsm_test_end);
+PG_FUNCTION_INFO_V1(tsm_test_reset);
+PG_FUNCTION_INFO_V1(tsm_test_cost);
+
+/*
+ * Initialize the state.
+ */
+Datum
+tsm_test_init(PG_FUNCTION_ARGS)
+{
+	SampleScanState *scanstate = (SampleScanState *) PG_GETARG_POINTER(0);
+	uint32			seed = PG_GETARG_UINT32(1);
+	char		   *attname;
+	AttrNumber		attnum;
+	Oid				atttype;
+	Relation		rel = scanstate->ss.ss_currentRelation;
+	HeapScanDesc	scan = scanstate->ss.ss_currentScanDesc;
+	tsm_test_state *state;
+	TupleDesc		tupDesc = RelationGetDescr(rel);
+
+	if (PG_ARGISNULL(2))
+		ereport(ERROR,
+				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+				 errmsg("invalid parameter for tablesample method tsm_test"),
+				 errhint("attnum cannot be NULL.")));
+
+	attname = text_to_cstring(PG_GETARG_TEXT_P(2));
+
+	attnum = get_attnum(rel->rd_id, attname);
+	if (!AttrNumberIsForUserDefinedAttr(attnum))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid parameter for tablesample method tsm_test"),
+				 errhint("column %s does not exist", attname)));
+
+	atttype = get_atttype(rel->rd_id, attnum);
+	if (atttype != FLOAT8OID)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("invalid parameter for tablesample method tsm_test"),
+				 errhint("column %s is not of type float.", attname)));
+
+	state = palloc0(sizeof(tsm_test_state));
+
+	/* Remember initial values for reinit */
+	state->seed = seed;
+	state->attnum = attnum;
+	state->tupDesc = tupDesc;
+	state->startblock = scan->rs_startblock;
+	state->nblocks = scan->rs_nblocks;
+	state->blockno = InvalidBlockNumber;
+	state->lt = InvalidOffsetNumber;
+	sampler_random_init_state(state->seed, state->randstate);
+
+	scanstate->tsmdata = (void *) state;
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Get next block number to read or InvalidBlockNumber if we are at the
+ * end of the relation.
+ */
+Datum
+tsm_test_nextblock(PG_FUNCTION_ARGS)
+{
+	SampleScanState *scanstate = (SampleScanState *) PG_GETARG_POINTER(0);
+	tsm_test_state  *state = (tsm_test_state *) scanstate->tsmdata;
+
+	/* Cycle from startblock to startblock to support syncscan. */
+	if (state->blockno == InvalidBlockNumber)
+		state->blockno = state->startblock;
+	else
+	{
+		state->blockno++;
+
+		if (state->blockno >= state->nblocks)
+			state->blockno = 0;
+
+		if (state->blockno == state->startblock)
+			PG_RETURN_UINT32(InvalidBlockNumber);
+	}
+
+	PG_RETURN_UINT32(state->blockno);
+}
+
+/*
+ * Get next tuple from current block.
+ */
+Datum
+tsm_test_nexttuple(PG_FUNCTION_ARGS)
+{
+	SampleScanState	*scanstate = (SampleScanState *) PG_GETARG_POINTER(0);
+	OffsetNumber	maxoffset = PG_GETARG_UINT16(2);
+	tsm_test_state *state = (tsm_test_state *) scanstate->tsmdata;
+
+	if (state->lt == InvalidOffsetNumber)
+		state->lt = FirstOffsetNumber;
+	else if (++state->lt > maxoffset)
+		PG_RETURN_UINT16(InvalidOffsetNumber);
+
+	PG_RETURN_UINT16(state->lt);
+}
+
+/*
+ * Examine tuple and decide if it should be returned.
+ */
+Datum
+tsm_test_examinetuple(PG_FUNCTION_ARGS)
+{
+	SampleScanState	*scanstate = (SampleScanState *) PG_GETARG_POINTER(0);
+	HeapTuple		tuple = (HeapTuple) PG_GETARG_POINTER(2);
+	bool			visible = PG_GETARG_BOOL(3);
+	tsm_test_state *state = (tsm_test_state *) scanstate->tsmdata;
+	bool			isnull;
+	float8			val, rand;
+
+	if (!visible)
+		PG_RETURN_BOOL(false);
+
+	val = DatumGetFloat8(heap_getattr(tuple, state->attnum, state->tupDesc, &isnull));
+	rand = sampler_random_fract(state->randstate);
+	if (isnull || val < rand)
+		PG_RETURN_BOOL(false);
+	else
+		PG_RETURN_BOOL(true);
+}
+
+/*
+ * Cleanup method.
+ */
+Datum
+tsm_test_end(PG_FUNCTION_ARGS)
+{
+	SampleScanState *scanstate = (SampleScanState *) PG_GETARG_POINTER(0);
+
+	pfree(scanstate->tsmdata);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Reset state (called by ReScan).
+ */
+Datum
+tsm_test_reset(PG_FUNCTION_ARGS)
+{
+	SampleScanState *scanstate = (SampleScanState *) PG_GETARG_POINTER(0);
+	tsm_test_state  *state = (tsm_test_state *) scanstate->tsmdata;
+
+	state->blockno = InvalidBlockNumber;
+	state->lt = InvalidOffsetNumber;
+
+	sampler_random_init_state(state->seed, state->randstate);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Costing function.
+ */
+Datum
+tsm_test_cost(PG_FUNCTION_ARGS)
+{
+	Path		   *path = (Path *) PG_GETARG_POINTER(1);
+	RelOptInfo	   *baserel = (RelOptInfo *) PG_GETARG_POINTER(2);
+	BlockNumber	   *pages = (BlockNumber *) PG_GETARG_POINTER(4);
+	double		   *tuples = (double *) PG_GETARG_POINTER(5);
+
+	*pages = baserel->pages;
+
+	/* This is very bad estimation */
+	*tuples = path->rows = path->rows/2;
+
+	PG_RETURN_VOID();
+}
+
diff --git a/src/test/modules/tablesample/tsm_test.control b/src/test/modules/tablesample/tsm_test.control
new file mode 100644
index 0000000..a7b2741
--- /dev/null
+++ b/src/test/modules/tablesample/tsm_test.control
@@ -0,0 +1,5 @@
+# tsm_test extension
+comment = 'test module for custom tablesample method'
+default_version = '1.0'
+module_pathname = '$libdir/tsm_test'
+relocatable = true
-- 
1.9.1

