sqlobject 3.13.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlobject/__init__.py +15 -0
- sqlobject/__version__.py +8 -0
- sqlobject/boundattributes.py +124 -0
- sqlobject/cache.py +376 -0
- sqlobject/classregistry.py +138 -0
- sqlobject/col.py +2187 -0
- sqlobject/compat.py +59 -0
- sqlobject/conftest.py +64 -0
- sqlobject/constraints.py +77 -0
- sqlobject/converters.py +278 -0
- sqlobject/dbconnection.py +1137 -0
- sqlobject/dberrors.py +54 -0
- sqlobject/declarative.py +211 -0
- sqlobject/events.py +365 -0
- sqlobject/firebird/__init__.py +8 -0
- sqlobject/firebird/firebirdconnection.py +463 -0
- sqlobject/include/__init__.py +1 -0
- sqlobject/include/hashcol.py +109 -0
- sqlobject/include/tests/__init__.py +1 -0
- sqlobject/include/tests/test_hashcol.py +84 -0
- sqlobject/index.py +189 -0
- sqlobject/inheritance/__init__.py +551 -0
- sqlobject/inheritance/iteration.py +106 -0
- sqlobject/inheritance/tests/__init__.py +1 -0
- sqlobject/inheritance/tests/test_aggregates.py +23 -0
- sqlobject/inheritance/tests/test_asdict.py +48 -0
- sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
- sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
- sqlobject/inheritance/tests/test_foreignKey.py +94 -0
- sqlobject/inheritance/tests/test_indexes.py +42 -0
- sqlobject/inheritance/tests/test_inheritance.py +164 -0
- sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
- sqlobject/joins.py +656 -0
- sqlobject/main.py +1880 -0
- sqlobject/manager/__init__.py +1 -0
- sqlobject/manager/command.py +1309 -0
- sqlobject/maxdb/__init__.py +8 -0
- sqlobject/maxdb/maxdbconnection.py +320 -0
- sqlobject/maxdb/readme.txt +27 -0
- sqlobject/mssql/__init__.py +8 -0
- sqlobject/mssql/mssqlconnection.py +417 -0
- sqlobject/mysql/__init__.py +8 -0
- sqlobject/mysql/mysqlconnection.py +562 -0
- sqlobject/postgres/__init__.py +8 -0
- sqlobject/postgres/pgconnection.py +619 -0
- sqlobject/sqlbuilder.py +1655 -0
- sqlobject/sqlite/__init__.py +8 -0
- sqlobject/sqlite/sqliteconnection.py +418 -0
- sqlobject/sresults.py +377 -0
- sqlobject/styles.py +174 -0
- sqlobject/sybase/__init__.py +8 -0
- sqlobject/sybase/sybaseconnection.py +183 -0
- sqlobject/tests/__init__.py +1 -0
- sqlobject/tests/dbtest.py +363 -0
- sqlobject/tests/test_ForeignKey.py +133 -0
- sqlobject/tests/test_ForeignKey_cascade.py +150 -0
- sqlobject/tests/test_NoneValuedResultItem.py +33 -0
- sqlobject/tests/test_SQLMultipleJoin.py +65 -0
- sqlobject/tests/test_SQLRelatedJoin.py +114 -0
- sqlobject/tests/test_SingleJoin.py +30 -0
- sqlobject/tests/test_aggregates.py +75 -0
- sqlobject/tests/test_aliases.py +49 -0
- sqlobject/tests/test_asdict.py +18 -0
- sqlobject/tests/test_auto.py +218 -0
- sqlobject/tests/test_basic.py +359 -0
- sqlobject/tests/test_blob.py +34 -0
- sqlobject/tests/test_boundattributes.py +67 -0
- sqlobject/tests/test_cache.py +57 -0
- sqlobject/tests/test_class_hash.py +22 -0
- sqlobject/tests/test_columns_order.py +18 -0
- sqlobject/tests/test_combining_joins.py +47 -0
- sqlobject/tests/test_comparison.py +24 -0
- sqlobject/tests/test_compat.py +10 -0
- sqlobject/tests/test_complex_sorting.py +77 -0
- sqlobject/tests/test_constraints.py +43 -0
- sqlobject/tests/test_converters.py +276 -0
- sqlobject/tests/test_create_drop.py +29 -0
- sqlobject/tests/test_csvexport.py +101 -0
- sqlobject/tests/test_csvimport.py +21 -0
- sqlobject/tests/test_cyclic_reference.py +55 -0
- sqlobject/tests/test_datetime.py +171 -0
- sqlobject/tests/test_dbconnection.py +17 -0
- sqlobject/tests/test_decimal.py +63 -0
- sqlobject/tests/test_declarative.py +83 -0
- sqlobject/tests/test_default_style.py +65 -0
- sqlobject/tests/test_delete.py +74 -0
- sqlobject/tests/test_distinct.py +36 -0
- sqlobject/tests/test_empty.py +22 -0
- sqlobject/tests/test_enum.py +75 -0
- sqlobject/tests/test_events.py +142 -0
- sqlobject/tests/test_exceptions.py +40 -0
- sqlobject/tests/test_expire.py +29 -0
- sqlobject/tests/test_groupBy.py +45 -0
- sqlobject/tests/test_identity.py +29 -0
- sqlobject/tests/test_indexes.py +100 -0
- sqlobject/tests/test_inheritance.py +35 -0
- sqlobject/tests/test_joins.py +160 -0
- sqlobject/tests/test_joins_conditional.py +143 -0
- sqlobject/tests/test_jsonbcol.py +53 -0
- sqlobject/tests/test_jsoncol.py +56 -0
- sqlobject/tests/test_lazy.py +165 -0
- sqlobject/tests/test_md5.py +13 -0
- sqlobject/tests/test_mysql.py +50 -0
- sqlobject/tests/test_new_joins.py +165 -0
- sqlobject/tests/test_parse_uri.py +147 -0
- sqlobject/tests/test_paste.py +92 -0
- sqlobject/tests/test_perConnection.py +20 -0
- sqlobject/tests/test_pickle.py +42 -0
- sqlobject/tests/test_picklecol.py +44 -0
- sqlobject/tests/test_postgres.py +82 -0
- sqlobject/tests/test_reparent_sqlmeta.py +33 -0
- sqlobject/tests/test_schema.py +30 -0
- sqlobject/tests/test_select.py +218 -0
- sqlobject/tests/test_select_through.py +63 -0
- sqlobject/tests/test_setters.py +23 -0
- sqlobject/tests/test_slice.py +59 -0
- sqlobject/tests/test_sorting.py +80 -0
- sqlobject/tests/test_sqlbuilder.py +134 -0
- sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
- sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
- sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
- sqlobject/tests/test_sqlite.py +160 -0
- sqlobject/tests/test_sqlmeta_idName.py +52 -0
- sqlobject/tests/test_sqlobject_admin.py +38 -0
- sqlobject/tests/test_string_id.py +79 -0
- sqlobject/tests/test_style.py +36 -0
- sqlobject/tests/test_subqueries.py +142 -0
- sqlobject/tests/test_transactions.py +122 -0
- sqlobject/tests/test_unicode.py +84 -0
- sqlobject/tests/test_uuidcol.py +40 -0
- sqlobject/tests/test_validation.py +125 -0
- sqlobject/tests/test_views.py +159 -0
- sqlobject/util/__init__.py +1 -0
- sqlobject/util/csvexport.py +210 -0
- sqlobject/util/csvimport.py +366 -0
- sqlobject/util/moduleloader.py +35 -0
- sqlobject/util/threadinglocal.py +6 -0
- sqlobject/versioning/__init__.py +120 -0
- sqlobject/versioning/test/__init__.py +0 -0
- sqlobject/versioning/test/test_version.py +183 -0
- sqlobject/views.py +151 -0
- sqlobject/wsgi_middleware.py +109 -0
- sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
- sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
- sqlobject-3.13.1.dist-info/METADATA +177 -0
- sqlobject-3.13.1.dist-info/RECORD +149 -0
- sqlobject-3.13.1.dist-info/WHEEL +6 -0
- sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
- sqlobject-3.13.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
from getpass import getuser
|
|
2
|
+
import re
|
|
3
|
+
from sqlobject import col
|
|
4
|
+
from sqlobject import dberrors
|
|
5
|
+
from sqlobject import sqlbuilder
|
|
6
|
+
from sqlobject.compat import PY2
|
|
7
|
+
from sqlobject.converters import registerConverter, sqlrepr
|
|
8
|
+
from sqlobject.dbconnection import DBAPI
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ErrorMessage(str):
|
|
12
|
+
def __new__(cls, e, append_msg=''):
|
|
13
|
+
eargs0 = emessage = e.args[0]
|
|
14
|
+
if e.__module__.startswith('pg8000') \
|
|
15
|
+
and isinstance(e.args, tuple) and len(e.args) > 1:
|
|
16
|
+
# pg8000 =~ 1.12 for Python 3.4
|
|
17
|
+
ecode = e.args[2]
|
|
18
|
+
eerror = emessage = e.args[3]
|
|
19
|
+
elif e.__module__.startswith('pg8000') and isinstance(eargs0, dict):
|
|
20
|
+
# pg8000 =~ 1.13 for Python 2.7
|
|
21
|
+
# pg8000 for Python 3.5+
|
|
22
|
+
ecode = eargs0['C']
|
|
23
|
+
eerror = emessage = eargs0['M']
|
|
24
|
+
elif e.__module__ in ('psycopg.errors', 'pg'): # psycopg, PyGreSQL
|
|
25
|
+
ecode = e.sqlstate
|
|
26
|
+
eerror = emessage = e.args[0]
|
|
27
|
+
elif hasattr(e, 'pgcode'): # psycopg2 or psycopg2.errors
|
|
28
|
+
ecode = getattr(e, 'pgcode', None)
|
|
29
|
+
eerror = getattr(e, 'pgerror', None)
|
|
30
|
+
else:
|
|
31
|
+
ecode = getattr(e, 'code', None)
|
|
32
|
+
eerror = getattr(e, 'error', None)
|
|
33
|
+
obj = str.__new__(cls, emessage + append_msg)
|
|
34
|
+
obj.code = ecode
|
|
35
|
+
obj.error = eerror
|
|
36
|
+
obj.module = e.__module__
|
|
37
|
+
obj.exception = e.__class__.__name__
|
|
38
|
+
return obj
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _getuser():
|
|
42
|
+
# ``getuser()`` on w32 can raise ``ImportError``
|
|
43
|
+
# due to absent of ``pwd`` module.
|
|
44
|
+
try:
|
|
45
|
+
return getuser()
|
|
46
|
+
except ImportError:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PostgresConnection(DBAPI):
|
|
51
|
+
|
|
52
|
+
supportTransactions = True
|
|
53
|
+
dbName = 'postgres'
|
|
54
|
+
schemes = [dbName, 'postgresql']
|
|
55
|
+
|
|
56
|
+
odbc_keywords = ('Server', 'Port', 'UID', 'Password', 'Database')
|
|
57
|
+
|
|
58
|
+
def __init__(self, dsn=None, host=None, port=None, db=None,
|
|
59
|
+
user=None, password=None, **kw):
|
|
60
|
+
drivers = kw.pop('driver', None) or 'psycopg,psycopg2,pygresql,pg8000'
|
|
61
|
+
for driver in drivers.split(','):
|
|
62
|
+
driver = driver.strip()
|
|
63
|
+
if not driver:
|
|
64
|
+
continue
|
|
65
|
+
try:
|
|
66
|
+
if driver == 'psycopg':
|
|
67
|
+
import psycopg
|
|
68
|
+
self.module = psycopg
|
|
69
|
+
elif driver == 'psycopg2':
|
|
70
|
+
import psycopg2
|
|
71
|
+
self.module = psycopg2
|
|
72
|
+
elif driver == 'pygresql':
|
|
73
|
+
import pgdb
|
|
74
|
+
self.module = pgdb
|
|
75
|
+
elif driver == 'pg8000':
|
|
76
|
+
try:
|
|
77
|
+
import pg8000.dbapi
|
|
78
|
+
self.module = pg8000.dbapi
|
|
79
|
+
except ImportError:
|
|
80
|
+
import pg8000
|
|
81
|
+
self.module = pg8000
|
|
82
|
+
elif driver == 'pyodbc':
|
|
83
|
+
import pyodbc
|
|
84
|
+
self.module = pyodbc
|
|
85
|
+
elif driver == 'pypyodbc':
|
|
86
|
+
import pypyodbc
|
|
87
|
+
self.module = pypyodbc
|
|
88
|
+
elif driver == 'odbc':
|
|
89
|
+
try:
|
|
90
|
+
import pyodbc
|
|
91
|
+
except ImportError:
|
|
92
|
+
import pypyodbc as pyodbc
|
|
93
|
+
self.module = pyodbc
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
'Unknown PostgreSQL driver "%s", '
|
|
97
|
+
'expected psycopg, psycopg2, '
|
|
98
|
+
'pygresql, pg8000, '
|
|
99
|
+
'odbc, pyodbc or pypyodbc' % driver)
|
|
100
|
+
except ImportError:
|
|
101
|
+
pass
|
|
102
|
+
else:
|
|
103
|
+
break
|
|
104
|
+
else:
|
|
105
|
+
raise ImportError(
|
|
106
|
+
'Cannot find a PostgreSQL driver, tried %s' % drivers)
|
|
107
|
+
|
|
108
|
+
if driver.startswith('psycopg2'):
|
|
109
|
+
# Register a converter for psycopg2 Binary type.
|
|
110
|
+
registerConverter(type(self.module.Binary('')),
|
|
111
|
+
Psyco2BinaryConverter)
|
|
112
|
+
elif driver.startswith('psycopg'):
|
|
113
|
+
# Register a converter for psycopg Binary type.
|
|
114
|
+
registerConverter(type(self.module.Binary('')),
|
|
115
|
+
PsycoBinaryConverter)
|
|
116
|
+
elif driver in ('pygresql', 'pg8000'):
|
|
117
|
+
registerConverter(type(self.module.Binary(b'')),
|
|
118
|
+
PostgresBinaryConverter)
|
|
119
|
+
elif driver in ('odbc', 'pyodbc', 'pypyodbc'):
|
|
120
|
+
registerConverter(bytearray, OdbcBinaryConverter)
|
|
121
|
+
|
|
122
|
+
self.db = db
|
|
123
|
+
self.user = user
|
|
124
|
+
self.password = password
|
|
125
|
+
self.host = host
|
|
126
|
+
self.port = port
|
|
127
|
+
if driver in ('odbc', 'pyodbc', 'pypyodbc'):
|
|
128
|
+
self.make_odbc_conn_str(kw.pop('odbcdrv', 'PostgreSQL ANSI'),
|
|
129
|
+
db, host, port, user, password
|
|
130
|
+
)
|
|
131
|
+
sslmode = kw.pop("sslmode", None)
|
|
132
|
+
if sslmode:
|
|
133
|
+
self.odbc_conn_str += ';sslmode=require'
|
|
134
|
+
else:
|
|
135
|
+
self.dsn_dict = dsn_dict = {}
|
|
136
|
+
if host:
|
|
137
|
+
dsn_dict["host"] = host
|
|
138
|
+
if port:
|
|
139
|
+
if driver == 'pygresql':
|
|
140
|
+
dsn_dict["host"] = "%s:%d" % (host, port)
|
|
141
|
+
elif driver.startswith('psycopg') and \
|
|
142
|
+
self.module.__version__.split('.')[0] == '1':
|
|
143
|
+
dsn_dict["port"] = str(port)
|
|
144
|
+
else:
|
|
145
|
+
dsn_dict["port"] = port
|
|
146
|
+
if db:
|
|
147
|
+
if driver == 'psycopg':
|
|
148
|
+
dsn_dict["dbname"] = db
|
|
149
|
+
else:
|
|
150
|
+
dsn_dict["database"] = db
|
|
151
|
+
if user:
|
|
152
|
+
dsn_dict["user"] = user
|
|
153
|
+
if password:
|
|
154
|
+
dsn_dict["password"] = password
|
|
155
|
+
sslmode = kw.pop("sslmode", None)
|
|
156
|
+
if sslmode:
|
|
157
|
+
dsn_dict["sslmode"] = sslmode
|
|
158
|
+
self.use_dsn = dsn is not None
|
|
159
|
+
if dsn is None:
|
|
160
|
+
if driver == 'pygresql':
|
|
161
|
+
dsn = ''
|
|
162
|
+
if host:
|
|
163
|
+
dsn += host
|
|
164
|
+
dsn += ':'
|
|
165
|
+
if db:
|
|
166
|
+
dsn += db
|
|
167
|
+
dsn += ':'
|
|
168
|
+
if user:
|
|
169
|
+
dsn += user
|
|
170
|
+
dsn += ':'
|
|
171
|
+
if password:
|
|
172
|
+
dsn += password
|
|
173
|
+
else:
|
|
174
|
+
dsn = []
|
|
175
|
+
if db:
|
|
176
|
+
dsn.append('dbname=%s' % db)
|
|
177
|
+
if user:
|
|
178
|
+
dsn.append('user=%s' % user)
|
|
179
|
+
if password:
|
|
180
|
+
dsn.append('password=%s' % password)
|
|
181
|
+
if host:
|
|
182
|
+
dsn.append('host=%s' % host)
|
|
183
|
+
if port:
|
|
184
|
+
dsn.append('port=%d' % port)
|
|
185
|
+
if sslmode:
|
|
186
|
+
dsn.append('sslmode=%s' % sslmode)
|
|
187
|
+
dsn = ' '.join(dsn)
|
|
188
|
+
if driver == 'pg8000':
|
|
189
|
+
if host and host.startswith('/'):
|
|
190
|
+
dsn_dict["host"] = None
|
|
191
|
+
dsn_dict["unix_sock"] = host
|
|
192
|
+
if user is None:
|
|
193
|
+
dsn_dict["user"] = _getuser()
|
|
194
|
+
self.dsn = dsn
|
|
195
|
+
self.driver = driver
|
|
196
|
+
self.unicodeCols = kw.pop('unicodeCols', False)
|
|
197
|
+
self.schema = kw.pop('schema', None)
|
|
198
|
+
self.dbEncoding = kw.pop("charset", None)
|
|
199
|
+
DBAPI.__init__(self, **kw)
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
def _connectionFromParams(cls, user, password, host, port, path, args):
|
|
203
|
+
path = path.strip('/')
|
|
204
|
+
if (host is None) and path.count('/'): # Non-default unix socket
|
|
205
|
+
path_parts = path.split('/')
|
|
206
|
+
host = '/' + '/'.join(path_parts[:-1])
|
|
207
|
+
path = path_parts[-1]
|
|
208
|
+
return cls(host=host, port=port, db=path,
|
|
209
|
+
user=user, password=password, **args)
|
|
210
|
+
|
|
211
|
+
def _setAutoCommit(self, conn, auto):
|
|
212
|
+
# psycopg2 does not have an autocommit method.
|
|
213
|
+
if hasattr(conn, 'autocommit'):
|
|
214
|
+
try:
|
|
215
|
+
conn.autocommit(auto)
|
|
216
|
+
except TypeError:
|
|
217
|
+
conn.autocommit = auto
|
|
218
|
+
|
|
219
|
+
def makeConnection(self):
|
|
220
|
+
try:
|
|
221
|
+
if self.driver in ('odbc', 'pyodbc', 'pypyodbc'):
|
|
222
|
+
self.debugWriter.write(
|
|
223
|
+
"ODBC connect string: " + self.odbc_conn_str)
|
|
224
|
+
conn = self.module.connect(self.odbc_conn_str)
|
|
225
|
+
elif self.use_dsn:
|
|
226
|
+
conn = self.module.connect(self.dsn)
|
|
227
|
+
else:
|
|
228
|
+
conn = self.module.connect(**self.dsn_dict)
|
|
229
|
+
except self.module.OperationalError as e:
|
|
230
|
+
raise dberrors.OperationalError(
|
|
231
|
+
ErrorMessage(e, "used connection string %r" % self.dsn))
|
|
232
|
+
|
|
233
|
+
# For printDebug in _executeRetry
|
|
234
|
+
self._connectionNumbers[id(conn)] = self._connectionCount
|
|
235
|
+
|
|
236
|
+
if self.autoCommit:
|
|
237
|
+
self._setAutoCommit(conn, 1)
|
|
238
|
+
c = conn.cursor()
|
|
239
|
+
if self.schema:
|
|
240
|
+
self._executeRetry(conn, c, "SET search_path TO " + self.schema)
|
|
241
|
+
dbEncoding = self.dbEncoding
|
|
242
|
+
if dbEncoding:
|
|
243
|
+
if self.driver in ('odbc', 'pyodbc'):
|
|
244
|
+
conn.setdecoding(self.module.SQL_CHAR, encoding=dbEncoding)
|
|
245
|
+
conn.setdecoding(self.module.SQL_WCHAR, encoding=dbEncoding)
|
|
246
|
+
if PY2:
|
|
247
|
+
conn.setencoding(str, encoding=dbEncoding)
|
|
248
|
+
conn.setencoding(unicode, encoding=dbEncoding) # noqa
|
|
249
|
+
else:
|
|
250
|
+
conn.setencoding(encoding=dbEncoding)
|
|
251
|
+
self._executeRetry(conn, c,
|
|
252
|
+
"SET client_encoding TO '%s'" % dbEncoding)
|
|
253
|
+
c.close()
|
|
254
|
+
return conn
|
|
255
|
+
|
|
256
|
+
def _executeRetry(self, conn, cursor, query):
|
|
257
|
+
if self.debug:
|
|
258
|
+
self.printDebug(conn, query, 'QueryR')
|
|
259
|
+
dbEncoding = self.dbEncoding
|
|
260
|
+
if dbEncoding and isinstance(query, bytes) and (
|
|
261
|
+
self.driver == 'pg8000'):
|
|
262
|
+
query = query.decode(dbEncoding)
|
|
263
|
+
try:
|
|
264
|
+
return cursor.execute(query)
|
|
265
|
+
except self.module.OperationalError as e:
|
|
266
|
+
raise dberrors.OperationalError(ErrorMessage(e))
|
|
267
|
+
except self.module.IntegrityError as e:
|
|
268
|
+
msg = ErrorMessage(e)
|
|
269
|
+
if getattr(msg, 'code', -1) == '23505' or \
|
|
270
|
+
getattr(e, 'code', -1) == '23505' or \
|
|
271
|
+
getattr(e, 'pgcode', -1) == '23505' or \
|
|
272
|
+
getattr(e, 'sqlstate', -1) == '23505' or \
|
|
273
|
+
e.args[0] == '23505':
|
|
274
|
+
raise dberrors.DuplicateEntryError(msg)
|
|
275
|
+
else:
|
|
276
|
+
raise dberrors.IntegrityError(msg)
|
|
277
|
+
except self.module.InternalError as e:
|
|
278
|
+
raise dberrors.InternalError(ErrorMessage(e))
|
|
279
|
+
except self.module.ProgrammingError as e:
|
|
280
|
+
msg = ErrorMessage(e)
|
|
281
|
+
if (
|
|
282
|
+
(len(e.args) > 2) and (e.args[1] == 'ERROR')
|
|
283
|
+
and (e.args[2] == '23505')) \
|
|
284
|
+
or ((len(e.args) >= 2) and (e.args[1] == '23505')):
|
|
285
|
+
raise dberrors.DuplicateEntryError(msg)
|
|
286
|
+
else:
|
|
287
|
+
raise dberrors.ProgrammingError(msg)
|
|
288
|
+
except self.module.DataError as e:
|
|
289
|
+
raise dberrors.DataError(ErrorMessage(e))
|
|
290
|
+
except self.module.NotSupportedError as e:
|
|
291
|
+
raise dberrors.NotSupportedError(ErrorMessage(e))
|
|
292
|
+
except self.module.DatabaseError as e:
|
|
293
|
+
msg = ErrorMessage(e)
|
|
294
|
+
if 'duplicate key value violates unique constraint' in msg:
|
|
295
|
+
raise dberrors.DuplicateEntryError(msg)
|
|
296
|
+
else:
|
|
297
|
+
raise dberrors.DatabaseError(msg)
|
|
298
|
+
except self.module.InterfaceError as e:
|
|
299
|
+
raise dberrors.InterfaceError(ErrorMessage(e))
|
|
300
|
+
except self.module.Warning as e:
|
|
301
|
+
raise Warning(ErrorMessage(e))
|
|
302
|
+
except self.module.Error as e:
|
|
303
|
+
raise dberrors.Error(ErrorMessage(e))
|
|
304
|
+
|
|
305
|
+
def _queryInsertID(self, conn, soInstance, id, names, values):
|
|
306
|
+
table = soInstance.sqlmeta.table
|
|
307
|
+
idName = soInstance.sqlmeta.idName
|
|
308
|
+
c = conn.cursor()
|
|
309
|
+
if id is not None:
|
|
310
|
+
names = [idName] + names
|
|
311
|
+
values = [id] + values
|
|
312
|
+
if names and values:
|
|
313
|
+
q = self._insertSQL(table, names, values)
|
|
314
|
+
else:
|
|
315
|
+
q = "INSERT INTO %s DEFAULT VALUES" % table
|
|
316
|
+
if id is None:
|
|
317
|
+
q += " RETURNING " + idName
|
|
318
|
+
if self.debug:
|
|
319
|
+
self.printDebug(conn, q, 'QueryIns')
|
|
320
|
+
self._executeRetry(conn, c, q)
|
|
321
|
+
if id is None:
|
|
322
|
+
id = c.fetchone()[0]
|
|
323
|
+
c.close()
|
|
324
|
+
if self.debugOutput:
|
|
325
|
+
self.printDebug(conn, id, 'QueryIns', 'result')
|
|
326
|
+
return id
|
|
327
|
+
|
|
328
|
+
@classmethod
|
|
329
|
+
def _queryAddLimitOffset(cls, query, start, end):
|
|
330
|
+
if not start:
|
|
331
|
+
return "%s LIMIT %i" % (query, end)
|
|
332
|
+
if not end:
|
|
333
|
+
return "%s OFFSET %i" % (query, start)
|
|
334
|
+
return "%s LIMIT %i OFFSET %i" % (query, end - start, start)
|
|
335
|
+
|
|
336
|
+
def createColumn(self, soClass, col):
|
|
337
|
+
return col.postgresCreateSQL()
|
|
338
|
+
|
|
339
|
+
def createReferenceConstraint(self, soClass, col):
|
|
340
|
+
return col.postgresCreateReferenceConstraint()
|
|
341
|
+
|
|
342
|
+
def createIndexSQL(self, soClass, index):
|
|
343
|
+
return index.postgresCreateIndexSQL(soClass)
|
|
344
|
+
|
|
345
|
+
def createIDColumn(self, soClass):
|
|
346
|
+
if soClass.sqlmeta.idType is int:
|
|
347
|
+
if soClass.sqlmeta.idSize in ('TINY', 'SMALL'):
|
|
348
|
+
key_type = 'SMALLSERIAL'
|
|
349
|
+
elif soClass.sqlmeta.idSize in ('MEDIUM', None):
|
|
350
|
+
key_type = 'SERIAL'
|
|
351
|
+
elif soClass.sqlmeta.idSize == 'BIG':
|
|
352
|
+
key_type = 'BIGSERIAL'
|
|
353
|
+
else:
|
|
354
|
+
raise ValueError(
|
|
355
|
+
"sqlmeta.idSize must be 'TINY', 'SMALL', 'MEDIUM', 'BIG' "
|
|
356
|
+
"or None, not %r" % soClass.sqlmeta.idSize)
|
|
357
|
+
elif soClass.sqlmeta.idType is str:
|
|
358
|
+
key_type = "TEXT"
|
|
359
|
+
else:
|
|
360
|
+
raise TypeError('sqlmeta.idType must be int or str, not %r'
|
|
361
|
+
% soClass.sqlmeta.idType)
|
|
362
|
+
return '%s %s PRIMARY KEY' % (soClass.sqlmeta.idName, key_type)
|
|
363
|
+
|
|
364
|
+
def dropTable(self, tableName, cascade=False):
|
|
365
|
+
self.query("DROP TABLE %s %s" % (tableName,
|
|
366
|
+
cascade and 'CASCADE' or ''))
|
|
367
|
+
|
|
368
|
+
def joinSQLType(self, join):
|
|
369
|
+
return 'INT NOT NULL'
|
|
370
|
+
|
|
371
|
+
def tableExists(self, tableName):
|
|
372
|
+
result = self.queryOne(
|
|
373
|
+
"SELECT COUNT(relname) FROM pg_class WHERE relname = %s" %
|
|
374
|
+
self.sqlrepr(tableName))
|
|
375
|
+
return result[0]
|
|
376
|
+
|
|
377
|
+
def addColumn(self, tableName, column):
|
|
378
|
+
self.query('ALTER TABLE %s ADD COLUMN %s' %
|
|
379
|
+
(tableName,
|
|
380
|
+
column.postgresCreateSQL()))
|
|
381
|
+
|
|
382
|
+
def delColumn(self, sqlmeta, column):
|
|
383
|
+
self.query('ALTER TABLE %s DROP COLUMN %s' % (sqlmeta.table,
|
|
384
|
+
column.dbName))
|
|
385
|
+
|
|
386
|
+
def columnsFromSchema(self, tableName, soClass):
|
|
387
|
+
|
|
388
|
+
keyQuery = """
|
|
389
|
+
SELECT pg_catalog.pg_get_constraintdef(oid) as condef
|
|
390
|
+
FROM pg_catalog.pg_constraint r
|
|
391
|
+
WHERE r.conrelid = %s::regclass AND r.contype = 'f'"""
|
|
392
|
+
|
|
393
|
+
colQuery = """
|
|
394
|
+
SELECT a.attname,
|
|
395
|
+
pg_catalog.format_type(a.atttypid, a.atttypmod), a.attnotnull,
|
|
396
|
+
(SELECT substring(pg_catalog.pg_get_expr(d.adbin, d.adrelid) for 128)
|
|
397
|
+
FROM pg_catalog.pg_attrdef d
|
|
398
|
+
WHERE d.adrelid=a.attrelid AND d.adnum = a.attnum)
|
|
399
|
+
FROM pg_catalog.pg_attribute a
|
|
400
|
+
WHERE a.attrelid =%s::regclass
|
|
401
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
|
402
|
+
ORDER BY a.attnum"""
|
|
403
|
+
|
|
404
|
+
primaryKeyQuery = """
|
|
405
|
+
SELECT pg_index.indisprimary,
|
|
406
|
+
pg_catalog.pg_get_indexdef(pg_index.indexrelid)
|
|
407
|
+
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
|
|
408
|
+
pg_catalog.pg_index AS pg_index
|
|
409
|
+
WHERE c.relname = %s
|
|
410
|
+
AND c.oid = pg_index.indrelid
|
|
411
|
+
AND pg_index.indexrelid = c2.oid
|
|
412
|
+
AND pg_index.indisprimary
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
otherKeyQuery = """
|
|
416
|
+
SELECT pg_index.indisprimary,
|
|
417
|
+
pg_catalog.pg_get_indexdef(pg_index.indexrelid)
|
|
418
|
+
FROM pg_catalog.pg_class c, pg_catalog.pg_class c2,
|
|
419
|
+
pg_catalog.pg_index AS pg_index
|
|
420
|
+
WHERE c.relname = %s
|
|
421
|
+
AND c.oid = pg_index.indrelid
|
|
422
|
+
AND pg_index.indexrelid = c2.oid
|
|
423
|
+
AND NOT pg_index.indisprimary
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
keyData = self.queryAll(keyQuery % self.sqlrepr(tableName))
|
|
427
|
+
keyRE = re.compile(r"\((.+)\) REFERENCES (.+)\(")
|
|
428
|
+
keymap = {}
|
|
429
|
+
|
|
430
|
+
for (condef,) in keyData:
|
|
431
|
+
match = keyRE.search(condef)
|
|
432
|
+
if match:
|
|
433
|
+
field, reftable = match.groups()
|
|
434
|
+
keymap[field] = reftable.capitalize()
|
|
435
|
+
|
|
436
|
+
primaryData = self.queryAll(primaryKeyQuery % self.sqlrepr(tableName))
|
|
437
|
+
primaryRE = re.compile(r'CREATE .*? USING .* \((.+?)\)')
|
|
438
|
+
primaryKey = None
|
|
439
|
+
for isPrimary, indexDef in primaryData:
|
|
440
|
+
match = primaryRE.search(indexDef)
|
|
441
|
+
assert match, "Unparseable contraint definition: %r" % indexDef
|
|
442
|
+
assert primaryKey is None, \
|
|
443
|
+
"Already found primary key (%r), " \
|
|
444
|
+
"then found: %r" % (primaryKey, indexDef)
|
|
445
|
+
primaryKey = match.group(1)
|
|
446
|
+
if primaryKey is None:
|
|
447
|
+
# VIEWs don't have PRIMARY KEYs - accept help from user
|
|
448
|
+
primaryKey = soClass.sqlmeta.idName
|
|
449
|
+
assert primaryKey, "No primary key found in table %r" % tableName
|
|
450
|
+
if primaryKey.startswith('"'):
|
|
451
|
+
assert primaryKey.endswith('"')
|
|
452
|
+
primaryKey = primaryKey[1:-1]
|
|
453
|
+
|
|
454
|
+
otherData = self.queryAll(otherKeyQuery % self.sqlrepr(tableName))
|
|
455
|
+
otherRE = primaryRE
|
|
456
|
+
otherKeys = []
|
|
457
|
+
for isPrimary, indexDef in otherData:
|
|
458
|
+
match = otherRE.search(indexDef)
|
|
459
|
+
assert match, "Unparseable constraint definition: %r" % indexDef
|
|
460
|
+
otherKey = match.group(1)
|
|
461
|
+
if otherKey.startswith('"'):
|
|
462
|
+
assert otherKey.endswith('"')
|
|
463
|
+
otherKey = otherKey[1:-1]
|
|
464
|
+
otherKeys.append(otherKey)
|
|
465
|
+
|
|
466
|
+
colData = self.queryAll(colQuery % self.sqlrepr(tableName))
|
|
467
|
+
results = []
|
|
468
|
+
if self.unicodeCols:
|
|
469
|
+
client_encoding = self.queryOne("SHOW client_encoding")[0]
|
|
470
|
+
for field, t, notnull, defaultstr in colData:
|
|
471
|
+
if field == primaryKey:
|
|
472
|
+
continue
|
|
473
|
+
if field in keymap:
|
|
474
|
+
colClass = col.ForeignKey
|
|
475
|
+
kw = {'foreignKey': soClass.sqlmeta.style.
|
|
476
|
+
dbTableToPythonClass(keymap[field])}
|
|
477
|
+
name = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
|
|
478
|
+
if name.endswith('ID'):
|
|
479
|
+
name = name[:-2]
|
|
480
|
+
kw['name'] = name
|
|
481
|
+
else:
|
|
482
|
+
colClass, kw = self.guessClass(t)
|
|
483
|
+
if self.unicodeCols and colClass is col.StringCol:
|
|
484
|
+
colClass = col.UnicodeCol
|
|
485
|
+
kw['dbEncoding'] = client_encoding
|
|
486
|
+
kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
|
|
487
|
+
kw['dbName'] = field
|
|
488
|
+
kw['notNone'] = notnull
|
|
489
|
+
if defaultstr is not None:
|
|
490
|
+
kw['default'] = self.defaultFromSchema(colClass, defaultstr)
|
|
491
|
+
elif not notnull:
|
|
492
|
+
kw['default'] = None
|
|
493
|
+
if field in otherKeys:
|
|
494
|
+
kw['alternateID'] = True
|
|
495
|
+
results.append(colClass(**kw))
|
|
496
|
+
return results
|
|
497
|
+
|
|
498
|
+
def guessClass(self, t):
|
|
499
|
+
if t.count('point'): # poINT before INT
|
|
500
|
+
return col.StringCol, {}
|
|
501
|
+
elif t.count('int'):
|
|
502
|
+
return col.IntCol, {}
|
|
503
|
+
elif t.count('varying') or t.count('varchar'):
|
|
504
|
+
if '(' in t:
|
|
505
|
+
return col.StringCol, {'length': int(t[t.index('(') + 1:-1])}
|
|
506
|
+
else: # varchar without length in Postgres means any length
|
|
507
|
+
return col.StringCol, {}
|
|
508
|
+
elif t.startswith('character('):
|
|
509
|
+
return col.StringCol, {'length': int(t[t.index('(') + 1:-1]),
|
|
510
|
+
'varchar': False}
|
|
511
|
+
elif t.count('float') or t.count('real') or t.count('double'):
|
|
512
|
+
return col.FloatCol, {}
|
|
513
|
+
elif t == 'text':
|
|
514
|
+
return col.StringCol, {}
|
|
515
|
+
elif t.startswith('timestamp'):
|
|
516
|
+
return col.DateTimeCol, {}
|
|
517
|
+
elif t.startswith('datetime'):
|
|
518
|
+
return col.DateTimeCol, {}
|
|
519
|
+
elif t.startswith('date'):
|
|
520
|
+
return col.DateCol, {}
|
|
521
|
+
elif t.startswith('bool'):
|
|
522
|
+
return col.BoolCol, {}
|
|
523
|
+
elif t.startswith('bytea'):
|
|
524
|
+
return col.BLOBCol, {}
|
|
525
|
+
else:
|
|
526
|
+
return col.Col, {}
|
|
527
|
+
|
|
528
|
+
def defaultFromSchema(self, colClass, defaultstr):
|
|
529
|
+
"""
|
|
530
|
+
If the default can be converted to a python constant, convert it.
|
|
531
|
+
Otherwise return is as a sqlbuilder constant.
|
|
532
|
+
"""
|
|
533
|
+
if colClass == col.BoolCol:
|
|
534
|
+
if defaultstr == 'false':
|
|
535
|
+
return False
|
|
536
|
+
elif defaultstr == 'true':
|
|
537
|
+
return True
|
|
538
|
+
return getattr(sqlbuilder.const, defaultstr)
|
|
539
|
+
|
|
540
|
+
def _createOrDropDatabase(self, op="CREATE"):
|
|
541
|
+
# We have to connect to *some* database, so we'll connect to
|
|
542
|
+
# template1, which is a common open database.
|
|
543
|
+
# @@: This doesn't use self.use_dsn or self.dsn_dict
|
|
544
|
+
if self.driver == 'pygresql':
|
|
545
|
+
dsn = '%s:template1:%s:%s' % (
|
|
546
|
+
self.host or '', self.user or '', self.password or '')
|
|
547
|
+
else:
|
|
548
|
+
dsn = 'dbname=template1'
|
|
549
|
+
if self.user:
|
|
550
|
+
dsn += ' user=%s' % self.user
|
|
551
|
+
if self.password:
|
|
552
|
+
dsn += ' password=%s' % self.password
|
|
553
|
+
if self.host:
|
|
554
|
+
dsn += ' host=%s' % self.host
|
|
555
|
+
conn = self.module.connect(dsn)
|
|
556
|
+
cur = conn.cursor()
|
|
557
|
+
# We must close the transaction with a commit so that
|
|
558
|
+
# the CREATE DATABASE can work (which can't be in a transaction):
|
|
559
|
+
try:
|
|
560
|
+
self._executeRetry(conn, cur, 'COMMIT')
|
|
561
|
+
self._executeRetry(conn, cur, '%s DATABASE %s' % (op, self.db))
|
|
562
|
+
finally:
|
|
563
|
+
cur.close()
|
|
564
|
+
conn.close()
|
|
565
|
+
|
|
566
|
+
def listTables(self):
|
|
567
|
+
return [v[0] for v in self.queryAll(
|
|
568
|
+
"""SELECT c.relname FROM pg_catalog.pg_class c
|
|
569
|
+
LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
570
|
+
WHERE c.relkind IN ('r','') AND
|
|
571
|
+
n.nspname NOT IN ('pg_catalog', 'pg_toast') AND
|
|
572
|
+
pg_catalog.pg_table_is_visible(c.oid)""")]
|
|
573
|
+
|
|
574
|
+
def listDatabases(self):
|
|
575
|
+
return [v[0] for v in self.queryAll("SELECT datname FROM pg_database")]
|
|
576
|
+
|
|
577
|
+
def createEmptyDatabase(self):
|
|
578
|
+
self._createOrDropDatabase()
|
|
579
|
+
|
|
580
|
+
def dropDatabase(self):
|
|
581
|
+
self._createOrDropDatabase(op="DROP")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
# Converters for Binary types
|
|
585
|
+
def Psyco2BinaryConverter(value, db):
|
|
586
|
+
assert db == 'postgres'
|
|
587
|
+
return str(value)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
if PY2:
|
|
591
|
+
def escape_bytea(value):
|
|
592
|
+
return ''.join(
|
|
593
|
+
['\\' + (x[1:].rjust(3, '0'))
|
|
594
|
+
for x in (oct(ord(c)) for c in value)]
|
|
595
|
+
)
|
|
596
|
+
else:
|
|
597
|
+
def escape_bytea(value):
|
|
598
|
+
return ''.join(
|
|
599
|
+
['\\' + (x[2:].rjust(3, '0'))
|
|
600
|
+
for x in (oct(ord(c)) for c in value.decode('latin1'))]
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def PsycoBinaryConverter(value, db):
|
|
605
|
+
assert db == 'postgres'
|
|
606
|
+
return sqlrepr(escape_bytea(value.obj), db)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def PostgresBinaryConverter(value, db):
|
|
610
|
+
assert db == 'postgres'
|
|
611
|
+
return sqlrepr(escape_bytea(value), db)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def OdbcBinaryConverter(value, db):
|
|
615
|
+
assert db == 'postgres'
|
|
616
|
+
value = bytes(value)
|
|
617
|
+
if not PY2:
|
|
618
|
+
value = value.decode('latin1')
|
|
619
|
+
return value
|