Home | Trees | Indices | Help |
|
---|
|
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 6062 # 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}.395 396 397 ## 398 # The class of object returned by the {@link #connect connect method}.241 self.cursor = interface.Cursor(conn) 242 self.arraysize = 1 243 self._connection = connection 244 self._override_rowcount = None245 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 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()) 269271 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_count276 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()) 286288 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 columns294 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.301 if self.cursor == None: 302 raise InterfaceError("cursor is closed") 303 self._override_rowcount = None 304 self._execute(operation, args)305307 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 raise317 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.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_count331 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.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.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 rows360 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.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. 378380 warn("DB-API extension cursor.next() used", stacklevel=2) 381 retval = self.fetchone() 382 if retval == None: 383 raise StopIteration() 384 return retval385 389 392400 # 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 414 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.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}.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.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.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.461 if self.conn == None: 462 raise ConnectionClosedError() 463 self.conn.close() 464 self.conn = None504 -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 511 514 517519 return Date(*time.localtime(ticks)[:3])520522 return Time(*time.localtime(ticks)[3:6])523525 return Timestamp(*time.localtime(ticks)[:6])526 527 ## 528 # Construct an object holding binary data. 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
Home | Trees | Indices | Help |
|
---|
Generated by Epydoc 3.0beta1 on Thu Jul 31 17:01:38 2008 | http://epydoc.sourceforge.net |