├── .gitignore ├── .travis.yml ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README ├── psycopg2ct ├── __init__.py ├── _config.py ├── _impl │ ├── __init__.py │ ├── adapters.py │ ├── connection.py │ ├── consts.py │ ├── cursor.py │ ├── encodings.py │ ├── exceptions.py │ ├── libpq.py │ ├── lobject.py │ ├── notify.py │ ├── typecasts.py │ ├── util.py │ └── xid.py ├── compat.py ├── errorcodes.py ├── extensions.py ├── extras.py ├── pool.py ├── tests │ ├── __init__.py │ ├── psycopg2_tests │ │ ├── __init__.py │ │ ├── dbapi20.py │ │ ├── dbapi20_tpc.py │ │ ├── psycopg2.py │ │ ├── test_async.py │ │ ├── test_bugX000.py │ │ ├── test_bug_gc.py │ │ ├── test_cancel.py │ │ ├── test_connection.py │ │ ├── test_copy.py │ │ ├── test_cursor.py │ │ ├── test_dates.py │ │ ├── test_extras_dictcursor.py │ │ ├── test_green.py │ │ ├── test_lobject.py │ │ ├── test_module.py │ │ ├── test_notify.py │ │ ├── test_psycopg2_dbapi20.py │ │ ├── test_quote.py │ │ ├── test_transaction.py │ │ ├── test_types_basic.py │ │ ├── test_types_extras.py │ │ ├── testconfig.py │ │ └── testutils.py │ └── test_notify.py └── tz.py ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | MANIFEST 4 | .tox/ 5 | build/ 6 | dist/ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | before_script: 3 | - "psql -c 'create database psycopg2_test;' -U postgres" 4 | env: 5 | - PSYCOPG2_TESTDB_USER=postgres 6 | python: 7 | - "2.6" 8 | - "2.7" 9 | script: python setup.py test 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Michael van Tellingen 2 | Daniele Varrazzo 3 | 4 | Based on the rpython psycopg2 module by: 5 | - Alex Gaynor 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | psycopg2-ctypes and the LGPL 2 | ============================ 3 | 4 | psycopg2-ctypes is free software: you can redistribute it and/or modify it 5 | under the terms of the GNU Lesser General Public License as published 6 | by the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | psycopg2-ctypes is distributed in the hope that it will be useful, but 10 | WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 12 | License for more details. 13 | 14 | In addition, as a special exception, the copyright holders give 15 | permission to link this program with the OpenSSL library (or with 16 | modified versions of OpenSSL that use the same license as OpenSSL), 17 | and distribute linked combinations including the two. 18 | 19 | You must obey the GNU Lesser General Public License in all respects for 20 | all of the code used other than OpenSSL. If you modify file(s) with this 21 | exception, you may extend this exception to your version of the file(s), 22 | but you are not obligated to do so. If you do not wish to do so, delete 23 | this exception statement from your version. If you delete this exception 24 | statement from all source files in the program, then also delete it here. 25 | 26 | You should have received a copy of the GNU Lesser General Public License 27 | along with psycopg2-ctypes (see the doc/ directory.) 28 | If not, see . 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README LICENSE 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Note: this module is no longer supported by me. If you want to take over the 2 | maintenance then let me know, otherwise see https://github.com/chtd/psycopg2cffi 3 | 4 | --- 5 | 6 | An implementation of the psycopg2 module using ctypes. 7 | The module is currently compatible with Psycopg 2.4.4. 8 | 9 | To use this package with Django or SQLAlchemy create a psycopg2.py file 10 | somewhere in your python path (e.g. the current working dir) and add:: 11 | 12 | from psycopg2ct import compat 13 | compat.register() 14 | 15 | This will map psycopg2ct to psycopg2. 16 | 17 | This module is only tested with python 2.6+ and PyPy 1.6+ 18 | 19 | This is a port of Alex Gaynor's rpython port 20 | (https://bitbucket.org/alex_gaynor/pypy-postgresql/overview) of psycopg2 to 21 | python + ctypes. 22 | -------------------------------------------------------------------------------- /psycopg2ct/__init__.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from time import localtime 3 | 4 | from psycopg2ct import extensions 5 | from psycopg2ct import tz 6 | from psycopg2ct._impl.adapters import Binary, Date, Time, Timestamp 7 | from psycopg2ct._impl.adapters import DateFromTicks, TimeFromTicks 8 | from psycopg2ct._impl.adapters import TimestampFromTicks 9 | from psycopg2ct._impl.connection import _connect 10 | from psycopg2ct._impl.exceptions import * 11 | from psycopg2ct._impl.typecasts import BINARY, DATETIME, NUMBER, ROWID, STRING 12 | 13 | __version__ = '2.4.4' 14 | apilevel = '2.0' 15 | paramstyle = 'pyformat' 16 | threadsafety = 2 17 | 18 | import psycopg2ct.extensions as _ext 19 | _ext.register_adapter(tuple, _ext.SQL_IN) 20 | _ext.register_adapter(type(None), _ext.NoneAdapter) 21 | 22 | # check for a more up-to-date version number generated at install time 23 | try: 24 | from psycopg2ct._config import VERSION as __version__ 25 | except ImportError: 26 | pass 27 | 28 | import re 29 | 30 | def _param_escape(s, 31 | re_escape=re.compile(r"([\\'])"), 32 | re_space=re.compile(r'\s')): 33 | """ 34 | Apply the escaping rule required by PQconnectdb 35 | """ 36 | if not s: return "''" 37 | 38 | s = re_escape.sub(r'\\\1', s) 39 | if re_space.search(s): 40 | s = "'" + s + "'" 41 | 42 | return s 43 | 44 | del re 45 | 46 | 47 | def connect(dsn=None, 48 | database=None, user=None, password=None, host=None, port=None, 49 | connection_factory=None, async=False, **kwargs): 50 | """ 51 | Create a new database connection. 52 | 53 | The connection parameters can be specified either as a string: 54 | 55 | conn = psycopg2.connect("dbname=test user=postgres password=secret") 56 | 57 | or using a set of keyword arguments: 58 | 59 | conn = psycopg2.connect(database="test", user="postgres", password="secret") 60 | 61 | The basic connection parameters are: 62 | 63 | - *dbname*: the database name (only in dsn string) 64 | - *database*: the database name (only as keyword argument) 65 | - *user*: user name used to authenticate 66 | - *password*: password used to authenticate 67 | - *host*: database host address (defaults to UNIX socket if not provided) 68 | - *port*: connection port number (defaults to 5432 if not provided) 69 | 70 | Using the *connection_factory* parameter a different class or connections 71 | factory can be specified. It should be a callable object taking a dsn 72 | argument. 73 | 74 | Using *async*=True an asynchronous connection will be created. 75 | 76 | Any other keyword parameter will be passed to the underlying client 77 | library: the list of supported parameter depends on the library version. 78 | 79 | """ 80 | if dsn is None: 81 | # Note: reproducing the behaviour of the previous C implementation: 82 | # keyword are silently swallowed if a DSN is specified. I would have 83 | # raised an exception. File under "histerical raisins". 84 | items = [] 85 | if database is not None: 86 | items.append(('dbname', database)) 87 | if user is not None: 88 | items.append(('user', user)) 89 | if password is not None: 90 | items.append(('password', password)) 91 | if host is not None: 92 | items.append(('host', host)) 93 | # Reproducing the previous C implementation behaviour: swallow a 94 | # negative port. The libpq would raise an exception for it. 95 | if port is not None and int(port) > 0: 96 | items.append(('port', port)) 97 | 98 | items.extend( 99 | [(k, v) for (k, v) in kwargs.iteritems() if v is not None]) 100 | dsn = " ".join(["%s=%s" % (k, _param_escape(str(v))) 101 | for (k, v) in items]) 102 | 103 | if not dsn: 104 | raise InterfaceError('missing dsn and no parameters') 105 | 106 | return _connect(dsn, 107 | connection_factory=connection_factory, async=async) 108 | 109 | 110 | __all__ = filter(lambda k: not k.startswith('_'), locals().keys()) 111 | -------------------------------------------------------------------------------- /psycopg2ct/_config.py: -------------------------------------------------------------------------------- 1 | # This will be replaced with values retrieved by setup.py 2 | from ctypes.util import find_library 3 | PG_LIBRARY = find_library('pq') 4 | PG_VERSION = 0x070400 5 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvantellingen/psycopg2-ctypes/c309bd243d06dc6ec82887ad325fa47d1c1e9e6c/psycopg2ct/_impl/__init__.py -------------------------------------------------------------------------------- /psycopg2ct/_impl/adapters.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import decimal 3 | import math 4 | 5 | from psycopg2ct._impl import libpq 6 | from psycopg2ct._impl.encodings import encodings 7 | from psycopg2ct._impl.exceptions import ProgrammingError 8 | from psycopg2ct._config import PG_VERSION 9 | from psycopg2ct.tz import LOCAL as TZ_LOCAL 10 | 11 | 12 | adapters = {} 13 | 14 | 15 | class _BaseAdapter(object): 16 | def __init__(self, wrapped_object): 17 | self._wrapped = wrapped_object 18 | self._conn = None 19 | 20 | def __str__(self): 21 | return self.getquoted() 22 | 23 | @property 24 | def adapted(self): 25 | return self._wrapped 26 | 27 | 28 | class ISQLQuote(_BaseAdapter): 29 | def getquoted(self): 30 | pass 31 | 32 | 33 | class AsIs(_BaseAdapter): 34 | def getquoted(self): 35 | return str(self._wrapped) 36 | 37 | 38 | class Binary(_BaseAdapter): 39 | def prepare(self, connection): 40 | self._conn = connection 41 | 42 | def __conform__(self, proto): 43 | return self 44 | 45 | def getquoted(self): 46 | if self._wrapped is None: 47 | return 'NULL' 48 | 49 | to_length = libpq.c_uint() 50 | 51 | if self._conn: 52 | data_pointer = libpq.PQescapeByteaConn( 53 | self._conn._pgconn, str(self._wrapped), len(self._wrapped), 54 | libpq.pointer(to_length)) 55 | else: 56 | data_pointer = libpq.PQescapeBytea( 57 | self._wrapped, len(self._wrapped), libpq.pointer(to_length)) 58 | 59 | data = data_pointer[:to_length.value - 1] 60 | libpq.PQfreemem(data_pointer) 61 | 62 | if self._conn and self._conn._equote: 63 | return r"E'%s'::bytea" % data 64 | 65 | return r"'%s'::bytea" % data 66 | 67 | 68 | class Boolean(_BaseAdapter): 69 | def getquoted(self): 70 | return 'true' if self._wrapped else 'false' 71 | 72 | 73 | class DateTime(_BaseAdapter): 74 | def getquoted(self): 75 | obj = self._wrapped 76 | if isinstance(obj, datetime.timedelta): 77 | # TODO: microseconds 78 | return "'%d days %d.0 seconds'::interval" % ( 79 | int(obj.days), int(obj.seconds)) 80 | else: 81 | iso = obj.isoformat() 82 | if isinstance(obj, datetime.datetime): 83 | format = 'timestamp' 84 | if getattr(obj, 'tzinfo', None): 85 | format = 'timestamptz' 86 | elif isinstance(obj, datetime.time): 87 | format = 'time' 88 | else: 89 | format = 'date' 90 | return "'%s'::%s" % (str(iso), format) 91 | 92 | 93 | def Date(year, month, day): 94 | date = datetime.date(year, month, day) 95 | return DateTime(date) 96 | 97 | 98 | def DateFromTicks(ticks): 99 | date = datetime.datetime.fromtimestamp(ticks).date() 100 | return DateTime(date) 101 | 102 | 103 | class Decimal(_BaseAdapter): 104 | def getquoted(self): 105 | if self._wrapped.is_finite(): 106 | value = str(self._wrapped) 107 | 108 | # Prepend a space in front of negative numbers 109 | if value.startswith('-'): 110 | value = ' ' + value 111 | return value 112 | return "'NaN'::numeric" 113 | 114 | 115 | class Float(ISQLQuote): 116 | def getquoted(self): 117 | n = float(self._wrapped) 118 | if math.isnan(n): 119 | return "'NaN'::float" 120 | elif math.isinf(n): 121 | if n > 0: 122 | return "'Infinity'::float" 123 | else: 124 | return "'-Infinity'::float" 125 | else: 126 | value = repr(self._wrapped) 127 | 128 | # Prepend a space in front of negative numbers 129 | if value.startswith('-'): 130 | value = ' ' + value 131 | return value 132 | 133 | 134 | class Int(_BaseAdapter): 135 | def getquoted(self): 136 | value = str(self._wrapped) 137 | 138 | # Prepend a space in front of negative numbers 139 | if value.startswith('-'): 140 | value = ' ' + value 141 | return value 142 | 143 | 144 | class List(_BaseAdapter): 145 | 146 | def prepare(self, connection): 147 | self._conn = connection 148 | 149 | def getquoted(self): 150 | length = len(self._wrapped) 151 | if length == 0: 152 | return "'{}'" 153 | 154 | quoted = [None] * length 155 | for i in xrange(length): 156 | obj = self._wrapped[i] 157 | quoted[i] = str(_getquoted(obj, self._conn)) 158 | return "ARRAY[%s]" % ", ".join(quoted) 159 | 160 | 161 | class Long(_BaseAdapter): 162 | def getquoted(self): 163 | value = str(self._wrapped) 164 | 165 | # Prepend a space in front of negative numbers 166 | if value.startswith('-'): 167 | value = ' ' + value 168 | return value 169 | 170 | 171 | def Time(hour, minutes, seconds, tzinfo=None): 172 | time = datetime.time(hour, minutes, seconds, tzinfo=tzinfo) 173 | return DateTime(time) 174 | 175 | 176 | def TimeFromTicks(ticks): 177 | time = datetime.datetime.fromtimestamp(ticks).time() 178 | return DateTime(time) 179 | 180 | 181 | def Timestamp(year, month, day, hour, minutes, seconds, tzinfo=None): 182 | dt = datetime.datetime( 183 | year, month, day, hour, minutes, seconds, tzinfo=tzinfo) 184 | return DateTime(dt) 185 | 186 | 187 | def TimestampFromTicks(ticks): 188 | dt = datetime.datetime.fromtimestamp(ticks, TZ_LOCAL) 189 | return DateTime(dt) 190 | 191 | 192 | class QuotedString(_BaseAdapter): 193 | def __init__(self, obj): 194 | super(QuotedString, self).__init__(obj) 195 | self.encoding = "latin-1" 196 | 197 | def prepare(self, conn): 198 | self._conn = conn 199 | self.encoding = conn.encoding 200 | 201 | def getquoted(self): 202 | 203 | obj = self._wrapped 204 | if isinstance(self._wrapped, unicode): 205 | encoding = encodings[self.encoding] 206 | obj = obj.encode(encoding) 207 | string = str(obj) 208 | length = len(string) 209 | 210 | if not self._conn: 211 | to = libpq.create_string_buffer('\0', (length * 2) + 1) 212 | libpq.PQescapeString(to, string, length) 213 | return "'%s'" % to.value 214 | 215 | if PG_VERSION < 0x090000: 216 | to = libpq.create_string_buffer('\0', (length * 2) + 1) 217 | err = libpq.c_int() 218 | libpq.PQescapeStringConn( 219 | self._conn._pgconn, to, string, length, err) 220 | 221 | if self._conn and self._conn._equote: 222 | return "E'%s'" % to.value 223 | return "'%s'" % to.value 224 | 225 | data_pointer = libpq.PQescapeLiteral( 226 | self._conn._pgconn, string, length) 227 | data = libpq.cast(data_pointer, libpq.c_char_p).value 228 | libpq.PQfreemem(data_pointer) 229 | return data 230 | 231 | 232 | def adapt(value, proto=ISQLQuote, alt=None): 233 | """Return the adapter for the given value""" 234 | obj_type = type(value) 235 | try: 236 | return adapters[(obj_type, proto)](value) 237 | except KeyError: 238 | for subtype in obj_type.mro()[1:]: 239 | try: 240 | return adapters[(subtype, proto)](value) 241 | except KeyError: 242 | pass 243 | 244 | conform = getattr(value, '__conform__', None) 245 | if conform is not None: 246 | return conform(proto) 247 | raise ProgrammingError("can't adapt type '%s'" % obj_type.__name__) 248 | 249 | 250 | def _getquoted(param, conn): 251 | """Helper method""" 252 | if param is None: 253 | return 'NULL' 254 | adapter = adapt(param) 255 | try: 256 | adapter.prepare(conn) 257 | except AttributeError: 258 | pass 259 | return adapter.getquoted() 260 | 261 | 262 | built_in_adapters = { 263 | bool: Boolean, 264 | str: QuotedString, 265 | unicode: QuotedString, 266 | list: List, 267 | bytearray: Binary, 268 | buffer: Binary, 269 | int: Int, 270 | long: Long, 271 | float: Float, 272 | datetime.date: DateTime, # DateFromPY 273 | datetime.datetime: DateTime, # TimestampFromPy 274 | datetime.time: DateTime, # TimeFromPy 275 | datetime.timedelta: DateTime, # IntervalFromPy 276 | decimal.Decimal: Decimal, 277 | } 278 | 279 | try: 280 | built_in_adapters[memoryview] = Binary 281 | except NameError: 282 | # Python 2.6 283 | pass 284 | 285 | for k, v in built_in_adapters.iteritems(): 286 | adapters[(k, ISQLQuote)] = v 287 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/consts.py: -------------------------------------------------------------------------------- 1 | """psycopg2ct -- global constants 2 | 3 | This module can be imported from everywhere without problems of cross imports. 4 | """ 5 | 6 | # Isolation level values. 7 | ISOLATION_LEVEL_AUTOCOMMIT = 0 8 | ISOLATION_LEVEL_READ_UNCOMMITTED = 4 9 | ISOLATION_LEVEL_READ_COMMITTED = 1 10 | ISOLATION_LEVEL_REPEATABLE_READ = 2 11 | ISOLATION_LEVEL_SERIALIZABLE = 3 12 | 13 | # psycopg connection status values. 14 | STATUS_SETUP = 0 15 | STATUS_READY = 1 16 | STATUS_BEGIN = 2 17 | STATUS_SYNC = 3 # currently unused 18 | STATUS_ASYNC = 4 # currently unused 19 | STATUS_PREPARED = 5 20 | STATUS_CONNECTING = 20 21 | STATUS_DATESTYLE = 21 22 | 23 | # This is a usefull mnemonic to check if the connection is in a transaction 24 | STATUS_IN_TRANSACTION = STATUS_BEGIN 25 | 26 | # psycopg asynchronous connection polling values 27 | POLL_OK = 0 28 | POLL_READ = 1 29 | POLL_WRITE = 2 30 | POLL_ERROR = 3 31 | 32 | # Backend transaction status values. 33 | TRANSACTION_STATUS_IDLE = 0 34 | TRANSACTION_STATUS_ACTIVE = 1 35 | TRANSACTION_STATUS_INTRANS = 2 36 | TRANSACTION_STATUS_INERROR = 3 37 | TRANSACTION_STATUS_UNKNOWN = 4 38 | 39 | 40 | ASYNC_DONE = 0 41 | ASYNC_READ = 1 42 | ASYNC_WRITE = 2 43 | 44 | LOBJECT_READ = 1 45 | LOBJECT_WRITE = 2 46 | LOBJECT_TEXT = 4 47 | LOBJECT_BINARY = 8 48 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/encodings.py: -------------------------------------------------------------------------------- 1 | encodings = { 2 | 'ABC': 'cp1258', 3 | 'ALT': 'cp866', 4 | 'BIG5': 'big5', 5 | 'EUC_CN': 'euccn', 6 | 'EUC_JIS_2004': 'euc_jis_2004', 7 | 'EUC_JP': 'euc_jp', 8 | 'EUC_KR': 'euc_kr', 9 | 'GB18030': 'gb18030', 10 | 'GBK': 'gbk', 11 | 'ISO_8859_1': 'iso8859_1', 12 | 'ISO_8859_2': 'iso8859_2', 13 | 'ISO_8859_3': 'iso8859_3', 14 | 'ISO_8859_5': 'iso8859_5', 15 | 'ISO_8859_6': 'iso8859_6', 16 | 'ISO_8859_7': 'iso8859_7', 17 | 'ISO_8859_8': 'iso8859_8', 18 | 'ISO_8859_9': 'iso8859_9', 19 | 'ISO_8859_10': 'iso8859_10', 20 | 'ISO_8859_13': 'iso8859_13', 21 | 'ISO_8859_14': 'iso8859_14', 22 | 'ISO_8859_15': 'iso8859_15', 23 | 'ISO_8859_16': 'iso8859_16', 24 | 'JOHAB': 'johab', 25 | 'KOI8': 'koi8_r', 26 | 'KOI8R': 'koi8_r', 27 | 'KOI8U': 'koi8_u', 28 | 'LATIN1': 'iso8859_1', 29 | 'LATIN2': 'iso8859_2', 30 | 'LATIN3': 'iso8859_3', 31 | 'LATIN4': 'iso8859_4', 32 | 'LATIN5': 'iso8859_9', 33 | 'LATIN6': 'iso8859_10', 34 | 'LATIN7': 'iso8859_13', 35 | 'LATIN8': 'iso8859_14', 36 | 'LATIN9': 'iso8859_15', 37 | 'LATIN10': 'iso8859_16', 38 | 'Mskanji': 'cp932', 39 | 'ShiftJIS': 'cp932', 40 | 'SHIFT_JIS_2004': 'shift_jis_2004', 41 | 'SJIS': 'cp932', 42 | 'SQL_ASCII': 'ascii', # XXX this is wrong: SQL_ASCII means "no 43 | # encoding" we should fix the unicode 44 | # typecaster to return a str or bytes in Py3 45 | 'TCVN': 'cp1258', 46 | 'TCVN5712': 'cp1258', 47 | 'UHC': 'cp949', 48 | 'UNICODE': 'utf_8', 49 | 'UTF8': 'utf_8', 50 | 'VSCII': 'cp1258', 51 | 'WIN': 'cp1251', 52 | 'WIN866': 'cp866', 53 | 'WIN874': 'cp874', 54 | 'WIN932': 'cp932', 55 | 'WIN936': 'gbk', 56 | 'WIN949': 'cp949', 57 | 'WIN950': 'cp950', 58 | 'WIN1250': 'cp1250', 59 | 'WIN1251': 'cp1251', 60 | 'WIN1252': 'cp1252', 61 | 'WIN1253': 'cp1253', 62 | 'WIN1254': 'cp1254', 63 | 'WIN1255': 'cp1255', 64 | 'WIN1256': 'cp1256', 65 | 'WIN1257': 'cp1257', 66 | 'WIN1258': 'cp1258', 67 | 'Windows932': 'cp932', 68 | 'Windows936': 'gbk', 69 | 'Windows949': 'cp949', 70 | 'Windows950': 'cp950', 71 | 72 | # these are missing from Python: 73 | # 'EUC_TW': ??? 74 | # 'MULE_INTERNAL': ??? 75 | } 76 | 77 | def normalize(name): 78 | """Normalize the name of an encoding.""" 79 | return name.replace('_', '').replace('-', '').upper() 80 | 81 | # Include a normalized version of the encodings above 82 | # (all uppercase, no - or _) 83 | for k, v in encodings.items(): 84 | encodings[normalize(k)] = v 85 | 86 | del k, v 87 | 88 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/exceptions.py: -------------------------------------------------------------------------------- 1 | class OperationError(Exception): 2 | pass 3 | 4 | 5 | class Warning(StandardError): 6 | pass 7 | 8 | 9 | class Error(StandardError): 10 | pass 11 | 12 | 13 | class InterfaceError(Error): 14 | pass 15 | 16 | 17 | class DatabaseError(Error): 18 | pass 19 | 20 | 21 | class DataError(DatabaseError): 22 | pass 23 | 24 | 25 | class OperationalError(DatabaseError): 26 | pass 27 | 28 | 29 | class IntegrityError(DatabaseError): 30 | pass 31 | 32 | 33 | class InternalError(DatabaseError): 34 | pass 35 | 36 | 37 | class ProgrammingError(DatabaseError): 38 | pass 39 | 40 | 41 | class NotSupportedError(DatabaseError): 42 | pass 43 | 44 | 45 | class QueryCanceledError(OperationalError): 46 | pass 47 | 48 | 49 | class TransactionRollbackError(OperationalError): 50 | pass 51 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/libpq.py: -------------------------------------------------------------------------------- 1 | """ctypes interface to the libpq library""" 2 | from ctypes import * 3 | 4 | from psycopg2ct._config import PG_LIBRARY, PG_VERSION 5 | 6 | 7 | if not PG_LIBRARY: 8 | raise RuntimeError('libpq not found!') 9 | libpq = cdll.LoadLibrary(PG_LIBRARY) 10 | 11 | 12 | class PGconn(Structure): 13 | _fields_ = [] 14 | 15 | PGconn_p = POINTER(PGconn) 16 | 17 | 18 | class PGresult(Structure): 19 | _fields_ = [] 20 | 21 | PGresult_p = POINTER(PGresult) 22 | 23 | 24 | class PGcancel(Structure): 25 | _fields_ = [] 26 | 27 | PGcancel_p = POINTER(PGcancel) 28 | 29 | 30 | CONNECTION_OK = 0 31 | CONNECTION_BAD = 1 32 | 33 | ConnStatusType = c_int 34 | 35 | PGRES_EMPTY_QUERY = 0 36 | PGRES_COMMAND_OK = 1 37 | PGRES_TUPLES_OK = 2 38 | PGRES_COPY_OUT = 3 39 | PGRES_COPY_IN = 4 40 | PGRES_BAD_RESPONSE = 5 41 | PGRES_NONFATAL_ERROR = 6 42 | PGRES_FATAL_ERROR = 7 43 | 44 | ExecStatusType = c_int 45 | 46 | PG_DIAG_SEVERITY = ord('S') 47 | PG_DIAG_SQLSTATE = ord('C') 48 | PG_DIAG_MESSAGE_PRIMARY = ord('M') 49 | PG_DIAG_MESSAGE_DETAIL = ord('D') 50 | PG_DIAG_MESSAGE_HINT = ord('H') 51 | PG_DIAG_STATEMENT_POSITION = 'P' 52 | PG_DIAG_INTERNAL_POSITION = 'p' 53 | PG_DIAG_INTERNAL_QUERY = ord('q') 54 | PG_DIAG_CONTEXT = ord('W') 55 | PG_DIAG_SOURCE_FILE = ord('F') 56 | DIAG_SOURCE_LINE = ord('L') 57 | PG_DIAG_SOURCE_FUNCTION = ord('R') 58 | 59 | 60 | PGRES_POLLING_FAILED = 0 61 | PGRES_POLLING_READING = 1 62 | PGRES_POLLING_WRITING = 2 63 | PGRES_POLLING_OK = 3 64 | PGRES_POLLING_ACTIVE = 4 65 | 66 | PostgresPollingStatusType = c_int 67 | 68 | 69 | class PGnotify(Structure): 70 | _fields_ = [ 71 | ('relname', c_char_p), 72 | ('be_pid', c_int), 73 | ('extra', c_char_p) 74 | ] 75 | 76 | PGnotify_p = POINTER(PGnotify) 77 | 78 | 79 | # Database connection control functions 80 | 81 | PQconnectdb = libpq.PQconnectdb 82 | PQconnectdb.argtypes = [c_char_p] 83 | PQconnectdb.restype = PGconn_p 84 | 85 | PQconnectStart = libpq.PQconnectStart 86 | PQconnectStart.argtypes = [c_char_p] 87 | PQconnectStart.restype = PGconn_p 88 | 89 | PQconnectPoll = libpq.PQconnectPoll 90 | PQconnectPoll.argtypes = [PGconn_p] 91 | PQconnectPoll.restype = PostgresPollingStatusType 92 | 93 | PQfinish = libpq.PQfinish 94 | PQfinish.argtypes = [PGconn_p] 95 | PQfinish.restype = None 96 | 97 | # Connection status functions 98 | 99 | PQdb = libpq.PQdb 100 | PQdb.argtypes = [PGconn_p] 101 | PQdb.restype = c_char_p 102 | 103 | PQuser = libpq.PQuser 104 | PQuser.argtypes = [PGconn_p] 105 | PQuser.restype = c_char_p 106 | 107 | PQstatus = libpq.PQstatus 108 | PQstatus.argtypes = [PGconn_p] 109 | PQstatus.restype = ConnStatusType 110 | 111 | PQtransactionStatus = libpq.PQtransactionStatus 112 | PQtransactionStatus.argtypes = [PGconn_p] 113 | PQtransactionStatus.restype = c_int 114 | 115 | PQparameterStatus = libpq.PQparameterStatus 116 | PQparameterStatus.argtypes = [PGconn_p, c_char_p] 117 | PQparameterStatus.restype = c_char_p 118 | 119 | PQprotocolVersion = libpq.PQprotocolVersion 120 | PQprotocolVersion.argtypes = [PGconn_p] 121 | PQprotocolVersion.restype = c_int 122 | 123 | PQserverVersion = libpq.PQserverVersion 124 | PQserverVersion.argtypes = [PGconn_p] 125 | PQserverVersion.restype = c_int 126 | 127 | PQerrorMessage = libpq.PQerrorMessage 128 | PQerrorMessage.argtypes = [PGconn_p] 129 | PQerrorMessage.restype = c_char_p 130 | 131 | PQsocket = libpq.PQsocket 132 | PQsocket.argtypes = [PGconn_p] 133 | PQsocket.restype = c_int 134 | 135 | PQbackendPID = libpq.PQbackendPID 136 | PQbackendPID.argtypes = [PGconn_p] 137 | PQbackendPID.restype = c_int 138 | 139 | # Command execution functions 140 | 141 | PQexec = libpq.PQexec 142 | PQexec.argtypes = [PGconn_p, c_char_p] 143 | PQexec.restype = PGresult_p 144 | 145 | PQresultStatus = libpq.PQresultStatus 146 | PQresultStatus.argtypes = [PGresult_p] 147 | PQresultStatus.restype = ExecStatusType 148 | 149 | PQresultErrorMessage = libpq.PQresultErrorMessage 150 | PQresultErrorMessage.argtypes = [PGresult_p] 151 | PQresultErrorMessage.restype = c_char_p 152 | 153 | PQresultErrorField = libpq.PQresultErrorField 154 | PQresultErrorField.argtypes = [PGresult_p, c_int] 155 | PQresultErrorField.restype = c_char_p 156 | 157 | PQclear = libpq.PQclear 158 | PQclear.argtypes = [POINTER(PGresult)] 159 | PQclear.restype = None 160 | 161 | # Retrieving query result information 162 | 163 | PQntuples = libpq.PQntuples 164 | PQntuples.argtypes = [PGresult_p] 165 | PQntuples.restype = c_int 166 | 167 | PQnfields = libpq.PQnfields 168 | PQnfields.argtypes = [PGresult_p] 169 | PQnfields.restype = c_int 170 | 171 | PQfname = libpq.PQfname 172 | PQfname.argtypes = [PGresult_p, c_int] 173 | PQfname.restype = c_char_p 174 | 175 | PQftype = libpq.PQftype 176 | PQftype.argtypes = [PGresult_p, c_int] 177 | PQftype.restype = c_uint 178 | 179 | PQfsize = libpq.PQfsize 180 | PQfsize.argtypes = [PGresult_p, c_int] 181 | PQfsize.restype = c_int 182 | 183 | PQfmod = libpq.PQfmod 184 | PQfmod.argtypes = [PGresult_p, c_int] 185 | PQfmod.restype = c_int 186 | 187 | PQgetisnull = libpq.PQgetisnull 188 | PQgetisnull.argtypes = [PGresult_p, c_int, c_int] 189 | PQgetisnull.restype = c_int 190 | 191 | PQgetlength = libpq.PQgetlength 192 | PQgetlength.argtypes = [PGresult_p, c_int, c_int] 193 | PQgetlength.restype = c_int 194 | 195 | PQgetvalue = libpq.PQgetvalue 196 | PQgetvalue.argtypes = [PGresult_p, c_int, c_int] 197 | PQgetvalue.restype = c_char_p 198 | 199 | # Retrieving other result information 200 | 201 | PQcmdStatus = libpq.PQcmdStatus 202 | PQcmdStatus.argtypes = [PGresult_p] 203 | PQcmdStatus.restype = c_char_p 204 | 205 | PQcmdTuples = libpq.PQcmdTuples 206 | PQcmdTuples.argtypes = [PGresult_p] 207 | PQcmdTuples.restype = c_char_p 208 | 209 | PQoidValue = libpq.PQoidValue 210 | PQoidValue.argtypes = [PGresult_p] 211 | PQoidValue.restype = c_uint 212 | 213 | # Escaping string for inclusion in sql commands 214 | 215 | if PG_VERSION >= 0x090000: 216 | PQescapeLiteral = libpq.PQescapeLiteral 217 | PQescapeLiteral.argtypes = [PGconn_p, c_char_p, c_uint] 218 | PQescapeLiteral.restype = POINTER(c_char) 219 | 220 | PQescapeStringConn = libpq.PQescapeStringConn 221 | PQescapeStringConn.restype = c_uint 222 | PQescapeStringConn.argtypes = [PGconn_p, c_char_p, c_char_p, c_uint, POINTER(c_int)] 223 | 224 | PQescapeString = libpq.PQescapeString 225 | PQescapeString.argtypes = [c_char_p, c_char_p, c_uint] 226 | PQescapeString.restype = c_uint 227 | 228 | PQescapeByteaConn = libpq.PQescapeByteaConn 229 | PQescapeByteaConn.argtypes = [PGconn_p, c_char_p, c_uint, POINTER(c_uint)] 230 | PQescapeByteaConn.restype = POINTER(c_char) 231 | 232 | PQescapeBytea = libpq.PQescapeBytea 233 | PQescapeBytea.argtypes = [c_char_p, c_uint, POINTER(c_uint)] 234 | PQescapeBytea.restype = POINTER(c_char) 235 | 236 | PQunescapeBytea = libpq.PQunescapeBytea 237 | PQunescapeBytea.argtypes = [POINTER(c_char), POINTER(c_uint)] 238 | PQunescapeBytea.restype = POINTER(c_char) 239 | 240 | # Asynchronous Command Processing 241 | 242 | PQsendQuery = libpq.PQsendQuery 243 | PQsendQuery.argtypes = [PGconn_p, c_char_p] 244 | PQsendQuery.restype = c_int 245 | 246 | PQgetResult = libpq.PQgetResult 247 | PQgetResult.argtypes = [PGconn_p] 248 | PQgetResult.restype = PGresult_p 249 | 250 | PQconsumeInput = libpq.PQconsumeInput 251 | PQconsumeInput.argtypes = [PGconn_p] 252 | PQconsumeInput.restype = c_int 253 | 254 | PQisBusy = libpq.PQisBusy 255 | PQisBusy.argtypes = [PGconn_p] 256 | PQisBusy.restype = c_int 257 | 258 | PQsetnonblocking = libpq.PQsetnonblocking 259 | PQsetnonblocking.argtypes = [PGconn_p, c_int] 260 | PQsetnonblocking.restype = c_int 261 | 262 | PQflush = libpq.PQflush 263 | PQflush.argtypes = [PGconn_p] 264 | PQflush.restype = c_int 265 | 266 | # Cancelling queries in progress 267 | 268 | PQgetCancel = libpq.PQgetCancel 269 | PQgetCancel.argtypes = [PGconn_p] 270 | PQgetCancel.restype = PGcancel_p 271 | 272 | PQfreeCancel = libpq.PQfreeCancel 273 | PQfreeCancel.argtypes = [PGcancel_p] 274 | PQfreeCancel.restype = None 275 | 276 | PQcancel = libpq.PQcancel 277 | PQcancel.argtypes = [PGcancel_p, c_char_p, c_int] 278 | PQcancel.restype = c_int 279 | 280 | PQrequestCancel = libpq.PQrequestCancel 281 | PQrequestCancel.argtypes = [PGconn_p] 282 | PQrequestCancel.restype = c_int 283 | 284 | # Functions Associated with the COPY Command 285 | 286 | PQgetCopyData = libpq.PQgetCopyData 287 | PQgetCopyData.argtypes = [PGconn_p, POINTER(c_char_p), c_int] 288 | PQgetCopyData.restype = c_int 289 | 290 | PQputCopyData = libpq.PQputCopyData 291 | PQputCopyData.argtypes = [PGconn_p, c_char_p, c_int] 292 | PQputCopyData.restype = c_int 293 | 294 | PQputCopyEnd = libpq.PQputCopyEnd 295 | PQputCopyEnd.argtypes = [PGconn_p, c_char_p] 296 | PQputCopyEnd.restype = c_int 297 | 298 | # Miscellaneous functions 299 | 300 | PQfreemem = libpq.PQfreemem 301 | PQfreemem.argtypes = [c_void_p] 302 | PQfreemem.restype = None 303 | 304 | # Notice processing 305 | 306 | PQnoticeProcessor = CFUNCTYPE(None, c_void_p, c_char_p) 307 | 308 | PQsetNoticeProcessor = libpq.PQsetNoticeProcessor 309 | PQsetNoticeProcessor.argtypes = [PGconn_p, PQnoticeProcessor, c_void_p] 310 | PQsetNoticeProcessor.restype = PQnoticeProcessor 311 | 312 | 313 | PQnotifies = libpq.PQnotifies 314 | PQnotifies.argtypes = [PGconn_p] 315 | PQnotifies.restype = PGnotify_p 316 | 317 | 318 | # Large object 319 | Oid = c_int 320 | lo_open = libpq.lo_open 321 | lo_open.argtypes = [PGconn_p, Oid, c_int] 322 | lo_open.restype = c_int 323 | 324 | lo_create = libpq.lo_create 325 | lo_create.argtypes = [PGconn_p, Oid] 326 | lo_create.restype = Oid 327 | 328 | lo_import = libpq.lo_import 329 | lo_import.argtypes = [PGconn_p, c_char_p] 330 | lo_import.restype = Oid 331 | 332 | lo_read = libpq.lo_read 333 | lo_read.argtypes = [PGconn_p, c_int, c_char_p, c_int] 334 | lo_read.restype = c_int 335 | 336 | lo_write = libpq.lo_write 337 | lo_write.argtypes = [PGconn_p, c_int, c_char_p, c_int] 338 | lo_write.restype = c_int 339 | 340 | lo_tell = libpq.lo_tell 341 | lo_tell.argtypes = [PGconn_p, c_int] 342 | lo_tell.restype = c_int 343 | 344 | lo_lseek = libpq.lo_lseek 345 | lo_lseek.argtypes = [PGconn_p, c_int, c_int, c_int] 346 | lo_lseek.restype = c_int 347 | 348 | lo_close = libpq.lo_close 349 | lo_close.argtypes = [PGconn_p, c_int] 350 | lo_close.restype = c_int 351 | 352 | lo_unlink = libpq.lo_unlink 353 | lo_unlink.argtypes = [PGconn_p, Oid] 354 | lo_unlink.restype = c_int 355 | 356 | lo_export = libpq.lo_export 357 | lo_export.argtypes = [PGconn_p, Oid, c_char_p] 358 | lo_export.restype = c_int 359 | 360 | lo_truncate = libpq.lo_truncate 361 | lo_truncate.argtypes = [PGconn_p, c_int, c_int] 362 | lo_truncate.restype = c_int 363 | 364 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/lobject.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import wraps 3 | 4 | from psycopg2ct._impl import exceptions 5 | from psycopg2ct._impl import consts 6 | from psycopg2ct._impl import libpq 7 | from psycopg2ct._impl import util 8 | 9 | INV_WRITE = 0x00020000 10 | INV_READ = 0x00040000 11 | 12 | 13 | def check_unmarked(func): 14 | @wraps(func) 15 | def check_unmarked_(self, *args, **kwargs): 16 | if self._mark != self._conn._mark: 17 | raise exceptions.ProgrammingError("lobject isn't valid anymore") 18 | return func(self, *args, **kwargs) 19 | return check_unmarked_ 20 | 21 | 22 | def check_closed(func): 23 | @wraps(func) 24 | def check_closed_(self, *args, **kwargs): 25 | if self.closed: 26 | raise exceptions.InterfaceError("lobject already closed") 27 | return func(self, *args, **kwargs) 28 | return check_closed_ 29 | 30 | 31 | class LargeObject(object): 32 | def __init__(self, conn=None, oid=0, mode='', new_oid=0, new_file=None): 33 | self._conn = conn 34 | self._oid = oid 35 | self._mode = self._parse_mode(mode) 36 | self._smode = mode 37 | self._new_oid = new_oid 38 | self._new_file = new_file 39 | self._fd = -1 40 | self._mark = conn._mark 41 | 42 | if conn.autocommit: 43 | raise exceptions.ProgrammingError( 44 | "can't use a lobject outside of transactions") 45 | self._open() 46 | 47 | @property 48 | def oid(self): 49 | return self._oid 50 | 51 | @property 52 | def mode(self): 53 | return self._smode 54 | 55 | @check_closed 56 | @check_unmarked 57 | def read(self, size=-1): 58 | """Read at most size bytes or to the end of the large object.""" 59 | if size < 0: 60 | where = self.tell() 61 | end = self.seek(0, os.SEEK_END) 62 | self.seek(where, os.SEEK_SET) 63 | size = end - where 64 | 65 | if size == 0: 66 | return '' 67 | 68 | buf = libpq.create_string_buffer('\0', size) 69 | length = libpq.lo_read(self._conn._pgconn, self._fd, buf, size) 70 | if length < 0: 71 | return 72 | 73 | if self._mode & consts.LOBJECT_BINARY: 74 | return buf.raw 75 | else: 76 | return buf.value.decode(self._conn._py_enc) 77 | 78 | @check_closed 79 | @check_unmarked 80 | def write(self, value): 81 | """Write a string to the large object.""" 82 | if isinstance(value, unicode): 83 | value = value.encode(self._conn._py_enc) 84 | length = libpq.lo_write( 85 | self._conn._pgconn, self._fd, value, len(value)) 86 | if length < 0: 87 | raise self._conn._create_exception() 88 | return length 89 | 90 | def export(self, file_name): 91 | """Export large object to given file.""" 92 | self._conn._begin_transaction() 93 | if libpq.lo_export(self._conn._pgconn, self._oid, file_name) < 0: 94 | raise self._conn._create_exception() 95 | 96 | @check_closed 97 | @check_unmarked 98 | def seek(self, offset, whence=0): 99 | """Set the lobject's current position.""" 100 | return libpq.lo_lseek(self._conn._pgconn, self._fd, offset, whence) 101 | 102 | @check_closed 103 | @check_unmarked 104 | def tell(self): 105 | """Return the lobject's current position.""" 106 | return libpq.lo_tell(self._conn._pgconn, self._fd) 107 | 108 | @check_closed 109 | @check_unmarked 110 | def truncate(self, length=0): 111 | ret = libpq.lo_truncate(self._conn._pgconn, self._fd, length) 112 | if ret < 0: 113 | raise self._conn._create_exception() 114 | return ret 115 | 116 | def close(self): 117 | """Close and then remove the lobject.""" 118 | if self.closed: 119 | return True 120 | if self._conn.autocommit or self._conn._mark != self._mark: 121 | return True 122 | 123 | ret = libpq.lo_close(self._conn._pgconn, self._fd) 124 | self._fd = -1 125 | if ret < 0: 126 | raise self._conn._create_exception() 127 | else: 128 | return True 129 | 130 | @property 131 | def closed(self): 132 | return self._fd < 0 or not self._conn or self._conn.closed 133 | 134 | def unlink(self): 135 | self._conn._begin_transaction() 136 | self.close() 137 | libpq.lo_unlink(self._conn._pgconn, self._oid) 138 | 139 | def _open(self): 140 | conn = self._conn 141 | 142 | conn._begin_transaction() 143 | 144 | if self._oid == 0: 145 | if self._new_file: 146 | self._oid = libpq.lo_import(conn._pgconn, self._new_file) 147 | else: 148 | self._oid = libpq.lo_create(conn._pgconn, self._new_oid) 149 | 150 | self._mode = \ 151 | (self._mode & ~consts.LOBJECT_READ) | consts.LOBJECT_WRITE 152 | 153 | pgmode = 0 154 | if self._mode & consts.LOBJECT_READ: 155 | pgmode |= INV_READ 156 | if self._mode & consts.LOBJECT_WRITE: 157 | pgmode |= INV_WRITE 158 | 159 | if pgmode: 160 | self._fd = libpq.lo_open(conn._pgconn, self._oid, pgmode) 161 | if self._fd < 0: 162 | raise self._conn._create_exception() 163 | 164 | self._smode = self._unparse_mode(self._mode) 165 | 166 | def _parse_mode(self, smode): 167 | """Convert a mode string to a mode int""" 168 | mode = 0 169 | pos = 0 170 | 171 | if not smode: 172 | return consts.LOBJECT_READ | consts.LOBJECT_BINARY 173 | 174 | if smode[0:2] == 'rw': 175 | mode |= consts.LOBJECT_READ | consts.LOBJECT_WRITE 176 | pos = 2 177 | else: 178 | if smode[0] == 'r': 179 | mode |= consts.LOBJECT_READ 180 | pos = 1 181 | elif smode[0] == 'w': 182 | mode |= consts.LOBJECT_WRITE 183 | pos = 1 184 | elif smode[0] == 'n': 185 | pos = 1 186 | else: 187 | mode |= consts.LOBJECT_READ 188 | 189 | if len(smode) > pos: 190 | if smode[pos] == 't': 191 | mode |= consts.LOBJECT_TEXT 192 | pos += 1 193 | elif smode[pos] == 'b': 194 | mode |= consts.LOBJECT_BINARY 195 | pos += 1 196 | else: 197 | mode |= consts.LOBJECT_BINARY 198 | else: 199 | mode |= consts.LOBJECT_BINARY 200 | 201 | if len(smode) != pos: 202 | raise ValueError("bad mode for lobject: '%s'", smode) 203 | return mode 204 | 205 | def _unparse_mode(self, mode): 206 | """Convert a mode int to a mode string""" 207 | smode = '' 208 | if mode & consts.LOBJECT_READ: 209 | smode += 'r' 210 | if mode & consts.LOBJECT_WRITE: 211 | smode += 'w' 212 | if not smode: 213 | smode += 'n' 214 | 215 | if mode & consts.LOBJECT_TEXT: 216 | smode += 't' 217 | else: 218 | smode += 'b' 219 | return smode 220 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/notify.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Notify(object): 4 | def __init__(self, pid, channel, payload=''): 5 | self.pid = pid 6 | self.channel = channel 7 | self.payload = payload 8 | 9 | def __eq__(self, other): 10 | if isinstance(other, tuple): 11 | return other == self._astuple(False) 12 | if isinstance(other, Notify): 13 | return self._astuple(True) == other._astuple(True) 14 | return False 15 | 16 | def __ne__(self, other): 17 | return not self.__eq__(other) 18 | 19 | def __hash__(self): 20 | return hash(self._astuple(bool(self.payload))) 21 | 22 | def __getitem__(self, key): 23 | return (self.pid, self.channel)[key] 24 | 25 | def __len__(self): 26 | return 2 27 | 28 | def _astuple(self, with_payload): 29 | if not with_payload: 30 | return (self.pid, self.channel) 31 | return (self.pid, self.channel, self.payload) 32 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/typecasts.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import decimal 3 | import math 4 | from time import localtime 5 | 6 | from psycopg2ct._impl import libpq 7 | 8 | 9 | string_types = {} 10 | 11 | binary_types = {} 12 | 13 | 14 | class Type(object): 15 | def __init__(self, name, values, caster=None, py_caster=None): 16 | self.name = name 17 | self.values = values 18 | self.caster = caster 19 | self.py_caster = py_caster 20 | 21 | def __eq__(self, other): 22 | return other in self.values 23 | 24 | def cast(self, value, cursor, length=None): 25 | if self.py_caster is not None: 26 | return self.py_caster(value, cursor) 27 | return self.caster(value, length, cursor) 28 | 29 | 30 | def register_type(type_obj, scope=None): 31 | typecasts = string_types 32 | if scope: 33 | from psycopg2ct._impl.connection import Connection 34 | from psycopg2ct._impl.cursor import Cursor 35 | 36 | if isinstance(scope, Connection): 37 | typecasts = scope._typecasts 38 | elif isinstance(scope, Cursor): 39 | typecasts = scope._typecasts 40 | else: 41 | typecasts = None 42 | 43 | for value in type_obj.values: 44 | typecasts[value] = type_obj 45 | 46 | 47 | def new_type(values, name, castobj): 48 | return Type(name, values, py_caster=castobj) 49 | 50 | 51 | def new_array_type(values, name, baseobj): 52 | caster = parse_array(baseobj) 53 | return Type(name, values, caster=caster) 54 | 55 | 56 | def typecast(caster, value, length, cursor): 57 | return caster.cast(value, cursor, length) 58 | 59 | 60 | def parse_unknown(value, length, cursor): 61 | if value != '{}': 62 | return value 63 | else: 64 | return [] 65 | 66 | 67 | def parse_string(value, length, cursor): 68 | return value 69 | 70 | 71 | def parse_longinteger(value, length, cursor): 72 | return long(value) 73 | 74 | 75 | def parse_integer(value, length, cursor): 76 | return int(value) 77 | 78 | 79 | def parse_float(value, length, cursor): 80 | return float(value) 81 | 82 | 83 | def parse_decimal(value, length, cursor): 84 | return decimal.Decimal(value) 85 | 86 | 87 | def parse_binary(value, length, cursor): 88 | to_length = libpq.c_uint() 89 | s = libpq.PQunescapeBytea(value, libpq.pointer(to_length)) 90 | try: 91 | res = buffer(s[:to_length.value]) 92 | finally: 93 | libpq.PQfreemem(s) 94 | return res 95 | 96 | 97 | def parse_boolean(value, length, cursor): 98 | """Typecast the postgres boolean to a python boolean. 99 | 100 | Postgres returns the boolean as a string with 'true' or 'false' 101 | 102 | """ 103 | return value[0] == "t" 104 | 105 | 106 | class parse_array(object): 107 | """Parse an array of a items using an configurable caster for the items 108 | 109 | The array syntax is defined as:: 110 | 111 | '{ val1 delim val2 delim ... }' 112 | 113 | A two-dimensional array with string items is defined as:: 114 | 115 | '{{"meeting", "lunch"}, {"training", "presentation"}}' 116 | 117 | """ 118 | def __init__(self, caster): 119 | self._caster = caster 120 | 121 | def cast(self, value, length, cursor): 122 | return self(value, length, cursor) 123 | 124 | def __call__(self, value, length, cursor): 125 | s = value 126 | assert s[0] == "{" and s[-1] == "}" 127 | i = 1 128 | array = [] 129 | stack = [array] 130 | value_length = len(s) - 1 131 | while i < value_length: 132 | if s[i] == '{': 133 | sub_array = [] 134 | array.append(sub_array) 135 | stack.append(sub_array) 136 | array = sub_array 137 | i += 1 138 | elif s[i] == '}': 139 | stack.pop() 140 | array = stack[-1] 141 | i += 1 142 | elif s[i] in ', ': 143 | i += 1 144 | else: 145 | # Number of quotes, this will always be 0 or 2 (int vs str) 146 | quotes = 0 147 | 148 | # Whether or not the next char should be escaped 149 | escape_char = False 150 | 151 | buf = [] 152 | while i < value_length: 153 | if not escape_char: 154 | if s[i] == '"': 155 | quotes += 1 156 | elif s[i] == '\\': 157 | escape_char = True 158 | elif quotes % 2 == 0 and (s[i] == '}' or s[i] == ','): 159 | break 160 | else: 161 | buf.append(s[i]) 162 | else: 163 | escape_char = False 164 | buf.append(s[i]) 165 | 166 | i += 1 167 | 168 | str_buf = ''.join(buf) 169 | if len(str_buf) == 4 and str_buf.lower() == 'null': 170 | val = typecast(self._caster, None, 0, cursor) 171 | else: 172 | val = typecast(self._caster, str_buf, len(str_buf), cursor) 173 | array.append(val) 174 | return stack[-1] 175 | 176 | 177 | def parse_unicode(value, length, cursor): 178 | """Decode the given value with the connection encoding""" 179 | return value.decode(cursor._conn._py_enc) 180 | 181 | 182 | def _parse_date(value): 183 | return datetime.date(*[int(x) for x in value.split('-')]) 184 | 185 | 186 | def _parse_time(value, cursor): 187 | """Parse the time to a datetime.time type. 188 | 189 | The given value is in the format of `16:28:09.506488+01` 190 | 191 | """ 192 | microsecond = 0 193 | hour, minute, second = value.split(':', 2) 194 | 195 | sign = 0 196 | tzinfo = None 197 | timezone = None 198 | if '-' in second: 199 | sign = -1 200 | second, timezone = second.split('-') 201 | elif '+' in second: 202 | sign = 1 203 | second, timezone = second.split('+') 204 | 205 | if not cursor.tzinfo_factory is None and sign: 206 | parts = timezone.split(':') 207 | tz_min = sign * 60 * int(parts[0]) 208 | if len(parts) > 1: 209 | tz_min += int(parts[1]) 210 | if len(parts) > 2: 211 | tz_min += int(int(parts[2]) / 60.0) 212 | tzinfo = cursor.tzinfo_factory(tz_min) 213 | 214 | if '.' in second: 215 | second, microsecond = second.split('.') 216 | microsecond = int(microsecond) * int(math.pow(10.0, 6.0 - len(microsecond))) 217 | 218 | return datetime.time(int(hour), int(minute), int(second), microsecond, 219 | tzinfo) 220 | 221 | 222 | def parse_datetime(value, length, cursor): 223 | date, time = value.split(' ') 224 | date = _parse_date(date) 225 | time = _parse_time(time, cursor) 226 | return datetime.datetime.combine(date, time) 227 | 228 | 229 | def parse_date(value, length, cursor): 230 | return _parse_date(value) 231 | 232 | 233 | def parse_time(value, length, cursor): 234 | return _parse_time(value, cursor) 235 | 236 | 237 | def parse_interval(value, length, cursor): 238 | """Typecast an interval to a datetime.timedelta instance. 239 | 240 | For example, the value '2 years 1 mon 3 days 10:01:39.100' is converted 241 | to `datetime.timedelta(763, 36099, 100)`. 242 | 243 | """ 244 | years = months = days = 0 245 | hours = minutes = seconds = hundreths = 0.0 246 | v = 0.0 247 | sign = 1 248 | denominator = 1.0 249 | part = 0 250 | skip_to_space = False 251 | 252 | s = value 253 | for c in s: 254 | if skip_to_space: 255 | if c == " ": 256 | skip_to_space = False 257 | continue 258 | if c == "-": 259 | sign = -1 260 | elif "0" <= c <= "9": 261 | v = v * 10 + ord(c) - ord("0") 262 | if part == 6: 263 | denominator *= 10 264 | elif c == "y": 265 | if part == 0: 266 | years = int(v * sign) 267 | skip_to_space = True 268 | v = 0.0 269 | sign = 1 270 | part = 1 271 | elif c == "m": 272 | if part <= 1: 273 | months = int(v * sign) 274 | skip_to_space = True 275 | v = 0.0 276 | sign = 1 277 | part = 2 278 | elif c == "d": 279 | if part <= 2: 280 | days = int(v * sign) 281 | skip_to_space = True 282 | v = 0.0 283 | sign = 1 284 | part = 3 285 | elif c == ":": 286 | if part <= 3: 287 | hours = v 288 | v = 0.0 289 | part = 4 290 | elif part == 4: 291 | minutes = v 292 | v = 0.0 293 | part = 5 294 | elif c == ".": 295 | if part == 5: 296 | seconds = v 297 | v = 0.0 298 | part = 6 299 | 300 | if part == 4: 301 | minutes = v 302 | elif part == 5: 303 | seconds = v 304 | elif part == 6: 305 | hundreths = v / denominator 306 | 307 | if sign < 0.0: 308 | seconds = - (hundreths + seconds + minutes * 60 + hours * 3600) 309 | else: 310 | seconds += hundreths + minutes * 60 + hours * 3600 311 | 312 | days += years * 365 + months * 30 313 | micro = (seconds - math.floor(seconds)) * 1000000.0 314 | seconds = int(math.floor(seconds)) 315 | return datetime.timedelta(days, seconds, int(micro)) 316 | 317 | 318 | 319 | def Date(year, month, day): 320 | from psycopg2ct.extensions.adapters import DateTime 321 | date = datetime.date(year, month, day) 322 | return DateTime(date) 323 | 324 | 325 | def DateFromTicks(ticks): 326 | tm = localtime() 327 | return Date(tm.tm_year, tm.tm_mon, tm.tm_mday) 328 | 329 | 330 | def Binary(obj): 331 | from psycopg2ct.extensions.adapters import Binary 332 | return Binary(obj) 333 | 334 | 335 | def _default_type(name, oids, caster): 336 | """Shortcut to register internal types""" 337 | type_obj = Type(name, oids, caster) 338 | register_type(type_obj) 339 | return type_obj 340 | 341 | 342 | # DB API 2.0 types 343 | BINARY = _default_type('BINARY', [17], parse_binary) 344 | DATETIME = _default_type('DATETIME', [1114, 1184, 704, 1186], parse_datetime) 345 | NUMBER = _default_type('NUMBER', [20, 33, 21, 701, 700, 1700], parse_float) 346 | ROWID = _default_type('ROWID', [26], parse_integer) 347 | STRING = _default_type('STRING', [19, 18, 25, 1042, 1043], parse_string) 348 | 349 | # Register the basic typecasters 350 | BOOLEAN = _default_type('BOOLEAN', [16], parse_boolean) 351 | DATE = _default_type('DATE', [1082], parse_date) 352 | DECIMAL = _default_type('DECIMAL', [1700], parse_decimal) 353 | FLOAT = _default_type('FLOAT', [701, 700], parse_float) 354 | INTEGER = _default_type('INTEGER', [23, 21], parse_integer) 355 | INTERVAL = _default_type('INTERVAL', [704, 1186], parse_interval) 356 | LONGINTEGER = _default_type('LONGINTEGER', [20], parse_longinteger) 357 | TIME = _default_type('TIME', [1083, 1266], parse_time) 358 | UNKNOWN = _default_type('UNKNOWN', [705], parse_unknown) 359 | 360 | # Array types 361 | BINARYARRAY = _default_type( 362 | 'BINARYARRAY', [1001], parse_array(BINARY)) 363 | BOOLEANARRAY = _default_type( 364 | 'BOOLEANARRAY', [1000], parse_array(BOOLEAN)) 365 | DATEARRAY = _default_type( 366 | 'DATEARRAY', [1182], parse_array(DATE)) 367 | DATETIMEARRAY = _default_type( 368 | 'DATETIMEARRAY', [1115, 1185], parse_array(DATETIME)) 369 | DECIMALARRAY = _default_type( 370 | 'DECIMALARRAY', [1231], parse_array(DECIMAL)) 371 | FLOATARRAY = _default_type( 372 | 'FLOATARRAY', [1017, 1021, 1022], parse_array(FLOAT)) 373 | INTEGERARRAY = _default_type( 374 | 'INTEGERARRAY', [1005, 1006, 1007], parse_array(INTEGER)) 375 | INTERVALARRAY = _default_type( 376 | 'INTERVALARRAY', [1187], parse_array(INTERVAL)) 377 | LONGINTEGERARRAY = _default_type( 378 | 'LONGINTEGERARRAY', [1016], parse_array(LONGINTEGER)) 379 | ROWIDARRAY = _default_type( 380 | 'ROWIDARRAY', [1013, 1028], parse_array(ROWID)) 381 | STRINGARRAY = _default_type( 382 | 'STRINGARRAY', [1002, 1003, 1009, 1014, 1015], parse_array(STRING)) 383 | TIMEARRAY = _default_type( 384 | 'TIMEARRAY', [1183, 1270], parse_array(TIME)) 385 | 386 | 387 | UNICODE = Type('UNICODE', [19, 18, 25, 1042, 1043], parse_unicode) 388 | UNICODEARRAY = Type('UNICODEARRAY', [1002, 1003, 1009, 1014, 1015], 389 | parse_array(UNICODE)) 390 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/util.py: -------------------------------------------------------------------------------- 1 | from psycopg2ct._impl import exceptions 2 | from psycopg2ct._impl import libpq 3 | from psycopg2ct._impl.adapters import QuotedString 4 | 5 | 6 | def pq_set_non_blocking(pgconn, arg, raise_exception=False): 7 | ret = libpq.PQsetnonblocking(pgconn, arg) 8 | if ret != 0 and raise_exception: 9 | raise exceptions.OperationalError('PQsetnonblocking() failed') 10 | return ret 11 | 12 | 13 | def pq_clear_async(pgconn): 14 | while True: 15 | pgres = libpq.PQgetResult(pgconn) 16 | if not pgres: 17 | break 18 | libpq.PQclear(pgres) 19 | 20 | 21 | def pq_get_last_result(pgconn): 22 | pgres_next = None 23 | pgres = libpq.PQgetResult(pgconn) 24 | if not pgres: 25 | return 26 | 27 | while True: 28 | pgres_next = libpq.PQgetResult(pgconn) 29 | if not pgres_next: 30 | break 31 | 32 | if pgres: 33 | libpq.PQclear(pgres) 34 | pgres = pgres_next 35 | 36 | return pgres 37 | 38 | 39 | def quote_string(conn, value): 40 | obj = QuotedString(value) 41 | obj.prepare(conn) 42 | return obj.getquoted() 43 | 44 | 45 | def get_exception_for_sqlstate(code): 46 | """Translate the sqlstate to a relevant exception. 47 | 48 | See for a list of possible errors: 49 | http://www.postgresql.org/docs/current/static/errcodes-appendix.html 50 | 51 | """ 52 | if code[0] == '0': 53 | # Class 0A - Feature Not Supported 54 | if code[1] == 'A': 55 | return exceptions.NotSupportedError 56 | 57 | elif code[0] == '2': 58 | # Class 21 - Cardinality Violation 59 | if code[1] == '1': 60 | return exceptions.ProgrammingError 61 | 62 | # Class 22 - Data Exception 63 | if code[1] == '2': 64 | return exceptions.DataError 65 | 66 | # Class 23 - Integrity Constraint Violation 67 | if code[1] == '3': 68 | return exceptions.IntegrityError 69 | 70 | # Class 24 - Invalid Cursor State 71 | # Class 25 - Invalid Transaction State 72 | if code[1] in '45': 73 | return exceptions.InternalError 74 | 75 | # Class 26 - Invalid SQL Statement Name 76 | # Class 27 - Triggered Data Change Violation 77 | # Class 28 - Invalid Authorization Specification 78 | if code[1] in '678': 79 | return exceptions.OperationalError 80 | 81 | # Class 2B - Dependent Privilege Descriptors Still Exist 82 | # Class 2D - Invalid Transaction Termination 83 | # Class 2F - SQL Routine Exception 84 | if code[1] in 'BDF': 85 | return exceptions.InternalError 86 | 87 | elif code[0] == '3': 88 | # Class 34 - Invalid Cursor Name 89 | if code[1] == '4': 90 | return exceptions.OperationalError 91 | 92 | # Class 38 - External Routine Exception 93 | # Class 39 - External Routine Invocation Exception 94 | # Class 3B - Savepoint Exception 95 | if code[1] in '89B': 96 | return exceptions.InternalError 97 | 98 | # Class 3D - Invalid Catalog Name 99 | # Class 3F - Invalid Schema Name 100 | if code[1] in 'DF': 101 | return exceptions.ProgrammingError 102 | 103 | elif code[0] == '4': 104 | # Class 40 - Transaction Rollback 105 | if code[1] == '0': 106 | return exceptions.TransactionRollbackError 107 | 108 | # Class 42 - Syntax Error or Access Rule Violation 109 | # Class 44 - WITH CHECK OPTION Violation 110 | if code[1] in '24': 111 | return exceptions.ProgrammingError 112 | 113 | elif code[0] == '5': 114 | if code == '57014': 115 | return exceptions.QueryCanceledError 116 | 117 | # Class 53 - Insufficient Resources 118 | # Class 54 - Program Limit Exceeded 119 | # Class 55 - Object Not In Prerequisite State 120 | # Class 57 - Operator Intervention 121 | # Class 58 - System Error (errors external to PostgreSQL itself) 122 | if code in '34578': 123 | return exceptions.OperationalError 124 | 125 | elif code[0] == 'F': 126 | # Class F0 - Configuration File Error 127 | return exceptions.InternalError 128 | 129 | elif code[0] == 'P': 130 | # Class P0 - PL/pgSQL Error 131 | return exceptions.InternalError 132 | 133 | elif code[0] == 'X': 134 | # Class XX - Internal Error 135 | return exceptions.InternalError 136 | 137 | # Fallback exception 138 | return exceptions.DatabaseError 139 | 140 | -------------------------------------------------------------------------------- /psycopg2ct/_impl/xid.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | 4 | from psycopg2ct._impl import consts 5 | 6 | 7 | class Xid(object): 8 | def __init__(self, format_id, gtrid, bqual): 9 | if not 0 <= format_id <= 0x7FFFFFFF: 10 | raise ValueError("format_id must be a non-negative 32-bit integer") 11 | 12 | if len(gtrid) > 64: 13 | raise ValueError("gtrid must be a string no longer than 64 characters") 14 | 15 | for char in gtrid: 16 | if not 0x20 <= ord(char) <= 0x7F: 17 | raise ValueError("gtrid must contain only printable characters") 18 | 19 | if len(bqual) > 64: 20 | raise ValueError("bqual must be a string no longer than 64 characters") 21 | 22 | for char in bqual: 23 | if not 0x20 <= ord(char) <= 0x7F: 24 | raise ValueError("bqual must contain only printable characters") 25 | 26 | self.format_id = format_id 27 | self.gtrid = gtrid 28 | self.bqual = bqual 29 | 30 | self.prepared = None 31 | self.owner = None 32 | self.database = None 33 | 34 | def as_tid(self): 35 | if self.format_id is not None: 36 | gtrid = self.gtrid.encode('base64')[:-1] 37 | bqual = self.bqual.encode('base64')[:-1] 38 | return "%d_%s_%s" % (int(self.format_id), gtrid, bqual) 39 | else: 40 | return self.gtrid 41 | 42 | def __str__(self): 43 | return self.as_tid() 44 | 45 | @classmethod 46 | def from_string(self, s, _re=re.compile("^(\\d+)_([^_]*)_([^_]*)$")): 47 | m = _re.match(s) 48 | if m is not None: 49 | try: 50 | format_id = int(m.group(1)) 51 | gtrid = m.group(2).decode('base64') 52 | bqual = m.group(3).decode('base64') 53 | return Xid(format_id, gtrid, bqual) 54 | except Exception: 55 | pass 56 | 57 | # parsing failed: unparsed xid 58 | xid = Xid(0, '', '') 59 | xid.gtrid = s 60 | xid.format_id = None 61 | xid.bqual = None 62 | 63 | return xid 64 | 65 | def __getitem__(self, idx): 66 | if idx < 0: 67 | idx += 3 68 | 69 | if idx == 0: 70 | return self.format_id 71 | elif idx == 1: 72 | return self.gtrid 73 | elif idx == 2: 74 | return self.bqual 75 | raise IndexError("index out of range") 76 | 77 | @classmethod 78 | def tpc_recover(self, conn): 79 | # should we rollback? 80 | rb = conn.status == consts.STATUS_READY and not conn.autocommit 81 | 82 | cur = conn.cursor() 83 | try: 84 | cur.execute( 85 | "SELECT gid, prepared, owner, database " 86 | "FROM pg_prepared_xacts") 87 | 88 | rv = [] 89 | for gid, prepared, owner, database in cur: 90 | xid = Xid.from_string(gid) 91 | xid.prepared = prepared 92 | xid.owner = owner 93 | xid.database = database 94 | rv.append(xid) 95 | 96 | return rv 97 | 98 | finally: 99 | if rb: 100 | conn.rollback() 101 | 102 | -------------------------------------------------------------------------------- /psycopg2ct/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import psycopg2ct 3 | 4 | 5 | def register(): 6 | sys.modules['psycopg2'] = psycopg2ct 7 | 8 | -------------------------------------------------------------------------------- /psycopg2ct/errorcodes.py: -------------------------------------------------------------------------------- 1 | """Error codes for PostgresSQL 2 | 3 | This module contains symbolic names for all PostgreSQL error codes. 4 | """ 5 | # psycopg2/errorcodes.py - PostgreSQL error codes 6 | # 7 | # Copyright (C) 2006-2010 Johan Dahlin 8 | # 9 | # psycopg2 is free software: you can redistribute it and/or modify it 10 | # under the terms of the GNU Lesser General Public License as published 11 | # by the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # In addition, as a special exception, the copyright holders give 15 | # permission to link this program with the OpenSSL library (or with 16 | # modified versions of OpenSSL that use the same license as OpenSSL), 17 | # and distribute linked combinations including the two. 18 | # 19 | # You must obey the GNU Lesser General Public License in all respects for 20 | # all of the code used other than OpenSSL. 21 | # 22 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 23 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 24 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 25 | # License for more details. 26 | # 27 | # Based on: 28 | # 29 | # http://www.postgresql.org/docs/8.4/static/errcodes-appendix.html 30 | # 31 | 32 | def lookup(code, _cache={}): 33 | """Lookup an error code or class code and return its symbolic name. 34 | 35 | Raise `KeyError` if the code is not found. 36 | """ 37 | if _cache: 38 | return _cache[code] 39 | 40 | # Generate the lookup map at first usage. 41 | for k, v in globals().iteritems(): 42 | if isinstance(v, str) and len(v) in (2, 5): 43 | _cache[v] = k 44 | 45 | return lookup(code) 46 | 47 | 48 | # autogenerated data: do not edit below this point. 49 | 50 | # Error classes 51 | CLASS_SUCCESSFUL_COMPLETION = '00' 52 | CLASS_WARNING = '01' 53 | CLASS_NO_DATA = '02' 54 | CLASS_SQL_STATEMENT_NOT_YET_COMPLETE = '03' 55 | CLASS_CONNECTION_EXCEPTION = '08' 56 | CLASS_TRIGGERED_ACTION_EXCEPTION = '09' 57 | CLASS_FEATURE_NOT_SUPPORTED = '0A' 58 | CLASS_INVALID_TRANSACTION_INITIATION = '0B' 59 | CLASS_LOCATOR_EXCEPTION = '0F' 60 | CLASS_INVALID_GRANTOR = '0L' 61 | CLASS_INVALID_ROLE_SPECIFICATION = '0P' 62 | CLASS_CASE_NOT_FOUND = '20' 63 | CLASS_CARDINALITY_VIOLATION = '21' 64 | CLASS_DATA_EXCEPTION = '22' 65 | CLASS_INTEGRITY_CONSTRAINT_VIOLATION = '23' 66 | CLASS_INVALID_CURSOR_STATE = '24' 67 | CLASS_INVALID_TRANSACTION_STATE = '25' 68 | CLASS_INVALID_SQL_STATEMENT_NAME = '26' 69 | CLASS_TRIGGERED_DATA_CHANGE_VIOLATION = '27' 70 | CLASS_INVALID_AUTHORIZATION_SPECIFICATION = '28' 71 | CLASS_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST = '2B' 72 | CLASS_INVALID_TRANSACTION_TERMINATION = '2D' 73 | CLASS_SQL_ROUTINE_EXCEPTION = '2F' 74 | CLASS_INVALID_CURSOR_NAME = '34' 75 | CLASS_EXTERNAL_ROUTINE_EXCEPTION = '38' 76 | CLASS_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION = '39' 77 | CLASS_SAVEPOINT_EXCEPTION = '3B' 78 | CLASS_INVALID_CATALOG_NAME = '3D' 79 | CLASS_INVALID_SCHEMA_NAME = '3F' 80 | CLASS_TRANSACTION_ROLLBACK = '40' 81 | CLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION = '42' 82 | CLASS_WITH_CHECK_OPTION_VIOLATION = '44' 83 | CLASS_INSUFFICIENT_RESOURCES = '53' 84 | CLASS_PROGRAM_LIMIT_EXCEEDED = '54' 85 | CLASS_OBJECT_NOT_IN_PREREQUISITE_STATE = '55' 86 | CLASS_OPERATOR_INTERVENTION = '57' 87 | CLASS_SYSTEM_ERROR = '58' 88 | CLASS_CONFIGURATION_FILE_ERROR = 'F0' 89 | CLASS_FOREIGN_DATA_WRAPPER_ERROR = 'HV' 90 | CLASS_PL_PGSQL_ERROR = 'P0' 91 | CLASS_INTERNAL_ERROR = 'XX' 92 | 93 | # Class 00 - Successful Completion 94 | SUCCESSFUL_COMPLETION = '00000' 95 | 96 | # Class 01 - Warning 97 | WARNING = '01000' 98 | NULL_VALUE_ELIMINATED_IN_SET_FUNCTION = '01003' 99 | STRING_DATA_RIGHT_TRUNCATION = '01004' 100 | PRIVILEGE_NOT_REVOKED = '01006' 101 | PRIVILEGE_NOT_GRANTED = '01007' 102 | IMPLICIT_ZERO_BIT_PADDING = '01008' 103 | DYNAMIC_RESULT_SETS_RETURNED = '0100C' 104 | DEPRECATED_FEATURE = '01P01' 105 | 106 | # Class 02 - No Data (this is also a warning class per the SQL standard) 107 | NO_DATA = '02000' 108 | NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED = '02001' 109 | 110 | # Class 03 - SQL Statement Not Yet Complete 111 | SQL_STATEMENT_NOT_YET_COMPLETE = '03000' 112 | 113 | # Class 08 - Connection Exception 114 | CONNECTION_EXCEPTION = '08000' 115 | SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION = '08001' 116 | CONNECTION_DOES_NOT_EXIST = '08003' 117 | SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION = '08004' 118 | CONNECTION_FAILURE = '08006' 119 | TRANSACTION_RESOLUTION_UNKNOWN = '08007' 120 | PROTOCOL_VIOLATION = '08P01' 121 | 122 | # Class 09 - Triggered Action Exception 123 | TRIGGERED_ACTION_EXCEPTION = '09000' 124 | 125 | # Class 0A - Feature Not Supported 126 | FEATURE_NOT_SUPPORTED = '0A000' 127 | 128 | # Class 0B - Invalid Transaction Initiation 129 | INVALID_TRANSACTION_INITIATION = '0B000' 130 | 131 | # Class 0F - Locator Exception 132 | LOCATOR_EXCEPTION = '0F000' 133 | INVALID_LOCATOR_SPECIFICATION = '0F001' 134 | 135 | # Class 0L - Invalid Grantor 136 | INVALID_GRANTOR = '0L000' 137 | INVALID_GRANT_OPERATION = '0LP01' 138 | 139 | # Class 0P - Invalid Role Specification 140 | INVALID_ROLE_SPECIFICATION = '0P000' 141 | 142 | # Class 20 - Case Not Found 143 | CASE_NOT_FOUND = '20000' 144 | 145 | # Class 21 - Cardinality Violation 146 | CARDINALITY_VIOLATION = '21000' 147 | 148 | # Class 22 - Data Exception 149 | DATA_EXCEPTION = '22000' 150 | STRING_DATA_RIGHT_TRUNCATION = '22001' 151 | NULL_VALUE_NO_INDICATOR_PARAMETER = '22002' 152 | NUMERIC_VALUE_OUT_OF_RANGE = '22003' 153 | NULL_VALUE_NOT_ALLOWED = '22004' 154 | ERROR_IN_ASSIGNMENT = '22005' 155 | INVALID_DATETIME_FORMAT = '22007' 156 | DATETIME_FIELD_OVERFLOW = '22008' 157 | INVALID_TIME_ZONE_DISPLACEMENT_VALUE = '22009' 158 | ESCAPE_CHARACTER_CONFLICT = '2200B' 159 | INVALID_USE_OF_ESCAPE_CHARACTER = '2200C' 160 | INVALID_ESCAPE_OCTET = '2200D' 161 | ZERO_LENGTH_CHARACTER_STRING = '2200F' 162 | MOST_SPECIFIC_TYPE_MISMATCH = '2200G' 163 | NOT_AN_XML_DOCUMENT = '2200L' 164 | INVALID_XML_DOCUMENT = '2200M' 165 | INVALID_XML_CONTENT = '2200N' 166 | INVALID_XML_COMMENT = '2200S' 167 | INVALID_XML_PROCESSING_INSTRUCTION = '2200T' 168 | INVALID_INDICATOR_PARAMETER_VALUE = '22010' 169 | SUBSTRING_ERROR = '22011' 170 | DIVISION_BY_ZERO = '22012' 171 | INVALID_ARGUMENT_FOR_NTILE_FUNCTION = '22014' 172 | INTERVAL_FIELD_OVERFLOW = '22015' 173 | INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION = '22016' 174 | INVALID_CHARACTER_VALUE_FOR_CAST = '22018' 175 | INVALID_ESCAPE_CHARACTER = '22019' 176 | INVALID_REGULAR_EXPRESSION = '2201B' 177 | INVALID_ARGUMENT_FOR_LOGARITHM = '2201E' 178 | INVALID_ARGUMENT_FOR_POWER_FUNCTION = '2201F' 179 | INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION = '2201G' 180 | INVALID_ROW_COUNT_IN_LIMIT_CLAUSE = '2201W' 181 | INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE = '2201X' 182 | INVALID_LIMIT_VALUE = '22020' 183 | CHARACTER_NOT_IN_REPERTOIRE = '22021' 184 | INDICATOR_OVERFLOW = '22022' 185 | INVALID_PARAMETER_VALUE = '22023' 186 | UNTERMINATED_C_STRING = '22024' 187 | INVALID_ESCAPE_SEQUENCE = '22025' 188 | STRING_DATA_LENGTH_MISMATCH = '22026' 189 | TRIM_ERROR = '22027' 190 | ARRAY_SUBSCRIPT_ERROR = '2202E' 191 | FLOATING_POINT_EXCEPTION = '22P01' 192 | INVALID_TEXT_REPRESENTATION = '22P02' 193 | INVALID_BINARY_REPRESENTATION = '22P03' 194 | BAD_COPY_FILE_FORMAT = '22P04' 195 | UNTRANSLATABLE_CHARACTER = '22P05' 196 | NONSTANDARD_USE_OF_ESCAPE_CHARACTER = '22P06' 197 | 198 | # Class 23 - Integrity Constraint Violation 199 | INTEGRITY_CONSTRAINT_VIOLATION = '23000' 200 | RESTRICT_VIOLATION = '23001' 201 | NOT_NULL_VIOLATION = '23502' 202 | FOREIGN_KEY_VIOLATION = '23503' 203 | UNIQUE_VIOLATION = '23505' 204 | CHECK_VIOLATION = '23514' 205 | EXCLUSION_VIOLATION = '23P01' 206 | 207 | # Class 24 - Invalid Cursor State 208 | INVALID_CURSOR_STATE = '24000' 209 | 210 | # Class 25 - Invalid Transaction State 211 | INVALID_TRANSACTION_STATE = '25000' 212 | ACTIVE_SQL_TRANSACTION = '25001' 213 | BRANCH_TRANSACTION_ALREADY_ACTIVE = '25002' 214 | INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION = '25003' 215 | INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION = '25004' 216 | NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION = '25005' 217 | READ_ONLY_SQL_TRANSACTION = '25006' 218 | SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED = '25007' 219 | HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL = '25008' 220 | NO_ACTIVE_SQL_TRANSACTION = '25P01' 221 | IN_FAILED_SQL_TRANSACTION = '25P02' 222 | 223 | # Class 26 - Invalid SQL Statement Name 224 | INVALID_SQL_STATEMENT_NAME = '26000' 225 | 226 | # Class 27 - Triggered Data Change Violation 227 | TRIGGERED_DATA_CHANGE_VIOLATION = '27000' 228 | 229 | # Class 28 - Invalid Authorization Specification 230 | INVALID_AUTHORIZATION_SPECIFICATION = '28000' 231 | INVALID_PASSWORD = '28P01' 232 | 233 | # Class 2B - Dependent Privilege Descriptors Still Exist 234 | DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST = '2B000' 235 | DEPENDENT_OBJECTS_STILL_EXIST = '2BP01' 236 | 237 | # Class 2D - Invalid Transaction Termination 238 | INVALID_TRANSACTION_TERMINATION = '2D000' 239 | 240 | # Class 2F - SQL Routine Exception 241 | SQL_ROUTINE_EXCEPTION = '2F000' 242 | MODIFYING_SQL_DATA_NOT_PERMITTED = '2F002' 243 | PROHIBITED_SQL_STATEMENT_ATTEMPTED = '2F003' 244 | READING_SQL_DATA_NOT_PERMITTED = '2F004' 245 | FUNCTION_EXECUTED_NO_RETURN_STATEMENT = '2F005' 246 | 247 | # Class 34 - Invalid Cursor Name 248 | INVALID_CURSOR_NAME = '34000' 249 | 250 | # Class 38 - External Routine Exception 251 | EXTERNAL_ROUTINE_EXCEPTION = '38000' 252 | CONTAINING_SQL_NOT_PERMITTED = '38001' 253 | MODIFYING_SQL_DATA_NOT_PERMITTED = '38002' 254 | PROHIBITED_SQL_STATEMENT_ATTEMPTED = '38003' 255 | READING_SQL_DATA_NOT_PERMITTED = '38004' 256 | 257 | # Class 39 - External Routine Invocation Exception 258 | EXTERNAL_ROUTINE_INVOCATION_EXCEPTION = '39000' 259 | INVALID_SQLSTATE_RETURNED = '39001' 260 | NULL_VALUE_NOT_ALLOWED = '39004' 261 | TRIGGER_PROTOCOL_VIOLATED = '39P01' 262 | SRF_PROTOCOL_VIOLATED = '39P02' 263 | 264 | # Class 3B - Savepoint Exception 265 | SAVEPOINT_EXCEPTION = '3B000' 266 | INVALID_SAVEPOINT_SPECIFICATION = '3B001' 267 | 268 | # Class 3D - Invalid Catalog Name 269 | INVALID_CATALOG_NAME = '3D000' 270 | 271 | # Class 3F - Invalid Schema Name 272 | INVALID_SCHEMA_NAME = '3F000' 273 | 274 | # Class 40 - Transaction Rollback 275 | TRANSACTION_ROLLBACK = '40000' 276 | SERIALIZATION_FAILURE = '40001' 277 | TRANSACTION_INTEGRITY_CONSTRAINT_VIOLATION = '40002' 278 | STATEMENT_COMPLETION_UNKNOWN = '40003' 279 | DEADLOCK_DETECTED = '40P01' 280 | 281 | # Class 42 - Syntax Error or Access Rule Violation 282 | SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION = '42000' 283 | INSUFFICIENT_PRIVILEGE = '42501' 284 | SYNTAX_ERROR = '42601' 285 | INVALID_NAME = '42602' 286 | INVALID_COLUMN_DEFINITION = '42611' 287 | NAME_TOO_LONG = '42622' 288 | DUPLICATE_COLUMN = '42701' 289 | AMBIGUOUS_COLUMN = '42702' 290 | UNDEFINED_COLUMN = '42703' 291 | UNDEFINED_OBJECT = '42704' 292 | DUPLICATE_OBJECT = '42710' 293 | DUPLICATE_ALIAS = '42712' 294 | DUPLICATE_FUNCTION = '42723' 295 | AMBIGUOUS_FUNCTION = '42725' 296 | GROUPING_ERROR = '42803' 297 | DATATYPE_MISMATCH = '42804' 298 | WRONG_OBJECT_TYPE = '42809' 299 | INVALID_FOREIGN_KEY = '42830' 300 | CANNOT_COERCE = '42846' 301 | UNDEFINED_FUNCTION = '42883' 302 | RESERVED_NAME = '42939' 303 | UNDEFINED_TABLE = '42P01' 304 | UNDEFINED_PARAMETER = '42P02' 305 | DUPLICATE_CURSOR = '42P03' 306 | DUPLICATE_DATABASE = '42P04' 307 | DUPLICATE_PREPARED_STATEMENT = '42P05' 308 | DUPLICATE_SCHEMA = '42P06' 309 | DUPLICATE_TABLE = '42P07' 310 | AMBIGUOUS_PARAMETER = '42P08' 311 | AMBIGUOUS_ALIAS = '42P09' 312 | INVALID_COLUMN_REFERENCE = '42P10' 313 | INVALID_CURSOR_DEFINITION = '42P11' 314 | INVALID_DATABASE_DEFINITION = '42P12' 315 | INVALID_FUNCTION_DEFINITION = '42P13' 316 | INVALID_PREPARED_STATEMENT_DEFINITION = '42P14' 317 | INVALID_SCHEMA_DEFINITION = '42P15' 318 | INVALID_TABLE_DEFINITION = '42P16' 319 | INVALID_OBJECT_DEFINITION = '42P17' 320 | INDETERMINATE_DATATYPE = '42P18' 321 | INVALID_RECURSION = '42P19' 322 | WINDOWING_ERROR = '42P20' 323 | COLLATION_MISMATCH = '42P21' 324 | INDETERMINATE_COLLATION = '42P22' 325 | 326 | # Class 44 - WITH CHECK OPTION Violation 327 | WITH_CHECK_OPTION_VIOLATION = '44000' 328 | 329 | # Class 53 - Insufficient Resources 330 | INSUFFICIENT_RESOURCES = '53000' 331 | DISK_FULL = '53100' 332 | OUT_OF_MEMORY = '53200' 333 | TOO_MANY_CONNECTIONS = '53300' 334 | 335 | # Class 54 - Program Limit Exceeded 336 | PROGRAM_LIMIT_EXCEEDED = '54000' 337 | STATEMENT_TOO_COMPLEX = '54001' 338 | TOO_MANY_COLUMNS = '54011' 339 | TOO_MANY_ARGUMENTS = '54023' 340 | 341 | # Class 55 - Object Not In Prerequisite State 342 | OBJECT_NOT_IN_PREREQUISITE_STATE = '55000' 343 | OBJECT_IN_USE = '55006' 344 | CANT_CHANGE_RUNTIME_PARAM = '55P02' 345 | LOCK_NOT_AVAILABLE = '55P03' 346 | 347 | # Class 57 - Operator Intervention 348 | OPERATOR_INTERVENTION = '57000' 349 | QUERY_CANCELED = '57014' 350 | ADMIN_SHUTDOWN = '57P01' 351 | CRASH_SHUTDOWN = '57P02' 352 | CANNOT_CONNECT_NOW = '57P03' 353 | DATABASE_DROPPED = '57P04' 354 | 355 | # Class 58 - System Error (errors external to PostgreSQL itself) 356 | IO_ERROR = '58030' 357 | UNDEFINED_FILE = '58P01' 358 | DUPLICATE_FILE = '58P02' 359 | 360 | # Class F0 - Configuration File Error 361 | CONFIG_FILE_ERROR = 'F0000' 362 | LOCK_FILE_EXISTS = 'F0001' 363 | 364 | # Class HV - Foreign Data Wrapper Error (SQL/MED) 365 | FDW_ERROR = 'HV000' 366 | FDW_OUT_OF_MEMORY = 'HV001' 367 | FDW_DYNAMIC_PARAMETER_VALUE_NEEDED = 'HV002' 368 | FDW_INVALID_DATA_TYPE = 'HV004' 369 | FDW_COLUMN_NAME_NOT_FOUND = 'HV005' 370 | FDW_INVALID_DATA_TYPE_DESCRIPTORS = 'HV006' 371 | FDW_INVALID_COLUMN_NAME = 'HV007' 372 | FDW_INVALID_COLUMN_NUMBER = 'HV008' 373 | FDW_INVALID_USE_OF_NULL_POINTER = 'HV009' 374 | FDW_INVALID_STRING_FORMAT = 'HV00A' 375 | FDW_INVALID_HANDLE = 'HV00B' 376 | FDW_INVALID_OPTION_INDEX = 'HV00C' 377 | FDW_INVALID_OPTION_NAME = 'HV00D' 378 | FDW_OPTION_NAME_NOT_FOUND = 'HV00J' 379 | FDW_REPLY_HANDLE = 'HV00K' 380 | FDW_UNABLE_TO_CREATE_EXECUTION = 'HV00L' 381 | FDW_UNABLE_TO_CREATE_REPLY = 'HV00M' 382 | FDW_UNABLE_TO_ESTABLISH_CONNECTION = 'HV00N' 383 | FDW_NO_SCHEMAS = 'HV00P' 384 | FDW_SCHEMA_NOT_FOUND = 'HV00Q' 385 | FDW_TABLE_NOT_FOUND = 'HV00R' 386 | FDW_FUNCTION_SEQUENCE_ERROR = 'HV010' 387 | FDW_TOO_MANY_HANDLES = 'HV014' 388 | FDW_INCONSISTENT_DESCRIPTOR_INFORMATION = 'HV021' 389 | FDW_INVALID_ATTRIBUTE_VALUE = 'HV024' 390 | FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH = 'HV090' 391 | FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER = 'HV091' 392 | 393 | # Class P0 - PL/pgSQL Error 394 | PLPGSQL_ERROR = 'P0000' 395 | RAISE_EXCEPTION = 'P0001' 396 | NO_DATA_FOUND = 'P0002' 397 | TOO_MANY_ROWS = 'P0003' 398 | 399 | # Class XX - Internal Error 400 | INTERNAL_ERROR = 'XX000' 401 | DATA_CORRUPTED = 'XX001' 402 | INDEX_CORRUPTED = 'XX002' 403 | -------------------------------------------------------------------------------- /psycopg2ct/extensions.py: -------------------------------------------------------------------------------- 1 | """psycopg extensions to the DBAPI-2.0 2 | 3 | This module holds all the extensions to the DBAPI-2.0 provided by psycopg. 4 | 5 | - `connection` -- the new-type inheritable connection class 6 | - `cursor` -- the new-type inheritable cursor class 7 | - `lobject` -- the new-type inheritable large object class 8 | - `adapt()` -- exposes the PEP-246_ compatible adapting mechanism used 9 | by psycopg to adapt Python types to PostgreSQL ones 10 | 11 | .. _PEP-246: http://www.python.org/peps/pep-0246.html 12 | """ 13 | import sys as _sys 14 | 15 | from psycopg2ct._impl import connection as _connection 16 | from psycopg2ct._impl.adapters import adapt, adapters 17 | from psycopg2ct._impl.adapters import Binary, Boolean, Int, Float 18 | from psycopg2ct._impl.adapters import QuotedString, AsIs, ISQLQuote 19 | from psycopg2ct._impl.connection import Connection as connection 20 | from psycopg2ct._impl.consts import * 21 | from psycopg2ct._impl.cursor import Cursor as cursor 22 | from psycopg2ct._impl.encodings import encodings 23 | from psycopg2ct._impl.exceptions import QueryCanceledError 24 | from psycopg2ct._impl.exceptions import TransactionRollbackError 25 | from psycopg2ct._impl.notify import Notify 26 | from psycopg2ct._impl.typecasts import ( 27 | UNICODE, INTEGER, LONGINTEGER, BOOLEAN, FLOAT, TIME, DATE, INTERVAL, 28 | DECIMAL, 29 | BINARYARRAY, BOOLEANARRAY, DATEARRAY, DATETIMEARRAY, DECIMALARRAY, 30 | FLOATARRAY, INTEGERARRAY, INTERVALARRAY, LONGINTEGERARRAY, ROWIDARRAY, 31 | STRINGARRAY, TIMEARRAY, UNICODEARRAY) 32 | from psycopg2ct._impl.typecasts import string_types, binary_types 33 | from psycopg2ct._impl.typecasts import new_type, new_array_type, register_type 34 | from psycopg2ct._impl.xid import Xid 35 | 36 | 37 | # Return bytes from a string 38 | if _sys.version_info[0] < 3: 39 | def b(s): 40 | return s 41 | else: 42 | def b(s): 43 | return s.encode('utf8') 44 | 45 | 46 | def register_adapter(typ, callable): 47 | """Register 'callable' as an ISQLQuote adapter for type 'typ'.""" 48 | adapters[(typ, ISQLQuote)] = callable 49 | 50 | 51 | # The SQL_IN class is the official adapter for tuples starting from 2.0.6. 52 | class SQL_IN(object): 53 | """Adapt any iterable to an SQL quotable object.""" 54 | 55 | def __init__(self, seq): 56 | self._seq = seq 57 | 58 | def prepare(self, conn): 59 | self._conn = conn 60 | 61 | def getquoted(self): 62 | # this is the important line: note how every object in the 63 | # list is adapted and then how getquoted() is called on it 64 | pobjs = [adapt(o) for o in self._seq] 65 | for obj in pobjs: 66 | if hasattr(obj, 'prepare'): 67 | obj.prepare(self._conn) 68 | qobjs = [o.getquoted() for o in pobjs] 69 | return b('(') + b(', ').join(qobjs) + b(')') 70 | 71 | def __str__(self): 72 | return str(self.getquoted()) 73 | 74 | 75 | class NoneAdapter(object): 76 | """Adapt None to NULL. 77 | 78 | This adapter is not used normally as a fast path in mogrify uses NULL, 79 | but it makes easier to adapt composite types. 80 | """ 81 | def __init__(self, obj): 82 | pass 83 | 84 | def getquoted(self, _null=b("NULL")): 85 | return _null 86 | 87 | 88 | def set_wait_callback(f): 89 | _connection._green_callback = f 90 | 91 | 92 | def get_wait_callback(): 93 | return _connection._green_callback 94 | 95 | 96 | __all__ = filter(lambda k: not k.startswith('_'), locals().keys()) 97 | -------------------------------------------------------------------------------- /psycopg2ct/pool.py: -------------------------------------------------------------------------------- 1 | """Connection pooling for psycopg2 2 | 3 | This module implements thread-safe (and not) connection pools. 4 | """ 5 | # psycopg/pool.py - pooling code for psycopg 6 | # 7 | # Copyright (C) 2003-2010 Federico Di Gregorio 8 | # 9 | # psycopg2 is free software: you can redistribute it and/or modify it 10 | # under the terms of the GNU Lesser General Public License as published 11 | # by the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # In addition, as a special exception, the copyright holders give 15 | # permission to link this program with the OpenSSL library (or with 16 | # modified versions of OpenSSL that use the same license as OpenSSL), 17 | # and distribute linked combinations including the two. 18 | # 19 | # You must obey the GNU Lesser General Public License in all respects for 20 | # all of the code used other than OpenSSL. 21 | # 22 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 23 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 24 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 25 | # License for more details. 26 | 27 | import psycopg2 28 | import psycopg2.extensions as _ext 29 | 30 | try: 31 | import logging 32 | # create logger object for psycopg2 module and sub-modules 33 | _logger = logging.getLogger("psycopg2") 34 | def dbg(*args): 35 | _logger.debug("psycopg2", ' '.join([str(x) for x in args])) 36 | try: 37 | import App # does this make sure that we're running in Zope? 38 | _logger.info("installed. Logging using Python logging module") 39 | except: 40 | _logger.debug("installed. Logging using Python logging module") 41 | 42 | except ImportError: 43 | from zLOG import LOG, DEBUG, INFO 44 | def dbg(*args): 45 | LOG('ZPsycopgDA', DEBUG, "", 46 | ' '.join([str(x) for x in args])+'\n') 47 | LOG('ZPsycopgDA', INFO, "Installed", "Logging using Zope's zLOG\n") 48 | 49 | except: 50 | import sys 51 | def dbg(*args): 52 | sys.stderr.write(' '.join(args)+'\n') 53 | 54 | 55 | class PoolError(psycopg2.Error): 56 | pass 57 | 58 | 59 | class AbstractConnectionPool(object): 60 | """Generic key-based pooling code.""" 61 | 62 | def __init__(self, minconn, maxconn, *args, **kwargs): 63 | """Initialize the connection pool. 64 | 65 | New 'minconn' connections are created immediately calling 'connfunc' 66 | with given parameters. The connection pool will support a maximum of 67 | about 'maxconn' connections. 68 | """ 69 | self.minconn = minconn 70 | self.maxconn = maxconn 71 | self.closed = False 72 | 73 | self._args = args 74 | self._kwargs = kwargs 75 | 76 | self._pool = [] 77 | self._used = {} 78 | self._rused = {} # id(conn) -> key map 79 | self._keys = 0 80 | 81 | for i in range(self.minconn): 82 | self._connect() 83 | 84 | def _connect(self, key=None): 85 | """Create a new connection and assign it to 'key' if not None.""" 86 | conn = psycopg2.connect(*self._args, **self._kwargs) 87 | if key is not None: 88 | self._used[key] = conn 89 | self._rused[id(conn)] = key 90 | else: 91 | self._pool.append(conn) 92 | return conn 93 | 94 | def _getkey(self): 95 | """Return a new unique key.""" 96 | self._keys += 1 97 | return self._keys 98 | 99 | def _getconn(self, key=None): 100 | """Get a free connection and assign it to 'key' if not None.""" 101 | if self.closed: raise PoolError("connection pool is closed") 102 | if key is None: key = self._getkey() 103 | 104 | if key in self._used: 105 | return self._used[key] 106 | 107 | if self._pool: 108 | self._used[key] = conn = self._pool.pop() 109 | self._rused[id(conn)] = key 110 | return conn 111 | else: 112 | if len(self._used) == self.maxconn: 113 | raise PoolError("connection pool exausted") 114 | return self._connect(key) 115 | 116 | def _putconn(self, conn, key=None, close=False): 117 | """Put away a connection.""" 118 | if self.closed: raise PoolError("connection pool is closed") 119 | if key is None: key = self._rused.get(id(conn)) 120 | 121 | if not key: 122 | raise PoolError("trying to put unkeyed connection") 123 | 124 | if len(self._pool) < self.minconn and not close: 125 | # Return the connection into a consistent state before putting 126 | # it back into the pool 127 | if not conn.closed: 128 | status = conn.get_transaction_status() 129 | if status == _ext.TRANSACTION_STATUS_UNKNOWN: 130 | # server connection lost 131 | conn.close() 132 | elif status != _ext.TRANSACTION_STATUS_IDLE: 133 | # connection in error or in transaction 134 | conn.rollback() 135 | self._pool.append(conn) 136 | else: 137 | # regular idle connection 138 | self._pool.append(conn) 139 | # If the connection is closed, we just discard it. 140 | else: 141 | conn.close() 142 | 143 | # here we check for the presence of key because it can happen that a 144 | # thread tries to put back a connection after a call to close 145 | if not self.closed or key in self._used: 146 | del self._used[key] 147 | del self._rused[id(conn)] 148 | 149 | def _closeall(self): 150 | """Close all connections. 151 | 152 | Note that this can lead to some code fail badly when trying to use 153 | an already closed connection. If you call .closeall() make sure 154 | your code can deal with it. 155 | """ 156 | if self.closed: raise PoolError("connection pool is closed") 157 | for conn in self._pool + list(self._used.values()): 158 | try: 159 | conn.close() 160 | except: 161 | pass 162 | self.closed = True 163 | 164 | 165 | class SimpleConnectionPool(AbstractConnectionPool): 166 | """A connection pool that can't be shared across different threads.""" 167 | 168 | getconn = AbstractConnectionPool._getconn 169 | putconn = AbstractConnectionPool._putconn 170 | closeall = AbstractConnectionPool._closeall 171 | 172 | 173 | class ThreadedConnectionPool(AbstractConnectionPool): 174 | """A connection pool that works with the threading module.""" 175 | 176 | def __init__(self, minconn, maxconn, *args, **kwargs): 177 | """Initialize the threading lock.""" 178 | import threading 179 | AbstractConnectionPool.__init__( 180 | self, minconn, maxconn, *args, **kwargs) 181 | self._lock = threading.Lock() 182 | 183 | def getconn(self, key=None): 184 | """Get a free connection and assign it to 'key' if not None.""" 185 | self._lock.acquire() 186 | try: 187 | return self._getconn(key) 188 | finally: 189 | self._lock.release() 190 | 191 | def putconn(self, conn=None, key=None, close=False): 192 | """Put away an unused connection.""" 193 | self._lock.acquire() 194 | try: 195 | self._putconn(conn, key, close) 196 | finally: 197 | self._lock.release() 198 | 199 | def closeall(self): 200 | """Close all connections (even the one currently in use.)""" 201 | self._lock.acquire() 202 | try: 203 | self._closeall() 204 | finally: 205 | self._lock.release() 206 | 207 | 208 | class PersistentConnectionPool(AbstractConnectionPool): 209 | """A pool that assigns persistent connections to different threads. 210 | 211 | Note that this connection pool generates by itself the required keys 212 | using the current thread id. This means that until a thread puts away 213 | a connection it will always get the same connection object by successive 214 | `!getconn()` calls. This also means that a thread can't use more than one 215 | single connection from the pool. 216 | """ 217 | 218 | def __init__(self, minconn, maxconn, *args, **kwargs): 219 | """Initialize the threading lock.""" 220 | import threading 221 | AbstractConnectionPool.__init__( 222 | self, minconn, maxconn, *args, **kwargs) 223 | self._lock = threading.Lock() 224 | 225 | # we we'll need the thread module, to determine thread ids, so we 226 | # import it here and copy it in an instance variable 227 | import thread 228 | self.__thread = thread 229 | 230 | def getconn(self): 231 | """Generate thread id and return a connection.""" 232 | key = self.__thread.get_ident() 233 | self._lock.acquire() 234 | try: 235 | return self._getconn(key) 236 | finally: 237 | self._lock.release() 238 | 239 | def putconn(self, conn=None, close=False): 240 | """Put away an unused connection.""" 241 | key = self.__thread.get_ident() 242 | self._lock.acquire() 243 | try: 244 | if not conn: conn = self._used[key] 245 | self._putconn(conn, key, close) 246 | finally: 247 | self._lock.release() 248 | 249 | def closeall(self): 250 | """Close all connections (even the one currently in use.)""" 251 | self._lock.acquire() 252 | try: 253 | self._closeall() 254 | finally: 255 | self._lock.release() 256 | -------------------------------------------------------------------------------- /psycopg2ct/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from psycopg2ct.tests import psycopg2_tests 3 | 4 | def suite(): 5 | suite = unittest.TestSuite() 6 | suite.addTest(psycopg2_tests.test_suite()) 7 | return suite 8 | 9 | 10 | if __name__ == '__main__': 11 | unittest.main(defaultTest='suite') 12 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # psycopg2 test suite 4 | # 5 | # Copyright (C) 2007-2011 Federico Di Gregorio 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import sys 26 | from testconfig import dsn 27 | from testutils import unittest 28 | 29 | import test_async 30 | import test_bugX000 31 | import test_bug_gc 32 | import test_cancel 33 | import test_connection 34 | import test_copy 35 | import test_cursor 36 | import test_dates 37 | import test_extras_dictcursor 38 | import test_green 39 | import test_lobject 40 | import test_module 41 | import test_notify 42 | import test_psycopg2_dbapi20 43 | import test_quote 44 | import test_transaction 45 | import test_types_basic 46 | import test_types_extras 47 | 48 | def test_suite(): 49 | # If connection to test db fails, bail out early. 50 | import psycopg2 51 | try: 52 | cnn = psycopg2.connect(dsn) 53 | except Exception, e: 54 | print "Failed connection to test db:", e.__class__.__name__, e 55 | print "Please set env vars 'PSYCOPG2_TESTDB*' to valid values." 56 | sys.exit(1) 57 | else: 58 | cnn.close() 59 | 60 | suite = unittest.TestSuite() 61 | suite.addTest(test_async.test_suite()) 62 | suite.addTest(test_bugX000.test_suite()) 63 | suite.addTest(test_bug_gc.test_suite()) 64 | suite.addTest(test_cancel.test_suite()) 65 | suite.addTest(test_connection.test_suite()) 66 | suite.addTest(test_copy.test_suite()) 67 | suite.addTest(test_cursor.test_suite()) 68 | suite.addTest(test_dates.test_suite()) 69 | suite.addTest(test_extras_dictcursor.test_suite()) 70 | suite.addTest(test_green.test_suite()) 71 | suite.addTest(test_lobject.test_suite()) 72 | suite.addTest(test_module.test_suite()) 73 | suite.addTest(test_notify.test_suite()) 74 | suite.addTest(test_psycopg2_dbapi20.test_suite()) 75 | suite.addTest(test_quote.test_suite()) 76 | suite.addTest(test_transaction.test_suite()) 77 | suite.addTest(test_types_basic.test_suite()) 78 | suite.addTest(test_types_extras.test_suite()) 79 | return suite 80 | 81 | if __name__ == '__main__': 82 | unittest.main(defaultTest='test_suite') 83 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/dbapi20_tpc.py: -------------------------------------------------------------------------------- 1 | """ Python DB API 2.0 driver Two Phase Commit compliance test suite. 2 | 3 | """ 4 | 5 | import unittest 6 | 7 | 8 | class TwoPhaseCommitTests(unittest.TestCase): 9 | 10 | driver = None 11 | 12 | def connect(self): 13 | """Make a database connection.""" 14 | raise NotImplementedError 15 | 16 | _last_id = 0 17 | _global_id_prefix = "dbapi20_tpc:" 18 | 19 | def make_xid(self, con): 20 | id = TwoPhaseCommitTests._last_id 21 | TwoPhaseCommitTests._last_id += 1 22 | return con.xid(42, "%s%d" % (self._global_id_prefix, id), "qualifier") 23 | 24 | def test_xid(self): 25 | con = self.connect() 26 | try: 27 | xid = con.xid(42, "global", "bqual") 28 | except self.driver.NotSupportedError: 29 | self.fail("Driver does not support transaction IDs.") 30 | 31 | self.assertEquals(xid[0], 42) 32 | self.assertEquals(xid[1], "global") 33 | self.assertEquals(xid[2], "bqual") 34 | 35 | # Try some extremes for the transaction ID: 36 | xid = con.xid(0, "", "") 37 | self.assertEquals(tuple(xid), (0, "", "")) 38 | xid = con.xid(0x7fffffff, "a" * 64, "b" * 64) 39 | self.assertEquals(tuple(xid), (0x7fffffff, "a" * 64, "b" * 64)) 40 | 41 | def test_tpc_begin(self): 42 | con = self.connect() 43 | try: 44 | xid = self.make_xid(con) 45 | try: 46 | con.tpc_begin(xid) 47 | except self.driver.NotSupportedError: 48 | self.fail("Driver does not support tpc_begin()") 49 | finally: 50 | con.close() 51 | 52 | def test_tpc_commit_without_prepare(self): 53 | con = self.connect() 54 | try: 55 | xid = self.make_xid(con) 56 | con.tpc_begin(xid) 57 | cursor = con.cursor() 58 | cursor.execute("SELECT 1") 59 | con.tpc_commit() 60 | finally: 61 | con.close() 62 | 63 | def test_tpc_rollback_without_prepare(self): 64 | con = self.connect() 65 | try: 66 | xid = self.make_xid(con) 67 | con.tpc_begin(xid) 68 | cursor = con.cursor() 69 | cursor.execute("SELECT 1") 70 | con.tpc_rollback() 71 | finally: 72 | con.close() 73 | 74 | def test_tpc_commit_with_prepare(self): 75 | con = self.connect() 76 | try: 77 | xid = self.make_xid(con) 78 | con.tpc_begin(xid) 79 | cursor = con.cursor() 80 | cursor.execute("SELECT 1") 81 | con.tpc_prepare() 82 | con.tpc_commit() 83 | finally: 84 | con.close() 85 | 86 | def test_tpc_rollback_with_prepare(self): 87 | con = self.connect() 88 | try: 89 | xid = self.make_xid(con) 90 | con.tpc_begin(xid) 91 | cursor = con.cursor() 92 | cursor.execute("SELECT 1") 93 | con.tpc_prepare() 94 | con.tpc_rollback() 95 | finally: 96 | con.close() 97 | 98 | def test_tpc_begin_in_transaction_fails(self): 99 | con = self.connect() 100 | try: 101 | xid = self.make_xid(con) 102 | 103 | cursor = con.cursor() 104 | cursor.execute("SELECT 1") 105 | self.assertRaises(self.driver.ProgrammingError, 106 | con.tpc_begin, xid) 107 | finally: 108 | con.close() 109 | 110 | def test_tpc_begin_in_tpc_transaction_fails(self): 111 | con = self.connect() 112 | try: 113 | xid = self.make_xid(con) 114 | 115 | cursor = con.cursor() 116 | cursor.execute("SELECT 1") 117 | self.assertRaises(self.driver.ProgrammingError, 118 | con.tpc_begin, xid) 119 | finally: 120 | con.close() 121 | 122 | def test_commit_in_tpc_fails(self): 123 | # calling commit() within a TPC transaction fails with 124 | # ProgrammingError. 125 | con = self.connect() 126 | try: 127 | xid = self.make_xid(con) 128 | con.tpc_begin(xid) 129 | 130 | self.assertRaises(self.driver.ProgrammingError, con.commit) 131 | finally: 132 | con.close() 133 | 134 | def test_rollback_in_tpc_fails(self): 135 | # calling rollback() within a TPC transaction fails with 136 | # ProgrammingError. 137 | con = self.connect() 138 | try: 139 | xid = self.make_xid(con) 140 | con.tpc_begin(xid) 141 | 142 | self.assertRaises(self.driver.ProgrammingError, con.rollback) 143 | finally: 144 | con.close() 145 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/psycopg2.py: -------------------------------------------------------------------------------- 1 | import sys, os.path 2 | 3 | sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..')) 4 | from psycopg2ct import compat 5 | 6 | compat.register() 7 | 8 | # Hack to make py.test and nose work 9 | if hasattr(sys, 'modules'): 10 | sys.modules['psycopg2ct.tests.psycopg2_tests.psycopg2'] = \ 11 | sys.modules['psycopg2'] 12 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_bugX000.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # bugX000.py - test for DateTime object allocation bug 4 | # 5 | # Copyright (C) 2007-2011 Federico Di Gregorio 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import psycopg2 26 | import time 27 | import unittest 28 | 29 | class DateTimeAllocationBugTestCase(unittest.TestCase): 30 | def test_date_time_allocation_bug(self): 31 | d1 = psycopg2.Date(2002,12,25) 32 | d2 = psycopg2.DateFromTicks(time.mktime((2002,12,25,0,0,0,0,0,0))) 33 | t1 = psycopg2.Time(13,45,30) 34 | t2 = psycopg2.TimeFromTicks(time.mktime((2001,1,1,13,45,30,0,0,0))) 35 | t1 = psycopg2.Timestamp(2002,12,25,13,45,30) 36 | t2 = psycopg2.TimestampFromTicks( 37 | time.mktime((2002,12,25,13,45,30,0,0,0))) 38 | 39 | 40 | def test_suite(): 41 | return unittest.TestLoader().loadTestsFromName(__name__) 42 | 43 | if __name__ == "__main__": 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_bug_gc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # bug_gc.py - test for refcounting/GC bug 4 | # 5 | # Copyright (C) 2010-2011 Federico Di Gregorio 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import psycopg2 26 | import psycopg2.extensions 27 | import time 28 | import unittest 29 | import gc 30 | 31 | from testconfig import dsn 32 | 33 | from testutils import skip_if_no_uuid 34 | 35 | class StolenReferenceTestCase(unittest.TestCase): 36 | def setUp(self): 37 | self.conn = psycopg2.connect(dsn) 38 | 39 | def tearDown(self): 40 | self.conn.close() 41 | 42 | @skip_if_no_uuid 43 | def test_stolen_reference_bug(self): 44 | def fish(val, cur): 45 | gc.collect() 46 | return 42 47 | UUID = psycopg2.extensions.new_type((2950,), "UUID", fish) 48 | psycopg2.extensions.register_type(UUID, self.conn) 49 | curs = self.conn.cursor() 50 | curs.execute("select 'b5219e01-19ab-4994-b71e-149225dc51e4'::uuid") 51 | curs.fetchone() 52 | 53 | def test_suite(): 54 | return unittest.TestLoader().loadTestsFromName(__name__) 55 | 56 | if __name__ == "__main__": 57 | unittest.main() 58 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_cancel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # test_cancel.py - unit test for query cancellation 5 | # 6 | # Copyright (C) 2010-2011 Jan Urbański 7 | # 8 | # psycopg2 is free software: you can redistribute it and/or modify it 9 | # under the terms of the GNU Lesser General Public License as published 10 | # by the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # In addition, as a special exception, the copyright holders give 14 | # permission to link this program with the OpenSSL library (or with 15 | # modified versions of OpenSSL that use the same license as OpenSSL), 16 | # and distribute linked combinations including the two. 17 | # 18 | # You must obey the GNU Lesser General Public License in all respects for 19 | # all of the code used other than OpenSSL. 20 | # 21 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 22 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 23 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 24 | # License for more details. 25 | 26 | import time 27 | import threading 28 | 29 | import psycopg2 30 | import psycopg2.extensions 31 | from psycopg2 import extras 32 | 33 | from testconfig import dsn 34 | from testutils import unittest, skip_before_postgres 35 | 36 | class CancelTests(unittest.TestCase): 37 | 38 | def setUp(self): 39 | self.conn = psycopg2.connect(dsn) 40 | cur = self.conn.cursor() 41 | cur.execute(''' 42 | CREATE TEMPORARY TABLE table1 ( 43 | id int PRIMARY KEY 44 | )''') 45 | self.conn.commit() 46 | 47 | def tearDown(self): 48 | self.conn.close() 49 | 50 | def test_empty_cancel(self): 51 | self.conn.cancel() 52 | 53 | @skip_before_postgres(8, 2) 54 | def test_cancel(self): 55 | errors = [] 56 | 57 | def neverending(conn): 58 | cur = conn.cursor() 59 | try: 60 | self.assertRaises(psycopg2.extensions.QueryCanceledError, 61 | cur.execute, "select pg_sleep(60)") 62 | # make sure the connection still works 63 | conn.rollback() 64 | cur.execute("select 1") 65 | self.assertEqual(cur.fetchall(), [(1, )]) 66 | except Exception, e: 67 | errors.append(e) 68 | raise 69 | 70 | def canceller(conn): 71 | cur = conn.cursor() 72 | try: 73 | conn.cancel() 74 | except Exception, e: 75 | errors.append(e) 76 | raise 77 | 78 | thread1 = threading.Thread(target=neverending, args=(self.conn, )) 79 | # wait a bit to make sure that the other thread is already in 80 | # pg_sleep -- ugly and racy, but the chances are ridiculously low 81 | thread2 = threading.Timer(0.3, canceller, args=(self.conn, )) 82 | thread1.start() 83 | thread2.start() 84 | thread1.join() 85 | thread2.join() 86 | 87 | self.assertEqual(errors, []) 88 | 89 | @skip_before_postgres(8, 2) 90 | def test_async_cancel(self): 91 | async_conn = psycopg2.connect(dsn, async=True) 92 | self.assertRaises(psycopg2.OperationalError, async_conn.cancel) 93 | extras.wait_select(async_conn) 94 | cur = async_conn.cursor() 95 | cur.execute("select pg_sleep(10000)") 96 | self.assertTrue(async_conn.isexecuting()) 97 | async_conn.cancel() 98 | self.assertRaises(psycopg2.extensions.QueryCanceledError, 99 | extras.wait_select, async_conn) 100 | cur.execute("select 1") 101 | extras.wait_select(async_conn) 102 | self.assertEqual(cur.fetchall(), [(1, )]) 103 | 104 | def test_async_connection_cancel(self): 105 | async_conn = psycopg2.connect(dsn, async=True) 106 | async_conn.close() 107 | self.assertTrue(async_conn.closed) 108 | 109 | 110 | def test_suite(): 111 | return unittest.TestLoader().loadTestsFromName(__name__) 112 | 113 | if __name__ == "__main__": 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_copy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_copy.py - unit test for COPY support 4 | # 5 | # Copyright (C) 2010-2011 Daniele Varrazzo 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import os 26 | import sys 27 | import string 28 | from testutils import unittest, decorate_all_tests, skip_if_no_iobase 29 | from cStringIO import StringIO 30 | from itertools import cycle, izip 31 | 32 | import psycopg2 33 | import psycopg2.extensions 34 | from testconfig import dsn, green 35 | 36 | def skip_if_green(f): 37 | def skip_if_green_(self): 38 | if green: 39 | return self.skipTest("copy in async mode currently not supported") 40 | else: 41 | return f(self) 42 | 43 | return skip_if_green_ 44 | 45 | 46 | if sys.version_info[0] < 3: 47 | _base = object 48 | else: 49 | from io import TextIOBase as _base 50 | 51 | class MinimalRead(_base): 52 | """A file wrapper exposing the minimal interface to copy from.""" 53 | def __init__(self, f): 54 | self.f = f 55 | 56 | def read(self, size): 57 | return self.f.read(size) 58 | 59 | def readline(self): 60 | return self.f.readline() 61 | 62 | class MinimalWrite(_base): 63 | """A file wrapper exposing the minimal interface to copy to.""" 64 | def __init__(self, f): 65 | self.f = f 66 | 67 | def write(self, data): 68 | return self.f.write(data) 69 | 70 | 71 | class CopyTests(unittest.TestCase): 72 | 73 | def setUp(self): 74 | self.conn = psycopg2.connect(dsn) 75 | self._create_temp_table() 76 | 77 | def _create_temp_table(self): 78 | curs = self.conn.cursor() 79 | curs.execute(''' 80 | CREATE TEMPORARY TABLE tcopy ( 81 | id serial PRIMARY KEY, 82 | data text 83 | )''') 84 | 85 | def tearDown(self): 86 | self.conn.close() 87 | 88 | def test_copy_from(self): 89 | curs = self.conn.cursor() 90 | try: 91 | self._copy_from(curs, nrecs=1024, srec=10*1024, copykw={}) 92 | finally: 93 | curs.close() 94 | 95 | def test_copy_from_insane_size(self): 96 | # Trying to trigger a "would block" error 97 | curs = self.conn.cursor() 98 | try: 99 | self._copy_from(curs, nrecs=10*1024, srec=10*1024, 100 | copykw={'size': 20*1024*1024}) 101 | finally: 102 | curs.close() 103 | 104 | def test_copy_from_cols(self): 105 | curs = self.conn.cursor() 106 | f = StringIO() 107 | for i in xrange(10): 108 | f.write("%s\n" % (i,)) 109 | 110 | f.seek(0) 111 | curs.copy_from(MinimalRead(f), "tcopy", columns=['id']) 112 | 113 | curs.execute("select * from tcopy order by id") 114 | self.assertEqual([(i, None) for i in range(10)], curs.fetchall()) 115 | 116 | def test_copy_from_cols_err(self): 117 | curs = self.conn.cursor() 118 | f = StringIO() 119 | for i in xrange(10): 120 | f.write("%s\n" % (i,)) 121 | 122 | f.seek(0) 123 | def cols(): 124 | raise ZeroDivisionError() 125 | yield 'id' 126 | 127 | self.assertRaises(ZeroDivisionError, 128 | curs.copy_from, MinimalRead(f), "tcopy", columns=cols()) 129 | 130 | def test_copy_to(self): 131 | curs = self.conn.cursor() 132 | try: 133 | self._copy_from(curs, nrecs=1024, srec=10*1024, copykw={}) 134 | self._copy_to(curs, srec=10*1024) 135 | finally: 136 | curs.close() 137 | 138 | @skip_if_no_iobase 139 | def test_copy_text(self): 140 | self.conn.set_client_encoding('latin1') 141 | self._create_temp_table() # the above call closed the xn 142 | 143 | if sys.version_info[0] < 3: 144 | abin = ''.join(map(chr, range(32, 127) + range(160, 256))) 145 | about = abin.decode('latin1').replace('\\', '\\\\') 146 | 147 | else: 148 | abin = bytes(range(32, 127) + range(160, 256)).decode('latin1') 149 | about = abin.replace('\\', '\\\\') 150 | 151 | curs = self.conn.cursor() 152 | curs.execute('insert into tcopy values (%s, %s)', 153 | (42, abin)) 154 | 155 | import io 156 | f = io.StringIO() 157 | curs.copy_to(f, 'tcopy', columns=('data',)) 158 | f.seek(0) 159 | self.assertEqual(f.readline().rstrip(), about) 160 | 161 | @skip_if_no_iobase 162 | def test_copy_bytes(self): 163 | self.conn.set_client_encoding('latin1') 164 | self._create_temp_table() # the above call closed the xn 165 | 166 | if sys.version_info[0] < 3: 167 | abin = ''.join(map(chr, range(32, 127) + range(160, 255))) 168 | about = abin.replace('\\', '\\\\') 169 | else: 170 | abin = bytes(range(32, 127) + range(160, 255)).decode('latin1') 171 | about = abin.replace('\\', '\\\\').encode('latin1') 172 | 173 | curs = self.conn.cursor() 174 | curs.execute('insert into tcopy values (%s, %s)', 175 | (42, abin)) 176 | 177 | import io 178 | f = io.BytesIO() 179 | curs.copy_to(f, 'tcopy', columns=('data',)) 180 | f.seek(0) 181 | self.assertEqual(f.readline().rstrip(), about) 182 | 183 | @skip_if_no_iobase 184 | def test_copy_expert_textiobase(self): 185 | self.conn.set_client_encoding('latin1') 186 | self._create_temp_table() # the above call closed the xn 187 | 188 | if sys.version_info[0] < 3: 189 | abin = ''.join(map(chr, range(32, 127) + range(160, 256))) 190 | abin = abin.decode('latin1') 191 | about = abin.replace('\\', '\\\\') 192 | 193 | else: 194 | abin = bytes(range(32, 127) + range(160, 256)).decode('latin1') 195 | about = abin.replace('\\', '\\\\') 196 | 197 | import io 198 | f = io.StringIO() 199 | f.write(about) 200 | f.seek(0) 201 | 202 | curs = self.conn.cursor() 203 | psycopg2.extensions.register_type( 204 | psycopg2.extensions.UNICODE, curs) 205 | 206 | curs.copy_expert('COPY tcopy (data) FROM STDIN', f) 207 | curs.execute("select data from tcopy;") 208 | self.assertEqual(curs.fetchone()[0], abin) 209 | 210 | f = io.StringIO() 211 | curs.copy_expert('COPY tcopy (data) TO STDOUT', f) 212 | f.seek(0) 213 | self.assertEqual(f.readline().rstrip(), about) 214 | 215 | 216 | def _copy_from(self, curs, nrecs, srec, copykw): 217 | f = StringIO() 218 | for i, c in izip(xrange(nrecs), cycle(string.ascii_letters)): 219 | l = c * srec 220 | f.write("%s\t%s\n" % (i,l)) 221 | 222 | f.seek(0) 223 | curs.copy_from(MinimalRead(f), "tcopy", **copykw) 224 | 225 | curs.execute("select count(*) from tcopy") 226 | self.assertEqual(nrecs, curs.fetchone()[0]) 227 | 228 | curs.execute("select data from tcopy where id < %s order by id", 229 | (len(string.ascii_letters),)) 230 | for i, (l,) in enumerate(curs): 231 | self.assertEqual(l, string.ascii_letters[i] * srec) 232 | 233 | def _copy_to(self, curs, srec): 234 | f = StringIO() 235 | curs.copy_to(MinimalWrite(f), "tcopy") 236 | 237 | f.seek(0) 238 | ntests = 0 239 | for line in f: 240 | n, s = line.split() 241 | if int(n) < len(string.ascii_letters): 242 | self.assertEqual(s, string.ascii_letters[int(n)] * srec) 243 | ntests += 1 244 | 245 | self.assertEqual(ntests, len(string.ascii_letters)) 246 | 247 | def test_copy_expert_file_refcount(self): 248 | class Whatever(object): 249 | pass 250 | 251 | f = Whatever() 252 | curs = self.conn.cursor() 253 | self.assertRaises(TypeError, 254 | curs.copy_expert, 'COPY tcopy (data) FROM STDIN', f) 255 | 256 | def test_copy_no_column_limit(self): 257 | cols = [ "c%050d" % i for i in range(200) ] 258 | 259 | curs = self.conn.cursor() 260 | curs.execute('CREATE TEMPORARY TABLE manycols (%s)' % ',\n'.join( 261 | [ "%s int" % c for c in cols])) 262 | curs.execute("INSERT INTO manycols DEFAULT VALUES") 263 | 264 | f = StringIO() 265 | curs.copy_to(f, "manycols", columns = cols) 266 | f.seek(0) 267 | self.assertEqual(f.read().split(), ['\\N'] * len(cols)) 268 | 269 | f.seek(0) 270 | curs.copy_from(f, "manycols", columns = cols) 271 | curs.execute("select count(*) from manycols;") 272 | self.assertEqual(curs.fetchone()[0], 2) 273 | 274 | 275 | decorate_all_tests(CopyTests, skip_if_green) 276 | 277 | 278 | def test_suite(): 279 | return unittest.TestLoader().loadTestsFromName(__name__) 280 | 281 | if __name__ == "__main__": 282 | unittest.main() 283 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_cursor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_cursor.py - unit test for cursor attributes 4 | # 5 | # Copyright (C) 2010-2011 Daniele Varrazzo 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import time 26 | import sys 27 | import psycopg2 28 | import psycopg2.extensions 29 | from psycopg2.extensions import b 30 | from testconfig import dsn 31 | from testutils import unittest, skip_before_postgres, skip_if_no_namedtuple 32 | from testutils import skipIf 33 | 34 | class CursorTests(unittest.TestCase): 35 | 36 | def setUp(self): 37 | self.conn = psycopg2.connect(dsn) 38 | 39 | def tearDown(self): 40 | self.conn.close() 41 | 42 | def test_empty_query(self): 43 | cur = self.conn.cursor() 44 | self.assertRaises(psycopg2.ProgrammingError, cur.execute, "") 45 | self.assertRaises(psycopg2.ProgrammingError, cur.execute, " ") 46 | self.assertRaises(psycopg2.ProgrammingError, cur.execute, ";") 47 | 48 | def test_executemany_propagate_exceptions(self): 49 | conn = self.conn 50 | cur = conn.cursor() 51 | cur.execute("create temp table test_exc (data int);") 52 | def buggygen(): 53 | yield 1//0 54 | self.assertRaises(ZeroDivisionError, 55 | cur.executemany, "insert into test_exc values (%s)", buggygen()) 56 | cur.close() 57 | 58 | def test_mogrify_unicode(self): 59 | conn = self.conn 60 | cur = conn.cursor() 61 | 62 | # test consistency between execute and mogrify. 63 | 64 | # unicode query containing only ascii data 65 | cur.execute(u"SELECT 'foo';") 66 | self.assertEqual('foo', cur.fetchone()[0]) 67 | self.assertEqual(b("SELECT 'foo';"), cur.mogrify(u"SELECT 'foo';")) 68 | 69 | conn.set_client_encoding('UTF8') 70 | snowman = u"\u2603" 71 | 72 | # unicode query with non-ascii data 73 | cur.execute(u"SELECT '%s';" % snowman) 74 | self.assertEqual(snowman.encode('utf8'), b(cur.fetchone()[0])) 75 | self.assertEqual(("SELECT '%s';" % snowman).encode('utf8'), 76 | cur.mogrify(u"SELECT '%s';" % snowman).replace(b("E'"), b("'"))) 77 | 78 | # unicode args 79 | cur.execute("SELECT %s;", (snowman,)) 80 | self.assertEqual(snowman.encode("utf-8"), b(cur.fetchone()[0])) 81 | self.assertEqual(("SELECT '%s';" % snowman).encode('utf8'), 82 | cur.mogrify("SELECT %s;", (snowman,)).replace(b("E'"), b("'"))) 83 | 84 | # unicode query and args 85 | cur.execute(u"SELECT %s;", (snowman,)) 86 | self.assertEqual(snowman.encode("utf-8"), b(cur.fetchone()[0])) 87 | self.assertEqual(("SELECT '%s';" % snowman).encode('utf8'), 88 | cur.mogrify(u"SELECT %s;", (snowman,)).replace(b("E'"), b("'"))) 89 | 90 | def test_mogrify_decimal_explodes(self): 91 | # issue #7: explodes on windows with python 2.5 and psycopg 2.2.2 92 | try: 93 | from decimal import Decimal 94 | except: 95 | return 96 | 97 | conn = self.conn 98 | cur = conn.cursor() 99 | self.assertEqual(b('SELECT 10.3;'), 100 | cur.mogrify("SELECT %s;", (Decimal("10.3"),))) 101 | 102 | @skipIf(not hasattr(sys, 'getrefcount'), "skipped, no sys.getrefcount()") 103 | def test_mogrify_leak_on_multiple_reference(self): 104 | # issue #81: reference leak when a parameter value is referenced 105 | # more than once from a dict. 106 | cur = self.conn.cursor() 107 | i = lambda x: x 108 | foo = i('foo') * 10 109 | import sys 110 | nref1 = sys.getrefcount(foo) 111 | cur.mogrify("select %(foo)s, %(foo)s, %(foo)s", {'foo': foo}) 112 | nref2 = sys.getrefcount(foo) 113 | self.assertEqual(nref1, nref2) 114 | 115 | def test_bad_placeholder(self): 116 | cur = self.conn.cursor() 117 | self.assertRaises(psycopg2.ProgrammingError, 118 | cur.mogrify, "select %(foo", {}) 119 | self.assertRaises(psycopg2.ProgrammingError, 120 | cur.mogrify, "select %(foo", {'foo': 1}) 121 | self.assertRaises(psycopg2.ProgrammingError, 122 | cur.mogrify, "select %(foo, %(bar)", {'foo': 1}) 123 | self.assertRaises(psycopg2.ProgrammingError, 124 | cur.mogrify, "select %(foo, %(bar)", {'foo': 1, 'bar': 2}) 125 | 126 | def test_cast(self): 127 | curs = self.conn.cursor() 128 | 129 | self.assertEqual(42, curs.cast(20, '42')) 130 | self.assertAlmostEqual(3.14, curs.cast(700, '3.14')) 131 | 132 | try: 133 | from decimal import Decimal 134 | except ImportError: 135 | self.assertAlmostEqual(123.45, curs.cast(1700, '123.45')) 136 | else: 137 | self.assertEqual(Decimal('123.45'), curs.cast(1700, '123.45')) 138 | 139 | from datetime import date 140 | self.assertEqual(date(2011,1,2), curs.cast(1082, '2011-01-02')) 141 | self.assertEqual("who am i?", curs.cast(705, 'who am i?')) # unknown 142 | 143 | def test_cast_specificity(self): 144 | curs = self.conn.cursor() 145 | self.assertEqual("foo", curs.cast(705, 'foo')) 146 | 147 | D = psycopg2.extensions.new_type((705,), "DOUBLING", lambda v, c: v * 2) 148 | psycopg2.extensions.register_type(D, self.conn) 149 | self.assertEqual("foofoo", curs.cast(705, 'foo')) 150 | 151 | T = psycopg2.extensions.new_type((705,), "TREBLING", lambda v, c: v * 3) 152 | psycopg2.extensions.register_type(T, curs) 153 | self.assertEqual("foofoofoo", curs.cast(705, 'foo')) 154 | 155 | curs2 = self.conn.cursor() 156 | self.assertEqual("foofoo", curs2.cast(705, 'foo')) 157 | 158 | def test_weakref(self): 159 | from weakref import ref 160 | curs = self.conn.cursor() 161 | w = ref(curs) 162 | del curs 163 | import gc; gc.collect() 164 | self.assert_(w() is None) 165 | 166 | def test_invalid_name(self): 167 | curs = self.conn.cursor() 168 | curs.execute("create temp table invname (data int);") 169 | for i in (10,20,30): 170 | curs.execute("insert into invname values (%s)", (i,)) 171 | curs.close() 172 | 173 | curs = self.conn.cursor(r'1-2-3 \ "test"') 174 | curs.execute("select data from invname order by data") 175 | self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)]) 176 | 177 | def test_withhold(self): 178 | self.assertRaises(psycopg2.ProgrammingError, self.conn.cursor, 179 | withhold=True) 180 | 181 | curs = self.conn.cursor() 182 | try: 183 | curs.execute("drop table withhold") 184 | except psycopg2.ProgrammingError: 185 | self.conn.rollback() 186 | curs.execute("create table withhold (data int)") 187 | for i in (10, 20, 30): 188 | curs.execute("insert into withhold values (%s)", (i,)) 189 | curs.close() 190 | 191 | curs = self.conn.cursor("W") 192 | self.assertEqual(curs.withhold, False); 193 | curs.withhold = True 194 | self.assertEqual(curs.withhold, True); 195 | curs.execute("select data from withhold order by data") 196 | self.conn.commit() 197 | self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)]) 198 | curs.close() 199 | 200 | curs = self.conn.cursor("W", withhold=True) 201 | self.assertEqual(curs.withhold, True); 202 | curs.execute("select data from withhold order by data") 203 | self.conn.commit() 204 | self.assertEqual(curs.fetchall(), [(10,), (20,), (30,)]) 205 | 206 | curs = self.conn.cursor() 207 | curs.execute("drop table withhold") 208 | self.conn.commit() 209 | 210 | @skip_before_postgres(8, 2) 211 | def test_iter_named_cursor_efficient(self): 212 | curs = self.conn.cursor('tmp') 213 | # if these records are fetched in the same roundtrip their 214 | # timestamp will not be influenced by the pause in Python world. 215 | curs.execute("""select clock_timestamp() from generate_series(1,2)""") 216 | i = iter(curs) 217 | t1 = (i.next())[0] # the brackets work around a 2to3 bug 218 | time.sleep(0.2) 219 | t2 = (i.next())[0] 220 | self.assert_((t2 - t1).microseconds * 1e-6 < 0.1, 221 | "named cursor records fetched in 2 roundtrips (delta: %s)" 222 | % (t2 - t1)) 223 | 224 | @skip_before_postgres(8, 0) 225 | def test_iter_named_cursor_default_itersize(self): 226 | curs = self.conn.cursor('tmp') 227 | curs.execute('select generate_series(1,50)') 228 | rv = [ (r[0], curs.rownumber) for r in curs ] 229 | # everything swallowed in one gulp 230 | self.assertEqual(rv, [(i,i) for i in range(1,51)]) 231 | 232 | @skip_before_postgres(8, 0) 233 | def test_iter_named_cursor_itersize(self): 234 | curs = self.conn.cursor('tmp') 235 | curs.itersize = 30 236 | curs.execute('select generate_series(1,50)') 237 | rv = [ (r[0], curs.rownumber) for r in curs ] 238 | # everything swallowed in two gulps 239 | self.assertEqual(rv, [(i,((i - 1) % 30) + 1) for i in range(1,51)]) 240 | 241 | @skip_if_no_namedtuple 242 | def test_namedtuple_description(self): 243 | curs = self.conn.cursor() 244 | curs.execute("""select 245 | 3.14::decimal(10,2) as pi, 246 | 'hello'::text as hi, 247 | '2010-02-18'::date as now; 248 | """) 249 | self.assertEqual(len(curs.description), 3) 250 | for c in curs.description: 251 | self.assertEqual(len(c), 7) # DBAPI happy 252 | for a in ('name', 'type_code', 'display_size', 'internal_size', 253 | 'precision', 'scale', 'null_ok'): 254 | self.assert_(hasattr(c, a), a) 255 | 256 | c = curs.description[0] 257 | self.assertEqual(c.name, 'pi') 258 | self.assert_(c.type_code in psycopg2.extensions.DECIMAL.values) 259 | self.assert_(c.internal_size > 0) 260 | self.assertEqual(c.precision, 10) 261 | self.assertEqual(c.scale, 2) 262 | 263 | c = curs.description[1] 264 | self.assertEqual(c.name, 'hi') 265 | self.assert_(c.type_code in psycopg2.STRING.values) 266 | self.assert_(c.internal_size < 0) 267 | self.assertEqual(c.precision, None) 268 | self.assertEqual(c.scale, None) 269 | 270 | c = curs.description[2] 271 | self.assertEqual(c.name, 'now') 272 | self.assert_(c.type_code in psycopg2.extensions.DATE.values) 273 | self.assert_(c.internal_size > 0) 274 | self.assertEqual(c.precision, None) 275 | self.assertEqual(c.scale, None) 276 | 277 | @skip_before_postgres(8, 0) 278 | def test_named_cursor_stealing(self): 279 | # you can use a named cursor to iterate on a refcursor created 280 | # somewhere else 281 | cur1 = self.conn.cursor() 282 | cur1.execute("DECLARE test CURSOR WITHOUT HOLD " 283 | " FOR SELECT generate_series(1,7)") 284 | 285 | cur2 = self.conn.cursor('test') 286 | # can call fetch without execute 287 | self.assertEqual((1,), cur2.fetchone()) 288 | self.assertEqual([(2,), (3,), (4,)], cur2.fetchmany(3)) 289 | self.assertEqual([(5,), (6,), (7,)], cur2.fetchall()) 290 | 291 | @skip_before_postgres(8, 0) 292 | def test_scroll(self): 293 | cur = self.conn.cursor() 294 | cur.execute("select generate_series(0,9)") 295 | cur.scroll(2) 296 | self.assertEqual(cur.fetchone(), (2,)) 297 | cur.scroll(2) 298 | self.assertEqual(cur.fetchone(), (5,)) 299 | cur.scroll(2, mode='relative') 300 | self.assertEqual(cur.fetchone(), (8,)) 301 | cur.scroll(-1) 302 | self.assertEqual(cur.fetchone(), (8,)) 303 | cur.scroll(-2) 304 | self.assertEqual(cur.fetchone(), (7,)) 305 | cur.scroll(2, mode='absolute') 306 | self.assertEqual(cur.fetchone(), (2,)) 307 | 308 | # on the boundary 309 | cur.scroll(0, mode='absolute') 310 | self.assertEqual(cur.fetchone(), (0,)) 311 | self.assertRaises((IndexError, psycopg2.ProgrammingError), 312 | cur.scroll, -1, mode='absolute') 313 | cur.scroll(0, mode='absolute') 314 | self.assertRaises((IndexError, psycopg2.ProgrammingError), 315 | cur.scroll, -1) 316 | 317 | cur.scroll(9, mode='absolute') 318 | self.assertEqual(cur.fetchone(), (9,)) 319 | self.assertRaises((IndexError, psycopg2.ProgrammingError), 320 | cur.scroll, 10, mode='absolute') 321 | cur.scroll(9, mode='absolute') 322 | self.assertRaises((IndexError, psycopg2.ProgrammingError), 323 | cur.scroll, 1) 324 | 325 | @skip_before_postgres(8, 0) 326 | def test_scroll_named(self): 327 | cur = self.conn.cursor() 328 | cur.execute("select generate_series(0,9)") 329 | cur.scroll(2) 330 | self.assertEqual(cur.fetchone(), (2,)) 331 | cur.scroll(2) 332 | self.assertEqual(cur.fetchone(), (5,)) 333 | cur.scroll(2, mode='relative') 334 | self.assertEqual(cur.fetchone(), (8,)) 335 | cur.scroll(9, mode='absolute') 336 | self.assertEqual(cur.fetchone(), (9,)) 337 | self.assertRaises((IndexError, psycopg2.ProgrammingError), 338 | cur.scroll, 10, mode='absolute') 339 | 340 | 341 | def test_suite(): 342 | return unittest.TestLoader().loadTestsFromName(__name__) 343 | 344 | if __name__ == "__main__": 345 | unittest.main() 346 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_extras_dictcursor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # extras_dictcursor - test if DictCursor extension class works 4 | # 5 | # Copyright (C) 2004-2010 Federico Di Gregorio 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 13 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 14 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 15 | # License for more details. 16 | 17 | import time 18 | from datetime import timedelta 19 | import psycopg2 20 | import psycopg2.extras 21 | from testutils import unittest, skip_before_postgres, skip_if_no_namedtuple 22 | from testconfig import dsn 23 | 24 | 25 | class ExtrasDictCursorTests(unittest.TestCase): 26 | """Test if DictCursor extension class works.""" 27 | 28 | def setUp(self): 29 | self.conn = psycopg2.connect(dsn) 30 | curs = self.conn.cursor() 31 | curs.execute("CREATE TEMPORARY TABLE ExtrasDictCursorTests (foo text)") 32 | curs.execute("INSERT INTO ExtrasDictCursorTests VALUES ('bar')") 33 | self.conn.commit() 34 | 35 | def tearDown(self): 36 | self.conn.close() 37 | 38 | def testDictCursorWithPlainCursorFetchOne(self): 39 | self._testWithPlainCursor(lambda curs: curs.fetchone()) 40 | 41 | def testDictCursorWithPlainCursorFetchMany(self): 42 | self._testWithPlainCursor(lambda curs: curs.fetchmany(100)[0]) 43 | 44 | def testDictCursorWithPlainCursorFetchAll(self): 45 | self._testWithPlainCursor(lambda curs: curs.fetchall()[0]) 46 | 47 | def testDictCursorWithPlainCursorIter(self): 48 | def getter(curs): 49 | for row in curs: 50 | return row 51 | self._testWithPlainCursor(getter) 52 | 53 | def testDictCursorWithPlainCursorRealFetchOne(self): 54 | self._testWithPlainCursorReal(lambda curs: curs.fetchone()) 55 | 56 | def testDictCursorWithPlainCursorRealFetchMany(self): 57 | self._testWithPlainCursorReal(lambda curs: curs.fetchmany(100)[0]) 58 | 59 | def testDictCursorWithPlainCursorRealFetchAll(self): 60 | self._testWithPlainCursorReal(lambda curs: curs.fetchall()[0]) 61 | 62 | def testDictCursorWithPlainCursorRealIter(self): 63 | def getter(curs): 64 | for row in curs: 65 | return row 66 | self._testWithPlainCursorReal(getter) 67 | 68 | 69 | def testDictCursorWithNamedCursorFetchOne(self): 70 | self._testWithNamedCursor(lambda curs: curs.fetchone()) 71 | 72 | def testDictCursorWithNamedCursorFetchMany(self): 73 | self._testWithNamedCursor(lambda curs: curs.fetchmany(100)[0]) 74 | 75 | def testDictCursorWithNamedCursorFetchAll(self): 76 | self._testWithNamedCursor(lambda curs: curs.fetchall()[0]) 77 | 78 | def testDictCursorWithNamedCursorIter(self): 79 | def getter(curs): 80 | for row in curs: 81 | return row 82 | self._testWithNamedCursor(getter) 83 | 84 | @skip_before_postgres(8, 2) 85 | def testDictCursorWithNamedCursorNotGreedy(self): 86 | curs = self.conn.cursor('tmp', cursor_factory=psycopg2.extras.DictCursor) 87 | self._testNamedCursorNotGreedy(curs) 88 | 89 | 90 | def testDictCursorRealWithNamedCursorFetchOne(self): 91 | self._testWithNamedCursorReal(lambda curs: curs.fetchone()) 92 | 93 | def testDictCursorRealWithNamedCursorFetchMany(self): 94 | self._testWithNamedCursorReal(lambda curs: curs.fetchmany(100)[0]) 95 | 96 | def testDictCursorRealWithNamedCursorFetchAll(self): 97 | self._testWithNamedCursorReal(lambda curs: curs.fetchall()[0]) 98 | 99 | def testDictCursorRealWithNamedCursorIter(self): 100 | def getter(curs): 101 | for row in curs: 102 | return row 103 | self._testWithNamedCursorReal(getter) 104 | 105 | @skip_before_postgres(8, 2) 106 | def testDictCursorRealWithNamedCursorNotGreedy(self): 107 | curs = self.conn.cursor('tmp', cursor_factory=psycopg2.extras.RealDictCursor) 108 | self._testNamedCursorNotGreedy(curs) 109 | 110 | 111 | def _testWithPlainCursor(self, getter): 112 | curs = self.conn.cursor(cursor_factory=psycopg2.extras.DictCursor) 113 | curs.execute("SELECT * FROM ExtrasDictCursorTests") 114 | row = getter(curs) 115 | self.failUnless(row['foo'] == 'bar') 116 | self.failUnless(row[0] == 'bar') 117 | return row 118 | 119 | def _testWithNamedCursor(self, getter): 120 | curs = self.conn.cursor('aname', cursor_factory=psycopg2.extras.DictCursor) 121 | curs.execute("SELECT * FROM ExtrasDictCursorTests") 122 | row = getter(curs) 123 | self.failUnless(row['foo'] == 'bar') 124 | self.failUnless(row[0] == 'bar') 125 | 126 | def _testWithPlainCursorReal(self, getter): 127 | curs = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) 128 | curs.execute("SELECT * FROM ExtrasDictCursorTests") 129 | row = getter(curs) 130 | self.failUnless(row['foo'] == 'bar') 131 | 132 | def _testWithNamedCursorReal(self, getter): 133 | curs = self.conn.cursor('aname', cursor_factory=psycopg2.extras.RealDictCursor) 134 | curs.execute("SELECT * FROM ExtrasDictCursorTests") 135 | row = getter(curs) 136 | self.failUnless(row['foo'] == 'bar') 137 | 138 | def testUpdateRow(self): 139 | row = self._testWithPlainCursor(lambda curs: curs.fetchone()) 140 | row['foo'] = 'qux' 141 | self.failUnless(row['foo'] == 'qux') 142 | self.failUnless(row[0] == 'qux') 143 | 144 | def _testNamedCursorNotGreedy(self, curs): 145 | curs.itersize = 2 146 | curs.execute("""select clock_timestamp() as ts from generate_series(1,3)""") 147 | recs = [] 148 | for t in curs: 149 | time.sleep(0.01) 150 | recs.append(t) 151 | 152 | # check that the dataset was not fetched in a single gulp 153 | self.assert_(recs[1]['ts'] - recs[0]['ts'] < timedelta(seconds=0.005)) 154 | self.assert_(recs[2]['ts'] - recs[1]['ts'] > timedelta(seconds=0.0099)) 155 | 156 | 157 | class NamedTupleCursorTest(unittest.TestCase): 158 | def setUp(self): 159 | from psycopg2.extras import NamedTupleConnection 160 | 161 | try: 162 | from collections import namedtuple 163 | except ImportError: 164 | self.conn = None 165 | return 166 | 167 | self.conn = psycopg2.connect(dsn, 168 | connection_factory=NamedTupleConnection) 169 | curs = self.conn.cursor() 170 | curs.execute("CREATE TEMPORARY TABLE nttest (i int, s text)") 171 | curs.execute("INSERT INTO nttest VALUES (1, 'foo')") 172 | curs.execute("INSERT INTO nttest VALUES (2, 'bar')") 173 | curs.execute("INSERT INTO nttest VALUES (3, 'baz')") 174 | self.conn.commit() 175 | 176 | def tearDown(self): 177 | if self.conn is not None: 178 | self.conn.close() 179 | 180 | @skip_if_no_namedtuple 181 | def test_fetchone(self): 182 | curs = self.conn.cursor() 183 | curs.execute("select * from nttest where i = 1") 184 | t = curs.fetchone() 185 | self.assertEqual(t[0], 1) 186 | self.assertEqual(t.i, 1) 187 | self.assertEqual(t[1], 'foo') 188 | self.assertEqual(t.s, 'foo') 189 | 190 | @skip_if_no_namedtuple 191 | def test_fetchmany(self): 192 | curs = self.conn.cursor() 193 | curs.execute("select * from nttest order by 1") 194 | res = curs.fetchmany(2) 195 | self.assertEqual(2, len(res)) 196 | self.assertEqual(res[0].i, 1) 197 | self.assertEqual(res[0].s, 'foo') 198 | self.assertEqual(res[1].i, 2) 199 | self.assertEqual(res[1].s, 'bar') 200 | 201 | @skip_if_no_namedtuple 202 | def test_fetchall(self): 203 | curs = self.conn.cursor() 204 | curs.execute("select * from nttest order by 1") 205 | res = curs.fetchall() 206 | self.assertEqual(3, len(res)) 207 | self.assertEqual(res[0].i, 1) 208 | self.assertEqual(res[0].s, 'foo') 209 | self.assertEqual(res[1].i, 2) 210 | self.assertEqual(res[1].s, 'bar') 211 | self.assertEqual(res[2].i, 3) 212 | self.assertEqual(res[2].s, 'baz') 213 | 214 | @skip_if_no_namedtuple 215 | def test_executemany(self): 216 | curs = self.conn.cursor() 217 | curs.executemany("delete from nttest where i = %s", 218 | [(1,), (2,)]) 219 | curs.execute("select * from nttest order by 1") 220 | res = curs.fetchall() 221 | self.assertEqual(1, len(res)) 222 | self.assertEqual(res[0].i, 3) 223 | self.assertEqual(res[0].s, 'baz') 224 | 225 | @skip_if_no_namedtuple 226 | def test_iter(self): 227 | curs = self.conn.cursor() 228 | curs.execute("select * from nttest order by 1") 229 | i = iter(curs) 230 | t = i.next() 231 | self.assertEqual(t.i, 1) 232 | self.assertEqual(t.s, 'foo') 233 | t = i.next() 234 | self.assertEqual(t.i, 2) 235 | self.assertEqual(t.s, 'bar') 236 | t = i.next() 237 | self.assertEqual(t.i, 3) 238 | self.assertEqual(t.s, 'baz') 239 | self.assertRaises(StopIteration, i.next) 240 | 241 | def test_error_message(self): 242 | try: 243 | from collections import namedtuple 244 | except ImportError: 245 | # an import error somewhere 246 | from psycopg2.extras import NamedTupleConnection 247 | try: 248 | if self.conn is not None: 249 | self.conn.close() 250 | self.conn = psycopg2.connect(dsn, 251 | connection_factory=NamedTupleConnection) 252 | curs = self.conn.cursor() 253 | curs.execute("select 1") 254 | curs.fetchone() 255 | except ImportError: 256 | pass 257 | else: 258 | self.fail("expecting ImportError") 259 | else: 260 | # skip the test 261 | pass 262 | 263 | @skip_if_no_namedtuple 264 | def test_record_updated(self): 265 | curs = self.conn.cursor() 266 | curs.execute("select 1 as foo;") 267 | r = curs.fetchone() 268 | self.assertEqual(r.foo, 1) 269 | 270 | curs.execute("select 2 as bar;") 271 | r = curs.fetchone() 272 | self.assertEqual(r.bar, 2) 273 | self.assertRaises(AttributeError, getattr, r, 'foo') 274 | 275 | @skip_if_no_namedtuple 276 | def test_no_result_no_surprise(self): 277 | curs = self.conn.cursor() 278 | curs.execute("update nttest set s = s") 279 | self.assertRaises(psycopg2.ProgrammingError, curs.fetchone) 280 | 281 | curs.execute("update nttest set s = s") 282 | self.assertRaises(psycopg2.ProgrammingError, curs.fetchall) 283 | 284 | @skip_if_no_namedtuple 285 | def test_minimal_generation(self): 286 | # Instrument the class to verify it gets called the minimum number of times. 287 | from psycopg2.extras import NamedTupleCursor 288 | f_orig = NamedTupleCursor._make_nt 289 | calls = [0] 290 | def f_patched(self_): 291 | calls[0] += 1 292 | return f_orig(self_) 293 | 294 | NamedTupleCursor._make_nt = f_patched 295 | 296 | try: 297 | curs = self.conn.cursor() 298 | curs.execute("select * from nttest order by 1") 299 | curs.fetchone() 300 | curs.fetchone() 301 | curs.fetchone() 302 | self.assertEqual(1, calls[0]) 303 | 304 | curs.execute("select * from nttest order by 1") 305 | curs.fetchone() 306 | curs.fetchall() 307 | self.assertEqual(2, calls[0]) 308 | 309 | curs.execute("select * from nttest order by 1") 310 | curs.fetchone() 311 | curs.fetchmany(1) 312 | self.assertEqual(3, calls[0]) 313 | 314 | finally: 315 | NamedTupleCursor._make_nt = f_orig 316 | 317 | @skip_if_no_namedtuple 318 | @skip_before_postgres(8, 0) 319 | def test_named(self): 320 | curs = self.conn.cursor('tmp') 321 | curs.execute("""select i from generate_series(0,9) i""") 322 | recs = [] 323 | recs.extend(curs.fetchmany(5)) 324 | recs.append(curs.fetchone()) 325 | recs.extend(curs.fetchall()) 326 | self.assertEqual(range(10), [t.i for t in recs]) 327 | 328 | @skip_if_no_namedtuple 329 | def test_named_fetchone(self): 330 | curs = self.conn.cursor('tmp') 331 | curs.execute("""select 42 as i""") 332 | t = curs.fetchone() 333 | self.assertEqual(t.i, 42) 334 | 335 | @skip_if_no_namedtuple 336 | def test_named_fetchmany(self): 337 | curs = self.conn.cursor('tmp') 338 | curs.execute("""select 42 as i""") 339 | recs = curs.fetchmany(10) 340 | self.assertEqual(recs[0].i, 42) 341 | 342 | @skip_if_no_namedtuple 343 | def test_named_fetchall(self): 344 | curs = self.conn.cursor('tmp') 345 | curs.execute("""select 42 as i""") 346 | recs = curs.fetchall() 347 | self.assertEqual(recs[0].i, 42) 348 | 349 | @skip_if_no_namedtuple 350 | @skip_before_postgres(8, 2) 351 | def test_not_greedy(self): 352 | curs = self.conn.cursor('tmp') 353 | curs.itersize = 2 354 | curs.execute("""select clock_timestamp() as ts from generate_series(1,3)""") 355 | recs = [] 356 | for t in curs: 357 | time.sleep(0.01) 358 | recs.append(t) 359 | 360 | # check that the dataset was not fetched in a single gulp 361 | self.assert_(recs[1].ts - recs[0].ts < timedelta(seconds=0.005)) 362 | self.assert_(recs[2].ts - recs[1].ts > timedelta(seconds=0.0099)) 363 | 364 | 365 | def test_suite(): 366 | return unittest.TestLoader().loadTestsFromName(__name__) 367 | 368 | if __name__ == "__main__": 369 | unittest.main() 370 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_green.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_green.py - unit test for async wait callback 4 | # 5 | # Copyright (C) 2010-2011 Daniele Varrazzo 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import unittest 26 | import psycopg2 27 | import psycopg2.extensions 28 | import psycopg2.extras 29 | from testconfig import dsn 30 | 31 | class ConnectionStub(object): 32 | """A `connection` wrapper allowing analysis of the `poll()` calls.""" 33 | def __init__(self, conn): 34 | self.conn = conn 35 | self.polls = [] 36 | 37 | def fileno(self): 38 | return self.conn.fileno() 39 | 40 | def poll(self): 41 | rv = self.conn.poll() 42 | self.polls.append(rv) 43 | return rv 44 | 45 | class GreenTests(unittest.TestCase): 46 | def setUp(self): 47 | self._cb = psycopg2.extensions.get_wait_callback() 48 | psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select) 49 | self.conn = psycopg2.connect(dsn) 50 | 51 | def tearDown(self): 52 | self.conn.close() 53 | psycopg2.extensions.set_wait_callback(self._cb) 54 | 55 | def set_stub_wait_callback(self, conn): 56 | stub = ConnectionStub(conn) 57 | psycopg2.extensions.set_wait_callback( 58 | lambda conn: psycopg2.extras.wait_select(stub)) 59 | return stub 60 | 61 | def test_flush_on_write(self): 62 | # a very large query requires a flush loop to be sent to the backend 63 | conn = self.conn 64 | stub = self.set_stub_wait_callback(conn) 65 | curs = conn.cursor() 66 | for mb in 1, 5, 10, 20, 50: 67 | size = mb * 1024 * 1024 68 | del stub.polls[:] 69 | curs.execute("select %s;", ('x' * size,)) 70 | self.assertEqual(size, len(curs.fetchone()[0])) 71 | if stub.polls.count(psycopg2.extensions.POLL_WRITE) > 1: 72 | return 73 | 74 | # This is more a testing glitch than an error: it happens 75 | # on high load on linux: probably because the kernel has more 76 | # buffers ready. A warning may be useful during development, 77 | # but an error is bad during regression testing. 78 | import warnings 79 | warnings.warn("sending a large query didn't trigger block on write.") 80 | 81 | def test_error_in_callback(self): 82 | conn = self.conn 83 | curs = conn.cursor() 84 | curs.execute("select 1") # have a BEGIN 85 | curs.fetchone() 86 | 87 | # now try to do something that will fail in the callback 88 | psycopg2.extensions.set_wait_callback(lambda conn: 1//0) 89 | self.assertRaises(ZeroDivisionError, curs.execute, "select 2") 90 | 91 | # check that the connection is left in an usable state 92 | psycopg2.extensions.set_wait_callback(psycopg2.extras.wait_select) 93 | conn.rollback() 94 | curs.execute("select 2") 95 | self.assertEqual(2, curs.fetchone()[0]) 96 | 97 | 98 | def test_suite(): 99 | return unittest.TestLoader().loadTestsFromName(__name__) 100 | 101 | if __name__ == "__main__": 102 | unittest.main() 103 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_lobject.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_lobject.py - unit test for large objects support 4 | # 5 | # Copyright (C) 2008-2011 James Henstridge 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import os 26 | import shutil 27 | import tempfile 28 | from testutils import unittest, decorate_all_tests, skip_if_tpc_disabled 29 | 30 | import psycopg2 31 | import psycopg2.extensions 32 | from psycopg2.extensions import b 33 | from testconfig import dsn, green 34 | from testutils import unittest, decorate_all_tests 35 | 36 | def skip_if_no_lo(f): 37 | def skip_if_no_lo_(self): 38 | if self.conn.server_version < 80100: 39 | return self.skipTest("large objects only supported from PG 8.1") 40 | else: 41 | return f(self) 42 | 43 | return skip_if_no_lo_ 44 | 45 | def skip_if_green(f): 46 | def skip_if_green_(self): 47 | if green: 48 | return self.skipTest("libpq doesn't support LO in async mode") 49 | else: 50 | return f(self) 51 | 52 | return skip_if_green_ 53 | 54 | 55 | class LargeObjectMixin(object): 56 | # doesn't derive from TestCase to avoid repeating tests twice. 57 | def setUp(self): 58 | self.conn = self.connect() 59 | self.lo_oid = None 60 | self.tmpdir = None 61 | 62 | def tearDown(self): 63 | if self.tmpdir: 64 | shutil.rmtree(self.tmpdir, ignore_errors=True) 65 | 66 | if self.conn.closed: 67 | return 68 | 69 | if self.lo_oid is not None: 70 | self.conn.rollback() 71 | try: 72 | lo = self.conn.lobject(self.lo_oid, "n") 73 | except psycopg2.OperationalError: 74 | pass 75 | else: 76 | lo.unlink() 77 | self.conn.close() 78 | 79 | def connect(self): 80 | return psycopg2.connect(dsn) 81 | 82 | 83 | class LargeObjectTests(LargeObjectMixin, unittest.TestCase): 84 | def test_create(self): 85 | lo = self.conn.lobject() 86 | self.assertNotEqual(lo, None) 87 | self.assertEqual(lo.mode[0], "w") 88 | 89 | def test_open_non_existent(self): 90 | # By creating then removing a large object, we get an Oid that 91 | # should be unused. 92 | lo = self.conn.lobject() 93 | lo.unlink() 94 | self.assertRaises(psycopg2.OperationalError, self.conn.lobject, lo.oid) 95 | 96 | def test_open_existing(self): 97 | lo = self.conn.lobject() 98 | lo2 = self.conn.lobject(lo.oid) 99 | self.assertNotEqual(lo2, None) 100 | self.assertEqual(lo2.oid, lo.oid) 101 | self.assertEqual(lo2.mode[0], "r") 102 | 103 | def test_open_for_write(self): 104 | lo = self.conn.lobject() 105 | lo2 = self.conn.lobject(lo.oid, "w") 106 | self.assertEqual(lo2.mode[0], "w") 107 | lo2.write(b("some data")) 108 | 109 | def test_open_mode_n(self): 110 | # Openning an object in mode "n" gives us a closed lobject. 111 | lo = self.conn.lobject() 112 | lo.close() 113 | 114 | lo2 = self.conn.lobject(lo.oid, "n") 115 | self.assertEqual(lo2.oid, lo.oid) 116 | self.assertEqual(lo2.closed, True) 117 | 118 | def test_close_connection_gone(self): 119 | lo = self.conn.lobject() 120 | self.conn.close() 121 | lo.close() 122 | 123 | def test_create_with_oid(self): 124 | # Create and delete a large object to get an unused Oid. 125 | lo = self.conn.lobject() 126 | oid = lo.oid 127 | lo.unlink() 128 | 129 | lo = self.conn.lobject(0, "w", oid) 130 | self.assertEqual(lo.oid, oid) 131 | 132 | def test_create_with_existing_oid(self): 133 | lo = self.conn.lobject() 134 | lo.close() 135 | 136 | self.assertRaises(psycopg2.OperationalError, 137 | self.conn.lobject, 0, "w", lo.oid) 138 | 139 | def test_import(self): 140 | self.tmpdir = tempfile.mkdtemp() 141 | filename = os.path.join(self.tmpdir, "data.txt") 142 | fp = open(filename, "wb") 143 | fp.write(b("some data")) 144 | fp.close() 145 | 146 | lo = self.conn.lobject(0, "r", 0, filename) 147 | self.assertEqual(lo.read(), "some data") 148 | 149 | def test_close(self): 150 | lo = self.conn.lobject() 151 | self.assertEqual(lo.closed, False) 152 | lo.close() 153 | self.assertEqual(lo.closed, True) 154 | 155 | def test_write(self): 156 | lo = self.conn.lobject() 157 | self.assertEqual(lo.write(b("some data")), len("some data")) 158 | 159 | def test_write_large(self): 160 | lo = self.conn.lobject() 161 | data = "data" * 1000000 162 | self.assertEqual(lo.write(data), len(data)) 163 | 164 | def test_read(self): 165 | lo = self.conn.lobject() 166 | length = lo.write(b("some data")) 167 | lo.close() 168 | 169 | lo = self.conn.lobject(lo.oid) 170 | x = lo.read(4) 171 | self.assertEqual(type(x), type('')) 172 | self.assertEqual(x, "some") 173 | self.assertEqual(lo.read(), " data") 174 | 175 | def test_read_binary(self): 176 | lo = self.conn.lobject() 177 | length = lo.write(b("some data")) 178 | lo.close() 179 | 180 | lo = self.conn.lobject(lo.oid, "rb") 181 | x = lo.read(4) 182 | self.assertEqual(type(x), type(b(''))) 183 | self.assertEqual(x, b("some")) 184 | self.assertEqual(lo.read(), b(" data")) 185 | 186 | def test_read_text(self): 187 | lo = self.conn.lobject() 188 | snowman = u"\u2603" 189 | length = lo.write(u"some data " + snowman) 190 | lo.close() 191 | 192 | lo = self.conn.lobject(lo.oid, "rt") 193 | x = lo.read(4) 194 | self.assertEqual(type(x), type(u'')) 195 | self.assertEqual(x, u"some") 196 | self.assertEqual(lo.read(), u" data " + snowman) 197 | 198 | def test_read_large(self): 199 | lo = self.conn.lobject() 200 | data = "data" * 1000000 201 | length = lo.write("some" + data) 202 | lo.close() 203 | 204 | lo = self.conn.lobject(lo.oid) 205 | self.assertEqual(lo.read(4), "some") 206 | data1 = lo.read() 207 | # avoid dumping megacraps in the console in case of error 208 | self.assert_(data == data1, 209 | "%r... != %r..." % (data[:100], data1[:100])) 210 | 211 | def test_seek_tell(self): 212 | lo = self.conn.lobject() 213 | length = lo.write(b("some data")) 214 | self.assertEqual(lo.tell(), length) 215 | lo.close() 216 | lo = self.conn.lobject(lo.oid) 217 | 218 | self.assertEqual(lo.seek(5, 0), 5) 219 | self.assertEqual(lo.tell(), 5) 220 | self.assertEqual(lo.read(), "data") 221 | 222 | # SEEK_CUR: relative current location 223 | lo.seek(5) 224 | self.assertEqual(lo.seek(2, 1), 7) 225 | self.assertEqual(lo.tell(), 7) 226 | self.assertEqual(lo.read(), "ta") 227 | 228 | # SEEK_END: relative to end of file 229 | self.assertEqual(lo.seek(-2, 2), length - 2) 230 | self.assertEqual(lo.read(), "ta") 231 | 232 | def test_unlink(self): 233 | lo = self.conn.lobject() 234 | lo.unlink() 235 | 236 | # the object doesn't exist now, so we can't reopen it. 237 | self.assertRaises(psycopg2.OperationalError, self.conn.lobject, lo.oid) 238 | # And the object has been closed. 239 | self.assertEquals(lo.closed, True) 240 | 241 | def test_export(self): 242 | lo = self.conn.lobject() 243 | lo.write(b("some data")) 244 | 245 | self.tmpdir = tempfile.mkdtemp() 246 | filename = os.path.join(self.tmpdir, "data.txt") 247 | lo.export(filename) 248 | self.assertTrue(os.path.exists(filename)) 249 | f = open(filename, "rb") 250 | try: 251 | self.assertEqual(f.read(), b("some data")) 252 | finally: 253 | f.close() 254 | 255 | def test_close_twice(self): 256 | lo = self.conn.lobject() 257 | lo.close() 258 | lo.close() 259 | 260 | def test_write_after_close(self): 261 | lo = self.conn.lobject() 262 | lo.close() 263 | self.assertRaises(psycopg2.InterfaceError, lo.write, b("some data")) 264 | 265 | def test_read_after_close(self): 266 | lo = self.conn.lobject() 267 | lo.close() 268 | self.assertRaises(psycopg2.InterfaceError, lo.read, 5) 269 | 270 | def test_seek_after_close(self): 271 | lo = self.conn.lobject() 272 | lo.close() 273 | self.assertRaises(psycopg2.InterfaceError, lo.seek, 0) 274 | 275 | def test_tell_after_close(self): 276 | lo = self.conn.lobject() 277 | lo.close() 278 | self.assertRaises(psycopg2.InterfaceError, lo.tell) 279 | 280 | def test_unlink_after_close(self): 281 | lo = self.conn.lobject() 282 | lo.close() 283 | # Unlink works on closed files. 284 | lo.unlink() 285 | 286 | def test_export_after_close(self): 287 | lo = self.conn.lobject() 288 | lo.write(b("some data")) 289 | lo.close() 290 | 291 | self.tmpdir = tempfile.mkdtemp() 292 | filename = os.path.join(self.tmpdir, "data.txt") 293 | lo.export(filename) 294 | self.assertTrue(os.path.exists(filename)) 295 | f = open(filename, "rb") 296 | try: 297 | self.assertEqual(f.read(), b("some data")) 298 | finally: 299 | f.close() 300 | 301 | def test_close_after_commit(self): 302 | lo = self.conn.lobject() 303 | self.lo_oid = lo.oid 304 | self.conn.commit() 305 | 306 | # Closing outside of the transaction is okay. 307 | lo.close() 308 | 309 | def test_write_after_commit(self): 310 | lo = self.conn.lobject() 311 | self.lo_oid = lo.oid 312 | self.conn.commit() 313 | 314 | self.assertRaises(psycopg2.ProgrammingError, lo.write, b("some data")) 315 | 316 | def test_read_after_commit(self): 317 | lo = self.conn.lobject() 318 | self.lo_oid = lo.oid 319 | self.conn.commit() 320 | 321 | self.assertRaises(psycopg2.ProgrammingError, lo.read, 5) 322 | 323 | def test_seek_after_commit(self): 324 | lo = self.conn.lobject() 325 | self.lo_oid = lo.oid 326 | self.conn.commit() 327 | 328 | self.assertRaises(psycopg2.ProgrammingError, lo.seek, 0) 329 | 330 | def test_tell_after_commit(self): 331 | lo = self.conn.lobject() 332 | self.lo_oid = lo.oid 333 | self.conn.commit() 334 | 335 | self.assertRaises(psycopg2.ProgrammingError, lo.tell) 336 | 337 | def test_unlink_after_commit(self): 338 | lo = self.conn.lobject() 339 | self.lo_oid = lo.oid 340 | self.conn.commit() 341 | 342 | # Unlink of stale lobject is okay 343 | lo.unlink() 344 | 345 | def test_export_after_commit(self): 346 | lo = self.conn.lobject() 347 | lo.write(b("some data")) 348 | self.conn.commit() 349 | 350 | self.tmpdir = tempfile.mkdtemp() 351 | filename = os.path.join(self.tmpdir, "data.txt") 352 | lo.export(filename) 353 | self.assertTrue(os.path.exists(filename)) 354 | f = open(filename, "rb") 355 | try: 356 | self.assertEqual(f.read(), b("some data")) 357 | finally: 358 | f.close() 359 | 360 | @skip_if_tpc_disabled 361 | def test_read_after_tpc_commit(self): 362 | self.conn.tpc_begin('test_lobject') 363 | lo = self.conn.lobject() 364 | self.lo_oid = lo.oid 365 | self.conn.tpc_commit() 366 | 367 | self.assertRaises(psycopg2.ProgrammingError, lo.read, 5) 368 | 369 | @skip_if_tpc_disabled 370 | def test_read_after_tpc_prepare(self): 371 | self.conn.tpc_begin('test_lobject') 372 | lo = self.conn.lobject() 373 | self.lo_oid = lo.oid 374 | self.conn.tpc_prepare() 375 | 376 | try: 377 | self.assertRaises(psycopg2.ProgrammingError, lo.read, 5) 378 | finally: 379 | self.conn.tpc_commit() 380 | 381 | 382 | decorate_all_tests(LargeObjectTests, skip_if_no_lo) 383 | decorate_all_tests(LargeObjectTests, skip_if_green) 384 | 385 | 386 | def skip_if_no_truncate(f): 387 | def skip_if_no_truncate_(self): 388 | if self.conn.server_version < 80300: 389 | return self.skipTest( 390 | "the server doesn't support large object truncate") 391 | 392 | if not hasattr(psycopg2.extensions.lobject, 'truncate'): 393 | return self.skipTest( 394 | "psycopg2 has been built against a libpq " 395 | "without large object truncate support.") 396 | 397 | return f(self) 398 | 399 | class LargeObjectTruncateTests(LargeObjectMixin, unittest.TestCase): 400 | def test_truncate(self): 401 | lo = self.conn.lobject() 402 | lo.write(b("some data")) 403 | lo.close() 404 | 405 | lo = self.conn.lobject(lo.oid, "w") 406 | lo.truncate(4) 407 | 408 | # seek position unchanged 409 | self.assertEqual(lo.tell(), 0) 410 | # data truncated 411 | self.assertEqual(lo.read(), b("some")) 412 | 413 | lo.truncate(6) 414 | lo.seek(0) 415 | # large object extended with zeroes 416 | self.assertEqual(lo.read(), b("some\x00\x00")) 417 | 418 | lo.truncate() 419 | lo.seek(0) 420 | # large object empty 421 | self.assertEqual(lo.read(), b("")) 422 | 423 | def test_truncate_after_close(self): 424 | lo = self.conn.lobject() 425 | lo.close() 426 | self.assertRaises(psycopg2.InterfaceError, lo.truncate) 427 | 428 | def test_truncate_after_commit(self): 429 | lo = self.conn.lobject() 430 | self.lo_oid = lo.oid 431 | self.conn.commit() 432 | 433 | self.assertRaises(psycopg2.ProgrammingError, lo.truncate) 434 | 435 | decorate_all_tests(LargeObjectTruncateTests, skip_if_no_lo) 436 | decorate_all_tests(LargeObjectTruncateTests, skip_if_green) 437 | decorate_all_tests(LargeObjectTruncateTests, skip_if_no_truncate) 438 | 439 | 440 | def test_suite(): 441 | return unittest.TestLoader().loadTestsFromName(__name__) 442 | 443 | if __name__ == "__main__": 444 | unittest.main() 445 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_module.py - unit test for the module interface 4 | # 5 | # Copyright (C) 2011 Daniele Varrazzo 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | from testutils import unittest 26 | 27 | import psycopg2 28 | 29 | class ConnectTestCase(unittest.TestCase): 30 | def setUp(self): 31 | self.args = None 32 | def conect_stub(dsn, connection_factory=None, async=False): 33 | self.args = (dsn, connection_factory, async) 34 | 35 | self._connect_orig = psycopg2._connect 36 | psycopg2._connect = conect_stub 37 | 38 | def tearDown(self): 39 | psycopg2._connect = self._connect_orig 40 | 41 | def test_there_has_to_be_something(self): 42 | self.assertRaises(psycopg2.InterfaceError, psycopg2.connect) 43 | self.assertRaises(psycopg2.InterfaceError, psycopg2.connect, 44 | connection_factory=lambda dsn, async=False: None) 45 | self.assertRaises(psycopg2.InterfaceError, psycopg2.connect, 46 | async=True) 47 | 48 | def test_no_keywords(self): 49 | psycopg2.connect('') 50 | self.assertEqual(self.args[0], '') 51 | self.assertEqual(self.args[1], None) 52 | self.assertEqual(self.args[2], False) 53 | 54 | def test_dsn(self): 55 | psycopg2.connect('dbname=blah x=y') 56 | self.assertEqual(self.args[0], 'dbname=blah x=y') 57 | self.assertEqual(self.args[1], None) 58 | self.assertEqual(self.args[2], False) 59 | 60 | def test_supported_keywords(self): 61 | psycopg2.connect(database='foo') 62 | self.assertEqual(self.args[0], 'dbname=foo') 63 | psycopg2.connect(user='postgres') 64 | self.assertEqual(self.args[0], 'user=postgres') 65 | psycopg2.connect(password='secret') 66 | self.assertEqual(self.args[0], 'password=secret') 67 | psycopg2.connect(port=5432) 68 | self.assertEqual(self.args[0], 'port=5432') 69 | psycopg2.connect(sslmode='require') 70 | self.assertEqual(self.args[0], 'sslmode=require') 71 | 72 | psycopg2.connect(database='foo', 73 | user='postgres', password='secret', port=5432) 74 | self.assert_('dbname=foo' in self.args[0]) 75 | self.assert_('user=postgres' in self.args[0]) 76 | self.assert_('password=secret' in self.args[0]) 77 | self.assert_('port=5432' in self.args[0]) 78 | self.assertEqual(len(self.args[0].split()), 4) 79 | 80 | def test_generic_keywords(self): 81 | psycopg2.connect(foo='bar') 82 | self.assertEqual(self.args[0], 'foo=bar') 83 | 84 | def test_factory(self): 85 | def f(dsn, async=False): 86 | pass 87 | 88 | psycopg2.connect(database='foo', bar='baz', connection_factory=f) 89 | self.assertEqual(self.args[0], 'dbname=foo bar=baz') 90 | self.assertEqual(self.args[1], f) 91 | self.assertEqual(self.args[2], False) 92 | 93 | psycopg2.connect("dbname=foo bar=baz", connection_factory=f) 94 | self.assertEqual(self.args[0], 'dbname=foo bar=baz') 95 | self.assertEqual(self.args[1], f) 96 | self.assertEqual(self.args[2], False) 97 | 98 | def test_async(self): 99 | psycopg2.connect(database='foo', bar='baz', async=1) 100 | self.assertEqual(self.args[0], 'dbname=foo bar=baz') 101 | self.assertEqual(self.args[1], None) 102 | self.assert_(self.args[2]) 103 | 104 | psycopg2.connect("dbname=foo bar=baz", async=True) 105 | self.assertEqual(self.args[0], 'dbname=foo bar=baz') 106 | self.assertEqual(self.args[1], None) 107 | self.assert_(self.args[2]) 108 | 109 | def test_empty_param(self): 110 | psycopg2.connect(database='sony', password='') 111 | self.assertEqual(self.args[0], "dbname=sony password=''") 112 | 113 | def test_escape(self): 114 | psycopg2.connect(database='hello world') 115 | self.assertEqual(self.args[0], "dbname='hello world'") 116 | 117 | psycopg2.connect(database=r'back\slash') 118 | self.assertEqual(self.args[0], r"dbname=back\\slash") 119 | 120 | psycopg2.connect(database="quo'te") 121 | self.assertEqual(self.args[0], r"dbname=quo\'te") 122 | 123 | psycopg2.connect(database="with\ttab") 124 | self.assertEqual(self.args[0], "dbname='with\ttab'") 125 | 126 | psycopg2.connect(database=r"\every thing'") 127 | self.assertEqual(self.args[0], r"dbname='\\every thing\''") 128 | 129 | 130 | def test_suite(): 131 | return unittest.TestLoader().loadTestsFromName(__name__) 132 | 133 | if __name__ == "__main__": 134 | unittest.main() 135 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_notify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_notify.py - unit test for async notifications 4 | # 5 | # Copyright (C) 2010-2011 Daniele Varrazzo 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | from testutils import unittest 26 | 27 | import psycopg2 28 | from psycopg2 import extensions 29 | from testconfig import dsn 30 | from testutils import script_to_py3 31 | 32 | import sys 33 | import time 34 | import select 35 | import signal 36 | from subprocess import Popen, PIPE 37 | 38 | 39 | class NotifiesTests(unittest.TestCase): 40 | 41 | def setUp(self): 42 | self.conn = psycopg2.connect(dsn) 43 | 44 | def tearDown(self): 45 | self.conn.close() 46 | 47 | def autocommit(self, conn): 48 | """Set a connection in autocommit mode.""" 49 | conn.set_isolation_level(extensions.ISOLATION_LEVEL_AUTOCOMMIT) 50 | 51 | def listen(self, name): 52 | """Start listening for a name on self.conn.""" 53 | curs = self.conn.cursor() 54 | curs.execute("LISTEN " + name) 55 | curs.close() 56 | 57 | def notify(self, name, sec=0, payload=None): 58 | """Send a notification to the database, eventually after some time.""" 59 | if payload is None: 60 | payload = '' 61 | else: 62 | payload = ", %r" % payload 63 | 64 | script = ("""\ 65 | import time 66 | time.sleep(%(sec)s) 67 | import psycopg2 68 | import psycopg2.extensions 69 | conn = psycopg2.connect(%(dsn)r) 70 | conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) 71 | print conn.get_backend_pid() 72 | curs = conn.cursor() 73 | curs.execute("NOTIFY " %(name)r %(payload)r) 74 | curs.close() 75 | conn.close() 76 | """ 77 | % { 'dsn': dsn, 'sec': sec, 'name': name, 'payload': payload}) 78 | 79 | return Popen([sys.executable, '-c', script_to_py3(script)], stdout=PIPE) 80 | 81 | def test_notifies_received_on_poll(self): 82 | self.autocommit(self.conn) 83 | self.listen('foo') 84 | 85 | proc = self.notify('foo', 1) 86 | 87 | t0 = time.time() 88 | ready = select.select([self.conn], [], [], 5) 89 | t1 = time.time() 90 | self.assert_(0.99 < t1 - t0 < 4, t1 - t0) 91 | 92 | pid = int(proc.communicate()[0]) 93 | self.assertEqual(0, len(self.conn.notifies)) 94 | self.assertEqual(extensions.POLL_OK, self.conn.poll()) 95 | self.assertEqual(1, len(self.conn.notifies)) 96 | self.assertEqual(pid, self.conn.notifies[0][0]) 97 | self.assertEqual('foo', self.conn.notifies[0][1]) 98 | 99 | def test_many_notifies(self): 100 | self.autocommit(self.conn) 101 | for name in ['foo', 'bar', 'baz']: 102 | self.listen(name) 103 | 104 | pids = {} 105 | for name in ['foo', 'bar', 'baz', 'qux']: 106 | pids[name] = int(self.notify(name).communicate()[0]) 107 | 108 | self.assertEqual(0, len(self.conn.notifies)) 109 | for i in range(10): 110 | self.assertEqual(extensions.POLL_OK, self.conn.poll()) 111 | self.assertEqual(3, len(self.conn.notifies)) 112 | 113 | names = dict.fromkeys(['foo', 'bar', 'baz']) 114 | for (pid, name) in self.conn.notifies: 115 | self.assertEqual(pids[name], pid) 116 | names.pop(name) # raise if name found twice 117 | 118 | def test_notifies_received_on_execute(self): 119 | self.autocommit(self.conn) 120 | self.listen('foo') 121 | pid = int(self.notify('foo').communicate()[0]) 122 | self.assertEqual(0, len(self.conn.notifies)) 123 | self.conn.cursor().execute('select 1;') 124 | self.assertEqual(1, len(self.conn.notifies)) 125 | self.assertEqual(pid, self.conn.notifies[0][0]) 126 | self.assertEqual('foo', self.conn.notifies[0][1]) 127 | 128 | def test_notify_object(self): 129 | self.autocommit(self.conn) 130 | self.listen('foo') 131 | self.notify('foo').communicate() 132 | time.sleep(0.5) 133 | self.conn.poll() 134 | notify = self.conn.notifies[0] 135 | self.assert_(isinstance(notify, psycopg2.extensions.Notify)) 136 | 137 | def test_notify_attributes(self): 138 | self.autocommit(self.conn) 139 | self.listen('foo') 140 | pid = int(self.notify('foo').communicate()[0]) 141 | time.sleep(0.5) 142 | self.conn.poll() 143 | self.assertEqual(1, len(self.conn.notifies)) 144 | notify = self.conn.notifies[0] 145 | self.assertEqual(pid, notify.pid) 146 | self.assertEqual('foo', notify.channel) 147 | self.assertEqual('', notify.payload) 148 | 149 | def test_notify_payload(self): 150 | if self.conn.server_version < 90000: 151 | return self.skipTest("server version %s doesn't support notify payload" 152 | % self.conn.server_version) 153 | self.autocommit(self.conn) 154 | self.listen('foo') 155 | pid = int(self.notify('foo', payload="Hello, world!").communicate()[0]) 156 | time.sleep(0.5) 157 | self.conn.poll() 158 | self.assertEqual(1, len(self.conn.notifies)) 159 | notify = self.conn.notifies[0] 160 | self.assertEqual(pid, notify.pid) 161 | self.assertEqual('foo', notify.channel) 162 | self.assertEqual('Hello, world!', notify.payload) 163 | 164 | def test_notify_init(self): 165 | n = psycopg2.extensions.Notify(10, 'foo') 166 | self.assertEqual(10, n.pid) 167 | self.assertEqual('foo', n.channel) 168 | self.assertEqual('', n.payload) 169 | (pid, channel) = n 170 | self.assertEqual((pid, channel), (10, 'foo')) 171 | 172 | n = psycopg2.extensions.Notify(42, 'bar', 'baz') 173 | self.assertEqual(42, n.pid) 174 | self.assertEqual('bar', n.channel) 175 | self.assertEqual('baz', n.payload) 176 | (pid, channel) = n 177 | self.assertEqual((pid, channel), (42, 'bar')) 178 | 179 | def test_compare(self): 180 | data = [(10, 'foo'), (20, 'foo'), (10, 'foo', 'bar'), (10, 'foo', 'baz')] 181 | for d1 in data: 182 | for d2 in data: 183 | n1 = psycopg2.extensions.Notify(*d1) 184 | n2 = psycopg2.extensions.Notify(*d2) 185 | self.assertEqual((n1 == n2), (d1 == d2)) 186 | self.assertEqual((n1 != n2), (d1 != d2)) 187 | 188 | def test_compare_tuple(self): 189 | from psycopg2.extensions import Notify 190 | self.assertEqual((10, 'foo'), Notify(10, 'foo')) 191 | self.assertEqual((10, 'foo'), Notify(10, 'foo', 'bar')) 192 | self.assertNotEqual((10, 'foo'), Notify(20, 'foo')) 193 | self.assertNotEqual((10, 'foo'), Notify(10, 'bar')) 194 | 195 | def test_hash(self): 196 | from psycopg2.extensions import Notify 197 | self.assertEqual(hash((10, 'foo')), hash(Notify(10, 'foo'))) 198 | self.assertNotEqual(hash(Notify(10, 'foo', 'bar')), 199 | hash(Notify(10, 'foo'))) 200 | 201 | def test_suite(): 202 | return unittest.TestLoader().loadTestsFromName(__name__) 203 | 204 | if __name__ == "__main__": 205 | unittest.main() 206 | 207 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_psycopg2_dbapi20.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_psycopg2_dbapi20.py - DB API conformance test for psycopg2 4 | # 5 | # Copyright (C) 2006-2011 Federico Di Gregorio 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import dbapi20 26 | import dbapi20_tpc 27 | from testutils import skip_if_tpc_disabled 28 | from testutils import unittest, decorate_all_tests 29 | import psycopg2 30 | 31 | from testconfig import dsn 32 | 33 | class Psycopg2Tests(dbapi20.DatabaseAPI20Test): 34 | driver = psycopg2 35 | connect_args = () 36 | connect_kw_args = {'dsn': dsn} 37 | 38 | lower_func = 'lower' # For stored procedure test 39 | 40 | def test_setoutputsize(self): 41 | # psycopg2's setoutputsize() is a no-op 42 | pass 43 | 44 | def test_nextset(self): 45 | # psycopg2 does not implement nextset() 46 | pass 47 | 48 | 49 | class Psycopg2TPCTests(dbapi20_tpc.TwoPhaseCommitTests, unittest.TestCase): 50 | driver = psycopg2 51 | 52 | def connect(self): 53 | return psycopg2.connect(dsn=dsn) 54 | 55 | decorate_all_tests(Psycopg2TPCTests, skip_if_tpc_disabled) 56 | 57 | 58 | def test_suite(): 59 | return unittest.TestLoader().loadTestsFromName(__name__) 60 | 61 | if __name__ == '__main__': 62 | unittest.main() 63 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_quote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_quote.py - unit test for strings quoting 4 | # 5 | # Copyright (C) 2007-2011 Daniele Varrazzo 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import sys 26 | from testutils import unittest 27 | from testconfig import dsn 28 | 29 | import psycopg2 30 | import psycopg2.extensions 31 | from psycopg2.extensions import b 32 | 33 | class QuotingTestCase(unittest.TestCase): 34 | r"""Checks the correct quoting of strings and binary objects. 35 | 36 | Since ver. 8.1, PostgreSQL is moving towards SQL standard conforming 37 | strings, where the backslash (\) is treated as literal character, 38 | not as escape. To treat the backslash as a C-style escapes, PG supports 39 | the E'' quotes. 40 | 41 | This test case checks that the E'' quotes are used whenever they are 42 | needed. The tests are expected to pass with all PostgreSQL server versions 43 | (currently tested with 7.4 <= PG <= 8.3beta) and with any 44 | 'standard_conforming_strings' server parameter value. 45 | The tests also check that no warning is raised ('escape_string_warning' 46 | should be on). 47 | 48 | http://www.postgresql.org/docs/8.1/static/sql-syntax.html#SQL-SYNTAX-STRINGS 49 | http://www.postgresql.org/docs/8.1/static/runtime-config-compatible.html 50 | """ 51 | def setUp(self): 52 | self.conn = psycopg2.connect(dsn) 53 | 54 | def tearDown(self): 55 | self.conn.close() 56 | 57 | def test_string(self): 58 | data = """some data with \t chars 59 | to escape into, 'quotes' and \\ a backslash too. 60 | """ 61 | data += "".join(map(chr, range(1,127))) 62 | 63 | curs = self.conn.cursor() 64 | curs.execute("SELECT %s;", (data,)) 65 | res = curs.fetchone()[0] 66 | 67 | self.assertEqual(res, data) 68 | self.assert_(not self.conn.notices) 69 | 70 | def test_binary(self): 71 | data = b("""some data with \000\013 binary 72 | stuff into, 'quotes' and \\ a backslash too. 73 | """) 74 | if sys.version_info[0] < 3: 75 | data += "".join(map(chr, range(256))) 76 | else: 77 | data += bytes(range(256)) 78 | 79 | curs = self.conn.cursor() 80 | curs.execute("SELECT %s::bytea;", (psycopg2.Binary(data),)) 81 | if sys.version_info[0] < 3: 82 | res = str(curs.fetchone()[0]) 83 | else: 84 | res = curs.fetchone()[0].tobytes() 85 | 86 | if res[0] in (b('x'), ord(b('x'))) and self.conn.server_version >= 90000: 87 | return self.skipTest( 88 | "bytea broken with server >= 9.0, libpq < 9") 89 | 90 | self.assertEqual(res, data) 91 | self.assert_(not self.conn.notices) 92 | 93 | def test_unicode(self): 94 | curs = self.conn.cursor() 95 | curs.execute("SHOW server_encoding") 96 | server_encoding = curs.fetchone()[0] 97 | if server_encoding != "UTF8": 98 | return self.skipTest( 99 | "Unicode test skipped since server encoding is %s" 100 | % server_encoding) 101 | 102 | data = u"""some data with \t chars 103 | to escape into, 'quotes', \u20ac euro sign and \\ a backslash too. 104 | """ 105 | data += u"".join(map(unichr, [ u for u in range(1,65536) 106 | if not 0xD800 <= u <= 0xDFFF ])) # surrogate area 107 | self.conn.set_client_encoding('UNICODE') 108 | 109 | psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn) 110 | curs.execute("SELECT %s::text;", (data,)) 111 | res = curs.fetchone()[0] 112 | 113 | self.assertEqual(res, data) 114 | self.assert_(not self.conn.notices) 115 | 116 | def test_latin1(self): 117 | self.conn.set_client_encoding('LATIN1') 118 | curs = self.conn.cursor() 119 | if sys.version_info[0] < 3: 120 | data = ''.join(map(chr, range(32, 127) + range(160, 256))) 121 | else: 122 | data = bytes(range(32, 127) + range(160, 256)).decode('latin1') 123 | 124 | # as string 125 | curs.execute("SELECT %s::text;", (data,)) 126 | res = curs.fetchone()[0] 127 | self.assertEqual(res, data) 128 | self.assert_(not self.conn.notices) 129 | 130 | # as unicode 131 | if sys.version_info[0] < 3: 132 | psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn) 133 | data = data.decode('latin1') 134 | 135 | curs.execute("SELECT %s::text;", (data,)) 136 | res = curs.fetchone()[0] 137 | self.assertEqual(res, data) 138 | self.assert_(not self.conn.notices) 139 | 140 | def test_koi8(self): 141 | self.conn.set_client_encoding('KOI8') 142 | curs = self.conn.cursor() 143 | if sys.version_info[0] < 3: 144 | data = ''.join(map(chr, range(32, 127) + range(128, 256))) 145 | else: 146 | data = bytes(range(32, 127) + range(128, 256)).decode('koi8_r') 147 | 148 | # as string 149 | curs.execute("SELECT %s::text;", (data,)) 150 | res = curs.fetchone()[0] 151 | self.assertEqual(res, data) 152 | self.assert_(not self.conn.notices) 153 | 154 | # as unicode 155 | if sys.version_info[0] < 3: 156 | psycopg2.extensions.register_type(psycopg2.extensions.UNICODE, self.conn) 157 | data = data.decode('koi8_r') 158 | 159 | curs.execute("SELECT %s::text;", (data,)) 160 | res = curs.fetchone()[0] 161 | self.assertEqual(res, data) 162 | self.assert_(not self.conn.notices) 163 | 164 | 165 | def test_suite(): 166 | return unittest.TestLoader().loadTestsFromName(__name__) 167 | 168 | if __name__ == "__main__": 169 | unittest.main() 170 | 171 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/test_transaction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # test_transaction - unit test on transaction behaviour 4 | # 5 | # Copyright (C) 2007-2011 Federico Di Gregorio 6 | # 7 | # psycopg2 is free software: you can redistribute it and/or modify it 8 | # under the terms of the GNU Lesser General Public License as published 9 | # by the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # In addition, as a special exception, the copyright holders give 13 | # permission to link this program with the OpenSSL library (or with 14 | # modified versions of OpenSSL that use the same license as OpenSSL), 15 | # and distribute linked combinations including the two. 16 | # 17 | # You must obey the GNU Lesser General Public License in all respects for 18 | # all of the code used other than OpenSSL. 19 | # 20 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 21 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 22 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 23 | # License for more details. 24 | 25 | import threading 26 | from testutils import unittest, skip_before_postgres 27 | 28 | import psycopg2 29 | from psycopg2.extensions import ( 30 | ISOLATION_LEVEL_SERIALIZABLE, STATUS_BEGIN, STATUS_READY) 31 | from testconfig import dsn 32 | 33 | class TransactionTests(unittest.TestCase): 34 | 35 | def setUp(self): 36 | self.conn = psycopg2.connect(dsn) 37 | self.conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE) 38 | curs = self.conn.cursor() 39 | curs.execute(''' 40 | CREATE TEMPORARY TABLE table1 ( 41 | id int PRIMARY KEY 42 | )''') 43 | # The constraint is set to deferrable for the commit_failed test 44 | curs.execute(''' 45 | CREATE TEMPORARY TABLE table2 ( 46 | id int PRIMARY KEY, 47 | table1_id int, 48 | CONSTRAINT table2__table1_id__fk 49 | FOREIGN KEY (table1_id) REFERENCES table1(id) DEFERRABLE)''') 50 | curs.execute('INSERT INTO table1 VALUES (1)') 51 | curs.execute('INSERT INTO table2 VALUES (1, 1)') 52 | self.conn.commit() 53 | 54 | def tearDown(self): 55 | self.conn.close() 56 | 57 | def test_rollback(self): 58 | # Test that rollback undoes changes 59 | curs = self.conn.cursor() 60 | curs.execute('INSERT INTO table2 VALUES (2, 1)') 61 | # Rollback takes us from BEGIN state to READY state 62 | self.assertEqual(self.conn.status, STATUS_BEGIN) 63 | self.conn.rollback() 64 | self.assertEqual(self.conn.status, STATUS_READY) 65 | curs.execute('SELECT id, table1_id FROM table2 WHERE id = 2') 66 | self.assertEqual(curs.fetchall(), []) 67 | 68 | def test_commit(self): 69 | # Test that commit stores changes 70 | curs = self.conn.cursor() 71 | curs.execute('INSERT INTO table2 VALUES (2, 1)') 72 | # Rollback takes us from BEGIN state to READY state 73 | self.assertEqual(self.conn.status, STATUS_BEGIN) 74 | self.conn.commit() 75 | self.assertEqual(self.conn.status, STATUS_READY) 76 | # Now rollback and show that the new record is still there: 77 | self.conn.rollback() 78 | curs.execute('SELECT id, table1_id FROM table2 WHERE id = 2') 79 | self.assertEqual(curs.fetchall(), [(2, 1)]) 80 | 81 | def test_failed_commit(self): 82 | # Test that we can recover from a failed commit. 83 | # We use a deferred constraint to cause a failure on commit. 84 | curs = self.conn.cursor() 85 | curs.execute('SET CONSTRAINTS table2__table1_id__fk DEFERRED') 86 | curs.execute('INSERT INTO table2 VALUES (2, 42)') 87 | # The commit should fail, and move the cursor back to READY state 88 | self.assertEqual(self.conn.status, STATUS_BEGIN) 89 | self.assertRaises(psycopg2.IntegrityError, self.conn.commit) 90 | self.assertEqual(self.conn.status, STATUS_READY) 91 | # The connection should be ready to use for the next transaction: 92 | curs.execute('SELECT 1') 93 | self.assertEqual(curs.fetchone()[0], 1) 94 | 95 | 96 | class DeadlockSerializationTests(unittest.TestCase): 97 | """Test deadlock and serialization failure errors.""" 98 | 99 | def connect(self): 100 | conn = psycopg2.connect(dsn) 101 | conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE) 102 | return conn 103 | 104 | def setUp(self): 105 | self.conn = self.connect() 106 | curs = self.conn.cursor() 107 | # Drop table if it already exists 108 | try: 109 | curs.execute("DROP TABLE table1") 110 | self.conn.commit() 111 | except psycopg2.DatabaseError: 112 | self.conn.rollback() 113 | try: 114 | curs.execute("DROP TABLE table2") 115 | self.conn.commit() 116 | except psycopg2.DatabaseError: 117 | self.conn.rollback() 118 | # Create sample data 119 | curs.execute(""" 120 | CREATE TABLE table1 ( 121 | id int PRIMARY KEY, 122 | name text) 123 | """) 124 | curs.execute("INSERT INTO table1 VALUES (1, 'hello')") 125 | curs.execute("CREATE TABLE table2 (id int PRIMARY KEY)") 126 | self.conn.commit() 127 | 128 | def tearDown(self): 129 | curs = self.conn.cursor() 130 | curs.execute("DROP TABLE table1") 131 | curs.execute("DROP TABLE table2") 132 | self.conn.commit() 133 | self.conn.close() 134 | 135 | def test_deadlock(self): 136 | self.thread1_error = self.thread2_error = None 137 | step1 = threading.Event() 138 | step2 = threading.Event() 139 | 140 | def task1(): 141 | try: 142 | conn = self.connect() 143 | curs = conn.cursor() 144 | curs.execute("LOCK table1 IN ACCESS EXCLUSIVE MODE") 145 | step1.set() 146 | step2.wait() 147 | curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE") 148 | except psycopg2.DatabaseError, exc: 149 | self.thread1_error = exc 150 | step1.set() 151 | conn.close() 152 | def task2(): 153 | try: 154 | conn = self.connect() 155 | curs = conn.cursor() 156 | step1.wait() 157 | curs.execute("LOCK table2 IN ACCESS EXCLUSIVE MODE") 158 | step2.set() 159 | curs.execute("LOCK table1 IN ACCESS EXCLUSIVE MODE") 160 | except psycopg2.DatabaseError, exc: 161 | self.thread2_error = exc 162 | step2.set() 163 | conn.close() 164 | 165 | # Run the threads in parallel. The "step1" and "step2" events 166 | # ensure that the two transactions overlap. 167 | thread1 = threading.Thread(target=task1) 168 | thread2 = threading.Thread(target=task2) 169 | thread1.start() 170 | thread2.start() 171 | thread1.join() 172 | thread2.join() 173 | 174 | # Exactly one of the threads should have failed with 175 | # TransactionRollbackError: 176 | self.assertFalse(self.thread1_error and self.thread2_error) 177 | error = self.thread1_error or self.thread2_error 178 | self.assertTrue(isinstance( 179 | error, psycopg2.extensions.TransactionRollbackError)) 180 | 181 | def test_serialisation_failure(self): 182 | self.thread1_error = self.thread2_error = None 183 | step1 = threading.Event() 184 | step2 = threading.Event() 185 | 186 | def task1(): 187 | try: 188 | conn = self.connect() 189 | curs = conn.cursor() 190 | curs.execute("SELECT name FROM table1 WHERE id = 1") 191 | curs.fetchall() 192 | step1.set() 193 | step2.wait() 194 | curs.execute("UPDATE table1 SET name='task1' WHERE id = 1") 195 | conn.commit() 196 | except psycopg2.DatabaseError, exc: 197 | self.thread1_error = exc 198 | step1.set() 199 | conn.close() 200 | def task2(): 201 | try: 202 | conn = self.connect() 203 | curs = conn.cursor() 204 | step1.wait() 205 | curs.execute("UPDATE table1 SET name='task2' WHERE id = 1") 206 | conn.commit() 207 | except psycopg2.DatabaseError, exc: 208 | self.thread2_error = exc 209 | step2.set() 210 | conn.close() 211 | 212 | # Run the threads in parallel. The "step1" and "step2" events 213 | # ensure that the two transactions overlap. 214 | thread1 = threading.Thread(target=task1) 215 | thread2 = threading.Thread(target=task2) 216 | thread1.start() 217 | thread2.start() 218 | thread1.join() 219 | thread2.join() 220 | 221 | # Exactly one of the threads should have failed with 222 | # TransactionRollbackError: 223 | self.assertFalse(self.thread1_error and self.thread2_error) 224 | error = self.thread1_error or self.thread2_error 225 | self.assertTrue(isinstance( 226 | error, psycopg2.extensions.TransactionRollbackError)) 227 | 228 | 229 | class QueryCancellationTests(unittest.TestCase): 230 | """Tests for query cancellation.""" 231 | 232 | def setUp(self): 233 | self.conn = psycopg2.connect(dsn) 234 | self.conn.set_isolation_level(ISOLATION_LEVEL_SERIALIZABLE) 235 | 236 | def tearDown(self): 237 | self.conn.close() 238 | 239 | @skip_before_postgres(8, 2) 240 | def test_statement_timeout(self): 241 | curs = self.conn.cursor() 242 | # Set a low statement timeout, then sleep for a longer period. 243 | curs.execute('SET statement_timeout TO 10') 244 | self.assertRaises(psycopg2.extensions.QueryCanceledError, 245 | curs.execute, 'SELECT pg_sleep(50)') 246 | 247 | 248 | def test_suite(): 249 | return unittest.TestLoader().loadTestsFromName(__name__) 250 | 251 | if __name__ == "__main__": 252 | unittest.main() 253 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/testconfig.py: -------------------------------------------------------------------------------- 1 | # Configure the test suite from the env variables. 2 | 3 | import os 4 | 5 | dbname = os.environ.get('PSYCOPG2_TESTDB', 'psycopg2_test') 6 | dbhost = os.environ.get('PSYCOPG2_TESTDB_HOST', None) 7 | dbport = os.environ.get('PSYCOPG2_TESTDB_PORT', None) 8 | dbuser = os.environ.get('PSYCOPG2_TESTDB_USER', None) 9 | dbpass = os.environ.get('PSYCOPG2_TESTDB_PASSWORD', None) 10 | 11 | # Check if we want to test psycopg's green path. 12 | green = os.environ.get('PSYCOPG2_TEST_GREEN', None) 13 | if green: 14 | if green == '1': 15 | from psycopg2.extras import wait_select as wait_callback 16 | elif green == 'eventlet': 17 | from eventlet.support.psycopg2_patcher import eventlet_wait_callback \ 18 | as wait_callback 19 | else: 20 | raise ValueError("please set 'PSYCOPG2_TEST_GREEN' to a valid value") 21 | 22 | import psycopg2.extensions 23 | psycopg2.extensions.set_wait_callback(wait_callback) 24 | 25 | # Construct a DSN to connect to the test database: 26 | dsn = 'dbname=%s' % dbname 27 | if dbhost is not None: 28 | dsn += ' host=%s' % dbhost 29 | if dbport is not None: 30 | dsn += ' port=%s' % dbport 31 | if dbuser is not None: 32 | dsn += ' user=%s' % dbuser 33 | if dbpass is not None: 34 | dsn += ' password=%s' % dbpass 35 | 36 | 37 | -------------------------------------------------------------------------------- /psycopg2ct/tests/psycopg2_tests/testutils.py: -------------------------------------------------------------------------------- 1 | # testutils.py - utility module for psycopg2 testing. 2 | 3 | # 4 | # Copyright (C) 2010-2011 Daniele Varrazzo 5 | # 6 | # psycopg2 is free software: you can redistribute it and/or modify it 7 | # under the terms of the GNU Lesser General Public License as published 8 | # by the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # In addition, as a special exception, the copyright holders give 12 | # permission to link this program with the OpenSSL library (or with 13 | # modified versions of OpenSSL that use the same license as OpenSSL), 14 | # and distribute linked combinations including the two. 15 | # 16 | # You must obey the GNU Lesser General Public License in all respects for 17 | # all of the code used other than OpenSSL. 18 | # 19 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 20 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 21 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 22 | # License for more details. 23 | 24 | 25 | # Use unittest2 if available. Otherwise mock a skip facility with warnings. 26 | 27 | import os 28 | import sys 29 | 30 | try: 31 | import unittest2 32 | unittest = unittest2 33 | except ImportError: 34 | import unittest 35 | unittest2 = None 36 | 37 | if hasattr(unittest, 'skipIf'): 38 | skip = unittest.skip 39 | skipIf = unittest.skipIf 40 | 41 | else: 42 | import warnings 43 | 44 | def skipIf(cond, msg): 45 | def skipIf_(f): 46 | def skipIf__(self): 47 | if cond: 48 | warnings.warn(msg) 49 | return 50 | else: 51 | return f(self) 52 | return skipIf__ 53 | return skipIf_ 54 | 55 | def skip(msg): 56 | return skipIf(True, msg) 57 | 58 | def skipTest(self, msg): 59 | warnings.warn(msg) 60 | return 61 | 62 | unittest.TestCase.skipTest = skipTest 63 | 64 | # Silence warnings caused by the stubborness of the Python unittest maintainers 65 | # http://bugs.python.org/issue9424 66 | if not hasattr(unittest.TestCase, 'assert_') \ 67 | or unittest.TestCase.assert_ is not unittest.TestCase.assertTrue: 68 | # mavaff... 69 | unittest.TestCase.assert_ = unittest.TestCase.assertTrue 70 | unittest.TestCase.failUnless = unittest.TestCase.assertTrue 71 | unittest.TestCase.assertEquals = unittest.TestCase.assertEqual 72 | unittest.TestCase.failUnlessEqual = unittest.TestCase.assertEqual 73 | 74 | 75 | def decorate_all_tests(cls, decorator): 76 | """Apply *decorator* to all the tests defined in the TestCase *cls*.""" 77 | for n in dir(cls): 78 | if n.startswith('test'): 79 | setattr(cls, n, decorator(getattr(cls, n))) 80 | 81 | 82 | def skip_if_no_uuid(f): 83 | """Decorator to skip a test if uuid is not supported by Py/PG.""" 84 | def skip_if_no_uuid_(self): 85 | try: 86 | import uuid 87 | except ImportError: 88 | return self.skipTest("uuid not available in this Python version") 89 | 90 | try: 91 | cur = self.conn.cursor() 92 | cur.execute("select typname from pg_type where typname = 'uuid'") 93 | has = cur.fetchone() 94 | finally: 95 | self.conn.rollback() 96 | 97 | if has: 98 | return f(self) 99 | else: 100 | return self.skipTest("uuid type not available on the server") 101 | 102 | return skip_if_no_uuid_ 103 | 104 | 105 | def skip_if_tpc_disabled(f): 106 | """Skip a test if the server has tpc support disabled.""" 107 | def skip_if_tpc_disabled_(self): 108 | from psycopg2 import ProgrammingError 109 | cnn = self.connect() 110 | cur = cnn.cursor() 111 | try: 112 | cur.execute("SHOW max_prepared_transactions;") 113 | except ProgrammingError: 114 | return self.skipTest( 115 | "server too old: two phase transactions not supported.") 116 | else: 117 | mtp = int(cur.fetchone()[0]) 118 | cnn.close() 119 | 120 | if not mtp: 121 | return self.skipTest( 122 | "server not configured for two phase transactions. " 123 | "set max_prepared_transactions to > 0 to run the test") 124 | return f(self) 125 | 126 | skip_if_tpc_disabled_.__name__ = f.__name__ 127 | return skip_if_tpc_disabled_ 128 | 129 | 130 | def skip_if_no_namedtuple(f): 131 | def skip_if_no_namedtuple_(self): 132 | try: 133 | from collections import namedtuple 134 | except ImportError: 135 | return self.skipTest("collections.namedtuple not available") 136 | else: 137 | return f(self) 138 | 139 | skip_if_no_namedtuple_.__name__ = f.__name__ 140 | return skip_if_no_namedtuple_ 141 | 142 | 143 | def skip_if_no_iobase(f): 144 | """Skip a test if io.TextIOBase is not available.""" 145 | def skip_if_no_iobase_(self): 146 | try: 147 | from io import TextIOBase 148 | except ImportError: 149 | return self.skipTest("io.TextIOBase not found.") 150 | else: 151 | return f(self) 152 | 153 | return skip_if_no_iobase_ 154 | 155 | 156 | def skip_before_postgres(*ver): 157 | """Skip a test on PostgreSQL before a certain version.""" 158 | ver = ver + (0,) * (3 - len(ver)) 159 | def skip_before_postgres_(f): 160 | def skip_before_postgres__(self): 161 | if self.conn.server_version < int("%d%02d%02d" % ver): 162 | return self.skipTest("skipped because PostgreSQL %s" 163 | % self.conn.server_version) 164 | else: 165 | return f(self) 166 | 167 | return skip_before_postgres__ 168 | return skip_before_postgres_ 169 | 170 | def skip_after_postgres(*ver): 171 | """Skip a test on PostgreSQL after (including) a certain version.""" 172 | ver = ver + (0,) * (3 - len(ver)) 173 | def skip_after_postgres_(f): 174 | def skip_after_postgres__(self): 175 | if self.conn.server_version >= int("%d%02d%02d" % ver): 176 | return self.skipTest("skipped because PostgreSQL %s" 177 | % self.conn.server_version) 178 | else: 179 | return f(self) 180 | 181 | return skip_after_postgres__ 182 | return skip_after_postgres_ 183 | 184 | def skip_before_python(*ver): 185 | """Skip a test on Python before a certain version.""" 186 | def skip_before_python_(f): 187 | def skip_before_python__(self): 188 | if sys.version_info[:len(ver)] < ver: 189 | return self.skipTest("skipped because Python %s" 190 | % ".".join(map(str, sys.version_info[:len(ver)]))) 191 | else: 192 | return f(self) 193 | 194 | return skip_before_python__ 195 | return skip_before_python_ 196 | 197 | def skip_from_python(*ver): 198 | """Skip a test on Python after (including) a certain version.""" 199 | def skip_from_python_(f): 200 | def skip_from_python__(self): 201 | if sys.version_info[:len(ver)] >= ver: 202 | return self.skipTest("skipped because Python %s" 203 | % ".".join(map(str, sys.version_info[:len(ver)]))) 204 | else: 205 | return f(self) 206 | 207 | return skip_from_python__ 208 | return skip_from_python_ 209 | 210 | 211 | def script_to_py3(script): 212 | """Convert a script to Python3 syntax if required.""" 213 | if sys.version_info[0] < 3: 214 | return script 215 | 216 | import tempfile 217 | f = tempfile.NamedTemporaryFile(suffix=".py", delete=False) 218 | f.write(script.encode()) 219 | f.flush() 220 | filename = f.name 221 | f.close() 222 | 223 | # 2to3 is way too chatty 224 | import logging 225 | logging.basicConfig(filename=os.devnull) 226 | 227 | from lib2to3.main import main 228 | if main("lib2to3.fixes", ['--no-diffs', '-w', '-n', filename]): 229 | raise Exception('py3 conversion failed') 230 | 231 | f2 = open(filename) 232 | try: 233 | return f2.read() 234 | finally: 235 | f2.close() 236 | os.remove(filename) 237 | 238 | -------------------------------------------------------------------------------- /psycopg2ct/tests/test_notify.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from psycopg2ct.extensions import Notify 4 | 5 | 6 | class TestNotify(TestCase): 7 | def test_compare(self): 8 | 9 | self.assertTrue(Notify(1, 'foo') == Notify(1, 'foo')) 10 | self.assertFalse(Notify(1, 'foo') != Notify(1, 'foo')) 11 | 12 | self.assertTrue(Notify(1, 'foo') != Notify(1, 'bar')) 13 | 14 | self.assertTrue(Notify(1, 'foo') != Notify(2, 'foo')) 15 | self.assertTrue(Notify(1, 'foo') != Notify(2, 'bar')) 16 | self.assertTrue(Notify(1, 'foo') != Notify(2, 'bar')) 17 | 18 | self.assertTrue(Notify(1, 'foo') != Notify(2, 'bar')) 19 | 20 | def test_compare_payload(self): 21 | self.assertTrue(Notify(1, 'foo', 'baz') == Notify(1, 'foo', 'baz')) 22 | self.assertTrue(Notify(1, 'foo') == Notify(1, 'foo', '')) 23 | self.assertTrue(Notify(1, 'foo', 'foo') != Notify(1, 'foo', 'bar')) 24 | 25 | def test_compare_tuple(self): 26 | self.assertTrue(Notify(1, 'foo') == (1, 'foo')) 27 | self.assertTrue(Notify(1, 'foo') != (1, 'bar')) 28 | self.assertTrue(Notify(1, 'foo') != (2, 'foo')) 29 | 30 | def test_compare_tuple_payload(self): 31 | self.assertTrue(Notify(1, 'foo', 'baz') == (1, 'foo')) 32 | self.assertTrue(Notify(1, 'foo', 'baz') != (1, 'bar')) 33 | self.assertTrue(Notify(1, 'foo', 'baz') != (2, 'foo')) 34 | 35 | def test_indexing(self): 36 | n = Notify(1, 'foo', 'baz') 37 | self.assertEqual(n[0], 1) 38 | self.assertEqual(n[1], 'foo') 39 | self.assertRaises(IndexError, n.__getitem__, 2) 40 | 41 | def test_len(self): 42 | n = Notify(1, 'foo', 'baz') 43 | self.assertEqual(len(n), 2) 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /psycopg2ct/tz.py: -------------------------------------------------------------------------------- 1 | """tzinfo implementations for psycopg2 2 | 3 | This module holds two different tzinfo implementations that can be used as 4 | the 'tzinfo' argument to datetime constructors, directly passed to psycopg 5 | functions or used to set the .tzinfo_factory attribute in cursors. 6 | """ 7 | # psycopg/tz.py - tzinfo implementation 8 | # 9 | # Copyright (C) 2003-2010 Federico Di Gregorio 10 | # 11 | # psycopg2 is free software: you can redistribute it and/or modify it 12 | # under the terms of the GNU Lesser General Public License as published 13 | # by the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # In addition, as a special exception, the copyright holders give 17 | # permission to link this program with the OpenSSL library (or with 18 | # modified versions of OpenSSL that use the same license as OpenSSL), 19 | # and distribute linked combinations including the two. 20 | # 21 | # You must obey the GNU Lesser General Public License in all respects for 22 | # all of the code used other than OpenSSL. 23 | # 24 | # psycopg2 is distributed in the hope that it will be useful, but WITHOUT 25 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 26 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 27 | # License for more details. 28 | 29 | import datetime 30 | import time 31 | 32 | ZERO = datetime.timedelta(0) 33 | 34 | class FixedOffsetTimezone(datetime.tzinfo): 35 | """Fixed offset in minutes east from UTC. 36 | 37 | This is exactly the implementation__ found in Python 2.3.x documentation, 38 | with a small change to the `!__init__()` method to allow for pickling 39 | and a default name in the form ``sHH:MM`` (``s`` is the sign.). 40 | 41 | .. __: http://docs.python.org/library/datetime.html#datetime-tzinfo 42 | """ 43 | _name = None 44 | _offset = ZERO 45 | 46 | def __init__(self, offset=None, name=None): 47 | if offset is not None: 48 | self._offset = datetime.timedelta(minutes = offset) 49 | if name is not None: 50 | self._name = name 51 | 52 | def __repr__(self): 53 | return "psycopg2.tz.FixedOffsetTimezone(offset=%r, name=%r)" \ 54 | % (self._offset.seconds // 60, self._name) 55 | 56 | def utcoffset(self, dt): 57 | return self._offset 58 | 59 | def tzname(self, dt): 60 | if self._name is not None: 61 | return self._name 62 | else: 63 | seconds = self._offset.seconds + self._offset.days * 86400 64 | hours, seconds = divmod(seconds, 3600) 65 | minutes = seconds/60 66 | if minutes: 67 | return "%+03d:%d" % (hours, minutes) 68 | else: 69 | return "%+03d" % hours 70 | 71 | def dst(self, dt): 72 | return ZERO 73 | 74 | 75 | STDOFFSET = datetime.timedelta(seconds = -time.timezone) 76 | if time.daylight: 77 | DSTOFFSET = datetime.timedelta(seconds = -time.altzone) 78 | else: 79 | DSTOFFSET = STDOFFSET 80 | DSTDIFF = DSTOFFSET - STDOFFSET 81 | 82 | class LocalTimezone(datetime.tzinfo): 83 | """Platform idea of local timezone. 84 | 85 | This is the exact implementation from the Python 2.3 documentation. 86 | """ 87 | 88 | def utcoffset(self, dt): 89 | if self._isdst(dt): 90 | return DSTOFFSET 91 | else: 92 | return STDOFFSET 93 | 94 | def dst(self, dt): 95 | if self._isdst(dt): 96 | return DSTDIFF 97 | else: 98 | return ZERO 99 | 100 | def tzname(self, dt): 101 | return time.tzname[self._isdst(dt)] 102 | 103 | def _isdst(self, dt): 104 | tt = (dt.year, dt.month, dt.day, 105 | dt.hour, dt.minute, dt.second, 106 | dt.weekday(), 0, -1) 107 | stamp = time.mktime(tt) 108 | tt = time.localtime(stamp) 109 | return tt.tm_isdst > 0 110 | 111 | LOCAL = LocalTimezone() 112 | 113 | # TODO: pre-generate some interesting time zones? 114 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # This file is almost entirely taken from psycopg2 with a couple of 2 | # adjustments for ctypes 3 | 4 | import os 5 | import re 6 | import sys 7 | import subprocess 8 | import ctypes.util 9 | 10 | from setuptools import setup 11 | 12 | from distutils.command.build_py import build_py as _build_py 13 | 14 | PLATFORM_IS_WINDOWS = sys.platform.lower().startswith('win') 15 | 16 | # read the version number from the package 17 | f = open(os.path.join(os.path.dirname(__file__), 'psycopg2ct/__init__.py')) 18 | try: 19 | for line in f: 20 | if line.startswith('__version__'): 21 | PSYCOPG_VERSION = line.split('=')[1].replace('"', '').replace("'", '').strip() 22 | break 23 | else: 24 | raise ValueError('__version__ not found in psycopg2ct package') 25 | finally: 26 | f.close() 27 | 28 | version_flags = ['ctypes'] 29 | 30 | class PostgresConfig: 31 | 32 | def __init__(self, build_py): 33 | self.build_py = build_py 34 | self.pg_config_exe = self.build_py.pg_config 35 | if not self.pg_config_exe: 36 | self.pg_config_exe = self.autodetect_pg_config_path() 37 | if self.pg_config_exe is None: 38 | sys.stderr.write("""\ 39 | Error: pg_config executable not found. 40 | 41 | Please add the directory containing pg_config to the PATH 42 | or specify the full executable path with the option: 43 | 44 | python setup.py build_py --pg-config /path/to/pg_config install ... 45 | 46 | or with the pg_config option in 'setup.cfg'. 47 | """) 48 | sys.exit(1) 49 | 50 | def query(self, attr_name): 51 | """Spawn the pg_config executable, querying for the given config 52 | name, and return the printed value, sanitized. """ 53 | try: 54 | pg_config_process = subprocess.Popen( 55 | [self.pg_config_exe, "--" + attr_name], 56 | stdin=subprocess.PIPE, 57 | stdout=subprocess.PIPE, 58 | stderr=subprocess.PIPE) 59 | except OSError: 60 | raise Warning("Unable to find 'pg_config' file in '%s'" % 61 | self.pg_config_exe) 62 | pg_config_process.stdin.close() 63 | result = pg_config_process.stdout.readline().strip() 64 | if not result: 65 | raise Warning(pg_config_process.stderr.readline()) 66 | if not isinstance(result, str): 67 | result = result.decode('ascii') 68 | return result 69 | 70 | def find_on_path(self, exename, path_directories=None): 71 | if not path_directories: 72 | path_directories = os.environ['PATH'].split(os.pathsep) 73 | for dir_name in path_directories: 74 | fullpath = os.path.join(dir_name, exename) 75 | if os.path.isfile(fullpath): 76 | return fullpath 77 | return None 78 | 79 | def autodetect_pg_config_path(self): 80 | """Find and return the path to the pg_config executable.""" 81 | if PLATFORM_IS_WINDOWS: 82 | return self.autodetect_pg_config_path_windows() 83 | else: 84 | return self.find_on_path('pg_config') 85 | 86 | def autodetect_pg_config_path_windows(self): 87 | """Attempt several different ways of finding the pg_config 88 | executable on Windows, and return its full path, if found.""" 89 | 90 | # This code only runs if they have not specified a pg_config option 91 | # in the config file or via the commandline. 92 | 93 | # First, check for pg_config.exe on the PATH, and use that if found. 94 | pg_config_exe = self.find_on_path('pg_config.exe') 95 | if pg_config_exe: 96 | return pg_config_exe 97 | 98 | # Now, try looking in the Windows Registry to find a PostgreSQL 99 | # installation, and infer the path from that. 100 | pg_config_exe = self._get_pg_config_from_registry() 101 | if pg_config_exe: 102 | return pg_config_exe 103 | 104 | return None 105 | 106 | def _get_pg_config_from_registry(self): 107 | try: 108 | import winreg 109 | except ImportError: 110 | import _winreg as winreg 111 | 112 | reg = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) 113 | try: 114 | pg_inst_list_key = winreg.OpenKey(reg, 115 | 'SOFTWARE\\PostgreSQL\\Installations') 116 | except EnvironmentError: 117 | # No PostgreSQL installation, as best as we can tell. 118 | return None 119 | 120 | try: 121 | # Determine the name of the first subkey, if any: 122 | try: 123 | first_sub_key_name = winreg.EnumKey(pg_inst_list_key, 0) 124 | except EnvironmentError: 125 | return None 126 | 127 | pg_first_inst_key = winreg.OpenKey(reg, 128 | 'SOFTWARE\\PostgreSQL\\Installations\\' 129 | + first_sub_key_name) 130 | try: 131 | pg_inst_base_dir = winreg.QueryValueEx( 132 | pg_first_inst_key, 'Base Directory')[0] 133 | finally: 134 | winreg.CloseKey(pg_first_inst_key) 135 | 136 | finally: 137 | winreg.CloseKey(pg_inst_list_key) 138 | 139 | pg_config_path = os.path.join( 140 | pg_inst_base_dir, 'bin', 'pg_config.exe') 141 | if not os.path.exists(pg_config_path): 142 | return None 143 | 144 | # Support unicode paths, if this version of Python provides the 145 | # necessary infrastructure: 146 | if sys.version_info[0] < 3 \ 147 | and hasattr(sys, 'getfilesystemencoding'): 148 | pg_config_path = pg_config_path.encode( 149 | sys.getfilesystemencoding()) 150 | 151 | return pg_config_path 152 | 153 | 154 | class build_py(_build_py): 155 | 156 | user_options = _build_py.user_options[:] 157 | user_options.extend([ 158 | ('pg-config=', None, 159 | "The name of the pg_config binary and/or full path to find it"), 160 | ]) 161 | 162 | def initialize_options(self): 163 | _build_py.initialize_options(self) 164 | self.pg_config = None 165 | 166 | def finalize_options(self): 167 | _build_py.finalize_options(self) 168 | pg_config_helper = PostgresConfig(self) 169 | self.libpq_path = self.find_libpq(pg_config_helper) 170 | self.libpq_version = self.find_version(pg_config_helper) 171 | 172 | def find_version(self, helper): 173 | try: 174 | # Here we take a conservative approach: we suppose that 175 | # *at least* PostgreSQL 7.4 is available (this is the only 176 | # 7.x series supported by psycopg 2) 177 | pgversion = helper.query('version').split()[1] 178 | except: 179 | pgversion = '7.4.0' 180 | 181 | verre = re.compile( 182 | r'(\d+)\.(\d+)(?:(?:\.(\d+))|(devel|(alpha|beta|rc)\d+))') 183 | m = verre.match(pgversion) 184 | if m: 185 | pgmajor, pgminor, pgpatch = m.group(1, 2, 3) 186 | if pgpatch is None or not pgpatch.isdigit(): 187 | pgpatch = 0 188 | else: 189 | sys.stderr.write( 190 | "Error: could not determine PostgreSQL version from '%s'" 191 | % pgversion) 192 | sys.exit(1) 193 | 194 | return '0x%02X%02X%02X' % (int(pgmajor), int(pgminor), int(pgpatch)) 195 | 196 | def find_libpq(self, helper): 197 | path = helper.query('libdir') 198 | fname = None 199 | if os.name == 'posix': 200 | if sys.platform == 'darwin': 201 | fname = os.path.join(path, 'libpq.dylib') 202 | if sys.platform in ['linux2', 'linux3']: 203 | fname = os.path.join(path, 'libpq.so') 204 | 205 | if fname: 206 | print 207 | print '=' * 80 208 | print 209 | print 'Found libpq at:' 210 | print ' -> %s' % fname 211 | print 212 | print '=' * 80 213 | return fname 214 | else: 215 | fname = ctypes.util.find_library('pq') 216 | print 217 | print '=' * 80 218 | print 219 | print 'Unable to find the libpq for your platform in:' 220 | print ' -> %s' % path 221 | print 222 | print 'Ignoring pg_config, trying ctypes.util.find_library()' 223 | if fname: 224 | print ' -> OK (%s)' % fname 225 | else: 226 | print ' -> FAILED' 227 | print 228 | print '=' * 80 229 | if not fname: 230 | sys.exit(1) 231 | return fname 232 | 233 | def run(self): 234 | if not self.dry_run: 235 | target_path = os.path.join(self.build_lib, 'psycopg2ct') 236 | self.mkpath(target_path) 237 | 238 | with open(os.path.join(target_path, '_config.py'), 'w') as fh: 239 | fh.write('# Auto-generated by setup.py\n') 240 | fh.write('VERSION = %r\n' % ('%s (%s)' % (PSYCOPG_VERSION, ' '.join(version_flags)))) 241 | fh.write('PG_LIBRARY = %r\n' % self.libpq_path) 242 | fh.write('PG_VERSION = %s\n' % self.libpq_version) 243 | 244 | _build_py.run(self) 245 | 246 | README = [] 247 | with open('README', 'r') as fh: 248 | README = fh.readlines() 249 | 250 | 251 | setup( 252 | name='psycopg2ct', 253 | author='Michael van Tellingen', 254 | author_email='michaelvantellingen@gmail.com', 255 | license='LGPL', 256 | url='http://github.com/mvantellingen/psycopg2-ctypes', 257 | version='%s (%s)' % (PSYCOPG_VERSION, ' '.join(version_flags)), 258 | cmdclass={ 259 | 'build_py': build_py 260 | }, 261 | classifiers=[ 262 | 'Development Status :: 4 - Beta', 263 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 264 | 'Intended Audience :: Developers', 265 | 'Programming Language :: Python :: 2.6', 266 | 'Programming Language :: Python :: 2.7', 267 | 'Programming Language :: Python :: Implementation :: CPython', 268 | 'Programming Language :: Python :: Implementation :: PyPy', 269 | 'Programming Language :: SQL', 270 | 'Topic :: Database', 271 | 'Topic :: Database :: Front-Ends', 272 | 273 | ], 274 | platforms=['any'], 275 | test_suite='psycopg2ct.tests.suite', 276 | description=README[0].strip(), 277 | long_description=''.join(README), 278 | packages=['psycopg2ct', 'psycopg2ct._impl', 'psycopg2ct.tests'], 279 | ) 280 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py26,py27,pypy 3 | 4 | [testenv] 5 | commands=python setup.py test -q 6 | --------------------------------------------------------------------------------