Package orm2 :: Package adapters :: Package pgsql :: Module datasource
[hide private]
[frames] | no frames]

Source Code for Module orm2.adapters.pgsql.datasource

  1  #!/usr/bin/env python 
  2  # -*- coding: iso-8859-1 -*- 
  3   
  4  ##  This file is part of orm, The Object Relational Membrane Version 2. 
  5  ## 
  6  ##  Copyright 2002-2006 by Diedrich Vorberg <diedrich@tux4web.de> 
  7  ## 
  8  ##  All Rights Reserved 
  9  ## 
 10  ##  For more Information on orm see the README file. 
 11  ## 
 12  ##  This program is free software; you can redistribute it and/or modify 
 13  ##  it under the terms of the GNU General Public License as published by 
 14  ##  the Free Software Foundation; either version 2 of the License, or 
 15  ##  (at your option) any later version. 
 16  ## 
 17  ##  This program is distributed in the hope that it will be useful, 
 18  ##  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 19  ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 20  ##  GNU General Public License for more details. 
 21  ## 
 22  ##  You should have received a copy of the GNU General Public License 
 23  ##  along with this program; if not, write to the Free Software 
 24  ##  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 25  ## 
 26  ##  I have added a copy of the GPL in the file COPYING 
 27   
 28  # Changelog 
 29  # --------- 
 30  # 
 31  # $Log: datasource.py,v $ 
 32  # Revision 1.16  2006/10/07 22:05:51  diedrich 
 33  # Added psycopg_version attribute. 
 34  # 
 35  # Revision 1.15  2006/09/19 14:25:25  diedrich 
 36  # Added preliminary support for psycopg2 (Zope) 
 37  # 
 38  # Revision 1.14  2006/09/05 16:54:00  diedrich 
 39  # Of course, Python's database API doesn't support ? as a placeholder 
 40  # but %s. This also means that all %s must be escaped in input SQL. This 
 41  # remains untested for firebird. 
 42  # 
 43  # Revision 1.13  2006/07/08 17:10:20  diedrich 
 44  # Added call rollback() before a db connection provided by Zope is used 
 45  # 
 46  # Revision 1.12  2006/07/04 22:47:51  diedrich 
 47  # Fixed running within Zope. 
 48  # 
 49  # Revision 1.11  2006/06/12 08:15:55  diedrich 
 50  # self._updates is not a list but a stupid_dict 
 51  # 
 52  # Revision 1.9  2006/06/09 15:37:06  diedrich 
 53  # Changed generation of default sequence names to incorporate schema names 
 54  # 
 55  # Revision 1.8  2006/06/09 09:08:08  diedrich 
 56  # Throw an exception if the backend's encoding is not known 
 57  # 
 58  # Revision 1.7  2006/05/13 17:23:41  diedrich 
 59  # Massive docstring update. 
 60  # 
 61  # Revision 1.6  2006/05/02 13:32:43  diedrich 
 62  # select_after_insert_where() check's for the dbclass' __primary_key__ 
 63  # not being None 
 64  # 
 65  # Revision 1.5  2006/04/28 09:49:26  diedrich 
 66  # Docstring updates for epydoc 
 67  # 
 68  # Revision 1.4  2006/02/25 00:20:20  diedrich 
 69  # - Added and tested the ability to use multiple column primary keys. 
 70  # - Some small misc bugs. 
 71  # 
 72  # Revision 1.3  2005/12/31 10:03:35  diedrich 
 73  # - Added accomodation for the common_serial datatype 
 74  # 
 75  # Revision 1.2  2005/12/18 22:35:46  diedrich 
 76  # - Inheritance 
 77  # - pgsql adapter 
 78  # - first unit tests 
 79  # - some more comments 
 80  # 
 81  # Revision 1.1.1.1  2005/11/20 14:55:46  diedrich 
 82  # Initial import 
 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  # Python 
 99  import sys, re 
