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.
Files changed (149) hide show
  1. sqlobject/__init__.py +15 -0
  2. sqlobject/__version__.py +8 -0
  3. sqlobject/boundattributes.py +124 -0
  4. sqlobject/cache.py +376 -0
  5. sqlobject/classregistry.py +138 -0
  6. sqlobject/col.py +2187 -0
  7. sqlobject/compat.py +59 -0
  8. sqlobject/conftest.py +64 -0
  9. sqlobject/constraints.py +77 -0
  10. sqlobject/converters.py +278 -0
  11. sqlobject/dbconnection.py +1137 -0
  12. sqlobject/dberrors.py +54 -0
  13. sqlobject/declarative.py +211 -0
  14. sqlobject/events.py +365 -0
  15. sqlobject/firebird/__init__.py +8 -0
  16. sqlobject/firebird/firebirdconnection.py +463 -0
  17. sqlobject/include/__init__.py +1 -0
  18. sqlobject/include/hashcol.py +109 -0
  19. sqlobject/include/tests/__init__.py +1 -0
  20. sqlobject/include/tests/test_hashcol.py +84 -0
  21. sqlobject/index.py +189 -0
  22. sqlobject/inheritance/__init__.py +551 -0
  23. sqlobject/inheritance/iteration.py +106 -0
  24. sqlobject/inheritance/tests/__init__.py +1 -0
  25. sqlobject/inheritance/tests/test_aggregates.py +23 -0
  26. sqlobject/inheritance/tests/test_asdict.py +48 -0
  27. sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
  28. sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
  29. sqlobject/inheritance/tests/test_foreignKey.py +94 -0
  30. sqlobject/inheritance/tests/test_indexes.py +42 -0
  31. sqlobject/inheritance/tests/test_inheritance.py +164 -0
  32. sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
  33. sqlobject/joins.py +656 -0
  34. sqlobject/main.py +1880 -0
  35. sqlobject/manager/__init__.py +1 -0
  36. sqlobject/manager/command.py +1309 -0
  37. sqlobject/maxdb/__init__.py +8 -0
  38. sqlobject/maxdb/maxdbconnection.py +320 -0
  39. sqlobject/maxdb/readme.txt +27 -0
  40. sqlobject/mssql/__init__.py +8 -0
  41. sqlobject/mssql/mssqlconnection.py +417 -0
  42. sqlobject/mysql/__init__.py +8 -0
  43. sqlobject/mysql/mysqlconnection.py +562 -0
  44. sqlobject/postgres/__init__.py +8 -0
  45. sqlobject/postgres/pgconnection.py +619 -0
  46. sqlobject/sqlbuilder.py +1655 -0
  47. sqlobject/sqlite/__init__.py +8 -0
  48. sqlobject/sqlite/sqliteconnection.py +418 -0
  49. sqlobject/sresults.py +377 -0
  50. sqlobject/styles.py +174 -0
  51. sqlobject/sybase/__init__.py +8 -0
  52. sqlobject/sybase/sybaseconnection.py +183 -0
  53. sqlobject/tests/__init__.py +1 -0
  54. sqlobject/tests/dbtest.py +363 -0
  55. sqlobject/tests/test_ForeignKey.py +133 -0
  56. sqlobject/tests/test_ForeignKey_cascade.py +150 -0
  57. sqlobject/tests/test_NoneValuedResultItem.py +33 -0
  58. sqlobject/tests/test_SQLMultipleJoin.py +65 -0
  59. sqlobject/tests/test_SQLRelatedJoin.py +114 -0
  60. sqlobject/tests/test_SingleJoin.py +30 -0
  61. sqlobject/tests/test_aggregates.py +75 -0
  62. sqlobject/tests/test_aliases.py +49 -0
  63. sqlobject/tests/test_asdict.py +18 -0
  64. sqlobject/tests/test_auto.py +218 -0
  65. sqlobject/tests/test_basic.py +359 -0
  66. sqlobject/tests/test_blob.py +34 -0
  67. sqlobject/tests/test_boundattributes.py +67 -0
  68. sqlobject/tests/test_cache.py +57 -0
  69. sqlobject/tests/test_class_hash.py +22 -0
  70. sqlobject/tests/test_columns_order.py +18 -0
  71. sqlobject/tests/test_combining_joins.py +47 -0
  72. sqlobject/tests/test_comparison.py +24 -0
  73. sqlobject/tests/test_compat.py +10 -0
  74. sqlobject/tests/test_complex_sorting.py +77 -0
  75. sqlobject/tests/test_constraints.py +43 -0
  76. sqlobject/tests/test_converters.py +276 -0
  77. sqlobject/tests/test_create_drop.py +29 -0
  78. sqlobject/tests/test_csvexport.py +101 -0
  79. sqlobject/tests/test_csvimport.py +21 -0
  80. sqlobject/tests/test_cyclic_reference.py +55 -0
  81. sqlobject/tests/test_datetime.py +171 -0
  82. sqlobject/tests/test_dbconnection.py +17 -0
  83. sqlobject/tests/test_decimal.py +63 -0
  84. sqlobject/tests/test_declarative.py +83 -0
  85. sqlobject/tests/test_default_style.py +65 -0
  86. sqlobject/tests/test_delete.py +74 -0
  87. sqlobject/tests/test_distinct.py +36 -0
  88. sqlobject/tests/test_empty.py +22 -0
  89. sqlobject/tests/test_enum.py +75 -0
  90. sqlobject/tests/test_events.py +142 -0
  91. sqlobject/tests/test_exceptions.py +40 -0
  92. sqlobject/tests/test_expire.py +29 -0
  93. sqlobject/tests/test_groupBy.py +45 -0
  94. sqlobject/tests/test_identity.py +29 -0
  95. sqlobject/tests/test_indexes.py +100 -0
  96. sqlobject/tests/test_inheritance.py +35 -0
  97. sqlobject/tests/test_joins.py +160 -0
  98. sqlobject/tests/test_joins_conditional.py +143 -0
  99. sqlobject/tests/test_jsonbcol.py +53 -0
  100. sqlobject/tests/test_jsoncol.py +56 -0
  101. sqlobject/tests/test_lazy.py +165 -0
  102. sqlobject/tests/test_md5.py +13 -0
  103. sqlobject/tests/test_mysql.py +50 -0
  104. sqlobject/tests/test_new_joins.py +165 -0
  105. sqlobject/tests/test_parse_uri.py +147 -0
  106. sqlobject/tests/test_paste.py +92 -0
  107. sqlobject/tests/test_perConnection.py +20 -0
  108. sqlobject/tests/test_pickle.py +42 -0
  109. sqlobject/tests/test_picklecol.py +44 -0
  110. sqlobject/tests/test_postgres.py +82 -0
  111. sqlobject/tests/test_reparent_sqlmeta.py +33 -0
  112. sqlobject/tests/test_schema.py +30 -0
  113. sqlobject/tests/test_select.py +218 -0
  114. sqlobject/tests/test_select_through.py +63 -0
  115. sqlobject/tests/test_setters.py +23 -0
  116. sqlobject/tests/test_slice.py +59 -0
  117. sqlobject/tests/test_sorting.py +80 -0
  118. sqlobject/tests/test_sqlbuilder.py +134 -0
  119. sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
  120. sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
  121. sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
  122. sqlobject/tests/test_sqlite.py +160 -0
  123. sqlobject/tests/test_sqlmeta_idName.py +52 -0
  124. sqlobject/tests/test_sqlobject_admin.py +38 -0
  125. sqlobject/tests/test_string_id.py +79 -0
  126. sqlobject/tests/test_style.py +36 -0
  127. sqlobject/tests/test_subqueries.py +142 -0
  128. sqlobject/tests/test_transactions.py +122 -0
  129. sqlobject/tests/test_unicode.py +84 -0
  130. sqlobject/tests/test_uuidcol.py +40 -0
  131. sqlobject/tests/test_validation.py +125 -0
  132. sqlobject/tests/test_views.py +159 -0
  133. sqlobject/util/__init__.py +1 -0
  134. sqlobject/util/csvexport.py +210 -0
  135. sqlobject/util/csvimport.py +366 -0
  136. sqlobject/util/moduleloader.py +35 -0
  137. sqlobject/util/threadinglocal.py +6 -0
  138. sqlobject/versioning/__init__.py +120 -0
  139. sqlobject/versioning/test/__init__.py +0 -0
  140. sqlobject/versioning/test/test_version.py +183 -0
  141. sqlobject/views.py +151 -0
  142. sqlobject/wsgi_middleware.py +109 -0
  143. sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
  144. sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
  145. sqlobject-3.13.1.dist-info/METADATA +177 -0
  146. sqlobject-3.13.1.dist-info/RECORD +149 -0
  147. sqlobject-3.13.1.dist-info/WHEEL +6 -0
  148. sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
  149. 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]
@@ -0,0 +1,8 @@
1
+ from sqlobject.dbconnection import registerConnection
2
+
3
+
4
+ def builder():
5
+ from . import pgconnection
6
+ return pgconnection.PostgresConnection
7
+
8
+ registerConnection(['postgres', 'postgresql', 'psycopg'], builder)