Package osh :: Package external :: Package pg8000 :: Module dbapi
[frames] | no frames]

Source Code for Module osh.external.pg8000.dbapi

  1  # vim: sw=4:expandtab:foldmethod=marker 
  2  # 
  3  # Copyright (c) 2007, Mathieu Fenniak 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions are 
  8  # met: 
  9  # 
 10  # * Redistributions of source code must retain the above copyright notice, 
 11  # this list of conditions and the following disclaimer. 
 12  # * Redistributions in binary form must reproduce the above copyright notice, 
 13  # this list of conditions and the following disclaimer in the documentation 
 14  # and/or other materials provided with the distribution. 
 15  # * The name of the author may not be used to endorse or promote products 
 16  # derived from this software without specific prior written permission. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 19  # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 20  # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 21  # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 22  # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 23  # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 24  # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 25  # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 26  # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 27  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 28  # POSSIBILITY OF SUCH DAMAGE. 
 29   
 30  __author__ = "Mathieu Fenniak" 
 31   
 32  import datetime 
 33  import time 
 34  import interface 
 35  import types 
 36  from errors import * 
 37   
 38  from warnings import warn 
 39   
 40  ## 
 41  # The DBAPI level supported.  Currently 2.0.  This property is part of the 
 42  # DBAPI 2.0 specification. 
 43  apilevel = "2.0" 
 44   
 45  ## 
 46  # Integer constant stating the level of thread safety the DBAPI interface 
 47  # supports.  This DBAPI interface supports sharing of the module, connections, 
 48  # and cursors.  This property is part of the DBAPI 2.0 specification. 
 49  threadsafety = 3 
 50   
 51  ## 
 52  # String property stating the type of parameter marker formatting expected by 
 53  # the interface.  This value defaults to "format".  This property is part of 
 54  # the DBAPI 2.0 specification. 
 55  # <p> 
 56  # Unlike the DBAPI specification, this value is not constant.  It can be 
 57  # changed to any standard paramstyle value (ie. qmark, numeric, named, format, 
 58  # and pyformat). 
 59  paramstyle = 'format' # paramstyle can be changed to any DB-API paramstyle 
 60   
