1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 __docformat__ = "epytext en"
87
88 """
89 This datasource module defines a datasource class for PostgreSQL databases.
90 It relies on the psycopg Module to connect to the backend. See
91
92 - Website: U{http://initd.org/software/initd/psycopg}
93 - Download: U{http://initd.org/pub/software/psycopg/psycopg-1.0.12.tar.gz }
94
95 for details.
96 """
97
98
99 import sys, re
100 from types import *
101 from string import *
102
103
104 try:
105 import psycopg
106 except ImportError:
107 import psycopg2 as psycopg
108
109
110 from orm2.debug import sqllog, debug
111 from orm2.exceptions import *
112 from orm2 import sql
113 import orm2.datasource
114 from orm2.util import stupid_dict
115
116 from orm2.datatypes import common_serial
117 import datatypes
118
119 _typeoid = {}
120
121 -class datasource(orm2.datasource.datasource_base):
122 escaped_chars = [ ("\\", "\\\\\\\\"), ("'", "\\'"), ('"', '\\"'),
123 ("\0", "\\\\000"), ("`", "\\`"), ("´", "\\´"),
124 ("\n", "\\n"), ("\r", "\\r"), ("\t", "\\t"),
125 ("%", "\\045",), ("?", "\\077",), ]
126
127 _dbfailures = 0
128 _ERRORS_BEFORE_RECONNECT = 50
129
130
131
132 encodings = \
133 { "SQL_ASCII": "ascii",
134 "EUC_JP": "?",
135 "EUC_CN": "?",
136 "EUC_KR": "?",
137 "JOHAB": "?",
138 "EUC_TW": "?",
139 "UNICODE": "utf-8",
140 "UTF8": "utf-8",
141 "MULE_INTERNAL": "?",
142 "LATIN1": "iso-8859-1",
143 "LATIN2": "iso-8859-2",
144 "LATIN3": "iso-8859-3",
145 "LATIN4": "iso-8859-4",
146 "LATIN5": "iso-8859-9",
147 "LATIN6": "iso-8859-10",
148 "LATIN7": "iso-8859-13",
149 "LATIN8": "iso-8859-14",
150 "LATIN9": "iso-8859-15",
151 "LATIN10": "iso-8859-16",
152
153 "ISO_8859_5": "iso-8859-5",
154 "ISO_8859_6": "iso-8859-6",
155 "ISO_8859_7": "iso-8859-7",
156 "ISO_8859_8": "iso-8859-8",
157 "KOI8": "?",
158 "WIN": "?",
159 "ALT": "?",
160 "WIN1256": "?",
161 "TCVN": "?",
162 "WIN874": "?"
163 }
164
166 """
167 DSN - PostgreSLQ dsn it's basically a string like
168 'field1=value field2=value' fields are describe below (from the
169 pgsql Programmer's Manual):
170
171 host
172 ====
173 Name of host to connect to. If this begins with a slash, it
174 specifies Unix-domain communication rather than TCP/IP
175 communication; the value bis the name of the directory in which
176 the socket file is stored. The default is to connect to a
177 Unix-domain socket in /tmp. hostaddr
178
179 IP address of host to connect to. This should be in standard
180 numbers-and-dots form, as used by the BSD functions inet_aton
181 et al. If a nonzero-length string is specified, TCP/IP
182 communication is used.
183
184 Using hostaddr instead of host allows the application to avoid
185 a host name look-up, which may be important in applications
186 with time constraints. However, Kerberos authentication
187 requires the host name. The following therefore applies. If
188 host is specified without hostaddr, a host name lookup is
189 forced. If hostaddr is specified without host, the value for
190 hostaddr gives the remote address; if Kerberos is used, this
191 causes a reverse name query. If both host and hostaddr are
192 specified, the value for hostaddr gives the remote address;
193 the value for host is ignored, unless Kerberos is used, in
194 which case that value is used for Kerberos
195 authentication. Note that authentication is likely to fail if
196 libpq is passed a host name that is not the name of the
197 machine at hostaddr.
198
199 Without either a host name or host address, libpq will connect
200 using a local Unix domain socket. port
201
202 Port number to connect to at the server host, or socket file
203 name extension for Unix-domain connections.
204
205 dbname
206 ======
207 The database name.
208
209 user
210 ====
211 User name to connect as.
212
213 password
214 ========
215 Password to be used if the server demands password authentication.
216
217 options
218 =======
219 Trace/debug options to be sent to the server.
220
221 tty
222 ===
223 A file or tty for optional debug output from the backend.
224
225 requiressl
226 ==========
227 Set to 1 to require SSL connection to the backend. Libpq will
228 then refuse to connect if the server does not support SSL. Set
229 to 0 (default) to negotiate with server.
230
231 example= 'host=localhost dbname=test user=test'
232 """
233 orm2.datasource.datasource_base.__init__(self)
234
235 self.psycopg_version = psycopg.__version__
236
237 self._dsn = dsn
238 self._encoding = None
239 self.connect()
240
242 """
243 A pgsql connection string may contain all the standard ORM keywords
244 plus all of those described above in the __init__ method's
245 doc
246 """
247 if params.has_key("db"):
248 dbname = params["db"]
249 del params["db"]
250 params["dbname"] = dbname
251
252 dsn = []
253 for a in params.items():
254 dsn.append("%s=%s" % a)
255
256 return datasource(join(dsn, " "))
257 from_params = staticmethod(_from_params)
258
260 ds = datasource(None)
261
262 ds._conn = conn
263 ds._update_cursor = ds._conn.cursor()
264
265 return ds
266
267 from_connection = staticmethod(_from_connection)
268
270 """
271 For each of the custom types register the built-in str class
272 as the psycopg cast class so the SQL literal the pgsql backend
273 returns can be parsed into an appropriate Python datastucture
274 by our datatype and column class
275 """
276 if datatype_class.sql_name:
277 type_name = datatype_class.sql_name
278 else:
279 type_name = datatype_class.__name__
280
281 if not _typeoid.has_key(type_name):
282
283
284
285 query = "SELECT %s FROM %s WHERE 0=1" % (column, relation)
286 cursor = self.execute(query)
287 type_oid = cursor.description[0][1]
288
289
290
291 TYPE = psycopg.new_type((type_oid,),
292 upper(type_name), str)
293 psycopg.register_type(TYPE)
294
295
296 _typeoid[type_name] = type_oid
297
299 """
300 Return the DSN this datasource has been initialized with.
301 """
302 return self._dsn
303
304 - def execute(self, query, modify=False):
305 """
306 Run a query on the database connection.
307
308 This function also performs failure accounting and will
309 re-connect to the database if a certain threshold has passed.
310 """
311 if type(query) == UnicodeType:
312 query = query.encode(self.backend_encoding())
313 try:
314 cursor = orm2.datasource.datasource_base.execute(self, query,
315 modify)
316 except psycopg.ProgrammingError, err:
317
318
319
320 self.rollback()
321
322 error_message = str(err)
323 if "duplicate key" in error_message:
324 raise DuplicateKey(error_message, err)
325 else:
326 raise BackendError(error_message, err)
327
328 except psycopg.Error, err:
329
330
331 if self._dsn is None:
332 raise
333
334 self._dbfailures += 1
335 if self._dbfailures > self._ERRORS_BEFORE_RECONNECT:
336 self._dbfailures = 0
337 try:
338 del self._update_cursor
339 del self._conn
340
341 self.connect()
342 except:
343 raise sys.exc_type, sys.exc_value, sys.exc_traceback
344
345 cursor.execute(query)
346 else:
347 raise sys.exc_type, sys.exc_value, sys.exc_traceback
348
349 return cursor
350
352 if self._dsn is not None:
353 self._conn = psycopg.connect(self._dsn)
354 self._update_cursor = self._conn.cursor()
355
356
358
359 cursor = self.cursor()
360 cursor.execute("SELECT version()")
361 result = cursor.fetchone()
362 version_info = result[0]
363
364 version_re = re.compile(r"PostgreSQL (\d)\.(\d)\.(\d) .*")
365 result = version_re.findall(version_info)
366 version = result[0]
367 version = map(int, version)
368 version = tuple(version)
369
370 return version
371
373 if self._encoding is None:
374
375
376 query = """SELECT pg_catalog.pg_encoding_to_char(encoding)
377 FROM pg_catalog.pg_database
378 WHERE datname = current_database()"""
379 cursor = self.cursor()
380 cursor.execute(query)
381 result = cursor.fetchone()
382 encoding = result[0]
383
384
385
386 if not self.encodings.has_key(encoding):
387 msg = "Unknown backend encoding %s. Most " +\
388 "probably you're running a version of PostgreSQL a " +\
389 "lot younger than your version of orm. You might want "+\
390 "to update or modify orm2/adapters/pgsql/datasource.py"+\
391 "to include the encoding in its encodings dict"
392 msg = msg % encoding
393 raise InternalError(msg)
394
395 self._encoding = self.encodings[encoding]
396
397 return self._encoding
398
400 if dbobj.__primary_key__ is None: raise PrimaryKeyNotKnown()
401
402 primary_key_attributes = tuple(dbobj.__primary_key__.attributes())
403
404 if len(primary_key_attributes) == 1:
405 primary_key_attribute = primary_key_attributes[0]
406 else:
407 primary_key_attribute = None
408
409 if isinstance(primary_key_attribute, datatypes.serial):
410
411
412 if primary_key_attribute.sequence_name is None:
413 if dbobj.__relation__.schema is not None:
414
415
416
417
418 relation = "%s.%s" % ( dbobj.__relation__.schema,
419 dbobj.__relation__.name.name, )
420 else:
421 relation = dbobj.__relation__.name
422
423 sequence_name = "%s_%s_seq" % ( relation,
424 primary_key_attribute.column, )
425 else:
426 sequence_name = primary_key_attribute.sequence_name
427
428 where = sql.where(sql.expression( primary_key_attribute.column,
429 " = ",
430 "currval('%s')" % (sequence_name, ) ))
431
432 elif isinstance(primary_key_attribute, common_serial):
433 where = sql.where(sql.expression( "id = ",
434 "currval('%s_id_seq')" % dbobj.__relation__ ))
435
436 elif dbobj.__primary_key__.isset():
437
438
439 where = dbobj.__primary_key__.where()
440
441 else:
442 raise PrimaryKeyNotKnown()
443
444 return where
445
456
458 if self._conn is None:
459 zpsycopg_obj = self.context.restrictedTraverse(
460 self.ds_name)
461
462 zope_conn = zpsycopg_obj()
463 if hasattr(zope_conn, "getconn"):
464 self._conn = zope_conn.getconn()
465 else:
466 self._conn = zope_conn.db
467
468 self._conn.rollback()
469
470 return self._conn
471
472 - def execute(self, query, modify=False):
473 try:
474 cursor = orm2.datasource.datasource_base.execute(
475 self, query, modify)
476
477 except psycopg.ProgrammingError, err:
478
479
480
481 self.rollback()
482
483 error_message = str(err)
484 if "duplicate key" in error_message:
485 raise DuplicateKey(error_message, err)
486 else:
487 raise BackendError(error_message, err)
488
489 except psycopg.Error, err:
490 self._dbfailures += 1
491 if self._dbfailures < self._ERRORS_BEFORE_RECONNECT:
492 try:
493 if hasattr(self, "_update_cursor"):
494 del self._update_cursor
495 if hasattr(self, "db"):
496 del self._conn.db
497 except:
498 raise sys.exc_type, sys.exc_value, sys.exc_traceback
499
500 self.execute(query, modify)
501 else:
502 raise sys.exc_type, sys.exc_value, sys.exc_traceback
503
504 return cursor
505
507 """
508 Undo the changes you made to the database since the last commit()
509 """
510 self._updates = stupid_dict()
511 db = getattr(self._conn, "db", None)
512 if db is not None:
513 db.rollback()
514