diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 5a3c8caa689efcfcdc0094ddab1b32424dbd07ab..237c881a5c129331af5ad7feb74273ab313438ce 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -886,9 +886,12 @@ $$ LANGUAGE plpythonu;
    list or dictionary object.  The result object can be accessed by
    row number and column name.  It has these additional methods:
    <function>nrows</function> which returns the number of rows
-   returned by the query, and <function>status</function> which is the
-   <function>SPI_execute()</function> return value.  The result object
-   can be modified.
+   returned by the query, <function>status</function> which is the
+   <function>SPI_execute()</function> return value,
+   <function>colnames</function> which is the list of column names,
+   <function>coltypes</function> which is the list of column type OIDs,
+   and <function>coltypmods</function> which is the list of type-specific type
+   modifiers for the columns.  The result object can be modified.
   </para>
 
   <para>
diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out
index 3b4d7a30105c6aa00eea135cdd332d78ba45cb4b..9ed081b184b7deecf7c55655511181abe037a860 100644
--- a/src/pl/plpython/expected/plpython_spi.out
+++ b/src/pl/plpython/expected/plpython_spi.out
@@ -117,16 +117,25 @@ SELECT join_sequences(sequences) FROM sequences
 --
 CREATE FUNCTION result_nrows_test() RETURNS int
 AS $$
-plan = plpy.prepare("SELECT 1 UNION SELECT 2")
+plan = plpy.prepare("SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'")
 plpy.info(plan.status()) # not really documented or useful
 result = plpy.execute(plan)
 if result.status() > 0:
+   plpy.info(result.colnames())
+   plpy.info(result.coltypes())
+   plpy.info(result.coltypmods())
    return result.nrows()
 else:
    return None
 $$ LANGUAGE plpythonu;
 SELECT result_nrows_test();
 INFO:  True
+CONTEXT:  PL/Python function "result_nrows_test"
+INFO:  ['foo', 'bar']
+CONTEXT:  PL/Python function "result_nrows_test"
+INFO:  [23, 25]
+CONTEXT:  PL/Python function "result_nrows_test"
+INFO:  [-1, -1]
 CONTEXT:  PL/Python function "result_nrows_test"
  result_nrows_test 
 -------------------
diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c
index bf46a165959a61b113420d4142d9f7cddcf099b5..b25e8083b9e1717b2b757d038e124d0bd35765dd 100644
--- a/src/pl/plpython/plpy_resultobject.c
+++ b/src/pl/plpython/plpy_resultobject.c
@@ -12,6 +12,9 @@
 
 
 static void PLy_result_dealloc(PyObject *arg);
+static PyObject *PLy_result_colnames(PyObject *self, PyObject *unused);
+static PyObject *PLy_result_coltypes(PyObject *self, PyObject *unused);
+static PyObject *PLy_result_coltypmods(PyObject *self, PyObject *unused);
 static PyObject *PLy_result_nrows(PyObject *self, PyObject *args);
 static PyObject *PLy_result_status(PyObject *self, PyObject *args);
 static Py_ssize_t PLy_result_length(PyObject *arg);
@@ -35,6 +38,9 @@ static PySequenceMethods PLy_result_as_sequence = {
 };
 
 static PyMethodDef PLy_result_methods[] = {
+	{"colnames", PLy_result_colnames, METH_NOARGS, NULL},
+	{"coltypes", PLy_result_coltypes, METH_NOARGS, NULL},
+	{"coltypmods", PLy_result_coltypmods, METH_NOARGS, NULL},
 	{"nrows", PLy_result_nrows, METH_VARARGS, NULL},
 	{"status", PLy_result_status, METH_VARARGS, NULL},
 	{NULL, NULL, 0, NULL}
@@ -96,6 +102,7 @@ PLy_result_new(void)
 	ob->status = Py_None;
 	ob->nrows = PyInt_FromLong(-1);
 	ob->rows = PyList_New(0);
+	ob->tupdesc = NULL;
 
 	return (PyObject *) ob;
 }
@@ -108,10 +115,57 @@ PLy_result_dealloc(PyObject *arg)
 	Py_XDECREF(ob->nrows);
 	Py_XDECREF(ob->rows);
 	Py_XDECREF(ob->status);