61 -def convert_paramstyle(src_style, query, args):
62 # I don't see any way to avoid scanning the query string char by char, 63 # so we might as well take that careful approach and create a 64 # state-based scanner. We'll use int variables for the state. 65 # 0 -- outside quoted string 66 # 1 -- inside single-quote string '...' 67 # 2 -- inside quoted identifier "..." 68 # 3 -- inside escaped single-quote string, E'...' 69 state = 0 70 output_query = "" 71 output_args = [] 72 if src_style == "numeric": 73 output_args = args 74 elif src_style in ("pyformat", "named"): 75 mapping_to_idx = {} 76 i = 0 77 while 1: 78 if i == len(query): 79 break 80 c = query[i] 81 # print "begin loop", repr(i), repr(c), repr(state) 82 if state == 0: 83 if c == "'": 84 i += 1 85 output_query += c 86 state = 1 87 elif c == '"': 88 i += 1 89 output_query += c 90 state = 2 91 elif c == 'E': 92 # check for escaped single-quote string 93 i += 1 94 if i < len(query) and i > 1 and query[i] == "'": 95 i += 1 96 output_query += "E'" 97 state = 3 98 else: 99 output_query += c 100 elif src_style == "qmark" and c == "?": 101 i += 1 102 param_idx = len(output_args) 103 if param_idx == len(args): 104 raise ProgrammingError("too many parameter fields, not enough parameters") 105 output_args.append(args[param_idx]) 106 output_query += "$" + str(param_idx + 1) 107 elif src_style == "numeric" and c == ":": 108 i += 1 109 if i < len(query) and i > 1 and query[i].isdigit(): 110 output_query += "$" + query[i] 111 i += 1 112 else: 113 raise ProgrammingError("numeric parameter : does not have numeric arg") 114 elif src_style == "named" and c == ":": 115 name = "" 116 while 1: 117 i += 1 118 if i == len(query): 119 break 120 c = query[i] 121 if c.isalnum() or c == '_': 122 name += c 123 else: 124 break 125 if name == "": 126 raise ProgrammingError("empty name of named parameter") 127 idx = mapping_to_idx.get(name) 128 if idx == None: 129 idx = len(output_args) 130 output_args.append(args[name]) 131 idx += 1 132 mapping_to_idx[name] = idx 133 output_query += "$" + str(idx) 134 elif src_style == "format" and c == "%": 135 i += 1 136 if i < len(query) and i > 1: 137 if query[i] == "s": 138 param_idx = len(output_args) 139 if param_idx == len(args): 140 raise ProgrammingError("too many parameter fields, not enough parameters") 141 output_args.append(args[param_idx]) 142 output_query += "$" + str(param_idx + 1) 143 elif query[i] == "%": 144 output_query += "%" 145 else: 146 raise ProgrammingError("Only %s and %% are supported") 147 i += 1 148 else: 149 raise ProgrammingError("numeric parameter : does not have numeric arg") 150 elif src_style == "pyformat" and c == "%": 151 i += 1 152 if i < len(query) and i > 1: 153 if query[i] == "(": 154 i += 1 155 # begin mapping name 156 end_idx = query.find(')', i) 157 if end_idx == -1: 158 raise ProgrammingError("began pyformat dict read, but couldn't find end of name") 159 else: 160 name = query[i:end_idx] 161 i = end_idx + 1 162 if i < len(query) and query[i] == "s": 163 i += 1 164 idx = mapping_to_idx.get(name) 165 if idx == None: 166 idx = len(output_args) 167 output_args.append(args[name]) 168 idx += 1 169 mapping_to_idx[name] = idx 170 output_query += "$" + str(idx) 171 else: 172 raise ProgrammingError("format not specified or not supported (only %(...)s supported)") 173 elif query[i] == "%": 174 output_query += "%" 175 elif query[i] == "s": 176 # we have a %s in a pyformat query string. Assume 177 # support for format instead. 178 i -= 1 179 src_style = "format" 180 else: 181 i += 1 182 output_query += c 183 elif state == 1: 184 output_query += c 185 i += 1 186 if c == "'": 187 # Could be a double '' 188 if i < len(query) and query[i] == "'": 189 # is a double quote. 190 output_query += query[i] 191 i += 1 192 else: 193 state = 0 194 elif src_style in ("pyformat","format") and c == "%": 195 # hm... we're only going to support an escaped percent sign 196 if i < len(query): 197 if query[i] == "%": 198 # good. We already output the first percent sign. 199 i += 1 200 else: 201 raise ProgrammingError("'%" + query[i] + "' not supported in quoted string") 202 elif state == 2: 203 output_query += c 204 i += 1 205 if c == '"': 206 state = 0 207 elif src_style in ("pyformat","format") and c == "%": 208 # hm... we're only going to support an escaped percent sign 209 if i < len(query): 210 if query[i] == "%": 211 # good. We already output the first percent sign. 212 i += 1 213 else: 214 raise ProgrammingError("'%" + query[i] + "' not supported in quoted string") 215 elif state == 3: 216 output_query += c 217 i += 1 218 if c == "\\": 219 # check for escaped single-quote 220 if i < len(query) and query[i] == "'": 221 output_query += "'" 222 i += 1 223 elif c == "'": 224 state = 0 225 elif src_style in ("pyformat","format") and c == "%": 226 # hm... we're only going to support an escaped percent sign 227 if i < len(query): 228 if query[i] == "%": 229 # good. We already output the first percent sign. 230 i += 1 231 else: 232 raise ProgrammingError("'%" + query[i] + "' not supported in quoted string") 233 234 return output_query, tuple(output_args)
235 236 237 ## 238 # The class of object returned by the {@link #ConnectionWrapper.cursor cursor method}.
239 -class CursorWrapper(object):
240 - def __init__(self, conn, connection):
241 self.cursor = interface.Cursor(conn) 242 self.arraysize = 1 243 self._connection = connection 244 self._override_rowcount = None
245 246 ## 247 # This read-only attribute returns a reference to the connection object on 248 # which the cursor was created. 249 # <p> 250 # Stability: Part of a DBAPI 2.0 extension. A warning "DB-API extension 251 # cursor.connection used" will be fired. 252 connection = property(lambda self: self._getConnection()) 253
254 - def _getConnection(self):
255 warn("DB-API extension cursor.connection used", stacklevel=3) 256 return self._connection
257 258 ## 259 # This read-only attribute specifies the number of rows that the last 260 # .execute*() produced (for DQL statements like 'select') or affected (for 261 # DML statements like 'update' or 'insert'). 262 # <p> 263 # The attribute is -1 in case no .execute*() has been performed on the 264 # cursor or the rowcount of the last operation is cannot be determined by 265 # the interface. 266 # <p> 267 # Stability: Part of the DBAPI 2.0 specification. 268 rowcount = property(lambda self: self._getRowCount()) 269
270 - def _getRowCount(self):
271 if self.cursor == None: 272 raise InterfaceError("cursor is closed") 273 if self._override_rowcount != None: 274 return self._override_rowcount 275 return self.cursor.row_count
276 277 ## 278 # This read-only attribute is a sequence of 7-item sequences. Each value 279 # contains information describing one result column. The 7 items returned 280 # for each column are (name, type_code, display_size, internal_size, 281 # precision, scale, null_ok). Only the first two values are provided by 282 # this interface implementation. 283 # <p> 284 # Stability: Part of the DBAPI 2.0 specification. 285 description = property(lambda self: self._getDescription()) 286
287 - def _getDescription(self):
288 if self.cursor.row_description == None: 289 return None 290 columns = [] 291 for col in self.cursor.row_description: 292 columns.append((col["name"], col["type_oid"], None, None, None, None, None)) 293 return columns
294 295 ## 296 # Executes a database operation. Parameters may be provided as a sequence 297 # or mapping and will be bound to variables in the operation. 298 # <p> 299 # Stability: Part of the DBAPI 2.0 specification.
300 - def execute(self, operation, args=()):
301 if self.cursor == None: 302 raise InterfaceError("cursor is closed") 303 self._override_rowcount = None 304 self._execute(operation, args)
305
306 - def _execute(self, operation, args=()):
307 new_query, new_args = convert_paramstyle(paramstyle, operation, args) 308 try: 309 self.cursor.execute(new_query, *new_args) 310 except ConnectionClosedError: 311 # can't rollback in this case 312 raise 313 except: 314 # any error will rollback the transaction to-date 315 self._connection.rollback() 316 raise
317 318 ## 319 # Prepare a database operation and then execute it against all parameter 320 # sequences or mappings provided. 321 # <p> 322 # Stability: Part of the DBAPI 2.0 specification.
323 - def executemany(self, operation, parameter_sets):
324 self._override_rowcount = 0 325 for parameters in parameter_sets: 326 self._execute(operation, parameters) 327 if self.cursor.row_count == -1 or self._override_rowcount == -1: 328 self._override_rowcount = -1 329 else: 330 self._override_rowcount += self.cursor.row_count
331 332 ## 333 # Fetch the next row of a query result set, returning a single sequence, or 334 # None when no more data is available. 335 # <p> 336 # Stability: Part of the DBAPI 2.0 specification.
337 - def fetchone(self):
338 if self.cursor == None: 339 raise InterfaceError("cursor is closed") 340 return self.cursor.read_tuple()
341 342 ## 343 # Fetch the next set of rows of a query result, returning a sequence of 344 # sequences. An empty sequence is returned when no more rows are 345 # available. 346 # <p> 347 # Stability: Part of the DBAPI 2.0 specification. 348 # @param size The number of rows to fetch when called. If not provided, 349 # the arraysize property value is used instead.
350 - def fetchmany(self, size=None):
351 if size == None: 352 size = self.arraysize 353 rows = [] 354 for i in range(size): 355 value = self.fetchone() 356 if value == None: 357 break 358 rows.append(value) 359 return rows
360 361 ## 362 # Fetch all remaining rows of a query result, returning them as a sequence 363 # of sequences. 364 # <p> 365 # Stability: Part of the DBAPI 2.0 specification.
366 - def fetchall(self):
367 if self.cursor == None: 368 raise InterfaceError("cursor is closed") 369 return tuple(self.cursor.iterate_tuple())
370 371 ## 372 # Close the cursor. 373 # <p> 374 # Stability: Part of the DBAPI 2.0 specification.
375 - def close(self):
376 self.cursor = None 377 self._override_rowcount = None
378
379 - def next(self):
380 warn("DB-API extension cursor.next() used", stacklevel=2) 381 retval = self.fetchone() 382 if retval == None: 383 raise StopIteration() 384 return retval
385
386 - def __iter__(self):
387 warn("DB-API extension cursor.__iter__() used", stacklevel=2) 388 return self
389
390 - def setinputsizes(self, sizes):
391 pass
392
393 - def setoutputsize(self, size, column=None):
394 pass
395 396 397 ## 398 # The class of object returned by the {@link #connect connect method}.
399 -class ConnectionWrapper(object):
400 # DBAPI Extension: supply exceptions as attributes on the connection 401 Warning = property(lambda self: self._getError(Warning)) 402 Error = property(lambda self: self._getError(Error)) 403 InterfaceError = property(lambda self: self._getError(InterfaceError)) 404 DatabaseError = property(lambda self: self._getError(DatabaseError)) 405 OperationalError = property(lambda self: self._getError(OperationalError)) 406 IntegrityError = property(lambda self: self._getError(IntegrityError)) 407 InternalError = property(lambda self: self._getError(InternalError)) 408 ProgrammingError = property(lambda self: self._getError(ProgrammingError)) 409 NotSupportedError = property(lambda self: self._getError(NotSupportedError)) 410
411 - def _getError(self, error):
412 warn("DB-API extension connection.%s used" % error.__name__, stacklevel=3) 413 return error
414
415 - def __init__(self, **kwargs):
416 self.conn = interface.Connection(**kwargs) 417 self.conn.begin()
418 419 ## 420 # Creates a {@link #CursorWrapper CursorWrapper} object bound to this 421 # connection. 422 # <p> 423 # Stability: Part of the DBAPI 2.0 specification.
424 - def cursor(self):
425 return CursorWrapper(self.conn, self)
426 427 ## 428 # Commits the current database transaction. 429 # <p> 430 # Stability: Part of the DBAPI 2.0 specification.
431 - def commit(self):
432 # There's a threading bug here. If a query is sent after the 433 # commit, but before the begin, it will be executed immediately 434 # without a surrounding transaction. Like all threading bugs -- it 435 # sounds unlikely, until it happens every time in one 436 # application... however, to fix this, we need to lock the 437 # database connection entirely, so that no cursors can execute 438 # statements on other threads. Support for that type of lock will 439 # be done later. 440 if self.conn == None: 441 raise ConnectionClosedError() 442 self.conn.commit() 443 self.conn.begin()
444 445 ## 446 # Rolls back the current database transaction. 447 # <p> 448 # Stability: Part of the DBAPI 2.0 specification.
449 - def rollback(self):
450 # see bug description in commit. 451 if self.conn == None: 452 raise ConnectionClosedError() 453 self.conn.rollback() 454 self.conn.begin()
455 456 ## 457 # Closes the database connection. 458 # <p> 459 # Stability: Part of the DBAPI 2.0 specification.
460 - def close(self):
461 if self.conn == None: 462 raise ConnectionClosedError() 463 self.conn.close() 464 self.conn = None
465 466 467 ## 468 # Creates a DBAPI 2.0 compatible interface to a PostgreSQL database. 469 # <p> 470 # Stability: Part of the DBAPI 2.0 specification. 471 # 472 # @param user The username to connect to the PostgreSQL server with. This 473 # parameter is required. 474 # 475 # @keyparam host The hostname of the PostgreSQL server to connect with. 476 # Providing this parameter is necessary for TCP/IP connections. One of either 477 # host, or unix_sock, must be provided. 478 # 479 # @keyparam unix_sock The path to the UNIX socket to access the database 480 # through, for example, '/tmp/.s.PGSQL.5432'. One of either unix_sock or host 481 # must be provided. The port parameter will have no affect if unix_sock is 482 # provided. 483 # 484 # @keyparam port The TCP/IP port of the PostgreSQL server instance. This 485 # parameter defaults to 5432, the registered and common port of PostgreSQL 486 # TCP/IP servers. 487 # 488 # @keyparam database The name of the database instance to connect with. This 489 # parameter is optional, if omitted the PostgreSQL server will assume the 490 # database name is the same as the username. 491 # 492 # @keyparam password The user password to connect to the server with. This 493 # parameter is optional. If omitted, and the database server requests password 494 # based authentication, the connection will fail. On the other hand, if this 495 # parameter is provided and the database does not request password 496 # authentication, then the password will not be used. 497 # 498 # @keyparam socket_timeout Socket connect timeout measured in seconds. 499 # Defaults to 60 seconds. 500 # 501 # @keyparam ssl Use SSL encryption for TCP/IP socket. Defaults to False. 502 # 503 # @return An instance of {@link #ConnectionWrapper ConnectionWrapper}.
504 -def connect(user, host=None, unix_sock=None, port=5432, database=None, password=None, socket_timeout=60, ssl=False):
505 return ConnectionWrapper(user=user, host=host, 506 unix_sock=unix_sock, port=port, database=database, 507 password=password, socket_timeout=socket_timeout, ssl=ssl)
508
509 -def Date(year, month, day):
510 return datetime.date(year, month, day)
511
512 -def Time(hour, minute, second):
513 return datetime.time(hour, minute, second)
514
515 -def Timestamp(year, month, day, hour, minute, second):
516 return datetime.datetime(year, month, day, hour, minute, second)
517
518 -def DateFromTicks(ticks):
519 return Date(*time.localtime(ticks)[:3])
520
521 -def TimeFromTicks(ticks):
522 return Time(*time.localtime(ticks)[3:6])
523
524 -def TimestampFromTicks(ticks):
525 return Timestamp(*time.localtime(ticks)[:6])
526 527 ## 528 # Construct an object holding binary data.
529 -def Binary(value):
530 return types.Bytea(value)
531 532 # I have no idea what this would be used for by a client app. Should it be 533 # TEXT, VARCHAR, CHAR? It will only compare against row_description's 534 # type_code if it is this one type. It is the varchar type oid for now, this 535 # appears to match expectations in the DB API 2.0 compliance test suite. 536 STRING = 1043 537 538 # bytea type_oid 539 BINARY = 17 540 541 # numeric type_oid 542 NUMBER = 1700 543 544 # timestamp type_oid 545 DATETIME = 1114 546 547 # oid type_oid 548 ROWID = 26 549