1############################################################################## 2# Copyright (c) 2009-2018, Hajime Nakagami<nakagami@gmail.com> 3# All rights reserved. 4# 5# Redistribution and use in source and binary forms, with or without 6# modification, are permitted provided that the following conditions are met: 7# 8# * Redistributions of source code must retain the above copyright notice, this 9# list of conditions and the following disclaimer. 10# 11# * Redistributions in binary form must reproduce the above copyright notice, 12# this list of conditions and the following disclaimer in the documentation 13# and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25# 26# Python DB-API 2.0 module for Firebird. 27############################################################################## 28from __future__ import print_function 29import sys 30import time 31import datetime 32import decimal 33import itertools 34from collections import Mapping 35from firebirdsql import InternalError, OperationalError, IntegrityError, NotSupportedError 36from firebirdsql.consts import * 37from firebirdsql.utils import * 38from firebirdsql.wireprotocol import WireProtocol 39from firebirdsql.socketstream import SocketStream 40from firebirdsql.xsqlvar import calc_blr, parse_xsqlda 41__version__ = '1.1.4' 42apilevel = '2.0' 43threadsafety = 1 44paramstyle = 'qmark' 45 46DEBUG = False 47 48 49def DEBUG_OUTPUT(*argv): 50 if not DEBUG: 51 return 52 for s in argv: 53 print(s, end=' ', file=sys.stderr) 54 print(file=sys.stderr) 55 56 57transaction_parameter_block = ( 58 # ISOLATION_LEVEL_READ_COMMITED_LEGACY 59 bs([isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version]), 60 # ISOLATION_LEVEL_READ_COMMITED 61 bs([isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_rec_version]), 62 # ISOLATION_LEVEL_REPEATABLE_READ 63 bs([isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_concurrency]), 64 # ISOLATION_LEVEL_SERIALIZABLE 65 bs([isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_consistency]), 66 # ISOLATION_LEVEL_READ_COMMITED_RO 67 bs([isc_tpb_version3, isc_tpb_read, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_rec_version]), 68) 69 70Date = datetime.date 71Time = datetime.time 72TimeDelta = datetime.timedelta 73Timestamp = datetime.datetime 74 75 76def DateFromTicks(ticks): 77 return apply(Date, time.localtime(ticks)[:3]) 78 79 80def TimeFromTicks(ticks): 81 return apply(Time, time.localtime(ticks)[3:6]) 82 83 84def TimestampFromTicks(ticks): 85 return apply(Timestamp, time.localtime(ticks)[:6]) 86 87 88def Binary(b): 89 return bytes(b) 90 91 92class DBAPITypeObject: 93 def __init__(self, *values): 94 self.values = values 95 96 def __cmp__(self, other): 97 if other in self.values: 98 return 0 99 if other < self.values: 100 return 1 101 else: 102 return -1 103 104 105STRING = DBAPITypeObject(str) 106if PYTHON_MAJOR_VER == 3: 107 BINARY = DBAPITypeObject(bytes) 108else: 109 BINARY = DBAPITypeObject(str) 110NUMBER = DBAPITypeObject(int, decimal.Decimal) 111DATETIME = DBAPITypeObject(datetime.datetime, datetime.date, datetime.time) 112DATE = DBAPITypeObject(datetime.date) 113TIME = DBAPITypeObject(datetime.time) 114ROWID = DBAPITypeObject() 115 116 117class Statement(object): 118 """ 119 statement handle and status (open/close) 120 """ 121 def __init__(self, trans): 122 DEBUG_OUTPUT("Statement::__init__()") 123 self.trans = trans 124 self._allocate_stmt() 125 self._is_open = False 126 self.stmt_type = None 127 128 def _allocate_stmt(self): 129 self.trans.connection._op_allocate_statement() 130 if self.trans.connection.accept_type == ptype_lazy_send: 131 self.trans.connection.lazy_response_count += 1 132 self.handle = -1 133 else: 134 (h, oid, buf) = self.trans.connection._op_response() 135 self.handle = h 136 137 def prepare(self, sql, explain_plan=False): 138 DEBUG_OUTPUT("Statement::prepare()", self.handle) 139 if explain_plan: 140 self.trans.connection._op_prepare_statement( 141 self.handle, self.trans.trans_handle, sql, 142 option_items=bs([isc_info_sql_get_plan])) 143 else: 144 self.trans.connection._op_prepare_statement( 145 self.handle, self.trans.trans_handle, sql) 146 self.plan = None 147 148 if self.trans.connection.lazy_response_count: 149 self.trans.connection.lazy_response_count -= 1 150 (h, oid, buf) = self.trans.connection._op_response() 151 self.handle = h 152 153 (h, oid, buf) = self.trans.connection._op_response() 154 155 i = 0 156 if byte_to_int(buf[i]) == isc_info_sql_get_plan: 157 l = bytes_to_int(buf[i+1:i+3]) 158 self.plan = self.trans.connection.bytes_to_str(buf[i+3:i+3+l]) 159 i += 3 + l 160 self.stmt_type, self.xsqlda = parse_xsqlda(buf[i:], self.trans.connection, self.handle) 161 if self.stmt_type == isc_info_sql_stmt_select: 162 self._is_open = True 163 164 def close(self): 165 DEBUG_OUTPUT("Statement::close()", self.handle) 166 if self.stmt_type == isc_info_sql_stmt_select and self._is_open: 167 self.trans.connection._op_free_statement(self.handle, DSQL_close) 168 if self.trans.connection.accept_type == ptype_lazy_send: 169 self.trans.connection.lazy_response_count += 1 170 else: 171 (h, oid, buf) = self.trans.connection._op_response() 172 self._is_open = False 173 174 def drop(self): 175 DEBUG_OUTPUT("Statement::drop()", self.handle) 176 if self.handle != -1 and self._is_open: 177 self.trans.connection._op_free_statement(self.handle, DSQL_drop) 178 if self.trans.connection.accept_type == ptype_lazy_send: 179 self.trans.connection.lazy_response_count += 1 180 else: 181 (h, oid, buf) = self.trans.connection._op_response() 182 self._is_open = False 183 self.handle = -1 184 185 @property 186 def is_opened(self): 187 return self._is_open and self.handle != -1 188 189 190class PreparedStatement(object): 191 def __init__(self, cur, sql, explain_plan=False): 192 DEBUG_OUTPUT("PreparedStatement::__init__()") 193 cur.transaction.check_trans_handle() 194 self.stmt = Statement(cur.transaction) 195 self.stmt.prepare(sql, explain_plan) 196 self.sql = sql 197 198 def __getattr__(self, attrname): 199 if attrname == 'description': 200 if len(self.stmt.xsqlda) == 0: 201 return None 202 r = [] 203 for x in self.stmt.xsqlda: 204 r.append(( 205 x.aliasname, x.sqltype, x.display_length(), 206 x.io_length(), x.precision(), 207 x.sqlscale, True if x.null_ok else False)) 208 return r 209 elif attrname == 'n_output_params': 210 return len(self.stmt.xsqlda) 211 raise AttributeError 212 213 def close(self): 214 self.stmt.close() 215 216 217def _fetch_generator(stmt): 218 DEBUG_OUTPUT("_fetch_generator()", stmt.handle, stmt.trans._trans_handle) 219 connection = stmt.trans.connection 220 more_data = True 221 while more_data: 222 if not stmt.is_opened: 223 return 224 connection._op_fetch(stmt.handle, calc_blr(stmt.xsqlda)) 225 (rows, more_data) = connection._op_fetch_response(stmt.handle, stmt.xsqlda) 226 for r in rows: 227 # Convert BLOB handle to data 228 for i in range(len(stmt.xsqlda)): 229 x = stmt.xsqlda[i] 230 if x.sqltype == SQL_TYPE_BLOB: 231 if not r[i]: 232 continue 233 connection._op_open_blob(r[i], stmt.trans.trans_handle) 234 (h, oid, buf) = connection._op_response() 235 v = bs([]) 236 n = 1 # 0,1:mora data 2:no more data 237 while n != 2: 238 connection._op_get_segment(h) 239 (n, oid, buf) = connection._op_response() 240 while buf: 241 ln = bytes_to_int(buf[:2]) 242 v += buf[2:ln+2] 243 buf = buf[ln+2:] 244 connection._op_close_blob(h) 245 if connection.accept_type == ptype_lazy_send: 246 connection.lazy_response_count += 1 247 else: 248 (h, oid, buf) = connection._op_response() 249 r[i] = v 250 if x.sqlsubtype == 1: # TEXT 251 if connection.use_unicode: 252 r[i] = connection.bytes_to_ustr(r[i]) 253 else: 254 r[i] = connection.bytes_to_str(r[i]) 255 yield tuple(r) 256 return 257 258 259class Cursor(object): 260 def __init__(self, trans): 261 DEBUG_OUTPUT("Cursor::__init__()") 262 self._transaction = trans 263 self.stmt = None 264 self.arraysize = 1 265 266 def __enter__(self): 267 return self 268 269 def __exit__(self, exc, value, traceback): 270 self.close() 271 272 @property 273 def transaction(self): 274 return self._transaction 275 276 def _convert_params(self, params): 277 cooked_params = [] 278 for param in params: 279 if type(param) == str or (PYTHON_MAJOR_VER == 2 and type(param) == unicode): 280 param = self.transaction.connection.str_to_bytes(param) 281 cooked_params.append(param) 282 return cooked_params 283 284 def _get_stmt(self, query): 285 self.query = query 286 if isinstance(query, PreparedStatement): 287 stmt = query.stmt 288 else: 289 if self.stmt: 290 self.stmt.drop() 291 self.stmt = None 292 if self.stmt is None: 293 self.stmt = Statement(self.transaction) 294 stmt = self.stmt 295 stmt.prepare(query) 296 return stmt 297 298 def prep(self, query, explain_plan=False): 299 DEBUG_OUTPUT("Cursor::prep()") 300 prepared_statement = PreparedStatement(self, query, explain_plan=explain_plan) 301 return prepared_statement 302 303 def execute(self, query, params=None): 304 if params is None: 305 params = [] 306 DEBUG_OUTPUT("Cursor::execute()", query, params) 307 self.transaction.check_trans_handle() 308 stmt = self._get_stmt(query) 309 cooked_params = self._convert_params(params) 310 if stmt.stmt_type == isc_info_sql_stmt_exec_procedure: 311 self.transaction.connection._op_execute2( 312 stmt.handle, 313 self.transaction.trans_handle, cooked_params, 314 calc_blr(stmt.xsqlda)) 315 self._callproc_result = self.transaction.connection._op_sql_response(stmt.xsqlda) 316 self.transaction.connection._op_response() 317 self._fetch_records = None 318 else: 319 DEBUG_OUTPUT( 320 "Cursor::execute() _op_execute()", 321 stmt.handle, self.transaction.trans_handle) 322 self.transaction.connection._op_execute( 323 stmt.handle, self.transaction.trans_handle, cooked_params) 324 (h, oid, buf) = self.transaction.connection._op_response() 325 326 if stmt.stmt_type == isc_info_sql_stmt_select: 327 self._fetch_records = _fetch_generator(stmt) 328 else: 329 self._fetch_records = None 330 self._callproc_result = None 331 self.transaction.is_dirty = True 332 333 return self 334 335 def callproc(self, procname, params=None): 336 if params is None: 337 params = [] 338 DEBUG_OUTPUT("Cursor::callproc()") 339 query = 'EXECUTE PROCEDURE ' + procname + ' ' + ','.join('?'*len(params)) 340 self.execute(query, params) 341 return self._callproc_result 342 343 def executemany(self, query, seq_of_params): 344 for params in seq_of_params: 345 self.execute(query, params) 346 347 def fetchone(self): 348 if not self.transaction.is_dirty: 349 return None 350 # callproc or not select statement 351 if not self._fetch_records: 352 if self._callproc_result: 353 r = self._callproc_result 354 self._callproc_result = None 355 return r 356 return None 357 # select statement 358 try: 359 if PYTHON_MAJOR_VER == 3: 360 return tuple(next(self._fetch_records)) 361 else: 362 return tuple(self._fetch_records.next()) 363 except StopIteration: 364 return None 365 366 def __iter__(self): 367 return self 368 369 def __next__(self): 370 r = self.fetchone() 371 if not r: 372 raise StopIteration() 373 return r 374 375 def next(self): 376 return self.__next__() 377 378 def fetchall(self): 379 # callproc or not select statement 380 if not self.transaction.is_dirty: 381 return None 382 if not self._fetch_records: 383 if self._callproc_result: 384 proc_r = [tuple(self._callproc_result)] 385 self._callproc_result = None 386 return proc_r 387 return [] 388 # select statement 389 return [tuple(r) for r in self._fetch_records] 390 391 def fetchmany(self, size=None): 392 if not size: 393 size = self.arraysize 394 # callproc or not select statement 395 if not self._fetch_records: 396 if self._callproc_result: 397 r = [self._callproc_result] 398 self._callproc_result = None 399 return r 400 return [] 401 # select statement 402 return list(itertools.islice(self._fetch_records, size)) 403 404 # kinterbasdb extended API 405 def fetchonemap(self): 406 r = self.fetchone() 407 if r is None: 408 return {} 409 return RowMapping(r, self.description) 410 411 def fetchallmap(self): 412 desc = self.description 413 return [RowMapping(row, desc) for row in self.fetchall()] 414 415 def fetchmanymap(self, size=None): 416 desc = self.description 417 return [RowMapping(row, desc) for row in self.fetchmany(size)] 418 419 def itermap(self): 420 r = self.fetchonemap() 421 while r: 422 yield r 423 r = self.fetchonemap() 424 425 def close(self): 426 DEBUG_OUTPUT("Cursor::close()") 427 if not self.stmt: 428 return 429 self.stmt.drop() 430 self.stmt = None 431 432 def nextset(self): 433 raise NotSupportedError() 434 435 def setinputsizes(self, sizes): 436 pass 437 438 def setoutputsize(self, size, column): 439 pass 440 441 @property 442 def description(self): 443 if not self.stmt: 444 return None 445 return [( 446 x.aliasname, x.sqltype, x.display_length(), x.io_length(), 447 x.precision(), x.sqlscale, True if x.null_ok else False 448 ) for x in self.stmt.xsqlda] 449 450 @property 451 def rowcount(self): 452 DEBUG_OUTPUT("Cursor::rowcount()") 453 if self.stmt.handle == -1: 454 return -1 455 456 self.transaction.connection._op_info_sql(self.stmt.handle, bs([isc_info_sql_records])) 457 (h, oid, buf) = self.transaction.connection._op_response() 458 assert buf[:3] == bs([0x17, 0x1d, 0x00]) # isc_info_sql_records 459 if self.stmt.stmt_type == isc_info_sql_stmt_select: 460 assert buf[17:20] == bs([0x0d, 0x04, 0x00]) # isc_info_req_select_count 461 count = bytes_to_int(buf[20:24]) 462 else: 463 count = bytes_to_int(buf[27:31]) + bytes_to_int(buf[6:10]) + bytes_to_int(buf[13:17]) 464 DEBUG_OUTPUT("Cursor::rowcount()", self.stmt.stmt_type, count) 465 return count 466 467 468class EventConduit(WireProtocol): 469 def __init__(self, conn, names, timeout): 470 self.sock = None 471 self.connection = conn 472 self.event_names = {} 473 for name in names: 474 self.event_names[name] = 0 475 self.timeout = timeout 476 self.connection._op_connect_request() 477 (h, oid, buf) = self.connection._op_response() 478 family = buf[:2] 479 port = bytes_to_bint(buf[2:4], u=True) 480 if family == b'\x02\x00': # IPv4 481 ip_address = '.'.join([str(byte_to_int(c)) for c in buf[4:8]]) 482 elif family == b'\x0a\x00': # IPv6 483 address = bytes_to_hex(buf[8:24]) 484 if not isinstance(address, str): # Py3 485 address = address.decode('ascii') 486 ip_address = ':'.join( 487 [address[i: i+4] for i in range(0, len(address), 4)] 488 ) 489 self.sock = SocketStream(ip_address, port, timeout) 490 self.connection.last_event_id += 1 491 self.event_id = self.connection.last_event_id 492 493 self.connection._op_que_events(self.event_names, self.event_id) 494 (h, oid, buf) = self.connection._op_response() 495 496 (event_id, event_names) = self._wait_for_event(timeout=timeout) 497 assert event_id == self.event_id # treat only one event_id 498 self.event_names.update(event_names) 499 500 def wait(self, timeout=None): 501 self.connection._op_que_events(self.event_names, self.event_id) 502 (h, oid, buf) = self.connection._op_response() 503 504 r = self._wait_for_event(timeout=timeout) 505 if r: 506 (event_id, event_names) = r 507 assert event_id == self.event_id # treat only one event_id 508 r = {} 509 for k in event_names: 510 r[k] = event_names[k]-self.event_names[k] 511 self.event_names[k] = event_names[k] 512 else: 513 r = {} 514 for k in self.event_names: 515 r[k] = 0 516 return r 517 518 def close(self): 519 self.connection._op_cancel_events(self.event_id) 520 (h, oid, buf) = self.connection._op_response() 521 self.sock.close() 522 self.sock = None 523 524 525class Connection(WireProtocol): 526 def cursor(self, factory=Cursor): 527 DEBUG_OUTPUT("Connection::cursor()") 528 if self._transaction is None: 529 self.begin() 530 return factory(self._transaction) 531 532 def begin(self): 533 DEBUG_OUTPUT("Connection::begin()") 534 if not self.sock: 535 raise InternalError("Missing socket") 536 if self._transaction is None: 537 self._transaction = Transaction(self, self._autocommit) 538 self._transaction.begin() 539 540 def commit(self, retaining=False): 541 DEBUG_OUTPUT("Connection::commit()") 542 if self._transaction: 543 self._transaction.commit(retaining=retaining) 544 545 def savepoint(self, name): 546 return self._transaction.savepoint(name) 547 548 def rollback(self, retaining=False, savepoint=None): 549 DEBUG_OUTPUT("Connection::rollback()") 550 if self._transaction: 551 self._transaction.rollback(retaining=retaining, savepoint=savepoint) 552 553 def execute_immediate(self, query): 554 if self._transaction is None: 555 self._transaction = Transaction(self, self._autocommit) 556 self._transaction.begin() 557 self._transaction.check_trans_handle() 558 self._op_exec_immediate( 559 self._transaction.trans_handle, query=query) 560 (h, oid, buf) = self._op_response() 561 self._transaction.is_dirty = True 562 563 def __init__( 564 self, dsn=None, user=None, password=None, role=None, host=None, 565 database=None, charset=DEFAULT_CHARSET, port=None, 566 page_size=4096, is_services=False, cloexec=False, 567 timeout=None, isolation_level=None, use_unicode=None, 568 auth_plugin_name=None, wire_crypt=True, create_new=False, 569 timezone=None 570 ): 571 DEBUG_OUTPUT("Connection::__init__()") 572 if auth_plugin_name is None: 573 auth_plugin_name = 'Srp256' 574 WireProtocol.__init__(self) 575 self.sock = None 576 self.db_handle = None 577 (self.hostname, self.port, self.filename, self.user, self.password) = parse_dsn(dsn, host, port, database, user, password) 578 self.role = role 579 self.charset = charset 580 self.timeout = float(timeout) if timeout is not None else None 581 self.auth_plugin_name = auth_plugin_name 582 self.wire_crypt = wire_crypt 583 self.page_size = page_size 584 self.is_services = is_services 585 if isolation_level is None: 586 self.isolation_level = ISOLATION_LEVEL_READ_COMMITED 587 else: 588 self.isolation_level = int(isolation_level) 589 self.use_unicode = use_unicode 590 self.timezone = timezone 591 self.last_event_id = 0 592 593 self._autocommit = False 594 self._transaction = None 595 self.sock = SocketStream(self.hostname, self.port, self.timeout, cloexec) 596 597 self._op_connect(auth_plugin_name, wire_crypt) 598 try: 599 self._parse_connect_response() 600 except OperationalError as e: 601 self.sock.close() 602 self.sock = None 603 raise e 604 if create_new: # create database 605 self._op_create(self.page_size) 606 elif self.is_services: # service api 607 self._op_service_attach() 608 else: # connect 609 self._op_attach() 610 (h, oid, buf) = self._op_response() 611 self.db_handle = h 612 613 def __enter__(self): 614 return self 615 616 def __exit__(self, exc, value, traceback): 617 "On successful exit, commit. On exception, rollback. " 618 if exc: 619 self.rollback() 620 else: 621 self.commit() 622 self.close() 623 624 def set_isolation_level(self, isolation_level): 625 self.isolation_level = int(isolation_level) 626 627 def set_autocommit(self, is_autocommit): 628 if self._autocommit != is_autocommit and self._transaction is not None: 629 self.rollback() 630 self._transaction = None 631 self._autocommit = is_autocommit 632 633 def _db_info(self, info_requests): 634 if info_requests[-1] == isc_info_end: 635 self._op_info_database(bs(info_requests)) 636 else: 637 self._op_info_database(bs(info_requests+type(info_requests)([isc_info_end]))) 638 (h, oid, buf) = self._op_response() 639 i = 0 640 i_request = 0 641 r = [] 642 while i < len(buf): 643 req = byte_to_int(buf[i]) 644 if req == isc_info_end: 645 break 646 assert req == info_requests[i_request] or req == isc_info_error 647 if req == isc_info_user_names: 648 user_names = [] 649 while req == isc_info_user_names: 650 l = bytes_to_int(buf[i+1:i+3]) 651 user_names.append(buf[i+3:i+3+l]) 652 i = i + 3 + l 653 req = byte_to_int(buf[i]) 654 r.append((req, user_names)) 655 else: 656 l = bytes_to_int(buf[i+1:i+3]) 657 r.append((req, buf[i+3:i+3+l])) 658 i = i + 3 + l 659 i_request += 1 660 return r 661 662 def _db_info_convert_type(self, info_request, v): 663 REQ_INT = set([ 664 isc_info_allocation, isc_info_no_reserve, isc_info_db_sql_dialect, 665 isc_info_ods_minor_version, isc_info_ods_version, 666 isc_info_page_size, isc_info_current_memory, isc_info_forced_writes, 667 isc_info_max_memory, isc_info_num_buffers, isc_info_sweep_interval, 668 isc_info_limbo, isc_info_attachment_id, isc_info_fetches, 669 isc_info_marks, isc_info_reads, isc_info_writes, 670 isc_info_set_page_buffers, isc_info_db_read_only, 671 isc_info_db_size_in_pages, isc_info_page_errors, 672 isc_info_record_errors, isc_info_bpage_errors, 673 isc_info_dpage_errors, isc_info_ipage_errors, 674 isc_info_ppage_errors, isc_info_tpage_errors, 675 # may not be available in some versions of Firebird 676 isc_info_oldest_transaction, isc_info_oldest_active, 677 isc_info_oldest_snapshot, isc_info_next_transaction, 678 isc_info_active_tran_count 679 ]) 680 REQ_COUNT = set([ 681 isc_info_backout_count, isc_info_delete_count, 682 isc_info_expunge_count, isc_info_insert_count, isc_info_purge_count, 683 isc_info_read_idx_count, isc_info_read_seq_count, 684 isc_info_update_count 685 ]) 686 687 if info_request in (isc_info_base_level, ): 688 # IB6 API guide p52 689 return byte_to_int(v[1]) 690 elif info_request in (isc_info_db_id, ): 691 # IB6 API guide p52 692 conn_code = byte_to_int(v[0]) 693 len1 = byte_to_int(v[1]) 694 filename = self.bytes_to_str(v[2:2+len1]) 695 len2 = byte_to_int(v[2+len1]) 696 sitename = self.bytes_to_str(v[3+len1:3+len1+len2]) 697 return (conn_code, filename, sitename) 698 elif info_request in (isc_info_implementation, ): 699 return (byte_to_int(v[1]), byte_to_int(v[2])) 700 elif info_request in (isc_info_version, isc_info_firebird_version): 701 # IB6 API guide p53 702 return self.bytes_to_str(v[2:2+byte_to_int(v[1])]) 703 elif info_request in (isc_info_user_names, ): 704 # IB6 API guide p54 705 user_names = [] 706 for u in v: 707 user_names.append(self.bytes_to_str(u[1:])) 708 return user_names 709 elif info_request in REQ_INT: 710 return bytes_to_int(v) 711 elif info_request in REQ_COUNT: 712 counts = {} 713 i = 0 714 while i < len(v): 715 counts[bytes_to_int(v[i:i+2])] = bytes_to_int(v[i+2:i+6]) 716 i += 6 717 return counts 718 elif info_request in (isc_info_creation_date,): 719 nday = bytes_to_int(v[:4]) + 2400001 - 1721119 720 century = (4 * nday - 1) // 146097 721 nday = 4 * nday - 1 - 146097 * century 722 dd = nday // 4 723 nday = (4 * dd + 3) // 1461 724 dd = 4 * dd + 3 - 1461 * nday 725 dd = (dd + 4) // 4 726 mm = (5 * dd - 3) // 153 727 dd = 5 * dd - 3 - 153 * mm 728 dd = (dd + 5) // 5 729 yyyy = 100 * century + nday 730 if mm < 10: 731 mm += 3 732 else: 733 mm -= 9 734 yyyy += 1 735 736 ntime = bytes_to_int(v[4:]) 737 h = ntime // (3600 * ISC_TIME_SECONDS_PRECISION) 738 ntime %= 3600 * ISC_TIME_SECONDS_PRECISION 739 m = ntime // (60 * ISC_TIME_SECONDS_PRECISION) 740 ntime %= 60 * ISC_TIME_SECONDS_PRECISION 741 s = ntime // ISC_TIME_SECONDS_PRECISION 742 ms = ntime % ISC_TIME_SECONDS_PRECISION * 100 743 744 return datetime.datetime(yyyy, mm, dd, h, m, s, ms) 745 else: 746 return v 747 748 def db_info(self, info_requests): 749 DEBUG_OUTPUT("Connection::db_info()") 750 if type(info_requests) == int: # singleton 751 r = self._db_info([info_requests]) 752 return self._db_info_convert_type(info_requests, r[0][1]) 753 else: 754 results = {} 755 rs = self._db_info(info_requests) 756 for i in range(len(info_requests)): 757 if rs[i][0] == isc_info_error: 758 results[info_requests[i]] = None 759 else: 760 results[info_requests[i]] = self._db_info_convert_type(info_requests[i], rs[i][1]) 761 return results 762 763 def trans_info(self, info_requests): 764 if self._transaction: 765 return self._transaction.trans_info(info_requests) 766 return {} 767 768 def close(self): 769 DEBUG_OUTPUT("Connection::close()") 770 if self.sock is None: 771 return 772 if self.db_handle: 773 if self.is_services: 774 self._op_service_detach() 775 else: 776 self._op_detach() 777 (h, oid, buf) = self._op_response() 778 self.sock.close() 779 self.sock = None 780 self.db_handle = None 781 782 def drop_database(self): 783 DEBUG_OUTPUT("Connection::drop_database()") 784 self._op_drop_database() 785 (h, oid, buf) = self._op_response() 786 self.sock.close() 787 self.sock = None 788 self.db_handle = None 789 790 def event_conduit(self, event_names, timeout=None): 791 return EventConduit(self, event_names, timeout) 792 793 def __del__(self): 794 if self.sock: 795 self.close() 796 797 def is_disconnect(self): 798 return self.sock is None 799 800 801class Transaction(object): 802 def __init__(self, connection, is_autocommit=False): 803 DEBUG_OUTPUT("Transaction::__init__()") 804 self._connection = connection 805 self._trans_handle = None 806 self._autocommit = is_autocommit 807 808 def _begin(self): 809 tpb = transaction_parameter_block[self.connection.isolation_level] 810 if self._autocommit: 811 tpb += bs([isc_tpb_autocommit]) 812 self.connection._op_transaction(tpb) 813 (h, oid, buf) = self.connection._op_response() 814 self._trans_handle = None if h < 0 else h 815 DEBUG_OUTPUT( 816 "Transaction::_begin()", self._trans_handle, self.connection) 817 self.is_dirty = False 818 819 def begin(self): 820 DEBUG_OUTPUT("Transaction::begin()") 821 self._begin() 822 823 def savepoint(self, name): 824 if self._trans_handle is None: 825 return 826 self.connection._op_exec_immediate(self._trans_handle, query='SAVEPOINT '+name) 827 (h, oid, buf) = self.connection._op_response() 828 829 def commit(self, retaining=False): 830 DEBUG_OUTPUT( 831 "Transaction::commit()", self._trans_handle, self, self.connection, retaining) 832 if self._trans_handle is None: 833 return 834 if not self.is_dirty: 835 return 836 if retaining: 837 self.connection._op_commit_retaining(self._trans_handle) 838 (h, oid, buf) = self.connection._op_response() 839 else: 840 self.connection._op_commit(self._trans_handle) 841 (h, oid, buf) = self.connection._op_response() 842 self._trans_handle = None 843 self.is_dirty = False 844 845 def rollback(self, retaining=False, savepoint=None): 846 DEBUG_OUTPUT( 847 "Transaction::rollback()", self._trans_handle, self, 848 self.connection, retaining, savepoint) 849 if self._trans_handle is None: 850 return 851 if savepoint: 852 self.connection._op_exec_immediate( 853 self._trans_handle, query='ROLLBACK TO '+savepoint) 854 (h, oid, buf) = self.connection._op_response() 855 return 856 if not self.is_dirty: 857 return 858 if retaining: 859 self.connection._op_rollback_retaining(self._trans_handle) 860 (h, oid, buf) = self.connection._op_response() 861 else: 862 self.connection._op_rollback(self._trans_handle) 863 (h, oid, buf) = self.connection._op_response() 864 self._trans_handle = None 865 self.is_dirty = False 866 867 def _trans_info(self, info_requests): 868 if info_requests[-1] == isc_info_end: 869 self.connection._op_info_transaction(self.trans_handle, bs(info_requests)) 870 else: 871 self.connection._op_info_transaction( 872 self.trans_handle, bs(info_requests+type(info_requests)([isc_info_end]))) 873 (h, oid, buf) = self.connection._op_response() 874 i = 0 875 i_request = 0 876 r = [] 877 while i < len(buf): 878 req = byte_to_int(buf[i]) 879 if req == isc_info_end: 880 break 881 assert req == info_requests[i_request] or req == isc_info_error 882 l = bytes_to_int(buf[i+1:i+3]) 883 r.append((req, buf[i+3:i+3+l])) 884 i = i + 3 + l 885 886 i_request += 1 887 return r 888 889 def trans_info(self, info_requests): 890 if type(info_requests) == int: # singleton 891 r = self._trans_info([info_requests]) 892 return {info_requests: r[1][0]} 893 else: 894 results = {} 895 rs = self._trans_info(info_requests) 896 for i in range(len(info_requests)): 897 if rs[i][0] == isc_info_tra_isolation: 898 v = (byte_to_int(rs[i][1][0]), byte_to_int(rs[i][1][1])) 899 elif rs[i][0] == isc_info_error: 900 v = None 901 else: 902 v = bytes_to_int(rs[i][1]) 903 results[info_requests[i]] = v 904 return results 905 906 def check_trans_handle(self): 907 if self._trans_handle is None: 908 self._begin() 909 910 @property 911 def connection(self): 912 return self._connection 913 914 @property 915 def trans_handle(self): 916 assert(self._trans_handle is not None) 917 return self._trans_handle 918 919 920class RowMapping(Mapping): 921 """dict like interface to result rows 922 """ 923 __slots__ = ("_description", "_fields") 924 925 def __init__(self, row, description): 926 self._fields = fields = {} 927 # result may contain multiple fields with the same name. The 928 # RowMapping API ignores these additional fields. 929 for i, descr in enumerate(description): 930 fields.setdefault(descr[0], row[i]) 931 self._description = description 932 933 def __getitem__(self, key): 934 fields = self._fields 935 # try unnormalized key first 936 try: 937 return fields[key] 938 except KeyError: 939 pass 940 941 # normalize field name 942 if key[0] == '"' and key[-1] == '"': 943 # field names in quotes are case sensitive 944 normkey = key[1:-1] 945 else: 946 # default is all upper case fields 947 normkey = key.upper() 948 949 try: 950 return fields[normkey] 951 except KeyError: 952 raise KeyError("RowMapping has no field names '%s'. Available " 953 "field names are: %s" % 954 (key, ", ".join(self.keys()))) 955 956 def __iter__(self): 957 return iter(self._fields) 958 959 def __len__(self): 960 return len(self._fields) 961 962 def __repr__(self): 963 fields = self._fields 964 values = ["%s=%r" % (desc[0], fields[desc[0]]) 965 for desc in self._description] 966 return ("<RowMapping at 0x%08x with fields: %s>" % 967 (id(self), ", ".join(values))) 968