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,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