Is there a good reason why PL languages do not support cstring type arguments and return values ?

Started by Hannu Krosingover 13 years ago12 messages
#1Hannu Krosing
hannu@2ndQuadrant.com

Is the lack of support of cstring in PLs just laziness/ovelook or is
there a good
reason why PL languages do not support cstring type arguments and return
values ?

I'm currently adding this to pl/pythonu with an aim to prototype type io
functions for a new type.

If it is just some security concern this could be alleviated by only
allowing it in the untrusted languages.

--------------------
Hannu Krosing

#2Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#1)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

Hannu Krosing <hannu@2ndQuadrant.com> writes:

Is the lack of support of cstring in PLs just laziness/ovelook or is
there a good
reason why PL languages do not support cstring type arguments and return
values ?

In general I don't think we should encourage the use of cstring as a
user-level data type. The number of text-like types in the system is
already enough to confuse users, and this one brings no redeeming social
value to the party. Besides which, it has essentially no built-in
operators, and I *don't* want to have to add a pile of them for it.

I'm currently adding this to pl/pythonu with an aim to prototype type io
functions for a new type.

The PLs aren't meant to be usable as I/O functions. cstring is not the
problem there, it's access to the bit-level representation of the other
datatype. It's hard for me to see how you'd make the above work without
circularity, ie the PL manager would end up recursively calling itself
trying to construct or deconstruct the value.

regards, tom lane

#3Hannu Krosing
hannu@krosing.net
In reply to: Tom Lane (#2)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

On 10/10/2012 02:58 PM, Tom Lane wrote:

Hannu Krosing <hannu@2ndQuadrant.com> writes:

Is the lack of support of cstring in PLs just laziness/ovelook or is
there a good
reason why PL languages do not support cstring type arguments and return
values ?

In general I don't think we should encourage the use of cstring as a
user-level data type. The number of text-like types in the system is
already enough to confuse users, and this one brings no redeeming social
value to the party. Besides which, it has essentially no built-in
operators, and I *don't* want to have to add a pile of them for it.

I'm currently adding this to pl/pythonu with an aim to prototype type io
functions for a new type.

The PLs aren't meant to be usable as I/O functions. cstring is not the
problem there, it's access to the bit-level representation of the other
datatype.

I don't understand where you see the problem here, python (and
I guess also most other pl-languages, possibly with the exception of
pl/pgsql) are well capable of accessing raw data.

It's hard for me to see how you'd make the above work without
circularity, ie the PL manager would end up recursively calling itself
trying to construct or deconstruct the value.

Again, could you be a bit more specific.
Recursion itself should not be a problem (except maybe for performance).
We already support calling pl* functions from inside other pl functions at
least via executing SELECT "plfunc()" .

Show quoted text

regards, tom lane

#4Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Tom Lane (#2)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

On 10.10.2012 16:58, Tom Lane wrote:

Hannu Krosing<hannu@2ndQuadrant.com> writes:

Is the lack of support of cstring in PLs just laziness/ovelook or is
there a good
reason why PL languages do not support cstring type arguments and return
values ?

In general I don't think we should encourage the use of cstring as a
user-level data type. The number of text-like types in the system is
already enough to confuse users, and this one brings no redeeming social
value to the party. Besides which, it has essentially no built-in
operators, and I *don't* want to have to add a pile of them for it.

I'm currently adding this to pl/pythonu with an aim to prototype type io
functions for a new type.

The PLs aren't meant to be usable as I/O functions. cstring is not the
problem there, it's access to the bit-level representation of the other
datatype. It's hard for me to see how you'd make the above work without
circularity, ie the PL manager would end up recursively calling itself
trying to construct or deconstruct the value.

I don't see the problem. The input function converts a text string to an
opaque chunk of bytes, and the output function does the reverse. In a
nutshell, an input function is like this:

bytea mytype_in(text_representation text)

and the output function is like this:

text mytype_out(internal_representation bytea)

In reality, of course, input functions take a cstring as argument, not
text, and returns a "mytype" datum, not bytea. But I don't see why we
couldn't allow the above signatures with text/bytea instead. That would
make it clear to the PL how to deal with the datums.

I've wanted to allow writing i/o functions in non-C languages for a long
time as well, but never got around to do anything about it. Custom
datatypes are really powerful, but as soon as you have to write C code,
that raises the bar significantly. I/O functions written in, say,
PL/pgSQL would be an order of magnitude slower than ones written in C,
but for many applications it would be OK.

- Heikki

#5Tom Lane
tgl@sss.pgh.pa.us
In reply to: Heikki Linnakangas (#4)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

On 10.10.2012 16:58, Tom Lane wrote:

The PLs aren't meant to be usable as I/O functions. cstring is not the
problem there, it's access to the bit-level representation of the other
datatype. It's hard for me to see how you'd make the above work without
circularity, ie the PL manager would end up recursively calling itself
trying to construct or deconstruct the value.

I don't see the problem. The input function converts a text string to an
opaque chunk of bytes, and the output function does the reverse. In a
nutshell, an input function is like this:

bytea mytype_in(text_representation text)

and the output function is like this:

text mytype_out(internal_representation bytea)

OK, that would work as long as you were willing to confine the feature
to types that are representation-equivalent to bytea. However, there's
a small problem, which is that I/O functions aren't actually *declared*
that way.

In reality, of course, input functions take a cstring as argument, not
text, and returns a "mytype" datum, not bytea. But I don't see why we
couldn't allow the above signatures with text/bytea instead.

Because you'd totally destroy the tiny modicum of error checking that
exists now on whether CREATE TYPE's function arguments are sane.

I've wanted to allow writing i/o functions in non-C languages for a long
time as well, but never got around to do anything about it.

If we're going to do that, it should not be done by blowing a truck-size
hole in the semantics of I/O functions. I would prefer to see some
kind of wart added to the PL manager that says, in effect, "treat this
argument or result as bytea even though it's declared differently".
For one thing, that would scale easily to cases that are not
representation-compatible to bytea but something else (eg int4), so long
as the underlying language had an equivalent native type.

regards, tom lane

#6Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#3)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

Hannu Krosing <hannu@krosing.net> writes:

On 10/10/2012 02:58 PM, Tom Lane wrote:

It's hard for me to see how you'd make the above work without
circularity, ie the PL manager would end up recursively calling itself
trying to construct or deconstruct the value.

Again, could you be a bit more specific.

If you try to write "foo_out(foo) returns cstring" in Python, the
first thing plpython will try to do is convert the argument value
to a Python object, which for a non-built-in type such as "foo" is
going to reduce to conversion to text, which will result in ...
you guessed it ... calling foo_out to convert the argument value
to text. Lather, rinse, repeat till stack overflow.

As I was mentioning to Heikki, it's possible that you could work around
that by somehow telling plpython to do the argument conversion as though
the argument were of some bit-compatible built-in type rather than foo.
But without some such type cheat you can't write an I/O function in a
PL, and it's not the cstring end of it that's the problem.

regards, tom lane

#7Hannu Krosing
hannu@krosing.net
In reply to: Tom Lane (#6)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

On 10/10/2012 03:46 PM, Tom Lane wrote:

Hannu Krosing <hannu@krosing.net> writes:

On 10/10/2012 02:58 PM, Tom Lane wrote:

It's hard for me to see how you'd make the above work without
circularity, ie the PL manager would end up recursively calling itself
trying to construct or deconstruct the value.

Again, could you be a bit more specific.

If you try to write "foo_out(foo) returns cstring" in Python, the
first thing plpython will try to do is convert the argument value
to a Python object, which for a non-built-in type such as "foo" is
going to reduce to conversion to text,

This should still be solvable _inside_ pl/python handler with a little
hackery.

My request for "reasons not to do it" was more about things in
pl manager or postgresql itself.

One way would be to check that we are in an any --> cstring
function - perhaps just by setting some static flag et entry and resetting
it at exit - and pass the original byte representation as "bytes" (or
string for py2.x)
or wrapped into plpy specific RawPg(rawinput) object.

Show quoted text

which will result in ...
you guessed it ... calling foo_out to convert the argument value
to text. Lather, rinse, repeat till stack overflow.

As I was mentioning to Heikki, it's possible that you could work around
that by somehow telling plpython to do the argument conversion as though
the argument were of some bit-compatible built-in type rather than foo.
But without some such type cheat you can't write an I/O function in a
PL, and it's not the cstring end of it that's the problem.

regards, tom lane

#8Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hannu Krosing (#7)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

Hannu Krosing <hannu@krosing.net> writes:

One way would be to check that we are in an any --> cstring
function - perhaps just by setting some static flag et entry and resetting
it at exit - and pass the original byte representation as "bytes" (or
string for py2.x)

Totally aside from the ugliness of driving that off the *other* end
being cstring, it seems quite insufficient to me. For example, if the
data type in question is toastable, you don't really want to leave the
Python code with the problem of detoasting a toasted value. Even if
it's just an int, your proposal saddles the Python code with enddianness
problems.

I think my suggestion of a way to pretend the argument or result is of
some specified other type for conversion purposes is quite a lot superior.
In the toastable-type case, referencing bytea would be enough to get the
Python code out from under detoasting and length-word management. There
might also be cases where the new type is really a skin over some
built-in type, and you can leverage that type's I/O behavior to simplify
what the Python code has to do.

regards, tom lane

#9Hannu Krosing
hannu@krosing.net
In reply to: Tom Lane (#8)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

On 10/10/2012 04:34 PM, Tom Lane wrote:

Hannu Krosing <hannu@krosing.net> writes:

One way would be to check that we are in an any --> cstring
function - perhaps just by setting some static flag et entry and resetting
it at exit - and pass the original byte representation as "bytes" (or
string for py2.x)

Totally aside from the ugliness of driving that off the *other* end
being cstring,

The cstring case seems trivial - you just have to omit the initial
conversion
to cstring that is happening now for most types and only do only the second
part which is the cstring_to_python or cstring_to_postgresql conversion
depending on if it is an input or output function.

it seems quite insufficient to me. For example, if the
data type in question is toastable, you don't really want to leave the
Python code with the problem of detoasting a toasted value. Even if
it's just an int, your proposal saddles the Python code with enddianness
problems.

I think my suggestion of a way to pretend the argument or result is of
some specified other type for conversion purposes is quite a lot superior.

Agreed, and it even seems that we can reuse current existing basetype
support present in CREATE TYPE and pg_proc. If not functionally then at
least for storing the equivalent type info.

Show quoted text

In the toastable-type case, referencing bytea would be enough to get the
Python code out from under detoasting and length-word management. There
might also be cases where the new type is really a skin over some
built-in type, and you can leverage that type's I/O behavior to simplify
what the Python code has to do.

regards, tom lane

#10Dimitri Fontaine
dimitri@2ndQuadrant.fr
In reply to: Heikki Linnakangas (#4)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I've wanted to allow writing i/o functions in non-C languages for a long
time as well, but never got around to do anything about it. Custom datatypes
are really powerful, but as soon as you have to write C code, that raises
the bar significantly. I/O functions written in, say, PL/pgSQL would be an
order of magnitude slower than ones written in C, but for many applications
it would be OK.

Do you want a crazy idea now? Yes, I do mean Yet Another One.

I'm thinking about what it would take to have a new PL/C language where
the backend would actually compile and link/load the C code at CREATE
FUNCTION time, using dynamic code generation techniques.

That would allow writing functions in C and not have to ship a binary
executable file on the system, which would solve a bunch of problems.
With that tool and this use case, you could simply ship inline your C
coded IO functions in the middle of the PL/pythonu extension, using the
exact same mechanisms.

In the more general view of our offerings, that would fix C coded
extensions for Hot Standby, for one thing.

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

#11Pavel Stehule
pavel.stehule@gmail.com
In reply to: Dimitri Fontaine (#10)
Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?

2012/10/11 Dimitri Fontaine <dimitri@2ndquadrant.fr>:

Heikki Linnakangas <hlinnakangas@vmware.com> writes:

I've wanted to allow writing i/o functions in non-C languages for a long
time as well, but never got around to do anything about it. Custom datatypes
are really powerful, but as soon as you have to write C code, that raises
the bar significantly. I/O functions written in, say, PL/pgSQL would be an
order of magnitude slower than ones written in C, but for many applications
it would be OK.

Do you want a crazy idea now? Yes, I do mean Yet Another One.

I'm thinking about what it would take to have a new PL/C language where
the backend would actually compile and link/load the C code at CREATE
FUNCTION time, using dynamic code generation techniques.

That would allow writing functions in C and not have to ship a binary
executable file on the system, which would solve a bunch of problems.
With that tool and this use case, you could simply ship inline your C
coded IO functions in the middle of the PL/pythonu extension, using the
exact same mechanisms.

In the more general view of our offerings, that would fix C coded
extensions for Hot Standby, for one thing.

long time I am thinking about it. I would to use our embedded C - but
replace libpq by SPI. Other idea - compile PL/pgSQL to C - and ...

Regards

Pavel

Show quoted text

Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Hannu Krosing
hannu@2ndQuadrant.com
In reply to: Tom Lane (#8)
2 attachment(s)
WIP patch for pl/python cstring and type io support (was: Re: Is there a good reason why PL languages do not support cstring type arguments and return values ?)

On 10/10/2012 05:34 PM, Tom Lane wrote:

Hannu Krosing <hannu@krosing.net> writes:

One way would be to check that we are in an any --> cstring
function - perhaps just by setting some static flag et entry and resetting
it at exit - and pass the original byte representation as "bytes" (or
string for py2.x)

Totally aside from the ugliness of driving that off the *other* end
being cstring, it seems quite insufficient to me.

Please find attached a patch, which solves the typeio function recursion
problem by simply testing if the function we are currently in is a type-io
function (fn_oid == argTypeStruct->typoutput ... )

This is definitely WIP, put here just to verify the approach is mostly sane.
Also there are not integrated tests in the patch or docs yet.
See attached test-pytypeio.sql for sample usage.

It is usable for simple cases, like non-toastable fixed length types
- both pass by value and pass py reference - and
non-toastable varlen types. It has no expicit support yet for any
more fancy things like toasting or new short varlen headers.

It also has the beginnings of support for type "internal" so that also
send and receive functions can be written in plpython.

Some of the work also went into accepting shell types so that you
actually can define typeio functions,

For example, if the
data type in question is toastable, you don't really want to leave the
Python code with the problem of detoasting a toasted value.

The next think I'll do is to fashion my raw input/output functions for
toastable cases after bytea. Currently they are just tested for simple
"old postgresql varlen type".

Even if
it's just an int, your proposal saddles the Python code with enddianness
problems.

This can also be seen as a feature, that is you _can_ encode the binary
exactly as you like. For example you have 4-byte strings encoded in
int4-sized pass-by value chunks. or 16 digit decimals encoded as 16
4-bit nibbles.

And endianness dead simple to do in pythons struct module, as
it's just one char prefix in format string.

I think my suggestion of a way to pretend the argument or result is of
some specified other type for conversion purposes is quite a lot superior.
In the toastable-type case, referencing bytea would be enough to get the
Python code out from under detoasting and length-word management.

Will look into it.

----
Hannu Krosing

Attachments:

plpython-cstring-and-typeio-WIP.difftext/x-patch; name=plpython-cstring-and-typeio-WIP.diffDownload
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index a28cb9a..17e9dd1 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -193,6 +193,16 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("trigger functions can only be called as triggers")));
+				else if (procStruct->prorettype == CSTRINGOID){
+					/* do the real work */
+					PLy_output_datum_func(&proc->result, rvTypeTup);
+				}
+				else if (rvTypeStruct->typisdefined == false){
+					/* leave shell type as uninitialised */
+					proc->result.out.d.typoid = procStruct->prorettype;
+					proc->result.out.d.typmod = -1;
+					proc->result.is_rowtype = -1; 
+				}
 				else if (procStruct->prorettype != VOIDOID &&
 						 procStruct->prorettype != RECORDOID)
 					ereport(ERROR,
@@ -212,6 +222,10 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 				proc->result.out.d.typmod = -1;
 				proc->result.is_rowtype = 2;
 			}
+			else if ((fn_oid == rvTypeStruct->typinput)||
+					 (fn_oid == rvTypeStruct->typreceive)) {
+				PLy_output_datum_func_direct(&proc->result, rvTypeTup);
+			}
 			else
 			{
 				/* do the real work */
@@ -276,7 +290,22 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 				switch (argTypeStruct->typtype)
 				{
 					case TYPTYPE_PSEUDO:
-						/* Disallow pseudotype argument */
+						if ((types[i] == CSTRINGOID) ||
+							(types[i] == INTERNALOID)){
+							PLy_input_datum_func(&(proc->args[pos]),
+												types[i],
+												argTypeTup);
+							break;
+						}
+						if (argTypeStruct->typisdefined == false) {
+							/*
+							 * should be safe to leave shell type uninitialised
+							 * by the time the function is called, it will be a full type
+							 */
+							proc->args[pos].is_rowtype = -1;
+							break;
+						}
+						/* Disallow other pseudotype arguments */
 						ereport(ERROR,
 								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 						  errmsg("PL/Python functions cannot accept type %s",
@@ -287,6 +316,14 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
 						proc->args[pos].is_rowtype = 2;
 						break;
 					default:
+						/* type io functions do direct conversion  */
+						if ((fn_oid == argTypeStruct->typoutput) ||
+							(fn_oid == argTypeStruct->typsend)) {
+							PLy_input_datum_func_direct(&(proc->args[pos]),
+												 types[i],
+												 argTypeTup);
+							break;
+						}
 						PLy_input_datum_func(&(proc->args[pos]),
 											 types[i],
 											 argTypeTup);
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 8f2367d..bb09472 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -41,8 +41,11 @@ static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d);
 static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
 static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
 static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyString_FromCString(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromInternal(PLyDatumToOb *arg, Datum d);
 static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
 static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyBytes_FromRawType(PLyDatumToOb *arg, Datum d);
 
 /* conversion from Python objects to Datums */
 static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
@@ -51,6 +54,9 @@ static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *pl
 static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
 static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
 
+static Datum PLyBytes_ToRawType(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
+
+
 /* conversion from Python objects to composite Datums (used by triggers and SRFs) */
 static Datum PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string);
 static Datum PLyMapping_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *mapping);
@@ -360,6 +366,107 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
 	return datum;
 }
 
+void
+PLy_output_datum_func_direct(PLyTypeInfo *ti, HeapTuple typeTup)
+{
+	PLyObToDatum *arg = &(ti->out.d);
+	Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+	
+	perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
+	arg->typoid = HeapTupleGetOid(typeTup);
+	arg->typmod = -1;
+	arg->typioparam = getTypeIOParam(typeTup);
+	arg->typbyval = typeStruct->typbyval;
+	arg->typlen = typeStruct->typlen;
+	arg->typalign = typeStruct->typalign;
+	
+	arg->func = PLyBytes_ToRawType;
+
+}
+
+/*
+ * Convert a Python bytes to a PostgreSQL bytea datum.  This doesn't
+ * go through the generic conversion function to circumvent problems
+ * with embedded nulls.  And it's faster this way.
+ */
+static Datum
+PLyBytes_ToRawType(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+	// copy of PLyObjecttoBytea now, will change if basic functioning is checked ok
+	PyObject   *volatile plrv_so = NULL;
+	Datum		rv;
+
+	Assert(plrv != Py_None);
+
+	plrv_so = PyObject_Bytes(plrv);
+	if (!plrv_so)
+		PLy_elog(ERROR, "could not create bytes representation of Python object");
+
+	PG_TRY();
+	{
+		char	   *plrv_sc = PyBytes_AsString(plrv_so);
+		size_t		len = PyBytes_Size(plrv_so);
+        size_t      size;
+		void	   *result;
+
+		/* return varlen types as bytea */
+		if (arg->typlen == -1) {
+			size = len + VARHDRSZ;
+			result = palloc(size);
+			SET_VARSIZE(result, size);
+			memcpy(VARDATA(result), plrv_sc, len);
+			rv = PointerGetDatum(result);
+		}
+		else {
+			size = arg->typlen;
+			if (len != size)
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_EXCEPTION),
+						 errmsg("PL/Python type output conversion data length mismatch"),
+						 errdetail("Python length %d, PostgreSQL type length %d", (int)len, (int)size)));
+			if (arg->typbyval){
+				switch (size) {
+					case 1:
+						rv = ((Datum) SET_1_BYTE(*(uint8*)plrv_sc));
+						break;
+					case 2:
+						rv = ((Datum) SET_2_BYTES(*(int16*)plrv_sc));
+						break;
+					case 4:
+						rv = ((Datum) SET_4_BYTES(*(int32*)plrv_sc));
+						break;
+					case 8:
+						rv = ((Datum) SET_8_BYTES(*(int64*)plrv_sc));
+						break;
+					default:
+						ereport(ERROR,
+								(errcode(ERRCODE_DATA_EXCEPTION),
+								 errmsg("PL/Python type output conversion not supported for pass-by-value length: %d", (int)size)
+								 ));
+				}
+			}
+			else {
+				result = palloc(size);
+				memcpy(result, plrv_sc, len);
+				rv = PointerGetDatum(result);
+			}
+		}
+	}
+	PG_CATCH();
+	{
+		Py_XDECREF(plrv_so);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	Py_XDECREF(plrv_so);
+
+	if (get_typtype(arg->typoid) == TYPTYPE_DOMAIN) // ToDo: this should be probably cached as arg->typtype
+		domain_check(rv, false, arg->typoid, &arg->typfunc.fn_extra, arg->typfunc.fn_mcxt);
+	
+	return rv;
+}
+
 static void
 PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
 {
@@ -422,6 +529,49 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
 	}
 }
 
+/*
+ * convert PostgreSQL binary representation directly to python.
+ * This is used by types output() and send() functions which
+ * must manage the actual conversion
+ */
+
+void
+PLy_input_datum_func_direct(PLyTypeInfo *ti, Oid typeOid, HeapTuple typeTup)
+{
+	PLyDatumToOb *arg = &(ti->in.d);
+	Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+	/* Get the type's conversion information */
+	perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
+	arg->typoid = HeapTupleGetOid(typeTup);
+	arg->typmod = -1;
+	arg->typioparam = getTypeIOParam(typeTup);
+	arg->typbyval = typeStruct->typbyval;
+	arg->typlen = typeStruct->typlen;
+	arg->typalign = typeStruct->typalign;
+	
+	arg->func = PLyBytes_FromRawType;
+
+}
+
+/*
+ * convert postgresql datum directly to python bytes()
+ * used in type-io functions where real conversion is done in python
+ */
+static PyObject *
+PLyBytes_FromRawType(PLyDatumToOb *arg, Datum d)
+{
+	if (arg->typbyval) {
+		return PyBytes_FromStringAndSize((char*) &d, arg->typlen);
+	}
+	else {
+		bytea	   *var = DatumGetByteaP(d);
+		char	   *bytes = VARDATA(var);
+		size_t		size = (arg->typlen > 0)? arg->typlen: VARSIZE(var) - VARHDRSZ;
+		return PyBytes_FromStringAndSize(bytes, size);
+	}
+}
+
 static void
 PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
 {
@@ -467,6 +617,12 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
 		case BYTEAOID:
 			arg->func = PLyBytes_FromBytea;
 			break;
+		case CSTRINGOID:
+			arg->func = PLyString_FromCString;
+			break;
+		case INTERNALOID:
+			arg->func = PLyLong_FromInternal;
+			break;
 		default:
 			arg->func = PLyString_FromDatum;
 			break;
@@ -567,6 +723,23 @@ PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
 }
 
 static PyObject *
+PLyString_FromCString(PLyDatumToOb *arg, Datum d)
+{
+	const char *txt = DatumGetCString(d);
+
+	return PyBytes_FromString(txt);
+}
+
+static PyObject *
+PLyLong_FromInternal(PLyDatumToOb *arg, Datum d)
+{
+	/* convert pgsql type 'internal' to integer so it can be used by ctypes */
+	void* p = DatumGetPointer(d);
+
+	return PyLong_FromVoidPtr(p);
+}
+
+static PyObject *
 PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
 {
 	char	   *x = OutputFunctionCall(&arg->typfunc, d);
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
index 82e472a..4648ddf 100644
--- a/src/pl/plpython/plpy_typeio.h
+++ b/src/pl/plpython/plpy_typeio.h
@@ -94,6 +94,9 @@ extern void PLy_typeinfo_dealloc(PLyTypeInfo *arg);
 extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup);
 extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup);
 
+extern void PLy_input_datum_func_direct(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup);
+extern void PLy_output_datum_func_direct(PLyTypeInfo *arg, HeapTuple typeTup);
+
 extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
 extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
 
test-pytypeio.sqltext/x-sql; name=test-pytypeio.sqlDownload