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