100  from types import * 
101  from string import * 
102   
103  # Postgres 
104  try: 
105      import psycopg 
106  except ImportError: 
107      import psycopg2 as psycopg 
108   
109  # orm 
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 # Map PostgreSQL to Python encoding names. (From the PostgreSQL 131 # documentation) 132 encodings = \ 133 { "SQL_ASCII": "ascii", # ASCII 134 "EUC_JP": "?", # Japanese EUC 135 "EUC_CN": "?", # Chinese EUC 136 "EUC_KR": "?", # Korean EUC 137 "JOHAB": "?", # Korean EUC (Hangle base) 138 "EUC_TW": "?", # Taiwan EUC 139 "UNICODE": "utf-8", # Unicode (UTF-8) 140 "UTF8": "utf-8", # Unicode (UTF-8) 141 "MULE_INTERNAL": "?", # Mule internal code 142 "LATIN1": "iso-8859-1", # ISO 8859-1/ECMA 94 (Latin alphabet no.1) 143 "LATIN2": "iso-8859-2", # ISO 8859-2/ECMA 94 (Latin alphabet no.2) 144 "LATIN3": "iso-8859-3", # ISO 8859-3/ECMA 94 (Latin alphabet no.3) 145 "LATIN4": "iso-8859-4", # ISO 8859-4/ECMA 94 (Latin alphabet no.4) 146 "LATIN5": "iso-8859-9", # ISO 8859-9/ECMA 128 (Latin alphabet no.5) 147 "LATIN6": "iso-8859-10", # ISO 8859-10/ECMA 144 (Latin alphabet no.6) 148 "LATIN7": "iso-8859-13", # ISO 8859-13 (Latin alphabet no.7) 149 "LATIN8": "iso-8859-14", # ISO 8859-14 (Latin alphabet no.8) 150 "LATIN9": "iso-8859-15", # ISO 8859-15 (Latin alphabet no.9) 151 "LATIN10": "iso-8859-16", # ISO 8859-16/ASRO SR 14111 152 # (Latin alphabet no.10) 153 "ISO_8859_5": "iso-8859-5", # ISO 8859-5/ECMA 113 (Latin/Cyrillic) 154 "ISO_8859_6": "iso-8859-6", # ISO 8859-6/ECMA 114 (Latin/Arabic) 155 "ISO_8859_7": "iso-8859-7", # ISO 8859-7/ECMA 118 (Latin/Greek) 156 "ISO_8859_8": "iso-8859-8", # ISO 8859-8/ECMA 121 (Latin/Hebrew) 157 "KOI8": "?", # KOI8-R(U) 158 "WIN": "?", # Windows CP1251 159 "ALT": "?", # Windows CP866 160 "WIN1256": "?", # Windows CP1256 (Arabic) 161 "TCVN": "?", # TCVN-5712/Windows CP1258 (Vietnamese) 162 "WIN874": "?" # Windows CP874 (Thai) 163 } 164
165 - def __init__(self, dsn=None):
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
241 - def _from_params(params):
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
259 - def _from_connection(conn):
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
269 - def check_cast_handler(self, datatype_class, relation, column):
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 # Run a query on the database that will return 283 # the SQL type's oid. We only need the cursor's 284 # description, really 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 # register the string class with psycopg so it always 290 # returns a SQL literal as a Python string 291 TYPE = psycopg.new_type((type_oid,), 292 upper(type_name), str) 293 psycopg.register_type(TYPE) 294 295 # store the Oid in our dict 296 _typeoid[type_name] = type_oid
297
298 - def dsn(self):
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 # In any case rollback the current transaction. 318 # For one thing to get rid of that stupid "current transaction is 319 # aborted, commands ignored until end of transaction block" 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 # if we've been constructed using fromConnection... 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
351 - def connect(self):
352 if self._dsn is not None: 353 self._conn = psycopg.connect(self._dsn) 354 self._update_cursor = self._conn.cursor()
355 356
357 - def backend_version(self):
358 # determine the backend's version 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
372 - def backend_encoding(self):
373 if self._encoding is None: 374 # determine the current backend encoding 375 # (this must be done backend version sensitive in the future) 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 # hm.. let's figure out the python equivalent 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
399 - def select_after_insert_where(self, dbobj):
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 # Serial columns are treated specially 411 412 if primary_key_attribute.sequence_name is None: 413 if dbobj.__relation__.schema is not None: 414 # If the relation's name is quoted and contains illegal 415 # characters for a sequence name, this wil result in an 416 # illegal identifyer. In that case please specify 417 # the sequence name by hand. 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 # If we know the primary key value, we use it to identify 438 # the new row. 439 where = dbobj.__primary_key__.where() 440 441 else: 442 raise PrimaryKeyNotKnown() 443 444 return where
445
446 -class zpsycopg_db_conn(datasource):
447 - def __init__(self, context, ds_name):
448 datasource.__init__(self) 449 450 self.context = context 451 self.ds_name = ds_name 452 453 self._dbfailures = 0 454 self._conn = None 455 self._ERRORS_BEFORE_RECONNECT = 3
456
457 - def _dbconn(self):
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 # In any case rollback the current transaction. 479 # For one thing to get rid of that stupid "current transaction is 480 # aborted, commands ignored until end of transaction block" 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
506 - def rollback(self):
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