From 4c7282395650bd1f158fba4d0f8508b5eef84f9c 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 v2

---
 doc/src/sgml/ref/allfiles.sgml                     |   2 +
 doc/src/sgml/ref/create_tablesamplemethod.sgml     | 149 ++++++++
 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                 | 398 +++++++++++++++++++++
 src/backend/parser/gram.y                          |  14 +-
 src/backend/tcop/utility.c                         |  12 +
 src/include/catalog/dependency.h                   |   1 +
 src/include/catalog/pg_tablesample_method.h        |  11 +
 src/include/nodes/parsenodes.h                     |   1 +
 src/include/nodes/relation.h                       |   2 +-
 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   |  39 ++
 src/test/modules/tablesample/sql/tablesample.sql   |  14 +
 src/test/modules/tablesample/tsm_test--1.0.sql     |  44 +++
 src/test/modules/tablesample/tsm_test.c            | 180 ++++++++++
 src/test/modules/tablesample/tsm_test.control      |   5 +
 26 files changed, 1075 insertions(+), 9 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..70720e5
--- /dev/null
+++ b/doc/src/sgml/ref/create_tablesamplemethod.sgml
@@ -0,0 +1,149 @@
+<!--
+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>
+)
+</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>
+
+  </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..3fcd0bd
--- /dev/null
+++ b/src/backend/commands/tablesample.c
@@ -0,0 +1,398 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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_tabmesample_method_func(DefElem *defel, int attnum)
+{
+	List	   *funcName = defGetQualifiedName(defel);
+	/* Big enough size for our needs. */
+	Oid		   *typeId = palloc0(6 * 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 = 2;
+			typeId[0] = INTERNALOID;
+			typeId[1] = BOOLOID;
+			retTypeId = INT4OID;
+			break;
+		case Anum_pg_tablesample_method_tsmnexttuple:
+			nargs = 4;
+			typeId[0] = INTERNALOID;
+			typeId[1] = INT4OID;
+			typeId[2] = INT2OID;
+			typeId[3] = BOOLOID;
+			retTypeId = INT2OID;
+			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 = 6;
+			typeId[0] = INTERNALOID;
+			typeId[1] = INTERNALOID;
+			typeId[2] = INTERNALOID;
+			typeId[3] = INTERNALOID;
+			typeId[4] = INTERNALOID;
+			typeId[5] = 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);
+
+	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, "init") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsminit - 1] =
+				get_tabmesample_method_func(defel,
+					Anum_pg_tablesample_method_tsminit);
+		}
+		else if (pg_strcasecmp(defel->defname, "nextblock") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmnextblock - 1] =
+				get_tabmesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmnextblock);
+		}
+		else if (pg_strcasecmp(defel->defname, "nexttuple") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmnexttuple - 1] =
+				get_tabmesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmnexttuple);
+		}
+		else if (pg_strcasecmp(defel->defname, "end") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmend - 1] =
+				get_tabmesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmend);
+		}
+		else if (pg_strcasecmp(defel->defname, "reset") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmreset - 1] =
+				get_tabmesample_method_func(defel,
+					Anum_pg_tablesample_method_tsmreset);
+		}
+		else if (pg_strcasecmp(defel->defname, "cost") == 0)
+		{
+			values[Anum_pg_tablesample_method_tsmcost - 1] =
+				get_tabmesample_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 01d72d4..4bf4aff 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/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 0e4a716..4ae8364 100644
--- a/src/include/catalog/pg_tablesample_method.h
+++ b/src/include/catalog/pg_tablesample_method.h
@@ -64,7 +64,18 @@ typedef FormData_pg_tablesample_method *Form_pg_tablesample_method;
 
 DATA(insert OID = 3283 ( system 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 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 d87343f..54c4ba5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1267,6 +1267,7 @@ typedef enum ObjectType
 	OBJECT_SEQUENCE,
 	OBJECT_TABCONSTRAINT,
 	OBJECT_TABLE,
+	OBJECT_TABLESAMPLEMETHOD,
 	OBJECT_TABLESPACE,
 	OBJECT_TRIGGER,
 	OBJECT_TSCONFIGURATION,
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 67c3b1f..33c0f3d 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -877,7 +877,7 @@ typedef struct TidPath
 typedef struct SamplePath
 {
 	Path		path;
-	Oid			tsmcost;		/* table sample method costing function */
+	Oid			tsmcost;		/* tablesample method costing function */
 	List	   *tsmargs;		/* arguments to a TABLESAMPLE clause */
 } SamplePath;
 
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..95c8036
--- /dev/null
+++ b/src/test/modules/tablesample/expected/tablesample.out
@@ -0,0 +1,39 @@
+CREATE EXTENSION tsm_test;
+CREATE TABLE test_tsm AS SELECT md5(i::text) a FROM generate_series(1,10) g(i);
+SELECT * FROM test_tsm TABLESAMPLE tsm_test(true);
+                a                 
+----------------------------------
+ c4ca4238a0b923820dcc509a6f75849b
+ c81e728d9d4c2f636f067f89cc14862c
+ eccbc87e4b5ce2fe28308fd9f2a7baf3
+ a87ff679a2f3e71d9181a67b7542122c
+ e4da3b7fbbce2345d7772b0674a318d5
+ 1679091c5a880faf6fb5e6087eb1b2dc
+ 8f14e45fceea167a5a36dedd4bea2543
+ c9f0f895fb98ab9159f51fd0297e236d
+ 45c48cce2e2d7fbdea1afc51c7c6ad26
+ d3d9446802a44259755d38e6d163e820
+(10 rows)
+
+SELECT * FROM test_tsm TABLESAMPLE tsm_test(false);
+ a 
+---
+(0 rows)
+
+CREATE VIEW test_tsm_v AS SELECT * FROM test_tsm TABLESAMPLE tsm_test(true);
+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;
+  tsmname  |      tsminit       |      tsmnextblock       |      tsmnexttuple       |      tsmend       |      tsmreset       |      tsmcost       
+-----------+--------------------+-------------------------+-------------------------+-------------------+---------------------+--------------------
+ system    | tsm_system_init    | tsm_system_nextblock    | tsm_system_nexttuple    | tsm_system_end    | tsm_system_reset    | tsm_system_cost
+ bernoulli | tsm_bernoulli_init | tsm_bernoulli_nextblock | tsm_bernoulli_nexttuple | tsm_bernoulli_end | tsm_bernoulli_reset | tsm_bernoulli_cost
+(2 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..70997bd
--- /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 FROM generate_series(1,10) g(i);
+
+SELECT * FROM test_tsm TABLESAMPLE tsm_test(true);
+SELECT * FROM test_tsm TABLESAMPLE tsm_test(false);
+
+CREATE VIEW test_tsm_v AS SELECT * FROM test_tsm TABLESAMPLE tsm_test(true);
+
+DROP TABLESAMPLE METHOD tsm_test;
+DROP EXTENSION tsm_test;
+DROP EXTENSION tsm_test CASCADE;
+
+SELECT * FROM pg_tablesample_method;
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..6cfa014
--- /dev/null
+++ b/src/test/modules/tablesample/tsm_test--1.0.sql
@@ -0,0 +1,44 @@
+/* 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, bool)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_nextblock(internal, bool)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION tsm_test_nexttuple(internal, int4, int2, bool)
+RETURNS int2
+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)
+RETURNS void
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+
+CREATE TABLESAMPLE METHOD tsm_test (
+    INIT		= tsm_test_init,
+    NEXTBLOCK	= tsm_test_nextblock,
+    NEXTTUPLE	= tsm_test_nexttuple,
+	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..77016fd
--- /dev/null
+++ b/src/test/modules/tablesample/tsm_test.c
@@ -0,0 +1,180 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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 "nodes/execnodes.h"
+#include "nodes/relation.h"
+#include "optimizer/clauses.h"
+#include "storage/bufmgr.h"
+#include "utils/tablesample.h"
+
+PG_MODULE_MAGIC;
+
+/* State */
+typedef struct
+{
+	bool ret;
+	BlockNumber tblocks;		/* total blocks in relation */
+	BlockNumber blockno;		/* current block */
+	OffsetNumber lt;			/* last tuple returned from current block */
+} 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_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);
+	bool			ret;
+	Relation		rel = scanstate->ss.ss_currentRelation;
+	tsm_test_state *state;
+
+	if (PG_ARGISNULL(2))
+		ereport(ERROR,
+				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+				 errmsg("invalid parameter for tablesample method tsm_test"),
+				 errhint("Return has cannot be NULL.")));
+
+	ret = PG_GETARG_BOOL(2);
+
+	state = palloc0(sizeof(tsm_test_state));
+
+	/* Remember initial values for reinit */
+	state->ret = ret;
+	state->tblocks = RelationGetNumberOfBlocks(rel);
+	state->blockno = InvalidBlockNumber;
+	state->lt = InvalidOffsetNumber;
+	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;
+
+	if (!state->ret)
+		PG_RETURN_UINT32(InvalidBlockNumber);
+
+	if (state->blockno == InvalidBlockNumber)
+		state->blockno = 0;
+	else if (++state->blockno >= state->tblocks)
+		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->ret)
+		PG_RETURN_UINT16(InvalidOffsetNumber);
+
+	if (state->lt == InvalidOffsetNumber)
+		state->lt = FirstOffsetNumber;
+	else if (++state->lt > maxoffset)
+		PG_RETURN_UINT16(InvalidOffsetNumber);
+
+	PG_RETURN_UINT16(state->lt);
+}
+
+/*
+ * 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;
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * Costing function.
+ */
+Datum
+tsm_test_cost(PG_FUNCTION_ARGS)
+{
+	PlannerInfo	   *root = (PlannerInfo *) PG_GETARG_POINTER(0);
+	SamplePath	   *path = (SamplePath *) PG_GETARG_POINTER(1);
+	RelOptInfo	   *baserel = (RelOptInfo *) PG_GETARG_POINTER(2);
+	BlockNumber	   *pages = (BlockNumber *) PG_GETARG_POINTER(3);
+	double		   *tuples = (double *) PG_GETARG_POINTER(4);
+	List		   *args = path->tsmargs;
+	Node		   *pctnode;
+	bool			ret;
+
+	SamplerAccessStrategy *strategy =
+		(SamplerAccessStrategy *) PG_GETARG_POINTER(5);
+
+	*strategy = SAS_SEQUENTIAL;
+
+	pctnode = linitial(args);
+	pctnode = estimate_expression_value(root, pctnode);
+
+	if (IsA(pctnode, RelabelType))
+		pctnode = (Node *) ((RelabelType *) pctnode)->arg;
+
+	if (IsA(pctnode, Const))
+		ret = DatumGetBool(((Const *) pctnode)->constvalue);
+	else
+		ret = true;
+
+	*pages = ret ? baserel->pages : 0;
+	*tuples = ret ? baserel->tuples : 0;
+
+	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

