2007-11-11 13:40:12 +03:00
|
|
|
#!/usr/bin/env python
|
2011-01-07 04:44:19 +03:00
|
|
|
|
|
|
|
# test_quote.py - unit test for strings quoting
|
|
|
|
#
|
2019-02-17 04:34:52 +03:00
|
|
|
# Copyright (C) 2007-2019 Daniele Varrazzo <daniele.varrazzo@gmail.com>
|
2021-06-15 02:37:22 +03:00
|
|
|
# Copyright (C) 2020-2021 The Psycopg Team
|
2011-01-07 04:44:19 +03:00
|
|
|
#
|
|
|
|
# psycopg2 is free software: you can redistribute it and/or modify it
|
|
|
|
# under the terms of the GNU Lesser General Public License as published
|
|
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# In addition, as a special exception, the copyright holders give
|
|
|
|
# permission to link this program with the OpenSSL library (or with
|
|
|
|
# modified versions of OpenSSL that use the same license as OpenSSL),
|
|
|
|
# and distribute linked combinations including the two.
|
|
|
|
#
|
|
|
|
# You must obey the GNU Lesser General Public License in all respects for
|
|
|
|
# all of the code used other than OpenSSL.
|
|
|
|
#
|
|
|
|
# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
|
|
# License for more details.
|
|
|
|
|
2017-12-04 05:47:19 +03:00
|
|
|
from . import testutils
|
2017-12-02 04:59:53 +03:00
|
|
|
import unittest
|
2020-11-17 21:39:39 +03:00
|
|
|
from .testutils import ConnectingTestCase, skip_if_crdb
|
2008-04-21 03:19:42 +04:00
|
|
|
|
2007-11-11 11:53:44 +03:00
|
|
|
import psycopg2
|
|
|
|
import psycopg2.extensions
|
2019-03-16 18:30:15 +03:00
|
|
|
from psycopg2.extensions import adapt, quote_ident
|
2007-11-11 11:53:44 +03:00
|
|
|
|
2016-07-01 18:57:25 +03:00
|
|
|
|
2013-04-07 03:23:30 +04:00
|
|
|
class QuotingTestCase(ConnectingTestCase):
|
2007-11-11 11:53:44 +03:00
|
|
|
r"""Checks the correct quoting of strings and binary objects.
|
|
|
|
|
|
|
|
Since ver. 8.1, PostgreSQL is moving towards SQL standard conforming
|
|
|
|
strings, where the backslash (\) is treated as literal character,
|
|
|
|
not as escape. To treat the backslash as a C-style escapes, PG supports
|
|
|
|
the E'' quotes.
|
|
|
|
|
|
|
|
This test case checks that the E'' quotes are used whenever they are
|
|
|
|
needed. The tests are expected to pass with all PostgreSQL server versions
|
|
|
|
(currently tested with 7.4 <= PG <= 8.3beta) and with any
|
|
|
|
'standard_conforming_strings' server parameter value.
|
|
|
|
The tests also check that no warning is raised ('escape_string_warning'
|
|
|
|
should be on).
|
|
|
|
|
2018-09-23 04:54:55 +03:00
|
|
|
https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS
|
|
|
|
https://www.postgresql.org/docs/current/static/runtime-config-compatible.html
|
2007-11-11 11:53:44 +03:00
|
|
|
"""
|
|
|
|
def test_string(self):
|
|
|
|
data = """some data with \t chars
|
|
|
|
to escape into, 'quotes' and \\ a backslash too.
|
|
|
|
"""
|
2016-07-01 18:57:25 +03:00
|
|
|
data += "".join(map(chr, range(1, 127)))
|
2007-11-11 11:53:44 +03:00
|
|
|
|
|
|
|
curs = self.conn.cursor()
|
|
|
|
curs.execute("SELECT %s;", (data,))
|
|
|
|
res = curs.fetchone()[0]
|
|
|
|
|
|
|
|
self.assertEqual(res, data)
|
|
|
|
self.assert_(not self.conn.notices)
|
|
|
|
|
2016-07-17 17:32:47 +03:00
|
|
|
def test_string_null_terminator(self):
|
|
|
|
curs = self.conn.cursor()
|
|
|
|
data = 'abcd\x01\x00cdefg'
|
|
|
|
|
2016-12-24 02:18:22 +03:00
|
|
|
try:
|
2016-07-17 17:32:47 +03:00
|
|
|
curs.execute("SELECT %s", (data,))
|
2016-12-24 02:18:22 +03:00
|
|
|
except ValueError as e:
|
|
|
|
self.assertEquals(str(e),
|
|
|
|
'A string literal cannot contain NUL (0x00) characters.')
|
|
|
|
else:
|
|
|
|
self.fail("ValueError not raised")
|
2016-07-17 17:32:47 +03:00
|
|
|
|
2007-11-11 11:53:44 +03:00
|
|
|
def test_binary(self):
|
2016-08-15 03:55:57 +03:00
|
|
|
data = b"""some data with \000\013 binary
|
2007-11-11 11:53:44 +03:00
|
|
|
stuff into, 'quotes' and \\ a backslash too.
|
2016-08-15 03:55:57 +03:00
|
|
|
"""
|
2020-11-17 21:39:39 +03:00
|
|
|
data += bytes(list(range(256)))
|
2007-11-11 11:53:44 +03:00
|
|
|
|
|
|
|
curs = self.conn.cursor()
|
|
|
|
curs.execute("SELECT %s::bytea;", (psycopg2.Binary(data),))
|
2020-11-17 21:39:39 +03:00
|
|
|
res = curs.fetchone()[0].tobytes()
|
2007-11-11 11:53:44 +03:00
|
|
|
|
2018-10-13 05:28:42 +03:00
|
|
|
if res[0] in (b'x', ord(b'x')) and self.conn.info.server_version >= 90000:
|
2011-02-23 17:04:27 +03:00
|
|
|
return self.skipTest(
|
|
|
|
"bytea broken with server >= 9.0, libpq < 9")
|
|
|
|
|
2007-11-11 11:53:44 +03:00
|
|
|
self.assertEqual(res, data)
|
|
|
|
self.assert_(not self.conn.notices)
|
|
|
|
|
|
|
|
def test_unicode(self):
|
2008-04-21 03:19:42 +04:00
|
|
|
curs = self.conn.cursor()
|
|
|
|
curs.execute("SHOW server_encoding")
|
|
|
|
server_encoding = curs.fetchone()[0]
|
|
|
|
if server_encoding != "UTF8":
|
2010-11-19 06:55:37 +03:00
|
|
|
return self.skipTest(
|
2020-11-18 00:52:11 +03:00
|
|
|
f"Unicode test skipped since server encoding is {server_encoding}")
|
2008-04-21 03:19:42 +04:00
|
|
|
|
2020-11-17 22:37:42 +03:00
|
|
|
data = """some data with \t chars
|
2007-11-11 11:53:44 +03:00
|
|
|
to escape into, 'quotes', \u20ac euro sign and \\ a backslash too.
|
|
|
|
"""
|
2020-11-17 22:37:42 +03:00
|
|
|
data += "".join(map(chr, [u for u in range(1, 65536)
|
2016-07-01 18:57:25 +03:00
|
|
|
if not 0xD800 <= u <= 0xDFFF])) # surrogate area
|
2007-11-11 11:53:44 +03:00
|
|
|
self.conn.set_client_encoding('UNICODE')
|
|
|
|
|
2011-01-03 19:29:04 +03:00
|
|
|
psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn)
|
2007-11-11 11:53:44 +03:00
|
|
|
curs.execute("SELECT %s::text;", (data,))
|
|
|
|
res = curs.fetchone()[0]
|
|
|
|
|
|
|
|
self.assertEqual(res, data)
|
|
|
|
self.assert_(not self.conn.notices)
|
|
|
|
|
2020-07-28 00:58:43 +03:00
|
|
|
@skip_if_crdb("encoding")
|
2010-12-28 15:47:29 +03:00
|
|
|
def test_latin1(self):
|
|
|
|
self.conn.set_client_encoding('LATIN1')
|
|
|
|
curs = self.conn.cursor()
|
2020-11-17 21:39:39 +03:00
|
|
|
data = bytes(list(range(32, 127))
|
|
|
|
+ list(range(160, 256))).decode('latin1')
|
2010-12-28 15:47:29 +03:00
|
|
|
|
|
|
|
# as string
|
|
|
|
curs.execute("SELECT %s::text;", (data,))
|
|
|
|
res = curs.fetchone()[0]
|
|
|
|
self.assertEqual(res, data)
|
|
|
|
self.assert_(not self.conn.notices)
|
|
|
|
|
|
|
|
|
2020-07-28 00:58:43 +03:00
|
|
|
@skip_if_crdb("encoding")
|
2010-12-28 15:47:29 +03:00
|
|
|
def test_koi8(self):
|
|
|
|
self.conn.set_client_encoding('KOI8')
|
|
|
|
curs = self.conn.cursor()
|
2020-11-17 21:39:39 +03:00
|
|
|
data = bytes(list(range(32, 127))
|
|
|
|
+ list(range(128, 256))).decode('koi8_r')
|
2010-12-28 15:47:29 +03:00
|
|
|
|
|
|
|
# as string
|
|
|
|
curs.execute("SELECT %s::text;", (data,))
|
|
|
|
res = curs.fetchone()[0]
|
|
|
|
self.assertEqual(res, data)
|
|
|
|
self.assert_(not self.conn.notices)
|
|
|
|
|
2019-01-18 18:10:17 +03:00
|
|
|
def test_bytes(self):
|
2020-11-17 22:37:42 +03:00
|
|
|
snowman = "\u2603"
|
2019-01-18 18:10:17 +03:00
|
|
|
conn = self.connect()
|
|
|
|
conn.set_client_encoding('UNICODE')
|
|
|
|
psycopg2.extensions.register_type(psycopg2.extensions.BYTES, conn)
|
|
|
|
curs = conn.cursor()
|
|
|
|
curs.execute("select %s::text", (snowman,))
|
|
|
|
x = curs.fetchone()[0]
|
|
|
|
self.assert_(isinstance(x, bytes))
|
|
|
|
self.assertEqual(x, snowman.encode('utf8'))
|
|
|
|
|
2010-12-28 15:47:29 +03:00
|
|
|
|
2013-04-07 03:23:30 +04:00
|
|
|
class TestQuotedString(ConnectingTestCase):
|
2016-07-01 19:33:12 +03:00
|
|
|
def test_encoding_from_conn(self):
|
2013-04-05 03:00:42 +04:00
|
|
|
q = psycopg2.extensions.QuotedString('hi')
|
|
|
|
self.assertEqual(q.encoding, 'latin1')
|
|
|
|
|
|
|
|
self.conn.set_client_encoding('utf_8')
|
|
|
|
q.prepare(self.conn)
|
|
|
|
self.assertEqual(q.encoding, 'utf_8')
|
|
|
|
|
|
|
|
|
2015-10-13 18:29:55 +03:00
|
|
|
class TestQuotedIdentifier(ConnectingTestCase):
|
|
|
|
def test_identifier(self):
|
|
|
|
self.assertEqual(quote_ident('blah-blah', self.conn), '"blah-blah"')
|
|
|
|
self.assertEqual(quote_ident('quote"inside', self.conn), '"quote""inside"')
|
|
|
|
|
2017-02-07 00:09:37 +03:00
|
|
|
@testutils.skip_before_postgres(8, 0)
|
2015-10-15 12:52:18 +03:00
|
|
|
def test_unicode_ident(self):
|
2020-11-17 22:37:42 +03:00
|
|
|
snowman = "\u2603"
|
2015-10-15 12:52:18 +03:00
|
|
|
quoted = '"' + snowman + '"'
|
2020-11-17 21:39:39 +03:00
|
|
|
self.assertEqual(quote_ident(snowman, self.conn), quoted)
|
2015-10-15 12:52:18 +03:00
|
|
|
|
2015-10-13 18:29:55 +03:00
|
|
|
|
2016-07-01 18:57:25 +03:00
|
|
|
class TestStringAdapter(ConnectingTestCase):
|
|
|
|
def test_encoding_default(self):
|
|
|
|
a = adapt("hello")
|
|
|
|
self.assertEqual(a.encoding, 'latin1')
|
2016-08-15 03:55:57 +03:00
|
|
|
self.assertEqual(a.getquoted(), b"'hello'")
|
2016-07-01 18:57:25 +03:00
|
|
|
|
2016-07-01 19:33:12 +03:00
|
|
|
# NOTE: we can't really test an encoding different from utf8, because
|
|
|
|
# when encoding without connection the libpq will use parameters from
|
|
|
|
# a previous one, so what would happens depends jn the tests run order.
|
|
|
|
# egrave = u'\xe8'
|
|
|
|
# self.assertEqual(adapt(egrave).getquoted(), "'\xe8'")
|
2016-07-01 18:57:25 +03:00
|
|
|
|
|
|
|
def test_encoding_error(self):
|
2020-11-17 22:37:42 +03:00
|
|
|
snowman = "\u2603"
|
2016-07-01 18:57:25 +03:00
|
|
|
a = adapt(snowman)
|
|
|
|
self.assertRaises(UnicodeEncodeError, a.getquoted)
|
|
|
|
|
|
|
|
def test_set_encoding(self):
|
2016-07-01 19:33:12 +03:00
|
|
|
# Note: this works-ish mostly in case when the standard db connection
|
|
|
|
# we test with is utf8, otherwise the encoding chosen by PQescapeString
|
|
|
|
# may give bad results.
|
2020-11-17 22:37:42 +03:00
|
|
|
snowman = "\u2603"
|
2016-07-01 18:57:25 +03:00
|
|
|
a = adapt(snowman)
|
|
|
|
a.encoding = 'utf8'
|
|
|
|
self.assertEqual(a.encoding, 'utf8')
|
2016-08-15 03:55:57 +03:00
|
|
|
self.assertEqual(a.getquoted(), b"'\xe2\x98\x83'")
|
2016-07-01 18:57:25 +03:00
|
|
|
|
|
|
|
def test_connection_wins_anyway(self):
|
2020-11-17 22:37:42 +03:00
|
|
|
snowman = "\u2603"
|
2016-07-01 18:57:25 +03:00
|
|
|
a = adapt(snowman)
|
|
|
|
a.encoding = 'latin9'
|
|
|
|
|
|
|
|
self.conn.set_client_encoding('utf8')
|
|
|
|
a.prepare(self.conn)
|
|
|
|
|
|
|
|
self.assertEqual(a.encoding, 'utf_8')
|
2017-02-06 21:43:39 +03:00
|
|
|
self.assertQuotedEqual(a.getquoted(), b"'\xe2\x98\x83'")
|
2016-07-01 18:57:25 +03:00
|
|
|
|
2016-07-01 21:11:04 +03:00
|
|
|
def test_adapt_bytes(self):
|
2020-11-17 22:37:42 +03:00
|
|
|
snowman = "\u2603"
|
2016-07-01 21:11:04 +03:00
|
|
|
self.conn.set_client_encoding('utf8')
|
|
|
|
a = psycopg2.extensions.QuotedString(snowman.encode('utf8'))
|
|
|
|
a.prepare(self.conn)
|
2017-02-06 21:43:39 +03:00
|
|
|
self.assertQuotedEqual(a.getquoted(), b"'\xe2\x98\x83'")
|
2016-07-01 21:11:04 +03:00
|
|
|
|
2016-07-01 18:57:25 +03:00
|
|
|
|
2007-11-11 11:53:44 +03:00
|
|
|
def test_suite():
|
|
|
|
return unittest.TestLoader().loadTestsFromName(__name__)
|
|
|
|
|
2018-10-23 02:39:14 +03:00
|
|
|
|
2007-11-11 11:53:44 +03:00
|
|
|
if __name__ == "__main__":
|
|
|
|
unittest.main()
|