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,562 @@
|
|
|
1
|
+
from sqlobject import col, dberrors
|
|
2
|
+
from sqlobject.compat import PY2
|
|
3
|
+
from sqlobject.converters import registerConverter, StringLikeConverter
|
|
4
|
+
from sqlobject.dbconnection import DBAPI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ErrorMessage(str):
|
|
8
|
+
def __new__(cls, e, append_msg=''):
|
|
9
|
+
if len(e.args) > 1:
|
|
10
|
+
errmsg = e.args[1]
|
|
11
|
+
else:
|
|
12
|
+
errmsg = ''
|
|
13
|
+
try:
|
|
14
|
+
errcode = int(e.args[0])
|
|
15
|
+
except ValueError:
|
|
16
|
+
errcode = e.args[0]
|
|
17
|
+
obj = str.__new__(cls, errmsg + append_msg)
|
|
18
|
+
obj.code = errcode
|
|
19
|
+
obj.module = e.__module__
|
|
20
|
+
obj.exception = e.__class__.__name__
|
|
21
|
+
return obj
|
|
22
|
+
|
|
23
|
+
mysql_Bin = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MySQLConnection(DBAPI):
|
|
27
|
+
|
|
28
|
+
supportTransactions = False
|
|
29
|
+
dbName = 'mysql'
|
|
30
|
+
schemes = [dbName]
|
|
31
|
+
|
|
32
|
+
odbc_keywords = ('Server', 'Port', 'UID', 'Password', 'Database')
|
|
33
|
+
|
|
34
|
+
def __init__(self, db, user, password='', host='localhost', port=0, **kw):
|
|
35
|
+
drivers = kw.pop('driver', None) or 'mysqldb,mysqlclient,' + \
|
|
36
|
+
'mysql-connector,mysql-connector-python,pymysql'
|
|
37
|
+
for driver in drivers.split(','):
|
|
38
|
+
driver = driver.strip().lower()
|
|
39
|
+
if not driver:
|
|
40
|
+
continue
|
|
41
|
+
try:
|
|
42
|
+
if driver in ('mysqldb', 'pymysql'):
|
|
43
|
+
if driver == 'pymysql':
|
|
44
|
+
import pymysql
|
|
45
|
+
pymysql.install_as_MySQLdb()
|
|
46
|
+
import MySQLdb
|
|
47
|
+
if driver == 'mysqldb':
|
|
48
|
+
if MySQLdb.version_info[:3] < (1, 2, 2):
|
|
49
|
+
raise ValueError(
|
|
50
|
+
'SQLObject requires MySQLdb 1.2.2 or later')
|
|
51
|
+
import MySQLdb.constants.CR
|
|
52
|
+
import MySQLdb.constants.ER
|
|
53
|
+
self.module = MySQLdb
|
|
54
|
+
if driver == 'mysqldb':
|
|
55
|
+
self.CR_SERVER_GONE_ERROR = \
|
|
56
|
+
MySQLdb.constants.CR.SERVER_GONE_ERROR
|
|
57
|
+
self.CR_SERVER_LOST = \
|
|
58
|
+
MySQLdb.constants.CR.SERVER_LOST
|
|
59
|
+
else:
|
|
60
|
+
self.CR_SERVER_GONE_ERROR = \
|
|
61
|
+
MySQLdb.constants.CR.CR_SERVER_GONE_ERROR
|
|
62
|
+
self.CR_SERVER_LOST = \
|
|
63
|
+
MySQLdb.constants.CR.CR_SERVER_LOST
|
|
64
|
+
self.ER_DUP_ENTRY = MySQLdb.constants.ER.DUP_ENTRY
|
|
65
|
+
elif driver in ('connector', 'connector-python'):
|
|
66
|
+
import mysql.connector
|
|
67
|
+
self.module = mysql.connector
|
|
68
|
+
self.CR_SERVER_GONE_ERROR = \
|
|
69
|
+
mysql.connector.errorcode.CR_SERVER_GONE_ERROR
|
|
70
|
+
self.CR_SERVER_LOST = \
|
|
71
|
+
mysql.connector.errorcode.CR_SERVER_LOST
|
|
72
|
+
self.ER_DUP_ENTRY = mysql.connector.errorcode.ER_DUP_ENTRY
|
|
73
|
+
if driver == 'connector-python':
|
|
74
|
+
self.connector_type = 'mysql.connector-python'
|
|
75
|
+
else:
|
|
76
|
+
self.connector_type = 'mysql.connector'
|
|
77
|
+
elif driver == 'mariadb':
|
|
78
|
+
import mariadb
|
|
79
|
+
self.module = mariadb
|
|
80
|
+
elif driver == 'pyodbc':
|
|
81
|
+
import pyodbc
|
|
82
|
+
self.module = pyodbc
|
|
83
|
+
elif driver == 'pypyodbc':
|
|
84
|
+
import pypyodbc
|
|
85
|
+
self.module = pypyodbc
|
|
86
|
+
elif driver == 'odbc':
|
|
87
|
+
try:
|
|
88
|
+
import pyodbc
|
|
89
|
+
except ImportError:
|
|
90
|
+
import pypyodbc as pyodbc
|
|
91
|
+
self.module = pyodbc
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
'Unknown MySQL driver "%s", '
|
|
95
|
+
'expected mysqldb, connector, connector-python, '
|
|
96
|
+
'pymysql, mariadb, '
|
|
97
|
+
'odbc, pyodbc or pypyodbc' % driver)
|
|
98
|
+
except ImportError:
|
|
99
|
+
pass
|
|
100
|
+
else:
|
|
101
|
+
break
|
|
102
|
+
else:
|
|
103
|
+
raise ImportError(
|
|
104
|
+
'Cannot find a MySQL driver, tried %s' % drivers)
|
|
105
|
+
|
|
106
|
+
self.host = host
|
|
107
|
+
self.port = port or 3306
|
|
108
|
+
self.db = db
|
|
109
|
+
self.user = user
|
|
110
|
+
self.password = password
|
|
111
|
+
self.kw = {}
|
|
112
|
+
|
|
113
|
+
for key in ("unix_socket", "init_command",
|
|
114
|
+
"read_default_file", "read_default_group", "conv"):
|
|
115
|
+
if key in kw:
|
|
116
|
+
self.kw[key] = kw.pop(key)
|
|
117
|
+
for key in ("connect_timeout", "compress", "named_pipe", "use_unicode",
|
|
118
|
+
"client_flag", "local_infile"):
|
|
119
|
+
if key in kw:
|
|
120
|
+
self.kw[key] = int(kw.pop(key))
|
|
121
|
+
if driver in ('connector', 'connector-python'):
|
|
122
|
+
for key in ("ssl_key", "ssl_cert", "ssl_ca", "ssl_capath"):
|
|
123
|
+
if key in kw:
|
|
124
|
+
self.kw[key] = kw.pop(key)
|
|
125
|
+
else:
|
|
126
|
+
for key in ("ssl_key", "ssl_cert", "ssl_ca", "ssl_capath"):
|
|
127
|
+
if key in kw:
|
|
128
|
+
if "ssl" not in self.kw:
|
|
129
|
+
self.kw["ssl"] = {}
|
|
130
|
+
self.kw["ssl"][key[4:]] = kw.pop(key)
|
|
131
|
+
if "charset" in kw:
|
|
132
|
+
self.dbEncoding = self.kw["charset"] = kw.pop("charset")
|
|
133
|
+
else:
|
|
134
|
+
self.dbEncoding = None
|
|
135
|
+
self.driver = driver
|
|
136
|
+
|
|
137
|
+
if driver in ('mariadb', 'odbc', 'pyodbc', 'pypyodbc'):
|
|
138
|
+
self.CR_SERVER_GONE_ERROR = 2006
|
|
139
|
+
self.CR_SERVER_LOST = 2013
|
|
140
|
+
self.ER_DUP_ENTRY = '23000'
|
|
141
|
+
|
|
142
|
+
if driver in ('odbc', 'pyodbc', 'pypyodbc'):
|
|
143
|
+
self.make_odbc_conn_str(kw.pop('odbcdrv',
|
|
144
|
+
'MySQL ODBC 5.3 ANSI Driver'),
|
|
145
|
+
db, host, port, user, password
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
elif driver == 'mariadb':
|
|
149
|
+
self.kw.pop("charset", None)
|
|
150
|
+
|
|
151
|
+
elif driver in ('connector', 'connector-python'):
|
|
152
|
+
registerConverter(bytes, ConnectorBytesConverter)
|
|
153
|
+
|
|
154
|
+
global mysql_Bin
|
|
155
|
+
if not PY2 and mysql_Bin is None:
|
|
156
|
+
mysql_Bin = self.module.Binary
|
|
157
|
+
self.module.Binary = lambda x: mysql_Bin(x).decode(
|
|
158
|
+
'ascii', errors='surrogateescape')
|
|
159
|
+
|
|
160
|
+
self._server_version = None
|
|
161
|
+
self._can_use_microseconds = None
|
|
162
|
+
self._can_use_json_funcs = None
|
|
163
|
+
|
|
164
|
+
DBAPI.__init__(self, **kw)
|
|
165
|
+
|
|
166
|
+
@classmethod
|
|
167
|
+
def _connectionFromParams(cls, user, password, host, port, path, args):
|
|
168
|
+
return cls(db=path.strip('/'),
|
|
169
|
+
user=user or '', password=password or '',
|
|
170
|
+
host=host or 'localhost', port=port, **args)
|
|
171
|
+
|
|
172
|
+
def makeConnection(self):
|
|
173
|
+
dbEncoding = self.dbEncoding
|
|
174
|
+
if dbEncoding:
|
|
175
|
+
if self.driver in ('mysqldb', 'pymysql'):
|
|
176
|
+
from MySQLdb.connections import Connection
|
|
177
|
+
if not hasattr(Connection, 'set_character_set'):
|
|
178
|
+
# monkeypatch pre MySQLdb 1.2.1
|
|
179
|
+
def character_set_name(self):
|
|
180
|
+
return dbEncoding + '_' + dbEncoding
|
|
181
|
+
Connection.character_set_name = character_set_name
|
|
182
|
+
if self.driver in ('connector', 'connector-python'):
|
|
183
|
+
self.kw['consume_results'] = True
|
|
184
|
+
try:
|
|
185
|
+
if self.driver in ('odbc', 'pyodbc', 'pypyodbc'):
|
|
186
|
+
self.debugWriter.write(
|
|
187
|
+
"ODBC connect string: " + self.odbc_conn_str)
|
|
188
|
+
conn = self.module.connect(self.odbc_conn_str)
|
|
189
|
+
else:
|
|
190
|
+
conn = self.module.connect(
|
|
191
|
+
host=self.host, port=self.port, db=self.db,
|
|
192
|
+
user=self.user, passwd=self.password, **self.kw)
|
|
193
|
+
if self.driver == 'mariadb':
|
|
194
|
+
# Attempt to reconnect.
|
|
195
|
+
# This setting is persistent due to ``auto_reconnect``.
|
|
196
|
+
# mariadb doesn't implement ping(True)
|
|
197
|
+
conn.auto_reconnect = True
|
|
198
|
+
conn.ping()
|
|
199
|
+
else:
|
|
200
|
+
# Attempt to reconnect. This setting is persistent.
|
|
201
|
+
conn.ping(True)
|
|
202
|
+
except self.module.OperationalError as e:
|
|
203
|
+
conninfo = ("; used connection string: "
|
|
204
|
+
"host=%(host)s, port=%(port)s, "
|
|
205
|
+
"db=%(db)s, user=%(user)s" % self.__dict__)
|
|
206
|
+
raise dberrors.OperationalError(ErrorMessage(e, conninfo))
|
|
207
|
+
|
|
208
|
+
self._setAutoCommit(conn, bool(self.autoCommit))
|
|
209
|
+
|
|
210
|
+
if dbEncoding:
|
|
211
|
+
if self.driver in ('odbc', 'pyodbc'):
|
|
212
|
+
conn.setdecoding(self.module.SQL_CHAR, encoding=dbEncoding)
|
|
213
|
+
conn.setdecoding(self.module.SQL_WCHAR, encoding=dbEncoding)
|
|
214
|
+
if PY2:
|
|
215
|
+
conn.setencoding(str, encoding=dbEncoding)
|
|
216
|
+
conn.setencoding(unicode, encoding=dbEncoding) # noqa
|
|
217
|
+
else:
|
|
218
|
+
conn.setencoding(encoding=dbEncoding)
|
|
219
|
+
elif hasattr(conn, 'set_character_set'):
|
|
220
|
+
conn.set_character_set(dbEncoding)
|
|
221
|
+
elif hasattr(conn, 'query'):
|
|
222
|
+
# works along with monkeypatching code above
|
|
223
|
+
conn.query("SET NAMES %s" % dbEncoding)
|
|
224
|
+
|
|
225
|
+
return conn
|
|
226
|
+
|
|
227
|
+
def _setAutoCommit(self, conn, auto):
|
|
228
|
+
if hasattr(conn, 'autocommit'):
|
|
229
|
+
try:
|
|
230
|
+
conn.autocommit(auto)
|
|
231
|
+
except TypeError:
|
|
232
|
+
# mysql-connector{-python} has autocommit as a property
|
|
233
|
+
conn.autocommit = auto
|
|
234
|
+
|
|
235
|
+
def _force_reconnect(self, conn):
|
|
236
|
+
if self.driver in ('pymysql',):
|
|
237
|
+
conn.ping(True)
|
|
238
|
+
self._setAutoCommit(conn, bool(self.autoCommit))
|
|
239
|
+
if self.dbEncoding:
|
|
240
|
+
conn.query("SET NAMES %s" % self.dbEncoding)
|
|
241
|
+
|
|
242
|
+
def _executeRetry(self, conn, cursor, query):
|
|
243
|
+
if self.debug:
|
|
244
|
+
self.printDebug(conn, query, 'QueryR')
|
|
245
|
+
dbEncoding = self.dbEncoding
|
|
246
|
+
if dbEncoding and not isinstance(query, bytes) and (
|
|
247
|
+
self.driver in ('mysqldb', 'connector', 'connector-python',
|
|
248
|
+
'mariadb')):
|
|
249
|
+
query = query.encode(dbEncoding, 'surrogateescape')
|
|
250
|
+
# When a server connection is lost and a query is attempted, most of
|
|
251
|
+
# the time the query will raise a SERVER_LOST exception, then at the
|
|
252
|
+
# second attempt to execute it, the mysql lib will reconnect and
|
|
253
|
+
# succeed. However is a few cases, the first attempt raises the
|
|
254
|
+
# SERVER_GONE exception, the second attempt the SERVER_LOST exception
|
|
255
|
+
# and only the third succeeds. Thus the 3 in the loop count.
|
|
256
|
+
# If it doesn't reconnect even after 3 attempts, while the database is
|
|
257
|
+
# up and running, it is because a 5.0.3 (or newer) server is used
|
|
258
|
+
# which no longer permits autoreconnects by default. In that case a
|
|
259
|
+
# reconnect flag must be set when making the connection to indicate
|
|
260
|
+
# that autoreconnecting is desired. In MySQLdb 1.2.2 or newer this is
|
|
261
|
+
# done by calling ping(True) on the connection.
|
|
262
|
+
# [PC]yMySQL need explicit reconnect
|
|
263
|
+
# each time we detect connection timeout.
|
|
264
|
+
for count in range(3):
|
|
265
|
+
try:
|
|
266
|
+
return cursor.execute(query)
|
|
267
|
+
except self.module.OperationalError as e:
|
|
268
|
+
if e.args[0] in (self.CR_SERVER_GONE_ERROR,
|
|
269
|
+
self.CR_SERVER_LOST):
|
|
270
|
+
if count == 2:
|
|
271
|
+
raise dberrors.OperationalError(ErrorMessage(e))
|
|
272
|
+
if self.debug:
|
|
273
|
+
self.printDebug(conn, str(e), 'ERROR')
|
|
274
|
+
if self.driver in ('pymysql',):
|
|
275
|
+
self._force_reconnect(conn)
|
|
276
|
+
else:
|
|
277
|
+
raise dberrors.OperationalError(ErrorMessage(e))
|
|
278
|
+
except self.module.IntegrityError as e:
|
|
279
|
+
msg = ErrorMessage(e)
|
|
280
|
+
if e.args[0] == self.ER_DUP_ENTRY:
|
|
281
|
+
raise dberrors.DuplicateEntryError(msg)
|
|
282
|
+
elif isinstance(e.args[0], str) \
|
|
283
|
+
and e.args[0].startswith('Duplicate'):
|
|
284
|
+
raise dberrors.DuplicateEntryError(msg)
|
|
285
|
+
else:
|
|
286
|
+
raise dberrors.IntegrityError(msg)
|
|
287
|
+
except self.module.InternalError as e:
|
|
288
|
+
raise dberrors.InternalError(ErrorMessage(e))
|
|
289
|
+
except self.module.ProgrammingError as e:
|
|
290
|
+
if e.args[0] is not None:
|
|
291
|
+
raise dberrors.ProgrammingError(ErrorMessage(e))
|
|
292
|
+
except self.module.DataError as e:
|
|
293
|
+
raise dberrors.DataError(ErrorMessage(e))
|
|
294
|
+
except self.module.NotSupportedError as e:
|
|
295
|
+
raise dberrors.NotSupportedError(ErrorMessage(e))
|
|
296
|
+
except self.module.DatabaseError as e:
|
|
297
|
+
raise dberrors.DatabaseError(ErrorMessage(e))
|
|
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
|
+
q = self._insertSQL(table, names, values)
|
|
313
|
+
if self.debug:
|
|
314
|
+
self.printDebug(conn, q, 'QueryIns')
|
|
315
|
+
self._executeRetry(conn, c, q)
|
|
316
|
+
if id is None:
|
|
317
|
+
try:
|
|
318
|
+
id = c.lastrowid
|
|
319
|
+
except AttributeError:
|
|
320
|
+
try:
|
|
321
|
+
id = c.insert_id
|
|
322
|
+
except AttributeError:
|
|
323
|
+
self._executeRetry(conn, c, "SELECT LAST_INSERT_ID();")
|
|
324
|
+
id = c.fetchone()[0]
|
|
325
|
+
else:
|
|
326
|
+
id = c.insert_id()
|
|
327
|
+
c.close()
|
|
328
|
+
if self.debugOutput:
|
|
329
|
+
self.printDebug(conn, id, 'QueryIns', 'result')
|
|
330
|
+
return id
|
|
331
|
+
|
|
332
|
+
@classmethod
|
|
333
|
+
def _queryAddLimitOffset(cls, query, start, end):
|
|
334
|
+
if not start:
|
|
335
|
+
return "%s LIMIT %i" % (query, end)
|
|
336
|
+
if not end:
|
|
337
|
+
return "%s LIMIT %i, -1" % (query, start)
|
|
338
|
+
return "%s LIMIT %i, %i" % (query, start, end - start)
|
|
339
|
+
|
|
340
|
+
def createReferenceConstraint(self, soClass, col):
|
|
341
|
+
return col.mysqlCreateReferenceConstraint()
|
|
342
|
+
|
|
343
|
+
def createColumn(self, soClass, col):
|
|
344
|
+
return col.mysqlCreateSQL(self)
|
|
345
|
+
|
|
346
|
+
def createIndexSQL(self, soClass, index):
|
|
347
|
+
return index.mysqlCreateIndexSQL(soClass)
|
|
348
|
+
|
|
349
|
+
def createIDColumn(self, soClass):
|
|
350
|
+
if soClass.sqlmeta.idType is str:
|
|
351
|
+
return '%s TEXT PRIMARY KEY' % soClass.sqlmeta.idName
|
|
352
|
+
if soClass.sqlmeta.idType is not int:
|
|
353
|
+
raise TypeError('sqlmeta.idType must be int or str, not %r'
|
|
354
|
+
% soClass.sqlmeta.idType)
|
|
355
|
+
if soClass.sqlmeta.idSize is None:
|
|
356
|
+
mysql_int_type = 'INT'
|
|
357
|
+
elif soClass.sqlmeta.idSize in ('TINY', 'SMALL', 'MEDIUM', 'BIG'):
|
|
358
|
+
mysql_int_type = '%sINT' % soClass.sqlmeta.idSize
|
|
359
|
+
else:
|
|
360
|
+
raise ValueError(
|
|
361
|
+
"sqlmeta.idSize must be 'TINY', 'SMALL', 'MEDIUM', 'BIG' "
|
|
362
|
+
"or None, not %r" % soClass.sqlmeta.idSize)
|
|
363
|
+
return '%s %s PRIMARY KEY AUTO_INCREMENT' \
|
|
364
|
+
% (soClass.sqlmeta.idName, mysql_int_type)
|
|
365
|
+
|
|
366
|
+
def joinSQLType(self, join):
|
|
367
|
+
return 'INT NOT NULL'
|
|
368
|
+
|
|
369
|
+
def tableExists(self, tableName):
|
|
370
|
+
try:
|
|
371
|
+
# Use DESCRIBE instead of SHOW TABLES because SHOW TABLES
|
|
372
|
+
# assumes there is a default database selected
|
|
373
|
+
# which is not always True (for an embedded application, e.g.)
|
|
374
|
+
self.query('DESCRIBE %s' % (tableName))
|
|
375
|
+
return True
|
|
376
|
+
except dberrors.ProgrammingError as e:
|
|
377
|
+
if e.args[0].code in (1146, '42S02'): # ER_NO_SUCH_TABLE
|
|
378
|
+
return False
|
|
379
|
+
if self.driver == 'mariadb':
|
|
380
|
+
return False
|
|
381
|
+
raise
|
|
382
|
+
|
|
383
|
+
def addColumn(self, tableName, column):
|
|
384
|
+
self.query('ALTER TABLE %s ADD COLUMN %s' %
|
|
385
|
+
(tableName,
|
|
386
|
+
column.mysqlCreateSQL(self)))
|
|
387
|
+
|
|
388
|
+
def delColumn(self, sqlmeta, column):
|
|
389
|
+
self.query('ALTER TABLE %s DROP COLUMN %s' % (sqlmeta.table,
|
|
390
|
+
column.dbName))
|
|
391
|
+
|
|
392
|
+
def columnsFromSchema(self, tableName, soClass):
|
|
393
|
+
colData = self.queryAll("SHOW COLUMNS FROM %s"
|
|
394
|
+
% tableName)
|
|
395
|
+
results = []
|
|
396
|
+
for field, t, nullAllowed, key, default, extra in colData:
|
|
397
|
+
if field == soClass.sqlmeta.idName:
|
|
398
|
+
continue
|
|
399
|
+
colClass, kw = self.guessClass(t)
|
|
400
|
+
if self.kw.get('use_unicode') and colClass is col.StringCol:
|
|
401
|
+
colClass = col.UnicodeCol
|
|
402
|
+
if self.dbEncoding:
|
|
403
|
+
kw['dbEncoding'] = self.dbEncoding
|
|
404
|
+
kw['name'] = soClass.sqlmeta.style.dbColumnToPythonAttr(field)
|
|
405
|
+
kw['dbName'] = field
|
|
406
|
+
|
|
407
|
+
# Since MySQL 5.0, 'NO' is returned in the NULL column
|
|
408
|
+
# (SQLObject expected '')
|
|
409
|
+
kw['notNone'] = (nullAllowed.upper() != 'YES' and True or False)
|
|
410
|
+
|
|
411
|
+
if not PY2 and isinstance(t, bytes):
|
|
412
|
+
t = t.decode('ascii')
|
|
413
|
+
|
|
414
|
+
if default and t.startswith('int'):
|
|
415
|
+
kw['default'] = int(default)
|
|
416
|
+
elif default and t.startswith('float'):
|
|
417
|
+
kw['default'] = float(default)
|
|
418
|
+
elif default == 'CURRENT_TIMESTAMP' and t == 'timestamp':
|
|
419
|
+
kw['default'] = None
|
|
420
|
+
elif default and colClass is col.BoolCol:
|
|
421
|
+
kw['default'] = int(default) and True or False
|
|
422
|
+
else:
|
|
423
|
+
kw['default'] = default
|
|
424
|
+
# @@ skip key...
|
|
425
|
+
# @@ skip extra...
|
|
426
|
+
results.append(colClass(**kw))
|
|
427
|
+
return results
|
|
428
|
+
|
|
429
|
+
def guessClass(self, t):
|
|
430
|
+
if not PY2 and isinstance(t, bytes):
|
|
431
|
+
t = t.decode('ascii')
|
|
432
|
+
if t.startswith('int'):
|
|
433
|
+
return col.IntCol, {}
|
|
434
|
+
elif t.startswith('enum'):
|
|
435
|
+
values = []
|
|
436
|
+
for i in t[5:-1].split(','): # take the enum() off and split
|
|
437
|
+
values.append(i[1:-1]) # remove the surrounding \'
|
|
438
|
+
return col.EnumCol, {'enumValues': values}
|
|
439
|
+
elif t.startswith('double'):
|
|
440
|
+
return col.FloatCol, {}
|
|
441
|
+
elif t.startswith('varchar'):
|
|
442
|
+
colType = col.StringCol
|
|
443
|
+
if self.kw.get('use_unicode', False):
|
|
444
|
+
colType = col.UnicodeCol
|
|
445
|
+
if t.endswith('binary'):
|
|
446
|
+
return colType, {'length': int(t[8:-8]),
|
|
447
|
+
'char_binary': True}
|
|
448
|
+
else:
|
|
449
|
+
return colType, {'length': int(t[8:-1])}
|
|
450
|
+
elif t.startswith('char'):
|
|
451
|
+
if t.endswith('binary'):
|
|
452
|
+
return col.StringCol, {'length': int(t[5:-8]),
|
|
453
|
+
'varchar': False,
|
|
454
|
+
'char_binary': True}
|
|
455
|
+
else:
|
|
456
|
+
return col.StringCol, {'length': int(t[5:-1]),
|
|
457
|
+
'varchar': False}
|
|
458
|
+
elif t.startswith('datetime'):
|
|
459
|
+
return col.DateTimeCol, {}
|
|
460
|
+
elif t.startswith('date'):
|
|
461
|
+
return col.DateCol, {}
|
|
462
|
+
elif t.startswith('time'):
|
|
463
|
+
return col.TimeCol, {}
|
|
464
|
+
elif t.startswith('timestamp'):
|
|
465
|
+
return col.TimestampCol, {}
|
|
466
|
+
elif t.startswith('bool'):
|
|
467
|
+
return col.BoolCol, {}
|
|
468
|
+
elif t.startswith('tinyblob'):
|
|
469
|
+
return col.BLOBCol, {"length": 2 ** 8 - 1}
|
|
470
|
+
elif t.startswith('tinytext'):
|
|
471
|
+
return col.StringCol, {"length": 2 ** 8 - 1, "varchar": True}
|
|
472
|
+
elif t.startswith('blob'):
|
|
473
|
+
return col.BLOBCol, {"length": 2 ** 16 - 1}
|
|
474
|
+
elif t.startswith('text'):
|
|
475
|
+
return col.StringCol, {"length": 2 ** 16 - 1, "varchar": True}
|
|
476
|
+
elif t.startswith('mediumblob'):
|
|
477
|
+
return col.BLOBCol, {"length": 2 ** 24 - 1}
|
|
478
|
+
elif t.startswith('mediumtext'):
|
|
479
|
+
return col.StringCol, {"length": 2 ** 24 - 1, "varchar": True}
|
|
480
|
+
elif t.startswith('longblob'):
|
|
481
|
+
return col.BLOBCol, {"length": 2 ** 32}
|
|
482
|
+
elif t.startswith('longtext'):
|
|
483
|
+
return col.StringCol, {"length": 2 ** 32, "varchar": True}
|
|
484
|
+
else:
|
|
485
|
+
return col.Col, {}
|
|
486
|
+
|
|
487
|
+
def listTables(self):
|
|
488
|
+
return _decodeBytearrays(self.queryAll("SHOW TABLES"))
|
|
489
|
+
|
|
490
|
+
def listDatabases(self):
|
|
491
|
+
return _decodeBytearrays(self.queryAll("SHOW DATABASES"))
|
|
492
|
+
|
|
493
|
+
def _createOrDropDatabase(self, op="CREATE"):
|
|
494
|
+
self.query('%s DATABASE %s' % (op, self.db))
|
|
495
|
+
|
|
496
|
+
def createEmptyDatabase(self):
|
|
497
|
+
self._createOrDropDatabase()
|
|
498
|
+
|
|
499
|
+
def dropDatabase(self):
|
|
500
|
+
self._createOrDropDatabase(op="DROP")
|
|
501
|
+
|
|
502
|
+
def server_version(self):
|
|
503
|
+
if self._server_version is not None:
|
|
504
|
+
return self._server_version
|
|
505
|
+
try:
|
|
506
|
+
server_version = self.queryOne("SELECT VERSION()")[0]
|
|
507
|
+
server_version = server_version.split('-', 1)
|
|
508
|
+
db_tag = "MySQL"
|
|
509
|
+
if len(server_version) == 2:
|
|
510
|
+
if "MariaDB" in server_version[1]:
|
|
511
|
+
db_tag = "MariaDB"
|
|
512
|
+
server_version = server_version[0]
|
|
513
|
+
server_version = tuple(int(v) for v in server_version.split('.'))
|
|
514
|
+
server_version = (server_version, db_tag)
|
|
515
|
+
except Exception:
|
|
516
|
+
server_version = None # unknown
|
|
517
|
+
self._server_version = server_version
|
|
518
|
+
return server_version
|
|
519
|
+
|
|
520
|
+
def can_use_microseconds(self):
|
|
521
|
+
if self._can_use_microseconds is not None:
|
|
522
|
+
return self._can_use_microseconds
|
|
523
|
+
server_version = self.server_version()
|
|
524
|
+
if server_version is None:
|
|
525
|
+
return None
|
|
526
|
+
server_version, db_tag = server_version
|
|
527
|
+
if db_tag == "MariaDB":
|
|
528
|
+
can_use_microseconds = (server_version >= (5, 3, 0))
|
|
529
|
+
else: # MySQL
|
|
530
|
+
can_use_microseconds = (server_version >= (5, 6, 4))
|
|
531
|
+
self._can_use_microseconds = can_use_microseconds
|
|
532
|
+
return can_use_microseconds
|
|
533
|
+
|
|
534
|
+
def can_use_json_funcs(self):
|
|
535
|
+
if self._can_use_json_funcs is not None:
|
|
536
|
+
return self._can_use_json_funcs
|
|
537
|
+
server_version = self.server_version()
|
|
538
|
+
if server_version is None:
|
|
539
|
+
return None
|
|
540
|
+
server_version, db_tag = server_version
|
|
541
|
+
if db_tag == "MariaDB":
|
|
542
|
+
can_use_json_funcs = (server_version >= (10, 2, 7))
|
|
543
|
+
else: # MySQL
|
|
544
|
+
can_use_json_funcs = (server_version >= (5, 7, 0))
|
|
545
|
+
self._can_use_json_funcs = can_use_json_funcs
|
|
546
|
+
return can_use_json_funcs
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def ConnectorBytesConverter(value, db):
|
|
550
|
+
if not PY2:
|
|
551
|
+
# For PY2 this converter is called also for SQLite
|
|
552
|
+
assert db == 'mysql'
|
|
553
|
+
value = value.decode('latin1')
|
|
554
|
+
return StringLikeConverter(value, db)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def _decodeBytearrays(v_list):
|
|
558
|
+
if not v_list:
|
|
559
|
+
return []
|
|
560
|
+
if not PY2 and isinstance(v_list[0][0], bytearray):
|
|
561
|
+
return [v[0].decode('ascii') for v in v_list]
|
|
562
|
+
return [v[0] for v in v_list]
|