+	if (ob->tupdesc)
+	{
+		FreeTupleDesc(ob->tupdesc);
+		ob->tupdesc = NULL;
+	}
 
 	arg->ob_type->tp_free(arg);
 }
 
+static PyObject *
+PLy_result_colnames(PyObject *self, PyObject *unused)
+{
+	PLyResultObject *ob = (PLyResultObject *) self;
+	PyObject   *list;
+	int			i;
+
+	list = PyList_New(ob->tupdesc->natts);
+	for (i = 0; i < ob->tupdesc->natts; i++)
+		PyList_SET_ITEM(list, i, PyString_FromString(NameStr(ob->tupdesc->attrs[i]->attname)));
+
+	return list;
+}
+
+static PyObject *
+PLy_result_coltypes(PyObject *self, PyObject *unused)
+{
+	PLyResultObject *ob = (PLyResultObject *) self;
+	PyObject   *list;
+	int			i;
+
+	list = PyList_New(ob->tupdesc->natts);
+	for (i = 0; i < ob->tupdesc->natts; i++)
+		PyList_SET_ITEM(list, i, PyInt_FromLong(ob->tupdesc->attrs[i]->atttypid));
+
+	return list;
+}
+
+static PyObject *
+PLy_result_coltypmods(PyObject *self, PyObject *unused)
+{
+	PLyResultObject *ob = (PLyResultObject *) self;
+	PyObject   *list;
+	int			i;
+
+	list = PyList_New(ob->tupdesc->natts);
+	for (i = 0; i < ob->tupdesc->natts; i++)
+		PyList_SET_ITEM(list, i, PyInt_FromLong(ob->tupdesc->attrs[i]->atttypmod));
+
+	return list;
+}
+
 static PyObject *
 PLy_result_nrows(PyObject *self, PyObject *args)
 {
diff --git a/src/pl/plpython/plpy_resultobject.h b/src/pl/plpython/plpy_resultobject.h
index 719828a3efbb1298cf87e4b9505143c4a572d8e2..1b37d1d0c0ca2d3439c42a496de9bac86a65d281 100644
--- a/src/pl/plpython/plpy_resultobject.h
+++ b/src/pl/plpython/plpy_resultobject.h
@@ -5,6 +5,9 @@
 #ifndef PLPY_RESULTOBJECT_H
 #define PLPY_RESULTOBJECT_H
 
+#include "access/tupdesc.h"
+
+
 typedef struct PLyResultObject
 {
 	PyObject_HEAD
@@ -12,6 +15,7 @@ typedef struct PLyResultObject
 	PyObject   *nrows;			/* number of rows returned by query */
 	PyObject   *rows;			/* data rows, or None if no data returned */
 	PyObject   *status;			/* query status, SPI_OK_*, or SPI_ERR_* */
+	TupleDesc	tupdesc;
 } PLyResultObject;
 
 extern void PLy_result_init_type(void);
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 3afb1093d57966ce9e461be4698ec393b0770def..0d63c4f5ce850da5ed23f80a2512494d6dcdd1d4 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -398,6 +398,8 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
 		oldcontext = CurrentMemoryContext;
 		PG_TRY();
 		{
+			result->tupdesc = CreateTupleDescCopy(tuptable->tupdesc);
+
 			if (rows)
 			{
 				Py_DECREF(result->rows);
diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql
index 874b31e6df6bbe2a7cecaf857aa15e53e7162467..b828744d1f80433a0247a4b79d1a339d42992741 100644
--- a/src/pl/plpython/sql/plpython_spi.sql
+++ b/src/pl/plpython/sql/plpython_spi.sql
@@ -95,10 +95,13 @@ SELECT join_sequences(sequences) FROM sequences
 
 CREATE FUNCTION result_nrows_test() RETURNS int
 AS $$
-plan = plpy.prepare("SELECT 1 UNION SELECT 2")
+plan = plpy.prepare("SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'")
 plpy.info(plan.status()) # not really documented or useful
 result = plpy.execute(plan)
 if result.status() > 0:
+   plpy.info(result.colnames())
+   plpy.info(result.coltypes())
+   plpy.info(result.coltypmods())
    return result.nrows()
 else:
    return None