From 49461c2c39debdb0c96201f733b0d19da28b70ac Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 1 Jan 2017 18:04:51 +0100 Subject: [PATCH] More doc love for the sql module --- doc/src/faq.rst | 18 +++++++++++++++ doc/src/sql.rst | 4 ++-- doc/src/usage.rst | 7 +++--- lib/sql.py | 58 +++++++++++++++++++++++++++++++---------------- 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/doc/src/faq.rst b/doc/src/faq.rst index 89d8a639..fb7b33dd 100644 --- a/doc/src/faq.rst +++ b/doc/src/faq.rst @@ -151,6 +151,24 @@ Psycopg converts :sql:`json` values into Python objects but :sql:`jsonb` values See :ref:`adapt-json` for further details. +.. _faq-identifier: +.. cssclass:: faq + +How can I pass field/table names to a query? + The arguments in the `~cursor.execute()` methods can only represent data + to pass to the query: they cannot represent a table or field name:: + + # This doesn't work + cur.execute("insert into %s values (%s)", ["my_table", 42]) + + If you want to build a query dynamically you can use the objects exposed + by the `psycopg2.sql` module:: + + cur.execute( + sql.SQL("insert into %s values (%%s)") % [sql.Identifier("my_table")], + [42]) + + .. _faq-bytea-9.0: .. cssclass:: faq diff --git a/doc/src/sql.rst b/doc/src/sql.rst index 1328a867..f044372d 100644 --- a/doc/src/sql.rst +++ b/doc/src/sql.rst @@ -9,7 +9,7 @@ The module contains objects and functions useful to generate SQL dynamically, in a convenient and safe way. SQL identifiers (e.g. names of tables and -fields) cannot be passed to the `~cursor.execute()` function like query +fields) cannot be passed to the `~cursor.execute()` method like query arguments:: # This will not work @@ -45,7 +45,7 @@ in the presence of a table or field name with containing characters to escape, or will present a potentially exploitable weakness. The objects exposed by the `!psycopg2.sql` module allow generating SQL -statements on the fly, separating clearly the variable parts in the statement +statements on the fly, separating clearly the variable parts of the statement from the query parameters:: from psycopg2 import sql diff --git a/doc/src/usage.rst b/doc/src/usage.rst index d9fea755..1366485a 100644 --- a/doc/src/usage.rst +++ b/doc/src/usage.rst @@ -132,9 +132,10 @@ query: >>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct >>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct -- Only variable values should be bound via this method: it shouldn't be used - to set table or field names. For these elements, ordinary string formatting - should be used before running `~cursor.execute()`. +- Only query values should be bound via this method: it shouldn't be used to + merge table or field names to the query. If you need to generate dynamically + an SQL query (for instance choosing dynamically a table name) you can use + the facilities provided by the `psycopg2.sql` module. diff --git a/lib/sql.py b/lib/sql.py index fc946b5d..ff7faa1a 100644 --- a/lib/sql.py +++ b/lib/sql.py @@ -78,9 +78,10 @@ class Composed(Composable): Example:: - >>> sql.Composed([sql.SQL("insert into "), sql.Identifier("table")]) \\ - ... .as_string(conn) - 'insert into "table"' + >>> comp = sql.Composed( + ... [sql.SQL("insert into "), sql.Identifier("table")]) + >>> print(comp.as_string(conn)) + insert into "table" .. automethod:: join """ @@ -119,8 +120,8 @@ class Composed(Composable): Example:: >>> fields = sql.Identifier('foo') + sql.Identifier('bar') # a Composed - >>> fields.join(', ').as_string(conn) - '"foo", "bar"' + >>> print(fields.join(', ').as_string(conn)) + "foo", "bar" """ if isinstance(joiner, basestring): @@ -155,9 +156,8 @@ class SQL(Composable): >>> query = sql.SQL("select %s from %s") % [ ... sql.SQL(', ').join([sql.Identifier('foo'), sql.Identifier('bar')]), ... sql.Identifier('table')] - >>> query.as_string(conn) - select "foo", "bar" from "table"' - + >>> print(query.as_string(conn)) + select "foo", "bar" from "table" .. automethod:: join """ @@ -184,8 +184,8 @@ class SQL(Composable): Example:: >>> snip - sql.SQL(', ').join(map(sql.Identifier, ['foo', 'bar', 'baz'])) - >>> snip.as_string(conn) - '"foo", "bar", "baz"' + >>> print(snip.as_string(conn)) + "foo", "bar", "baz" """ if isinstance(seq, Composed): seq = seq._seq @@ -214,6 +214,15 @@ class Identifier(Composable): .. __: https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html# \ SQL-SYNTAX-IDENTIFIERS + + Example:: + + >>> t1 = sql.Identifier("foo") + >>> t2 = sql.Identifier("ba'r") + >>> t3 = sql.Identifier('ba"z') + >>> print(sql.SQL(', ').join([t1, t2, t3]).as_string(conn)) + "foo", "ba'r", "ba""z" + """ def __init__(self, string): if not isinstance(string, basestring): @@ -239,6 +248,14 @@ class Literal(Composable): The string returned by `!as_string()` follows the normal :ref:`adaptation rules ` for Python objects. + Example:: + + >>> s1 = sql.Literal("foo") + >>> s2 = sql.Literal("ba'r") + >>> s3 = sql.Literal(42) + >>> print(sql.SQL(', ').join([s1, s2, s3]).as_string(conn)) + 'foo', 'ba''r', 42 + """ def __init__(self, wrapped): self._wrapped = wrapped @@ -277,17 +294,20 @@ class Placeholder(Composable): Examples:: - >>> (sql.SQL("insert into table (%s) values (%s)") % [ - ... sql.SQL(', ').join(map(sql.Identifier, names)), - ... sql.SQL(', ').join(sql.Placeholder() * 3) - ... ]).as_string(conn) - 'insert into table ("foo", "bar", "baz") values (%s, %s, %s)' + >>> names = ['foo', 'bar', 'baz'] - >>> (sql.SQL("insert into table (%s) values (%s)") % [ + >>> q1 = sql.SQL("insert into table (%s) values (%s)") % [ ... sql.SQL(', ').join(map(sql.Identifier, names)), - ... sql.SQL(', ').join(map(sql.Placeholder, names)) - ... ]).as_string(conn) - 'insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s)' + ... sql.SQL(', ').join(sql.Placeholder() * 3)] + >>> print(q1.as_string(conn)) + insert into table ("foo", "bar", "baz") values (%s, %s, %s) + + >>> q2 = sql.SQL("insert into table (%s) values (%s)") % [ + ... sql.SQL(', ').join(map(sql.Identifier, names)), + ... sql.SQL(', ').join(map(sql.Placeholder, names))] + >>> print(q2.as_string(conn)) + insert into table ("foo", "bar", "baz") values (%(foo)s, %(bar)s, %(baz)s) + """ def __init__(self, name=None):