On 06/02/11 10:54, Jan Urbański wrote: > On 04/02/11 18:10, Hitoshi Harada wrote: >> 2011/1/11 Jan Urbański <wulc...@wulczer.org>: >>> Here's a patch that adds a few PL/Python functions for quoting strings. >>> It's an incremental patch on top of the plpython-refactor patch sent in >>> http://archives.postgresql.org/message-id/4d135170.3080...@wulczer.org. >>> >>> Git branch for this patch: >>> https://github.com/wulczer/postgres/tree/functions >>> >>> The new functions are plpy.quote_literal, plpy.quote_nullable and >>> plpy.quote_ident, and work just like their sql or plperl equivalents. >>> >> >> I reviewed this. >> >> The patch applies and compiles cleanly and all the tests are passed. >> The patch adds 3 functions which works as the corresponding SQL >> functions. The test is enough, without any additional docs. No >> feature/performance issues found. >> >> I mark this "Reader for Committer". > > Thanks! > > I guess a short paragraph in the Utility Functions section of the > PL/Python docs would be in order, I'll try to add it today.
Added docs and merged with master.
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index e05c293..8a995b2 100644 *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** $$ LANGUAGE plpythonu; *** 997,1002 **** --- 997,1018 ---- <xref linkend="guc-client-min-messages"> configuration variables. See <xref linkend="runtime-config"> for more information. </para> + <para> + Another set of utility functions + are <literal>plpy.quote_literal(<replaceable>string</>)</literal>, + <literal>plpy.quote_nullable(<replaceable>string</>)</literal> + and <literal>plpy.quote_ident(<replaceable>string</>)</literal>. They are + equivalent to the builtin quoting functions described + in <xref linkend="functions-string">. They are useful when constructing + ad-hoc queries. A PL/Python equivalent of dynamic SQL + from <xref linkend="plpgsql-quote-literal-example"> would be: + <programlisting> + plpy.execute("UPDATE tbl SET %s = %s where key = %s" % ( + plpy.quote_ident(colname), + plpy.quote_nullable(newvalue), + plpy.quote_literal(keyvalue))) + </programlisting> + </para> </sect1> <sect1 id="plpython-envar"> diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 16d78ae..292e360 100644 *** a/src/pl/plpython/Makefile --- b/src/pl/plpython/Makefile *************** REGRESS = \ *** 79,84 **** --- 79,85 ---- plpython_types \ plpython_error \ plpython_unicode \ + plpython_quote \ plpython_drop # where to find psql for running the tests PSQLDIR = $(bindir) diff --git a/src/pl/plpython/expected/plpython_quote.out b/src/pl/plpython/expected/plpython_quote.out index ...b33ee3f . *** a/src/pl/plpython/expected/plpython_quote.out --- b/src/pl/plpython/expected/plpython_quote.out *************** *** 0 **** --- 1,87 ---- + -- test quoting functions + CREATE FUNCTION quote(t text, how text) RETURNS text AS $$ + if how == "literal": + return plpy.quote_literal(t) + elif how == "nullable": + return plpy.quote_nullable(t) + elif how == "ident": + return plpy.quote_ident(t) + else: + raise plpy.Error("unrecognized quote type %s" % how) + $$ LANGUAGE plpythonu; + SELECT quote(t, 'literal') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + ('xyzv')) AS v(t); + quote + ----------- + 'abc' + 'a''bc' + '''abc''' + '' + '''' + 'xyzv' + (6 rows) + + SELECT quote(t, 'nullable') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + (NULL)) AS v(t); + quote + ----------- + 'abc' + 'a''bc' + '''abc''' + '' + '''' + NULL + (6 rows) + + SELECT quote(t, 'ident') FROM (VALUES + ('abc'), + ('a b c'), + ('a " ''abc''')) AS v(t); + quote + -------------- + abc + "a b c" + "a "" 'abc'" + (3 rows) + + -- test errors + SELECT quote(NULL::text, 'literal'); + ERROR: TypeError: argument 1 must be string, not None + CONTEXT: PL/Python function "quote" + SELECT quote(NULL::text, 'ident'); + ERROR: TypeError: argument 1 must be string, not None + CONTEXT: PL/Python function "quote" + SELECT quote('abc', 'random'); + ERROR: plpy.Error: unrecognized quote type random + CONTEXT: PL/Python function "quote" + DO $$ plpy.quote_literal(3) $$ LANGUAGE plpythonu; + ERROR: TypeError: argument 1 must be string, not int + CONTEXT: PL/Python anonymous code block + DO $$ plpy.quote_literal(None) $$ LANGUAGE plpythonu; + ERROR: TypeError: argument 1 must be string, not None + CONTEXT: PL/Python anonymous code block + DO $$ plpy.quote_literal({'a': 'b'}) $$ LANGUAGE plpythonu; + ERROR: TypeError: argument 1 must be string, not dict + CONTEXT: PL/Python anonymous code block + DO $$ plpy.quote_literal() $$ LANGUAGE plpythonu; + ERROR: TypeError: function takes exactly 1 argument (0 given) + CONTEXT: PL/Python anonymous code block + DO $$ plpy.quote_literal(1, 2) $$ LANGUAGE plpythonu; + ERROR: TypeError: function takes exactly 1 argument (2 given) + CONTEXT: PL/Python anonymous code block + DO $$ plpy.quote_nullable([1, 2]) $$ LANGUAGE plpythonu; + ERROR: TypeError: argument 1 must be string or None, not list + CONTEXT: PL/Python anonymous code block + DO $$ plpy.quote_ident(1.5) $$ LANGUAGE plpythonu; + ERROR: TypeError: argument 1 must be string, not float + CONTEXT: PL/Python anonymous code block diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out index d92c987..961f6c0 100644 *** a/src/pl/plpython/expected/plpython_test.out --- b/src/pl/plpython/expected/plpython_test.out *************** contents.sort() *** 43,51 **** return ", ".join(contents) $$ LANGUAGE plpythonu; select module_contents(); ! module_contents ! ------------------------------------------------------------------------------------------- ! Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, warning (1 row) CREATE FUNCTION elog_test() RETURNS void --- 43,51 ---- return ", ".join(contents) $$ LANGUAGE plpythonu; select module_contents(); ! module_contents ! --------------------------------------------------------------------------------------------------------------------------------------- ! Error, Fatal, SPIError, debug, error, execute, fatal, info, log, notice, prepare, quote_ident, quote_literal, quote_nullable, warning (1 row) CREATE FUNCTION elog_test() RETURNS void diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index fff7de7..39c7e53 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** static PyObject *PLy_spi_execute_query(c *** 2558,2563 **** --- 2558,2567 ---- static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, long); static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int); + static PyObject *PLy_quote_literal(PyObject *, PyObject *); + static PyObject *PLy_quote_nullable(PyObject *, PyObject *); + static PyObject *PLy_quote_ident(PyObject *, PyObject *); + static PyMethodDef PLy_plan_methods[] = { {"status", PLy_plan_status, METH_VARARGS, NULL}, *************** static PyMethodDef PLy_methods[] = { *** 2672,2677 **** --- 2676,2688 ---- */ {"execute", PLy_spi_execute, METH_VARARGS, NULL}, + /* + * escaping strings + */ + {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL}, + {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL}, + {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} }; *************** PLy_output(volatile int level, PyObject *** 3608,3613 **** --- 3619,3683 ---- return Py_None; } + static PyObject * + PLy_quote_literal(PyObject *self, PyObject *args) + { + char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + quoted = quote_literal_cstr(str); + ret = PyString_FromString(quoted); + pfree(quoted); + + return ret; + } + + static PyObject * + PLy_quote_nullable(PyObject *self, PyObject *args) + { + char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "z", &str)) + return NULL; + + if (str == NULL) + return PyString_FromString("NULL"); + + quoted = quote_literal_cstr(str); + ret = PyString_FromString(quoted); + pfree(quoted); + + return ret; + } + + static PyObject * + PLy_quote_ident(PyObject *self, PyObject *args) + { + char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s", &str)) + return NULL; + + /* cast to char * to avoid a "discards qualifier" warning */ + quoted = (char *) quote_identifier(str); + ret = PyString_FromString(quoted); + /* + * quote_identifier sometimes returns a palloc'd string, and sometimes + * just the pointer passed + */ + if (quoted != str) + pfree(quoted); + + return ret; + } /* * Get the name of the last procedure called by the backend (the diff --git a/src/pl/plpython/sql/plpython_quote.sql b/src/pl/plpython/sql/plpython_quote.sql index ...15cab3c . *** a/src/pl/plpython/sql/plpython_quote.sql --- b/src/pl/plpython/sql/plpython_quote.sql *************** *** 0 **** --- 1,45 ---- + -- test quoting functions + + CREATE FUNCTION quote(t text, how text) RETURNS text AS $$ + if how == "literal": + return plpy.quote_literal(t) + elif how == "nullable": + return plpy.quote_nullable(t) + elif how == "ident": + return plpy.quote_ident(t) + else: + raise plpy.Error("unrecognized quote type %s" % how) + $$ LANGUAGE plpythonu; + + SELECT quote(t, 'literal') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + ('xyzv')) AS v(t); + + SELECT quote(t, 'nullable') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + (NULL)) AS v(t); + + SELECT quote(t, 'ident') FROM (VALUES + ('abc'), + ('a b c'), + ('a " ''abc''')) AS v(t); + + -- test errors + SELECT quote(NULL::text, 'literal'); + SELECT quote(NULL::text, 'ident'); + SELECT quote('abc', 'random'); + DO $$ plpy.quote_literal(3) $$ LANGUAGE plpythonu; + DO $$ plpy.quote_literal(None) $$ LANGUAGE plpythonu; + DO $$ plpy.quote_literal({'a': 'b'}) $$ LANGUAGE plpythonu; + DO $$ plpy.quote_literal() $$ LANGUAGE plpythonu; + DO $$ plpy.quote_literal(1, 2) $$ LANGUAGE plpythonu; + DO $$ plpy.quote_nullable([1, 2]) $$ LANGUAGE plpythonu; + DO $$ plpy.quote_ident(1.5) $$ LANGUAGE plpythonu;
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers