1#coding:utf-8
2#
3#   PROGRAM/MODULE: fdb
4#   FILE:           testfdb.py
5#   DESCRIPTION:    Python driver for Firebird - Unit tests
6#   CREATED:        12.10.2011
7#
8#  Software distributed under the License is distributed AS IS,
9#  WITHOUT WARRANTY OF ANY KIND, either express or implied.
10#  See the License for the specific language governing rights
11#  and limitations under the License.
12#
13#  The Original Code was created by Pavel Cisar
14#
15#  Copyright (c) Pavel Cisar <pcisar@users.sourceforge.net>
16#  and all contributors signed below.
17#
18#  All Rights Reserved.
19#  Contributor(s): Philippe Makowski <pmakowski@ibphoenix.fr>
20#                  ______________________________________.
21#
22# See LICENSE.TXT for details.
23
24import unittest
25import datetime, decimal, types
26import fdb
27import fdb.ibase as ibase
28import fdb.schema as sm
29import fdb.utils as utils
30import fdb.gstat as gstat
31import fdb.log as log
32import sys, os
33import threading
34import time
35import collections
36from decimal import Decimal
37from contextlib import closing
38from re import finditer
39from pprint import pprint
40from fdb.gstat import FillDistribution, Encryption, StatDatabase
41from fdb.log import LogEntry
42from locale import LC_ALL, getlocale, setlocale, getdefaultlocale
43
44if ibase.PYTHON_MAJOR_VER == 3:
45    from io import StringIO, BytesIO
46else:
47    from cStringIO import StringIO
48    BytesIO = StringIO
49
50FB20 = '2.0'
51FB21 = '2.1'
52FB25 = '2.5'
53FB30 = '3.0'
54
55# Default server host
56#FBTEST_HOST = ''
57FBTEST_HOST = 'localhost'
58# Default user
59FBTEST_USER = 'SYSDBA'
60# Default user password
61FBTEST_PASSWORD = 'masterkey'
62
63def linesplit_iter(string):
64    return (m.group(2) for m in finditer('((.*)\n|(.+)$)', string))
65
66def get_object_data(obj, skip=[]):
67    def add(item):
68        if item not in skip:
69            value = getattr(obj, item)
70            if isinstance(value, collections.Sized) and isinstance(value, (collections.MutableSequence, collections.Mapping)):
71                value = len(value)
72            data[item] = value
73
74    data = {}
75    for item in utils.iter_class_variables(obj):
76        add(item)
77    for item in utils.iter_class_properties(obj):
78        add(item)
79    return data
80
81class SchemaVisitor(fdb.utils.Visitor):
82    def __init__(self, test, action, follow='dependencies'):
83        self.test = test
84        self.seen = []
85        self.action = action
86        self.follow = follow
87    def default_action(self, obj):
88        if not obj.issystemobject() and self.action in obj.actions:
89            if self.follow == 'dependencies':
90                for dependency in obj.get_dependencies():
91                    d = dependency.depended_on
92                    if d and d not in self.seen:
93                        d.accept(self)
94            elif self.follow == 'dependents':
95                for dependency in obj.get_dependents():
96                    d = dependency.dependent
97                    if d and d not in self.seen:
98                        d.accept(self)
99            if obj not in self.seen:
100                self.test.printout(obj.get_sql_for(self.action))
101                self.seen.append(obj)
102    def visit_TableColumn(self, column):
103        column.table.accept(self)
104    def visit_ViewColumn(self, column):
105        column.view.accept(self)
106    def visit_ProcedureParameter(self, param):
107        param.procedure.accept(self)
108    def visit_FunctionArgument(self, arg):
109        arg.function.accept(self)
110
111class FDBTestBase(unittest.TestCase):
112    def __init__(self, methodName='runTest'):
113        super(FDBTestBase, self).__init__(methodName)
114        self.output = StringIO()
115    def setUp(self):
116        with closing(fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)) as svc:
117            self.version = svc.version
118        if self.version.startswith('2.0'):
119            self.FBTEST_DB = 'fbtest20.fdb'
120            self.version = FB20
121        elif self.version.startswith('2.1'):
122            self.FBTEST_DB = 'fbtest21.fdb'
123            self.version = FB21
124        elif self.version.startswith('2.5'):
125            self.FBTEST_DB = 'fbtest25.fdb'
126            self.version = FB25
127        elif self.version.startswith('3.0'):
128            self.FBTEST_DB = 'fbtest30.fdb'
129            self.version = FB30
130        else:
131            raise Exception("Unsupported Firebird version (%s)" % self.version)
132        #
133        self.cwd = os.getcwd()
134        self.dbpath = self.cwd if os.path.split(self.cwd)[1] == 'test' \
135            else os.path.join(self.cwd, 'test')
136    def clear_output(self):
137        self.output.close()
138        self.output = StringIO()
139    def show_output(self):
140        sys.stdout.write(self.output.getvalue())
141        sys.stdout.flush()
142    def printout(self, text='', newline=True, no_rstrip=False):
143        if no_rstrip:
144            self.output.write(text)
145        else:
146            self.output.write(text.rstrip())
147        if newline:
148            self.output.write('\n')
149        self.output.flush()
150    def printData(self, cur, print_header=True):
151        """Print data from open cursor to stdout."""
152        if print_header:
153            # Print a header.
154            line = []
155            for fieldDesc in cur.description:
156                line.append(fieldDesc[fdb.DESCRIPTION_NAME].ljust(fieldDesc[fdb.DESCRIPTION_DISPLAY_SIZE]))
157            self.printout(' '.join(line))
158            line = []
159            for fieldDesc in cur.description:
160                line.append("-" * max((len(fieldDesc[fdb.DESCRIPTION_NAME]), fieldDesc[fdb.DESCRIPTION_DISPLAY_SIZE])))
161            self.printout(' '.join(line))
162        # For each row, print the value of each field left-justified within
163        # the maximum possible width of that field.
164        fieldIndices = range(len(cur.description))
165        for row in cur:
166            line = []
167            for fieldIndex in fieldIndices:
168                fieldValue = str(row[fieldIndex])
169                fieldMaxWidth = max((len(cur.description[fieldIndex][fdb.DESCRIPTION_NAME]), cur.description[fieldIndex][fdb.DESCRIPTION_DISPLAY_SIZE]))
170                line.append(fieldValue.ljust(fieldMaxWidth))
171            self.printout(' '.join(line))
172
173class TestCreateDrop(FDBTestBase):
174    def setUp(self):
175        super(TestCreateDrop, self).setUp()
176        self.dbfile = os.path.join(self.dbpath, 'droptest.fdb')
177        if os.path.exists(self.dbfile):
178            os.remove(self.dbfile)
179    def test_create_drop(self):
180        with closing(fdb.create_database(host=FBTEST_HOST, database=self.dbfile,
181                                         user=FBTEST_USER, password=FBTEST_PASSWORD)) as con:
182            self.assertEqual(con.sql_dialect, 3)
183            self.assertEqual(con.charset, None)
184            con.drop_database()
185        #
186        with closing(fdb.create_database(host=FBTEST_HOST, port=3050, database=self.dbfile,
187                                         user=FBTEST_USER, password=FBTEST_PASSWORD)) as con:
188            self.assertEqual(con.sql_dialect, 3)
189            self.assertEqual(con.charset, None)
190            con.drop_database()
191        #
192        with closing(fdb.create_database(host=FBTEST_HOST, database=self.dbfile,
193                                         user=FBTEST_USER, password=FBTEST_PASSWORD,
194                                         sql_dialect=1, charset='UTF8')) as con:
195            self.assertEqual(con.sql_dialect, 1)
196            self.assertEqual(con.charset, 'UTF8')
197            con.drop_database()
198
199class TestConnection(FDBTestBase):
200    def setUp(self):
201        super(TestConnection, self).setUp()
202        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
203    def tearDown(self):
204        pass
205    def test_connect(self):
206        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
207            self.assertIsNotNone(con._db_handle)
208            dpb = [1, 0x1c, len(FBTEST_USER)]
209            dpb.extend(ord(x) for x in FBTEST_USER)
210            dpb.extend((0x1d, len(FBTEST_PASSWORD)))
211            dpb.extend(ord(x) for x in FBTEST_PASSWORD)
212            dpb.extend((ord('?'), 1, 3))
213            self.assertEqual(con._dpb, fdb.bs(dpb))
214        with fdb.connect(database=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
215            self.assertIsNotNone(con._db_handle)
216        with fdb.connect(port=3050, database=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
217            self.assertIsNotNone(con._db_handle)
218        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD,
219                         no_gc=1, no_db_triggers=1) as con:
220            dpb.extend([ibase.isc_dpb_no_garbage_collect, 1, 1])
221            dpb.extend([ibase.isc_dpb_no_db_triggers, 1, 1])
222            self.assertEqual(con._dpb, fdb.bs(dpb))
223        # UTF-8 filenames (FB 2.5+)
224        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD, utf8params=True) as con:
225            self.assertIsNotNone(con._db_handle)
226            dpb = [1, 0x1c, len(FBTEST_USER)]
227            dpb.extend(ord(x) for x in FBTEST_USER)
228            dpb.extend((0x1d, len(FBTEST_PASSWORD)))
229            dpb.extend(ord(x) for x in FBTEST_PASSWORD)
230            dpb.extend((ord('?'), 1, 3))
231            dpb.extend((77, 1, 1))
232            self.assertEqual(con._dpb, fdb.bs(dpb))
233    def test_properties(self):
234        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
235            self.assertIn('Firebird', con.server_version)
236            self.assertIn('Firebird', con.firebird_version)
237            self.assertIsInstance(con.version, str)
238            self.assertGreaterEqual(con.engine_version, 2.0)
239            self.assertGreaterEqual(con.ods, 11.0)
240            self.assertIsNone(con.group)
241            self.assertIsNone(con.charset)
242            self.assertEqual(len(con.transactions), 2)
243            self.assertIn(con.main_transaction, con.transactions)
244            self.assertIn(con.query_transaction, con.transactions)
245            self.assertEqual(con.default_tpb, fdb.ISOLATION_LEVEL_READ_COMMITED)
246            self.assertIsInstance(con.schema, sm.Schema)
247            self.assertFalse(con.closed)
248    def test_connect_role(self):
249        rolename = 'role'
250        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER,
251                         password=FBTEST_PASSWORD, role=rolename) as con:
252            self.assertIsNotNone(con._db_handle)
253            dpb = [1, 0x1c, len(FBTEST_USER)]
254            dpb.extend(ord(x) for x in FBTEST_USER)
255            dpb.extend((0x1d, len(FBTEST_PASSWORD)))
256            dpb.extend(ord(x) for x in FBTEST_PASSWORD)
257            dpb.extend((ord('<'), len(rolename)))
258            dpb.extend(ord(x) for x in rolename)
259            dpb.extend((ord('?'), 1, 3))
260            self.assertEqual(con._dpb, fdb.bs(dpb))
261    def test_transaction(self):
262        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
263            self.assertIsNotNone(con.main_transaction)
264            self.assertFalse(con.main_transaction.active)
265            self.assertFalse(con.main_transaction.closed)
266            self.assertEqual(con.main_transaction.default_action, 'commit')
267            self.assertEqual(len(con.main_transaction._connections), 1)
268            self.assertEqual(con.main_transaction._connections[0](), con)
269            con.begin()
270            self.assertFalse(con.main_transaction.closed)
271            con.commit()
272            self.assertFalse(con.main_transaction.active)
273            con.begin()
274            con.rollback()
275            self.assertFalse(con.main_transaction.active)
276            con.begin()
277            con.commit(retaining=True)
278            self.assertTrue(con.main_transaction.active)
279            con.rollback(retaining=True)
280            self.assertTrue(con.main_transaction.active)
281            tr = con.trans()
282            self.assertIsInstance(tr, fdb.Transaction)
283            self.assertFalse(con.main_transaction.closed)
284            self.assertEqual(len(con.transactions), 3)
285            tr.begin()
286            self.assertFalse(tr.closed)
287            con.begin()
288            con.close()
289            self.assertFalse(con.main_transaction.active)
290            self.assertTrue(con.main_transaction.closed)
291            self.assertFalse(tr.active)
292            self.assertTrue(tr.closed)
293    def test_execute_immediate(self):
294        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
295            con.execute_immediate("recreate table t (c1 integer)")
296            con.commit()
297            con.execute_immediate("delete from t")
298            con.commit()
299    def test_database_info(self):
300        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
301            self.assertEqual(con.database_info(fdb.isc_info_db_read_only, 'i'), 0)
302            if con.ods < fdb.ODS_FB_30:
303                self.assertEqual(con.database_info(fdb.isc_info_page_size, 'i'), 4096)
304            else:
305                self.assertEqual(con.database_info(fdb.isc_info_page_size, 'i'), 8192)
306            self.assertEqual(con.database_info(fdb.isc_info_db_sql_dialect, 'i'), 3)
307    def test_db_info(self):
308        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
309            with con.trans() as t1, con.trans() as t2:
310                self.assertListEqual(con.db_info(fdb.isc_info_active_transactions), [])
311                t1.begin()
312                t2.begin()
313                self.assertListEqual(con.db_info(fdb.isc_info_active_transactions),
314                                     [t1.transaction_id, t2.transaction_id])
315            #
316            self.assertEqual(len(con.get_page_contents(0)), con.page_size)
317            #
318            res = con.db_info([fdb.isc_info_page_size, fdb.isc_info_db_read_only,
319                               fdb.isc_info_db_sql_dialect, fdb.isc_info_user_names])
320            if con.ods < fdb.ODS_FB_30:
321                self.assertDictEqual(res, {53: {'SYSDBA': 1}, 62: 3, 14: 4096, 63: 0})
322            else:
323                self.assertDictEqual(res, {53: {'SYSDBA': 1}, 62: 3, 14: 8192, 63: 0})
324            res = con.db_info(fdb.isc_info_read_seq_count)
325            if con.ods < fdb.ODS_FB_30:
326                self.assertDictEqual(res, {0: 98, 1: 1})
327            else:
328                self.assertDictEqual(res, {0: 106, 1: 2})
329            #
330            self.assertIsInstance(con.db_info(fdb.isc_info_allocation), int)
331            self.assertIsInstance(con.db_info(fdb.isc_info_base_level), int)
332            res = con.db_info(fdb.isc_info_db_id)
333            self.assertIsInstance(res, tuple)
334            self.assertEqual(res[0].upper(), self.dbfile.upper())
335            res = con.db_info(ibase.isc_info_implementation)
336            self.assertIsInstance(res, tuple)
337            self.assertEqual(len(res), 2)
338            self.assertIsInstance(res[0], int)
339            self.assertIsInstance(res[1], int)
340            self.assertNotEqual(fdb.IMPLEMENTATION_NAMES.get(res[0], 'Unknown'), 'Unknown')
341            self.assertIn('Firebird', con.db_info(fdb.isc_info_version))
342            self.assertIn('Firebird', con.db_info(fdb.isc_info_firebird_version))
343            self.assertIn(con.db_info(fdb.isc_info_no_reserve), (0, 1))
344            self.assertIn(con.db_info(fdb.isc_info_forced_writes), (0, 1))
345            self.assertIsInstance(con.db_info(fdb.isc_info_base_level), int)
346            self.assertIsInstance(con.db_info(fdb.isc_info_ods_version), int)
347            self.assertIsInstance(con.db_info(fdb.isc_info_ods_minor_version), int)
348
349    def test_info_attributes(self):
350        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
351            self.assertGreater(con.attachment_id, 0)
352            self.assertEqual(con.sql_dialect, 3)
353            self.assertEqual(con.database_sql_dialect, 3)
354            self.assertEqual(con.database_name.upper(), self.dbfile.upper())
355            self.assertIsInstance(con.site_name, str)
356            self.assertIn(con.implementation_id, fdb.IMPLEMENTATION_NAMES.keys())
357            self.assertIn(con.provider_id, fdb.PROVIDER_NAMES.keys())
358            self.assertIn(con.db_class_id, fdb.DB_CLASS_NAMES.keys())
359            self.assertIsInstance(con.creation_date, datetime.datetime)
360            self.assertIn(con.page_size, [4096, 8192, 16384])
361            self.assertEqual(con.sweep_interval, 20000)
362            self.assertTrue(con.space_reservation)
363            self.assertTrue(con.forced_writes)
364            self.assertGreater(con.current_memory, 0)
365            self.assertGreater(con.max_memory, 0)
366            self.assertGreater(con.oit, 0)
367            self.assertGreater(con.oat, 0)
368            self.assertGreater(con.ost, 0)
369            self.assertGreater(con.next_transaction, 0)
370            self.assertFalse(con.isreadonly())
371            #
372            io = con.io_stats
373            self.assertEqual(len(io), 4)
374            self.assertIsInstance(io, dict)
375            s = con.get_table_access_stats()
376            self.assertEqual(len(s), 6)
377            self.assertIsInstance(s[0], fdb.fbcore._TableAccessStats)
378            #
379            with con.trans() as t1, con.trans() as t2:
380                self.assertListEqual(con.get_active_transaction_ids(), [])
381                t1.begin()
382                t2.begin()
383                self.assertListEqual(con.get_active_transaction_ids(),
384                                     [t1.transaction_id, t2.transaction_id])
385                self.assertEqual(con.get_active_transaction_count(), 2)
386
387class TestTransaction(FDBTestBase):
388    def setUp(self):
389        super(TestTransaction, self).setUp()
390        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
391        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
392                               user=FBTEST_USER, password=FBTEST_PASSWORD)
393        #self.con.execute_immediate("recreate table t (c1 integer)")
394        #self.con.commit()
395    def tearDown(self):
396        self.con.execute_immediate("delete from t")
397        self.con.commit()
398        self.con.close()
399    def test_cursor(self):
400        tr = self.con.main_transaction
401        tr.begin()
402        cur = tr.cursor()
403        cur.execute("insert into t (c1) values (1)")
404        tr.commit()
405        cur.execute("select * from t")
406        rows = cur.fetchall()
407        self.assertListEqual(rows, [(1,)])
408        cur.execute("delete from t")
409        tr.commit()
410        self.assertEqual(len(tr.cursors), 1)
411        self.assertIs(tr.cursors[0], cur)
412    def test_context_manager(self):
413        with fdb.TransactionContext(self.con) as tr:
414            cur = tr.cursor()
415            cur.execute("insert into t (c1) values (1)")
416
417        cur.execute("select * from t")
418        rows = cur.fetchall()
419        self.assertListEqual(rows, [(1,)])
420
421        try:
422            with fdb.TransactionContext(self.con) as tr:
423                cur.execute("delete from t")
424                raise Exception()
425        except Exception as e:
426            pass
427
428        cur.execute("select * from t")
429        rows = cur.fetchall()
430        self.assertListEqual(rows, [(1,)])
431
432        with fdb.TransactionContext(self.con) as tr:
433            cur.execute("delete from t")
434
435        cur.execute("select * from t")
436        rows = cur.fetchall()
437        self.assertListEqual(rows, [])
438    def test_savepoint(self):
439        self.con.begin()
440        tr = self.con.main_transaction
441        self.con.execute_immediate("insert into t (c1) values (1)")
442        tr.savepoint('test')
443        self.con.execute_immediate("insert into t (c1) values (2)")
444        tr.rollback(savepoint='test')
445        tr.commit()
446        cur = tr.cursor()
447        cur.execute("select * from t")
448        rows = cur.fetchall()
449        self.assertListEqual(rows, [(1,)])
450    def test_fetch_after_commit(self):
451        self.con.execute_immediate("insert into t (c1) values (1)")
452        self.con.commit()
453        cur = self.con.cursor()
454        cur.execute("select * from t")
455        self.con.commit()
456        with self.assertRaises(fdb.DatabaseError) as cm:
457            rows = cur.fetchall()
458        self.assertTupleEqual(cm.exception.args, ('Cannot fetch from this cursor because it has not executed a statement.',))
459    def test_fetch_after_rollback(self):
460        self.con.execute_immediate("insert into t (c1) values (1)")
461        self.con.rollback()
462        cur = self.con.cursor()
463        cur.execute("select * from t")
464        self.con.commit()
465        with self.assertRaises(fdb.DatabaseError) as cm:
466            rows = cur.fetchall()
467        self.assertTupleEqual(cm.exception.args, ('Cannot fetch from this cursor because it has not executed a statement.',))
468    def test_tpb(self):
469        tpb = fdb.TPB()
470        tpb.access_mode = fdb.isc_tpb_write
471        tpb.isolation_level = fdb.isc_tpb_read_committed
472        tpb.isolation_level = (fdb.isc_tpb_read_committed, fdb.isc_tpb_rec_version)
473        tpb.lock_resolution = fdb.isc_tpb_wait
474        tpb.lock_timeout = 10
475        tpb.table_reservation['COUNTRY'] = (fdb.isc_tpb_protected, fdb.isc_tpb_lock_write)
476        tr = self.con.trans(tpb)
477        tr.begin()
478        tr.commit()
479    def test_transaction_info(self):
480        self.con.begin()
481        tr = self.con.main_transaction
482        info = tr.transaction_info(ibase.isc_info_tra_isolation, 'b')
483        self.assertEqual(info, ibase.b('\x08\x02\x00\x03\x01'))
484        #
485        self.assertGreater(tr.transaction_id, 0)
486        self.assertGreater(tr.oit, 0)
487        self.assertGreater(tr.oat, 0)
488        self.assertGreater(tr.ost, 0)
489        self.assertEqual(tr.lock_timeout, -1)
490        self.assertTupleEqual(tr.isolation, (3, 1))
491        tr.commit()
492
493class TestDistributedTransaction(FDBTestBase):
494    def setUp(self):
495        super(TestDistributedTransaction, self).setUp()
496        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
497        self.db1 = os.path.join(self.dbpath, 'fbtest-1.fdb')
498        self.db2 = os.path.join(self.dbpath, 'fbtest-2.fdb')
499        if not os.path.exists(self.db1):
500            self.con1 = fdb.create_database(host=FBTEST_HOST, database=self.db1,
501                                            user=FBTEST_USER,
502                                            password=FBTEST_PASSWORD)
503        else:
504            self.con1 = fdb.connect(host=FBTEST_HOST, database=self.db1,
505                                    user=FBTEST_USER, password=FBTEST_PASSWORD)
506        self.con1.execute_immediate("recreate table T (PK integer, C1 integer)")
507        self.con1.commit()
508        if not os.path.exists(self.db2):
509            self.con2 = fdb.create_database(host=FBTEST_HOST, database=self.db2,
510                                            user=FBTEST_USER,
511                                            password=FBTEST_PASSWORD)
512        else:
513            self.con2 = fdb.connect(host=FBTEST_HOST, database=self.db2,
514                                    user=FBTEST_USER, password=FBTEST_PASSWORD)
515        self.con2.execute_immediate("recreate table T (PK integer, C1 integer)")
516        self.con2.commit()
517    def tearDown(self):
518        if self.con1 and self.con1.group:
519            # We can't drop database via connection in group
520            self.con1.group.disband()
521        if not self.con1:
522            self.con1 = fdb.connect(host=FBTEST_HOST, database=self.db1,
523                                    user=FBTEST_USER, password=FBTEST_PASSWORD)
524        self.con1.drop_database()
525        self.con1.close()
526        if not self.con2:
527            self.con2 = fdb.connect(host=FBTEST_HOST, database=self.db2,
528                                    user=FBTEST_USER, password=FBTEST_PASSWORD)
529        self.con2.drop_database()
530        self.con2.close()
531    def test_context_manager(self):
532        cg = fdb.ConnectionGroup((self.con1, self.con2))
533
534        q = 'select * from T order by pk'
535        c1 = cg.cursor(self.con1)
536        cc1 = self.con1.cursor()
537        p1 = cc1.prep(q)
538
539        c2 = cg.cursor(self.con2)
540        cc2 = self.con2.cursor()
541        p2 = cc2.prep(q)
542
543        # Distributed transaction: COMMIT
544        with fdb.TransactionContext(cg):
545            c1.execute('insert into t (pk) values (1)')
546            c2.execute('insert into t (pk) values (1)')
547
548        self.con1.commit()
549        cc1.execute(p1)
550        result = cc1.fetchall()
551        self.assertListEqual(result, [(1, None)])
552        self.con2.commit()
553        cc2.execute(p2)
554        result = cc2.fetchall()
555        self.assertListEqual(result, [(1, None)])
556
557        # Distributed transaction: ROLLBACK
558        try:
559            with fdb.TransactionContext(cg):
560                c1.execute('insert into t (pk) values (2)')
561                c2.execute('insert into t (pk) values (2)')
562                raise Exception()
563        except Exception as e:
564            pass
565
566        c1.execute(q)
567        result = c1.fetchall()
568        self.assertListEqual(result, [(1, None)])
569        c2.execute(q)
570        result = c2.fetchall()
571        self.assertListEqual(result, [(1, None)])
572
573        cg.disband()
574
575    def test_simple_dt(self):
576        cg = fdb.ConnectionGroup((self.con1, self.con2))
577        self.assertEqual(self.con1.group, cg)
578        self.assertEqual(self.con2.group, cg)
579
580        q = 'select * from T order by pk'
581        c1 = cg.cursor(self.con1)
582        cc1 = self.con1.cursor()
583        p1 = cc1.prep(q)
584
585        c2 = cg.cursor(self.con2)
586        cc2 = self.con2.cursor()
587        p2 = cc2.prep(q)
588
589        # Distributed transaction: COMMIT
590        c1.execute('insert into t (pk) values (1)')
591        c2.execute('insert into t (pk) values (1)')
592        cg.commit()
593
594        self.con1.commit()
595        cc1.execute(p1)
596        result = cc1.fetchall()
597        self.assertListEqual(result, [(1, None)])
598        self.con2.commit()
599        cc2.execute(p2)
600        result = cc2.fetchall()
601        self.assertListEqual(result, [(1, None)])
602
603        # Distributed transaction: PREPARE+COMMIT
604        c1.execute('insert into t (pk) values (2)')
605        c2.execute('insert into t (pk) values (2)')
606        cg.prepare()
607        cg.commit()
608
609        self.con1.commit()
610        cc1.execute(p1)
611        result = cc1.fetchall()
612        self.assertListEqual(result, [(1, None), (2, None)])
613        self.con2.commit()
614        cc2.execute(p2)
615        result = cc2.fetchall()
616        self.assertListEqual(result, [(1, None), (2, None)])
617
618        # Distributed transaction: SAVEPOINT+ROLLBACK to it
619        c1.execute('insert into t (pk) values (3)')
620        cg.savepoint('CG_SAVEPOINT')
621        c2.execute('insert into t (pk) values (3)')
622        cg.rollback(savepoint='CG_SAVEPOINT')
623
624        c1.execute(q)
625        result = c1.fetchall()
626        self.assertListEqual(result, [(1, None), (2, None), (3, None)])
627        c2.execute(q)
628        result = c2.fetchall()
629        self.assertListEqual(result, [(1, None), (2, None)])
630
631        # Distributed transaction: ROLLBACK
632        cg.rollback()
633
634        self.con1.commit()
635        cc1.execute(p1)
636        result = cc1.fetchall()
637        self.assertListEqual(result, [(1, None), (2, None)])
638        self.con2.commit()
639        cc2.execute(p2)
640        result = cc2.fetchall()
641        self.assertListEqual(result, [(1, None), (2, None)])
642
643        # Distributed transaction: EXECUTE_IMMEDIATE
644        cg.execute_immediate('insert into t (pk) values (3)')
645        cg.commit()
646
647        self.con1.commit()
648        cc1.execute(p1)
649        result = cc1.fetchall()
650        self.assertListEqual(result, [(1, None), (2, None), (3, None)])
651        self.con2.commit()
652        cc2.execute(p2)
653        result = cc2.fetchall()
654        self.assertListEqual(result, [(1, None), (2, None), (3, None)])
655
656        cg.disband()
657        self.assertIsNone(self.con1.group)
658        self.assertIsNone(self.con2.group)
659    def test_limbo_transactions(self):
660        return
661        cg = fdb.ConnectionGroup((self.con1, self.con2))
662        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
663
664        ids1 = svc.get_limbo_transaction_ids(self.db1)
665        self.assertEqual(ids1, [])
666        ids2 = svc.get_limbo_transaction_ids(self.db2)
667        self.assertEqual(ids2, [])
668
669        cg.execute_immediate('insert into t (pk) values (3)')
670        cg.prepare()
671
672        # Force out both connections
673        self.con1._set_group(None)
674        cg._cons.remove(self.con1)
675        del self.con1
676        self.con1 = None
677
678        self.con2._set_group(None)
679        cg._cons.remove(self.con2)
680        del self.con2
681        self.con2 = None
682
683        # Disband will raise an error
684        with self.assertRaises(fdb.DatabaseError) as cm:
685            cg.disband()
686        self.assertTupleEqual(cm.exception.args,
687                              ('Error while rolling back transaction:\n- SQLCODE: -901\n- invalid transaction handle (expecting explicit transaction start)', -901, 335544332))
688
689        ids1 = svc.get_limbo_transaction_ids(self.db1)
690        id1 = ids1[0]
691        ids2 = svc.get_limbo_transaction_ids(self.db2)
692        id2 = ids2[0]
693
694        # Data chould be blocked by limbo transaction
695        if not self.con1:
696            self.con1 = fdb.connect(dsn=self.db1, user=FBTEST_USER,
697                                    password=FBTEST_PASSWORD)
698        if not self.con2:
699            self.con2 = fdb.connect(dsn=self.db2, user=FBTEST_USER,
700                                    password=FBTEST_PASSWORD)
701        c1 = self.con1.cursor()
702        c1.execute('select * from t')
703        with self.assertRaises(fdb.DatabaseError) as cm:
704            row = c1.fetchall()
705        self.assertTupleEqual(cm.exception.args,
706                              ('Cursor.fetchone:\n- SQLCODE: -911\n- record from transaction %i is stuck in limbo' % id1, -911, 335544459))
707        c2 = self.con2.cursor()
708        c2.execute('select * from t')
709        with self.assertRaises(fdb.DatabaseError) as cm:
710            row = c2.fetchall()
711        self.assertTupleEqual(cm.exception.args,
712                              ('Cursor.fetchone:\n- SQLCODE: -911\n- record from transaction %i is stuck in limbo' % id2, -911, 335544459))
713
714        # resolve via service
715        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
716        svc.commit_limbo_transaction(self.db1, id1)
717        svc.rollback_limbo_transaction(self.db2, id2)
718
719        # check the resolution
720        c1 = self.con1.cursor()
721        c1.execute('select * from t')
722        row = c1.fetchall()
723        self.assertListEqual(row, [(3, None)])
724        c2 = self.con2.cursor()
725        c2.execute('select * from t')
726        row = c2.fetchall()
727        self.assertListEqual(row, [])
728
729        svc.close()
730
731class TestCursor(FDBTestBase):
732    def setUp(self):
733        super(TestCursor, self).setUp()
734        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
735        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
736                               user=FBTEST_USER, password=FBTEST_PASSWORD)
737        self.con.execute_immediate("recreate table t (c1 integer primary key)")
738        self.con.commit()
739    def tearDown(self):
740        self.con.execute_immediate("delete from t")
741        self.con.commit()
742        self.con.close()
743    def test_executemany(self):
744        cur = self.con.cursor()
745        cur.executemany("insert into t values(?)", [(1,), (2,)])
746        cur.executemany("insert into t values(?)", [(3,)])
747        cur.executemany("insert into t values(?)", [(4,), (5,), (6,)])
748        self.con.commit()
749        p = cur.prep("insert into t values(?)")
750        cur.executemany(p, [(7,), (8,)])
751        cur.executemany(p, [(9,)])
752        cur.executemany(p, [(10,), (11,), (12,)])
753        self.con.commit()
754        cur.execute("select * from T order by c1")
755        rows = cur.fetchall()
756        self.assertListEqual(rows, [(1,), (2,), (3,), (4,),
757                                    (5,), (6,), (7,), (8,),
758                                    (9,), (10,), (11,), (12,)])
759    def test_iteration(self):
760        if self.con.ods < fdb.ODS_FB_30:
761            data = [('USA', 'Dollar'), ('England', 'Pound'), ('Canada', 'CdnDlr'),
762                    ('Switzerland', 'SFranc'), ('Japan', 'Yen'), ('Italy', 'Lira'),
763                    ('France', 'FFranc'), ('Germany', 'D-Mark'), ('Australia', 'ADollar'),
764                    ('Hong Kong', 'HKDollar'), ('Netherlands', 'Guilder'),
765                    ('Belgium', 'BFranc'), ('Austria', 'Schilling'), ('Fiji', 'FDollar')]
766        else:
767            data = [('USA', 'Dollar'), ('England', 'Pound'), ('Canada', 'CdnDlr'),
768                    ('Switzerland', 'SFranc'), ('Japan', 'Yen'), ('Italy', 'Euro'),
769                    ('France', 'Euro'), ('Germany', 'Euro'), ('Australia', 'ADollar'),
770                    ('Hong Kong', 'HKDollar'), ('Netherlands', 'Euro'), ('Belgium', 'Euro'),
771                    ('Austria', 'Euro'), ('Fiji', 'FDollar'), ('Russia', 'Ruble'),
772                    ('Romania', 'RLeu')]
773        cur = self.con.cursor()
774        cur.execute('select * from country')
775        rows = [row for row in cur]
776        self.assertEqual(len(rows), len(data))
777        self.assertListEqual(rows, data)
778        cur.execute('select * from country')
779        rows = []
780        for row in cur:
781            rows.append(row)
782        self.assertEqual(len(rows), len(data))
783        self.assertListEqual(rows, data)
784        cur.execute('select * from country')
785        i = 0
786        for row in cur:
787            i += 1
788            self.assertIn(row, data)
789        self.assertEqual(i, len(data))
790    def test_description(self):
791        cur = self.con.cursor()
792        cur.execute('select * from country')
793        self.assertEqual(len(cur.description), 2)
794        if ibase.PYTHON_MAJOR_VER == 3:
795            self.assertEqual(repr(cur.description),
796                             "(('COUNTRY', <class 'str'>, 15, 15, 0, 0, False), " \
797                             "('CURRENCY', <class 'str'>, 10, 10, 0, 0, False))")
798        else:
799            self.assertEqual(repr(cur.description),
800                             "(('COUNTRY', <type 'str'>, 15, 15, 0, 0, False), " \
801                             "('CURRENCY', <type 'str'>, 10, 10, 0, 0, False))")
802        cur.execute('select country as CT, currency as CUR from country')
803        self.assertEqual(len(cur.description), 2)
804        cur.execute('select * from customer')
805        if ibase.PYTHON_MAJOR_VER == 3:
806            self.assertEqual(repr(cur.description),
807                             "(('CUST_NO', <class 'int'>, 11, 4, 0, 0, False), " \
808                             "('CUSTOMER', <class 'str'>, 25, 25, 0, 0, False), " \
809                             "('CONTACT_FIRST', <class 'str'>, 15, 15, 0, 0, True), " \
810                             "('CONTACT_LAST', <class 'str'>, 20, 20, 0, 0, True), " \
811                             "('PHONE_NO', <class 'str'>, 20, 20, 0, 0, True), " \
812                             "('ADDRESS_LINE1', <class 'str'>, 30, 30, 0, 0, True), " \
813                             "('ADDRESS_LINE2', <class 'str'>, 30, 30, 0, 0, True), " \
814                             "('CITY', <class 'str'>, 25, 25, 0, 0, True), " \
815                             "('STATE_PROVINCE', <class 'str'>, 15, 15, 0, 0, True), " \
816                             "('COUNTRY', <class 'str'>, 15, 15, 0, 0, True), " \
817                             "('POSTAL_CODE', <class 'str'>, 12, 12, 0, 0, True), " \
818                             "('ON_HOLD', <class 'str'>, 1, 1, 0, 0, True))")
819        else:
820            self.assertEqual(repr(cur.description),
821                             "(('CUST_NO', <type 'int'>, 11, 4, 0, 0, False), " \
822                             "('CUSTOMER', <type 'str'>, 25, 25, 0, 0, False), " \
823                             "('CONTACT_FIRST', <type 'str'>, 15, 15, 0, 0, True), " \
824                             "('CONTACT_LAST', <type 'str'>, 20, 20, 0, 0, True), " \
825                             "('PHONE_NO', <type 'str'>, 20, 20, 0, 0, True), " \
826                             "('ADDRESS_LINE1', <type 'str'>, 30, 30, 0, 0, True), " \
827                             "('ADDRESS_LINE2', <type 'str'>, 30, 30, 0, 0, True), " \
828                             "('CITY', <type 'str'>, 25, 25, 0, 0, True), " \
829                             "('STATE_PROVINCE', <type 'str'>, 15, 15, 0, 0, True), " \
830                             "('COUNTRY', <type 'str'>, 15, 15, 0, 0, True), " \
831                             "('POSTAL_CODE', <type 'str'>, 12, 12, 0, 0, True), " \
832                             "('ON_HOLD', <type 'str'>, 1, 1, 0, 0, True))")
833        cur.execute('select * from job')
834        if ibase.PYTHON_MAJOR_VER == 3:
835            self.assertEqual(repr(cur.description),
836                             "(('JOB_CODE', <class 'str'>, 5, 5, 0, 0, False), " \
837                             "('JOB_GRADE', <class 'int'>, 6, 2, 0, 0, False), " \
838                             "('JOB_COUNTRY', <class 'str'>, 15, 15, 0, 0, False), " \
839                             "('JOB_TITLE', <class 'str'>, 25, 25, 0, 0, False), " \
840                             "('MIN_SALARY', <class 'decimal.Decimal'>, 20, 8, 10, -2, False), " \
841                             "('MAX_SALARY', <class 'decimal.Decimal'>, 20, 8, 10, -2, False), " \
842                             "('JOB_REQUIREMENT', <class 'str'>, 0, 8, 0, 1, True), " \
843                             "('LANGUAGE_REQ', <class 'list'>, -1, 8, 0, 0, True))")
844        else:
845            self.assertEqual(repr(cur.description),
846                             "(('JOB_CODE', <type 'str'>, 5, 5, 0, 0, False), " \
847                             "('JOB_GRADE', <type 'int'>, 6, 2, 0, 0, False), " \
848                             "('JOB_COUNTRY', <type 'str'>, 15, 15, 0, 0, False), " \
849                             "('JOB_TITLE', <type 'str'>, 25, 25, 0, 0, False), " \
850                             "('MIN_SALARY', <class 'decimal.Decimal'>, 20, 8, 10, -2, False), " \
851                             "('MAX_SALARY', <class 'decimal.Decimal'>, 20, 8, 10, -2, False), " \
852                             "('JOB_REQUIREMENT', <type 'str'>, 0, 8, 0, 1, True), " \
853                             "('LANGUAGE_REQ', <type 'list'>, -1, 8, 0, 0, True))")
854        cur.execute('select * from proj_dept_budget')
855        if ibase.PYTHON_MAJOR_VER == 3:
856            self.assertEqual(repr(cur.description),
857                             "(('FISCAL_YEAR', <class 'int'>, 11, 4, 0, 0, False), " \
858                             "('PROJ_ID', <class 'str'>, 5, 5, 0, 0, False), " \
859                             "('DEPT_NO', <class 'str'>, 3, 3, 0, 0, False), " \
860                             "('QUART_HEAD_CNT', <class 'list'>, -1, 8, 0, 0, True), " \
861                             "('PROJECTED_BUDGET', <class 'decimal.Decimal'>, 20, 8, 12, -2, True))")
862        else:
863            self.assertEqual(repr(cur.description),
864                             "(('FISCAL_YEAR', <type 'int'>, 11, 4, 0, 0, False), " \
865                             "('PROJ_ID', <type 'str'>, 5, 5, 0, 0, False), " \
866                             "('DEPT_NO', <type 'str'>, 3, 3, 0, 0, False), " \
867                             "('QUART_HEAD_CNT', <type 'list'>, -1, 8, 0, 0, True), " \
868                             "('PROJECTED_BUDGET', <class 'decimal.Decimal'>, 20, 8, 12, -2, True))")
869        # Check for precision cache
870        cur2 = self.con.cursor()
871        cur2.execute('select * from proj_dept_budget')
872        if ibase.PYTHON_MAJOR_VER == 3:
873            self.assertEqual(repr(cur2.description),
874                             "(('FISCAL_YEAR', <class 'int'>, 11, 4, 0, 0, False), " \
875                             "('PROJ_ID', <class 'str'>, 5, 5, 0, 0, False), " \
876                             "('DEPT_NO', <class 'str'>, 3, 3, 0, 0, False), " \
877                             "('QUART_HEAD_CNT', <class 'list'>, -1, 8, 0, 0, True), " \
878                             "('PROJECTED_BUDGET', <class 'decimal.Decimal'>, 20, 8, 12, -2, True))")
879        else:
880            self.assertEqual(repr(cur2.description),
881                             "(('FISCAL_YEAR', <type 'int'>, 11, 4, 0, 0, False), " \
882                             "('PROJ_ID', <type 'str'>, 5, 5, 0, 0, False), " \
883                             "('DEPT_NO', <type 'str'>, 3, 3, 0, 0, False), " \
884                             "('QUART_HEAD_CNT', <type 'list'>, -1, 8, 0, 0, True), " \
885                             "('PROJECTED_BUDGET', <class 'decimal.Decimal'>, 20, 8, 12, -2, True))")
886    def test_exec_after_close(self):
887        cur = self.con.cursor()
888        cur.execute('select * from country')
889        row = cur.fetchone()
890        self.assertTupleEqual(row, ('USA', 'Dollar'))
891        cur.close()
892        cur.execute('select * from country')
893        row = cur.fetchone()
894        self.assertTupleEqual(row, ('USA', 'Dollar'))
895    def test_fetchone(self):
896        cur = self.con.cursor()
897        cur.execute('select * from country')
898        row = cur.fetchone()
899        self.assertTupleEqual(row, ('USA', 'Dollar'))
900    def test_fetchall(self):
901        cur = self.con.cursor()
902        cur.execute('select * from country')
903        rows = cur.fetchall()
904        if self.con.ods < fdb.ODS_FB_30:
905            self.assertListEqual(rows,
906                                 [('USA', 'Dollar'), ('England', 'Pound'), ('Canada', 'CdnDlr'),
907                                  ('Switzerland', 'SFranc'), ('Japan', 'Yen'), ('Italy', 'Lira'),
908                                  ('France', 'FFranc'), ('Germany', 'D-Mark'), ('Australia', 'ADollar'),
909                                  ('Hong Kong', 'HKDollar'), ('Netherlands', 'Guilder'),
910                                  ('Belgium', 'BFranc'), ('Austria', 'Schilling'), ('Fiji', 'FDollar')])
911        else:
912            self.assertListEqual(rows,
913                                 [('USA', 'Dollar'), ('England', 'Pound'), ('Canada', 'CdnDlr'),
914                                  ('Switzerland', 'SFranc'), ('Japan', 'Yen'), ('Italy', 'Euro'),
915                                  ('France', 'Euro'), ('Germany', 'Euro'), ('Australia', 'ADollar'),
916                                  ('Hong Kong', 'HKDollar'), ('Netherlands', 'Euro'),
917                                  ('Belgium', 'Euro'), ('Austria', 'Euro'), ('Fiji', 'FDollar'),
918                                  ('Russia', 'Ruble'), ('Romania', 'RLeu')])
919    def test_fetchmany(self):
920        cur = self.con.cursor()
921        cur.execute('select * from country')
922        rows = cur.fetchmany(10)
923        if self.con.ods < fdb.ODS_FB_30:
924            self.assertListEqual(rows,
925                                 [('USA', 'Dollar'), ('England', 'Pound'), ('Canada', 'CdnDlr'),
926                                  ('Switzerland', 'SFranc'), ('Japan', 'Yen'), ('Italy', 'Lira'),
927                                  ('France', 'FFranc'), ('Germany', 'D-Mark'), ('Australia', 'ADollar'),
928                                  ('Hong Kong', 'HKDollar')])
929            rows = cur.fetchmany(10)
930            self.assertListEqual(rows,
931                                 [('Netherlands', 'Guilder'), ('Belgium', 'BFranc'),
932                                  ('Austria', 'Schilling'), ('Fiji', 'FDollar')])
933            rows = cur.fetchmany(10)
934            self.assertEqual(len(rows), 0)
935        else:
936            self.assertListEqual(rows,
937                                 [('USA', 'Dollar'), ('England', 'Pound'), ('Canada', 'CdnDlr'),
938                                  ('Switzerland', 'SFranc'), ('Japan', 'Yen'), ('Italy', 'Euro'),
939                                  ('France', 'Euro'), ('Germany', 'Euro'), ('Australia', 'ADollar'),
940                                  ('Hong Kong', 'HKDollar')])
941            rows = cur.fetchmany(10)
942            self.assertListEqual(rows,
943                                 [('Netherlands', 'Euro'), ('Belgium', 'Euro'), ('Austria', 'Euro'),
944                                  ('Fiji', 'FDollar'), ('Russia', 'Ruble'), ('Romania', 'RLeu')])
945            rows = cur.fetchmany(10)
946            self.assertEqual(len(rows), 0)
947    def test_fetchonemap(self):
948        cur = self.con.cursor()
949        cur.execute('select * from country')
950        row = cur.fetchonemap()
951        self.assertListEqual(row.items(), [('COUNTRY', 'USA'), ('CURRENCY', 'Dollar')])
952    def test_fetchallmap(self):
953        cur = self.con.cursor()
954        cur.execute('select * from country')
955        rows = cur.fetchallmap()
956        if self.con.ods < fdb.ODS_FB_30:
957            self.assertListEqual([row.items() for row in rows],
958                                 [[('COUNTRY', 'USA'), ('CURRENCY', 'Dollar')],
959                                  [('COUNTRY', 'England'), ('CURRENCY', 'Pound')],
960                                  [('COUNTRY', 'Canada'), ('CURRENCY', 'CdnDlr')],
961                                  [('COUNTRY', 'Switzerland'), ('CURRENCY', 'SFranc')],
962                                  [('COUNTRY', 'Japan'), ('CURRENCY', 'Yen')],
963                                  [('COUNTRY', 'Italy'), ('CURRENCY', 'Lira')],
964                                  [('COUNTRY', 'France'), ('CURRENCY', 'FFranc')],
965                                  [('COUNTRY', 'Germany'), ('CURRENCY', 'D-Mark')],
966                                  [('COUNTRY', 'Australia'), ('CURRENCY', 'ADollar')],
967                                  [('COUNTRY', 'Hong Kong'), ('CURRENCY', 'HKDollar')],
968                                  [('COUNTRY', 'Netherlands'), ('CURRENCY', 'Guilder')],
969                                  [('COUNTRY', 'Belgium'), ('CURRENCY', 'BFranc')],
970                                  [('COUNTRY', 'Austria'), ('CURRENCY', 'Schilling')],
971                                  [('COUNTRY', 'Fiji'), ('CURRENCY', 'FDollar')]])
972        else:
973            self.assertListEqual([row.items() for row in rows],
974                                 [[('COUNTRY', 'USA'), ('CURRENCY', 'Dollar')],
975                                  [('COUNTRY', 'England'), ('CURRENCY', 'Pound')],
976                                  [('COUNTRY', 'Canada'), ('CURRENCY', 'CdnDlr')],
977                                  [('COUNTRY', 'Switzerland'), ('CURRENCY', 'SFranc')],
978                                  [('COUNTRY', 'Japan'), ('CURRENCY', 'Yen')],
979                                  [('COUNTRY', 'Italy'), ('CURRENCY', 'Euro')],
980                                  [('COUNTRY', 'France'), ('CURRENCY', 'Euro')],
981                                  [('COUNTRY', 'Germany'), ('CURRENCY', 'Euro')],
982                                  [('COUNTRY', 'Australia'), ('CURRENCY', 'ADollar')],
983                                  [('COUNTRY', 'Hong Kong'), ('CURRENCY', 'HKDollar')],
984                                  [('COUNTRY', 'Netherlands'), ('CURRENCY', 'Euro')],
985                                  [('COUNTRY', 'Belgium'), ('CURRENCY', 'Euro')],
986                                  [('COUNTRY', 'Austria'), ('CURRENCY', 'Euro')],
987                                  [('COUNTRY', 'Fiji'), ('CURRENCY', 'FDollar')],
988                                  [('COUNTRY', 'Russia'), ('CURRENCY', 'Ruble')],
989                                  [('COUNTRY', 'Romania'), ('CURRENCY', 'RLeu')]])
990    def test_fetchmanymap(self):
991        cur = self.con.cursor()
992        cur.execute('select * from country')
993        rows = cur.fetchmanymap(10)
994        if self.con.ods < fdb.ODS_FB_30:
995            self.assertListEqual([row.items() for row in rows],
996                                 [[('COUNTRY', 'USA'), ('CURRENCY', 'Dollar')],
997                                  [('COUNTRY', 'England'), ('CURRENCY', 'Pound')],
998                                  [('COUNTRY', 'Canada'), ('CURRENCY', 'CdnDlr')],
999                                  [('COUNTRY', 'Switzerland'), ('CURRENCY', 'SFranc')],
1000                                  [('COUNTRY', 'Japan'), ('CURRENCY', 'Yen')],
1001                                  [('COUNTRY', 'Italy'), ('CURRENCY', 'Lira')],
1002                                  [('COUNTRY', 'France'), ('CURRENCY', 'FFranc')],
1003                                  [('COUNTRY', 'Germany'), ('CURRENCY', 'D-Mark')],
1004                                  [('COUNTRY', 'Australia'), ('CURRENCY', 'ADollar')],
1005                                  [('COUNTRY', 'Hong Kong'), ('CURRENCY', 'HKDollar')]])
1006            rows = cur.fetchmanymap(10)
1007            self.assertListEqual([row.items() for row in rows],
1008                                 [[('COUNTRY', 'Netherlands'), ('CURRENCY', 'Guilder')],
1009                                  [('COUNTRY', 'Belgium'), ('CURRENCY', 'BFranc')],
1010                                  [('COUNTRY', 'Austria'), ('CURRENCY', 'Schilling')],
1011                                  [('COUNTRY', 'Fiji'), ('CURRENCY', 'FDollar')]])
1012            rows = cur.fetchmany(10)
1013            self.assertEqual(len(rows), 0)
1014        else:
1015            self.assertListEqual([row.items() for row in rows],
1016                                 [[('COUNTRY', 'USA'), ('CURRENCY', 'Dollar')],
1017                                  [('COUNTRY', 'England'), ('CURRENCY', 'Pound')],
1018                                  [('COUNTRY', 'Canada'), ('CURRENCY', 'CdnDlr')],
1019                                  [('COUNTRY', 'Switzerland'), ('CURRENCY', 'SFranc')],
1020                                  [('COUNTRY', 'Japan'), ('CURRENCY', 'Yen')],
1021                                  [('COUNTRY', 'Italy'), ('CURRENCY', 'Euro')],
1022                                  [('COUNTRY', 'France'), ('CURRENCY', 'Euro')],
1023                                  [('COUNTRY', 'Germany'), ('CURRENCY', 'Euro')],
1024                                  [('COUNTRY', 'Australia'), ('CURRENCY', 'ADollar')],
1025                                  [('COUNTRY', 'Hong Kong'), ('CURRENCY', 'HKDollar')]])
1026            rows = cur.fetchmanymap(10)
1027            self.assertListEqual([row.items() for row in rows],
1028                                 [[('COUNTRY', 'Netherlands'), ('CURRENCY', 'Euro')],
1029                                  [('COUNTRY', 'Belgium'), ('CURRENCY', 'Euro')],
1030                                  [('COUNTRY', 'Austria'), ('CURRENCY', 'Euro')],
1031                                  [('COUNTRY', 'Fiji'), ('CURRENCY', 'FDollar')],
1032                                  [('COUNTRY', 'Russia'), ('CURRENCY', 'Ruble')],
1033                                  [('COUNTRY', 'Romania'), ('CURRENCY', 'RLeu')]])
1034            rows = cur.fetchmany(10)
1035            self.assertEqual(len(rows), 0)
1036    def test_rowcount(self):
1037        cur = self.con.cursor()
1038        self.assertEqual(cur.rowcount, -1)
1039        cur.execute('select * from project')
1040        self.assertEqual(cur.rowcount, 0)
1041        cur.fetchone()
1042        rcount = 1 if FBTEST_HOST == '' and self.con.engine_version >= 3.0 else 6
1043        self.assertEqual(cur.rowcount, rcount)
1044    def test_name(self):
1045        def assign_name():
1046            cur.name = 'testx'
1047        cur = self.con.cursor()
1048        self.assertIsNone(cur.name)
1049        self.assertRaises(fdb.ProgrammingError, assign_name)
1050        cur.execute('select * from country')
1051        cur.name = 'test'
1052        self.assertEqual(cur.name, 'test')
1053        self.assertRaises(fdb.ProgrammingError, assign_name)
1054    def test_use_after_close(self):
1055        cmd = 'select * from country'
1056        cur = self.con.cursor()
1057        cur.execute(cmd)
1058        cur.close()
1059        cur.execute(cmd)
1060        row = cur.fetchone()
1061        self.assertTupleEqual(row, ('USA', 'Dollar'))
1062
1063class TestPreparedStatement(FDBTestBase):
1064    def setUp(self):
1065        super(TestPreparedStatement, self).setUp()
1066        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
1067        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1068                               user=FBTEST_USER, password=FBTEST_PASSWORD)
1069        #self.con.execute_immediate("recreate table t (c1 integer)")
1070        #self.con.commit()
1071    def tearDown(self):
1072        self.con.execute_immediate("delete from t")
1073        self.con.commit()
1074        self.con.close()
1075    def test_basic(self):
1076        cur = self.con.cursor()
1077        ps = cur.prep('select * from country')
1078        self.assertEqual(ps._in_sqlda.sqln, 10)
1079        self.assertEqual(ps._in_sqlda.sqld, 0)
1080        self.assertEqual(ps._out_sqlda.sqln, 10)
1081        self.assertEqual(ps._out_sqlda.sqld, 2)
1082        self.assertEqual(ps.statement_type, 1)
1083        self.assertEqual(ps.sql, 'select * from country')
1084    def test_get_plan(self):
1085        cur = self.con.cursor()
1086        ps = cur.prep('select * from job')
1087        self.assertEqual(ps.plan, "PLAN (JOB NATURAL)")
1088    def test_execution(self):
1089        cur = self.con.cursor()
1090        ps = cur.prep('select * from country')
1091        cur.execute(ps)
1092        row = cur.fetchone()
1093        self.assertTupleEqual(row, ('USA', 'Dollar'))
1094    def test_wrong_cursor(self):
1095        cur = self.con.cursor()
1096        cur2 = self.con.cursor()
1097        ps = cur.prep('select * from country')
1098        with self.assertRaises(ValueError) as cm:
1099            cur2.execute(ps)
1100        self.assertTupleEqual(cm.exception.args,
1101                              ('PreparedStatement was created by different Cursor.',))
1102
1103
1104class TestArrays(FDBTestBase):
1105    def setUp(self):
1106        super(TestArrays, self).setUp()
1107        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
1108        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1109                               user=FBTEST_USER, password=FBTEST_PASSWORD)
1110        tbl = """recreate table AR (c1 integer,
1111                                    c2 integer[1:4,0:3,1:2],
1112                                    c3 varchar(15)[0:5,1:2],
1113                                    c4 char(5)[5],
1114                                    c5 timestamp[2],
1115                                    c6 time[2],
1116                                    c7 decimal(10,2)[2],
1117                                    c8 numeric(10,2)[2],
1118                                    c9 smallint[2],
1119                                    c10 bigint[2],
1120                                    c11 float[2],
1121                                    c12 double precision[2],
1122                                    c13 decimal(10,1)[2],
1123                                    c14 decimal(10,5)[2],
1124                                    c15 decimal(18,5)[2]
1125                                    )
1126"""
1127        #
1128        self.c2 = [[[1, 1], [2, 2], [3, 3], [4, 4]], [[5, 5], [6, 6], [7, 7], [8, 8]], [[9, 9], [10, 10], [11, 11], [12, 12]], [[13, 13], [14, 14], [15, 15], [16, 16]]]
1129        self.c3 = [['a', 'a'], ['bb', 'bb'], ['ccc', 'ccc'], ['dddd', 'dddd'], ['eeeee', 'eeeee'], ['fffffff78901234', 'fffffff78901234']]
1130        self.c4 = ['a    ', 'bb   ', 'ccc  ', 'dddd ', 'eeeee']
1131        self.c5 = [datetime.datetime(2012, 11, 22, 12, 8, 24, 474800), datetime.datetime(2012, 11, 22, 12, 8, 24, 474800)]
1132        self.c6 = [datetime.time(12, 8, 24, 474800), datetime.time(12, 8, 24, 474800)]
1133        self.c7 = [decimal.Decimal('10.22'), decimal.Decimal('100000.33')]
1134        self.c8 = [decimal.Decimal('10.22'), decimal.Decimal('100000.33')]
1135        self.c9 = [1, 0]
1136        self.c10 = [5555555, 7777777]
1137        self.c11 = [3.140000104904175, 3.140000104904175]
1138        self.c12 = [3.14, 3.14]
1139        self.c13 = [decimal.Decimal('10.2'), decimal.Decimal('100000.3')]
1140        self.c14 = [decimal.Decimal('10.22222'), decimal.Decimal('100000.333')]
1141        self.c15 = [decimal.Decimal('1000000000000.22222'), decimal.Decimal('1000000000000.333')]
1142        self.c16 = [True, False, True]
1143        #self.con.execute_immediate(tbl)
1144        #self.con.commit()
1145        #cur = self.con.cursor()
1146        #cur.execute("insert into ar (c1,c2,c3,c4,c5,c6,c7,c8,c9,c10,c11,c12) values (1,?,?,?,?,?,?,?,?,?,?,?)",
1147                    #[self.c2,self.c3,self.c4,self.c5,self.c6,self.c7,self.c8,self.c9,
1148                        #self.c10,self.c11,self.c12])
1149        #cur.execute("insert into ar (c1,c2) values (2,?)",[self.c2])
1150        #cur.execute("insert into ar (c1,c3) values (3,?)",[self.c3])
1151        #cur.execute("insert into ar (c1,c4) values (4,?)",[self.c4])
1152        #cur.execute("insert into ar (c1,c5) values (5,?)",[self.c5])
1153        #cur.execute("insert into ar (c1,c6) values (6,?)",[self.c6])
1154        #cur.execute("insert into ar (c1,c7) values (7,?)",[self.c7])
1155        #cur.execute("insert into ar (c1,c8) values (8,?)",[self.c8])
1156        #cur.execute("insert into ar (c1,c9) values (9,?)",[self.c9])
1157        #cur.execute("insert into ar (c1,c10) values (10,?)",[self.c10])
1158        #cur.execute("insert into ar (c1,c11) values (11,?)",[self.c11])
1159        #cur.execute("insert into ar (c1,c12) values (12,?)",[self.c12])
1160        #cur.execute("insert into ar (c1,c13) values (13,?)",[self.c13])
1161        #cur.execute("insert into ar (c1,c14) values (14,?)",[self.c14])
1162        #cur.execute("insert into ar (c1,c15) values (15,?)",[self.c15])
1163        #self.con.commit()
1164    def tearDown(self):
1165        self.con.execute_immediate("delete from AR where c1>=100")
1166        self.con.commit()
1167        self.con.close()
1168    def test_basic(self):
1169        cur = self.con.cursor()
1170        cur.execute("select LANGUAGE_REQ from job "\
1171                    "where job_code='Eng' and job_grade=3 and job_country='Japan'")
1172        row = cur.fetchone()
1173        self.assertTupleEqual(row,
1174                              (['Japanese\n', 'Mandarin\n', 'English\n', '\n', '\n'],))
1175        cur.execute('select QUART_HEAD_CNT from proj_dept_budget')
1176        row = cur.fetchall()
1177        self.assertListEqual(row,
1178                             [([1, 1, 1, 0],), ([3, 2, 1, 0],), ([0, 0, 0, 1],), ([2, 1, 0, 0],),
1179                              ([1, 1, 0, 0],), ([1, 1, 0, 0],), ([1, 1, 1, 1],), ([2, 3, 2, 1],),
1180                              ([1, 1, 2, 2],), ([1, 1, 1, 2],), ([1, 1, 1, 2],), ([4, 5, 6, 6],),
1181                              ([2, 2, 0, 3],), ([1, 1, 2, 2],), ([7, 7, 4, 4],), ([2, 3, 3, 3],),
1182                              ([4, 5, 6, 6],), ([1, 1, 1, 1],), ([4, 5, 5, 3],), ([4, 3, 2, 2],),
1183                              ([2, 2, 2, 1],), ([1, 1, 2, 3],), ([3, 3, 1, 1],), ([1, 1, 0, 0],)])
1184    def test_read_full(self):
1185        cur = self.con.cursor()
1186        cur.execute("select c1,c2 from ar where c1=2")
1187        row = cur.fetchone()
1188        self.assertListEqual(row[1], self.c2)
1189        cur.execute("select c1,c3 from ar where c1=3")
1190        row = cur.fetchone()
1191        self.assertListEqual(row[1], self.c3)
1192        cur.execute("select c1,c4 from ar where c1=4")
1193        row = cur.fetchone()
1194        self.assertListEqual(row[1], self.c4)
1195        cur.execute("select c1,c5 from ar where c1=5")
1196        row = cur.fetchone()
1197        self.assertListEqual(row[1], self.c5)
1198        cur.execute("select c1,c6 from ar where c1=6")
1199        row = cur.fetchone()
1200        self.assertListEqual(row[1], self.c6)
1201        cur.execute("select c1,c7 from ar where c1=7")
1202        row = cur.fetchone()
1203        self.assertListEqual(row[1], self.c7)
1204        cur.execute("select c1,c8 from ar where c1=8")
1205        row = cur.fetchone()
1206        self.assertListEqual(row[1], self.c8)
1207        cur.execute("select c1,c9 from ar where c1=9")
1208        row = cur.fetchone()
1209        self.assertListEqual(row[1], self.c9)
1210        cur.execute("select c1,c10 from ar where c1=10")
1211        row = cur.fetchone()
1212        self.assertListEqual(row[1], self.c10)
1213        cur.execute("select c1,c11 from ar where c1=11")
1214        row = cur.fetchone()
1215        self.assertListEqual(row[1], self.c11)
1216        cur.execute("select c1,c12 from ar where c1=12")
1217        row = cur.fetchone()
1218        self.assertListEqual(row[1], self.c12)
1219        cur.execute("select c1,c13 from ar where c1=13")
1220        row = cur.fetchone()
1221        self.assertListEqual(row[1], self.c13)
1222        cur.execute("select c1,c14 from ar where c1=14")
1223        row = cur.fetchone()
1224        self.assertListEqual(row[1], self.c14)
1225        cur.execute("select c1,c15 from ar where c1=15")
1226        row = cur.fetchone()
1227        self.assertListEqual(row[1], self.c15)
1228    def test_write_full(self):
1229        cur = self.con.cursor()
1230        # INTEGER
1231        cur.execute("insert into ar (c1,c2) values (102,?)", [self.c2])
1232        self.con.commit()
1233        cur.execute("select c1,c2 from ar where c1=102")
1234        row = cur.fetchone()
1235        self.assertListEqual(row[1], self.c2)
1236
1237        # VARCHAR
1238        cur.execute("insert into ar (c1,c3) values (103,?)", [self.c3])
1239        self.con.commit()
1240        cur.execute("select c1,c3 from ar where c1=103")
1241        row = cur.fetchone()
1242        self.assertListEqual(row[1], self.c3)
1243
1244        cur.execute("insert into ar (c1,c3) values (103,?)", [tuple(self.c3)])
1245        self.con.commit()
1246        cur.execute("select c1,c3 from ar where c1=103")
1247        row = cur.fetchone()
1248        self.assertListEqual(row[1], self.c3)
1249
1250        # CHAR
1251        cur.execute("insert into ar (c1,c4) values (104,?)", [self.c4])
1252        self.con.commit()
1253        cur.execute("select c1,c4 from ar where c1=104")
1254        row = cur.fetchone()
1255        self.assertListEqual(row[1], self.c4)
1256
1257        # TIMESTAMP
1258        cur.execute("insert into ar (c1,c5) values (105,?)", [self.c5])
1259        self.con.commit()
1260        cur.execute("select c1,c5 from ar where c1=105")
1261        row = cur.fetchone()
1262        self.assertListEqual(row[1], self.c5)
1263
1264        # TIME OK
1265        cur.execute("insert into ar (c1,c6) values (106,?)", [self.c6])
1266        self.con.commit()
1267        cur.execute("select c1,c6 from ar where c1=106")
1268        row = cur.fetchone()
1269        self.assertListEqual(row[1], self.c6)
1270
1271        # DECIMAL(10,2)
1272        cur.execute("insert into ar (c1,c7) values (107,?)", [self.c7])
1273        self.con.commit()
1274        cur.execute("select c1,c7 from ar where c1=107")
1275        row = cur.fetchone()
1276        self.assertListEqual(row[1], self.c7)
1277
1278        # NUMERIC(10,2)
1279        cur.execute("insert into ar (c1,c8) values (108,?)", [self.c8])
1280        self.con.commit()
1281        cur.execute("select c1,c8 from ar where c1=108")
1282        row = cur.fetchone()
1283        self.assertListEqual(row[1], self.c8)
1284
1285        # SMALLINT
1286        cur.execute("insert into ar (c1,c9) values (109,?)", [self.c9])
1287        self.con.commit()
1288        cur.execute("select c1,c9 from ar where c1=109")
1289        row = cur.fetchone()
1290        self.assertListEqual(row[1], self.c9)
1291
1292        # BIGINT
1293        cur.execute("insert into ar (c1,c10) values (110,?)", [self.c10])
1294        self.con.commit()
1295        cur.execute("select c1,c10 from ar where c1=110")
1296        row = cur.fetchone()
1297        self.assertListEqual(row[1], self.c10)
1298
1299        # FLOAT
1300        cur.execute("insert into ar (c1,c11) values (111,?)", [self.c11])
1301        self.con.commit()
1302        cur.execute("select c1,c11 from ar where c1=111")
1303        row = cur.fetchone()
1304        self.assertListEqual(row[1], self.c11)
1305
1306        # DOUBLE PRECISION
1307        cur.execute("insert into ar (c1,c12) values (112,?)", [self.c12])
1308        self.con.commit()
1309        cur.execute("select c1,c12 from ar where c1=112")
1310        row = cur.fetchone()
1311        self.assertListEqual(row[1], self.c12)
1312
1313        # DECIMAL(10,1) OK
1314        cur.execute("insert into ar (c1,c13) values (113,?)", [self.c13])
1315        self.con.commit()
1316        cur.execute("select c1,c13 from ar where c1=113")
1317        row = cur.fetchone()
1318        self.assertListEqual(row[1], self.c13)
1319
1320        # DECIMAL(10,5)
1321        cur.execute("insert into ar (c1,c14) values (114,?)", [self.c14])
1322        self.con.commit()
1323        cur.execute("select c1,c14 from ar where c1=114")
1324        row = cur.fetchone()
1325        self.assertListEqual(row[1], self.c14)
1326
1327        # DECIMAL(18,5)
1328        cur.execute("insert into ar (c1,c15) values (115,?)", [self.c15])
1329        self.con.commit()
1330        cur.execute("select c1,c15 from ar where c1=115")
1331        row = cur.fetchone()
1332        self.assertListEqual(row[1], self.c15)
1333
1334        if self.version == FB30:
1335            # BOOLEAN
1336            cur.execute("insert into ar (c1,c16) values (116,?)", [self.c16])
1337            self.con.commit()
1338            cur.execute("select c1,c16 from ar where c1=116")
1339            row = cur.fetchone()
1340            self.assertListEqual(row[1], self.c16)
1341    def test_write_wrong(self):
1342        cur = self.con.cursor()
1343
1344        with self.assertRaises(ValueError) as cm:
1345            cur.execute("insert into ar (c1,c2) values (102,?)", [self.c3])
1346        self.assertTupleEqual(cm.exception.args, ('Incorrect ARRAY field value.',))
1347        with self.assertRaises(ValueError) as cm:
1348            cur.execute("insert into ar (c1,c2) values (102,?)", [self.c2[:-1]])
1349        self.assertTupleEqual(cm.exception.args, ('Incorrect ARRAY field value.',))
1350
1351class TestInsertData(FDBTestBase):
1352    def setUp(self):
1353        super(TestInsertData, self).setUp()
1354        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
1355        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1356                               user=FBTEST_USER, password=FBTEST_PASSWORD)
1357        self.con2 = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1358                                user=FBTEST_USER, password=FBTEST_PASSWORD,
1359                                charset='utf-8')
1360        #self.con.execute_immediate("recreate table t (c1 integer)")
1361        #self.con.commit()
1362        #self.con.execute_immediate("RECREATE TABLE T2 (C1 Smallint,C2 Integer,C3 Bigint,C4 Char(5),C5 Varchar(10),C6 Date,C7 Time,C8 Timestamp,C9 Blob sub_type 1,C10 Numeric(18,2),C11 Decimal(18,2),C12 Float,C13 Double precision,C14 Numeric(8,4),C15 Decimal(8,4))")
1363        #self.con.commit()
1364    def tearDown(self):
1365        self.con2.close()
1366        self.con.execute_immediate("delete from t")
1367        self.con.execute_immediate("delete from t2")
1368        self.con.commit()
1369        self.con.close()
1370    def test_insert_integers(self):
1371        cur = self.con.cursor()
1372        cur.execute('insert into T2 (C1,C2,C3) values (?,?,?)', [1, 1, 1])
1373        self.con.commit()
1374        cur.execute('select C1,C2,C3 from T2 where C1 = 1')
1375        rows = cur.fetchall()
1376        self.assertListEqual(rows, [(1, 1, 1)])
1377        cur.execute('insert into T2 (C1,C2,C3) values (?,?,?)',
1378                    [2, 1, 9223372036854775807])
1379        cur.execute('insert into T2 (C1,C2,C3) values (?,?,?)',
1380                    [2, 1, -9223372036854775807-1])
1381        self.con.commit()
1382        cur.execute('select C1,C2,C3 from T2 where C1 = 2')
1383        rows = cur.fetchall()
1384        self.assertListEqual(rows,
1385                             [(2, 1, 9223372036854775807), (2, 1, -9223372036854775808)])
1386    def test_insert_char_varchar(self):
1387        cur = self.con.cursor()
1388        cur.execute('insert into T2 (C1,C4,C5) values (?,?,?)', [2, 'AA', 'AA'])
1389        self.con.commit()
1390        cur.execute('select C1,C4,C5 from T2 where C1 = 2')
1391        rows = cur.fetchall()
1392        self.assertListEqual(rows, [(2, 'AA   ', 'AA')])
1393        # Too long values
1394        with self.assertRaises(ValueError) as cm:
1395            cur.execute('insert into T2 (C1,C4) values (?,?)', [3, '123456'])
1396            self.con.commit()
1397        self.assertTupleEqual(cm.exception.args,
1398                              ('Value of parameter (1) is too long, expected 5, found 6',))
1399        with self.assertRaises(ValueError) as cm:
1400            cur.execute('insert into T2 (C1,C5) values (?,?)', [3, '12345678901'])
1401            self.con.commit()
1402        self.assertTupleEqual(cm.exception.args,
1403                              ('Value of parameter (1) is too long, expected 10, found 11',))
1404    def test_insert_datetime(self):
1405        cur = self.con.cursor()
1406        now = datetime.datetime(2011, 11, 13, 15, 00, 1, 200)
1407        cur.execute('insert into T2 (C1,C6,C7,C8) values (?,?,?,?)', [3, now.date(), now.time(), now])
1408        self.con.commit()
1409        cur.execute('select C1,C6,C7,C8 from T2 where C1 = 3')
1410        rows = cur.fetchall()
1411        self.assertListEqual(rows,
1412                             [(3, datetime.date(2011, 11, 13), datetime.time(15, 0, 1, 200),
1413                               datetime.datetime(2011, 11, 13, 15, 0, 1, 200))])
1414
1415        cur.execute('insert into T2 (C1,C6,C7,C8) values (?,?,?,?)', [4, '2011-11-13', '15:0:1:200', '2011-11-13 15:0:1:200'])
1416        self.con.commit()
1417        cur.execute('select C1,C6,C7,C8 from T2 where C1 = 4')
1418        rows = cur.fetchall()
1419        self.assertListEqual(rows,
1420                             [(4, datetime.date(2011, 11, 13), datetime.time(15, 0, 1, 200000),
1421                               datetime.datetime(2011, 11, 13, 15, 0, 1, 200000))])
1422    def test_insert_blob(self):
1423        cur = self.con.cursor()
1424        cur2 = self.con2.cursor()
1425        cur.execute('insert into T2 (C1,C9) values (?,?)', [4, 'This is a BLOB!'])
1426        cur.transaction.commit()
1427        cur.execute('select C1,C9 from T2 where C1 = 4')
1428        rows = cur.fetchall()
1429        self.assertListEqual(rows, [(4, 'This is a BLOB!')])
1430        # Non-textual BLOB
1431        blob_data = fdb.bs([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
1432        cur.execute('insert into T2 (C1,C16) values (?,?)', [8, blob_data])
1433        cur.transaction.commit()
1434        cur.execute('select C1,C16 from T2 where C1 = 8')
1435        rows = cur.fetchall()
1436        self.assertListEqual(rows, [(8, blob_data)])
1437        # BLOB bigger than max. segment size
1438        big_blob = '123456789' * 10000
1439        cur.execute('insert into T2 (C1,C9) values (?,?)', [5, big_blob])
1440        cur.transaction.commit()
1441        cur.execute('select C1,C9 from T2 where C1 = 5')
1442        row = cur.fetchone()
1443        self.assertIsInstance(row[1], fdb.BlobReader)
1444        self.assertEqual(row[1].read(), big_blob)
1445        # Unicode in BLOB
1446        blob_text = 'This is a BLOB!'
1447        if not isinstance(blob_text, ibase.myunicode):
1448            blob_text = blob_text.decode('utf-8')
1449        cur2.execute('insert into T2 (C1,C9) values (?,?)', [6, blob_text])
1450        cur2.transaction.commit()
1451        cur2.execute('select C1,C9 from T2 where C1 = 6')
1452        rows = cur2.fetchall()
1453        self.assertListEqual(rows, [(6, blob_text)])
1454        # Unicode non-textual BLOB
1455        with self.assertRaises(TypeError) as cm:
1456            cur2.execute('insert into T2 (C1,C16) values (?,?)', [7, blob_text])
1457        self.assertTupleEqual(cm.exception.args,
1458                              ("Unicode strings are not acceptable input for a non-textual BLOB column.",))
1459    def test_insert_float_double(self):
1460        cur = self.con.cursor()
1461        cur.execute('insert into T2 (C1,C12,C13) values (?,?,?)', [5, 1.0, 1.0])
1462        self.con.commit()
1463        cur.execute('select C1,C12,C13 from T2 where C1 = 5')
1464        rows = cur.fetchall()
1465        self.assertListEqual(rows, [(5, 1.0, 1.0)])
1466        cur.execute('insert into T2 (C1,C12,C13) values (?,?,?)', [6, 1, 1])
1467        self.con.commit()
1468        cur.execute('select C1,C12,C13 from T2 where C1 = 6')
1469        rows = cur.fetchall()
1470        self.assertListEqual(rows, [(6, 1.0, 1.0)])
1471    def test_insert_numeric_decimal(self):
1472        cur = self.con.cursor()
1473        cur.execute('insert into T2 (C1,C10,C11) values (?,?,?)', [6, 1.1, 1.1])
1474        cur.execute('insert into T2 (C1,C10,C11) values (?,?,?)', [6, decimal.Decimal('100.11'), decimal.Decimal('100.11')])
1475        self.con.commit()
1476        cur.execute('select C1,C10,C11 from T2 where C1 = 6')
1477        rows = cur.fetchall()
1478        self.assertListEqual(rows,
1479                             [(6, Decimal('1.1'), Decimal('1.1')),
1480                              (6, Decimal('100.11'), Decimal('100.11'))])
1481    def test_insert_returning(self):
1482        cur = self.con.cursor()
1483        cur.execute('insert into T2 (C1,C10,C11) values (?,?,?) returning C1', [7, 1.1, 1.1])
1484        result = cur.fetchall()
1485        self.assertListEqual(result, [(7,)])
1486    def test_insert_boolean(self):
1487        if self.version == FB30:
1488            cur = self.con.cursor()
1489            cur.execute('insert into T2 (C1,C17) values (?,?) returning C1', [8, True])
1490            cur.execute('insert into T2 (C1,C17) values (?,?) returning C1', [8, False])
1491            cur.execute('select C1,C17 from T2 where C1 = 8')
1492            result = cur.fetchall()
1493            self.assertListEqual(result, [(8, True), (8, False)])
1494
1495class TestStoredProc(FDBTestBase):
1496    def setUp(self):
1497        super(TestStoredProc, self).setUp()
1498        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
1499        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1500                               user=FBTEST_USER, password=FBTEST_PASSWORD)
1501    def tearDown(self):
1502        self.con.close()
1503    def test_callproc(self):
1504        cur = self.con.cursor()
1505        result = cur.callproc('sub_tot_budget', ['100'])
1506        self.assertListEqual(result, ['100'])
1507        row = cur.fetchone()
1508        self.assertTupleEqual(row, (Decimal('3800000'), Decimal('760000'),
1509                                    Decimal('500000'), Decimal('1500000')))
1510        result = cur.callproc('sub_tot_budget', [100])
1511        self.assertListEqual(result, [100])
1512        row = cur.fetchone()
1513        self.assertTupleEqual(row, (Decimal('3800000'), Decimal('760000'),
1514                                    Decimal('500000'), Decimal('1500000')))
1515
1516class TestServices(FDBTestBase):
1517    def setUp(self):
1518        super(TestServices, self).setUp()
1519        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
1520    def test_attach(self):
1521        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1522        svc.close()
1523    def test_query(self):
1524        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1525        self.assertEqual(svc.get_service_manager_version(), 2)
1526        self.assertIn('Firebird', svc.get_server_version())
1527        self.assertIn('Firebird', svc.get_architecture())
1528        x = svc.get_home_directory()
1529        #self.assertEqual(x,'/opt/firebird/')
1530        if svc.engine_version < 3.0:
1531            self.assertIn('security2.fdb', svc.get_security_database_path())
1532        else:
1533            self.assertIn('security3.fdb', svc.get_security_database_path())
1534        x = svc.get_lock_file_directory()
1535        #self.assertEqual(x,'/tmp/firebird/')
1536        x = svc.get_server_capabilities()
1537        self.assertIsInstance(x, type(tuple()))
1538        x = svc.get_message_file_directory()
1539        #self.assertEqual(x,'/opt/firebird/')
1540        con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1541                          user=FBTEST_USER, password=FBTEST_PASSWORD)
1542        con2 = fdb.connect(host=FBTEST_HOST, database='employee',
1543                           user=FBTEST_USER, password=FBTEST_PASSWORD)
1544        self.assertGreaterEqual(len(svc.get_attached_database_names()), 2, "Should work for Superserver, may fail with value 0 for Classic")
1545        self.assertIn(self.dbfile.upper(),
1546                      [s.upper() for s in svc.get_attached_database_names()])
1547
1548        #self.assertIn('/opt/firebird/examples/empbuild/employee.fdb',x)
1549        self.assertGreaterEqual(svc.get_connection_count(), 2)
1550        svc.close()
1551    def test_running(self):
1552        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1553        self.assertFalse(svc.isrunning())
1554        svc.get_log()
1555        #self.assertTrue(svc.isrunning())
1556        self.assertTrue(svc.fetching)
1557        # fetch materialized
1558        log = svc.readlines()
1559        self.assertFalse(svc.isrunning())
1560        svc.close()
1561    def test_wait(self):
1562        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1563        self.assertFalse(svc.isrunning())
1564        svc.get_log()
1565        self.assertTrue(svc.isrunning())
1566        self.assertTrue(svc.fetching)
1567        svc.wait()
1568        self.assertFalse(svc.isrunning())
1569        self.assertFalse(svc.fetching)
1570        svc.close()
1571
1572class TestServices2(FDBTestBase):
1573    def setUp(self):
1574        super(TestServices2, self).setUp()
1575        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
1576        self.fbk = os.path.join(self.dbpath, 'test_employee.fbk')
1577        self.fbk2 = os.path.join(self.dbpath, 'test_employee.fbk2')
1578        self.rfdb = os.path.join(self.dbpath, 'test_employee.fdb')
1579        self.svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1580        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
1581                               user=FBTEST_USER, password=FBTEST_PASSWORD)
1582        if not os.path.exists(self.rfdb):
1583            c = fdb.create_database(host=FBTEST_HOST, database=self.rfdb,
1584                                    user=FBTEST_USER, password=FBTEST_PASSWORD)
1585            c.close()
1586    def tearDown(self):
1587        self.svc.close()
1588        self.con.execute_immediate("delete from t")
1589        self.con.commit()
1590        self.con.close()
1591        if os.path.exists(self.rfdb):
1592            os.remove(self.rfdb)
1593        if os.path.exists(self.fbk):
1594            os.remove(self.fbk)
1595        if os.path.exists(self.fbk2):
1596            os.remove(self.fbk2)
1597    def test_log(self):
1598        def fetchline(line):
1599            output.append(line)
1600        self.svc.get_log()
1601        self.assertTrue(self.svc.fetching)
1602        # fetch materialized
1603        log = self.svc.readlines()
1604        self.assertFalse(self.svc.fetching)
1605        self.assertTrue(log)
1606        self.assertIsInstance(log, type(list()))
1607        # iterate over result
1608        self.svc.get_log()
1609        for line in self.svc:
1610            self.assertIsNotNone(line)
1611            self.assertIsInstance(line, fdb.StringType)
1612        self.assertFalse(self.svc.fetching)
1613        # callback
1614        output = []
1615        self.svc.get_log(callback=fetchline)
1616        self.assertGreater(len(output), 0)
1617        self.assertEqual(output, log)
1618    def test_getLimboTransactionIDs(self):
1619        ids = self.svc.get_limbo_transaction_ids('employee')
1620        self.assertIsInstance(ids, type(list()))
1621    def test_getStatistics(self):
1622        def fetchline(line):
1623            output.append(line)
1624        self.svc.get_statistics('employee')
1625        self.assertTrue(self.svc.fetching)
1626        self.assertTrue(self.svc.isrunning())
1627        # fetch materialized
1628        stats = self.svc.readlines()
1629        self.assertFalse(self.svc.fetching)
1630        self.assertFalse(self.svc.isrunning())
1631        self.assertIsInstance(stats, type(list()))
1632        # iterate over result
1633        self.svc.get_statistics('employee',
1634                                show_system_tables_and_indexes=True,
1635                                show_record_versions=True)
1636        for line in self.svc:
1637            self.assertIsInstance(line, fdb.StringType)
1638        self.assertFalse(self.svc.fetching)
1639        # callback
1640        output = []
1641        self.svc.get_statistics('employee', callback=fetchline)
1642        self.assertGreater(len(output), 0)
1643        # fetch only selected tables
1644        stats = self.svc.get_statistics('employee',
1645                                        show_user_data_pages=True,
1646                                        tables='COUNTRY')
1647        stats = '\n'.join(self.svc.readlines())
1648        self.assertIn('COUNTRY', stats)
1649        self.assertNotIn('JOB', stats)
1650        #
1651        stats = self.svc.get_statistics('employee',
1652                                        show_user_data_pages=True,
1653                                        tables=('COUNTRY', 'PROJECT'))
1654        stats = '\n'.join(self.svc.readlines())
1655        self.assertIn('COUNTRY', stats)
1656        self.assertIn('PROJECT', stats)
1657        self.assertNotIn('JOB', stats)
1658    def test_backup(self):
1659        def fetchline(line):
1660            output.append(line)
1661        self.svc.backup('employee', self.fbk)
1662        self.assertTrue(self.svc.fetching)
1663        self.assertTrue(self.svc.isrunning())
1664        # fetch materialized
1665        report = self.svc.readlines()
1666        self.assertFalse(self.svc.fetching)
1667        self.assertFalse(self.svc.isrunning())
1668        self.assertTrue(os.path.exists(self.fbk))
1669        self.assertIsInstance(report, type(list()))
1670        # iterate over result
1671        self.svc.backup('employee', self.fbk,
1672                        ignore_checksums=1,
1673                        ignore_limbo_transactions=1,
1674                        metadata_only=1,
1675                        collect_garbage=0,
1676                        transportable=0,
1677                        convert_external_tables=1,
1678                        compressed=0,
1679                        no_db_triggers=0)
1680        for line in self.svc:
1681            self.assertIsNotNone(line)
1682            self.assertIsInstance(line, fdb.StringType)
1683        self.assertFalse(self.svc.fetching)
1684        # callback
1685        output = []
1686        self.svc.backup('employee', self.fbk, callback=fetchline)
1687        self.assertGreater(len(output), 0)
1688        # Firebird 3.0 stats
1689        if self.con.ods >= fdb.ODS_FB_30:
1690            output = []
1691            self.svc.backup('employee', self.fbk, callback=fetchline,
1692                            stats=[fdb.services.STATS_TOTAL_TIME, fdb.services.STATS_TIME_DELTA,
1693                                   fdb.services.STATS_PAGE_READS, fdb.services.STATS_PAGE_WRITES])
1694            self.assertGreater(len(output), 0)
1695            self.assertIn('gbak: time     delta  reads  writes ', output)
1696    def test_restore(self):
1697        def fetchline(line):
1698            output.append(line)
1699        output = []
1700        self.svc.backup('employee', self.fbk, callback=fetchline)
1701        self.assertTrue(os.path.exists(self.fbk))
1702        self.svc.restore(self.fbk, self.rfdb, replace=1)
1703        self.assertTrue(self.svc.fetching)
1704        self.assertTrue(self.svc.isrunning())
1705        # fetch materialized
1706        report = self.svc.readlines()
1707        self.assertFalse(self.svc.fetching)
1708        time.sleep(1)  # Sometimes service is still running after there is no more data to fetch (slower shutdown)
1709        self.assertFalse(self.svc.isrunning())
1710        self.assertIsInstance(report, type(list()))
1711        # iterate over result
1712        self.svc.restore(self.fbk, self.rfdb, replace=1)
1713        for line in self.svc:
1714            self.assertIsNotNone(line)
1715            self.assertIsInstance(line, fdb.StringType)
1716        self.assertFalse(self.svc.fetching)
1717        # callback
1718        output = []
1719        self.svc.restore(self.fbk, self.rfdb, replace=1, callback=fetchline)
1720        self.assertGreater(len(output), 0)
1721        # Firebird 3.0 stats
1722        if self.con.ods >= fdb.ODS_FB_30:
1723            output = []
1724            self.svc.restore(self.fbk, self.rfdb, replace=1, callback=fetchline,
1725                             stats=[fdb.services.STATS_TOTAL_TIME, fdb.services.STATS_TIME_DELTA,
1726                                    fdb.services.STATS_PAGE_READS, fdb.services.STATS_PAGE_WRITES])
1727            self.assertGreater(len(output), 0)
1728            self.assertIn('gbak: time     delta  reads  writes ', output)
1729    def test_local_backup(self):
1730        self.svc.backup('employee', self.fbk)
1731        self.svc.wait()
1732        with open(self.fbk, mode='rb') as f:
1733            bkp = f.read()
1734        backup_stream = BytesIO()
1735        self.svc.local_backup('employee', backup_stream)
1736        backup_stream.seek(0)
1737        self.assertEqual(bkp, backup_stream.read())
1738    def test_local_restore(self):
1739        backup_stream = BytesIO()
1740        self.svc.local_backup('employee', backup_stream)
1741        backup_stream.seek(0)
1742        self.svc.local_restore(backup_stream, self.rfdb, replace=1)
1743        self.assertTrue(os.path.exists(self.rfdb))
1744    def test_nbackup(self):
1745        if self.con.engine_version < 2.5:
1746            return
1747        self.svc.nbackup('employee', self.fbk)
1748        self.assertTrue(os.path.exists(self.fbk))
1749    def test_nrestore(self):
1750        if self.con.engine_version < 2.5:
1751            return
1752        self.test_nbackup()
1753        self.assertTrue(os.path.exists(self.fbk))
1754        if os.path.exists(self.rfdb):
1755            os.remove(self.rfdb)
1756        self.svc.nrestore(self.fbk, self.rfdb)
1757        self.assertTrue(os.path.exists(self.rfdb))
1758    def test_trace(self):
1759        if self.con.engine_version < 2.5:
1760            return
1761        trace_config = """<database %s>
1762          enabled true
1763          log_statement_finish true
1764          print_plan true
1765          include_filter %%SELECT%%
1766          exclude_filter %%RDB$%%
1767          time_threshold 0
1768          max_sql_length 2048
1769        </database>
1770        """ % self.dbfile
1771        svc2 = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1772        svcx = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
1773        # Start trace sessions
1774        trace1_id = self.svc.trace_start(trace_config, 'test_trace_1')
1775        trace2_id = svc2.trace_start(trace_config)
1776        # check sessions
1777        sessions = svcx.trace_list()
1778        self.assertIn(trace1_id, sessions)
1779        seq = list(sessions[trace1_id].keys())
1780        seq.sort()
1781        self.assertListEqual(seq,['date', 'flags', 'name', 'user'])
1782        self.assertIn(trace2_id, sessions)
1783        seq = list(sessions[trace2_id].keys())
1784        seq.sort()
1785        self.assertListEqual(seq,['date', 'flags', 'user'])
1786        if self.con.engine_version < 3.0:
1787            self.assertListEqual(sessions[trace1_id]['flags'], ['active', ' admin', ' trace'])
1788            self.assertListEqual(sessions[trace2_id]['flags'], ['active', ' admin', ' trace'])
1789        else:
1790            self.assertListEqual(sessions[trace1_id]['flags'], ['active', ' trace'])
1791            self.assertListEqual(sessions[trace2_id]['flags'], ['active', ' trace'])
1792        # Pause session
1793        svcx.trace_suspend(trace2_id)
1794        self.assertIn('suspend', svcx.trace_list()[trace2_id]['flags'])
1795        # Resume session
1796        svcx.trace_resume(trace2_id)
1797        self.assertIn('active', svcx.trace_list()[trace2_id]['flags'])
1798        # Stop session
1799        svcx.trace_stop(trace2_id)
1800        self.assertNotIn(trace2_id, svcx.trace_list())
1801        # Finalize
1802        svcx.trace_stop(trace1_id)
1803        svc2.close()
1804        svcx.close()
1805    def test_setDefaultPageBuffers(self):
1806        self.svc.set_default_page_buffers(self.rfdb, 100)
1807    def test_setSweepInterval(self):
1808        self.svc.set_sweep_interval(self.rfdb, 10000)
1809    def test_shutdown_bringOnline(self):
1810        if self.con.engine_version < 2.5:
1811            # Basic shutdown/online
1812            self.svc.shutdown(self.rfdb,
1813                              fdb.services.SHUT_LEGACY,
1814                              fdb.services.SHUT_FORCE, 0)
1815            self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1816            self.assertIn('multi-user maintenance', ''.join(self.svc.readlines()))
1817            # Return to normal state
1818            self.svc.bring_online(self.rfdb, fdb.services.SHUT_LEGACY)
1819            self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1820            self.assertNotIn('multi-user maintenance', ''.join(self.svc.readlines()))
1821        else:
1822            # Shutdown database to single-user maintenance mode
1823            self.svc.shutdown(self.rfdb,
1824                              fdb.services.SHUT_SINGLE,
1825                              fdb.services.SHUT_FORCE, 0)
1826            self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1827            self.assertIn('single-user maintenance', ''.join(self.svc.readlines()))
1828            # Enable multi-user maintenance
1829            self.svc.bring_online(self.rfdb, fdb.services.SHUT_MULTI)
1830            self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1831            self.assertIn('multi-user maintenance', ''.join(self.svc.readlines()))
1832
1833            # Go to full shutdown mode, disabling new attachments during 5 seconds
1834            self.svc.shutdown(self.rfdb,
1835                              fdb.services.SHUT_FULL,
1836                              fdb.services.SHUT_DENY_NEW_ATTACHMENTS, 5)
1837            self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1838            self.assertIn('full shutdown', ''.join(self.svc.readlines()))
1839            # Enable single-user maintenance
1840            self.svc.bring_online(self.rfdb, fdb.services.SHUT_SINGLE)
1841            self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1842            self.assertIn('single-user maintenance', ''.join(self.svc.readlines()))
1843            # Return to normal state
1844            self.svc.bring_online(self.rfdb)
1845    def test_setShouldReservePageSpace(self):
1846        self.svc.set_reserve_page_space(self.rfdb, False)
1847        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1848        self.assertIn('no reserve', ''.join(self.svc.readlines()))
1849        self.svc.set_reserve_page_space(self.rfdb, True)
1850        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1851        self.assertNotIn('no reserve', ''.join(self.svc.readlines()))
1852    def test_setWriteMode(self):
1853        # Forced writes
1854        self.svc.set_write_mode(self.rfdb, fdb.services.WRITE_FORCED)
1855        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1856        self.assertIn('force write', ''.join(self.svc.readlines()))
1857        # No Forced writes
1858        self.svc.set_write_mode(self.rfdb, fdb.services.WRITE_BUFFERED)
1859        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1860        self.assertNotIn('force write', ''.join(self.svc.readlines()))
1861    def test_setAccessMode(self):
1862        # Read Only
1863        self.svc.set_access_mode(self.rfdb, fdb.services.ACCESS_READ_ONLY)
1864        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1865        self.assertIn('read only', ''.join(self.svc.readlines()))
1866        # Read/Write
1867        self.svc.set_access_mode(self.rfdb, fdb.services.ACCESS_READ_WRITE)
1868        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1869        self.assertNotIn('read only', ''.join(self.svc.readlines()))
1870    def test_setSQLDialect(self):
1871        self.svc.set_sql_dialect(self.rfdb, 1)
1872        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1873        self.assertIn('Database dialect\t1', ''.join(self.svc.readlines()))
1874        self.svc.set_sql_dialect(self.rfdb, 3)
1875        self.svc.get_statistics(self.rfdb, show_only_db_header_pages=1)
1876        self.assertIn('Database dialect\t3', ''.join(self.svc.readlines()))
1877    def test_activateShadowFile(self):
1878        self.svc.activate_shadow(self.rfdb)
1879    def test_nolinger(self):
1880        if self.con.ods >= fdb.ODS_FB_30:
1881            self.svc.no_linger(self.rfdb)
1882    def test_sweep(self):
1883        self.svc.sweep(self.rfdb)
1884    def test_repair(self):
1885        result = self.svc.repair(self.rfdb)
1886        self.assertFalse(result)
1887    def test_validate(self):
1888        def fetchline(line):
1889            output.append(line)
1890        output = []
1891        self.svc.validate(self.dbfile)
1892        # fetch materialized
1893        report = self.svc.readlines()
1894        self.assertFalse(self.svc.fetching)
1895        self.assertFalse(self.svc.isrunning())
1896        self.assertIsInstance(report, type(list()))
1897        self.assertIn('Validation started', '/n'.join(report))
1898        self.assertIn('Validation finished', '/n'.join(report))
1899        # iterate over result
1900        self.svc.validate(self.dbfile)
1901        for line in self.svc:
1902            self.assertIsNotNone(line)
1903            self.assertIsInstance(line, fdb.StringType)
1904        self.assertFalse(self.svc.fetching)
1905        # callback
1906        output = []
1907        self.svc.validate(self.dbfile, callback=fetchline)
1908        self.assertGreater(len(output), 0)
1909        # Parameters
1910        self.svc.validate(self.dbfile, include_tables='COUNTRY|SALES',
1911                          include_indices='SALESTATX', lock_timeout=-1)
1912        report = '/n'.join(self.svc.readlines())
1913        self.assertIn('(COUNTRY)', report)
1914        self.assertIn('(SALES)', report)
1915        self.assertIn('(SALESTATX)', report)
1916    def test_getUsers(self):
1917        users = self.svc.get_users()
1918        self.assertIsInstance(users, type(list()))
1919        self.assertIsInstance(users[0], fdb.services.User)
1920        self.assertEqual(users[0].name, 'SYSDBA')
1921    def test_manage_user(self):
1922        user = fdb.services.User('FDB_TEST')
1923        user.password = 'FDB_TEST'
1924        user.first_name = 'FDB'
1925        user.middle_name = 'X.'
1926        user.last_name = 'TEST'
1927        try:
1928            self.svc.remove_user(user)
1929        except fdb.DatabaseError as e:
1930            if 'SQLCODE: -85' in e.args[0]:
1931                pass
1932            else:
1933                raise e
1934        self.svc.add_user(user)
1935        self.assertTrue(self.svc.user_exists(user))
1936        self.assertTrue(self.svc.user_exists('FDB_TEST'))
1937        users = [u for u in self.svc.get_users() if u.name == 'FDB_TEST']
1938        self.assertTrue(users)
1939        self.assertEqual(len(users), 1)
1940        #self.assertEqual(users[0].password,'FDB_TEST')
1941        self.assertEqual(users[0].first_name, 'FDB')
1942        self.assertEqual(users[0].middle_name, 'X.')
1943        self.assertEqual(users[0].last_name, 'TEST')
1944        user.password = 'XFDB_TEST'
1945        user.first_name = 'XFDB'
1946        user.middle_name = 'XX.'
1947        user.last_name = 'XTEST'
1948        self.svc.modify_user(user)
1949        users = [u for u in self.svc.get_users() if u.name == 'FDB_TEST']
1950        self.assertTrue(users)
1951        self.assertEqual(len(users), 1)
1952        #self.assertEqual(users[0].password,'XFDB_TEST')
1953        self.assertEqual(users[0].first_name, 'XFDB')
1954        self.assertEqual(users[0].middle_name, 'XX.')
1955        self.assertEqual(users[0].last_name, 'XTEST')
1956        self.svc.remove_user(user)
1957        self.assertFalse(self.svc.user_exists('FDB_TEST'))
1958
1959
1960class TestEvents(FDBTestBase):
1961    def setUp(self):
1962        super(TestEvents, self).setUp()
1963        self.dbfile = os.path.join(self.dbpath, 'fbevents.fdb')
1964        if os.path.exists(self.dbfile):
1965            os.remove(self.dbfile)
1966        self.con = fdb.create_database(host=FBTEST_HOST, database=self.dbfile,
1967                                       user=FBTEST_USER, password=FBTEST_PASSWORD)
1968        c = self.con.cursor()
1969        c.execute("CREATE TABLE T (PK Integer, C1 Integer)")
1970        c.execute("""CREATE TRIGGER EVENTS_AU FOR T ACTIVE
1971BEFORE UPDATE POSITION 0
1972AS
1973BEGIN
1974    if (old.C1 <> new.C1) then
1975        post_event 'c1_updated' ;
1976END""")
1977        c.execute("""CREATE TRIGGER EVENTS_AI FOR T ACTIVE
1978AFTER INSERT POSITION 0
1979AS
1980BEGIN
1981    if (new.c1 = 1) then
1982        post_event 'insert_1' ;
1983    else if (new.c1 = 2) then
1984        post_event 'insert_2' ;
1985    else if (new.c1 = 3) then
1986        post_event 'insert_3' ;
1987    else
1988        post_event 'insert_other' ;
1989END""")
1990        self.con.commit()
1991    def tearDown(self):
1992        self.con.drop_database()
1993        self.con.close()
1994    def test_one_event(self):
1995        def send_events(command_list):
1996            c = self.con.cursor()
1997            for cmd in command_list:
1998                c.execute(cmd)
1999            self.con.commit()
2000
2001        timed_event = threading.Timer(3.0, send_events, args=[["insert into T (PK,C1) values (1,1)",]])
2002        with self.con.event_conduit(['insert_1']) as events:
2003            timed_event.start()
2004            e = events.wait()
2005        timed_event.join()
2006        self.assertDictEqual(e, {'insert_1': 1})
2007    def test_multiple_events(self):
2008        def send_events(command_list):
2009            c = self.con.cursor()
2010            for cmd in command_list:
2011                c.execute(cmd)
2012            self.con.commit()
2013        cmds = ["insert into T (PK,C1) values (1,1)",
2014                "insert into T (PK,C1) values (1,2)",
2015                "insert into T (PK,C1) values (1,3)",
2016                "insert into T (PK,C1) values (1,1)",
2017                "insert into T (PK,C1) values (1,2)",]
2018        timed_event = threading.Timer(3.0, send_events, args=[cmds])
2019        with self.con.event_conduit(['insert_1', 'insert_3']) as events:
2020            timed_event.start()
2021            e = events.wait()
2022        timed_event.join()
2023        self.assertDictEqual(e, {'insert_3': 1, 'insert_1': 2})
2024    def test_20_events(self):
2025        def send_events(command_list):
2026            c = self.con.cursor()
2027            for cmd in command_list:
2028                c.execute(cmd)
2029            self.con.commit()
2030        cmds = ["insert into T (PK,C1) values (1,1)",
2031                "insert into T (PK,C1) values (1,2)",
2032                "insert into T (PK,C1) values (1,3)",
2033                "insert into T (PK,C1) values (1,1)",
2034                "insert into T (PK,C1) values (1,2)",]
2035        self.e = {}
2036        timed_event = threading.Timer(1.0, send_events, args=[cmds])
2037        with self.con.event_conduit(['insert_1', 'A', 'B', 'C', 'D',
2038                                     'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
2039                                     'N', 'O', 'P', 'Q', 'R', 'insert_3']) as events:
2040            timed_event.start()
2041            time.sleep(3)
2042            e = events.wait()
2043        timed_event.join()
2044        self.assertDictEqual(e,
2045                             {'A': 0, 'C': 0, 'B': 0, 'E': 0, 'D': 0, 'G': 0, 'insert_1': 2,
2046                              'I': 0, 'H': 0, 'K': 0, 'J': 0, 'M': 0, 'L': 0, 'O': 0, 'N': 0,
2047                              'Q': 0, 'P': 0, 'R': 0, 'insert_3': 1, 'F': 0})
2048    def test_flush_events(self):
2049        def send_events(command_list):
2050            c = self.con.cursor()
2051            for cmd in command_list:
2052                c.execute(cmd)
2053            self.con.commit()
2054
2055        timed_event = threading.Timer(3.0, send_events, args=[["insert into T (PK,C1) values (1,1)",]])
2056        with self.con.event_conduit(['insert_1']) as events:
2057            send_events(["insert into T (PK,C1) values (1,1)",
2058                         "insert into T (PK,C1) values (1,1)"])
2059            time.sleep(2)
2060            events.flush()
2061            timed_event.start()
2062            e = events.wait()
2063        timed_event.join()
2064        self.assertDictEqual(e, {'insert_1': 1})
2065
2066class TestStreamBLOBs(FDBTestBase):
2067    def setUp(self):
2068        super(TestStreamBLOBs, self).setUp()
2069        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
2070        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
2071                               user=FBTEST_USER, password=FBTEST_PASSWORD)
2072        #self.con.execute_immediate("recreate table t (c1 integer)")
2073        #self.con.commit()
2074        #self.con.execute_immediate("RECREATE TABLE T2 (C1 Smallint,C2 Integer,C3 Bigint,C4 Char(5),C5 Varchar(10),C6 Date,C7 Time,C8 Timestamp,C9 Blob sub_type 1,C10 Numeric(18,2),C11 Decimal(18,2),C12 Float,C13 Double precision,C14 Numeric(8,4),C15 Decimal(8,4))")
2075        #self.con.commit()
2076    def tearDown(self):
2077        self.con.execute_immediate("delete from t")
2078        self.con.execute_immediate("delete from t2")
2079        self.con.commit()
2080        self.con.close()
2081    def testBlobBasic(self):
2082        blob = """Firebird supports two types of blobs, stream and segmented.
2083The database stores segmented blobs in chunks.
2084Each chunk starts with a two byte length indicator followed by however many bytes of data were passed as a segment.
2085Stream blobs are stored as a continuous array of data bytes with no length indicators included."""
2086        cur = self.con.cursor()
2087        cur.execute('insert into T2 (C1,C9) values (?,?)', [4, StringIO(blob)])
2088        self.con.commit()
2089        p = cur.prep('select C1,C9 from T2 where C1 = 4')
2090        p.set_stream_blob('C9')
2091        cur.execute(p)
2092        row = cur.fetchone()
2093        blob_reader = row[1]
2094        ## Necessary to avoid bad BLOB handle on BlobReader.close in tearDown
2095        ## because BLOB handle is no longer valid after table purge
2096        with closing(p):
2097            self.assertEqual(blob_reader.read(20), 'Firebird supports tw')
2098            self.assertEqual(blob_reader.read(20), 'o types of blobs, st')
2099            self.assertEqual(blob_reader.read(400), 'ream and segmented.\nThe database stores segmented blobs in '
2100                             'chunks.\nEach chunk starts with a two byte length indicator '
2101                             'followed by however many bytes of data were passed as '
2102                             'a segment.\nStream blobs are stored as a continuous array '
2103                             'of data bytes with no length indicators included.')
2104            self.assertEqual(blob_reader.read(20), '')
2105            self.assertEqual(blob_reader.tell(), 318)
2106            blob_reader.seek(20)
2107            self.assertEqual(blob_reader.tell(), 20)
2108            self.assertEqual(blob_reader.read(20), 'o types of blobs, st')
2109            blob_reader.seek(0)
2110            self.assertEqual(blob_reader.tell(), 0)
2111            self.assertListEqual(blob_reader.readlines(), StringIO(blob).readlines())
2112            blob_reader.seek(0)
2113            for line in blob_reader:
2114                self.assertIn(line.rstrip('\n'), blob.split('\n'))
2115            blob_reader.seek(0)
2116            self.assertEqual(blob_reader.read(), blob)
2117            blob_reader.seek(-9, os.SEEK_END)
2118            self.assertEqual(blob_reader.read(), 'included.')
2119            blob_reader.seek(-20, os.SEEK_END)
2120            blob_reader.seek(11, os.SEEK_CUR)
2121            self.assertEqual(blob_reader.read(), 'included.')
2122            blob_reader.seek(60)
2123            self.assertEqual(blob_reader.readline(),
2124                             'The database stores segmented blobs in chunks.\n')
2125            self.assertIsInstance(blob_reader.blob_id, ibase.GDS_QUAD)
2126            self.assertTrue(blob_reader.is_text)
2127            self.assertEqual(blob_reader.blob_charset, None)
2128            self.assertEqual(blob_reader.charset, 'UTF-8')
2129    def testBlobExtended(self):
2130        blob = """Firebird supports two types of blobs, stream and segmented.
2131The database stores segmented blobs in chunks.
2132Each chunk starts with a two byte length indicator followed by however many bytes of data were passed as a segment.
2133Stream blobs are stored as a continuous array of data bytes with no length indicators included."""
2134        cur = self.con.cursor()
2135        cur.execute('insert into T2 (C1,C9) values (?,?)', [1, StringIO(blob)])
2136        cur.execute('insert into T2 (C1,C9) values (?,?)', [2, StringIO(blob)])
2137        self.con.commit()
2138        p = cur.prep('select C1,C9 from T2')
2139        p.set_stream_blob('C9')
2140        cur.execute(p)
2141        #rows = [row for row in cur]
2142        # Necessary to avoid bad BLOB handle on BlobReader.close in tearDown
2143        # because BLOB handle is no longer valid after table purge
2144        with closing(p):
2145            for row in cur:
2146                blob_reader = row[1]
2147                self.assertEqual(blob_reader.read(20), 'Firebird supports tw')
2148                self.assertEqual(blob_reader.read(20), 'o types of blobs, st')
2149                self.assertEqual(blob_reader.read(400), 'ream and segmented.\nThe database stores segmented blobs '
2150                                 'in chunks.\nEach chunk starts with a two byte length '
2151                                 'indicator followed by however many bytes of data were '
2152                                 'passed as a segment.\nStream blobs are stored as a '
2153                                 'continuous array of data bytes with no length indicators '
2154                                 'included.')
2155                self.assertEqual(blob_reader.read(20), '')
2156                self.assertEqual(blob_reader.tell(), 318)
2157                blob_reader.seek(20)
2158                self.assertEqual(blob_reader.tell(), 20)
2159                self.assertEqual(blob_reader.read(20), 'o types of blobs, st')
2160                blob_reader.seek(0)
2161                self.assertEqual(blob_reader.tell(), 0)
2162                self.assertListEqual(blob_reader.readlines(),
2163                                     StringIO(blob).readlines())
2164                blob_reader.seek(0)
2165                for line in blob_reader:
2166                    self.assertIn(line.rstrip('\n'), blob.split('\n'))
2167                blob_reader.seek(0)
2168                self.assertEqual(blob_reader.read(), blob)
2169                blob_reader.seek(-9, os.SEEK_END)
2170                self.assertEqual(blob_reader.read(), 'included.')
2171                blob_reader.seek(-20, os.SEEK_END)
2172                blob_reader.seek(11, os.SEEK_CUR)
2173                self.assertEqual(blob_reader.read(), 'included.')
2174                blob_reader.seek(60)
2175                self.assertEqual(blob_reader.readline(),
2176                                 'The database stores segmented blobs in chunks.\n')
2177
2178class TestCharsetConversion(FDBTestBase):
2179    def setUp(self):
2180        super(TestCharsetConversion, self).setUp()
2181        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
2182        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
2183                               user=FBTEST_USER, password=FBTEST_PASSWORD,
2184                               charset='utf8')
2185        #self.con.execute_immediate("recreate table t (c1 integer)")
2186        #self.con.commit()
2187        #self.con.execute_immediate("RECREATE TABLE T2 (C1 Smallint,C2 Integer,C3 Bigint,C4 Char(5),C5 Varchar(10),C6 Date,C7 Time,C8 Timestamp,C9 Blob sub_type 1,C10 Numeric(18,2),C11 Decimal(18,2),C12 Float,C13 Double precision,C14 Numeric(8,4),C15 Decimal(8,4))")
2188        #self.con.commit()
2189    def tearDown(self):
2190        self.con.execute_immediate("delete from t3")
2191        self.con.execute_immediate("delete from t4")
2192        self.con.commit()
2193        self.con.close()
2194    def test_octets(self):
2195        bytestring = fdb.fbcore.bs([1, 2, 3, 4, 5])
2196        cur = self.con.cursor()
2197        cur.execute("insert into T4 (C1, C_OCTETS, V_OCTETS) values (?,?,?)",
2198                    (1, bytestring, bytestring))
2199        self.con.commit()
2200        cur.execute("select C1, C_OCTETS, V_OCTETS from T4 where C1 = 1")
2201        row = cur.fetchone()
2202        if ibase.PYTHON_MAJOR_VER == 3:
2203            self.assertTupleEqual(row,
2204                                  (1, b'\x01\x02\x03\x04\x05', b'\x01\x02\x03\x04\x05'))
2205        else:
2206            self.assertTupleEqual(row,
2207                                  (1, '\x01\x02\x03\x04\x05', '\x01\x02\x03\x04\x05'))
2208    def test_utf82win1250(self):
2209        s5 = 'ěščřž'
2210        s30 = 'ěščřžýáíéúůďťňóĚŠČŘŽÝÁÍÉÚŮĎŤŇÓ'
2211        if ibase.PYTHON_MAJOR_VER != 3:
2212            s5 = s5.decode('utf8')
2213            s30 = s30.decode('utf8')
2214
2215        con1250 = fdb.connect(host=FBTEST_HOST, database=self.dbfile, user=FBTEST_USER,
2216                              password=FBTEST_PASSWORD, charset='win1250')
2217        c_utf8 = self.con.cursor()
2218        c_win1250 = con1250.cursor()
2219
2220        # Insert unicode data
2221        c_utf8.execute("insert into T4 (C1, C_WIN1250, V_WIN1250, C_UTF8, V_UTF8)"
2222                       "values (?,?,?,?,?)",
2223                       (1, s5, s30, s5, s30))
2224        self.con.commit()
2225
2226        # Should return the same unicode content when read from win1250 or utf8 connection
2227        c_win1250.execute("select C1, C_WIN1250, V_WIN1250,"
2228                          "C_UTF8, V_UTF8 from T4 where C1 = 1")
2229        row = c_win1250.fetchone()
2230        self.assertTupleEqual(row, (1, s5, s30, s5, s30))
2231        c_utf8.execute("select C1, C_WIN1250, V_WIN1250,"
2232                       "C_UTF8, V_UTF8 from T4 where C1 = 1")
2233        row = c_utf8.fetchone()
2234        self.assertTupleEqual(row, (1, s5, s30, s5, s30))
2235
2236    def testCharVarchar(self):
2237        s = 'Introdução'
2238        if ibase.PYTHON_MAJOR_VER != 3:
2239            s = s.decode('utf8')
2240        self.assertEqual(len(s), 10)
2241        data = tuple([1, s, s])
2242        cur = self.con.cursor()
2243        cur.execute('insert into T3 (C1,C2,C3) values (?,?,?)', data)
2244        self.con.commit()
2245        cur.execute('select C1,C2,C3 from T3 where C1 = 1')
2246        row = cur.fetchone()
2247        self.assertEqual(row, data)
2248    def testBlob(self):
2249        s = """Introdução
2250
2251Este artigo descreve como você pode fazer o InterBase e o Firebird 1.5
2252coehabitarem pacificamente seu computador Windows. Por favor, note que esta
2253solução não permitirá que o Interbase e o Firebird rodem ao mesmo tempo.
2254Porém você poderá trocar entre ambos com um mínimo de luta. """
2255        if ibase.PYTHON_MAJOR_VER != 3:
2256            s = s.decode('utf8')
2257        self.assertEqual(len(s), 292)
2258        data = tuple([2, s])
2259        b_data = tuple([3, ibase.b('bytestring')])
2260        cur = self.con.cursor()
2261        # Text BLOB
2262        cur.execute('insert into T3 (C1,C4) values (?,?)', data)
2263        self.con.commit()
2264        cur.execute('select C1,C4 from T3 where C1 = 2')
2265        row = cur.fetchone()
2266        self.assertEqual(row, data)
2267        # Insert Unicode into non-textual BLOB
2268        with self.assertRaises(TypeError) as cm:
2269            cur.execute('insert into T3 (C1,C5) values (?,?)', data)
2270            self.con.commit()
2271        self.assertTupleEqual(cm.exception.args,
2272                              ('Unicode strings are not acceptable input for a non-textual BLOB column.',))
2273        # Read binary from non-textual BLOB
2274        cur.execute('insert into T3 (C1,C5) values (?,?)', b_data)
2275        self.con.commit()
2276        cur.execute('select C1,C5 from T3 where C1 = 3')
2277        row = cur.fetchone()
2278        self.assertEqual(row, b_data)
2279
2280class TestSchema(FDBTestBase):
2281    def setUp(self):
2282        super(TestSchema, self).setUp()
2283        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
2284        #self.dbfile = '/home/data/db/employee30.fdb'
2285        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
2286                               user=FBTEST_USER, password=FBTEST_PASSWORD)
2287    def tearDown(self):
2288        self.con.close()
2289    def testSchemaBindClose(self):
2290        s = sm.Schema()
2291        self.assertTrue(s.closed)
2292        s.bind(self.con)
2293        # properties
2294        self.assertIsNone(s.description)
2295        self.assertIsNone(s.linger)
2296        self.assertEqual(s.owner_name, 'SYSDBA')
2297        self.assertEqual(s.default_character_set.name, 'NONE')
2298        if self.con.ods < fdb.ODS_FB_30:
2299            self.assertIsNone(s.security_class)
2300        else:
2301            self.assertEqual(s.security_class, 'SQL$363')
2302        self.assertFalse(s.closed)
2303        #
2304        s.close()
2305        self.assertTrue(s.closed)
2306        s.bind(self.con)
2307        self.assertFalse(s.closed)
2308        #
2309        s.bind(self.con)
2310        self.assertFalse(s.closed)
2311        #
2312        del s
2313    def testSchemaFromConnection(self):
2314        s = self.con.schema
2315        # enum_* disctionaries
2316        self.assertDictEqual(s.enum_param_type_from,
2317                             {0: 'DATATYPE', 1: 'DOMAIN', 2: 'TYPE OF DOMAIN', 3: 'TYPE OF COLUMN'})
2318        if self.con.ods <= fdb.ODS_FB_20:
2319            self.assertDictEqual(s.enum_object_types,
2320                                 {0: 'RELATION', 1: 'VIEW', 2: 'TRIGGER', 3: 'COMPUTED_FIELD',
2321                                  4: 'VALIDATION', 5: 'PROCEDURE', 6: 'EXPRESSION_INDEX',
2322                                  7: 'EXCEPTION', 8: 'USER', 9: 'FIELD', 10: 'INDEX',
2323                                  11: 'DEPENDENT_COUNT', 12: 'USER_GROUP', 13: 'ROLE',
2324                                  14: 'GENERATOR', 15: 'UDF', 16: 'BLOB_FILTER'})
2325            self.assertDictEqual(s.enum_object_type_codes,
2326                                 {'INDEX': 10, 'EXCEPTION': 7, 'GENERATOR': 14, 'UDF': 15,
2327                                  'EXPRESSION_INDEX': 6, 'FIELD': 9, 'COMPUTED_FIELD': 3,
2328                                  'TRIGGER': 2, 'RELATION': 0, 'USER': 8, 'DEPENDENT_COUNT': 11,
2329                                  'USER_GROUP': 12, 'BLOB_FILTER': 16, 'ROLE': 13,
2330                                  'VALIDATION': 4, 'PROCEDURE': 5, 'VIEW': 1})
2331        elif self.con.ods > fdb.ODS_FB_20 and self.con.ods < fdb.ODS_FB_30:
2332            self.assertDictEqual(s.enum_object_types,
2333                                 {0: 'RELATION', 1: 'VIEW', 2: 'TRIGGER', 3: 'COMPUTED_FIELD',
2334                                  4: 'VALIDATION', 5: 'PROCEDURE', 6: 'EXPRESSION_INDEX',
2335                                  7: 'EXCEPTION', 8: 'USER', 9: 'FIELD', 10: 'INDEX',
2336                                  12: 'USER_GROUP', 13: 'ROLE', 14: 'GENERATOR', 15: 'UDF',
2337                                  16: 'BLOB_FILTER', 17: 'COLLATION'})
2338            self.assertDictEqual(s.enum_object_type_codes,
2339                                 {'INDEX': 10, 'EXCEPTION': 7, 'GENERATOR': 14, 'COLLATION': 17,
2340                                  'UDF': 15, 'EXPRESSION_INDEX': 6, 'FIELD': 9,
2341                                  'COMPUTED_FIELD': 3, 'TRIGGER': 2, 'RELATION': 0, 'USER': 8,
2342                                  'USER_GROUP': 12, 'BLOB_FILTER': 16, 'ROLE': 13,
2343                                  'VALIDATION': 4, 'PROCEDURE': 5, 'VIEW': 1})
2344        else:
2345            self.assertDictEqual(s.enum_object_types,
2346                                 {0: 'RELATION', 1: 'VIEW', 2: 'TRIGGER', 3: 'COMPUTED_FIELD',
2347                                  4: 'VALIDATION', 5: 'PROCEDURE', 6: 'EXPRESSION_INDEX',
2348                                  7: 'EXCEPTION', 8: 'USER', 9: 'FIELD', 10: 'INDEX',
2349                                  11: 'CHARACTER_SET', 12: 'USER_GROUP', 13: 'ROLE',
2350                                  14: 'GENERATOR', 15: 'UDF', 16: 'BLOB_FILTER', 17: 'COLLATION',
2351                                  18:'PACKAGE', 19:'PACKAGE BODY'})
2352            self.assertDictEqual(s.enum_object_type_codes,
2353                                 {'INDEX': 10, 'EXCEPTION': 7, 'GENERATOR': 14, 'COLLATION': 17,
2354                                  'UDF': 15, 'EXPRESSION_INDEX': 6, 'FIELD': 9,
2355                                  'COMPUTED_FIELD': 3, 'TRIGGER': 2, 'RELATION': 0, 'USER': 8,
2356                                  'USER_GROUP': 12, 'BLOB_FILTER': 16, 'ROLE': 13,
2357                                  'VALIDATION': 4, 'PROCEDURE': 5, 'VIEW': 1, 'CHARACTER_SET':11,
2358                                  'PACKAGE':18, 'PACKAGE BODY':19})
2359        if self.con.ods <= fdb.ODS_FB_20:
2360            self.assertDictEqual(s.enum_character_set_names,
2361                                 {0: 'NONE', 1: 'BINARY', 2: 'ASCII7', 3: 'SQL_TEXT', 4: 'UTF-8',
2362                                  5: 'SJIS', 6: 'EUCJ', 9: 'DOS_737', 10: 'DOS_437',
2363                                  11: 'DOS_850', 12: 'DOS_865', 13: 'DOS_860', 14: 'DOS_863',
2364                                  15: 'DOS_775', 16: 'DOS_858', 17: 'DOS_862', 18: 'DOS_864',
2365                                  19: 'NEXT', 21: 'ANSI', 22: 'ISO-8859-2', 23: 'ISO-8859-3',
2366                                  34: 'ISO-8859-4', 35: 'ISO-8859-5', 36: 'ISO-8859-6',
2367                                  37: 'ISO-8859-7', 38: 'ISO-8859-8', 39: 'ISO-8859-9',
2368                                  40: 'ISO-8859-13', 44: 'WIN_949', 45: 'DOS_852', 46: 'DOS_857',
2369                                  47: 'DOS_861', 48: 'DOS_866', 49: 'DOS_869', 50: 'CYRL',
2370                                  51: 'WIN_1250', 52: 'WIN_1251', 53: 'WIN_1252', 54: 'WIN_1253',
2371                                  55: 'WIN_1254', 56: 'WIN_950', 57: 'WIN_936', 58: 'WIN_1255',
2372                                  59: 'WIN_1256', 60: 'WIN_1257', 63: 'KOI8R', 64: 'KOI8U',
2373                                  65: 'WIN1258'})
2374        elif self.con.ods == fdb.ODS_FB_21:
2375            self.assertDictEqual(s.enum_character_set_names,
2376                                 {0: 'NONE', 1: 'BINARY', 2: 'ASCII7', 3: 'SQL_TEXT',
2377                                  4: 'UTF-8', 5: 'SJIS', 6: 'EUCJ', 9: 'DOS_737', 10: 'DOS_437',
2378                                  11: 'DOS_850', 12: 'DOS_865', 13: 'DOS_860', 14: 'DOS_863',
2379                                  15: 'DOS_775', 16: 'DOS_858', 17: 'DOS_862', 18: 'DOS_864',
2380                                  19: 'NEXT', 21: 'ANSI', 22: 'ISO-8859-2', 23: 'ISO-8859-3',
2381                                  34: 'ISO-8859-4', 35: 'ISO-8859-5', 36: 'ISO-8859-6',
2382                                  37: 'ISO-8859-7', 38: 'ISO-8859-8', 39: 'ISO-8859-9',
2383                                  40: 'ISO-8859-13', 44: 'WIN_949', 45: 'DOS_852', 46: 'DOS_857',
2384                                  47: 'DOS_861', 48: 'DOS_866', 49: 'DOS_869', 50: 'CYRL',
2385                                  51: 'WIN_1250', 52: 'WIN_1251', 53: 'WIN_1252', 54: 'WIN_1253',
2386                                  55: 'WIN_1254', 56: 'WIN_950', 57: 'WIN_936', 58: 'WIN_1255',
2387                                  59: 'WIN_1256', 60: 'WIN_1257', 63: 'KOI8R', 64: 'KOI8U',
2388                                  65: 'WIN1258', 66: 'TIS620', 67: 'GBK', 68: 'CP943C'})
2389        elif self.con.ods >= fdb.ODS_FB_25:
2390            self.assertDictEqual(s.enum_character_set_names,
2391                                 {0: 'NONE', 1: 'BINARY', 2: 'ASCII7', 3: 'SQL_TEXT', 4: 'UTF-8',
2392                                  5: 'SJIS', 6: 'EUCJ', 9: 'DOS_737', 10: 'DOS_437', 11: 'DOS_850',
2393                                  12: 'DOS_865', 13: 'DOS_860', 14: 'DOS_863', 15: 'DOS_775',
2394                                  16: 'DOS_858', 17: 'DOS_862', 18: 'DOS_864', 19: 'NEXT',
2395                                  21: 'ANSI', 22: 'ISO-8859-2', 23: 'ISO-8859-3', 34: 'ISO-8859-4',
2396                                  35: 'ISO-8859-5', 36: 'ISO-8859-6', 37: 'ISO-8859-7',
2397                                  38: 'ISO-8859-8', 39: 'ISO-8859-9', 40: 'ISO-8859-13',
2398                                  44: 'WIN_949', 45: 'DOS_852', 46: 'DOS_857', 47: 'DOS_861',
2399                                  48: 'DOS_866', 49: 'DOS_869', 50: 'CYRL', 51: 'WIN_1250',
2400                                  52: 'WIN_1251', 53: 'WIN_1252', 54: 'WIN_1253', 55: 'WIN_1254',
2401                                  56: 'WIN_950', 57: 'WIN_936', 58: 'WIN_1255', 59: 'WIN_1256',
2402                                  60: 'WIN_1257', 63: 'KOI8R', 64: 'KOI8U', 65: 'WIN_1258',
2403                                  66: 'TIS620', 67: 'GBK', 68: 'CP943C', 69: 'GB18030'})
2404        if self.con.ods < fdb.ODS_FB_30:
2405            self.assertDictEqual(s.enum_field_types,
2406                                 {35: 'TIMESTAMP', 37: 'VARYING', 7: 'SHORT', 8: 'LONG',
2407                                  9: 'QUAD', 10: 'FLOAT', 12: 'DATE', 45: 'BLOB_ID', 14: 'TEXT',
2408                                  13: 'TIME', 16: 'INT64', 40: 'CSTRING', 27: 'DOUBLE',
2409                                  261: 'BLOB'})
2410        else:
2411            self.assertDictEqual(s.enum_field_types,
2412                                 {35: 'TIMESTAMP', 37: 'VARYING', 7: 'SHORT', 8: 'LONG',
2413                                  9: 'QUAD', 10: 'FLOAT', 12: 'DATE', 45: 'BLOB_ID', 14: 'TEXT',
2414                                  13: 'TIME', 16: 'INT64', 40: 'CSTRING', 27: 'DOUBLE',
2415                                  261: 'BLOB', 23:'BOOLEAN'})
2416        if self.con.ods <= fdb.ODS_FB_20:
2417            self.assertDictEqual(s.enum_field_subtypes,
2418                                 {0: 'BINARY', 1: 'TEXT', 2: 'BLR', 3: 'ACL', 4: 'RANGES',
2419                                  5: 'SUMMARY', 6: 'FORMAT', 7: 'TRANSACTION_DESCRIPTION',
2420                                  8: 'EXTERNAL_FILE_DESCRIPTION'})
2421        elif self.con.ods > fdb.ODS_FB_20:
2422            self.assertDictEqual(s.enum_field_subtypes,
2423                                 {0: 'BINARY', 1: 'TEXT', 2: 'BLR', 3: 'ACL', 4: 'RANGES',
2424                                  5: 'SUMMARY', 6: 'FORMAT', 7: 'TRANSACTION_DESCRIPTION',
2425                                  8: 'EXTERNAL_FILE_DESCRIPTION', 9: 'DEBUG_INFORMATION'})
2426        self.assertDictEqual(s.enum_function_types, {0: 'VALUE', 1: 'BOOLEAN'})
2427        self.assertDictEqual(s.enum_mechanism_types,
2428                             {0: 'BY_VALUE', 1: 'BY_REFERENCE',
2429                              2: 'BY_VMS_DESCRIPTOR', 3: 'BY_ISC_DESCRIPTOR',
2430                              4: 'BY_SCALAR_ARRAY_DESCRIPTOR',
2431                              5: 'BY_REFERENCE_WITH_NULL'})
2432        if self.con.ods <= fdb.ODS_FB_20:
2433            self.assertDictEqual(s.enum_parameter_mechanism_types, {})
2434        elif self.con.ods > fdb.ODS_FB_20:
2435            self.assertDictEqual(s.enum_parameter_mechanism_types,
2436                                 {0: 'NORMAL', 1: 'TYPE OF'})
2437        self.assertDictEqual(s.enum_procedure_types,
2438                             {0: 'LEGACY', 1: 'SELECTABLE', 2: 'EXECUTABLE'})
2439        if self.con.ods <= fdb.ODS_FB_20:
2440            self.assertDictEqual(s.enum_relation_types, {})
2441        elif self.con.ods > fdb.ODS_FB_20:
2442            self.assertDictEqual(s.enum_relation_types,
2443                                 {0: 'PERSISTENT', 1: 'VIEW', 2: 'EXTERNAL', 3: 'VIRTUAL',
2444                                  4: 'GLOBAL_TEMPORARY_PRESERVE', 5: 'GLOBAL_TEMPORARY_DELETE'})
2445        if self.con.ods < fdb.ODS_FB_30:
2446            self.assertDictEqual(s.enum_system_flag_types,
2447                                 {0: 'USER', 1: 'SYSTEM', 2: 'QLI', 3: 'CHECK_CONSTRAINT',
2448                                  4: 'REFERENTIAL_CONSTRAINT', 5: 'VIEW_CHECK'})
2449        else:
2450            self.assertDictEqual(s.enum_system_flag_types,
2451                                 {0: 'USER', 1: 'SYSTEM', 2: 'QLI', 3: 'CHECK_CONSTRAINT',
2452                                  4: 'REFERENTIAL_CONSTRAINT', 5: 'VIEW_CHECK', 6: 'IDENTITY_GENERATOR'})
2453        self.assertDictEqual(s.enum_transaction_state_types,
2454                             {1: 'LIMBO', 2: 'COMMITTED', 3: 'ROLLED_BACK'})
2455        if self.con.ods <= fdb.ODS_FB_20:
2456            self.assertDictEqual(s.enum_trigger_types,
2457                                 {1: 'PRE_STORE', 2: 'POST_STORE', 3: 'PRE_MODIFY',
2458                                  4: 'POST_MODIFY', 5: 'PRE_ERASE', 6: 'POST_ERASE'})
2459        elif self.con.ods > fdb.ODS_FB_20:
2460            self.assertDictEqual(s.enum_trigger_types,
2461                                 {8192: 'CONNECT', 1: 'PRE_STORE', 2: 'POST_STORE',
2462                                  3: 'PRE_MODIFY', 4: 'POST_MODIFY', 5: 'PRE_ERASE',
2463                                  6: 'POST_ERASE', 8193: 'DISCONNECT', 8194: 'TRANSACTION_START',
2464                                  8195: 'TRANSACTION_COMMIT', 8196: 'TRANSACTION_ROLLBACK'})
2465        if self.con.ods >= fdb.ODS_FB_30:
2466            self.assertDictEqual(s.enum_parameter_types,
2467                                 {0: 'INPUT', 1: 'OUTPUT'})
2468            self.assertDictEqual(s.enum_index_activity_flags,
2469                                 {0: 'ACTIVE', 1: 'INACTIVE'})
2470            self.assertDictEqual(s.enum_index_unique_flags,
2471                                 {0: 'NON_UNIQUE', 1: 'UNIQUE'})
2472            self.assertDictEqual(s.enum_trigger_activity_flags,
2473                                 {0: 'ACTIVE', 1: 'INACTIVE'})
2474            self.assertDictEqual(s.enum_grant_options,
2475                                 {0: 'NONE', 1: 'GRANT_OPTION', 2: 'ADMIN_OPTION'})
2476            self.assertDictEqual(s.enum_page_types,
2477                                 {1: 'HEADER', 2: 'PAGE_INVENTORY', 3: 'TRANSACTION_INVENTORY',
2478                                  4: 'POINTER', 5: 'DATA', 6: 'INDEX_ROOT', 7: 'INDEX_BUCKET',
2479                                  8: 'BLOB', 9: 'GENERATOR', 10: 'SCN_INVENTORY'})
2480            self.assertDictEqual(s.enum_privacy_flags,
2481                                 {0: 'PUBLIC', 1: 'PRIVATE'})
2482            self.assertDictEqual(s.enum_legacy_flags,
2483                                 {0: 'NEW_STYLE', 1: 'LEGACY_STYLE'})
2484            self.assertDictEqual(s.enum_determinism_flags,
2485                                 {0: 'NON_DETERMINISTIC', 1: 'DETERMINISTIC'})
2486
2487        # properties
2488        self.assertIsNone(s.description)
2489        self.assertEqual(s.owner_name, 'SYSDBA')
2490        self.assertEqual(s.default_character_set.name, 'NONE')
2491        if self.con.ods < fdb.ODS_FB_30:
2492            self.assertIsNone(s.security_class)
2493        else:
2494            self.assertEqual(s.security_class, 'SQL$363')
2495        # Lists of db objects
2496        self.assertIsInstance(s.collations, list)
2497        self.assertIsInstance(s.character_sets, list)
2498        self.assertIsInstance(s.exceptions, list)
2499        self.assertIsInstance(s.generators, list)
2500        self.assertIsInstance(s.sysgenerators, list)
2501        self.assertIsInstance(s.sequences, list)
2502        self.assertIsInstance(s.syssequences, list)
2503        self.assertIsInstance(s.domains, list)
2504        self.assertIsInstance(s.sysdomains, list)
2505        self.assertIsInstance(s.indices, list)
2506        self.assertIsInstance(s.sysindices, list)
2507        self.assertIsInstance(s.tables, list)
2508        self.assertIsInstance(s.systables, list)
2509        self.assertIsInstance(s.views, list)
2510        self.assertIsInstance(s.sysviews, list)
2511        self.assertIsInstance(s.triggers, list)
2512        self.assertIsInstance(s.systriggers, list)
2513        self.assertIsInstance(s.procedures, list)
2514        self.assertIsInstance(s.sysprocedures, list)
2515        self.assertIsInstance(s.constraints, list)
2516        self.assertIsInstance(s.roles, list)
2517        self.assertIsInstance(s.dependencies, list)
2518        self.assertIsInstance(s.functions, list)
2519        self.assertIsInstance(s.files, list)
2520        s.reload()
2521        if self.con.ods <= fdb.ODS_FB_20:
2522            self.assertEqual(len(s.collations), 138)
2523        elif self.con.ods == fdb.ODS_FB_21:
2524            self.assertEqual(len(s.collations), 146)
2525        elif self.con.ods == fdb.ODS_FB_25:
2526            self.assertEqual(len(s.collations), 149)
2527        elif self.con.ods >= fdb.ODS_FB_30:
2528            self.assertEqual(len(s.collations), 150)
2529        if self.con.ods <= fdb.ODS_FB_20:
2530            self.assertEqual(len(s.character_sets), 48)
2531        elif self.con.ods == fdb.ODS_FB_21:
2532            self.assertEqual(len(s.character_sets), 51)
2533        elif self.con.ods >= fdb.ODS_FB_25:
2534            self.assertEqual(len(s.character_sets), 52)
2535        self.assertEqual(len(s.exceptions), 5)
2536        self.assertEqual(len(s.generators), 2)
2537        if self.con.ods < fdb.ODS_FB_30:
2538            self.assertEqual(len(s.sysgenerators), 9)
2539            self.assertEqual(len(s.syssequences), 9)
2540        else:
2541            self.assertEqual(len(s.sysgenerators), 13)
2542            self.assertEqual(len(s.syssequences), 13)
2543        self.assertEqual(len(s.sequences), 2)
2544        self.assertEqual(len(s.domains), 15)
2545        if self.con.ods <= fdb.ODS_FB_20:
2546            self.assertEqual(len(s.sysdomains), 203)
2547        elif self.con.ods == fdb.ODS_FB_21:
2548            self.assertEqual(len(s.sysdomains), 227)
2549        elif self.con.ods == fdb.ODS_FB_25:
2550            self.assertEqual(len(s.sysdomains), 230)
2551        elif self.con.ods == fdb.ODS_FB_30:
2552            self.assertEqual(len(s.sysdomains), 277)
2553        else:
2554            self.assertEqual(len(s.sysdomains), 247)
2555        self.assertEqual(len(s.indices), 12)
2556        if self.con.ods <= fdb.ODS_FB_21:
2557            self.assertEqual(len(s.sysindices), 72)
2558        elif self.con.ods == fdb.ODS_FB_25:
2559            self.assertEqual(len(s.sysindices), 76)
2560        elif self.con.ods == fdb.ODS_FB_30:
2561            self.assertEqual(len(s.sysindices), 82)
2562        else:
2563            self.assertEqual(len(s.sysindices), 78)
2564        if self.con.ods < fdb.ODS_FB_30:
2565            self.assertEqual(len(s.tables), 15)
2566        else:
2567            self.assertEqual(len(s.tables), 16)
2568        if self.con.ods <= fdb.ODS_FB_20:
2569            self.assertEqual(len(s.systables), 33)
2570        elif self.con.ods == fdb.ODS_FB_21:
2571            self.assertEqual(len(s.systables), 40)
2572        elif self.con.ods == fdb.ODS_FB_25:
2573            self.assertEqual(len(s.systables), 42)
2574        elif self.con.ods == fdb.ODS_FB_30:
2575            self.assertEqual(len(s.systables), 50)
2576        else:
2577            self.assertEqual(len(s.systables), 44)
2578        self.assertEqual(len(s.views), 1)
2579        self.assertEqual(len(s.sysviews), 0)
2580        self.assertEqual(len(s.triggers), 6)
2581        if self.con.ods < fdb.ODS_FB_30:
2582            self.assertEqual(len(s.systriggers), 63)
2583        else:
2584            self.assertEqual(len(s.systriggers), 57)
2585        if self.con.ods < fdb.ODS_FB_30:
2586            self.assertEqual(len(s.procedures), 10)
2587        else:
2588            self.assertEqual(len(s.procedures), 11)
2589        self.assertEqual(len(s.sysprocedures), 0)
2590        if self.con.ods < fdb.ODS_FB_30:
2591            self.assertEqual(len(s.constraints), 82)
2592        else:
2593            self.assertEqual(len(s.constraints), 110)
2594        if self.con.ods <= fdb.ODS_FB_21:
2595            self.assertEqual(len(s.roles), 1)
2596        elif self.con.ods >= fdb.ODS_FB_25:
2597            self.assertEqual(len(s.roles), 2)
2598        if self.con.ods <= fdb.ODS_FB_20:
2599            self.assertEqual(len(s.dependencies), 163)
2600        elif self.con.ods == fdb.ODS_FB_21:
2601            self.assertEqual(len(s.dependencies), 157)
2602        elif self.con.ods == fdb.ODS_FB_25:
2603            self.assertEqual(len(s.dependencies), 163)
2604        elif self.con.ods >= fdb.ODS_FB_30:
2605            self.assertEqual(len(s.dependencies), 168)
2606        if self.con.ods < fdb.ODS_FB_30:
2607            self.assertEqual(len(s.functions), 0)
2608        else:
2609            self.assertEqual(len(s.functions), 6)
2610        if self.con.ods < fdb.ODS_FB_30:
2611            self.assertEqual(len(s.sysfunctions), 2)
2612        else:
2613            self.assertEqual(len(s.sysfunctions), 0)
2614        self.assertEqual(len(s.files), 0)
2615        #
2616        self.assertIsInstance(s.collations[0], sm.Collation)
2617        self.assertIsInstance(s.character_sets[0], sm.CharacterSet)
2618        self.assertIsInstance(s.exceptions[0], sm.DatabaseException)
2619        self.assertIsInstance(s.generators[0], sm.Sequence)
2620        self.assertIsInstance(s.sysgenerators[0], sm.Sequence)
2621        self.assertIsInstance(s.sequences[0], sm.Sequence)
2622        self.assertIsInstance(s.syssequences[0], sm.Sequence)
2623        self.assertIsInstance(s.domains[0], sm.Domain)
2624        self.assertIsInstance(s.sysdomains[0], sm.Domain)
2625        self.assertIsInstance(s.indices[0], sm.Index)
2626        self.assertIsInstance(s.sysindices[0], sm.Index)
2627        self.assertIsInstance(s.tables[0], sm.Table)
2628        self.assertIsInstance(s.systables[0], sm.Table)
2629        self.assertIsInstance(s.views[0], sm.View)
2630        if len(s.sysviews) > 0:
2631            self.assertIsInstance(s.sysviews[0],sm.View)
2632        self.assertIsInstance(s.triggers[0], sm.Trigger)
2633        self.assertIsInstance(s.systriggers[0], sm.Trigger)
2634        self.assertIsInstance(s.procedures[0], sm.Procedure)
2635        if len(s.sysprocedures) > 0:
2636            self.assertIsInstance(s.sysprocedures[0],sm.Procedure)
2637        self.assertIsInstance(s.constraints[0], sm.Constraint)
2638        if len(s.roles) > 0:
2639            self.assertIsInstance(s.roles[0],sm.Role)
2640        self.assertIsInstance(s.dependencies[0], sm.Dependency)
2641        if self.con.ods < fdb.ODS_FB_30:
2642            self.assertIsInstance(s.sysfunctions[0], sm.Function)
2643        if len(s.files) > 0:
2644            self.assertIsInstance(s.files[0],sm.DatabaseFile)
2645        #
2646        self.assertEqual(s.get_collation('OCTETS').name, 'OCTETS')
2647        self.assertEqual(s.get_character_set('WIN1250').name, 'WIN1250')
2648        self.assertEqual(s.get_exception('UNKNOWN_EMP_ID').name, 'UNKNOWN_EMP_ID')
2649        self.assertEqual(s.get_generator('EMP_NO_GEN').name, 'EMP_NO_GEN')
2650        self.assertEqual(s.get_sequence('EMP_NO_GEN').name, 'EMP_NO_GEN')
2651        self.assertEqual(s.get_index('MINSALX').name, 'MINSALX')
2652        self.assertEqual(s.get_domain('FIRSTNAME').name, 'FIRSTNAME')
2653        self.assertEqual(s.get_table('COUNTRY').name, 'COUNTRY')
2654        self.assertEqual(s.get_view('PHONE_LIST').name, 'PHONE_LIST')
2655        self.assertEqual(s.get_trigger('SET_EMP_NO').name, 'SET_EMP_NO')
2656        self.assertEqual(s.get_procedure('GET_EMP_PROJ').name, 'GET_EMP_PROJ')
2657        self.assertEqual(s.get_constraint('INTEG_1').name, 'INTEG_1')
2658        #self.assertEqual(s.get_role('X').name,'X')
2659        if self.con.ods < fdb.ODS_FB_30:
2660            self.assertEqual(s.get_function('RDB$GET_CONTEXT').name, 'RDB$GET_CONTEXT')
2661        self.assertEqual(s.get_collation_by_id(0, 0).name, 'NONE')
2662        self.assertEqual(s.get_character_set_by_id(0).name, 'NONE')
2663        self.assertFalse(s.ismultifile())
2664        #
2665        self.assertFalse(s.closed)
2666        #
2667        with self.assertRaises(fdb.ProgrammingError) as cm:
2668            s.close()
2669        self.assertTupleEqual(cm.exception.args,
2670                              ("Call to 'close' not allowed for embedded Schema.",))
2671        with self.assertRaises(fdb.ProgrammingError) as cm:
2672            s.bind(self.con)
2673        self.assertTupleEqual(cm.exception.args,
2674                              ("Call to 'bind' not allowed for embedded Schema.",))
2675    def testCollation(self):
2676        # System collation
2677        c = self.con.schema.get_collation('ES_ES')
2678        # common properties
2679        self.assertEqual(c.name, 'ES_ES')
2680        self.assertIsNone(c.description)
2681        self.assertListEqual(c.actions, ['comment'])
2682        self.assertTrue(c.issystemobject())
2683        self.assertEqual(c.get_quoted_name(), 'ES_ES')
2684        self.assertListEqual(c.get_dependents(), [])
2685        self.assertListEqual(c.get_dependencies(), [])
2686        if self.con.ods < fdb.ODS_FB_30:
2687            self.assertIsNone(c.security_class)
2688            self.assertIsNone(c.owner_name)
2689        else:
2690            self.assertEqual(c.security_class, 'SQL$263')
2691            self.assertEqual(c.owner_name, 'SYSDBA')
2692        #
2693        self.assertEqual(c.id, 10)
2694        self.assertEqual(c.character_set.name, 'ISO8859_1')
2695        self.assertIsNone(c.base_collation)
2696        self.assertEqual(c.attributes, 1)
2697        if self.con.ods <= fdb.ODS_FB_20:
2698            self.assertIsNone(c.specific_attributes)
2699        elif self.con.ods > fdb.ODS_FB_20:
2700            self.assertEqual(c.specific_attributes,
2701                             'DISABLE-COMPRESSIONS=1;SPECIALS-FIRST=1')
2702        self.assertIsNone(c.function_name)
2703        # User defined collation
2704        # create collation TEST_COLLATE
2705        # for win1250
2706        # from WIN_CZ no pad case insensitive accent insensitive
2707        # 'DISABLE-COMPRESSIONS=0;DISABLE-EXPANSIONS=0'
2708        c = self.con.schema.get_collation('TEST_COLLATE')
2709        # common properties
2710        self.assertEqual(c.name, 'TEST_COLLATE')
2711        self.assertIsNone(c.description)
2712        self.assertListEqual(c.actions, ['comment', 'create', 'drop'])
2713        self.assertFalse(c.issystemobject())
2714        self.assertEqual(c.get_quoted_name(), 'TEST_COLLATE')
2715        self.assertListEqual(c.get_dependents(), [])
2716        self.assertListEqual(c.get_dependencies(), [])
2717        #
2718        self.assertEqual(c.id, 126)
2719        self.assertEqual(c.character_set.name, 'WIN1250')
2720        self.assertEqual(c.base_collation.name, 'WIN_CZ')
2721        self.assertEqual(c.attributes, 6)
2722        self.assertEqual(c.specific_attributes,
2723                         'DISABLE-COMPRESSIONS=0;DISABLE-EXPANSIONS=0')
2724        self.assertIsNone(c.function_name)
2725        self.assertEqual(c.get_sql_for('create'),
2726                         """CREATE COLLATION TEST_COLLATE
2727   FOR WIN1250
2728   FROM WIN_CZ
2729   NO PAD
2730   CASE INSENSITIVE
2731   ACCENT INSENSITIVE
2732   'DISABLE-COMPRESSIONS=0;DISABLE-EXPANSIONS=0'""")
2733        self.assertEqual(c.get_sql_for('drop'), "DROP COLLATION TEST_COLLATE")
2734        with self.assertRaises(fdb.ProgrammingError) as cm:
2735            c.get_sql_for('drop', badparam='')
2736        self.assertTupleEqual(cm.exception.args,
2737                              ("Unsupported parameter(s) 'badparam'",))
2738        self.assertEqual(c.get_sql_for('comment'),
2739                         "COMMENT ON COLLATION TEST_COLLATE IS NULL")
2740
2741    def testCharacterSet(self):
2742        c = self.con.schema.get_character_set('UTF8')
2743        # common properties
2744        self.assertEqual(c.name, 'UTF8')
2745        self.assertIsNone(c.description)
2746        self.assertListEqual(c.actions, ['alter', 'comment'])
2747        self.assertTrue(c.issystemobject())
2748        self.assertEqual(c.get_quoted_name(), 'UTF8')
2749        self.assertListEqual(c.get_dependents(), [])
2750        self.assertListEqual(c.get_dependencies(), [])
2751        if self.con.ods < fdb.ODS_FB_30:
2752            self.assertIsNone(c.security_class)
2753            self.assertIsNone(c.owner_name)
2754        else:
2755            self.assertEqual(c.security_class, 'SQL$166')
2756            self.assertEqual(c.owner_name, 'SYSDBA')
2757        #
2758        self.assertEqual(c.id, 4)
2759        self.assertEqual(c.bytes_per_character, 4)
2760        self.assertEqual(c.default_collate.name, 'UTF8')
2761        if self.con.ods <= fdb.ODS_FB_20:
2762            self.assertListEqual([x.name for x in c.collations],
2763                                 ['UTF8', 'UCS_BASIC', 'UNICODE'])
2764        elif self.con.ods == fdb.ODS_FB_21:
2765            self.assertListEqual([x.name for x in c.collations],
2766                                 ['UTF8', 'UCS_BASIC', 'UNICODE', 'UNICODE_CI'])
2767        elif self.con.ods >= fdb.ODS_FB_25:
2768            self.assertListEqual([x.name for x in c.collations],
2769                                 ['UTF8', 'UCS_BASIC', 'UNICODE', 'UNICODE_CI', 'UNICODE_CI_AI'])
2770        #
2771        self.assertEqual(c.get_sql_for('alter', collation='UCS_BASIC'),
2772                         "ALTER CHARACTER SET UTF8 SET DEFAULT COLLATION UCS_BASIC")
2773        with self.assertRaises(fdb.ProgrammingError) as cm:
2774            c.get_sql_for('alter', badparam='UCS_BASIC')
2775        self.assertTupleEqual(cm.exception.args,
2776                              ("Unsupported parameter(s) 'badparam'",))
2777        with self.assertRaises(fdb.ProgrammingError) as cm:
2778            c.get_sql_for('alter')
2779        self.assertTupleEqual(cm.exception.args,
2780                              ("Missing required parameter: 'collation'.",))
2781        #
2782        self.assertEqual(c.get_sql_for('comment'),
2783                         'COMMENT ON CHARACTER SET UTF8 IS NULL')
2784        #
2785        self.assertEqual(c.get_collation('UCS_BASIC').name, 'UCS_BASIC')
2786        self.assertEqual(c.get_collation_by_id(c.get_collation('UCS_BASIC').id).name,
2787                         'UCS_BASIC')
2788    def testException(self):
2789        c = self.con.schema.get_exception('UNKNOWN_EMP_ID')
2790        # common properties
2791        self.assertEqual(c.name, 'UNKNOWN_EMP_ID')
2792        self.assertIsNone(c.description)
2793        self.assertListEqual(c.actions,
2794                             ['comment', 'create', 'recreate', 'alter', 'create_or_alter', 'drop'])
2795        self.assertFalse(c.issystemobject())
2796        self.assertEqual(c.get_quoted_name(), 'UNKNOWN_EMP_ID')
2797        d = c.get_dependents()
2798        self.assertEqual(len(d), 1)
2799        d = d[0]
2800        self.assertEqual(d.dependent_name, 'ADD_EMP_PROJ')
2801        self.assertEqual(d.dependent_type, 5)
2802        self.assertIsInstance(d.dependent, sm.Procedure)
2803        self.assertEqual(d.depended_on_name, 'UNKNOWN_EMP_ID')
2804        self.assertEqual(d.depended_on_type, 7)
2805        self.assertIsInstance(d.depended_on, sm.DatabaseException)
2806        self.assertListEqual(c.get_dependencies(), [])
2807        if self.con.ods < fdb.ODS_FB_30:
2808            self.assertIsNone(c.security_class)
2809            self.assertIsNone(c.owner_name)
2810        else:
2811            self.assertEqual(c.security_class, 'SQL$476')
2812            self.assertEqual(c.owner_name, 'SYSDBA')
2813        #
2814        self.assertEqual(c.id, 1)
2815        self.assertEqual(c.message, "Invalid employee number or project id.")
2816        #
2817        self.assertEqual(c.get_sql_for('create'),
2818                         "CREATE EXCEPTION UNKNOWN_EMP_ID 'Invalid employee number or project id.'")
2819        self.assertEqual(c.get_sql_for('recreate'),
2820                         "RECREATE EXCEPTION UNKNOWN_EMP_ID 'Invalid employee number or project id.'")
2821        self.assertEqual(c.get_sql_for('drop'),
2822                         "DROP EXCEPTION UNKNOWN_EMP_ID")
2823        self.assertEqual(c.get_sql_for('alter', message="New message."),
2824                         "ALTER EXCEPTION UNKNOWN_EMP_ID 'New message.'")
2825        with self.assertRaises(fdb.ProgrammingError) as cm:
2826            c.get_sql_for('alter', badparam="New message.")
2827        self.assertTupleEqual(cm.exception.args,
2828                              ("Unsupported parameter(s) 'badparam'",))
2829        with self.assertRaises(fdb.ProgrammingError) as cm:
2830            c.get_sql_for('alter')
2831        self.assertTupleEqual(cm.exception.args,
2832                              ("Missing required parameter: 'message'.",))
2833        self.assertEqual(c.get_sql_for('create_or_alter'),
2834                         "CREATE OR ALTER EXCEPTION UNKNOWN_EMP_ID 'Invalid employee number or project id.'")
2835        self.assertEqual(c.get_sql_for('comment'),
2836                         "COMMENT ON EXCEPTION UNKNOWN_EMP_ID IS NULL")
2837    def testSequence(self):
2838        # System generator
2839        c = self.con.schema.get_sequence('RDB$FIELD_NAME')
2840        # common properties
2841        self.assertEqual(c.name, 'RDB$FIELD_NAME')
2842        self.assertEqual(c.description, "Implicit domain name")
2843        self.assertListEqual(c.actions, ['comment'])
2844        self.assertTrue(c.issystemobject())
2845        self.assertEqual(c.get_quoted_name(), 'RDB$FIELD_NAME')
2846        self.assertListEqual(c.get_dependents(), [])
2847        self.assertListEqual(c.get_dependencies(), [])
2848        #
2849        self.assertEqual(c.id, 6)
2850        # User generator
2851        c = self.con.schema.get_generator('EMP_NO_GEN')
2852        # common properties
2853        self.assertEqual(c.name, 'EMP_NO_GEN')
2854        self.assertIsNone(c.description)
2855        self.assertListEqual(c.actions, ['comment', 'create',
2856                                         'alter', 'drop'])
2857        self.assertFalse(c.issystemobject())
2858        self.assertEqual(c.get_quoted_name(), 'EMP_NO_GEN')
2859        d = c.get_dependents()
2860        self.assertEqual(len(d), 1)
2861        d = d[0]
2862        self.assertEqual(d.dependent_name, 'SET_EMP_NO')
2863        self.assertEqual(d.dependent_type, 2)
2864        self.assertIsInstance(d.dependent, sm.Trigger)
2865        self.assertEqual(d.depended_on_name, 'EMP_NO_GEN')
2866        self.assertEqual(d.depended_on_type, 14)
2867        self.assertIsInstance(d.depended_on, sm.Sequence)
2868        self.assertListEqual(c.get_dependencies(), [])
2869        #
2870        if self.con.ods < fdb.ODS_FB_30:
2871            self.assertEqual(c.id, 10)
2872            self.assertIsNone(c.security_class)
2873            self.assertIsNone(c.owner_name)
2874            self.assertIsNone(c.inital_value)
2875            self.assertIsNone(c.increment)
2876        else:
2877            self.assertEqual(c.id, 12)
2878            self.assertEqual(c.security_class, 'SQL$429')
2879            self.assertEqual(c.owner_name, 'SYSDBA')
2880            self.assertEqual(c.inital_value, 0)
2881            self.assertEqual(c.increment, 1)
2882        self.assertEqual(c.value, 145)
2883        #
2884        self.assertEqual(c.get_sql_for('create'), "CREATE SEQUENCE EMP_NO_GEN")
2885        self.assertEqual(c.get_sql_for('drop'), "DROP SEQUENCE EMP_NO_GEN")
2886        self.assertEqual(c.get_sql_for('alter', value=10),
2887                         "ALTER SEQUENCE EMP_NO_GEN RESTART WITH 10")
2888        with self.assertRaises(fdb.ProgrammingError) as cm:
2889            c.get_sql_for('alter', badparam=10)
2890        self.assertTupleEqual(cm.exception.args,
2891                              ("Unsupported parameter(s) 'badparam'",))
2892        with self.assertRaises(fdb.ProgrammingError) as cm:
2893            c.get_sql_for('alter')
2894        self.assertTupleEqual(cm.exception.args,
2895                              ("Missing required parameter: 'value'.",))
2896        self.assertEqual(c.get_sql_for('comment'),
2897                         "COMMENT ON SEQUENCE EMP_NO_GEN IS NULL")
2898        c.schema.opt_generator_keyword = 'GENERATOR'
2899        self.assertEqual(c.get_sql_for('comment'),
2900                         "COMMENT ON GENERATOR EMP_NO_GEN IS NULL")
2901    def testTableColumn(self):
2902        # System column
2903        c = self.con.schema.get_table('RDB$PAGES').get_column('RDB$PAGE_NUMBER')
2904        # common properties
2905        self.assertEqual(c.name, 'RDB$PAGE_NUMBER')
2906        self.assertIsNone(c.description)
2907        self.assertListEqual(c.actions, ['comment'])
2908        self.assertTrue(c.issystemobject())
2909        self.assertEqual(c.get_quoted_name(), 'RDB$PAGE_NUMBER')
2910        self.assertListEqual(c.get_dependents(), [])
2911        self.assertListEqual(c.get_dependencies(), [])
2912        self.assertFalse(c.isidentity())
2913        self.assertIsNone(c.generator)
2914        # User column
2915        c = self.con.schema.get_table('DEPARTMENT').get_column('PHONE_NO')
2916        # common properties
2917        self.assertEqual(c.name, 'PHONE_NO')
2918        self.assertIsNone(c.description)
2919        self.assertListEqual(c.actions, ['comment', 'alter', 'drop'])
2920        self.assertFalse(c.issystemobject())
2921        self.assertEqual(c.get_quoted_name(), 'PHONE_NO')
2922        d = c.get_dependents()
2923        self.assertEqual(len(d), 1)
2924        d = d[0]
2925        self.assertEqual(d.dependent_name, 'PHONE_LIST')
2926        self.assertEqual(d.dependent_type, 1)
2927        self.assertIsInstance(d.dependent, sm.View)
2928        self.assertEqual(d.depended_on_name, 'DEPARTMENT')
2929        self.assertEqual(d.depended_on_type, 0)
2930        self.assertIsInstance(d.depended_on, sm.TableColumn)
2931        self.assertListEqual(c.get_dependencies(), [])
2932        #
2933        self.assertEqual(c.table.name, 'DEPARTMENT')
2934        self.assertEqual(c.domain.name, 'PHONENUMBER')
2935        self.assertEqual(c.position, 6)
2936        self.assertIsNone(c.security_class)
2937        self.assertEqual(c.default, "'555-1234'")
2938        self.assertIsNone(c.collation)
2939        self.assertEqual(c.datatype, 'VARCHAR(20)')
2940        #
2941        self.assertTrue(c.isnullable())
2942        self.assertFalse(c.iscomputed())
2943        self.assertTrue(c.isdomainbased())
2944        self.assertTrue(c.has_default())
2945        self.assertIsNone(c.get_computedby())
2946        #
2947        self.assertEqual(c.get_sql_for('comment'),
2948                         "COMMENT ON COLUMN DEPARTMENT.PHONE_NO IS NULL")
2949        self.assertEqual(c.get_sql_for('drop'),
2950                         "ALTER TABLE DEPARTMENT DROP PHONE_NO")
2951        self.assertEqual(c.get_sql_for('alter', name='NewName'),
2952                         'ALTER TABLE DEPARTMENT ALTER COLUMN PHONE_NO TO "NewName"')
2953        self.assertEqual(c.get_sql_for('alter', position=2),
2954                         "ALTER TABLE DEPARTMENT ALTER COLUMN PHONE_NO POSITION 2")
2955        self.assertEqual(c.get_sql_for('alter', datatype='VARCHAR(25)'),
2956                         "ALTER TABLE DEPARTMENT ALTER COLUMN PHONE_NO TYPE VARCHAR(25)")
2957        with self.assertRaises(fdb.ProgrammingError) as cm:
2958            c.get_sql_for('alter', badparam=10)
2959        self.assertTupleEqual(cm.exception.args, ("Unsupported parameter(s) 'badparam'",))
2960        with self.assertRaises(fdb.ProgrammingError) as cm:
2961            c.get_sql_for('alter')
2962        self.assertTupleEqual(cm.exception.args, ("Parameter required.",))
2963        with self.assertRaises(fdb.ProgrammingError) as cm:
2964            c.get_sql_for('alter', expression='(1+1)')
2965        self.assertTupleEqual(cm.exception.args,
2966                              ("Change from persistent column to computed is not allowed.",))
2967        # Computed column
2968        c = self.con.schema.get_table('EMPLOYEE').get_column('FULL_NAME')
2969        self.assertTrue(c.isnullable())
2970        self.assertTrue(c.iscomputed())
2971        self.assertFalse(c.isdomainbased())
2972        self.assertFalse(c.has_default())
2973        self.assertEqual(c.get_computedby(), "(last_name || ', ' || first_name)")
2974        if self.con.ods < fdb.ODS_FB_30:
2975            self.assertEqual(c.datatype, 'VARCHAR(0)')
2976        else:
2977            self.assertEqual(c.datatype, 'VARCHAR(37)')
2978        #
2979        self.assertEqual(c.get_sql_for('alter', datatype='VARCHAR(50)',
2980                                       expression="(first_name || ', ' || last_name)"),
2981                         "ALTER TABLE EMPLOYEE ALTER COLUMN FULL_NAME TYPE VARCHAR(50) " \
2982                         "COMPUTED BY (first_name || ', ' || last_name)")
2983
2984        with self.assertRaises(fdb.ProgrammingError) as cm:
2985            c.get_sql_for('alter', datatype='VARCHAR(50)')
2986        self.assertTupleEqual(cm.exception.args,
2987                              ("Change from computed column to persistent is not allowed.",))
2988        # Array column
2989        c = self.con.schema.get_table('AR').get_column('C2')
2990        self.assertEqual(c.datatype, 'INTEGER[4, 0:3, 2]')
2991        # Identity column
2992        if self.con.ods >= fdb.ODS_FB_30:
2993            c = self.con.schema.get_table('T5').get_column('ID')
2994            self.assertTrue(c.isidentity())
2995            self.assertTrue(c.generator.isidentity())
2996            self.assertEqual(c.identity_type, 1)
2997            #
2998            self.assertEqual(c.get_sql_for('alter', restart=None),
2999                             "ALTER TABLE T5 ALTER COLUMN ID RESTART")
3000            self.assertEqual(c.get_sql_for('alter', restart=100),
3001                             "ALTER TABLE T5 ALTER COLUMN ID RESTART WITH 100")
3002    def testIndex(self):
3003        # System index
3004        c = self.con.schema.get_index('RDB$INDEX_0')
3005        # common properties
3006        self.assertEqual(c.name, 'RDB$INDEX_0')
3007        self.assertIsNone(c.description)
3008        self.assertListEqual(c.actions, ['activate', 'recompute', 'comment'])
3009        self.assertTrue(c.issystemobject())
3010        self.assertEqual(c.get_quoted_name(), 'RDB$INDEX_0')
3011        self.assertListEqual(c.get_dependents(), [])
3012        self.assertListEqual(c.get_dependencies(), [])
3013        #
3014        self.assertEqual(c.table.name, 'RDB$RELATIONS')
3015        self.assertListEqual(c.segment_names, ['RDB$RELATION_NAME'])
3016        # user index
3017        c = self.con.schema.get_index('MAXSALX')
3018        # common properties
3019        self.assertEqual(c.name, 'MAXSALX')
3020        self.assertIsNone(c.description)
3021        self.assertListEqual(c.actions, ['activate', 'recompute', 'comment', 'create', 'deactivate', 'drop'])
3022        self.assertFalse(c.issystemobject())
3023        self.assertEqual(c.get_quoted_name(), 'MAXSALX')
3024        self.assertListEqual(c.get_dependents(), [])
3025        self.assertListEqual(c.get_dependencies(), [])
3026        #
3027        self.assertEqual(c.id, 3)
3028        self.assertEqual(c.table.name, 'JOB')
3029        self.assertEqual(c.index_type, 'DESCENDING')
3030        self.assertIsNone(c.partner_index)
3031        self.assertIsNone(c.expression)
3032        # startswith() is necessary, because Python 3 returns more precise value.
3033        self.assertTrue(str(c.statistics).startswith('0.0384615398943'))
3034        self.assertListEqual(c.segment_names, ['JOB_COUNTRY', 'MAX_SALARY'])
3035        self.assertEqual(len(c.segments), 2)
3036        for segment in c.segments:
3037            self.assertIsInstance(segment, sm.TableColumn)
3038        self.assertEqual(c.segments[0].name, 'JOB_COUNTRY')
3039        self.assertEqual(c.segments[1].name, 'MAX_SALARY')
3040
3041        if self.con.ods <= fdb.ODS_FB_20:
3042            self.assertListEqual(c.segment_statistics, [None, None])
3043        elif self.con.ods > fdb.ODS_FB_20:
3044            self.assertListEqual(c.segment_statistics,
3045                                 [0.1428571492433548, 0.03846153989434242])
3046        self.assertIsNone(c.constraint)
3047        #
3048        self.assertFalse(c.isexpression())
3049        self.assertFalse(c.isunique())
3050        self.assertFalse(c.isinactive())
3051        self.assertFalse(c.isenforcer())
3052        #
3053        self.assertEqual(c.get_sql_for('create'),
3054                         """CREATE DESCENDING INDEX MAXSALX ON JOB (JOB_COUNTRY,MAX_SALARY)""")
3055        self.assertEqual(c.get_sql_for('activate'), "ALTER INDEX MAXSALX ACTIVE")
3056        self.assertEqual(c.get_sql_for('deactivate'), "ALTER INDEX MAXSALX INACTIVE")
3057        self.assertEqual(c.get_sql_for('recompute'), "SET STATISTICS INDEX MAXSALX")
3058        self.assertEqual(c.get_sql_for('drop'), "DROP INDEX MAXSALX")
3059        self.assertEqual(c.get_sql_for('comment'),
3060                         "COMMENT ON INDEX MAXSALX IS NULL")
3061        # Constraint index
3062        c = self.con.schema.get_index('RDB$FOREIGN6')
3063        # common properties
3064        self.assertEqual(c.name, 'RDB$FOREIGN6')
3065        self.assertTrue(c.issystemobject())
3066        self.assertTrue(c.isenforcer())
3067        self.assertEqual(c.partner_index.name, 'RDB$PRIMARY5')
3068        self.assertEqual(c.constraint.name, 'INTEG_17')
3069    def testViewColumn(self):
3070        c = self.con.schema.get_view('PHONE_LIST').get_column('LAST_NAME')
3071        # common properties
3072        self.assertEqual(c.name, 'LAST_NAME')
3073        self.assertIsNone(c.description)
3074        self.assertListEqual(c.actions, ['comment'])
3075        self.assertFalse(c.issystemobject())
3076        self.assertEqual(c.get_quoted_name(), 'LAST_NAME')
3077        self.assertListEqual(c.get_dependents(), [])
3078        d = c.get_dependencies()
3079        self.assertEqual(len(d), 1)
3080        d = d[0]
3081        self.assertEqual(d.dependent_name, 'PHONE_LIST')
3082        self.assertEqual(d.dependent_type, 1)
3083        self.assertIsInstance(d.dependent, sm.View)
3084        self.assertEqual(d.field_name, 'LAST_NAME')
3085        self.assertEqual(d.depended_on_name, 'EMPLOYEE')
3086        self.assertEqual(d.depended_on_type, 0)
3087        self.assertIsInstance(d.depended_on, sm.TableColumn)
3088        self.assertEqual(d.depended_on.name, 'LAST_NAME')
3089        self.assertEqual(d.depended_on.table.name, 'EMPLOYEE')
3090        #
3091        self.assertEqual(c.view.name, 'PHONE_LIST')
3092        self.assertEqual(c.base_field.name, 'LAST_NAME')
3093        self.assertEqual(c.base_field.table.name, 'EMPLOYEE')
3094        self.assertEqual(c.domain.name, 'LASTNAME')
3095        self.assertEqual(c.position, 2)
3096        self.assertIsNone(c.security_class)
3097        self.assertEqual(c.collation.name, 'NONE')
3098        self.assertEqual(c.datatype, 'VARCHAR(20)')
3099        #
3100        self.assertTrue(c.isnullable())
3101        #
3102        self.assertEqual(c.get_sql_for('comment'),
3103                         "COMMENT ON COLUMN PHONE_LIST.LAST_NAME IS NULL")
3104    def testDomain(self):
3105        # System domain
3106        c = self.con.schema.get_domain('RDB$6')
3107        # common properties
3108        self.assertEqual(c.name, 'RDB$6')
3109        self.assertIsNone(c.description)
3110        self.assertListEqual(c.actions, ['comment'])
3111        self.assertTrue(c.issystemobject())
3112        self.assertEqual(c.get_quoted_name(), 'RDB$6')
3113        self.assertListEqual(c.get_dependents(), [])
3114        self.assertListEqual(c.get_dependencies(), [])
3115        if self.con.ods < fdb.ODS_FB_30:
3116            self.assertIsNone(c.security_class)
3117            self.assertIsNone(c.owner_name)
3118        else:
3119            self.assertEqual(c.security_class, 'SQL$439')
3120            self.assertEqual(c.owner_name, 'SYSDBA')
3121        # User domain
3122        c = self.con.schema.get_domain('PRODTYPE')
3123        # common properties
3124        self.assertEqual(c.name, 'PRODTYPE')
3125        self.assertIsNone(c.description)
3126        self.assertListEqual(c.actions, ['comment', 'create',
3127                                         'alter', 'drop'])
3128        self.assertFalse(c.issystemobject())
3129        self.assertEqual(c.get_quoted_name(), 'PRODTYPE')
3130        self.assertListEqual(c.get_dependents(), [])
3131        self.assertListEqual(c.get_dependencies(), [])
3132        #
3133        self.assertIsNone(c.expression)
3134        self.assertEqual(c.validation,
3135                         "CHECK (VALUE IN ('software', 'hardware', 'other', 'N/A'))")
3136        self.assertEqual(c.default, "'software'")
3137        self.assertEqual(c.length, 12)
3138        self.assertEqual(c.scale, 0)
3139        self.assertEqual(c.field_type, 37)
3140        self.assertEqual(c.sub_type, 0)
3141        self.assertIsNone(c.segment_length)
3142        self.assertIsNone(c.external_length)
3143        self.assertIsNone(c.external_scale)
3144        self.assertIsNone(c.external_type)
3145        self.assertListEqual(c.dimensions, [])
3146        self.assertEqual(c.character_length, 12)
3147        self.assertEqual(c.collation.name, 'NONE')
3148        self.assertEqual(c.character_set.name, 'NONE')
3149        self.assertIsNone(c.precision)
3150        self.assertEqual(c.datatype, 'VARCHAR(12)')
3151        #
3152        self.assertFalse(c.isnullable())
3153        self.assertFalse(c.iscomputed())
3154        self.assertTrue(c.isvalidated())
3155        self.assertFalse(c.isarray())
3156        self.assertTrue(c.has_default())
3157        #
3158        self.assertEqual(c.get_sql_for('create'),
3159                         "CREATE DOMAIN PRODTYPE AS VARCHAR(12) DEFAULT 'software' " \
3160                         "NOT NULL CHECK (VALUE IN ('software', 'hardware', 'other', 'N/A'))")
3161        self.assertEqual(c.get_sql_for('drop'), "DROP DOMAIN PRODTYPE")
3162        self.assertEqual(c.get_sql_for('alter', name='New_name'),
3163                         'ALTER DOMAIN PRODTYPE TO "New_name"')
3164        self.assertEqual(c.get_sql_for('alter', default="'New_default'"),
3165                         "ALTER DOMAIN PRODTYPE SET DEFAULT 'New_default'")
3166        self.assertEqual(c.get_sql_for('alter', check="VALUE STARTS WITH 'X'"),
3167                         "ALTER DOMAIN PRODTYPE ADD CHECK (VALUE STARTS WITH 'X')")
3168        self.assertEqual(c.get_sql_for('alter', datatype='VARCHAR(30)'),
3169                         "ALTER DOMAIN PRODTYPE TYPE VARCHAR(30)")
3170        with self.assertRaises(fdb.ProgrammingError) as cm:
3171            c.get_sql_for('alter', badparam=10)
3172        self.assertTupleEqual(cm.exception.args,
3173                              ("Unsupported parameter(s) 'badparam'",))
3174        with self.assertRaises(fdb.ProgrammingError) as cm:
3175            c.get_sql_for('alter')
3176        self.assertTupleEqual(cm.exception.args, ("Parameter required.",))
3177        # Domain with quoted name
3178        c = self.con.schema.get_domain('FIRSTNAME')
3179        self.assertEqual(c.name, 'FIRSTNAME')
3180        self.assertEqual(c.get_quoted_name(), '"FIRSTNAME"')
3181        self.assertEqual(c.get_sql_for('create'),
3182                         'CREATE DOMAIN "FIRSTNAME" AS VARCHAR(15)')
3183        self.assertEqual(c.get_sql_for('comment'),
3184                         'COMMENT ON DOMAIN "FIRSTNAME" IS NULL')
3185    def testDependency(self):
3186        l = self.con.schema.get_table('DEPARTMENT').get_dependents()
3187        self.assertEqual(len(l), 18)
3188        c = l[0] if self.con.ods < fdb.ODS_FB_30 else l[3]
3189        # common properties
3190        self.assertIsNone(c.name)
3191        self.assertIsNone(c.description)
3192        self.assertListEqual(c.actions, [])
3193        self.assertTrue(c.issystemobject())
3194        self.assertIsNone(c.get_quoted_name())
3195        self.assertListEqual(c.get_dependents(), [])
3196        self.assertListEqual(c.get_dependencies(), [])
3197        self.assertIsNone(c.package)
3198        self.assertFalse(c.ispackaged())
3199        #
3200        self.assertEqual(c.dependent_name, 'PHONE_LIST')
3201        self.assertEqual(c.dependent_type, 1)
3202        self.assertIsInstance(c.dependent, sm.View)
3203        self.assertEqual(c.dependent.name, 'PHONE_LIST')
3204        self.assertEqual(c.field_name, 'DEPT_NO')
3205        self.assertEqual(c.depended_on_name, 'DEPARTMENT')
3206        self.assertEqual(c.depended_on_type, 0)
3207        self.assertIsInstance(c.depended_on, sm.TableColumn)
3208        self.assertEqual(c.depended_on.name, 'DEPT_NO')
3209        #
3210        if self.con.engine_version >= 3.0:
3211            self.assertListEqual(c.get_dependents(), [])
3212            l = self.con.schema.get_package('TEST2').get_dependencies()
3213            self.assertEqual(len(l), 2)
3214            x = l[0]
3215            self.assertEqual(x.depended_on.name, 'FN')
3216            self.assertFalse(x.depended_on.ispackaged())
3217            x = l[1]
3218            self.assertEqual(x.depended_on.name, 'F')
3219            self.assertTrue(x.depended_on.ispackaged())
3220            self.assertIsInstance(x.package, sm.Package)
3221    def testConstraint(self):
3222        # Common / PRIMARY KEY
3223        c = self.con.schema.get_table('CUSTOMER').primary_key
3224        # common properties
3225        self.assertEqual(c.name, 'INTEG_60')
3226        self.assertIsNone(c.description)
3227        self.assertListEqual(c.actions, ['create', 'drop'])
3228        self.assertFalse(c.issystemobject())
3229        self.assertEqual(c.get_quoted_name(), 'INTEG_60')
3230        self.assertListEqual(c.get_dependents(), [])
3231        self.assertListEqual(c.get_dependencies(), [])
3232        #
3233        self.assertEqual(c.constraint_type, 'PRIMARY KEY')
3234        self.assertEqual(c.table.name, 'CUSTOMER')
3235        self.assertEqual(c.index.name, 'RDB$PRIMARY22')
3236        self.assertListEqual(c.trigger_names, [])
3237        self.assertListEqual(c.triggers, [])
3238        self.assertIsNone(c.column_name)
3239        self.assertIsNone(c.partner_constraint)
3240        self.assertIsNone(c.match_option)
3241        self.assertIsNone(c.update_rule)
3242        self.assertIsNone(c.delete_rule)
3243        #
3244        self.assertFalse(c.isnotnull())
3245        self.assertTrue(c.ispkey())
3246        self.assertFalse(c.isfkey())
3247        self.assertFalse(c.isunique())
3248        self.assertFalse(c.ischeck())
3249        self.assertFalse(c.isdeferrable())
3250        self.assertFalse(c.isdeferred())
3251        #
3252        self.assertEqual(c.get_sql_for('create'),
3253                         "ALTER TABLE CUSTOMER ADD PRIMARY KEY (CUST_NO)")
3254        self.assertEqual(c.get_sql_for('drop'),
3255                         "ALTER TABLE CUSTOMER DROP CONSTRAINT INTEG_60")
3256        # FOREIGN KEY
3257        c = self.con.schema.get_table('CUSTOMER').foreign_keys[0]
3258        #
3259        self.assertListEqual(c.actions, ['create', 'drop'])
3260        self.assertEqual(c.constraint_type, 'FOREIGN KEY')
3261        self.assertEqual(c.table.name, 'CUSTOMER')
3262        self.assertEqual(c.index.name, 'RDB$FOREIGN23')
3263        self.assertListEqual(c.trigger_names, [])
3264        self.assertListEqual(c.triggers, [])
3265        self.assertIsNone(c.column_name)
3266        self.assertEqual(c.partner_constraint.name, 'INTEG_2')
3267        self.assertEqual(c.match_option, 'FULL')
3268        self.assertEqual(c.update_rule, 'RESTRICT')
3269        self.assertEqual(c.delete_rule, 'RESTRICT')
3270        #
3271        self.assertFalse(c.isnotnull())
3272        self.assertFalse(c.ispkey())
3273        self.assertTrue(c.isfkey())
3274        self.assertFalse(c.isunique())
3275        self.assertFalse(c.ischeck())
3276        #
3277        self.assertEqual(c.get_sql_for('create'),
3278                         """ALTER TABLE CUSTOMER ADD FOREIGN KEY (COUNTRY)
3279  REFERENCES COUNTRY (COUNTRY)""")
3280        # CHECK
3281        c = self.con.schema.get_constraint('INTEG_59')
3282        #
3283        self.assertListEqual(c.actions, ['create', 'drop'])
3284        self.assertEqual(c.constraint_type, 'CHECK')
3285        self.assertEqual(c.table.name, 'CUSTOMER')
3286        self.assertIsNone(c.index)
3287        self.assertListEqual(c.trigger_names, ['CHECK_9', 'CHECK_10'])
3288        self.assertEqual(c.triggers[0].name, 'CHECK_9')
3289        self.assertEqual(c.triggers[1].name, 'CHECK_10')
3290        self.assertIsNone(c.column_name)
3291        self.assertIsNone(c.partner_constraint)
3292        self.assertIsNone(c.match_option)
3293        self.assertIsNone(c.update_rule)
3294        self.assertIsNone(c.delete_rule)
3295        #
3296        self.assertFalse(c.isnotnull())
3297        self.assertFalse(c.ispkey())
3298        self.assertFalse(c.isfkey())
3299        self.assertFalse(c.isunique())
3300        self.assertTrue(c.ischeck())
3301        #
3302        self.assertEqual(c.get_sql_for('create'),
3303                         "ALTER TABLE CUSTOMER ADD CHECK (on_hold IS NULL OR on_hold = '*')")
3304        # UNIQUE
3305        c = self.con.schema.get_constraint('INTEG_15')
3306        #
3307        self.assertListEqual(c.actions, ['create', 'drop'])
3308        self.assertEqual(c.constraint_type, 'UNIQUE')
3309        self.assertEqual(c.table.name, 'DEPARTMENT')
3310        self.assertEqual(c.index.name, 'RDB$4')
3311        self.assertListEqual(c.trigger_names, [])
3312        self.assertListEqual(c.triggers, [])
3313        self.assertIsNone(c.column_name)
3314        self.assertIsNone(c.partner_constraint)
3315        self.assertIsNone(c.match_option)
3316        self.assertIsNone(c.update_rule)
3317        self.assertIsNone(c.delete_rule)
3318        #
3319        self.assertFalse(c.isnotnull())
3320        self.assertFalse(c.ispkey())
3321        self.assertFalse(c.isfkey())
3322        self.assertTrue(c.isunique())
3323        self.assertFalse(c.ischeck())
3324        #
3325        self.assertEqual(c.get_sql_for('create'),
3326                         "ALTER TABLE DEPARTMENT ADD UNIQUE (DEPARTMENT)")
3327        # NOT NULL
3328        c = self.con.schema.get_constraint('INTEG_13')
3329        #
3330        self.assertListEqual(c.actions, [])
3331        self.assertEqual(c.constraint_type, 'NOT NULL')
3332        self.assertEqual(c.table.name, 'DEPARTMENT')
3333        self.assertIsNone(c.index)
3334        self.assertListEqual(c.trigger_names, [])
3335        self.assertListEqual(c.triggers, [])
3336        self.assertEqual(c.column_name, 'DEPT_NO')
3337        self.assertIsNone(c.partner_constraint)
3338        self.assertIsNone(c.match_option)
3339        self.assertIsNone(c.update_rule)
3340        self.assertIsNone(c.delete_rule)
3341        #
3342        self.assertTrue(c.isnotnull())
3343        self.assertFalse(c.ispkey())
3344        self.assertFalse(c.isfkey())
3345        self.assertFalse(c.isunique())
3346        self.assertFalse(c.ischeck())
3347    def testTable(self):
3348        # System table
3349        c = self.con.schema.get_table('RDB$PAGES')
3350        # common properties
3351        self.assertEqual(c.name, 'RDB$PAGES')
3352        self.assertIsNone(c.description)
3353        self.assertListEqual(c.actions, ['comment'])
3354        self.assertTrue(c.issystemobject())
3355        self.assertEqual(c.get_quoted_name(), 'RDB$PAGES')
3356        self.assertListEqual(c.get_dependents(), [])
3357        self.assertListEqual(c.get_dependencies(), [])
3358        # User table
3359        c = self.con.schema.get_table('EMPLOYEE')
3360        # common properties
3361        self.assertEqual(c.name, 'EMPLOYEE')
3362        self.assertIsNone(c.description)
3363        self.assertListEqual(c.actions, ['comment', 'create',
3364                                         'recreate', 'drop'])
3365        self.assertFalse(c.issystemobject())
3366        self.assertEqual(c.get_quoted_name(), 'EMPLOYEE')
3367        d = c.get_dependents()
3368        if self.con.ods <= fdb.ODS_FB_20:
3369            self.assertListEqual([(x.dependent_name, x.dependent_type) for x in d],
3370                                 [('RDB$9', 3), ('RDB$9', 3), ('PHONE_LIST', 1),
3371                                  ('PHONE_LIST', 1), ('PHONE_LIST', 1), ('PHONE_LIST', 1),
3372                                  ('PHONE_LIST', 1), ('PHONE_LIST', 1), ('CHECK_3', 2),
3373                                  ('CHECK_3', 2), ('CHECK_3', 2), ('CHECK_3', 2),
3374                                  ('CHECK_4', 2), ('CHECK_4', 2), ('CHECK_4', 2),
3375                                  ('CHECK_4', 2), ('SET_EMP_NO', 2), ('SAVE_SALARY_CHANGE', 2),
3376                                  ('SAVE_SALARY_CHANGE', 2), ('DELETE_EMPLOYEE', 5),
3377                                  ('DELETE_EMPLOYEE', 5), ('ORG_CHART', 5), ('ORG_CHART', 5),
3378                                  ('ORG_CHART', 5), ('ORG_CHART', 5), ('ORG_CHART', 5)])
3379        elif self.con.ods == fdb.ODS_FB_21:
3380            self.assertListEqual([(x.dependent_name, x.dependent_type) for x in d],
3381                                 [('PHONE_LIST', 1), ('PHONE_LIST', 1), ('PHONE_LIST', 1),
3382                                  ('PHONE_LIST', 1), ('PHONE_LIST', 1), ('CHECK_3', 2),
3383                                  ('CHECK_3', 2), ('CHECK_3', 2), ('CHECK_3', 2),
3384                                  ('CHECK_4', 2), ('CHECK_4', 2), ('CHECK_4', 2),
3385                                  ('CHECK_4', 2), ('SET_EMP_NO', 2), ('SAVE_SALARY_CHANGE', 2),
3386                                  ('SAVE_SALARY_CHANGE', 2), ('PHONE_LIST', 1),
3387                                  ('DELETE_EMPLOYEE', 5), ('DELETE_EMPLOYEE', 5),
3388                                  ('ORG_CHART', 5), ('ORG_CHART', 5), ('ORG_CHART', 5),
3389                                  ('ORG_CHART', 5), ('ORG_CHART', 5)])
3390        elif self.con.ods == fdb.ODS_FB_25:
3391            self.assertListEqual([(x.dependent_name, x.dependent_type) for x in d],
3392                                 [('RDB$9', 3), ('RDB$9', 3), ('PHONE_LIST', 1),
3393                                  ('PHONE_LIST', 1), ('PHONE_LIST', 1), ('CHECK_3', 2),
3394                                  ('CHECK_3', 2), ('CHECK_3', 2), ('CHECK_3', 2), ('CHECK_4', 2),
3395                                  ('CHECK_4', 2), ('CHECK_4', 2), ('CHECK_4', 2), ('SET_EMP_NO', 2),
3396                                  ('SAVE_SALARY_CHANGE', 2), ('PHONE_LIST', 1), ('PHONE_LIST', 1),
3397                                  ('SAVE_SALARY_CHANGE', 2), ('PHONE_LIST', 1), ('ORG_CHART', 5),
3398                                  ('ORG_CHART', 5), ('ORG_CHART', 5), ('ORG_CHART', 5), ('ORG_CHART', 5),
3399                                  ('DELETE_EMPLOYEE', 5), ('DELETE_EMPLOYEE', 5)])
3400        elif self.con.ods >= fdb.ODS_FB_30:
3401            self.assertListEqual([(x.dependent_name, x.dependent_type) for x in d],
3402                                 [('SAVE_SALARY_CHANGE', 2), ('SAVE_SALARY_CHANGE', 2), ('CHECK_3', 2),
3403                                  ('CHECK_3', 2), ('CHECK_3', 2), ('CHECK_3', 2), ('CHECK_4', 2),
3404                                  ('CHECK_4', 2), ('CHECK_4', 2), ('CHECK_4', 2), ('PHONE_LIST', 1),
3405                                  ('PHONE_LIST', 1), ('PHONE_LIST', 1), ('PHONE_LIST', 1), ('PHONE_LIST', 1),
3406                                  ('PHONE_LIST', 1), ('DELETE_EMPLOYEE', 5), ('DELETE_EMPLOYEE', 5),
3407                                  ('ORG_CHART', 5), ('ORG_CHART', 5), ('ORG_CHART', 5), ('ORG_CHART', 5),
3408                                  ('ORG_CHART', 5), ('RDB$9', 3), ('RDB$9', 3), ('SET_EMP_NO', 2)])
3409        self.assertListEqual(c.get_dependencies(), [])
3410        #
3411        self.assertEqual(c.id, 131)
3412        self.assertEqual(c.dbkey_length, 8)
3413        if self.con.ods <= fdb.ODS_FB_20:
3414            self.assertEqual(c.format, 1)
3415        elif (self.con.ods > fdb.ODS_FB_20) and (self.con.ods < fdb.ODS_FB_30):
3416            self.assertEqual(c.format, 2)
3417        elif self.con.ods >= fdb.ODS_FB_30:
3418            self.assertEqual(c.format, 1)
3419        self.assertEqual(c.table_type, 'PERSISTENT')
3420        if self.con.ods <= fdb.ODS_FB_21:
3421            self.assertEqual(c.security_class, 'SQL$EMPLOYEE')
3422        elif self.con.ods == fdb.ODS_FB_25:
3423            self.assertEqual(c.security_class, 'SQL$7')
3424        elif self.con.ods == fdb.ODS_FB_30:
3425            self.assertEqual(c.security_class, 'SQL$440')
3426        else:
3427            self.assertEqual(c.security_class, 'SQL$482')
3428        self.assertIsNone(c.external_file)
3429        self.assertEqual(c.owner_name, 'SYSDBA')
3430        if self.con.ods <= fdb.ODS_FB_20:
3431            self.assertEqual(c.default_class, 'SQL$DEFAULT5')
3432        elif (self.con.ods > fdb.ODS_FB_20) and (self.con.ods < fdb.ODS_FB_30):
3433            self.assertEqual(c.default_class, 'SQL$DEFAULT7')
3434        elif self.con.ods >= fdb.ODS_FB_30:
3435            self.assertEqual(c.default_class, 'SQL$DEFAULT54')
3436        self.assertEqual(c.flags, 1)
3437        self.assertEqual(c.primary_key.name, 'INTEG_27')
3438        self.assertListEqual([x.name for x in c.foreign_keys],
3439                             ['INTEG_28', 'INTEG_29'])
3440        self.assertListEqual([x.name for x in c.columns],
3441                             ['EMP_NO', 'FIRST_NAME', 'LAST_NAME', 'PHONE_EXT',
3442                              'HIRE_DATE', 'DEPT_NO', 'JOB_CODE', 'JOB_GRADE',
3443                              'JOB_COUNTRY', 'SALARY', 'FULL_NAME'])
3444        self.assertListEqual([x.name for x in c.constraints],
3445                             ['INTEG_18', 'INTEG_19', 'INTEG_20', 'INTEG_21',
3446                              'INTEG_22', 'INTEG_23', 'INTEG_24', 'INTEG_25',
3447                              'INTEG_26', 'INTEG_27', 'INTEG_28', 'INTEG_29',
3448                              'INTEG_30'])
3449        self.assertListEqual([x.name for x in c.indices],
3450                             ['NAMEX', 'RDB$PRIMARY7', 'RDB$FOREIGN8', 'RDB$FOREIGN9'])
3451        self.assertListEqual([x.name for x in c.triggers],
3452                             ['SET_EMP_NO', 'SAVE_SALARY_CHANGE'])
3453        #
3454        self.assertEqual(c.get_column('EMP_NO').name, 'EMP_NO')
3455        self.assertFalse(c.isgtt())
3456        self.assertTrue(c.ispersistent())
3457        self.assertFalse(c.isexternal())
3458        self.assertTrue(c.has_pkey())
3459        self.assertTrue(c.has_fkey())
3460        #
3461        self.assertEqual(c.get_sql_for('create'), """CREATE TABLE EMPLOYEE (
3462  EMP_NO EMPNO NOT NULL,
3463  FIRST_NAME "FIRSTNAME" NOT NULL,
3464  LAST_NAME "LASTNAME" NOT NULL,
3465  PHONE_EXT VARCHAR(4),
3466  HIRE_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,
3467  DEPT_NO DEPTNO NOT NULL,
3468  JOB_CODE JOBCODE NOT NULL,
3469  JOB_GRADE JOBGRADE NOT NULL,
3470  JOB_COUNTRY COUNTRYNAME NOT NULL,
3471  SALARY SALARY NOT NULL,
3472  FULL_NAME COMPUTED BY (last_name || ', ' || first_name),
3473  PRIMARY KEY (EMP_NO)
3474)""")
3475        self.assertEqual(c.get_sql_for('create', no_pk=True), """CREATE TABLE EMPLOYEE (
3476  EMP_NO EMPNO NOT NULL,
3477  FIRST_NAME "FIRSTNAME" NOT NULL,
3478  LAST_NAME "LASTNAME" NOT NULL,
3479  PHONE_EXT VARCHAR(4),
3480  HIRE_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,
3481  DEPT_NO DEPTNO NOT NULL,
3482  JOB_CODE JOBCODE NOT NULL,
3483  JOB_GRADE JOBGRADE NOT NULL,
3484  JOB_COUNTRY COUNTRYNAME NOT NULL,
3485  SALARY SALARY NOT NULL,
3486  FULL_NAME COMPUTED BY (last_name || ', ' || first_name)
3487)""")
3488        self.assertEqual(c.get_sql_for('recreate'), """RECREATE TABLE EMPLOYEE (
3489  EMP_NO EMPNO NOT NULL,
3490  FIRST_NAME "FIRSTNAME" NOT NULL,
3491  LAST_NAME "LASTNAME" NOT NULL,
3492  PHONE_EXT VARCHAR(4),
3493  HIRE_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,
3494  DEPT_NO DEPTNO NOT NULL,
3495  JOB_CODE JOBCODE NOT NULL,
3496  JOB_GRADE JOBGRADE NOT NULL,
3497  JOB_COUNTRY COUNTRYNAME NOT NULL,
3498  SALARY SALARY NOT NULL,
3499  FULL_NAME COMPUTED BY (last_name || ', ' || first_name),
3500  PRIMARY KEY (EMP_NO)
3501)""")
3502        self.assertEqual(c.get_sql_for('drop'), "DROP TABLE EMPLOYEE")
3503        self.assertEqual(c.get_sql_for('comment'),
3504                         'COMMENT ON TABLE EMPLOYEE IS NULL')
3505        # Identity colums
3506        if self.con.ods >= fdb.ODS_FB_30:
3507            c = self.con.schema.get_table('T5')
3508            self.assertEqual(c.get_sql_for('create'), """CREATE TABLE T5 (
3509  ID NUMERIC(10, 0) GENERATED BY DEFAULT AS IDENTITY,
3510  C1 VARCHAR(15),
3511  UQ BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 100),
3512  PRIMARY KEY (ID)
3513)""")
3514
3515    def testView(self):
3516        # User view
3517        c = self.con.schema.get_view('PHONE_LIST')
3518        # common properties
3519        self.assertEqual(c.name, 'PHONE_LIST')
3520        self.assertIsNone(c.description)
3521        self.assertListEqual(c.actions, ['comment', 'create',
3522                                         'recreate', 'alter',
3523                                         'create_or_alter', 'drop'])
3524        self.assertFalse(c.issystemobject())
3525        self.assertEqual(c.get_quoted_name(), 'PHONE_LIST')
3526        self.assertListEqual(c.get_dependents(), [])
3527        d = c.get_dependencies()
3528        if self.con.ods < fdb.ODS_FB_30:
3529            self.assertListEqual([(x.depended_on_name, x.field_name, x.depended_on_type) for x in d],
3530                                 [('DEPARTMENT', 'DEPT_NO', 0), ('EMPLOYEE', 'DEPT_NO', 0),
3531                                  ('DEPARTMENT', None, 0), ('EMPLOYEE', None, 0),
3532                                  ('DEPARTMENT', 'PHONE_NO', 0), ('EMPLOYEE', 'PHONE_EXT', 0),
3533                                  ('EMPLOYEE', 'LAST_NAME', 0), ('EMPLOYEE', 'EMP_NO', 0),
3534                                  ('DEPARTMENT', 'LOCATION', 0), ('EMPLOYEE', 'FIRST_NAME', 0)])
3535        else:
3536            self.assertListEqual([(x.depended_on_name, x.field_name, x.depended_on_type) for x in d],
3537                                 [('DEPARTMENT', 'DEPT_NO', 0), ('EMPLOYEE', 'DEPT_NO', 0),
3538                                  ('DEPARTMENT', None, 0), ('EMPLOYEE', None, 0), ('EMPLOYEE', 'EMP_NO', 0),
3539                                  ('EMPLOYEE', 'FIRST_NAME', 0), ('EMPLOYEE', 'LAST_NAME', 0),
3540                                  ('EMPLOYEE', 'PHONE_EXT', 0), ('DEPARTMENT', 'LOCATION', 0),
3541                                  ('DEPARTMENT', 'PHONE_NO', 0)])
3542        #
3543        if self.con.ods < fdb.ODS_FB_30:
3544            self.assertEqual(c.id, 143)
3545        else:
3546            self.assertEqual(c.id, 132)
3547        self.assertEqual(c.sql, """SELECT
3548    emp_no, first_name, last_name, phone_ext, location, phone_no
3549    FROM employee, department
3550    WHERE employee.dept_no = department.dept_no""")
3551        self.assertEqual(c.dbkey_length, 16)
3552        self.assertEqual(c.format, 1)
3553        if self.con.ods <= fdb.ODS_FB_21:
3554            self.assertEqual(c.security_class, 'SQL$PHONE_LIST')
3555        elif self.con.ods == fdb.ODS_FB_25:
3556            self.assertEqual(c.security_class, 'SQL$8')
3557        elif self.con.ods == fdb.ODS_FB_30:
3558            self.assertEqual(c.security_class, 'SQL$444')
3559        else:
3560            self.assertEqual(c.security_class, 'SQL$483')
3561        self.assertEqual(c.owner_name, 'SYSDBA')
3562        if self.con.ods <= fdb.ODS_FB_20:
3563            self.assertEqual(c.default_class, 'SQL$DEFAULT17')
3564        elif (self.con.ods > fdb.ODS_FB_20) and (self.con.ods < fdb.ODS_FB_30):
3565            self.assertEqual(c.default_class, 'SQL$DEFAULT19')
3566        elif self.con.ods >= fdb.ODS_FB_30:
3567            self.assertEqual(c.default_class, 'SQL$DEFAULT55')
3568        self.assertEqual(c.flags, 1)
3569        self.assertListEqual([x.name for x in c.columns], ['EMP_NO', 'FIRST_NAME',
3570                                                           'LAST_NAME', 'PHONE_EXT',
3571                                                           'LOCATION', 'PHONE_NO'])
3572        self.assertListEqual(c.triggers, [])
3573        #
3574        self.assertEqual(c.get_column('LAST_NAME').name, 'LAST_NAME')
3575        self.assertFalse(c.has_checkoption())
3576        #
3577        self.assertEqual(c.get_sql_for('create'),
3578                         """CREATE VIEW PHONE_LIST (EMP_NO,FIRST_NAME,LAST_NAME,PHONE_EXT,LOCATION,PHONE_NO)
3579   AS
3580     SELECT
3581    emp_no, first_name, last_name, phone_ext, location, phone_no
3582    FROM employee, department
3583    WHERE employee.dept_no = department.dept_no""")
3584        self.assertEqual(c.get_sql_for('recreate'),
3585                         """RECREATE VIEW PHONE_LIST (EMP_NO,FIRST_NAME,LAST_NAME,PHONE_EXT,LOCATION,PHONE_NO)
3586   AS
3587     SELECT
3588    emp_no, first_name, last_name, phone_ext, location, phone_no
3589    FROM employee, department
3590    WHERE employee.dept_no = department.dept_no""")
3591        self.assertEqual(c.get_sql_for('drop'), "DROP VIEW PHONE_LIST")
3592        self.assertEqual(c.get_sql_for('alter', query='select * from country'),
3593                         "ALTER VIEW PHONE_LIST \n   AS\n     select * from country")
3594        self.assertEqual(c.get_sql_for('alter', columns='country,currency',
3595                                       query='select * from country'),
3596                         "ALTER VIEW PHONE_LIST (country,currency)\n   AS\n     select * from country")
3597        self.assertEqual(c.get_sql_for('alter', columns='country,currency',
3598                                       query='select * from country', check=True),
3599                         "ALTER VIEW PHONE_LIST (country,currency)\n   AS\n     select * from country\n     WITH CHECK OPTION")
3600        self.assertEqual(c.get_sql_for('alter', columns=('country', 'currency'),
3601                                       query='select * from country', check=True),
3602                         "ALTER VIEW PHONE_LIST (country,currency)\n   AS\n     select * from country\n     WITH CHECK OPTION")
3603        with self.assertRaises(fdb.ProgrammingError) as cm:
3604            c.get_sql_for('alter', badparam='select * from country')
3605        self.assertTupleEqual(cm.exception.args,
3606                              ("Unsupported parameter(s) 'badparam'",))
3607        with self.assertRaises(fdb.ProgrammingError) as cm:
3608            c.get_sql_for('alter')
3609        self.assertTupleEqual(cm.exception.args, ("Missing required parameter: 'query'.",))
3610        self.assertEqual(c.get_sql_for('create_or_alter'),
3611                         """CREATE OR ALTER VIEW PHONE_LIST (EMP_NO,FIRST_NAME,LAST_NAME,PHONE_EXT,LOCATION,PHONE_NO)
3612   AS
3613     SELECT
3614    emp_no, first_name, last_name, phone_ext, location, phone_no
3615    FROM employee, department
3616    WHERE employee.dept_no = department.dept_no""")
3617        self.assertEqual(c.get_sql_for('comment'),
3618                         'COMMENT ON VIEW PHONE_LIST IS NULL')
3619
3620    def testTrigger(self):
3621        # System trigger
3622        c = self.con.schema.get_trigger('RDB$TRIGGER_1')
3623        # common properties
3624        self.assertEqual(c.name, 'RDB$TRIGGER_1')
3625        self.assertIsNone(c.description)
3626        self.assertListEqual(c.actions, ['comment'])
3627        self.assertTrue(c.issystemobject())
3628        self.assertEqual(c.get_quoted_name(), 'RDB$TRIGGER_1')
3629        self.assertListEqual(c.get_dependents(), [])
3630        self.assertListEqual(c.get_dependencies(), [])
3631        # User trigger
3632        c = self.con.schema.get_trigger('SET_EMP_NO')
3633        # common properties
3634        self.assertEqual(c.name, 'SET_EMP_NO')
3635        self.assertIsNone(c.description)
3636        self.assertListEqual(c.actions,
3637                             ['comment', 'create', 'recreate', 'alter', 'create_or_alter', 'drop'])
3638        self.assertFalse(c.issystemobject())
3639        self.assertEqual(c.get_quoted_name(), 'SET_EMP_NO')
3640        self.assertListEqual(c.get_dependents(), [])
3641        d = c.get_dependencies()
3642        self.assertListEqual([(x.depended_on_name, x.field_name, x.depended_on_type) for x in d],
3643                             [('EMPLOYEE', 'EMP_NO', 0), ('EMP_NO_GEN', None, 14)])
3644        #
3645        self.assertEqual(c.relation.name, 'EMPLOYEE')
3646        self.assertEqual(c.sequence, 0)
3647        self.assertEqual(c.trigger_type, 1)
3648        self.assertEqual(c.source,
3649                         "AS\nBEGIN\n    if (new.emp_no is null) then\n    new.emp_no = gen_id(emp_no_gen, 1);\nEND")
3650        self.assertEqual(c.flags, 1)
3651        #
3652        self.assertTrue(c.isactive())
3653        self.assertTrue(c.isbefore())
3654        self.assertFalse(c.isafter())
3655        self.assertFalse(c.isdbtrigger())
3656        self.assertTrue(c.isinsert())
3657        self.assertFalse(c.isupdate())
3658        self.assertFalse(c.isdelete())
3659        self.assertEqual(c.get_type_as_string(), 'BEFORE INSERT')
3660        #
3661        if self.con.ods < fdb.ODS_FB_30:
3662            self.assertIsNone(c.valid_blr)
3663            self.assertIsNone(c.engine_name)
3664            self.assertIsNone(c.entrypoint)
3665        else:
3666            self.assertEqual(c.valid_blr, 1)
3667            self.assertIsNone(c.engine_name)
3668            self.assertIsNone(c.entrypoint)
3669        #
3670        self.assertEqual(c.get_sql_for('create'),
3671                         """CREATE TRIGGER SET_EMP_NO FOR EMPLOYEE ACTIVE
3672BEFORE INSERT POSITION 0
3673AS
3674BEGIN
3675    if (new.emp_no is null) then
3676    new.emp_no = gen_id(emp_no_gen, 1);
3677END""")
3678        self.assertEqual(c.get_sql_for('recreate'),
3679                         """RECREATE TRIGGER SET_EMP_NO FOR EMPLOYEE ACTIVE
3680BEFORE INSERT POSITION 0
3681AS
3682BEGIN
3683    if (new.emp_no is null) then
3684    new.emp_no = gen_id(emp_no_gen, 1);
3685END""")
3686        with self.assertRaises(fdb.ProgrammingError) as cm:
3687            c.get_sql_for('alter')
3688        self.assertTupleEqual(cm.exception.args,
3689                              ("Header or body definition required.",))
3690        with self.assertRaises(fdb.ProgrammingError) as cm:
3691            c.get_sql_for('alter', declare="DECLARE VARIABLE i integer;")
3692        self.assertTupleEqual(cm.exception.args,
3693                              ("Header or body definition required.",))
3694        self.assertEqual(c.get_sql_for('alter', fire_on='AFTER INSERT',
3695                                       active=False, sequence=0,
3696                                       declare='  DECLARE VARIABLE i integer;\n  DECLARE VARIABLE x integer;',
3697                                       code='  i = 1;\n  x = 2;'),
3698                         """ALTER TRIGGER SET_EMP_NO INACTIVE
3699  AFTER INSERT
3700  POSITION 0
3701AS
3702  DECLARE VARIABLE i integer;
3703  DECLARE VARIABLE x integer;
3704BEGIN
3705  i = 1;
3706  x = 2;
3707END""")
3708        self.assertEqual(c.get_sql_for('alter',
3709                                       declare=['DECLARE VARIABLE i integer;',
3710                                                'DECLARE VARIABLE x integer;'],
3711                                       code=['i = 1;', 'x = 2;']),
3712                         """ALTER TRIGGER SET_EMP_NO
3713AS
3714  DECLARE VARIABLE i integer;
3715  DECLARE VARIABLE x integer;
3716BEGIN
3717  i = 1;
3718  x = 2;
3719END""")
3720        self.assertEqual(c.get_sql_for('alter', active=False),
3721                         "ALTER TRIGGER SET_EMP_NO INACTIVE")
3722        self.assertEqual(c.get_sql_for('alter', sequence=10,
3723                                       code=('i = 1;', 'x = 2;')),
3724                         """ALTER TRIGGER SET_EMP_NO
3725  POSITION 10
3726AS
3727BEGIN
3728  i = 1;
3729  x = 2;
3730END""")
3731        with self.assertRaises(fdb.ProgrammingError) as cm:
3732            c.get_sql_for('alter', fire_on='ON CONNECT')
3733        self.assertTupleEqual(cm.exception.args,
3734                              ("Trigger type change is not allowed.",))
3735        self.assertEqual(c.get_sql_for('create_or_alter'),
3736                         """CREATE OR ALTER TRIGGER SET_EMP_NO FOR EMPLOYEE ACTIVE
3737BEFORE INSERT POSITION 0
3738AS
3739BEGIN
3740    if (new.emp_no is null) then
3741    new.emp_no = gen_id(emp_no_gen, 1);
3742END""")
3743        self.assertEqual(c.get_sql_for('drop'), "DROP TRIGGER SET_EMP_NO")
3744        self.assertEqual(c.get_sql_for('comment'),
3745                         'COMMENT ON TRIGGER SET_EMP_NO IS NULL')
3746        # Multi-trigger
3747        c = self.con.schema.get_trigger('TR_MULTI')
3748        #
3749        self.assertTrue(c.isinsert())
3750        self.assertTrue(c.isupdate())
3751        self.assertTrue(c.isdelete())
3752        self.assertEqual(c.get_type_as_string(),
3753                         'AFTER INSERT OR UPDATE OR DELETE')
3754        # DB trigger
3755        c = self.con.schema.get_trigger('TR_CONNECT')
3756        #
3757        self.assertTrue(c.isdbtrigger())
3758        self.assertFalse(c.isinsert())
3759        self.assertFalse(c.isupdate())
3760        self.assertFalse(c.isdelete())
3761        self.assertEqual(c.get_type_as_string(), 'ON CONNECT')
3762
3763    def testProcedureParameter(self):
3764        # Input parameter
3765        c = self.con.schema.get_procedure('GET_EMP_PROJ').input_params[0]
3766        # common properties
3767        self.assertEqual(c.name, 'EMP_NO')
3768        self.assertIsNone(c.description)
3769        self.assertListEqual(c.actions, ['comment'])
3770        self.assertFalse(c.issystemobject())
3771        self.assertEqual(c.get_quoted_name(), 'EMP_NO')
3772        self.assertListEqual(c.get_dependents(), [])
3773        self.assertListEqual(c.get_dependencies(), [])
3774        #
3775        self.assertEqual(c.procedure.name, 'GET_EMP_PROJ')
3776        self.assertEqual(c.sequence, 0)
3777        self.assertEqual(c.domain.name, 'RDB$32')
3778        self.assertEqual(c.datatype, 'SMALLINT')
3779        self.assertEqual(c.type_from, sm.PROCPAR_DATATYPE)
3780        self.assertIsNone(c.default)
3781        self.assertIsNone(c.collation)
3782        if self.con.ods <= fdb.ODS_FB_25:
3783            self.assertEqual(c.mechanism, 0)
3784        elif self.con.ods > fdb.ODS_FB_25:
3785            self.assertEqual(c.mechanism, 0)
3786        self.assertIsNone(c.column)
3787        #
3788        self.assertTrue(c.isinput())
3789        self.assertTrue(c.isnullable())
3790        self.assertFalse(c.has_default())
3791        self.assertEqual(c.get_sql_definition(), 'EMP_NO SMALLINT')
3792        # Output parameter
3793        c = self.con.schema.get_procedure('GET_EMP_PROJ').output_params[0]
3794        # common properties
3795        self.assertEqual(c.name, 'PROJ_ID')
3796        self.assertIsNone(c.description)
3797        self.assertListEqual(c.actions, ['comment'])
3798        self.assertFalse(c.issystemobject())
3799        self.assertEqual(c.get_quoted_name(), 'PROJ_ID')
3800        self.assertListEqual(c.get_dependents(), [])
3801        self.assertListEqual(c.get_dependencies(), [])
3802        #
3803        self.assertEqual(c.get_sql_for('comment'),
3804                         'COMMENT ON PARAMETER GET_EMP_PROJ.PROJ_ID IS NULL')
3805        #
3806        self.assertFalse(c.isinput())
3807        self.assertEqual(c.get_sql_definition(), 'PROJ_ID CHAR(5)')
3808    def testProcedure(self):
3809        c = self.con.schema.get_procedure('GET_EMP_PROJ')
3810        # common properties
3811        self.assertEqual(c.name, 'GET_EMP_PROJ')
3812        self.assertIsNone(c.description)
3813        self.assertListEqual(c.actions, ['comment', 'create',
3814                                         'recreate', 'alter',
3815                                         'create_or_alter', 'drop'])
3816        self.assertFalse(c.issystemobject())
3817        self.assertEqual(c.get_quoted_name(), 'GET_EMP_PROJ')
3818        self.assertListEqual(c.get_dependents(), [])
3819        d = c.get_dependencies()
3820        self.assertListEqual([(x.depended_on_name, x.field_name, x.depended_on_type) for x in d],
3821                             [('EMPLOYEE_PROJECT', 'PROJ_ID', 0), ('EMPLOYEE_PROJECT', 'EMP_NO', 0),
3822                              ('EMPLOYEE_PROJECT', None, 0)])
3823        #
3824        self.assertEqual(c.id, 1)
3825        self.assertEqual(c.source, """BEGIN
3826	FOR SELECT proj_id
3827		FROM employee_project
3828		WHERE emp_no = :emp_no
3829		INTO :proj_id
3830	DO
3831		SUSPEND;
3832END""")
3833        if self.con.ods < fdb.ODS_FB_25:
3834            self.assertEqual(c.security_class, 'SQL$GET_EMP_PROJ')
3835        elif self.con.ods == fdb.ODS_FB_25:
3836            self.assertEqual(c.security_class, 'SQL$20')
3837        elif self.con.ods >= fdb.ODS_FB_30:
3838            self.assertEqual(c.security_class, 'SQL$473')
3839        self.assertEqual(c.owner_name, 'SYSDBA')
3840        self.assertListEqual([x.name for x in c.input_params], ['EMP_NO'])
3841        self.assertListEqual([x.name for x in c.output_params], ['PROJ_ID'])
3842        if self.con.engine_version >= 3.0:
3843            self.assertTrue(c.valid_blr)
3844            self.assertEqual(c.proc_type, 1)
3845            self.assertIsNone(c.engine_name)
3846            self.assertIsNone(c.entrypoint)
3847            self.assertIsNone(c.package)
3848            self.assertIsNone(c.privacy)
3849        else:
3850            self.assertIsNone(c.valid_blr)
3851            self.assertEqual(c.proc_type, 0)
3852        #
3853        self.assertEqual(c.get_param('EMP_NO').name, 'EMP_NO')
3854        self.assertEqual(c.get_param('PROJ_ID').name, 'PROJ_ID')
3855        #
3856        self.assertEqual(c.get_sql_for('create'),
3857                         """CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3858RETURNS (PROJ_ID CHAR(5))
3859AS
3860BEGIN
3861	FOR SELECT proj_id
3862		FROM employee_project
3863		WHERE emp_no = :emp_no
3864		INTO :proj_id
3865	DO
3866		SUSPEND;
3867END""")
3868        if self.version == FB30:
3869            self.assertEqual(c.get_sql_for('create', no_code=True),
3870                             """CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3871RETURNS (PROJ_ID CHAR(5))
3872AS
3873BEGIN
3874  SUSPEND;
3875END""")
3876        else:
3877            self.assertEqual(c.get_sql_for('create', no_code=True),
3878                             """CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3879RETURNS (PROJ_ID CHAR(5))
3880AS
3881BEGIN
3882END""")
3883        self.assertEqual(c.get_sql_for('recreate'),
3884                         """RECREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3885RETURNS (PROJ_ID CHAR(5))
3886AS
3887BEGIN
3888	FOR SELECT proj_id
3889		FROM employee_project
3890		WHERE emp_no = :emp_no
3891		INTO :proj_id
3892	DO
3893		SUSPEND;
3894END""")
3895        if self.version == FB30:
3896            self.assertEqual(c.get_sql_for('recreate', no_code=True),
3897                             """RECREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3898RETURNS (PROJ_ID CHAR(5))
3899AS
3900BEGIN
3901  SUSPEND;
3902END""")
3903        else:
3904            self.assertEqual(c.get_sql_for('recreate', no_code=True),
3905                             """RECREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3906RETURNS (PROJ_ID CHAR(5))
3907AS
3908BEGIN
3909END""")
3910
3911        self.assertEqual(c.get_sql_for('create_or_alter'),
3912                         """CREATE OR ALTER PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3913RETURNS (PROJ_ID CHAR(5))
3914AS
3915BEGIN
3916	FOR SELECT proj_id
3917		FROM employee_project
3918		WHERE emp_no = :emp_no
3919		INTO :proj_id
3920	DO
3921		SUSPEND;
3922END""")
3923        if self.version == FB30:
3924            self.assertEqual(c.get_sql_for('create_or_alter', no_code=True),
3925                             """CREATE OR ALTER PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3926RETURNS (PROJ_ID CHAR(5))
3927AS
3928BEGIN
3929  SUSPEND;
3930END""")
3931        else:
3932            self.assertEqual(c.get_sql_for('create_or_alter', no_code=True),
3933                             """CREATE OR ALTER PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)
3934RETURNS (PROJ_ID CHAR(5))
3935AS
3936BEGIN
3937END""")
3938        self.assertEqual(c.get_sql_for('drop'), "DROP PROCEDURE GET_EMP_PROJ")
3939        self.assertEqual(c.get_sql_for('alter', code="  /* PASS */"),
3940                         """ALTER PROCEDURE GET_EMP_PROJ
3941AS
3942BEGIN
3943  /* PASS */
3944END""")
3945        with self.assertRaises(fdb.ProgrammingError) as cm:
3946            c.get_sql_for('alter', declare="DECLARE VARIABLE i integer;")
3947            self.assertTupleEqual(cm.exception.args,
3948                                  ("Missing required parameter: 'code'.",))
3949        self.assertEqual(c.get_sql_for('alter', code=''),
3950                         """ALTER PROCEDURE GET_EMP_PROJ
3951AS
3952BEGIN
3953END""")
3954        self.assertEqual(c.get_sql_for('alter', input="IN1 integer", code=''),
3955                         """ALTER PROCEDURE GET_EMP_PROJ (IN1 integer)
3956AS
3957BEGIN
3958END""")
3959        self.assertEqual(c.get_sql_for('alter', output="OUT1 integer", code=''),
3960                         """ALTER PROCEDURE GET_EMP_PROJ
3961RETURNS (OUT1 integer)
3962AS
3963BEGIN
3964END""")
3965        self.assertEqual(c.get_sql_for('alter', input="IN1 integer",
3966                                       output="OUT1 integer", code=''),
3967                         """ALTER PROCEDURE GET_EMP_PROJ (IN1 integer)
3968RETURNS (OUT1 integer)
3969AS
3970BEGIN
3971END""")
3972        self.assertEqual(c.get_sql_for('alter',
3973                                       input=["IN1 integer", "IN2 VARCHAR(10)"],
3974                                       code=''),
3975                         """ALTER PROCEDURE GET_EMP_PROJ (
3976  IN1 integer,
3977  IN2 VARCHAR(10)
3978)
3979AS
3980BEGIN
3981END""")
3982        self.assertEqual(c.get_sql_for('alter',
3983                                       output=["OUT1 integer", "OUT2 VARCHAR(10)"],
3984                                       code=''),
3985                         """ALTER PROCEDURE GET_EMP_PROJ
3986RETURNS (
3987  OUT1 integer,
3988  OUT2 VARCHAR(10)
3989)
3990AS
3991BEGIN
3992END""")
3993        self.assertEqual(c.get_sql_for('alter',
3994                                       input=["IN1 integer", "IN2 VARCHAR(10)"],
3995                                       output=["OUT1 integer", "OUT2 VARCHAR(10)"],
3996                                       code=''),
3997                         """ALTER PROCEDURE GET_EMP_PROJ (
3998  IN1 integer,
3999  IN2 VARCHAR(10)
4000)
4001RETURNS (
4002  OUT1 integer,
4003  OUT2 VARCHAR(10)
4004)
4005AS
4006BEGIN
4007END""")
4008        self.assertEqual(c.get_sql_for('alter', code="  -- line 1;\n  -- line 2;"),
4009                         """ALTER PROCEDURE GET_EMP_PROJ
4010AS
4011BEGIN
4012  -- line 1;
4013  -- line 2;
4014END""")
4015        self.assertEqual(c.get_sql_for('alter', code=["-- line 1;", "-- line 2;"]),
4016                         """ALTER PROCEDURE GET_EMP_PROJ
4017AS
4018BEGIN
4019  -- line 1;
4020  -- line 2;
4021END""")
4022        self.assertEqual(c.get_sql_for('alter', code="  /* PASS */",
4023                                       declare="  -- line 1;\n  -- line 2;"),
4024                         """ALTER PROCEDURE GET_EMP_PROJ
4025AS
4026  -- line 1;
4027  -- line 2;
4028BEGIN
4029  /* PASS */
4030END""")
4031        self.assertEqual(c.get_sql_for('alter', code="  /* PASS */",
4032                                       declare=["-- line 1;", "-- line 2;"]),
4033                         """ALTER PROCEDURE GET_EMP_PROJ
4034AS
4035  -- line 1;
4036  -- line 2;
4037BEGIN
4038  /* PASS */
4039END""")
4040        self.assertEqual(c.get_sql_for('comment'),
4041                         'COMMENT ON PROCEDURE GET_EMP_PROJ IS NULL')
4042    def testRole(self):
4043        c = self.con.schema.get_role('TEST_ROLE')
4044        # common properties
4045        self.assertEqual(c.name, 'TEST_ROLE')
4046        self.assertIsNone(c.description)
4047        self.assertListEqual(c.actions, ['comment', 'create', 'drop'])
4048        self.assertFalse(c.issystemobject())
4049        self.assertEqual(c.get_quoted_name(), 'TEST_ROLE')
4050        self.assertListEqual(c.get_dependents(), [])
4051        self.assertListEqual(c.get_dependencies(), [])
4052        #
4053        self.assertEqual(c.owner_name, 'SYSDBA')
4054        #
4055        self.assertEqual(c.get_sql_for('create'), "CREATE ROLE TEST_ROLE")
4056        self.assertEqual(c.get_sql_for('drop'), "DROP ROLE TEST_ROLE")
4057        self.assertEqual(c.get_sql_for('comment'),
4058                         'COMMENT ON ROLE TEST_ROLE IS NULL')
4059    def _mockFunction(self, name):
4060        f = None
4061        if name == 'STRLEN':
4062            f = sm.Function(self.con.schema,
4063                            {'RDB$ENTRYPOINT': 'IB_UDF_strlen                  ',
4064                             'RDB$SYSTEM_FLAG': 0, 'RDB$RETURN_ARGUMENT': 0,
4065                             'RDB$MODULE_NAME': 'ib_udf', 'RDB$FUNCTION_TYPE': None,
4066                             'RDB$DESCRIPTION': None,
4067                             'RDB$FUNCTION_NAME': 'STRLEN                         '})
4068            f._load_arguments(
4069                [{'RDB$FIELD_PRECISION': 0, 'RDB$FIELD_LENGTH': 4,
4070                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 0,
4071                  'RDB$FIELD_TYPE': 8, 'RDB$MECHANISM': 0,
4072                  'RDB$CHARACTER_SET_ID': None, 'RDB$CHARACTER_LENGTH': None,
4073                  'RDB$FUNCTION_NAME': 'STRLEN                         ',
4074                  'RDB$ARGUMENT_POSITION': 0},
4075                 {'RDB$FIELD_PRECISION': None, 'RDB$FIELD_LENGTH': 32767,
4076                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 0,
4077                  'RDB$FIELD_TYPE': 40, 'RDB$MECHANISM': 1,
4078                  'RDB$CHARACTER_SET_ID': 0, 'RDB$CHARACTER_LENGTH': 32767,
4079                  'RDB$FUNCTION_NAME': 'STRLEN                         ',
4080                  'RDB$ARGUMENT_POSITION': 1}])
4081        elif name == 'STRING2BLOB':
4082            f = sm.Function(self.con.schema,
4083                            {'RDB$ENTRYPOINT': 'string2blob                    ',
4084                             'RDB$SYSTEM_FLAG': 0, 'RDB$RETURN_ARGUMENT': 2,
4085                             'RDB$MODULE_NAME': 'fbudf', 'RDB$FUNCTION_TYPE': None,
4086                             'RDB$DESCRIPTION': None,
4087                             'RDB$FUNCTION_NAME': 'STRING2BLOB                    '})
4088            f._load_arguments(
4089                [{'RDB$FIELD_PRECISION': None, 'RDB$FIELD_LENGTH': 300,
4090                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 0,
4091                  'RDB$FIELD_TYPE': 37, 'RDB$MECHANISM': 2,
4092                  'RDB$CHARACTER_SET_ID': 0, 'RDB$CHARACTER_LENGTH': 300,
4093                  'RDB$FUNCTION_NAME': 'STRING2BLOB                    ',
4094                  'RDB$ARGUMENT_POSITION': 1},
4095                 {'RDB$FIELD_PRECISION': None, 'RDB$FIELD_LENGTH': 8,
4096                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 0,
4097                  'RDB$FIELD_TYPE': 261, 'RDB$MECHANISM': 3,
4098                  'RDB$CHARACTER_SET_ID': None, 'RDB$CHARACTER_LENGTH': None,
4099                  'RDB$FUNCTION_NAME': 'STRING2BLOB                    ',
4100                  'RDB$ARGUMENT_POSITION': 2}])
4101        elif name == 'LTRIM':
4102            f = sm.Function(self.con.schema,
4103                            {'RDB$ENTRYPOINT': 'IB_UDF_ltrim                   ',
4104                             'RDB$SYSTEM_FLAG': 0, 'RDB$RETURN_ARGUMENT': 0,
4105                             'RDB$MODULE_NAME': 'ib_udf', 'RDB$FUNCTION_TYPE': None,
4106                             'RDB$DESCRIPTION': None,
4107                             'RDB$FUNCTION_NAME': 'LTRIM                          '})
4108            f._load_arguments(
4109                [{'RDB$FIELD_PRECISION': None, 'RDB$FIELD_LENGTH': 255,
4110                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 0,
4111                  'RDB$FIELD_TYPE': 40, 'RDB$MECHANISM': -1,
4112                  'RDB$CHARACTER_SET_ID': 0, 'RDB$CHARACTER_LENGTH': 255,
4113                  'RDB$FUNCTION_NAME': 'LTRIM                          ',
4114                  'RDB$ARGUMENT_POSITION': 0},
4115                 {'RDB$FIELD_PRECISION': None, 'RDB$FIELD_LENGTH': 255,
4116                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 0,
4117                  'RDB$FIELD_TYPE': 40, 'RDB$MECHANISM': 1,
4118                  'RDB$CHARACTER_SET_ID': 0, 'RDB$CHARACTER_LENGTH': 255,
4119                  'RDB$FUNCTION_NAME': 'LTRIM                          ',
4120                  'RDB$ARGUMENT_POSITION': 1}])
4121        elif name == 'I64NVL':
4122            f = sm.Function(self.con.schema,
4123                            {'RDB$ENTRYPOINT': 'idNvl                          ',
4124                             'RDB$SYSTEM_FLAG': 0, 'RDB$RETURN_ARGUMENT': 0,
4125                             'RDB$MODULE_NAME': 'fbudf', 'RDB$FUNCTION_TYPE': None,
4126                             'RDB$DESCRIPTION': None,
4127                             'RDB$FUNCTION_NAME': 'I64NVL                         '})
4128            f._load_arguments(
4129                [{'RDB$FIELD_PRECISION': 18, 'RDB$FIELD_LENGTH': 8,
4130                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 1,
4131                  'RDB$FIELD_TYPE': 16, 'RDB$MECHANISM': 2,
4132                  'RDB$CHARACTER_SET_ID': None, 'RDB$CHARACTER_LENGTH': None,
4133                  'RDB$FUNCTION_NAME': 'I64NVL                         ',
4134                  'RDB$ARGUMENT_POSITION': 0},
4135                 {'RDB$FIELD_PRECISION': 18, 'RDB$FIELD_LENGTH': 8,
4136                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 1,
4137                  'RDB$FIELD_TYPE': 16, 'RDB$MECHANISM': 2,
4138                  'RDB$CHARACTER_SET_ID': None, 'RDB$CHARACTER_LENGTH': None,
4139                  'RDB$FUNCTION_NAME': 'I64NVL                         ',
4140                  'RDB$ARGUMENT_POSITION': 1},
4141                 {'RDB$FIELD_PRECISION': 18, 'RDB$FIELD_LENGTH': 8,
4142                  'RDB$FIELD_SCALE': 0, 'RDB$FIELD_SUB_TYPE': 1,
4143                  'RDB$FIELD_TYPE': 16, 'RDB$MECHANISM': 2,
4144                  'RDB$CHARACTER_SET_ID': None, 'RDB$CHARACTER_LENGTH': None,
4145                  'RDB$FUNCTION_NAME': 'I64NVL                         ',
4146                  'RDB$ARGUMENT_POSITION': 2}])
4147        if f:
4148            return f
4149        else:
4150            raise Exception("Udefined function for mock.")
4151    def testFunctionArgument(self):
4152        f = self._mockFunction('STRLEN')
4153        c = f.arguments[0]
4154        self.assertEqual(len(f.arguments), 1)
4155        # common properties
4156        self.assertEqual(c.name, 'STRLEN_1')
4157        self.assertIsNone(c.description)
4158        self.assertListEqual(c.actions, [])
4159        self.assertFalse(c.issystemobject())
4160        self.assertEqual(c.get_quoted_name(), 'STRLEN_1')
4161        self.assertListEqual(c.get_dependents(), [])
4162        self.assertListEqual(c.get_dependencies(), [])
4163        #
4164        self.assertEqual(c.function.name, 'STRLEN')
4165        self.assertEqual(c.position, 1)
4166        self.assertEqual(c.mechanism, 1)
4167        self.assertEqual(c.field_type, 40)
4168        self.assertEqual(c.length, 32767)
4169        self.assertEqual(c.scale, 0)
4170        self.assertIsNone(c.precision)
4171        self.assertEqual(c.sub_type, 0)
4172        self.assertEqual(c.character_length, 32767)
4173        self.assertEqual(c.character_set.name, 'NONE')
4174        self.assertEqual(c.datatype, 'CSTRING(32767)')
4175        #
4176        self.assertFalse(c.isbyvalue())
4177        self.assertTrue(c.isbyreference())
4178        self.assertFalse(c.isbydescriptor())
4179        self.assertFalse(c.iswithnull())
4180        self.assertFalse(c.isfreeit())
4181        self.assertFalse(c.isreturning())
4182        self.assertEqual(c.get_sql_definition(), 'CSTRING(32767)')
4183        #
4184        c = f.returns
4185        #
4186        self.assertEqual(c.position, 0)
4187        self.assertEqual(c.mechanism, 0)
4188        self.assertEqual(c.field_type, 8)
4189        self.assertEqual(c.length, 4)
4190        self.assertEqual(c.scale, 0)
4191        self.assertEqual(c.precision, 0)
4192        self.assertEqual(c.sub_type, 0)
4193        self.assertIsNone(c.character_length)
4194        self.assertIsNone(c.character_set)
4195        self.assertEqual(c.datatype, 'INTEGER')
4196        #
4197        self.assertTrue(c.isbyvalue())
4198        self.assertFalse(c.isbyreference())
4199        self.assertFalse(c.isbydescriptor())
4200        self.assertFalse(c.iswithnull())
4201        self.assertFalse(c.isfreeit())
4202        self.assertTrue(c.isreturning())
4203        self.assertEqual(c.get_sql_definition(), 'INTEGER BY VALUE')
4204        #
4205        f = self._mockFunction('STRING2BLOB')
4206        self.assertEqual(len(f.arguments), 2)
4207        c = f.arguments[0]
4208        self.assertEqual(c.function.name, 'STRING2BLOB')
4209        self.assertEqual(c.position, 1)
4210        self.assertEqual(c.mechanism, 2)
4211        self.assertEqual(c.field_type, 37)
4212        self.assertEqual(c.length, 300)
4213        self.assertEqual(c.scale, 0)
4214        self.assertIsNone(c.precision)
4215        self.assertEqual(c.sub_type, 0)
4216        self.assertEqual(c.character_length, 300)
4217        self.assertEqual(c.character_set.name, 'NONE')
4218        self.assertEqual(c.datatype, 'VARCHAR(300)')
4219        #
4220        self.assertFalse(c.isbyvalue())
4221        self.assertFalse(c.isbyreference())
4222        self.assertTrue(c.isbydescriptor())
4223        self.assertFalse(c.iswithnull())
4224        self.assertFalse(c.isfreeit())
4225        self.assertFalse(c.isreturning())
4226        self.assertEqual(c.get_sql_definition(), 'VARCHAR(300) BY DESCRIPTOR')
4227        #
4228        c = f.arguments[1]
4229        self.assertIs(f.arguments[1], f.returns)
4230        self.assertEqual(c.function.name, 'STRING2BLOB')
4231        self.assertEqual(c.position, 2)
4232        self.assertEqual(c.mechanism, 3)
4233        self.assertEqual(c.field_type, 261)
4234        self.assertEqual(c.length, 8)
4235        self.assertEqual(c.scale, 0)
4236        self.assertIsNone(c.precision)
4237        self.assertEqual(c.sub_type, 0)
4238        self.assertIsNone(c.character_length)
4239        self.assertIsNone(c.character_set)
4240        self.assertEqual(c.datatype, 'BLOB')
4241        #
4242        self.assertFalse(c.isbyvalue())
4243        self.assertFalse(c.isbyreference())
4244        self.assertFalse(c.isbydescriptor())
4245        self.assertTrue(c.isbydescriptor(any=True))
4246        self.assertFalse(c.iswithnull())
4247        self.assertFalse(c.isfreeit())
4248        self.assertTrue(c.isreturning())
4249        self.assertEqual(c.get_sql_definition(), 'BLOB')
4250        #
4251        f = self._mockFunction('LTRIM')
4252        self.assertEqual(len(f.arguments), 1)
4253        c = f.arguments[0]
4254        self.assertEqual(c.function.name, 'LTRIM')
4255        self.assertEqual(c.position, 1)
4256        self.assertEqual(c.mechanism, 1)
4257        self.assertEqual(c.field_type, 40)
4258        self.assertEqual(c.length, 255)
4259        self.assertEqual(c.scale, 0)
4260        self.assertIsNone(c.precision)
4261        self.assertEqual(c.sub_type, 0)
4262        self.assertEqual(c.character_length, 255)
4263        self.assertEqual(c.character_set.name, 'NONE')
4264        self.assertEqual(c.datatype, 'CSTRING(255)')
4265        #
4266        self.assertFalse(c.isbyvalue())
4267        self.assertTrue(c.isbyreference())
4268        self.assertFalse(c.isbydescriptor())
4269        self.assertFalse(c.iswithnull())
4270        self.assertFalse(c.isfreeit())
4271        self.assertFalse(c.isreturning())
4272        self.assertEqual(c.get_sql_definition(), 'CSTRING(255)')
4273        #
4274        c = f.returns
4275        self.assertEqual(c.function.name, 'LTRIM')
4276        self.assertEqual(c.position, 0)
4277        self.assertEqual(c.mechanism, 1)
4278        self.assertEqual(c.field_type, 40)
4279        self.assertEqual(c.length, 255)
4280        self.assertEqual(c.scale, 0)
4281        self.assertIsNone(c.precision)
4282        self.assertEqual(c.sub_type, 0)
4283        self.assertEqual(c.character_length, 255)
4284        self.assertEqual(c.character_set.name, 'NONE')
4285        self.assertEqual(c.datatype, 'CSTRING(255)')
4286        #
4287        self.assertFalse(c.isbyvalue())
4288        self.assertTrue(c.isbyreference())
4289        self.assertFalse(c.isbydescriptor())
4290        self.assertFalse(c.isbydescriptor(any=True))
4291        self.assertFalse(c.iswithnull())
4292        self.assertTrue(c.isfreeit())
4293        self.assertTrue(c.isreturning())
4294        self.assertEqual(c.get_sql_definition(), 'CSTRING(255)')
4295        #
4296        f = self._mockFunction('I64NVL')
4297        self.assertEqual(len(f.arguments), 2)
4298        for a in f.arguments:
4299            self.assertEqual(a.datatype, 'NUMERIC(18, 0)')
4300            self.assertTrue(a.isbydescriptor())
4301            self.assertEqual(a.get_sql_definition(),
4302                             'NUMERIC(18, 0) BY DESCRIPTOR')
4303        self.assertEqual(f.returns.datatype, 'NUMERIC(18, 0)')
4304        self.assertTrue(f.returns.isbydescriptor())
4305        self.assertEqual(f.returns.get_sql_definition(),
4306                         'NUMERIC(18, 0) BY DESCRIPTOR')
4307    def testFunction(self):
4308        c = self._mockFunction('STRLEN')
4309        self.assertEqual(len(c.arguments), 1)
4310        # common properties
4311        self.assertEqual(c.name, 'STRLEN')
4312        self.assertIsNone(c.description)
4313        self.assertIsNone(c.package)
4314        self.assertIsNone(c.engine_mame)
4315        self.assertIsNone(c.private_flag)
4316        self.assertIsNone(c.source)
4317        self.assertIsNone(c.id)
4318        self.assertIsNone(c.valid_blr)
4319        self.assertIsNone(c.security_class)
4320        self.assertIsNone(c.owner_name)
4321        self.assertIsNone(c.legacy_flag)
4322        self.assertIsNone(c.deterministic_flag)
4323        self.assertListEqual(c.actions, ['comment', 'declare', 'drop'])
4324        self.assertFalse(c.issystemobject())
4325        self.assertEqual(c.get_quoted_name(), 'STRLEN')
4326        self.assertListEqual(c.get_dependents(), [])
4327        self.assertListEqual(c.get_dependencies(), [])
4328        self.assertFalse(c.ispackaged())
4329        #
4330        self.assertEqual(c.module_name, 'ib_udf')
4331        self.assertEqual(c.entrypoint, 'IB_UDF_strlen')
4332        self.assertEqual(c.returns.name, 'STRLEN_0')
4333        self.assertListEqual([a.name for a in c.arguments], ['STRLEN_1'])
4334        #
4335        self.assertTrue(c.has_arguments())
4336        self.assertTrue(c.has_return())
4337        self.assertFalse(c.has_return_argument())
4338        #
4339        self.assertEqual(c.get_sql_for('drop'), "DROP EXTERNAL FUNCTION STRLEN")
4340        with self.assertRaises(fdb.ProgrammingError) as cm:
4341            c.get_sql_for('drop', badparam='')
4342        self.assertTupleEqual(cm.exception.args,
4343                              ("Unsupported parameter(s) 'badparam'",))
4344        self.assertEqual(c.get_sql_for('declare'),
4345                         """DECLARE EXTERNAL FUNCTION STRLEN
4346  CSTRING(32767)
4347RETURNS INTEGER BY VALUE
4348ENTRY_POINT 'IB_UDF_strlen'
4349MODULE_NAME 'ib_udf'""")
4350        with self.assertRaises(fdb.ProgrammingError) as cm:
4351            c.get_sql_for('declare', badparam='')
4352        self.assertTupleEqual(cm.exception.args,
4353                              ("Unsupported parameter(s) 'badparam'",))
4354        self.assertEqual(c.get_sql_for('comment'),
4355                         'COMMENT ON EXTERNAL FUNCTION STRLEN IS NULL')
4356        #
4357        c = self._mockFunction('STRING2BLOB')
4358        self.assertEqual(len(c.arguments), 2)
4359        #
4360        self.assertTrue(c.has_arguments())
4361        self.assertTrue(c.has_return())
4362        self.assertTrue(c.has_return_argument())
4363        #
4364        self.assertEqual(c.get_sql_for('declare'),
4365                         """DECLARE EXTERNAL FUNCTION STRING2BLOB
4366  VARCHAR(300) BY DESCRIPTOR,
4367  BLOB
4368RETURNS PARAMETER 2
4369ENTRY_POINT 'string2blob'
4370MODULE_NAME 'fbudf'""")
4371        #
4372        c = self._mockFunction('LTRIM')
4373        self.assertEqual(len(c.arguments), 1)
4374        #
4375        self.assertTrue(c.has_arguments())
4376        self.assertTrue(c.has_return())
4377        self.assertFalse(c.has_return_argument())
4378        #
4379        self.assertEqual(c.get_sql_for('declare'),
4380                         """DECLARE EXTERNAL FUNCTION LTRIM
4381  CSTRING(255)
4382RETURNS CSTRING(255) FREE_IT
4383ENTRY_POINT 'IB_UDF_ltrim'
4384MODULE_NAME 'ib_udf'""")
4385        #
4386        c = self._mockFunction('I64NVL')
4387        self.assertEqual(len(c.arguments), 2)
4388        #
4389        self.assertTrue(c.has_arguments())
4390        self.assertTrue(c.has_return())
4391        self.assertFalse(c.has_return_argument())
4392        #
4393        self.assertEqual(c.get_sql_for('declare'),
4394                         """DECLARE EXTERNAL FUNCTION I64NVL
4395  NUMERIC(18, 0) BY DESCRIPTOR,
4396  NUMERIC(18, 0) BY DESCRIPTOR
4397RETURNS NUMERIC(18, 0) BY DESCRIPTOR
4398ENTRY_POINT 'idNvl'
4399MODULE_NAME 'fbudf'""")
4400        #
4401        # Internal PSQL functions (Firebird 3.0)
4402        if self.con.ods >= fdb.ODS_FB_30:
4403            c = self.con.schema.get_function('F2')
4404            # common properties
4405            self.assertEqual(c.name, 'F2')
4406            self.assertIsNone(c.description)
4407            self.assertIsNone(c.package)
4408            self.assertIsNone(c.engine_mame)
4409            self.assertIsNone(c.private_flag)
4410            self.assertEqual(c.source, 'BEGIN\n  RETURN X+1;\nEND')
4411            self.assertEqual(c.id, 3)
4412            self.assertTrue(c.valid_blr)
4413            self.assertEqual(c.security_class, 'SQL$588')
4414            self.assertEqual(c.owner_name, 'SYSDBA')
4415            self.assertEqual(c.legacy_flag, 0)
4416            self.assertEqual(c.deterministic_flag, 0)
4417            #
4418            self.assertListEqual(c.actions, ['create', 'recreate', 'alter', 'create_or_alter', 'drop'])
4419            self.assertFalse(c.issystemobject())
4420            self.assertEqual(c.get_quoted_name(), 'F2')
4421            self.assertListEqual(c.get_dependents(), [])
4422            self.assertListEqual(c.get_dependencies(), [])
4423            #
4424            self.assertIsNone(c.module_name)
4425            self.assertIsNone(c.entrypoint)
4426            self.assertEqual(c.returns.name, 'F2_0')
4427            self.assertListEqual([a.name for a in c.arguments], ['X'])
4428            #
4429            self.assertTrue(c.has_arguments())
4430            self.assertTrue(c.has_return())
4431            self.assertFalse(c.has_return_argument())
4432            self.assertFalse(c.ispackaged())
4433            #
4434            self.assertEqual(c.get_sql_for('drop'), "DROP FUNCTION F2")
4435            self.assertEqual(c.get_sql_for('create'),
4436                             """CREATE FUNCTION F2 (X INTEGER)
4437RETURNS INTEGER
4438AS
4439BEGIN
4440  RETURN X+1;
4441END""")
4442            self.assertEqual(c.get_sql_for('create', no_code=True),
4443                             """CREATE FUNCTION F2 (X INTEGER)
4444RETURNS INTEGER
4445AS
4446BEGIN
4447END""")
4448            self.assertEqual(c.get_sql_for('recreate'),
4449                             """RECREATE FUNCTION F2 (X INTEGER)
4450RETURNS INTEGER
4451AS
4452BEGIN
4453  RETURN X+1;
4454END""")
4455
4456            self.assertEqual(c.get_sql_for('create_or_alter'),
4457                             """CREATE OR ALTER FUNCTION F2 (X INTEGER)
4458RETURNS INTEGER
4459AS
4460BEGIN
4461  RETURN X+1;
4462END""")
4463            with self.assertRaises(fdb.ProgrammingError) as cm:
4464                c.get_sql_for('alter', declare="DECLARE VARIABLE i integer;", code='')
4465            self.assertTupleEqual(cm.exception.args,
4466                                  ("Missing required parameter: 'returns'.",))
4467            with self.assertRaises(fdb.ProgrammingError) as cm:
4468                c.get_sql_for('alter', declare="DECLARE VARIABLE i integer;", returns='INTEGER')
4469            self.assertTupleEqual(cm.exception.args,
4470                                  ("Missing required parameter: 'code'.",))
4471            self.assertEqual(c.get_sql_for('alter', returns='INTEGER', code=''),
4472                             """ALTER FUNCTION F2
4473RETURNS INTEGER
4474AS
4475BEGIN
4476END""")
4477            self.assertEqual(c.get_sql_for('alter', arguments="IN1 integer", returns='INTEGER', code=''),
4478                             """ALTER FUNCTION F2 (IN1 integer)
4479RETURNS INTEGER
4480AS
4481BEGIN
4482END""")
4483            self.assertEqual(c.get_sql_for('alter', returns='INTEGER',
4484                                           arguments=["IN1 integer", "IN2 VARCHAR(10)"],
4485                                           code=''),
4486                             """ALTER FUNCTION F2 (
4487  IN1 integer,
4488  IN2 VARCHAR(10)
4489)
4490RETURNS INTEGER
4491AS
4492BEGIN
4493END""")
4494            #
4495            c = self.con.schema.get_function('FX')
4496            self.assertEqual(c.get_sql_for('create'),"""CREATE FUNCTION FX (
4497  F TYPE OF "FIRSTNAME",
4498  L TYPE OF COLUMN CUSTOMER.CONTACT_LAST
4499)
4500RETURNS VARCHAR(35)
4501AS
4502BEGIN
4503  RETURN L || \', \' || F;
4504END""")
4505                             #"""CREATE FUNCTION FX (
4506  #L TYPE OF COLUMN CUSTOMER.CONTACT_LAST
4507#)
4508#RETURNS VARCHAR(35)
4509#AS
4510#BEGIN
4511  #RETURN L || ', ' || F;
4512#END""")
4513            #
4514            c = self.con.schema.get_function('F1')
4515            self.assertEqual(c.name, 'F1')
4516            self.assertIsNotNone(c.package)
4517            self.assertIsInstance(c.package, sm.Package)
4518            self.assertListEqual(c.actions, [])
4519            self.assertTrue(c.private_flag)
4520            self.assertTrue(c.ispackaged())
4521
4522    def testDatabaseFile(self):
4523        # We have to use mock
4524        c = sm.DatabaseFile(self.con.schema, {'RDB$FILE_LENGTH': 1000,
4525                                              'RDB$FILE_NAME': '/path/dbfile.f02',
4526                                              'RDB$FILE_START': 500,
4527                                              'RDB$FILE_SEQUENCE': 1})
4528        # common properties
4529        self.assertEqual(c.name, 'FILE_1')
4530        self.assertIsNone(c.description)
4531        self.assertListEqual(c.actions, [])
4532        self.assertTrue(c.issystemobject())
4533        self.assertEqual(c.get_quoted_name(), 'FILE_1')
4534        self.assertListEqual(c.get_dependents(), [])
4535        self.assertListEqual(c.get_dependencies(), [])
4536        #
4537        self.assertEqual(c.filename, '/path/dbfile.f02')
4538        self.assertEqual(c.sequence, 1)
4539        self.assertEqual(c.start, 500)
4540        self.assertEqual(c.length, 1000)
4541        #
4542    def testShadow(self):
4543        # We have to use mocks
4544        c = sm.Shadow(self.con.schema, {'RDB$FILE_FLAGS': 1,
4545                                        'RDB$SHADOW_NUMBER': 3})
4546        files = []
4547        files.append(sm.DatabaseFile(self.con.schema, {'RDB$FILE_LENGTH': 500,
4548                                                       'RDB$FILE_NAME': '/path/shadow.sf1',
4549                                                       'RDB$FILE_START': 0,
4550                                                       'RDB$FILE_SEQUENCE': 0}))
4551        files.append(sm.DatabaseFile(self.con.schema, {'RDB$FILE_LENGTH': 500,
4552                                                       'RDB$FILE_NAME': '/path/shadow.sf2',
4553                                                       'RDB$FILE_START': 1000,
4554                                                       'RDB$FILE_SEQUENCE': 1}))
4555        files.append(sm.DatabaseFile(self.con.schema, {'RDB$FILE_LENGTH': 0,
4556                                                       'RDB$FILE_NAME': '/path/shadow.sf3',
4557                                                       'RDB$FILE_START': 1500,
4558                                                       'RDB$FILE_SEQUENCE': 2}))
4559        c.__dict__['_Shadow__files'] = files
4560        # common properties
4561        self.assertEqual(c.name, 'SHADOW_3')
4562        self.assertIsNone(c.description)
4563        self.assertListEqual(c.actions, ['create', 'drop'])
4564        self.assertFalse(c.issystemobject())
4565        self.assertEqual(c.get_quoted_name(), 'SHADOW_3')
4566        self.assertListEqual(c.get_dependents(), [])
4567        self.assertListEqual(c.get_dependencies(), [])
4568        #
4569        self.assertEqual(c.id, 3)
4570        self.assertEqual(c.flags, 1)
4571        self.assertListEqual([(f.name, f.filename, f.start, f.length) for f in c.files],
4572                             [('FILE_0', '/path/shadow.sf1', 0, 500),
4573                              ('FILE_1', '/path/shadow.sf2', 1000, 500),
4574                              ('FILE_2', '/path/shadow.sf3', 1500, 0)])
4575        #
4576        self.assertFalse(c.isconditional())
4577        self.assertFalse(c.isinactive())
4578        self.assertFalse(c.ismanual())
4579        #
4580        self.assertEqual(c.get_sql_for('create'),
4581                         """CREATE SHADOW 3 AUTO '/path/shadow.sf1' LENGTH 500
4582  FILE '/path/shadow.sf2' STARTING AT 1000 LENGTH 500
4583  FILE '/path/shadow.sf3' STARTING AT 1500""")
4584        self.assertEqual(c.get_sql_for('drop'), "DROP SHADOW 3")
4585        self.assertEqual(c.get_sql_for('drop', preserve=True), "DROP SHADOW 3 PRESERVE FILE")
4586    def testPrivilegeBasic(self):
4587        p = self.con.schema.get_procedure('ALL_LANGS')
4588        #
4589        self.assertIsInstance(p.privileges, list)
4590        self.assertEqual(len(p.privileges), 2)
4591        c = p.privileges[0]
4592        # common properties
4593        self.assertIsNone(c.name)
4594        self.assertIsNone(c.description)
4595        self.assertListEqual(c.actions, ['grant', 'revoke'])
4596        self.assertTrue(c.issystemobject())
4597        self.assertIsNone(c.get_quoted_name())
4598        self.assertListEqual(c.get_dependents(), [])
4599        self.assertListEqual(c.get_dependencies(), [])
4600        #
4601        self.assertIsInstance(c.user, fdb.services.User)
4602        self.assertIn(c.user.name, ['SYSDBA', 'PUBLIC'])
4603        self.assertIsInstance(c.grantor, fdb.services.User)
4604        self.assertEqual(c.grantor.name, 'SYSDBA')
4605        self.assertEqual(c.privilege, 'X')
4606        self.assertIsInstance(c.subject, sm.Procedure)
4607        self.assertEqual(c.subject.name, 'ALL_LANGS')
4608        self.assertIn(c.user_name, ['SYSDBA', 'PUBLIC'])
4609        self.assertEqual(c.user_type, self.con.schema.enum_object_type_codes['USER'])
4610        self.assertEqual(c.grantor_name, 'SYSDBA')
4611        self.assertEqual(c.subject_name, 'ALL_LANGS')
4612        self.assertEqual(c.subject_type, self.con.schema.enum_object_type_codes['PROCEDURE'])
4613        self.assertIsNone(c.field_name)
4614        #
4615        self.assertFalse(c.has_grant())
4616        self.assertFalse(c.isselect())
4617        self.assertFalse(c.isinsert())
4618        self.assertFalse(c.isupdate())
4619        self.assertFalse(c.isdelete())
4620        self.assertTrue(c.isexecute())
4621        self.assertFalse(c.isreference())
4622        self.assertFalse(c.ismembership())
4623        #
4624        self.assertEqual(c.get_sql_for('grant'),
4625                         "GRANT EXECUTE ON PROCEDURE ALL_LANGS TO SYSDBA")
4626        self.assertEqual(c.get_sql_for('grant', grantors=[]),
4627                         "GRANT EXECUTE ON PROCEDURE ALL_LANGS TO SYSDBA GRANTED BY SYSDBA")
4628        self.assertEqual(c.get_sql_for('grant', grantors=['SYSDBA', 'TEST_USER']),
4629                         "GRANT EXECUTE ON PROCEDURE ALL_LANGS TO SYSDBA")
4630        with self.assertRaises(fdb.ProgrammingError) as cm:
4631            c.get_sql_for('grant', badparam=True)
4632        self.assertTupleEqual(cm.exception.args,
4633                              ("Unsupported parameter(s) 'badparam'",))
4634        self.assertEqual(c.get_sql_for('revoke'),
4635                         "REVOKE EXECUTE ON PROCEDURE ALL_LANGS FROM SYSDBA")
4636        self.assertEqual(c.get_sql_for('revoke', grantors=[]),
4637                         "REVOKE EXECUTE ON PROCEDURE ALL_LANGS FROM SYSDBA GRANTED BY SYSDBA")
4638        self.assertEqual(c.get_sql_for('revoke', grantors=['SYSDBA', 'TEST_USER']),
4639                         "REVOKE EXECUTE ON PROCEDURE ALL_LANGS FROM SYSDBA")
4640        with self.assertRaises(fdb.ProgrammingError) as cm:
4641            c.get_sql_for('revoke', grant_option=True)
4642        self.assertTupleEqual(cm.exception.args,
4643                              ("Can't revoke grant option that wasn't granted.",))
4644        with self.assertRaises(fdb.ProgrammingError) as cm:
4645            c.get_sql_for('revoke', badparam=True)
4646        self.assertTupleEqual(cm.exception.args,
4647                              ("Unsupported parameter(s) 'badparam'",))
4648        c = p.privileges[1]
4649        self.assertEqual(c.get_sql_for('grant'),
4650                         "GRANT EXECUTE ON PROCEDURE ALL_LANGS TO PUBLIC WITH GRANT OPTION")
4651        self.assertEqual(c.get_sql_for('revoke'),
4652                         "REVOKE EXECUTE ON PROCEDURE ALL_LANGS FROM PUBLIC")
4653        self.assertEqual(c.get_sql_for('revoke', grant_option=True),
4654                         "REVOKE GRANT OPTION FOR EXECUTE ON PROCEDURE ALL_LANGS FROM PUBLIC")
4655        # get_privileges_of()
4656        u = fdb.services.User('PUBLIC')
4657        p = self.con.schema.get_privileges_of(u)
4658        if self.con.ods <= fdb.ODS_FB_20:
4659            self.assertEqual(len(p), 66)
4660        elif self.con.ods >= fdb.ODS_FB_30:
4661            self.assertEqual(len(p), 115)
4662        else:
4663            self.assertEqual(len(p), 68)
4664        with self.assertRaises(fdb.ProgrammingError) as cm:
4665            p = self.con.schema.get_privileges_of('PUBLIC')
4666        self.assertTupleEqual(cm.exception.args,
4667                              ("Unknown user_type code.",))
4668        with self.assertRaises(fdb.ProgrammingError) as cm:
4669            p = self.con.schema.get_privileges_of('PUBLIC', 50)
4670        self.assertTupleEqual(cm.exception.args,
4671                              ("Unknown user_type code.",))
4672        #
4673    def testPrivilegeExtended(self):
4674        def get_privilege(obj, privilege):
4675            x = [x for x in obj.privileges if x.privilege == privilege]
4676            return x[0]
4677        p = utils.ObjectList()
4678        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4679                                                'RDB$PRIVILEGE': 'X',
4680                                                'RDB$RELATION_NAME': 'ALL_LANGS',
4681                                                'RDB$OBJECT_TYPE': 5,
4682                                                'RDB$USER_TYPE': 8,
4683                                                'RDB$FIELD_NAME': None,
4684                                                'RDB$GRANTOR': 'SYSDBA',
4685                                                'RDB$GRANT_OPTION': None}))
4686        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4687                                                'RDB$PRIVILEGE': 'X',
4688                                                'RDB$RELATION_NAME': 'ALL_LANGS',
4689                                                'RDB$OBJECT_TYPE': 5,
4690                                                'RDB$USER_TYPE': 8,
4691                                                'RDB$FIELD_NAME': None,
4692                                                'RDB$GRANTOR': 'SYSDBA',
4693                                                'RDB$GRANT_OPTION': 1}))
4694        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4695                                                'RDB$PRIVILEGE': 'X',
4696                                                'RDB$RELATION_NAME': 'ALL_LANGS',
4697                                                'RDB$OBJECT_TYPE': 5,
4698                                                'RDB$USER_TYPE': 8,
4699                                                'RDB$FIELD_NAME': None,
4700                                                'RDB$GRANTOR': 'SYSDBA',
4701                                                'RDB$GRANT_OPTION': 0}))
4702        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'TEST_ROLE',
4703                                                'RDB$PRIVILEGE': 'X',
4704                                                'RDB$RELATION_NAME': 'ALL_LANGS',
4705                                                'RDB$OBJECT_TYPE': 5,
4706                                                'RDB$USER_TYPE': 13,
4707                                                'RDB$FIELD_NAME': None,
4708                                                'RDB$GRANTOR': 'SYSDBA',
4709                                                'RDB$GRANT_OPTION': 1}))
4710        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4711                                                'RDB$PRIVILEGE': 'X',
4712                                                'RDB$RELATION_NAME': 'ALL_LANGS',
4713                                                'RDB$OBJECT_TYPE': 5,
4714                                                'RDB$USER_TYPE': 8,
4715                                                'RDB$FIELD_NAME': None,
4716                                                'RDB$GRANTOR': 'T_USER',
4717                                                'RDB$GRANT_OPTION': 0}))
4718        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4719                                                'RDB$PRIVILEGE': 'S',
4720                                                'RDB$RELATION_NAME': 'COUNTRY',
4721                                                'RDB$OBJECT_TYPE': 0,
4722                                                'RDB$USER_TYPE': 8,
4723                                                'RDB$FIELD_NAME': None,
4724                                                'RDB$GRANTOR': 'SYSDBA',
4725                                                'RDB$GRANT_OPTION': 1}))
4726        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4727                                                'RDB$PRIVILEGE': 'I',
4728                                                'RDB$RELATION_NAME': 'COUNTRY',
4729                                                'RDB$OBJECT_TYPE': 0,
4730                                                'RDB$USER_TYPE': 8,
4731                                                'RDB$FIELD_NAME': None,
4732                                                'RDB$GRANTOR': 'SYSDBA',
4733                                                'RDB$GRANT_OPTION': 1}))
4734        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4735                                                'RDB$PRIVILEGE': 'U',
4736                                                'RDB$RELATION_NAME': 'COUNTRY',
4737                                                'RDB$OBJECT_TYPE': 0,
4738                                                'RDB$USER_TYPE': 8,
4739                                                'RDB$FIELD_NAME': None,
4740                                                'RDB$GRANTOR': 'SYSDBA',
4741                                                'RDB$GRANT_OPTION': 1}))
4742        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4743                                                'RDB$PRIVILEGE': 'D',
4744                                                'RDB$RELATION_NAME': 'COUNTRY',
4745                                                'RDB$OBJECT_TYPE': 0,
4746                                                'RDB$USER_TYPE': 8,
4747                                                'RDB$FIELD_NAME': None,
4748                                                'RDB$GRANTOR': 'SYSDBA',
4749                                                'RDB$GRANT_OPTION': 1}))
4750        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4751                                                'RDB$PRIVILEGE': 'R',
4752                                                'RDB$RELATION_NAME': 'COUNTRY',
4753                                                'RDB$OBJECT_TYPE': 0,
4754                                                'RDB$USER_TYPE': 8,
4755                                                'RDB$FIELD_NAME': None,
4756                                                'RDB$GRANTOR': 'SYSDBA',
4757                                                'RDB$GRANT_OPTION': 1}))
4758        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4759                                                'RDB$PRIVILEGE': 'S',
4760                                                'RDB$RELATION_NAME': 'COUNTRY',
4761                                                'RDB$OBJECT_TYPE': 0,
4762                                                'RDB$USER_TYPE': 8,
4763                                                'RDB$FIELD_NAME': None,
4764                                                'RDB$GRANTOR': 'SYSDBA',
4765                                                'RDB$GRANT_OPTION': 1}))
4766        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4767                                                'RDB$PRIVILEGE': 'R',
4768                                                'RDB$RELATION_NAME': 'COUNTRY',
4769                                                'RDB$OBJECT_TYPE': 0,
4770                                                'RDB$USER_TYPE': 8,
4771                                                'RDB$FIELD_NAME': None,
4772                                                'RDB$GRANTOR': 'SYSDBA',
4773                                                'RDB$GRANT_OPTION': 1}))
4774        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4775                                                'RDB$PRIVILEGE': 'I',
4776                                                'RDB$RELATION_NAME': 'COUNTRY',
4777                                                'RDB$OBJECT_TYPE': 0,
4778                                                'RDB$USER_TYPE': 8,
4779                                                'RDB$FIELD_NAME': None,
4780                                                'RDB$GRANTOR': 'SYSDBA',
4781                                                'RDB$GRANT_OPTION': 0}))
4782        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4783                                                'RDB$PRIVILEGE': 'U',
4784                                                'RDB$RELATION_NAME': 'COUNTRY',
4785                                                'RDB$OBJECT_TYPE': 0,
4786                                                'RDB$USER_TYPE': 8,
4787                                                'RDB$FIELD_NAME': 'CURRENCY',
4788                                                'RDB$GRANTOR': 'SYSDBA',
4789                                                'RDB$GRANT_OPTION': 0}))
4790        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4791                                                'RDB$PRIVILEGE': 'R',
4792                                                'RDB$RELATION_NAME': 'COUNTRY',
4793                                                'RDB$OBJECT_TYPE': 0,
4794                                                'RDB$USER_TYPE': 8,
4795                                                'RDB$FIELD_NAME': 'COUNTRY',
4796                                                'RDB$GRANTOR': 'SYSDBA',
4797                                                'RDB$GRANT_OPTION': 0}))
4798        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4799                                                'RDB$PRIVILEGE': 'S',
4800                                                'RDB$RELATION_NAME': 'COUNTRY',
4801                                                'RDB$OBJECT_TYPE': 0,
4802                                                'RDB$USER_TYPE': 8,
4803                                                'RDB$FIELD_NAME': None,
4804                                                'RDB$GRANTOR': 'SYSDBA',
4805                                                'RDB$GRANT_OPTION': 0}))
4806        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4807                                                'RDB$PRIVILEGE': 'I',
4808                                                'RDB$RELATION_NAME': 'COUNTRY',
4809                                                'RDB$OBJECT_TYPE': 0,
4810                                                'RDB$USER_TYPE': 8,
4811                                                'RDB$FIELD_NAME': None,
4812                                                'RDB$GRANTOR': 'SYSDBA',
4813                                                'RDB$GRANT_OPTION': 0}))
4814        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4815                                                'RDB$PRIVILEGE': 'D',
4816                                                'RDB$RELATION_NAME': 'COUNTRY',
4817                                                'RDB$OBJECT_TYPE': 0,
4818                                                'RDB$USER_TYPE': 8,
4819                                                'RDB$FIELD_NAME': None,
4820                                                'RDB$GRANTOR': 'SYSDBA',
4821                                                'RDB$GRANT_OPTION': 0}))
4822        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4823                                                'RDB$PRIVILEGE': 'U',
4824                                                'RDB$RELATION_NAME': 'COUNTRY',
4825                                                'RDB$OBJECT_TYPE': 0,
4826                                                'RDB$USER_TYPE': 8,
4827                                                'RDB$FIELD_NAME': None,
4828                                                'RDB$GRANTOR': 'SYSDBA',
4829                                                'RDB$GRANT_OPTION': 0}))
4830        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4831                                                'RDB$PRIVILEGE': 'R',
4832                                                'RDB$RELATION_NAME': 'COUNTRY',
4833                                                'RDB$OBJECT_TYPE': 0,
4834                                                'RDB$USER_TYPE': 8,
4835                                                'RDB$FIELD_NAME': None,
4836                                                'RDB$GRANTOR': 'SYSDBA',
4837                                                'RDB$GRANT_OPTION': 0}))
4838        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4839                                                'RDB$PRIVILEGE': 'U',
4840                                                'RDB$RELATION_NAME': 'COUNTRY',
4841                                                'RDB$OBJECT_TYPE': 0,
4842                                                'RDB$USER_TYPE': 8,
4843                                                'RDB$FIELD_NAME': 'COUNTRY',
4844                                                'RDB$GRANTOR': 'SYSDBA',
4845                                                'RDB$GRANT_OPTION': 0}))
4846        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
4847                                                'RDB$PRIVILEGE': 'R',
4848                                                'RDB$RELATION_NAME': 'COUNTRY',
4849                                                'RDB$OBJECT_TYPE': 0,
4850                                                'RDB$USER_TYPE': 8,
4851                                                'RDB$FIELD_NAME': 'CURRENCY',
4852                                                'RDB$GRANTOR': 'SYSDBA',
4853                                                'RDB$GRANT_OPTION': 0}))
4854        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4855                                                'RDB$PRIVILEGE': 'D',
4856                                                'RDB$RELATION_NAME': 'COUNTRY',
4857                                                'RDB$OBJECT_TYPE': 0,
4858                                                'RDB$USER_TYPE': 8,
4859                                                'RDB$FIELD_NAME': None,
4860                                                'RDB$GRANTOR': 'SYSDBA',
4861                                                'RDB$GRANT_OPTION': 0}))
4862        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4863                                                'RDB$PRIVILEGE': 'U',
4864                                                'RDB$RELATION_NAME': 'COUNTRY',
4865                                                'RDB$OBJECT_TYPE': 0,
4866                                                'RDB$USER_TYPE': 8,
4867                                                'RDB$FIELD_NAME': None,
4868                                                'RDB$GRANTOR': 'SYSDBA',
4869                                                'RDB$GRANT_OPTION': 0}))
4870        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4871                                                'RDB$PRIVILEGE': 'S',
4872                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4873                                                'RDB$OBJECT_TYPE': 0,
4874                                                'RDB$USER_TYPE': 8,
4875                                                'RDB$FIELD_NAME': None,
4876                                                'RDB$GRANTOR': 'SYSDBA',
4877                                                'RDB$GRANT_OPTION': 1}))
4878        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4879                                                'RDB$PRIVILEGE': 'I',
4880                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4881                                                'RDB$OBJECT_TYPE': 0,
4882                                                'RDB$USER_TYPE': 8,
4883                                                'RDB$FIELD_NAME': None,
4884                                                'RDB$GRANTOR': 'SYSDBA',
4885                                                'RDB$GRANT_OPTION': 1}))
4886        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4887                                                'RDB$PRIVILEGE': 'U',
4888                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4889                                                'RDB$OBJECT_TYPE': 0,
4890                                                'RDB$USER_TYPE': 8,
4891                                                'RDB$FIELD_NAME': None,
4892                                                'RDB$GRANTOR': 'SYSDBA',
4893                                                'RDB$GRANT_OPTION': 1}))
4894        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4895                                                'RDB$PRIVILEGE': 'D',
4896                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4897                                                'RDB$OBJECT_TYPE': 0,
4898                                                'RDB$USER_TYPE': 8,
4899                                                'RDB$FIELD_NAME': None,
4900                                                'RDB$GRANTOR': 'SYSDBA',
4901                                                'RDB$GRANT_OPTION': 1}))
4902        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4903                                                'RDB$PRIVILEGE': 'R',
4904                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4905                                                'RDB$OBJECT_TYPE': 0,
4906                                                'RDB$USER_TYPE': 8,
4907                                                'RDB$FIELD_NAME': None,
4908                                                'RDB$GRANTOR': 'SYSDBA',
4909                                                'RDB$GRANT_OPTION': 1}))
4910        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4911                                                'RDB$PRIVILEGE': 'S',
4912                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4913                                                'RDB$OBJECT_TYPE': 0,
4914                                                'RDB$USER_TYPE': 8,
4915                                                'RDB$FIELD_NAME': None,
4916                                                'RDB$GRANTOR': 'SYSDBA',
4917                                                'RDB$GRANT_OPTION': 1}))
4918        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4919                                                'RDB$PRIVILEGE': 'I',
4920                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4921                                                'RDB$OBJECT_TYPE': 0,
4922                                                'RDB$USER_TYPE': 8,
4923                                                'RDB$FIELD_NAME': None,
4924                                                'RDB$GRANTOR': 'SYSDBA',
4925                                                'RDB$GRANT_OPTION': 1}))
4926        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4927                                                'RDB$PRIVILEGE': 'U',
4928                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4929                                                'RDB$OBJECT_TYPE': 0,
4930                                                'RDB$USER_TYPE': 8,
4931                                                'RDB$FIELD_NAME': None,
4932                                                'RDB$GRANTOR': 'SYSDBA',
4933                                                'RDB$GRANT_OPTION': 1}))
4934        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4935                                                'RDB$PRIVILEGE': 'D',
4936                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4937                                                'RDB$OBJECT_TYPE': 0,
4938                                                'RDB$USER_TYPE': 8,
4939                                                'RDB$FIELD_NAME': None,
4940                                                'RDB$GRANTOR': 'SYSDBA',
4941                                                'RDB$GRANT_OPTION': 1}))
4942        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4943                                                'RDB$PRIVILEGE': 'R',
4944                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4945                                                'RDB$OBJECT_TYPE': 0,
4946                                                'RDB$USER_TYPE': 8,
4947                                                'RDB$FIELD_NAME': None,
4948                                                'RDB$GRANTOR': 'SYSDBA',
4949                                                'RDB$GRANT_OPTION': 1}))
4950        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'ORG_CHART',
4951                                                'RDB$PRIVILEGE': 'S',
4952                                                'RDB$RELATION_NAME': 'DEPARTMENT',
4953                                                'RDB$OBJECT_TYPE': 0,
4954                                                'RDB$USER_TYPE': 5,
4955                                                'RDB$FIELD_NAME': None,
4956                                                'RDB$GRANTOR': 'SYSDBA',
4957                                                'RDB$GRANT_OPTION': 0}))
4958        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4959                                                'RDB$PRIVILEGE': 'S',
4960                                                'RDB$RELATION_NAME': 'EMPLOYEE',
4961                                                'RDB$OBJECT_TYPE': 0,
4962                                                'RDB$USER_TYPE': 8,
4963                                                'RDB$FIELD_NAME': None,
4964                                                'RDB$GRANTOR': 'SYSDBA',
4965                                                'RDB$GRANT_OPTION': 1}))
4966        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4967                                                'RDB$PRIVILEGE': 'I',
4968                                                'RDB$RELATION_NAME': 'EMPLOYEE',
4969                                                'RDB$OBJECT_TYPE': 0,
4970                                                'RDB$USER_TYPE': 8,
4971                                                'RDB$FIELD_NAME': None,
4972                                                'RDB$GRANTOR': 'SYSDBA',
4973                                                'RDB$GRANT_OPTION': 1}))
4974        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4975                                                'RDB$PRIVILEGE': 'U',
4976                                                'RDB$RELATION_NAME': 'EMPLOYEE',
4977                                                'RDB$OBJECT_TYPE': 0,
4978                                                'RDB$USER_TYPE': 8,
4979                                                'RDB$FIELD_NAME': None,
4980                                                'RDB$GRANTOR': 'SYSDBA',
4981                                                'RDB$GRANT_OPTION': 1}))
4982        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4983                                                'RDB$PRIVILEGE': 'D',
4984                                                'RDB$RELATION_NAME': 'EMPLOYEE',
4985                                                'RDB$OBJECT_TYPE': 0,
4986                                                'RDB$USER_TYPE': 8,
4987                                                'RDB$FIELD_NAME': None,
4988                                                'RDB$GRANTOR': 'SYSDBA',
4989                                                'RDB$GRANT_OPTION': 1}))
4990        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
4991                                                'RDB$PRIVILEGE': 'R',
4992                                                'RDB$RELATION_NAME': 'EMPLOYEE',
4993                                                'RDB$OBJECT_TYPE': 0,
4994                                                'RDB$USER_TYPE': 8,
4995                                                'RDB$FIELD_NAME': None,
4996                                                'RDB$GRANTOR': 'SYSDBA',
4997                                                'RDB$GRANT_OPTION': 1}))
4998        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
4999                                                'RDB$PRIVILEGE': 'S',
5000                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5001                                                'RDB$OBJECT_TYPE': 0,
5002                                                'RDB$USER_TYPE': 8,
5003                                                'RDB$FIELD_NAME': None,
5004                                                'RDB$GRANTOR': 'SYSDBA',
5005                                                'RDB$GRANT_OPTION': 1}))
5006        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5007                                                'RDB$PRIVILEGE': 'I',
5008                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5009                                                'RDB$OBJECT_TYPE': 0,
5010                                                'RDB$USER_TYPE': 8,
5011                                                'RDB$FIELD_NAME': None,
5012                                                'RDB$GRANTOR': 'SYSDBA',
5013                                                'RDB$GRANT_OPTION': 1}))
5014        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5015                                                'RDB$PRIVILEGE': 'U',
5016                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5017                                                'RDB$OBJECT_TYPE': 0,
5018                                                'RDB$USER_TYPE': 8,
5019                                                'RDB$FIELD_NAME': None,
5020                                                'RDB$GRANTOR': 'SYSDBA',
5021                                                'RDB$GRANT_OPTION': 1}))
5022        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5023                                                'RDB$PRIVILEGE': 'D',
5024                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5025                                                'RDB$OBJECT_TYPE': 0,
5026                                                'RDB$USER_TYPE': 8,
5027                                                'RDB$FIELD_NAME': None,
5028                                                'RDB$GRANTOR': 'SYSDBA',
5029                                                'RDB$GRANT_OPTION': 1}))
5030        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5031                                                'RDB$PRIVILEGE': 'R',
5032                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5033                                                'RDB$OBJECT_TYPE': 0,
5034                                                'RDB$USER_TYPE': 8,
5035                                                'RDB$FIELD_NAME': None,
5036                                                'RDB$GRANTOR': 'SYSDBA',
5037                                                'RDB$GRANT_OPTION': 1}))
5038        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'ORG_CHART',
5039                                                'RDB$PRIVILEGE': 'S',
5040                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5041                                                'RDB$OBJECT_TYPE': 0,
5042                                                'RDB$USER_TYPE': 5,
5043                                                'RDB$FIELD_NAME': None,
5044                                                'RDB$GRANTOR': 'SYSDBA',
5045                                                'RDB$GRANT_OPTION': 0}))
5046        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5047                                                'RDB$PRIVILEGE': 'X',
5048                                                'RDB$RELATION_NAME': 'ORG_CHART',
5049                                                'RDB$OBJECT_TYPE': 5,
5050                                                'RDB$USER_TYPE': 8,
5051                                                'RDB$FIELD_NAME': None,
5052                                                'RDB$GRANTOR': 'SYSDBA',
5053                                                'RDB$GRANT_OPTION': None}))
5054        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5055                                                'RDB$PRIVILEGE': 'X',
5056                                                'RDB$RELATION_NAME': 'ORG_CHART',
5057                                                'RDB$OBJECT_TYPE': 5,
5058                                                'RDB$USER_TYPE': 8,
5059                                                'RDB$FIELD_NAME': None,
5060                                                'RDB$GRANTOR': 'SYSDBA',
5061                                                'RDB$GRANT_OPTION': 1}))
5062        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5063                                                'RDB$PRIVILEGE': 'S',
5064                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5065                                                'RDB$OBJECT_TYPE': 0,
5066                                                'RDB$USER_TYPE': 8,
5067                                                'RDB$FIELD_NAME': None,
5068                                                'RDB$GRANTOR': 'SYSDBA',
5069                                                'RDB$GRANT_OPTION': 1}))
5070        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5071                                                'RDB$PRIVILEGE': 'I',
5072                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5073                                                'RDB$OBJECT_TYPE': 0,
5074                                                'RDB$USER_TYPE': 8,
5075                                                'RDB$FIELD_NAME': None,
5076                                                'RDB$GRANTOR': 'SYSDBA',
5077                                                'RDB$GRANT_OPTION': 1}))
5078        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5079                                                'RDB$PRIVILEGE': 'U',
5080                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5081                                                'RDB$OBJECT_TYPE': 0,
5082                                                'RDB$USER_TYPE': 8,
5083                                                'RDB$FIELD_NAME': None,
5084                                                'RDB$GRANTOR': 'SYSDBA',
5085                                                'RDB$GRANT_OPTION': 1}))
5086        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5087                                                'RDB$PRIVILEGE': 'D',
5088                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5089                                                'RDB$OBJECT_TYPE': 0,
5090                                                'RDB$USER_TYPE': 8,
5091                                                'RDB$FIELD_NAME': None,
5092                                                'RDB$GRANTOR': 'SYSDBA',
5093                                                'RDB$GRANT_OPTION': 1}))
5094        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5095                                                'RDB$PRIVILEGE': 'R',
5096                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5097                                                'RDB$OBJECT_TYPE': 0,
5098                                                'RDB$USER_TYPE': 8,
5099                                                'RDB$FIELD_NAME': None,
5100                                                'RDB$GRANTOR': 'SYSDBA',
5101                                                'RDB$GRANT_OPTION': 1}))
5102        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5103                                                'RDB$PRIVILEGE': 'S',
5104                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5105                                                'RDB$OBJECT_TYPE': 0,
5106                                                'RDB$USER_TYPE': 8,
5107                                                'RDB$FIELD_NAME': None,
5108                                                'RDB$GRANTOR': 'SYSDBA',
5109                                                'RDB$GRANT_OPTION': 1}))
5110        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5111                                                'RDB$PRIVILEGE': 'I',
5112                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5113                                                'RDB$OBJECT_TYPE': 0,
5114                                                'RDB$USER_TYPE': 8,
5115                                                'RDB$FIELD_NAME': None,
5116                                                'RDB$GRANTOR': 'SYSDBA',
5117                                                'RDB$GRANT_OPTION': 1}))
5118        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5119                                                'RDB$PRIVILEGE': 'U',
5120                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5121                                                'RDB$OBJECT_TYPE': 0,
5122                                                'RDB$USER_TYPE': 8,
5123                                                'RDB$FIELD_NAME': None,
5124                                                'RDB$GRANTOR': 'SYSDBA',
5125                                                'RDB$GRANT_OPTION': 1}))
5126        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5127                                                'RDB$PRIVILEGE': 'D',
5128                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5129                                                'RDB$OBJECT_TYPE': 0,
5130                                                'RDB$USER_TYPE': 8,
5131                                                'RDB$FIELD_NAME': None,
5132                                                'RDB$GRANTOR': 'SYSDBA',
5133                                                'RDB$GRANT_OPTION': 1}))
5134        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5135                                                'RDB$PRIVILEGE': 'R',
5136                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5137                                                'RDB$OBJECT_TYPE': 0,
5138                                                'RDB$USER_TYPE': 8,
5139                                                'RDB$FIELD_NAME': None,
5140                                                'RDB$GRANTOR': 'SYSDBA',
5141                                                'RDB$GRANT_OPTION': 1}))
5142        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5143                                                'RDB$PRIVILEGE': 'R',
5144                                                'RDB$RELATION_NAME': 'PHONE_LIST',
5145                                                'RDB$OBJECT_TYPE': 0,
5146                                                'RDB$USER_TYPE': 8,
5147                                                'RDB$FIELD_NAME': 'EMP_NO',
5148                                                'RDB$GRANTOR': 'SYSDBA',
5149                                                'RDB$GRANT_OPTION': 0}))
5150        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5151                                                'RDB$PRIVILEGE': 'S',
5152                                                'RDB$RELATION_NAME': 'RDB$PAGES',
5153                                                'RDB$OBJECT_TYPE': 0,
5154                                                'RDB$USER_TYPE': 8,
5155                                                'RDB$FIELD_NAME': None,
5156                                                'RDB$GRANTOR': 'SYSDBA',
5157                                                'RDB$GRANT_OPTION': 1}))
5158        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5159                                                'RDB$PRIVILEGE': 'I',
5160                                                'RDB$RELATION_NAME': 'RDB$PAGES',
5161                                                'RDB$OBJECT_TYPE': 0,
5162                                                'RDB$USER_TYPE': 8,
5163                                                'RDB$FIELD_NAME': None,
5164                                                'RDB$GRANTOR': 'SYSDBA',
5165                                                'RDB$GRANT_OPTION': 1}))
5166        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5167                                                'RDB$PRIVILEGE': 'U',
5168                                                'RDB$RELATION_NAME': 'RDB$PAGES',
5169                                                'RDB$OBJECT_TYPE': 0,
5170                                                'RDB$USER_TYPE': 8,
5171                                                'RDB$FIELD_NAME': None,
5172                                                'RDB$GRANTOR': 'SYSDBA',
5173                                                'RDB$GRANT_OPTION': 1}))
5174        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5175                                                'RDB$PRIVILEGE': 'D',
5176                                                'RDB$RELATION_NAME': 'RDB$PAGES',
5177                                                'RDB$OBJECT_TYPE': 0,
5178                                                'RDB$USER_TYPE': 8,
5179                                                'RDB$FIELD_NAME': None,
5180                                                'RDB$GRANTOR': 'SYSDBA',
5181                                                'RDB$GRANT_OPTION': 1}))
5182        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5183                                                'RDB$PRIVILEGE': 'R',
5184                                                'RDB$RELATION_NAME': 'RDB$PAGES',
5185                                                'RDB$OBJECT_TYPE': 0,
5186                                                'RDB$USER_TYPE': 8,
5187                                                'RDB$FIELD_NAME': None,
5188                                                'RDB$GRANTOR': 'SYSDBA',
5189                                                'RDB$GRANT_OPTION': 1}))
5190        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5191                                                'RDB$PRIVILEGE': 'S',
5192                                                'RDB$RELATION_NAME': 'RDB$PAGES',
5193                                                'RDB$OBJECT_TYPE': 0,
5194                                                'RDB$USER_TYPE': 8,
5195                                                'RDB$FIELD_NAME': None,
5196                                                'RDB$GRANTOR': 'SYSDBA',
5197                                                'RDB$GRANT_OPTION': 0}))
5198        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SYSDBA',
5199                                                'RDB$PRIVILEGE': 'X',
5200                                                'RDB$RELATION_NAME': 'SHIP_ORDER',
5201                                                'RDB$OBJECT_TYPE': 5,
5202                                                'RDB$USER_TYPE': 8,
5203                                                'RDB$FIELD_NAME': None,
5204                                                'RDB$GRANTOR': 'SYSDBA',
5205                                                'RDB$GRANT_OPTION': None}))
5206        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PUBLIC',
5207                                                'RDB$PRIVILEGE': 'X',
5208                                                'RDB$RELATION_NAME': 'SHIP_ORDER',
5209                                                'RDB$OBJECT_TYPE': 5,
5210                                                'RDB$USER_TYPE': 8,
5211                                                'RDB$FIELD_NAME': None,
5212                                                'RDB$GRANTOR': 'SYSDBA',
5213                                                'RDB$GRANT_OPTION': 1}))
5214        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'T_USER',
5215                                                'RDB$PRIVILEGE': 'M',
5216                                                'RDB$RELATION_NAME': 'TEST_ROLE',
5217                                                'RDB$OBJECT_TYPE': 13,
5218                                                'RDB$USER_TYPE': 8,
5219                                                'RDB$FIELD_NAME': None,
5220                                                'RDB$GRANTOR': 'SYSDBA',
5221                                                'RDB$GRANT_OPTION': 0}))
5222        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'SAVE_SALARY_CHANGE',
5223                                                'RDB$PRIVILEGE': 'I',
5224                                                'RDB$RELATION_NAME': 'SALARY_HISTORY',
5225                                                'RDB$OBJECT_TYPE': 0,
5226                                                'RDB$USER_TYPE': 2,
5227                                                'RDB$FIELD_NAME': None,
5228                                                'RDB$GRANTOR': 'SYSDBA',
5229                                                'RDB$GRANT_OPTION': 0}))
5230        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PHONE_LIST',
5231                                                'RDB$PRIVILEGE': 'S',
5232                                                'RDB$RELATION_NAME': 'DEPARTMENT',
5233                                                'RDB$OBJECT_TYPE': 0,
5234                                                'RDB$USER_TYPE': 1,
5235                                                'RDB$FIELD_NAME': None,
5236                                                'RDB$GRANTOR': 'SYSDBA',
5237                                                'RDB$GRANT_OPTION': 0}))
5238        p.append(sm.Privilege(self.con.schema, {'RDB$USER': 'PHONE_LIST',
5239                                                'RDB$PRIVILEGE': 'S',
5240                                                'RDB$RELATION_NAME': 'EMPLOYEE',
5241                                                'RDB$OBJECT_TYPE': 0,
5242                                                'RDB$USER_TYPE': 1,
5243                                                'RDB$FIELD_NAME': None,
5244                                                'RDB$GRANTOR': 'SYSDBA',
5245                                                'RDB$GRANT_OPTION': 0}))
5246        #
5247        self.con.schema.__dict__['_Schema__privileges'] = p
5248        # Table
5249        p = self.con.schema.get_table('COUNTRY')
5250        self.assertEqual(len(p.privileges), 19)
5251        self.assertEqual(len([x for x in p.privileges if x.user_name == 'SYSDBA']), 5)
5252        self.assertEqual(len([x for x in p.privileges if x.user_name == 'PUBLIC']), 5)
5253        self.assertEqual(len([x for x in p.privileges if x.user_name == 'T_USER']), 9)
5254        #
5255        self.assertTrue(get_privilege(p, 'S').isselect())
5256        self.assertTrue(get_privilege(p, 'I').isinsert())
5257        self.assertTrue(get_privilege(p, 'U').isupdate())
5258        self.assertTrue(get_privilege(p, 'D').isdelete())
5259        self.assertTrue(get_privilege(p, 'R').isreference())
5260        #
5261        x = p.privileges[0]
5262        self.assertIsInstance(x.subject, sm.Table)
5263        self.assertEqual(x.subject.name, p.name)
5264        # TableColumn
5265        p = p.get_column('CURRENCY')
5266        self.assertEqual(len(p.privileges), 2)
5267        x = p.privileges[0]
5268        self.assertIsInstance(x.subject, sm.Table)
5269        self.assertEqual(x.field_name, p.name)
5270        # View
5271        p = self.con.schema.get_view('PHONE_LIST')
5272        self.assertEqual(len(p.privileges), 11)
5273        self.assertEqual(len([x for x in p.privileges if x.user_name == 'SYSDBA']), 5)
5274        self.assertEqual(len([x for x in p.privileges if x.user_name == 'PUBLIC']), 6)
5275        #
5276        x = p.privileges[0]
5277        self.assertIsInstance(x.subject, sm.View)
5278        self.assertEqual(x.subject.name, p.name)
5279        # ViewColumn
5280        p = p.get_column('EMP_NO')
5281        self.assertEqual(len(p.privileges), 1)
5282        x = p.privileges[0]
5283        self.assertIsInstance(x.subject, sm.View)
5284        self.assertEqual(x.field_name, p.name)
5285        # Procedure
5286        p = self.con.schema.get_procedure('ORG_CHART')
5287        self.assertEqual(len(p.privileges), 2)
5288        self.assertEqual(len([x for x in p.privileges if x.user_name == 'SYSDBA']), 1)
5289        self.assertEqual(len([x for x in p.privileges if x.user_name == 'PUBLIC']), 1)
5290        #
5291        x = p.privileges[0]
5292        self.assertFalse(x.has_grant())
5293        self.assertIsInstance(x.subject, sm.Procedure)
5294        self.assertEqual(x.subject.name, p.name)
5295        #
5296        x = p.privileges[1]
5297        self.assertTrue(x.has_grant())
5298        # Role
5299        p = self.con.schema.get_role('TEST_ROLE')
5300        self.assertEqual(len(p.privileges), 1)
5301        x = p.privileges[0]
5302        self.assertIsInstance(x.user, sm.Role)
5303        self.assertEqual(x.user.name, p.name)
5304        self.assertTrue(x.isexecute())
5305        # Trigger as grantee
5306        p = self.con.schema.get_table('SALARY_HISTORY')
5307        x = p.privileges[0]
5308        self.assertIsInstance(x.user, sm.Trigger)
5309        self.assertEqual(x.user.name, 'SAVE_SALARY_CHANGE')
5310        # View as grantee
5311        p = self.con.schema.get_view('PHONE_LIST')
5312        x = self.con.schema.get_privileges_of(p)
5313        self.assertEqual(len(x), 2)
5314        x = x[0]
5315        self.assertIsInstance(x.user, sm.View)
5316        self.assertEqual(x.user.name, 'PHONE_LIST')
5317        # get_grants()
5318        self.assertListEqual(sm.get_grants(p.privileges),
5319                             ['GRANT REFERENCES(EMP_NO) ON PHONE_LIST TO PUBLIC',
5320                              'GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE ON PHONE_LIST TO PUBLIC WITH GRANT OPTION',
5321                              'GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE ON PHONE_LIST TO SYSDBA WITH GRANT OPTION'])
5322        p = self.con.schema.get_table('COUNTRY')
5323        self.assertListEqual(sm.get_grants(p.privileges),
5324                             ['GRANT DELETE, INSERT, UPDATE ON COUNTRY TO PUBLIC',
5325                              'GRANT REFERENCES, SELECT ON COUNTRY TO PUBLIC WITH GRANT OPTION',
5326                              'GRANT DELETE, INSERT, REFERENCES, SELECT, UPDATE ON COUNTRY TO SYSDBA WITH GRANT OPTION',
5327                              'GRANT DELETE, INSERT, REFERENCES(COUNTRY,CURRENCY), SELECT, UPDATE(COUNTRY,CURRENCY) ON COUNTRY TO T_USER'])
5328        p = self.con.schema.get_role('TEST_ROLE')
5329        self.assertListEqual(sm.get_grants(p.privileges), ['GRANT EXECUTE ON PROCEDURE ALL_LANGS TO TEST_ROLE WITH GRANT OPTION'])
5330        p = self.con.schema.get_table('SALARY_HISTORY')
5331        self.assertListEqual(sm.get_grants(p.privileges),
5332                             ['GRANT INSERT ON SALARY_HISTORY TO TRIGGER SAVE_SALARY_CHANGE'])
5333        p = self.con.schema.get_procedure('ORG_CHART')
5334        self.assertListEqual(sm.get_grants(p.privileges),
5335                             ['GRANT EXECUTE ON PROCEDURE ORG_CHART TO PUBLIC WITH GRANT OPTION',
5336                              'GRANT EXECUTE ON PROCEDURE ORG_CHART TO SYSDBA'])
5337        #
5338    def testPackage(self):
5339        if self.con.ods < fdb.ODS_FB_30:
5340            return
5341        c = self.con.schema.get_package('TEST')
5342        # common properties
5343        self.assertEqual(c.name, 'TEST')
5344        self.assertIsNone(c.description)
5345        self.assertFalse(c.issystemobject())
5346        self.assertListEqual(c.actions,
5347                             ['create', 'recreate', 'create_or_alter', 'alter', 'drop'])
5348        self.assertEqual(c.get_quoted_name(), 'TEST')
5349        self.assertEqual(c.owner_name, 'SYSDBA')
5350        self.assertEqual(c.security_class, 'SQL$575')
5351        self.assertEqual(c.header, """BEGIN
5352  PROCEDURE P1(I INT) RETURNS (O INT); -- public procedure
5353  FUNCTION F(X INT) RETURNS INT;
5354END""")
5355        self.assertEqual(c.body, """BEGIN
5356  FUNCTION F1(I INT) RETURNS INT; -- private function
5357
5358  PROCEDURE P1(I INT) RETURNS (O INT)
5359  AS
5360  BEGIN
5361  END
5362
5363  FUNCTION F1(I INT) RETURNS INT
5364  AS
5365  BEGIN
5366    RETURN F(I)+10;
5367  END
5368
5369  FUNCTION F(X INT) RETURNS INT
5370  AS
5371  BEGIN
5372    RETURN X+1;
5373  END
5374END""")
5375        self.assertListEqual(c.get_dependents(), [])
5376        self.assertEqual(len(c.get_dependencies()), 1)
5377        self.assertEqual(len(c.functions), 2)
5378        self.assertEqual(len(c.procedures), 1)
5379        #
5380        self.assertEqual(c.get_sql_for('create'), """CREATE PACKAGE TEST
5381AS
5382BEGIN
5383  PROCEDURE P1(I INT) RETURNS (O INT); -- public procedure
5384  FUNCTION F(X INT) RETURNS INT;
5385END""")
5386        self.assertEqual(c.get_sql_for('create', body=True), """CREATE PACKAGE BODY TEST
5387AS
5388BEGIN
5389  FUNCTION F1(I INT) RETURNS INT; -- private function
5390
5391  PROCEDURE P1(I INT) RETURNS (O INT)
5392  AS
5393  BEGIN
5394  END
5395
5396  FUNCTION F1(I INT) RETURNS INT
5397  AS
5398  BEGIN
5399    RETURN F(I)+10;
5400  END
5401
5402  FUNCTION F(X INT) RETURNS INT
5403  AS
5404  BEGIN
5405    RETURN X+1;
5406  END
5407END""")
5408        self.assertEqual(c.get_sql_for('alter', header="FUNCTION F2(I INT) RETURNS INT;"),
5409                         """ALTER PACKAGE TEST
5410AS
5411BEGIN
5412FUNCTION F2(I INT) RETURNS INT;
5413END""")
5414        self.assertEqual(c.get_sql_for('drop'), """DROP PACKAGE TEST""")
5415        self.assertEqual(c.get_sql_for('drop', body=True), """DROP PACKAGE BODY TEST""")
5416        self.assertEqual(c.get_sql_for('create_or_alter'), """CREATE OR ALTER PACKAGE TEST
5417AS
5418BEGIN
5419  PROCEDURE P1(I INT) RETURNS (O INT); -- public procedure
5420  FUNCTION F(X INT) RETURNS INT;
5421END""")
5422        #
5423    def testVisitor(self):
5424        v = SchemaVisitor(self, 'create', follow='dependencies')
5425        c = self.con.schema.get_procedure('ALL_LANGS')
5426        c.accept(v)
5427        self.maxDiff = None
5428        output = "CREATE TABLE JOB (\n  JOB_CODE JOBCODE NOT NULL,\n" \
5429            "  JOB_GRADE JOBGRADE NOT NULL,\n" \
5430            "  JOB_COUNTRY COUNTRYNAME NOT NULL,\n" \
5431            "  JOB_TITLE VARCHAR(25) NOT NULL,\n" \
5432            "  MIN_SALARY SALARY NOT NULL,\n" \
5433            "  MAX_SALARY SALARY NOT NULL,\n" \
5434            "  JOB_REQUIREMENT BLOB SUB_TYPE TEXT SEGMENT SIZE 400,\n" \
5435            "  LANGUAGE_REQ VARCHAR(15)[5],\n" \
5436            "  PRIMARY KEY (JOB_CODE,JOB_GRADE,JOB_COUNTRY)\n" \
5437            ")\n" \
5438            "CREATE PROCEDURE SHOW_LANGS (\n" \
5439            "  CODE VARCHAR(5),\n" \
5440            "  GRADE SMALLINT,\n" \
5441            "  CTY VARCHAR(15)\n" \
5442            ")\n" \
5443            "RETURNS (LANGUAGES VARCHAR(15))\n" \
5444            "AS\n" \
5445            "DECLARE VARIABLE i INTEGER;\n" \
5446            "BEGIN\n" \
5447            "  i = 1;\n" \
5448            "  WHILE (i <= 5) DO\n" \
5449            "  BEGIN\n" \
5450            "    SELECT language_req[:i] FROM joB\n" \
5451            "    WHERE ((job_code = :code) AND (job_grade = :grade) AND (job_country = :cty)\n" \
5452            "           AND (language_req IS NOT NULL))\n" \
5453            "    INTO :languages;\n" \
5454            "    IF (languages = ' ') THEN  /* Prints 'NULL' instead of blanks */\n" \
5455            "       languages = 'NULL';         \n" \
5456            "    i = i +1;\n" \
5457            "    SUSPEND;\n" \
5458            "  END\nEND\nCREATE PROCEDURE ALL_LANGS\n" \
5459            "RETURNS (\n" \
5460            "  CODE VARCHAR(5),\n" \
5461            "  GRADE VARCHAR(5),\n" \
5462            "  COUNTRY VARCHAR(15),\n" \
5463            "  LANG VARCHAR(15)\n" \
5464            ")\n" \
5465            "AS\n" \
5466            "BEGIN\n" \
5467            "\tFOR SELECT job_code, job_grade, job_country FROM job \n" \
5468            "\t\tINTO :code, :grade, :country\n" \
5469            "\n" \
5470            "\tDO\n" \
5471            "\tBEGIN\n" \
5472            "\t    FOR SELECT languages FROM show_langs \n" \
5473            " \t\t    (:code, :grade, :country) INTO :lang DO\n" \
5474            "\t        SUSPEND;\n" \
5475            "\t    /* Put nice separators between rows */\n" \
5476            "\t    code = '=====';\n" \
5477            "\t    grade = '=====';\n" \
5478            "\t    country = '===============';\n" \
5479            "\t    lang = '==============';\n" \
5480            "\t    SUSPEND;\n" \
5481            "\tEND\n" \
5482            "    END\n"
5483        self.assertMultiLineEqual(self.output.getvalue(), output)
5484
5485        v = SchemaVisitor(self, 'drop', follow='dependents')
5486        c = self.con.schema.get_table('JOB')
5487        self.clear_output()
5488        c.accept(v)
5489        self.assertEqual(self.output.getvalue(), """DROP PROCEDURE ALL_LANGS
5490DROP PROCEDURE SHOW_LANGS
5491DROP TABLE JOB
5492""")
5493
5494    def testScript(self):
5495        self.maxDiff = None
5496        self.assertEqual(25, len(sm.SCRIPT_DEFAULT_ORDER))
5497        s = self.con.schema
5498        script = s.get_metadata_ddl([sm.SCRIPT_COLLATIONS])
5499        self.assertListEqual(script, ["""CREATE COLLATION TEST_COLLATE
5500   FOR WIN1250
5501   FROM WIN_CZ
5502   NO PAD
5503   CASE INSENSITIVE
5504   ACCENT INSENSITIVE
5505   'DISABLE-COMPRESSIONS=0;DISABLE-EXPANSIONS=0'"""])
5506        script = s.get_metadata_ddl([sm.SCRIPT_CHARACTER_SETS])
5507        self.assertListEqual(script, [])
5508        script = s.get_metadata_ddl([sm.SCRIPT_UDFS])
5509        self.assertListEqual(script, [])
5510        script = s.get_metadata_ddl([sm.SCRIPT_GENERATORS])
5511        self.assertListEqual(script, ['CREATE SEQUENCE EMP_NO_GEN',
5512                                      'CREATE SEQUENCE CUST_NO_GEN'])
5513        script = s.get_metadata_ddl([sm.SCRIPT_EXCEPTIONS])
5514        self.assertListEqual(script, ["CREATE EXCEPTION UNKNOWN_EMP_ID 'Invalid employee number or project id.'",
5515                                      "CREATE EXCEPTION REASSIGN_SALES 'Reassign the sales records before deleting this employee.'",
5516                                      'CREATE EXCEPTION ORDER_ALREADY_SHIPPED \'Order status is "shipped."\'',
5517                                      "CREATE EXCEPTION CUSTOMER_ON_HOLD 'This customer is on hold.'",
5518                                      "CREATE EXCEPTION CUSTOMER_CHECK 'Overdue balance -- can not ship.'"])
5519        script = s.get_metadata_ddl([sm.SCRIPT_DOMAINS])
5520        self.assertListEqual(script, ['CREATE DOMAIN "FIRSTNAME" AS VARCHAR(15)',
5521                                      'CREATE DOMAIN "LASTNAME" AS VARCHAR(20)',
5522                                      'CREATE DOMAIN PHONENUMBER AS VARCHAR(20)',
5523                                      'CREATE DOMAIN COUNTRYNAME AS VARCHAR(15)',
5524                                      'CREATE DOMAIN ADDRESSLINE AS VARCHAR(30)',
5525                                      'CREATE DOMAIN EMPNO AS SMALLINT',
5526                                      "CREATE DOMAIN DEPTNO AS CHAR(3) CHECK (VALUE = '000' OR (VALUE > '0' AND VALUE <= '999') OR VALUE IS NULL)",
5527                                      'CREATE DOMAIN PROJNO AS CHAR(5) CHECK (VALUE = UPPER (VALUE))',
5528                                      'CREATE DOMAIN CUSTNO AS INTEGER CHECK (VALUE > 1000)',
5529                                      "CREATE DOMAIN JOBCODE AS VARCHAR(5) CHECK (VALUE > '99999')",
5530                                      'CREATE DOMAIN JOBGRADE AS SMALLINT CHECK (VALUE BETWEEN 0 AND 6)',
5531                                      'CREATE DOMAIN SALARY AS NUMERIC(10, 2) DEFAULT 0 CHECK (VALUE > 0)',
5532                                      'CREATE DOMAIN BUDGET AS DECIMAL(12, 2) DEFAULT 50000 CHECK (VALUE > 10000 AND VALUE <= 2000000)',
5533                                      "CREATE DOMAIN PRODTYPE AS VARCHAR(12) DEFAULT 'software' NOT NULL CHECK (VALUE IN ('software', 'hardware', 'other', 'N/A'))",
5534                                      "CREATE DOMAIN PONUMBER AS CHAR(8) CHECK (VALUE STARTING WITH 'V')"])
5535        if self.version == FB30:
5536            script = s.get_metadata_ddl([sm.SCRIPT_PACKAGE_DEFS])
5537            self.assertListEqual(script, ['CREATE PACKAGE TEST\nAS\nBEGIN\n  PROCEDURE P1(I INT) RETURNS (O INT); -- public procedure\n  FUNCTION F(X INT) RETURNS INT;\nEND',
5538                                          'CREATE PACKAGE TEST2\nAS\nBEGIN\n  FUNCTION F3(X INT) RETURNS INT;\nEND'])
5539        if self.version == FB30:
5540            script = s.get_metadata_ddl([sm.SCRIPT_FUNCTION_DEFS])
5541            self.assertListEqual(script, ['CREATE FUNCTION F2 (X INTEGER)\nRETURNS INTEGER\nAS\nBEGIN\nEND',
5542                                          'CREATE FUNCTION FX (\n  F TYPE OF "FIRSTNAME",\n  L TYPE OF COLUMN CUSTOMER.CONTACT_LAST\n)\nRETURNS VARCHAR(35)\nAS\nBEGIN\nEND',
5543                                          'CREATE FUNCTION FN\nRETURNS INTEGER\nAS\nBEGIN\nEND'])
5544        script = s.get_metadata_ddl([sm.SCRIPT_PROCEDURE_DEFS])
5545        if self.version == FB30:
5546            self.assertListEqual(script, ['CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)\nRETURNS (PROJ_ID CHAR(5))\nAS\nBEGIN\n  SUSPEND;\nEND',
5547                                          'CREATE PROCEDURE ADD_EMP_PROJ (\n  EMP_NO SMALLINT,\n  PROJ_ID CHAR(5)\n)\nAS\nBEGIN\n  SUSPEND;\nEND',
5548                                          'CREATE PROCEDURE SUB_TOT_BUDGET (HEAD_DEPT CHAR(3))\nRETURNS (\n  TOT_BUDGET DECIMAL(12, 2),\n  AVG_BUDGET DECIMAL(12, 2),\n  MIN_BUDGET DECIMAL(12, 2),\n  MAX_BUDGET DECIMAL(12, 2)\n)\nAS\nBEGIN\n  SUSPEND;\nEND',
5549                                          'CREATE PROCEDURE DELETE_EMPLOYEE (EMP_NUM INTEGER)\nAS\nBEGIN\n  SUSPEND;\nEND',
5550                                          'CREATE PROCEDURE DEPT_BUDGET (DNO CHAR(3))\nRETURNS (TOT DECIMAL(12, 2))\nAS\nBEGIN\n  SUSPEND;\nEND',
5551                                          'CREATE PROCEDURE ORG_CHART\nRETURNS (\n  HEAD_DEPT CHAR(25),\n  DEPARTMENT CHAR(25),\n  MNGR_NAME CHAR(20),\n  TITLE CHAR(5),\n  EMP_CNT INTEGER\n)\nAS\nBEGIN\n  SUSPEND;\nEND',
5552                                          'CREATE PROCEDURE MAIL_LABEL (CUST_NO INTEGER)\nRETURNS (\n  LINE1 CHAR(40),\n  LINE2 CHAR(40),\n  LINE3 CHAR(40),\n  LINE4 CHAR(40),\n  LINE5 CHAR(40),\n  LINE6 CHAR(40)\n)\nAS\nBEGIN\n  SUSPEND;\nEND',
5553                                          'CREATE PROCEDURE SHIP_ORDER (PO_NUM CHAR(8))\nAS\nBEGIN\n  SUSPEND;\nEND',
5554                                          'CREATE PROCEDURE SHOW_LANGS (\n  CODE VARCHAR(5),\n  GRADE SMALLINT,\n  CTY VARCHAR(15)\n)\nRETURNS (LANGUAGES VARCHAR(15))\nAS\nBEGIN\n  SUSPEND;\nEND',
5555                                          'CREATE PROCEDURE ALL_LANGS\nRETURNS (\n  CODE VARCHAR(5),\n  GRADE VARCHAR(5),\n  COUNTRY VARCHAR(15),\n  LANG VARCHAR(15)\n)\nAS\nBEGIN\n  SUSPEND;\nEND'])
5556        else:
5557            self.assertListEqual(script, ['CREATE PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)\nRETURNS (PROJ_ID CHAR(5))\nAS\nBEGIN\nEND',
5558                                          'CREATE PROCEDURE ADD_EMP_PROJ (\n  EMP_NO SMALLINT,\n  PROJ_ID CHAR(5)\n)\nAS\nBEGIN\nEND',
5559                                          'CREATE PROCEDURE SUB_TOT_BUDGET (HEAD_DEPT CHAR(3))\nRETURNS (\n  TOT_BUDGET DECIMAL(12, 2),\n  AVG_BUDGET DECIMAL(12, 2),\n  MIN_BUDGET DECIMAL(12, 2),\n  MAX_BUDGET DECIMAL(12, 2)\n)\nAS\nBEGIN\nEND',
5560                                          'CREATE PROCEDURE DELETE_EMPLOYEE (EMP_NUM INTEGER)\nAS\nBEGIN\nEND',
5561                                          'CREATE PROCEDURE DEPT_BUDGET (DNO CHAR(3))\nRETURNS (TOT DECIMAL(12, 2))\nAS\nBEGIN\nEND',
5562                                          'CREATE PROCEDURE ORG_CHART\nRETURNS (\n  HEAD_DEPT CHAR(25),\n  DEPARTMENT CHAR(25),\n  MNGR_NAME CHAR(20),\n  TITLE CHAR(5),\n  EMP_CNT INTEGER\n)\nAS\nBEGIN\nEND',
5563                                          'CREATE PROCEDURE MAIL_LABEL (CUST_NO INTEGER)\nRETURNS (\n  LINE1 CHAR(40),\n  LINE2 CHAR(40),\n  LINE3 CHAR(40),\n  LINE4 CHAR(40),\n  LINE5 CHAR(40),\n  LINE6 CHAR(40)\n)\nAS\nBEGIN\nEND',
5564                                          'CREATE PROCEDURE SHIP_ORDER (PO_NUM CHAR(8))\nAS\nBEGIN\nEND',
5565                                          'CREATE PROCEDURE SHOW_LANGS (\n  CODE VARCHAR(5),\n  GRADE SMALLINT,\n  CTY VARCHAR(15)\n)\nRETURNS (LANGUAGES VARCHAR(15))\nAS\nBEGIN\nEND',
5566                                          'CREATE PROCEDURE ALL_LANGS\nRETURNS (\n  CODE VARCHAR(5),\n  GRADE VARCHAR(5),\n  COUNTRY VARCHAR(15),\n  LANG VARCHAR(15)\n)\nAS\nBEGIN\nEND'])
5567        script = s.get_metadata_ddl([sm.SCRIPT_TABLES])
5568        if self.version == FB30:
5569            self.assertListEqual(script, ['CREATE TABLE COUNTRY (\n  COUNTRY COUNTRYNAME NOT NULL,\n  CURRENCY VARCHAR(10) NOT NULL\n)',
5570                                          'CREATE TABLE JOB (\n  JOB_CODE JOBCODE NOT NULL,\n  JOB_GRADE JOBGRADE NOT NULL,\n  JOB_COUNTRY COUNTRYNAME NOT NULL,\n  JOB_TITLE VARCHAR(25) NOT NULL,\n  MIN_SALARY SALARY NOT NULL,\n  MAX_SALARY SALARY NOT NULL,\n  JOB_REQUIREMENT BLOB SUB_TYPE TEXT SEGMENT SIZE 400,\n  LANGUAGE_REQ VARCHAR(15)[5]\n)',
5571                                          "CREATE TABLE DEPARTMENT (\n  DEPT_NO DEPTNO NOT NULL,\n  DEPARTMENT VARCHAR(25) NOT NULL,\n  HEAD_DEPT DEPTNO,\n  MNGR_NO EMPNO,\n  BUDGET BUDGET,\n  LOCATION VARCHAR(15),\n  PHONE_NO PHONENUMBER DEFAULT '555-1234'\n)",
5572                                          'CREATE TABLE EMPLOYEE (\n  EMP_NO EMPNO NOT NULL,\n  FIRST_NAME "FIRSTNAME" NOT NULL,\n  LAST_NAME "LASTNAME" NOT NULL,\n  PHONE_EXT VARCHAR(4),\n  HIRE_DATE TIMESTAMP DEFAULT \'NOW\' NOT NULL,\n  DEPT_NO DEPTNO NOT NULL,\n  JOB_CODE JOBCODE NOT NULL,\n  JOB_GRADE JOBGRADE NOT NULL,\n  JOB_COUNTRY COUNTRYNAME NOT NULL,\n  SALARY SALARY NOT NULL,\n  FULL_NAME COMPUTED BY (last_name || \', \' || first_name)\n)',
5573                                          'CREATE TABLE CUSTOMER (\n  CUST_NO CUSTNO NOT NULL,\n  CUSTOMER VARCHAR(25) NOT NULL,\n  CONTACT_FIRST "FIRSTNAME",\n  CONTACT_LAST "LASTNAME",\n  PHONE_NO PHONENUMBER,\n  ADDRESS_LINE1 ADDRESSLINE,\n  ADDRESS_LINE2 ADDRESSLINE,\n  CITY VARCHAR(25),\n  STATE_PROVINCE VARCHAR(15),\n  COUNTRY COUNTRYNAME,\n  POSTAL_CODE VARCHAR(12),\n  ON_HOLD CHAR(1) DEFAULT NULL\n)',
5574                                          'CREATE TABLE PROJECT (\n  PROJ_ID PROJNO NOT NULL,\n  PROJ_NAME VARCHAR(20) NOT NULL,\n  PROJ_DESC BLOB SUB_TYPE TEXT SEGMENT SIZE 800,\n  TEAM_LEADER EMPNO,\n  PRODUCT PRODTYPE\n)',
5575                                          'CREATE TABLE EMPLOYEE_PROJECT (\n  EMP_NO EMPNO NOT NULL,\n  PROJ_ID PROJNO NOT NULL\n)',
5576                                          'CREATE TABLE PROJ_DEPT_BUDGET (\n  FISCAL_YEAR INTEGER NOT NULL,\n  PROJ_ID PROJNO NOT NULL,\n  DEPT_NO DEPTNO NOT NULL,\n  QUART_HEAD_CNT INTEGER[4],\n  PROJECTED_BUDGET BUDGET\n)',
5577                                          "CREATE TABLE SALARY_HISTORY (\n  EMP_NO EMPNO NOT NULL,\n  CHANGE_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,\n  UPDATER_ID VARCHAR(20) NOT NULL,\n  OLD_SALARY SALARY NOT NULL,\n  PERCENT_CHANGE DOUBLE PRECISION DEFAULT 0 NOT NULL,\n  NEW_SALARY COMPUTED BY (old_salary + old_salary * percent_change / 100)\n)",
5578                                          "CREATE TABLE SALES (\n  PO_NUMBER PONUMBER NOT NULL,\n  CUST_NO CUSTNO NOT NULL,\n  SALES_REP EMPNO,\n  ORDER_STATUS VARCHAR(7) DEFAULT 'new' NOT NULL,\n  ORDER_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,\n  SHIP_DATE TIMESTAMP,\n  DATE_NEEDED TIMESTAMP,\n  PAID CHAR(1) DEFAULT 'n',\n  QTY_ORDERED INTEGER DEFAULT 1 NOT NULL,\n  TOTAL_VALUE DECIMAL(9, 2) NOT NULL,\n  DISCOUNT FLOAT DEFAULT 0 NOT NULL,\n  ITEM_TYPE PRODTYPE,\n  AGED COMPUTED BY (ship_date - order_date)\n)",
5579                                          'CREATE TABLE AR (\n  C1 INTEGER,\n  C2 INTEGER[4, 0:3, 2],\n  C3 VARCHAR(15)[0:5, 2],\n  C4 CHAR(5)[5],\n  C5 TIMESTAMP[2],\n  C6 TIME[2],\n  C7 DECIMAL(10, 2)[2],\n  C8 NUMERIC(10, 2)[2],\n  C9 SMALLINT[2],\n  C10 BIGINT[2],\n  C11 FLOAT[2],\n  C12 DOUBLE PRECISION[2],\n  C13 DECIMAL(10, 1)[2],\n  C14 DECIMAL(10, 5)[2],\n  C15 DECIMAL(18, 5)[2],\n  C16 BOOLEAN[3]\n)',
5580                                          'CREATE TABLE T2 (\n  C1 SMALLINT,\n  C2 INTEGER,\n  C3 BIGINT,\n  C4 CHAR(5),\n  C5 VARCHAR(10),\n  C6 DATE,\n  C7 TIME,\n  C8 TIMESTAMP,\n  C9 BLOB SUB_TYPE TEXT SEGMENT SIZE 80,\n  C10 NUMERIC(18, 2),\n  C11 DECIMAL(18, 2),\n  C12 FLOAT,\n  C13 DOUBLE PRECISION,\n  C14 NUMERIC(8, 4),\n  C15 DECIMAL(8, 4),\n  C16 BLOB SUB_TYPE BINARY SEGMENT SIZE 80,\n  C17 BOOLEAN\n)',
5581                                          'CREATE TABLE T3 (\n  C1 INTEGER,\n  C2 CHAR(10) CHARACTER SET UTF8,\n  C3 VARCHAR(10) CHARACTER SET UTF8,\n  C4 BLOB SUB_TYPE TEXT SEGMENT SIZE 80 CHARACTER SET UTF8,\n  C5 BLOB SUB_TYPE BINARY SEGMENT SIZE 80\n)',
5582                                          'CREATE TABLE T4 (\n  C1 INTEGER,\n  C_OCTETS CHAR(5) CHARACTER SET OCTETS,\n  V_OCTETS VARCHAR(30) CHARACTER SET OCTETS,\n  C_NONE CHAR(5),\n  V_NONE VARCHAR(30),\n  C_WIN1250 CHAR(5) CHARACTER SET WIN1250,\n  V_WIN1250 VARCHAR(30) CHARACTER SET WIN1250,\n  C_UTF8 CHAR(5) CHARACTER SET UTF8,\n  V_UTF8 VARCHAR(30) CHARACTER SET UTF8\n)',
5583                                          'CREATE TABLE T5 (\n  ID NUMERIC(10, 0) GENERATED BY DEFAULT AS IDENTITY,\n  C1 VARCHAR(15),\n  UQ BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 100)\n)', 'CREATE TABLE T (\n  C1 INTEGER NOT NULL\n)'])
5584        else:
5585            self.assertListEqual(script, ['CREATE TABLE COUNTRY (\n  COUNTRY COUNTRYNAME NOT NULL,\n  CURRENCY VARCHAR(10) NOT NULL\n)',
5586                                          'CREATE TABLE JOB (\n  JOB_CODE JOBCODE NOT NULL,\n  JOB_GRADE JOBGRADE NOT NULL,\n  JOB_COUNTRY COUNTRYNAME NOT NULL,\n  JOB_TITLE VARCHAR(25) NOT NULL,\n  MIN_SALARY SALARY NOT NULL,\n  MAX_SALARY SALARY NOT NULL,\n  JOB_REQUIREMENT BLOB SUB_TYPE TEXT SEGMENT SIZE 400,\n  LANGUAGE_REQ VARCHAR(15)[5]\n)',
5587                                          "CREATE TABLE DEPARTMENT (\n  DEPT_NO DEPTNO NOT NULL,\n  DEPARTMENT VARCHAR(25) NOT NULL,\n  HEAD_DEPT DEPTNO,\n  MNGR_NO EMPNO,\n  BUDGET BUDGET,\n  LOCATION VARCHAR(15),\n  PHONE_NO PHONENUMBER DEFAULT '555-1234'\n)",
5588                                          'CREATE TABLE EMPLOYEE (\n  EMP_NO EMPNO NOT NULL,\n  FIRST_NAME "FIRSTNAME" NOT NULL,\n  LAST_NAME "LASTNAME" NOT NULL,\n  PHONE_EXT VARCHAR(4),\n  HIRE_DATE TIMESTAMP DEFAULT \'NOW\' NOT NULL,\n  DEPT_NO DEPTNO NOT NULL,\n  JOB_CODE JOBCODE NOT NULL,\n  JOB_GRADE JOBGRADE NOT NULL,\n  JOB_COUNTRY COUNTRYNAME NOT NULL,\n  SALARY SALARY NOT NULL,\n  FULL_NAME COMPUTED BY (last_name || \', \' || first_name)\n)',
5589                                          'CREATE TABLE CUSTOMER (\n  CUST_NO CUSTNO NOT NULL,\n  CUSTOMER VARCHAR(25) NOT NULL,\n  CONTACT_FIRST "FIRSTNAME",\n  CONTACT_LAST "LASTNAME",\n  PHONE_NO PHONENUMBER,\n  ADDRESS_LINE1 ADDRESSLINE,\n  ADDRESS_LINE2 ADDRESSLINE,\n  CITY VARCHAR(25),\n  STATE_PROVINCE VARCHAR(15),\n  COUNTRY COUNTRYNAME,\n  POSTAL_CODE VARCHAR(12),\n  ON_HOLD CHAR(1) DEFAULT NULL\n)',
5590                                          'CREATE TABLE T4 (\n  C1 INTEGER,\n  C_OCTETS CHAR(5) CHARACTER SET OCTETS,\n  V_OCTETS VARCHAR(30) CHARACTER SET OCTETS,\n  C_NONE CHAR(5),\n  V_NONE VARCHAR(30),\n  C_WIN1250 CHAR(5) CHARACTER SET WIN1250,\n  V_WIN1250 VARCHAR(30) CHARACTER SET WIN1250,\n  C_UTF8 CHAR(5) CHARACTER SET UTF8,\n  V_UTF8 VARCHAR(30) CHARACTER SET UTF8\n)',
5591                                          'CREATE TABLE PROJECT (\n  PROJ_ID PROJNO NOT NULL,\n  PROJ_NAME VARCHAR(20) NOT NULL,\n  PROJ_DESC BLOB SUB_TYPE TEXT SEGMENT SIZE 800,\n  TEAM_LEADER EMPNO,\n  PRODUCT PRODTYPE\n)',
5592                                          'CREATE TABLE EMPLOYEE_PROJECT (\n  EMP_NO EMPNO NOT NULL,\n  PROJ_ID PROJNO NOT NULL\n)',
5593                                          'CREATE TABLE PROJ_DEPT_BUDGET (\n  FISCAL_YEAR INTEGER NOT NULL,\n  PROJ_ID PROJNO NOT NULL,\n  DEPT_NO DEPTNO NOT NULL,\n  QUART_HEAD_CNT INTEGER[4],\n  PROJECTED_BUDGET BUDGET\n)',
5594                                          "CREATE TABLE SALARY_HISTORY (\n  EMP_NO EMPNO NOT NULL,\n  CHANGE_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,\n  UPDATER_ID VARCHAR(20) NOT NULL,\n  OLD_SALARY SALARY NOT NULL,\n  PERCENT_CHANGE DOUBLE PRECISION DEFAULT 0 NOT NULL,\n  NEW_SALARY COMPUTED BY (old_salary + old_salary * percent_change / 100)\n)",
5595                                          "CREATE TABLE SALES (\n  PO_NUMBER PONUMBER NOT NULL,\n  CUST_NO CUSTNO NOT NULL,\n  SALES_REP EMPNO,\n  ORDER_STATUS VARCHAR(7) DEFAULT 'new' NOT NULL,\n  ORDER_DATE TIMESTAMP DEFAULT 'NOW' NOT NULL,\n  SHIP_DATE TIMESTAMP,\n  DATE_NEEDED TIMESTAMP,\n  PAID CHAR(1) DEFAULT 'n',\n  QTY_ORDERED INTEGER DEFAULT 1 NOT NULL,\n  TOTAL_VALUE DECIMAL(9, 2) NOT NULL,\n  DISCOUNT FLOAT DEFAULT 0 NOT NULL,\n  ITEM_TYPE PRODTYPE,\n  AGED COMPUTED BY (ship_date - order_date)\n)",
5596                                          'CREATE TABLE T3 (\n  C1 INTEGER,\n  C2 CHAR(10) CHARACTER SET UTF8,\n  C3 VARCHAR(10) CHARACTER SET UTF8,\n  C4 BLOB SUB_TYPE TEXT SEGMENT SIZE 80 CHARACTER SET UTF8,\n  C5 BLOB SUB_TYPE BINARY SEGMENT SIZE 80\n)',
5597                                          'CREATE TABLE T2 (\n  C1 SMALLINT,\n  C2 INTEGER,\n  C3 BIGINT,\n  C4 CHAR(5),\n  C5 VARCHAR(10),\n  C6 DATE,\n  C7 TIME,\n  C8 TIMESTAMP,\n  C9 BLOB SUB_TYPE TEXT SEGMENT SIZE 80,\n  C10 NUMERIC(18, 2),\n  C11 DECIMAL(18, 2),\n  C12 FLOAT,\n  C13 DOUBLE PRECISION,\n  C14 NUMERIC(8, 4),\n  C15 DECIMAL(8, 4),\n  C16 BLOB SUB_TYPE BINARY SEGMENT SIZE 80\n)',
5598                                          'CREATE TABLE AR (\n  C1 INTEGER,\n  C2 INTEGER[4, 0:3, 2],\n  C3 VARCHAR(15)[0:5, 2],\n  C4 CHAR(5)[5],\n  C5 TIMESTAMP[2],\n  C6 TIME[2],\n  C7 DECIMAL(10, 2)[2],\n  C8 NUMERIC(10, 2)[2],\n  C9 SMALLINT[2],\n  C10 BIGINT[2],\n  C11 FLOAT[2],\n  C12 DOUBLE PRECISION[2],\n  C13 DECIMAL(10, 1)[2],\n  C14 DECIMAL(10, 5)[2],\n  C15 DECIMAL(18, 5)[2]\n)',
5599                                          'CREATE TABLE T (\n  C1 INTEGER NOT NULL\n)'])
5600        script = s.get_metadata_ddl([sm.SCRIPT_PRIMARY_KEYS])
5601        if self.version == FB30:
5602            self.assertListEqual(script, ['ALTER TABLE COUNTRY ADD PRIMARY KEY (COUNTRY)',
5603                                          'ALTER TABLE JOB ADD PRIMARY KEY (JOB_CODE,JOB_GRADE,JOB_COUNTRY)',
5604                                          'ALTER TABLE DEPARTMENT ADD PRIMARY KEY (DEPT_NO)',
5605                                          'ALTER TABLE EMPLOYEE ADD PRIMARY KEY (EMP_NO)',
5606                                          'ALTER TABLE PROJECT ADD PRIMARY KEY (PROJ_ID)',
5607                                          'ALTER TABLE EMPLOYEE_PROJECT ADD PRIMARY KEY (EMP_NO,PROJ_ID)',
5608                                          'ALTER TABLE PROJ_DEPT_BUDGET ADD PRIMARY KEY (FISCAL_YEAR,PROJ_ID,DEPT_NO)',
5609                                          'ALTER TABLE SALARY_HISTORY ADD PRIMARY KEY (EMP_NO,CHANGE_DATE,UPDATER_ID)',
5610                                          'ALTER TABLE CUSTOMER ADD PRIMARY KEY (CUST_NO)',
5611                                          'ALTER TABLE SALES ADD PRIMARY KEY (PO_NUMBER)',
5612                                          'ALTER TABLE T5 ADD PRIMARY KEY (ID)',
5613                                          'ALTER TABLE T ADD PRIMARY KEY (C1)'],)
5614        else:
5615            self.assertListEqual(script, ['ALTER TABLE COUNTRY ADD PRIMARY KEY (COUNTRY)',
5616                                          'ALTER TABLE JOB ADD PRIMARY KEY (JOB_CODE,JOB_GRADE,JOB_COUNTRY)',
5617                                          'ALTER TABLE DEPARTMENT ADD PRIMARY KEY (DEPT_NO)',
5618                                          'ALTER TABLE EMPLOYEE ADD PRIMARY KEY (EMP_NO)',
5619                                          'ALTER TABLE PROJECT ADD PRIMARY KEY (PROJ_ID)',
5620                                          'ALTER TABLE EMPLOYEE_PROJECT ADD PRIMARY KEY (EMP_NO,PROJ_ID)',
5621                                          'ALTER TABLE PROJ_DEPT_BUDGET ADD PRIMARY KEY (FISCAL_YEAR,PROJ_ID,DEPT_NO)',
5622                                          'ALTER TABLE SALARY_HISTORY ADD PRIMARY KEY (EMP_NO,CHANGE_DATE,UPDATER_ID)',
5623                                          'ALTER TABLE CUSTOMER ADD PRIMARY KEY (CUST_NO)',
5624                                          'ALTER TABLE SALES ADD PRIMARY KEY (PO_NUMBER)',
5625                                          'ALTER TABLE T ADD PRIMARY KEY (C1)'],)
5626        script = s.get_metadata_ddl([sm.SCRIPT_UNIQUE_CONSTRAINTS])
5627        self.assertListEqual(script, ['ALTER TABLE DEPARTMENT ADD UNIQUE (DEPARTMENT)',
5628                                      'ALTER TABLE PROJECT ADD UNIQUE (PROJ_NAME)'])
5629        script = s.get_metadata_ddl([sm.SCRIPT_CHECK_CONSTRAINTS])
5630        #self.assertListEqual(script, ['ALTER TABLE JOB ADD CHECK (min_salary < max_salary)',
5631                                      #'ALTER TABLE EMPLOYEE ADD CHECK ( salary >= (SELECT min_salary FROM job WHERE\n                        job.job_code = employee.job_code AND\n                        job.job_grade = employee.job_grade AND\n                        job.job_country = employee.job_country) AND\n            salary <= (SELECT max_salary FROM job WHERE\n                        job.job_code = employee.job_code AND\n                        job.job_grade = employee.job_grade AND\n                        job.job_country = employee.job_country))',
5632                                      #"ALTER TABLE CUSTOMER ADD CHECK (on_hold IS NULL OR on_hold = '*')",
5633                                      #'ALTER TABLE PROJ_DEPT_BUDGET ADD CHECK (FISCAL_YEAR >= 1993)',
5634                                      #'ALTER TABLE SALARY_HISTORY ADD CHECK (percent_change between -50 and 50)',
5635                                      #'ALTER TABLE SALES ADD CHECK (total_value >= 0)',
5636                                      #'ALTER TABLE SALES ADD CHECK (ship_date >= order_date OR ship_date IS NULL)',
5637                                      #"ALTER TABLE SALES ADD CHECK (NOT (order_status = 'shipped' AND\n            EXISTS (SELECT on_hold FROM customer\n                    WHERE customer.cust_no = sales.cust_no\n                    AND customer.on_hold = '*')))",
5638                                      #'ALTER TABLE SALES ADD CHECK (date_needed > order_date OR date_needed IS NULL)',
5639                                      #"ALTER TABLE SALES ADD CHECK (paid in ('y', 'n'))",
5640                                      #"ALTER TABLE SALES ADD CHECK (NOT (order_status = 'shipped' AND ship_date IS NULL))",
5641                                      #"ALTER TABLE SALES ADD CHECK (order_status in\n                            ('new', 'open', 'shipped', 'waiting'))",
5642                                      #'ALTER TABLE SALES ADD CHECK (discount >= 0 AND discount <= 1)',
5643                                      #'ALTER TABLE SALES ADD CHECK (qty_ordered >= 1)'])
5644        script = s.get_metadata_ddl([sm.SCRIPT_FOREIGN_CONSTRAINTS])
5645        self.assertListEqual(script, ['ALTER TABLE JOB ADD FOREIGN KEY (JOB_COUNTRY)\n  REFERENCES COUNTRY (COUNTRY)',
5646                                      'ALTER TABLE DEPARTMENT ADD FOREIGN KEY (HEAD_DEPT)\n  REFERENCES DEPARTMENT (DEPT_NO)',
5647                                      'ALTER TABLE DEPARTMENT ADD FOREIGN KEY (MNGR_NO)\n  REFERENCES EMPLOYEE (EMP_NO)',
5648                                      'ALTER TABLE EMPLOYEE ADD FOREIGN KEY (DEPT_NO)\n  REFERENCES DEPARTMENT (DEPT_NO)',
5649                                      'ALTER TABLE EMPLOYEE ADD FOREIGN KEY (JOB_CODE,JOB_GRADE,JOB_COUNTRY)\n  REFERENCES JOB (JOB_CODE,JOB_GRADE,JOB_COUNTRY)',
5650                                      'ALTER TABLE CUSTOMER ADD FOREIGN KEY (COUNTRY)\n  REFERENCES COUNTRY (COUNTRY)',
5651                                      'ALTER TABLE PROJECT ADD FOREIGN KEY (TEAM_LEADER)\n  REFERENCES EMPLOYEE (EMP_NO)',
5652                                      'ALTER TABLE EMPLOYEE_PROJECT ADD FOREIGN KEY (EMP_NO)\n  REFERENCES EMPLOYEE (EMP_NO)',
5653                                      'ALTER TABLE EMPLOYEE_PROJECT ADD FOREIGN KEY (PROJ_ID)\n  REFERENCES PROJECT (PROJ_ID)',
5654                                      'ALTER TABLE PROJ_DEPT_BUDGET ADD FOREIGN KEY (DEPT_NO)\n  REFERENCES DEPARTMENT (DEPT_NO)',
5655                                      'ALTER TABLE PROJ_DEPT_BUDGET ADD FOREIGN KEY (PROJ_ID)\n  REFERENCES PROJECT (PROJ_ID)',
5656                                      'ALTER TABLE SALARY_HISTORY ADD FOREIGN KEY (EMP_NO)\n  REFERENCES EMPLOYEE (EMP_NO)',
5657                                      'ALTER TABLE SALES ADD FOREIGN KEY (CUST_NO)\n  REFERENCES CUSTOMER (CUST_NO)',
5658                                      'ALTER TABLE SALES ADD FOREIGN KEY (SALES_REP)\n  REFERENCES EMPLOYEE (EMP_NO)'])
5659        script = s.get_metadata_ddl([sm.SCRIPT_INDICES])
5660        self.assertListEqual(script, ['CREATE ASCENDING INDEX MINSALX ON JOB (JOB_COUNTRY,MIN_SALARY)',
5661                                      'CREATE DESCENDING INDEX MAXSALX ON JOB (JOB_COUNTRY,MAX_SALARY)',
5662                                      'CREATE DESCENDING INDEX BUDGETX ON DEPARTMENT (BUDGET)',
5663                                      'CREATE ASCENDING INDEX NAMEX ON EMPLOYEE (LAST_NAME,FIRST_NAME)',
5664                                      'CREATE ASCENDING INDEX CUSTNAMEX ON CUSTOMER (CUSTOMER)',
5665                                      'CREATE ASCENDING INDEX CUSTREGION ON CUSTOMER (COUNTRY,CITY)',
5666                                      'CREATE UNIQUE ASCENDING INDEX PRODTYPEX ON PROJECT (PRODUCT,PROJ_NAME)',
5667                                      'CREATE ASCENDING INDEX UPDATERX ON SALARY_HISTORY (UPDATER_ID)',
5668                                      'CREATE DESCENDING INDEX CHANGEX ON SALARY_HISTORY (CHANGE_DATE)',
5669                                      'CREATE ASCENDING INDEX NEEDX ON SALES (DATE_NEEDED)',
5670                                      'CREATE ASCENDING INDEX SALESTATX ON SALES (ORDER_STATUS,PAID)',
5671                                      'CREATE DESCENDING INDEX QTYX ON SALES (ITEM_TYPE,QTY_ORDERED)'])
5672        script = s.get_metadata_ddl([sm.SCRIPT_VIEWS])
5673        self.assertListEqual(script, ['CREATE VIEW PHONE_LIST (EMP_NO,FIRST_NAME,LAST_NAME,PHONE_EXT,LOCATION,PHONE_NO)\n   AS\n     SELECT\n    emp_no, first_name, last_name, phone_ext, location, phone_no\n    FROM employee, department\n    WHERE employee.dept_no = department.dept_no'])
5674        if self.version == FB30:
5675            script = s.get_metadata_ddl([sm.SCRIPT_PACKAGE_BODIES])
5676            self.assertListEqual(script, ['CREATE PACKAGE BODY TEST\nAS\nBEGIN\n  FUNCTION F1(I INT) RETURNS INT; -- private function\n\n  PROCEDURE P1(I INT) RETURNS (O INT)\n  AS\n  BEGIN\n  END\n\n  FUNCTION F1(I INT) RETURNS INT\n  AS\n  BEGIN\n    RETURN F(I)+10;\n  END\n\n  FUNCTION F(X INT) RETURNS INT\n  AS\n  BEGIN\n    RETURN X+1;\n  END\nEND', 'CREATE PACKAGE BODY TEST2\nAS\nBEGIN\n  FUNCTION F3(X INT) RETURNS INT\n  AS\n  BEGIN\n    RETURN TEST.F(X)+100+FN();\n  END\nEND'])
5677            script = s.get_metadata_ddl([sm.SCRIPT_FUNCTION_BODIES])
5678            self.assertListEqual(script, ['ALTER FUNCTION F2 (X INTEGER)\nRETURNS INTEGER\nAS\nBEGIN\n  RETURN X+1;\nEND',
5679                                          'ALTER FUNCTION FX (\n  F TYPE OF "FIRSTNAME",\n  L TYPE OF COLUMN CUSTOMER.CONTACT_LAST\n)\nRETURNS VARCHAR(35)\nAS\nBEGIN\n  RETURN L || \', \' || F;\nEND',
5680                                          'ALTER FUNCTION FN\nRETURNS INTEGER\nAS\nBEGIN\n  RETURN 0;\nEND'])
5681        script = s.get_metadata_ddl([sm.SCRIPT_PROCEDURE_BODIES])
5682        self.assertListEqual(script, ['ALTER PROCEDURE GET_EMP_PROJ (EMP_NO SMALLINT)\nRETURNS (PROJ_ID CHAR(5))\nAS\nBEGIN\n\tFOR SELECT proj_id\n\t\tFROM employee_project\n\t\tWHERE emp_no = :emp_no\n\t\tINTO :proj_id\n\tDO\n\t\tSUSPEND;\nEND', 'ALTER PROCEDURE ADD_EMP_PROJ (\n  EMP_NO SMALLINT,\n  PROJ_ID CHAR(5)\n)\nAS\nBEGIN\n\tBEGIN\n\tINSERT INTO employee_project (emp_no, proj_id) VALUES (:emp_no, :proj_id);\n\tWHEN SQLCODE -530 DO\n\t\tEXCEPTION unknown_emp_id;\n\tEND\n\tSUSPEND;\nEND',
5683                                      'ALTER PROCEDURE SUB_TOT_BUDGET (HEAD_DEPT CHAR(3))\nRETURNS (\n  TOT_BUDGET DECIMAL(12, 2),\n  AVG_BUDGET DECIMAL(12, 2),\n  MIN_BUDGET DECIMAL(12, 2),\n  MAX_BUDGET DECIMAL(12, 2)\n)\nAS\nBEGIN\n\tSELECT SUM(budget), AVG(budget), MIN(budget), MAX(budget)\n\t\tFROM department\n\t\tWHERE head_dept = :head_dept\n\t\tINTO :tot_budget, :avg_budget, :min_budget, :max_budget;\n\tSUSPEND;\nEND',
5684                                      "ALTER PROCEDURE DELETE_EMPLOYEE (EMP_NUM INTEGER)\nAS\nDECLARE VARIABLE any_sales INTEGER;\nBEGIN\n\tany_sales = 0;\n\n\t/*\n\t *\tIf there are any sales records referencing this employee,\n\t *\tcan't delete the employee until the sales are re-assigned\n\t *\tto another employee or changed to NULL.\n\t */\n\tSELECT count(po_number)\n\tFROM sales\n\tWHERE sales_rep = :emp_num\n\tINTO :any_sales;\n\n\tIF (any_sales > 0) THEN\n\tBEGIN\n\t\tEXCEPTION reassign_sales;\n\t\tSUSPEND;\n\tEND\n\n\t/*\n\t *\tIf the employee is a manager, update the department.\n\t */\n\tUPDATE department\n\tSET mngr_no = NULL\n\tWHERE mngr_no = :emp_num;\n\n\t/*\n\t *\tIf the employee is a project leader, update project.\n\t */\n\tUPDATE project\n\tSET team_leader = NULL\n\tWHERE team_leader = :emp_num;\n\n\t/*\n\t *\tDelete the employee from any projects.\n\t */\n\tDELETE FROM employee_project\n\tWHERE emp_no = :emp_num;\n\n\t/*\n\t *\tDelete old salary records.\n\t */\n\tDELETE FROM salary_history\n\tWHERE emp_no = :emp_num;\n\n\t/*\n\t *\tDelete the employee.\n\t */\n\tDELETE FROM employee\n\tWHERE emp_no = :emp_num;\n\n\tSUSPEND;\nEND",
5685                                      'ALTER PROCEDURE DEPT_BUDGET (DNO CHAR(3))\nRETURNS (TOT DECIMAL(12, 2))\nAS\nDECLARE VARIABLE sumb DECIMAL(12, 2);\n\tDECLARE VARIABLE rdno CHAR(3);\n\tDECLARE VARIABLE cnt INTEGER;\nBEGIN\n\ttot = 0;\n\n\tSELECT budget FROM department WHERE dept_no = :dno INTO :tot;\n\n\tSELECT count(budget) FROM department WHERE head_dept = :dno INTO :cnt;\n\n\tIF (cnt = 0) THEN\n\t\tSUSPEND;\n\n\tFOR SELECT dept_no\n\t\tFROM department\n\t\tWHERE head_dept = :dno\n\t\tINTO :rdno\n\tDO\n\t\tBEGIN\n\t\t\tEXECUTE PROCEDURE dept_budget :rdno RETURNING_VALUES :sumb;\n\t\t\ttot = tot + sumb;\n\t\tEND\n\n\tSUSPEND;\nEND',
5686                                      "ALTER PROCEDURE ORG_CHART\nRETURNS (\n  HEAD_DEPT CHAR(25),\n  DEPARTMENT CHAR(25),\n  MNGR_NAME CHAR(20),\n  TITLE CHAR(5),\n  EMP_CNT INTEGER\n)\nAS\nDECLARE VARIABLE mngr_no INTEGER;\n\tDECLARE VARIABLE dno CHAR(3);\nBEGIN\n\tFOR SELECT h.department, d.department, d.mngr_no, d.dept_no\n\t\tFROM department d\n\t\tLEFT OUTER JOIN department h ON d.head_dept = h.dept_no\n\t\tORDER BY d.dept_no\n\t\tINTO :head_dept, :department, :mngr_no, :dno\n\tDO\n\tBEGIN\n\t\tIF (:mngr_no IS NULL) THEN\n\t\tBEGIN\n\t\t\tmngr_name = '--TBH--';\n\t\t\ttitle = '';\n\t\tEND\n\n\t\tELSE\n\t\t\tSELECT full_name, job_code\n\t\t\tFROM employee\n\t\t\tWHERE emp_no = :mngr_no\n\t\t\tINTO :mngr_name, :title;\n\n\t\tSELECT COUNT(emp_no)\n\t\tFROM employee\n\t\tWHERE dept_no = :dno\n\t\tINTO :emp_cnt;\n\n\t\tSUSPEND;\n\tEND\nEND",
5687                                      "ALTER PROCEDURE MAIL_LABEL (CUST_NO INTEGER)\nRETURNS (\n  LINE1 CHAR(40),\n  LINE2 CHAR(40),\n  LINE3 CHAR(40),\n  LINE4 CHAR(40),\n  LINE5 CHAR(40),\n  LINE6 CHAR(40)\n)\nAS\nDECLARE VARIABLE customer\tVARCHAR(25);\n\tDECLARE VARIABLE first_name\t\tVARCHAR(15);\n\tDECLARE VARIABLE last_name\t\tVARCHAR(20);\n\tDECLARE VARIABLE addr1\t\tVARCHAR(30);\n\tDECLARE VARIABLE addr2\t\tVARCHAR(30);\n\tDECLARE VARIABLE city\t\tVARCHAR(25);\n\tDECLARE VARIABLE state\t\tVARCHAR(15);\n\tDECLARE VARIABLE country\tVARCHAR(15);\n\tDECLARE VARIABLE postcode\tVARCHAR(12);\n\tDECLARE VARIABLE cnt\t\tINTEGER;\nBEGIN\n\tline1 = '';\n\tline2 = '';\n\tline3 = '';\n\tline4 = '';\n\tline5 = '';\n\tline6 = '';\n\n\tSELECT customer, contact_first, contact_last, address_line1,\n\t\taddress_line2, city, state_province, country, postal_code\n\tFROM CUSTOMER\n\tWHERE cust_no = :cust_no\n\tINTO :customer, :first_name, :last_name, :addr1, :addr2,\n\t\t:city, :state, :country, :postcode;\n\n\tIF (customer IS NOT NULL) THEN\n\t\tline1 = customer;\n\tIF (first_name IS NOT NULL) THEN\n\t\tline2 = first_name || ' ' || last_name;\n\tELSE\n\t\tline2 = last_name;\n\tIF (addr1 IS NOT NULL) THEN\n\t\tline3 = addr1;\n\tIF (addr2 IS NOT NULL) THEN\n\t\tline4 = addr2;\n\n\tIF (country = 'USA') THEN\n\tBEGIN\n\t\tIF (city IS NOT NULL) THEN\n\t\t\tline5 = city || ', ' || state || '  ' || postcode;\n\t\tELSE\n\t\t\tline5 = state || '  ' || postcode;\n\tEND\n\tELSE\n\tBEGIN\n\t\tIF (city IS NOT NULL) THEN\n\t\t\tline5 = city || ', ' || state;\n\t\tELSE\n\t\t\tline5 = state;\n\t\tline6 = country || '    ' || postcode;\n\tEND\n\n\tSUSPEND;\nEND",
5688                                      "ALTER PROCEDURE SHIP_ORDER (PO_NUM CHAR(8))\nAS\nDECLARE VARIABLE ord_stat CHAR(7);\n\tDECLARE VARIABLE hold_stat CHAR(1);\n\tDECLARE VARIABLE cust_no INTEGER;\n\tDECLARE VARIABLE any_po CHAR(8);\nBEGIN\n\tSELECT s.order_status, c.on_hold, c.cust_no\n\tFROM sales s, customer c\n\tWHERE po_number = :po_num\n\tAND s.cust_no = c.cust_no\n\tINTO :ord_stat, :hold_stat, :cust_no;\n\n\t/* This purchase order has been already shipped. */\n\tIF (ord_stat = 'shipped') THEN\n\tBEGIN\n\t\tEXCEPTION order_already_shipped;\n\t\tSUSPEND;\n\tEND\n\n\t/*\tCustomer is on hold. */\n\tELSE IF (hold_stat = '*') THEN\n\tBEGIN\n\t\tEXCEPTION customer_on_hold;\n\t\tSUSPEND;\n\tEND\n\n\t/*\n\t *\tIf there is an unpaid balance on orders shipped over 2 months ago,\n\t *\tput the customer on hold.\n\t */\n\tFOR SELECT po_number\n\t\tFROM sales\n\t\tWHERE cust_no = :cust_no\n\t\tAND order_status = 'shipped'\n\t\tAND paid = 'n'\n\t\tAND ship_date < CAST('NOW' AS TIMESTAMP) - 60\n\t\tINTO :any_po\n\tDO\n\tBEGIN\n\t\tEXCEPTION customer_check;\n\n\t\tUPDATE customer\n\t\tSET on_hold = '*'\n\t\tWHERE cust_no = :cust_no;\n\n\t\tSUSPEND;\n\tEND\n\n\t/*\n\t *\tShip the order.\n\t */\n\tUPDATE sales\n\tSET order_status = 'shipped', ship_date = 'NOW'\n\tWHERE po_number = :po_num;\n\n\tSUSPEND;\nEND",
5689                                      "ALTER PROCEDURE SHOW_LANGS (\n  CODE VARCHAR(5),\n  GRADE SMALLINT,\n  CTY VARCHAR(15)\n)\nRETURNS (LANGUAGES VARCHAR(15))\nAS\nDECLARE VARIABLE i INTEGER;\nBEGIN\n  i = 1;\n  WHILE (i <= 5) DO\n  BEGIN\n    SELECT language_req[:i] FROM joB\n    WHERE ((job_code = :code) AND (job_grade = :grade) AND (job_country = :cty)\n           AND (language_req IS NOT NULL))\n    INTO :languages;\n    IF (languages = ' ') THEN  /* Prints 'NULL' instead of blanks */\n       languages = 'NULL';         \n    i = i +1;\n    SUSPEND;\n  END\nEND",
5690                                      "ALTER PROCEDURE ALL_LANGS\nRETURNS (\n  CODE VARCHAR(5),\n  GRADE VARCHAR(5),\n  COUNTRY VARCHAR(15),\n  LANG VARCHAR(15)\n)\nAS\nBEGIN\n\tFOR SELECT job_code, job_grade, job_country FROM job \n\t\tINTO :code, :grade, :country\n\n\tDO\n\tBEGIN\n\t    FOR SELECT languages FROM show_langs \n \t\t    (:code, :grade, :country) INTO :lang DO\n\t        SUSPEND;\n\t    /* Put nice separators between rows */\n\t    code = '=====';\n\t    grade = '=====';\n\t    country = '===============';\n\t    lang = '==============';\n\t    SUSPEND;\n\tEND\n    END"])
5691        script = s.get_metadata_ddl([sm.SCRIPT_TRIGGERS])
5692        if self.version == FB30:
5693            self.assertListEqual(script, ['CREATE TRIGGER SET_EMP_NO FOR EMPLOYEE ACTIVE\nBEFORE INSERT POSITION 0\nAS\nBEGIN\n    if (new.emp_no is null) then\n    new.emp_no = gen_id(emp_no_gen, 1);\nEND',
5694                                          "CREATE TRIGGER SAVE_SALARY_CHANGE FOR EMPLOYEE ACTIVE\nAFTER UPDATE POSITION 0\nAS\nBEGIN\n    IF (old.salary <> new.salary) THEN\n        INSERT INTO salary_history\n            (emp_no, change_date, updater_id, old_salary, percent_change)\n        VALUES (\n            old.emp_no,\n            'NOW',\n            user,\n            old.salary,\n            (new.salary - old.salary) * 100 / old.salary);\nEND",
5695                                          'CREATE TRIGGER SET_CUST_NO FOR CUSTOMER ACTIVE\nBEFORE INSERT POSITION 0\nAS\nBEGIN\n    if (new.cust_no is null) then\n    new.cust_no = gen_id(cust_no_gen, 1);\nEND',
5696                                          "CREATE TRIGGER POST_NEW_ORDER FOR SALES ACTIVE\nAFTER INSERT POSITION 0\nAS\nBEGIN\n    POST_EVENT 'new_order';\nEND",
5697                                          'CREATE TRIGGER TR_CONNECT ACTIVE\nON CONNECT POSITION 0\nAS \nBEGIN \n    /* enter trigger code here */ \nEND',
5698                                          'CREATE TRIGGER TR_MULTI FOR COUNTRY ACTIVE\nAFTER INSERT OR UPDATE OR DELETE POSITION 0\nAS \nBEGIN \n    /* enter trigger code here */ \nEND'])
5699        else:
5700            self.assertListEqual(script, ['CREATE TRIGGER SET_EMP_NO FOR EMPLOYEE ACTIVE\nBEFORE INSERT POSITION 0\nAS\nBEGIN\n    if (new.emp_no is null) then\n    new.emp_no = gen_id(emp_no_gen, 1);\nEND',
5701                                          "CREATE TRIGGER SAVE_SALARY_CHANGE FOR EMPLOYEE ACTIVE\nAFTER UPDATE POSITION 0\nAS\nBEGIN\n    IF (old.salary <> new.salary) THEN\n        INSERT INTO salary_history\n            (emp_no, change_date, updater_id, old_salary, percent_change)\n        VALUES (\n            old.emp_no,\n            'NOW',\n            user,\n            old.salary,\n            (new.salary - old.salary) * 100 / old.salary);\nEND",
5702                                          'CREATE TRIGGER SET_CUST_NO FOR CUSTOMER ACTIVE\nBEFORE INSERT POSITION 0\nAS\nBEGIN\n    if (new.cust_no is null) then\n    new.cust_no = gen_id(cust_no_gen, 1);\nEND',
5703                                          "CREATE TRIGGER POST_NEW_ORDER FOR SALES ACTIVE\nAFTER INSERT POSITION 0\nAS\nBEGIN\n    POST_EVENT 'new_order';\nEND",
5704                                          'CREATE TRIGGER TR_MULTI FOR COUNTRY ACTIVE\nAFTER INSERT OR UPDATE OR DELETE POSITION 0\nAS \nBEGIN \n    /* enter trigger code here */ \nEND',
5705                                          'CREATE TRIGGER TR_CONNECT ACTIVE\nON CONNECT POSITION 0\nAS \nBEGIN \n    /* enter trigger code here */ \nEND'])
5706        script = s.get_metadata_ddl([sm.SCRIPT_ROLES])
5707        self.assertListEqual(script, ['CREATE ROLE TEST_ROLE'])
5708        script = s.get_metadata_ddl([sm.SCRIPT_GRANTS])
5709        self.assertListEqual(script, ['GRANT SELECT ON COUNTRY TO PUBLIC WITH GRANT OPTION',
5710                                      'GRANT INSERT ON COUNTRY TO PUBLIC WITH GRANT OPTION',
5711                                      'GRANT UPDATE ON COUNTRY TO PUBLIC WITH GRANT OPTION',
5712                                      'GRANT DELETE ON COUNTRY TO PUBLIC WITH GRANT OPTION',
5713                                      'GRANT REFERENCES ON COUNTRY TO PUBLIC WITH GRANT OPTION',
5714                                      'GRANT SELECT ON JOB TO PUBLIC WITH GRANT OPTION',
5715                                      'GRANT INSERT ON JOB TO PUBLIC WITH GRANT OPTION',
5716                                      'GRANT UPDATE ON JOB TO PUBLIC WITH GRANT OPTION',
5717                                      'GRANT DELETE ON JOB TO PUBLIC WITH GRANT OPTION',
5718                                      'GRANT REFERENCES ON JOB TO PUBLIC WITH GRANT OPTION',
5719                                      'GRANT SELECT ON DEPARTMENT TO PUBLIC WITH GRANT OPTION',
5720                                      'GRANT INSERT ON DEPARTMENT TO PUBLIC WITH GRANT OPTION',
5721                                      'GRANT UPDATE ON DEPARTMENT TO PUBLIC WITH GRANT OPTION',
5722                                      'GRANT DELETE ON DEPARTMENT TO PUBLIC WITH GRANT OPTION',
5723                                      'GRANT REFERENCES ON DEPARTMENT TO PUBLIC WITH GRANT OPTION',
5724                                      'GRANT SELECT ON EMPLOYEE TO PUBLIC WITH GRANT OPTION',
5725                                      'GRANT INSERT ON EMPLOYEE TO PUBLIC WITH GRANT OPTION',
5726                                      'GRANT UPDATE ON EMPLOYEE TO PUBLIC WITH GRANT OPTION',
5727                                      'GRANT DELETE ON EMPLOYEE TO PUBLIC WITH GRANT OPTION',
5728                                      'GRANT REFERENCES ON EMPLOYEE TO PUBLIC WITH GRANT OPTION',
5729                                      'GRANT SELECT ON PHONE_LIST TO PUBLIC WITH GRANT OPTION',
5730                                      'GRANT INSERT ON PHONE_LIST TO PUBLIC WITH GRANT OPTION',
5731                                      'GRANT UPDATE ON PHONE_LIST TO PUBLIC WITH GRANT OPTION',
5732                                      'GRANT DELETE ON PHONE_LIST TO PUBLIC WITH GRANT OPTION',
5733                                      'GRANT REFERENCES ON PHONE_LIST TO PUBLIC WITH GRANT OPTION',
5734                                      'GRANT SELECT ON PROJECT TO PUBLIC WITH GRANT OPTION',
5735                                      'GRANT INSERT ON PROJECT TO PUBLIC WITH GRANT OPTION',
5736                                      'GRANT UPDATE ON PROJECT TO PUBLIC WITH GRANT OPTION',
5737                                      'GRANT DELETE ON PROJECT TO PUBLIC WITH GRANT OPTION',
5738                                      'GRANT REFERENCES ON PROJECT TO PUBLIC WITH GRANT OPTION',
5739                                      'GRANT SELECT ON EMPLOYEE_PROJECT TO PUBLIC WITH GRANT OPTION',
5740                                      'GRANT INSERT ON EMPLOYEE_PROJECT TO PUBLIC WITH GRANT OPTION',
5741                                      'GRANT UPDATE ON EMPLOYEE_PROJECT TO PUBLIC WITH GRANT OPTION',
5742                                      'GRANT DELETE ON EMPLOYEE_PROJECT TO PUBLIC WITH GRANT OPTION',
5743                                      'GRANT REFERENCES ON EMPLOYEE_PROJECT TO PUBLIC WITH GRANT OPTION',
5744                                      'GRANT SELECT ON PROJ_DEPT_BUDGET TO PUBLIC WITH GRANT OPTION',
5745                                      'GRANT INSERT ON PROJ_DEPT_BUDGET TO PUBLIC WITH GRANT OPTION',
5746                                      'GRANT UPDATE ON PROJ_DEPT_BUDGET TO PUBLIC WITH GRANT OPTION',
5747                                      'GRANT DELETE ON PROJ_DEPT_BUDGET TO PUBLIC WITH GRANT OPTION',
5748                                      'GRANT REFERENCES ON PROJ_DEPT_BUDGET TO PUBLIC WITH GRANT OPTION',
5749                                      'GRANT SELECT ON SALARY_HISTORY TO PUBLIC WITH GRANT OPTION',
5750                                      'GRANT INSERT ON SALARY_HISTORY TO PUBLIC WITH GRANT OPTION',
5751                                      'GRANT UPDATE ON SALARY_HISTORY TO PUBLIC WITH GRANT OPTION',
5752                                      'GRANT DELETE ON SALARY_HISTORY TO PUBLIC WITH GRANT OPTION',
5753                                      'GRANT REFERENCES ON SALARY_HISTORY TO PUBLIC WITH GRANT OPTION',
5754                                      'GRANT SELECT ON CUSTOMER TO PUBLIC WITH GRANT OPTION',
5755                                      'GRANT INSERT ON CUSTOMER TO PUBLIC WITH GRANT OPTION',
5756                                      'GRANT UPDATE ON CUSTOMER TO PUBLIC WITH GRANT OPTION',
5757                                      'GRANT DELETE ON CUSTOMER TO PUBLIC WITH GRANT OPTION',
5758                                      'GRANT REFERENCES ON CUSTOMER TO PUBLIC WITH GRANT OPTION',
5759                                      'GRANT SELECT ON SALES TO PUBLIC WITH GRANT OPTION',
5760                                      'GRANT INSERT ON SALES TO PUBLIC WITH GRANT OPTION',
5761                                      'GRANT UPDATE ON SALES TO PUBLIC WITH GRANT OPTION',
5762                                      'GRANT DELETE ON SALES TO PUBLIC WITH GRANT OPTION',
5763                                      'GRANT REFERENCES ON SALES TO PUBLIC WITH GRANT OPTION',
5764                                      'GRANT EXECUTE ON PROCEDURE GET_EMP_PROJ TO PUBLIC WITH GRANT OPTION',
5765                                      'GRANT EXECUTE ON PROCEDURE ADD_EMP_PROJ TO PUBLIC WITH GRANT OPTION',
5766                                      'GRANT EXECUTE ON PROCEDURE SUB_TOT_BUDGET TO PUBLIC WITH GRANT OPTION',
5767                                      'GRANT EXECUTE ON PROCEDURE DELETE_EMPLOYEE TO PUBLIC WITH GRANT OPTION',
5768                                      'GRANT EXECUTE ON PROCEDURE DEPT_BUDGET TO PUBLIC WITH GRANT OPTION',
5769                                      'GRANT EXECUTE ON PROCEDURE ORG_CHART TO PUBLIC WITH GRANT OPTION',
5770                                      'GRANT EXECUTE ON PROCEDURE MAIL_LABEL TO PUBLIC WITH GRANT OPTION',
5771                                      'GRANT EXECUTE ON PROCEDURE SHIP_ORDER TO PUBLIC WITH GRANT OPTION',
5772                                      'GRANT EXECUTE ON PROCEDURE SHOW_LANGS TO PUBLIC WITH GRANT OPTION',
5773                                      'GRANT EXECUTE ON PROCEDURE ALL_LANGS TO PUBLIC WITH GRANT OPTION'])
5774        script = s.get_metadata_ddl([sm.SCRIPT_COMMENTS])
5775        self.assertListEqual(script, ["COMMENT ON CHARACTER SET NONE IS 'Comment on NONE character set'"])
5776        script = s.get_metadata_ddl([sm.SCRIPT_SHADOWS])
5777        self.assertListEqual(script, [])
5778        script = s.get_metadata_ddl([sm.SCRIPT_INDEX_DEACTIVATIONS])
5779        if self.version == FB30:
5780            self.assertListEqual(script, ['ALTER INDEX MINSALX INACTIVE',
5781                                          'ALTER INDEX MAXSALX INACTIVE',
5782                                          'ALTER INDEX BUDGETX INACTIVE',
5783                                          'ALTER INDEX NAMEX INACTIVE',
5784                                          'ALTER INDEX PRODTYPEX INACTIVE',
5785                                          'ALTER INDEX UPDATERX INACTIVE',
5786                                          'ALTER INDEX CHANGEX INACTIVE',
5787                                          'ALTER INDEX CUSTNAMEX INACTIVE',
5788                                          'ALTER INDEX CUSTREGION INACTIVE',
5789                                          'ALTER INDEX NEEDX INACTIVE',
5790                                          'ALTER INDEX SALESTATX INACTIVE',
5791                                          'ALTER INDEX QTYX INACTIVE'])
5792        else:
5793            self.assertListEqual(script, ['ALTER INDEX NEEDX INACTIVE',
5794                                          'ALTER INDEX SALESTATX INACTIVE',
5795                                          'ALTER INDEX QTYX INACTIVE',
5796                                          'ALTER INDEX UPDATERX INACTIVE',
5797                                          'ALTER INDEX CHANGEX INACTIVE',
5798                                          'ALTER INDEX PRODTYPEX INACTIVE',
5799                                          'ALTER INDEX CUSTNAMEX INACTIVE',
5800                                          'ALTER INDEX CUSTREGION INACTIVE',
5801                                          'ALTER INDEX NAMEX INACTIVE',
5802                                          'ALTER INDEX BUDGETX INACTIVE',
5803                                          'ALTER INDEX MINSALX INACTIVE',
5804                                          'ALTER INDEX MAXSALX INACTIVE'])
5805        script = s.get_metadata_ddl([sm.SCRIPT_INDEX_ACTIVATIONS])
5806        if self.version == FB30:
5807            self.assertListEqual(script, ['ALTER INDEX MINSALX ACTIVE',
5808                                          'ALTER INDEX MAXSALX ACTIVE',
5809                                          'ALTER INDEX BUDGETX ACTIVE',
5810                                          'ALTER INDEX NAMEX ACTIVE',
5811                                          'ALTER INDEX PRODTYPEX ACTIVE',
5812                                          'ALTER INDEX UPDATERX ACTIVE',
5813                                          'ALTER INDEX CHANGEX ACTIVE',
5814                                          'ALTER INDEX CUSTNAMEX ACTIVE',
5815                                          'ALTER INDEX CUSTREGION ACTIVE',
5816                                          'ALTER INDEX NEEDX ACTIVE',
5817                                          'ALTER INDEX SALESTATX ACTIVE',
5818                                          'ALTER INDEX QTYX ACTIVE'])
5819        else:
5820            self.assertListEqual(script, ['ALTER INDEX NEEDX ACTIVE',
5821                                          'ALTER INDEX SALESTATX ACTIVE',
5822                                          'ALTER INDEX QTYX ACTIVE',
5823                                          'ALTER INDEX UPDATERX ACTIVE',
5824                                          'ALTER INDEX CHANGEX ACTIVE',
5825                                          'ALTER INDEX PRODTYPEX ACTIVE',
5826                                          'ALTER INDEX CUSTNAMEX ACTIVE',
5827                                          'ALTER INDEX CUSTREGION ACTIVE',
5828                                          'ALTER INDEX NAMEX ACTIVE',
5829                                          'ALTER INDEX BUDGETX ACTIVE',
5830                                          'ALTER INDEX MINSALX ACTIVE',
5831                                          'ALTER INDEX MAXSALX ACTIVE'])
5832        script = s.get_metadata_ddl([sm.SCRIPT_SET_GENERATORS])
5833        self.assertListEqual(script, ['ALTER SEQUENCE EMP_NO_GEN RESTART WITH 145',
5834                                      'ALTER SEQUENCE CUST_NO_GEN RESTART WITH 1015'])
5835        script = s.get_metadata_ddl([sm.SCRIPT_TRIGGER_DEACTIVATIONS])
5836        if self.version == FB30:
5837            self.assertListEqual(script, ['ALTER TRIGGER SET_EMP_NO INACTIVE',
5838                                          'ALTER TRIGGER SAVE_SALARY_CHANGE INACTIVE',
5839                                          'ALTER TRIGGER SET_CUST_NO INACTIVE',
5840                                          'ALTER TRIGGER POST_NEW_ORDER INACTIVE',
5841                                          'ALTER TRIGGER TR_CONNECT INACTIVE',
5842                                          'ALTER TRIGGER TR_MULTI INACTIVE'])
5843        else:
5844            self.assertListEqual(script, ['ALTER TRIGGER SET_EMP_NO INACTIVE',
5845                                          'ALTER TRIGGER SAVE_SALARY_CHANGE INACTIVE',
5846                                          'ALTER TRIGGER SET_CUST_NO INACTIVE',
5847                                          'ALTER TRIGGER POST_NEW_ORDER INACTIVE',
5848                                          'ALTER TRIGGER TR_MULTI INACTIVE',
5849                                          'ALTER TRIGGER TR_CONNECT INACTIVE'])
5850        script = s.get_metadata_ddl([sm.SCRIPT_TRIGGER_ACTIVATIONS])
5851        if self.version == FB30:
5852            self.assertListEqual(script, ['ALTER TRIGGER SET_EMP_NO ACTIVE',
5853                                          'ALTER TRIGGER SAVE_SALARY_CHANGE ACTIVE',
5854                                          'ALTER TRIGGER SET_CUST_NO ACTIVE',
5855                                          'ALTER TRIGGER POST_NEW_ORDER ACTIVE',
5856                                          'ALTER TRIGGER TR_CONNECT ACTIVE',
5857                                          'ALTER TRIGGER TR_MULTI ACTIVE'])
5858        else:
5859            self.assertListEqual(script, ['ALTER TRIGGER SET_EMP_NO ACTIVE',
5860                                          'ALTER TRIGGER SAVE_SALARY_CHANGE ACTIVE',
5861                                          'ALTER TRIGGER SET_CUST_NO ACTIVE',
5862                                          'ALTER TRIGGER POST_NEW_ORDER ACTIVE',
5863                                          'ALTER TRIGGER TR_MULTI ACTIVE',
5864                                          'ALTER TRIGGER TR_CONNECT ACTIVE'])
5865
5866class TestMonitor(FDBTestBase):
5867    def setUp(self):
5868        super(TestMonitor, self).setUp()
5869        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
5870        self.con = fdb.connect(host=FBTEST_HOST, database=self.dbfile,
5871                               user=FBTEST_USER, password=FBTEST_PASSWORD)
5872    def tearDown(self):
5873        self.con.close()
5874    def testMonitorBindClose(self):
5875        if self.con.ods < fdb.ODS_FB_21:
5876            return
5877        s = fdb.monitor.Monitor()
5878        self.assertTrue(s.closed)
5879        s.bind(self.con)
5880        # properties
5881        self.assertEqual(s.db.name.upper(), self.dbfile.upper())
5882        self.assertFalse(s.db.read_only)
5883        self.assertFalse(s.closed)
5884        #
5885        s.close()
5886        self.assertTrue(s.closed)
5887        s.bind(self.con)
5888        self.assertFalse(s.closed)
5889        #
5890        s.bind(self.con)
5891        self.assertFalse(s.closed)
5892        #
5893        del s
5894    def testMonitor(self):
5895        if self.con.ods < fdb.ODS_FB_21:
5896            return
5897        c = self.con.cursor()
5898        sql = "select RDB$SET_CONTEXT('USER_SESSION','TESTVAR','TEST_VALUE') from rdb$database"
5899        c.execute(sql)
5900        c.fetchone()
5901        m = self.con.monitor
5902        m.refresh()
5903        self.assertIsNotNone(m.db)
5904        self.assertIsInstance(m.db, fdb.monitor.DatabaseInfo)
5905        self.assertGreater(len(m.attachments), 0)
5906        self.assertIsInstance(m.attachments[0], fdb.monitor.AttachmentInfo)
5907        self.assertGreater(len(m.transactions), 0)
5908        self.assertIsInstance(m.transactions[0], fdb.monitor.TransactionInfo)
5909        self.assertGreater(len(m.statements), 0)
5910        self.assertIsInstance(m.statements[0], fdb.monitor.StatementInfo)
5911        self.assertEqual(len(m.callstack), 0)
5912        self.assertGreater(len(m.iostats), 0)
5913        self.assertIsInstance(m.iostats[0], fdb.monitor.IOStatsInfo)
5914        if self.con.ods == fdb.ODS_FB_21:
5915            self.assertEqual(len(m.variables), 0)
5916        elif self.con.ods >= fdb.ODS_FB_25:
5917            self.assertGreater(len(m.variables), 0)
5918            self.assertIsInstance(m.variables[0], fdb.monitor.ContextVariableInfo)
5919        #
5920        att_id = m._con.db_info(fdb.isc_info_attachment_id)
5921        self.assertEqual(m.get_attachment(att_id).id, att_id)
5922        tra_id = m._con.trans_info(fdb.isc_info_tra_id)
5923        self.assertEqual(m.get_transaction(tra_id).id, tra_id)
5924        stmt_id = None
5925        for stmt in m.statements:
5926            if stmt.sql_text == sql:
5927                stmt_id = stmt.id
5928        self.assertEqual(m.get_statement(stmt_id).id, stmt_id)
5929        # m.get_call()
5930        self.assertIsInstance(m.this_attachment, fdb.monitor.AttachmentInfo)
5931        self.assertEqual(m.this_attachment.id,
5932                         self.con.db_info(fdb.isc_info_attachment_id))
5933        self.assertFalse(m.closed)
5934        #
5935        with self.assertRaises(fdb.ProgrammingError) as cm:
5936            m.close()
5937        self.assertTupleEqual(cm.exception.args,
5938                              ("Call to 'close' not allowed for embedded Monitor.",))
5939        with self.assertRaises(fdb.ProgrammingError) as cm:
5940            m.bind(self.con)
5941        self.assertTupleEqual(cm.exception.args,
5942                              ("Call to 'bind' not allowed for embedded Monitor.",))
5943    def testDatabaseInfo(self):
5944        if self.con.ods < fdb.ODS_FB_21:
5945            return
5946        m = self.con.monitor
5947        m.refresh()
5948        self.assertEqual(m.db.name.upper(), self.dbfile.upper())
5949        if self.con.ods < fdb.ODS_FB_30:
5950            self.assertEqual(m.db.page_size, 4096)
5951        else:
5952            self.assertEqual(m.db.page_size, 8192)
5953        if self.con.ods == fdb.ODS_FB_20:
5954            self.assertEqual(m.db.ods, 11.0)
5955        elif self.con.ods == fdb.ODS_FB_21:
5956            self.assertEqual(m.db.ods, 11.1)
5957        elif self.con.ods == fdb.ODS_FB_25:
5958            self.assertEqual(m.db.ods, 11.2)
5959        elif self.con.ods >= fdb.ODS_FB_30:
5960            self.assertEqual(m.db.ods, 12.0)
5961        self.assertIsInstance(m.db.oit, int)
5962        self.assertIsInstance(m.db.oat, int)
5963        self.assertIsInstance(m.db.ost, int)
5964        self.assertIsInstance(m.db.next_transaction, int)
5965        self.assertIsInstance(m.db.cache_size, int)
5966        self.assertEqual(m.db.sql_dialect, 3)
5967        self.assertEqual(m.db.shutdown_mode, fdb.monitor.SHUTDOWN_MODE_ONLINE)
5968        self.assertEqual(m.db.sweep_interval, 20000)
5969        self.assertFalse(m.db.read_only)
5970        self.assertTrue(m.db.forced_writes)
5971        self.assertTrue(m.db.reserve_space)
5972        self.assertIsInstance(m.db.created, datetime.datetime)
5973        self.assertIsInstance(m.db.pages, int)
5974        self.assertEqual(m.db.backup_state, fdb.monitor.BACKUP_STATE_NORMAL)
5975        if self.con.ods < fdb.ODS_FB_30:
5976            self.assertIsNone(m.db.crypt_page)
5977            self.assertIsNone(m.db.owner)
5978            self.assertIsNone(m.db.security_database)
5979        else:
5980            self.assertEqual(m.db.crypt_page, 0)
5981            self.assertEqual(m.db.owner, 'SYSDBA')
5982            self.assertEqual(m.db.security_database, 'Default')
5983        self.assertEqual(m.db.iostats.group, fdb.monitor.STAT_DATABASE)
5984        self.assertEqual(m.db.iostats.stat_id, m.db.stat_id)
5985        self.assertIsInstance(m.db.tablestats, dict)
5986        if self.con.ods < fdb.ODS_FB_30:
5987            self.assertEqual(len(m.db.tablestats), 0)
5988        else:
5989            self.assertGreater(len(m.db.tablestats), 0)
5990    def testAttachmentInfo(self):
5991        if self.con.ods < fdb.ODS_FB_21:
5992            return
5993        c = self.con.cursor()
5994        sql = "select RDB$SET_CONTEXT('USER_SESSION','TESTVAR','TEST_VALUE') from rdb$database"
5995        c.execute(sql)
5996        c.fetchone()
5997        m = self.con.monitor
5998        m.refresh()
5999        s = m.this_attachment
6000        #
6001        self.assertEqual(s.id, self.con.db_info(fdb.isc_info_attachment_id))
6002        self.assertIsInstance(s.server_pid, int)
6003        self.assertIn(s.state, [fdb.monitor.STATE_ACTIVE, fdb.monitor.STATE_IDLE])
6004        self.assertEqual(s.name.upper(), self.dbfile.upper())
6005        self.assertEqual(s.user, 'SYSDBA')
6006        self.assertEqual(s.role, 'NONE')
6007        if not FBTEST_HOST and self.con.engine_version >= 3.0:
6008            self.assertIsNone(s.remote_protocol)
6009            self.assertIsNone(s.remote_address)
6010            self.assertIsNone(s.remote_pid)
6011            self.assertIsNone(s.remote_process)
6012        else:
6013            self.assertIn(s.remote_protocol, ['XNET', 'TCPv4', 'TCPv6'])
6014            self.assertIsInstance(s.remote_address, str)
6015            self.assertIsInstance(s.remote_pid, int)
6016            self.assertIsInstance(s.remote_process, str)
6017        self.assertIsInstance(s.character_set, sm.CharacterSet)
6018        self.assertIsInstance(s.timestamp, datetime.datetime)
6019        self.assertIsInstance(s.transactions, list)
6020        if self.con.ods < fdb.ODS_FB_30:
6021            self.assertIsNone(s.auth_method)
6022            self.assertIsNone(s.client_version)
6023            self.assertIsNone(s.remote_version)
6024            self.assertIsNone(s.remote_os_user)
6025            self.assertIsNone(s.remote_host)
6026        else:
6027            self.assertIn(s.auth_method, ['Srp', 'Win_Sspi', 'Legacy_Auth'])
6028            self.assertIsInstance(s.client_version, str)
6029            self.assertEqual(s.remote_version, 'P15')  # Firebird 3.0.3, may fail with other versions
6030            self.assertIsInstance(s.remote_os_user, str)
6031            self.assertIsInstance(s.remote_host, str)
6032        for x in s.transactions:
6033            self.assertIsInstance(x, fdb.monitor.TransactionInfo)
6034        self.assertIsInstance(s.statements, list)
6035        for x in s.statements:
6036            self.assertIsInstance(x, fdb.monitor.StatementInfo)
6037        self.assertIsInstance(s.variables, list)
6038        if self.con.ods >= fdb.ODS_FB_25:
6039            self.assertGreater(len(s.variables), 0)
6040        else:
6041            self.assertEqual(len(s.variables), 0)
6042        for x in s.variables:
6043            self.assertIsInstance(x, fdb.monitor.ContextVariableInfo)
6044        self.assertEqual(s.iostats.group, fdb.monitor.STAT_ATTACHMENT)
6045        self.assertEqual(s.iostats.stat_id, s.stat_id)
6046        if self.con.ods < fdb.ODS_FB_30:
6047            self.assertEqual(len(m.db.tablestats), 0)
6048        else:
6049            self.assertGreater(len(m.db.tablestats), 0)
6050        #
6051        self.assertTrue(s.isactive())
6052        self.assertFalse(s.isidle())
6053        self.assertFalse(s.isinternal())
6054        self.assertTrue(s.isgcallowed())
6055    def testTransactionInfo(self):
6056        if self.con.ods < fdb.ODS_FB_21:
6057            return
6058        c = self.con.cursor()
6059        sql = "select RDB$SET_CONTEXT('USER_TRANSACTION','TESTVAR','TEST_VALUE') from rdb$database"
6060        c.execute(sql)
6061        c.fetchone()
6062        m = self.con.monitor
6063        m.refresh()
6064        s = m.this_attachment.transactions[0]
6065        #
6066        self.assertEqual(s.id, m._ic.transaction.trans_info(fdb.isc_info_tra_id))
6067        self.assertIs(s.attachment, m.this_attachment)
6068        self.assertIn(s.state, [fdb.monitor.STATE_ACTIVE, fdb.monitor.STATE_IDLE])
6069        self.assertIsInstance(s.timestamp, datetime.datetime)
6070        self.assertIsInstance(s.top, int)
6071        self.assertIsInstance(s.oldest, int)
6072        self.assertIsInstance(s.oldest_active, int)
6073        self.assertEqual(s.isolation_mode, fdb.monitor.ISOLATION_READ_COMMITTED_RV)
6074        self.assertEqual(s.lock_timeout, fdb.monitor.INFINITE_WAIT)
6075        self.assertIsInstance(s.statements, list)
6076        for x in s.statements:
6077            self.assertIsInstance(x, fdb.monitor.StatementInfo)
6078        self.assertIsInstance(s.variables, list)
6079        self.assertEqual(s.iostats.group, fdb.monitor.STAT_TRANSACTION)
6080        self.assertEqual(s.iostats.stat_id, s.stat_id)
6081        if self.con.ods < fdb.ODS_FB_30:
6082            self.assertEqual(len(m.db.tablestats), 0)
6083        else:
6084            self.assertGreater(len(m.db.tablestats), 0)
6085        #
6086        self.assertTrue(s.isactive())
6087        self.assertFalse(s.isidle())
6088        self.assertTrue(s.isreadonly())
6089        self.assertFalse(s.isautocommit())
6090        self.assertTrue(s.isautoundo())
6091        #
6092        s = m.get_transaction(c.transaction.trans_info(fdb.isc_info_tra_id))
6093        self.assertIsInstance(s.variables, list)
6094        if self.con.ods >= fdb.ODS_FB_25:
6095            self.assertGreater(len(s.variables), 0)
6096        else:
6097            self.assertEqual(len(s.variables), 0)
6098        for x in s.variables:
6099            self.assertIsInstance(x, fdb.monitor.ContextVariableInfo)
6100    def testStatementInfo(self):
6101        if self.con.ods < fdb.ODS_FB_21:
6102            return
6103        m = self.con.monitor
6104        m.refresh()
6105        s = m.this_attachment.statements[0]
6106        #
6107        self.assertIsInstance(s.id, int)
6108        self.assertIs(s.attachment, m.this_attachment)
6109        self.assertEqual(s.transaction.id, m.transactions[0].id)
6110        self.assertIn(s.state, [fdb.monitor.STATE_ACTIVE, fdb.monitor.STATE_IDLE])
6111        self.assertIsInstance(s.timestamp, datetime.datetime)
6112        self.assertEqual(s.sql_text, "select * from mon$database")
6113        if self.con.ods < fdb.ODS_FB_30:
6114            self.assertIsNone(s.plan)
6115        else:
6116            self.assertEqual(s.plan, 'Select Expression\n    -> Table "MON$DATABASE" Full Scan')
6117        # We have to use mocks for callstack
6118        stack = utils.ObjectList()
6119        stack.append(fdb.monitor.CallStackInfo(m,
6120                                               {'MON$CALL_ID':1, 'MON$STATEMENT_ID':s.id-1, 'MON$CALLER_ID':None,
6121                                                'MON$OBJECT_NAME':'TRIGGER_1', 'MON$OBJECT_TYPE':2, 'MON$TIMESTAMP':datetime.datetime.now(),
6122                                                'MON$SOURCE_LINE':1, 'MON$SOURCE_COLUMN':1, 'MON$STAT_ID':s.stat_id+100}))
6123        stack.append(fdb.monitor.CallStackInfo(m,
6124                                               {'MON$CALL_ID':2, 'MON$STATEMENT_ID':s.id, 'MON$CALLER_ID':None,
6125                                                'MON$OBJECT_NAME':'TRIGGER_2', 'MON$OBJECT_TYPE':2, 'MON$TIMESTAMP':datetime.datetime.now(),
6126                                                'MON$SOURCE_LINE':1, 'MON$SOURCE_COLUMN':1, 'MON$STAT_ID':s.stat_id+101}))
6127        stack.append(fdb.monitor.CallStackInfo(m,
6128                                               {'MON$CALL_ID':3, 'MON$STATEMENT_ID':s.id, 'MON$CALLER_ID':2,
6129                                                'MON$OBJECT_NAME':'PROC_1', 'MON$OBJECT_TYPE':5, 'MON$TIMESTAMP':datetime.datetime.now(),
6130                                                'MON$SOURCE_LINE':2, 'MON$SOURCE_COLUMN':2, 'MON$STAT_ID':s.stat_id+102}))
6131        stack.append(fdb.monitor.CallStackInfo(m,
6132                                               {'MON$CALL_ID':4, 'MON$STATEMENT_ID':s.id, 'MON$CALLER_ID':3,
6133                                                'MON$OBJECT_NAME':'PROC_2', 'MON$OBJECT_TYPE':5, 'MON$TIMESTAMP':datetime.datetime.now(),
6134                                                'MON$SOURCE_LINE':3, 'MON$SOURCE_COLUMN':3, 'MON$STAT_ID':s.stat_id+103}))
6135        stack.append(fdb.monitor.CallStackInfo(m,
6136                                               {'MON$CALL_ID':5, 'MON$STATEMENT_ID':s.id+1, 'MON$CALLER_ID':None,
6137                                                'MON$OBJECT_NAME':'PROC_3', 'MON$OBJECT_TYPE':5, 'MON$TIMESTAMP':datetime.datetime.now(),
6138                                                'MON$SOURCE_LINE':1, 'MON$SOURCE_COLUMN':1, 'MON$STAT_ID':s.stat_id+104}))
6139        m.__dict__['_Monitor__callstack'] = stack
6140        #
6141        self.assertListEqual(s.callstack, [stack[1], stack[2], stack[3]])
6142        self.assertEqual(s.iostats.group, fdb.monitor.STAT_STATEMENT)
6143        self.assertEqual(s.iostats.stat_id, s.stat_id)
6144        if self.con.ods < fdb.ODS_FB_30:
6145            self.assertEqual(len(m.db.tablestats), 0)
6146        else:
6147            self.assertGreater(len(m.db.tablestats), 0)
6148        #
6149        self.assertTrue(s.isactive())
6150        self.assertFalse(s.isidle())
6151    def testCallStackInfo(self):
6152        if self.con.ods < fdb.ODS_FB_21:
6153            return
6154        m = self.con.monitor
6155        m.refresh()
6156        stmt = m.this_attachment.statements[0]
6157        # We have to use mocks for callstack
6158        stack = utils.ObjectList()
6159        stack.append(fdb.monitor.CallStackInfo(m,
6160                                               {'MON$CALL_ID':1, 'MON$STATEMENT_ID':stmt.id-1, 'MON$CALLER_ID':None,
6161                                                'MON$OBJECT_NAME':'POST_NEW_ORDER', 'MON$OBJECT_TYPE':2, 'MON$TIMESTAMP':datetime.datetime.now(),
6162                                                'MON$SOURCE_LINE':1, 'MON$SOURCE_COLUMN':1, 'MON$STAT_ID':stmt.stat_id+100}))
6163        stack.append(fdb.monitor.CallStackInfo(m,
6164                                               {'MON$CALL_ID':2, 'MON$STATEMENT_ID':stmt.id, 'MON$CALLER_ID':None,
6165                                                'MON$OBJECT_NAME':'POST_NEW_ORDER', 'MON$OBJECT_TYPE':2, 'MON$TIMESTAMP':datetime.datetime.now(),
6166                                                'MON$SOURCE_LINE':1, 'MON$SOURCE_COLUMN':1, 'MON$STAT_ID':stmt.stat_id+101}))
6167        stack.append(fdb.monitor.CallStackInfo(m,
6168                                               {'MON$CALL_ID':3, 'MON$STATEMENT_ID':stmt.id, 'MON$CALLER_ID':2,
6169                                                'MON$OBJECT_NAME':'SHIP_ORDER', 'MON$OBJECT_TYPE':5, 'MON$TIMESTAMP':datetime.datetime.now(),
6170                                                'MON$SOURCE_LINE':2, 'MON$SOURCE_COLUMN':2, 'MON$STAT_ID':stmt.stat_id+102}))
6171        stack.append(fdb.monitor.CallStackInfo(m,
6172                                               {'MON$CALL_ID':4, 'MON$STATEMENT_ID':stmt.id, 'MON$CALLER_ID':3,
6173                                                'MON$OBJECT_NAME':'SUB_TOT_BUDGET', 'MON$OBJECT_TYPE':5, 'MON$TIMESTAMP':datetime.datetime.now(),
6174                                                'MON$SOURCE_LINE':3, 'MON$SOURCE_COLUMN':3, 'MON$STAT_ID':stmt.stat_id+103}))
6175        stack.append(fdb.monitor.CallStackInfo(m,
6176                                               {'MON$CALL_ID':5, 'MON$STATEMENT_ID':stmt.id+1, 'MON$CALLER_ID':None,
6177                                                'MON$OBJECT_NAME':'SUB_TOT_BUDGET', 'MON$OBJECT_TYPE':5, 'MON$TIMESTAMP':datetime.datetime.now(),
6178                                                'MON$SOURCE_LINE':1, 'MON$SOURCE_COLUMN':1, 'MON$STAT_ID':stmt.stat_id+104}))
6179        m.__dict__['_Monitor__callstack'] = stack
6180        data = m.iostats[0]._attributes
6181        data['MON$STAT_ID'] = stmt.stat_id+101
6182        data['MON$STAT_GROUP'] = fdb.monitor.STAT_CALL
6183        m.__dict__['_Monitor__iostats'] = utils.ObjectList(m.iostats)
6184        m.__dict__['_Monitor__iostats'].append(fdb.monitor.IOStatsInfo(m, data))
6185        #
6186        s = m.get_call(2)
6187        #
6188        self.assertEqual(s.id, 2)
6189        self.assertIs(s.statement, m.get_statement(stmt.id))
6190        self.assertIsNone(s.caller)
6191        self.assertIsInstance(s.dbobject, sm.Trigger)
6192        self.assertEqual(s.dbobject.name, 'POST_NEW_ORDER')
6193        self.assertIsInstance(s.timestamp, datetime.datetime)
6194        self.assertEqual(s.line, 1)
6195        self.assertEqual(s.column, 1)
6196        self.assertEqual(s.iostats.group, fdb.monitor.STAT_CALL)
6197        self.assertEqual(s.iostats.stat_id, s.stat_id)
6198        #
6199        x = m.get_call(3)
6200        self.assertIs(x.caller, s)
6201        self.assertIsInstance(x.dbobject, sm.Procedure)
6202        self.assertEqual(x.dbobject.name, 'SHIP_ORDER')
6203    def testIOStatsInfo(self):
6204        if self.con.ods < fdb.ODS_FB_21:
6205            return
6206        m = self.con.monitor
6207        m.refresh()
6208        #
6209        for io in m.iostats:
6210            self.assertIs(io, io.owner.iostats)
6211        #
6212        s = m.iostats[0]
6213        self.assertIsInstance(s.owner, fdb.monitor.DatabaseInfo)
6214        self.assertEqual(s.group, fdb.monitor.STAT_DATABASE)
6215        self.assertIsInstance(s.reads, int)
6216        self.assertIsInstance(s.writes, int)
6217        self.assertIsInstance(s.fetches, int)
6218        self.assertIsInstance(s.marks, int)
6219        self.assertIsInstance(s.seq_reads, int)
6220        self.assertIsInstance(s.idx_reads, int)
6221        self.assertIsInstance(s.inserts, int)
6222        self.assertIsInstance(s.updates, int)
6223        self.assertIsInstance(s.deletes, int)
6224        self.assertIsInstance(s.backouts, int)
6225        self.assertIsInstance(s.purges, int)
6226        self.assertIsInstance(s.expunges, int)
6227        if self.con.ods >= fdb.ODS_FB_30:
6228            self.assertIsInstance(s.locks, int)
6229            self.assertIsInstance(s.waits, int)
6230            self.assertIsInstance(s.conflits, int)
6231            self.assertIsInstance(s.backversion_reads, int)
6232            self.assertIsInstance(s.fragment_reads, int)
6233            self.assertIsInstance(s.repeated_reads, int)
6234        else:
6235            self.assertIsNone(s.locks)
6236            self.assertIsNone(s.waits)
6237            self.assertIsNone(s.conflits)
6238            self.assertIsNone(s.backversion_reads)
6239            self.assertIsNone(s.fragment_reads)
6240            self.assertIsNone(s.repeated_reads)
6241        if self.con.ods >= fdb.ODS_FB_25:
6242            self.assertIsInstance(s.memory_used, int)
6243            self.assertIsInstance(s.memory_allocated, int)
6244            self.assertIsInstance(s.max_memory_used, int)
6245            self.assertIsInstance(s.max_memory_allocated, int)
6246        else:
6247            self.assertIsNone(s.memory_used)
6248            self.assertIsNone(s.memory_allocated)
6249            self.assertIsNone(s.max_memory_used)
6250            self.assertIsNone(s.max_memory_allocated)
6251    def testContextVariableInfo(self):
6252        if self.con.ods <= fdb.ODS_FB_21:
6253            return
6254        c = self.con.cursor()
6255        sql = "select RDB$SET_CONTEXT('USER_SESSION','SVAR','TEST_VALUE') from rdb$database"
6256        c.execute(sql)
6257        c.fetchone()
6258        c = self.con.cursor()
6259        sql = "select RDB$SET_CONTEXT('USER_TRANSACTION','TVAR','TEST_VALUE') from rdb$database"
6260        c.execute(sql)
6261        c.fetchone()
6262        m = self.con.monitor
6263        m.refresh()
6264        #
6265        self.assertEqual(len(m.variables), 2)
6266        #
6267        s = m.variables[0]
6268        self.assertIs(s.attachment, m.this_attachment)
6269        self.assertIsNone(s.transaction)
6270        self.assertEqual(s.name, 'SVAR')
6271        self.assertEqual(s.value, 'TEST_VALUE')
6272        self.assertTrue(s.isattachmentvar())
6273        self.assertFalse(s.istransactionvar())
6274        #
6275        s = m.variables[1]
6276        self.assertIsNone(s.attachment)
6277        self.assertIs(s.transaction,
6278                      m.get_transaction(c.transaction.trans_info(fdb.isc_info_tra_id)))
6279        self.assertEqual(s.name, 'TVAR')
6280        self.assertEqual(s.value, 'TEST_VALUE')
6281        self.assertFalse(s.isattachmentvar())
6282        self.assertTrue(s.istransactionvar())
6283
6284
6285class TestConnectionWithSchema(FDBTestBase):
6286    def setUp(self):
6287        super(TestConnectionWithSchema, self).setUp()
6288        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
6289        #self.con = fdb.connect(dsn=self.dbfile,user=FBTEST_USER,password=FBTEST_PASSWORD)
6290    def tearDown(self):
6291        #self.con.close()
6292        pass
6293    def testConnectSchema(self):
6294        s = fdb.connect(host=FBTEST_HOST, database=self.dbfile, user=FBTEST_USER,
6295                        password=FBTEST_PASSWORD,
6296                        connection_class=fdb.ConnectionWithSchema)
6297        if s.ods < fdb.ODS_FB_30:
6298            self.assertEqual(len(s.tables), 15)
6299        else:
6300            self.assertEqual(len(s.tables), 16)
6301        self.assertEqual(s.get_table('JOB').name, 'JOB')
6302
6303
6304class TestHooks(FDBTestBase):
6305    def setUp(self):
6306        super(TestHooks, self).setUp()
6307        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
6308    def __hook_service_attached(self, con):
6309        self._svc = con
6310        return con
6311    def __hook_db_attached(self, con):
6312        self._db = con
6313        return con
6314    def __hook_db_attach_request_a(self, dsn, dpb):
6315        return None
6316    def __hook_db_attach_request_b(self, dsn, dpb):
6317        return self._hook_con
6318    def test_hook_db_attached(self):
6319        fdb.add_hook(fdb.HOOK_DATABASE_ATTACHED,
6320                     self.__hook_db_attached)
6321        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
6322            self.assertEqual(con, self._db)
6323        fdb.remove_hook(fdb.HOOK_DATABASE_ATTACHED,
6324                        self.__hook_db_attached)
6325    def test_hook_db_attach_request(self):
6326        self._hook_con = fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD)
6327        fdb.add_hook(fdb.HOOK_DATABASE_ATTACH_REQUEST,
6328                     self.__hook_db_attach_request_a)
6329        fdb.add_hook(fdb.HOOK_DATABASE_ATTACH_REQUEST,
6330                     self.__hook_db_attach_request_b)
6331        self.assertListEqual([self.__hook_db_attach_request_a,
6332                              self.__hook_db_attach_request_b],
6333                             fdb.get_hooks(fdb.HOOK_DATABASE_ATTACH_REQUEST))
6334        with fdb.connect(dsn=self.dbfile, user=FBTEST_USER, password=FBTEST_PASSWORD) as con:
6335            self.assertEqual(con, self._hook_con)
6336        self._hook_con.close()
6337        fdb.remove_hook(fdb.HOOK_DATABASE_ATTACH_REQUEST,
6338                        self.__hook_db_attach_request_a)
6339        fdb.remove_hook(fdb.HOOK_DATABASE_ATTACH_REQUEST,
6340                        self.__hook_db_attach_request_b)
6341    def test_hook_service_attached(self):
6342        fdb.add_hook(fdb.HOOK_SERVICE_ATTACHED,
6343                     self.__hook_service_attached)
6344        svc = fdb.services.connect(host=FBTEST_HOST, password=FBTEST_PASSWORD)
6345        self.assertEqual(svc, self._svc)
6346        svc.close()
6347        fdb.remove_hook(fdb.HOOK_SERVICE_ATTACHED,
6348                        self.__hook_service_attached)
6349
6350
6351class TestBugs(FDBTestBase):
6352    "Tests for bugs logged in tracker, URL pattern: http://tracker.firebirdsql.org/browse/PYFB-<number>"
6353    def setUp(self):
6354        super(TestBugs, self).setUp()
6355        self.dbfile = os.path.join(self.dbpath, 'fbbugs.fdb')
6356        if os.path.exists(self.dbfile):
6357            os.remove(self.dbfile)
6358        self.con = fdb.create_database(host=FBTEST_HOST, database=self.dbfile,
6359                                       user=FBTEST_USER, password=FBTEST_PASSWORD)
6360    def tearDown(self):
6361        self.con.drop_database()
6362        self.con.close()
6363    def test_pyfb_17(self):
6364        "(PYFB-17) NOT NULL constraint + Insert Trigger"
6365        create_table = """
6366        Create Table table1  (
6367            ID Integer,
6368            C1 Integer NOT Null
6369        );
6370        """
6371
6372        create_trigger = """CREATE TRIGGER BIU_Trigger FOR table1
6373        ACTIVE BEFORE INSERT OR UPDATE POSITION 0
6374        as
6375        begin
6376          if (new.C1 IS NULL) then
6377          begin
6378            new.C1 = 1;
6379          end
6380        end
6381        """
6382
6383        cur = self.con.cursor()
6384        cur.execute(create_table)
6385        cur.execute(create_trigger)
6386        self.con.commit()
6387        # PYFB-17: fails with fdb, passes with kinterbasdb
6388        cur.execute('insert into table1 (ID, C1) values(1, ?)', (None, ))
6389    def test_pyfb_22(self):
6390        "(PYFB-22) SELECT FROM VARCHAR COLUMN WITH TEXT LONGER THAN 128 CHARS RETURN EMPTY STRING"
6391        create_table = """
6392        CREATE TABLE FDBTEST (
6393            ID INTEGER,
6394            TEST80 VARCHAR(80),
6395            TEST128 VARCHAR(128),
6396            TEST255 VARCHAR(255),
6397            TEST1024 VARCHAR(1024),
6398            TESTCLOB BLOB SUB_TYPE 1 SEGMENT SIZE 255
6399        );
6400        """
6401        cur = self.con.cursor()
6402        cur.execute(create_table)
6403        self.con.commit()
6404        # test data
6405        data = ("1234567890" * 25) + "12345"
6406        for i in ibase.xrange(255):
6407            cur.execute("insert into fdbtest (id, test255) values (?, ?)",
6408                        (i, data[:i]))
6409        self.con.commit()
6410        # PYFB-22: fails with fdb, passes with kinterbasdb
6411        cur.execute("select test255 from fdbtest order by id")
6412        i = 0
6413        for row in cur:
6414            value = row[0]
6415            self.assertEqual(len(value), i)
6416            self.assertEqual(value, data[:i])
6417            i += 1
6418    def test_pyfb_25(self):
6419        "(PYFB-25) Trancate long text from VARCHAR(5000)"
6420        create_table = """
6421        CREATE TABLE FDBTEST2 (
6422            ID INTEGER,
6423            TEST5000 VARCHAR(5000)
6424        );
6425        """
6426        cur = self.con.cursor()
6427        cur.execute(create_table)
6428        self.con.commit()
6429        # test data
6430        data = "1234567890" * 500
6431        cur.execute("insert into fdbtest2 (id, test5000) values (?, ?)",
6432                    (1, data))
6433        self.con.commit()
6434        # PYFB-25: fails with fdb, passes with kinterbasdb
6435        cur.execute("select test5000 from fdbtest2")
6436        row = cur.fetchone()
6437        self.assertEqual(row[0], data)
6438    def test_pyfb_30(self):
6439        "(PYFB-30) BLOBs are truncated at first zero byte"
6440        create_table = """
6441        CREATE TABLE FDBTEST3 (
6442            ID INTEGER,
6443            T_BLOB BLOB sub_type BINARY
6444        );
6445        """
6446        cur = self.con.cursor()
6447        cur.execute(create_table)
6448        self.con.commit()
6449        # test data
6450        data_bytes = (1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
6451        blob_data = fdb.bs(data_bytes)
6452        cur.execute("insert into fdbtest3 (id, t_blob) values (?, ?)",
6453                    (1, blob_data))
6454        cur.execute("insert into fdbtest3 (id, t_blob) values (?, ?)",
6455                    (2, BytesIO(blob_data)))
6456        self.con.commit()
6457        # PYFB-XX: binary blob trucated at zero-byte
6458        cur.execute("select t_blob from fdbtest3 where id = 1")
6459        row = cur.fetchone()
6460        self.assertEqual(row[0], blob_data)
6461        cur.execute("select t_blob from fdbtest3 where id = 2")
6462        row = cur.fetchone()
6463        self.assertEqual(row[0], blob_data)
6464        p = cur.prep("select t_blob from fdbtest3 where id = 2")
6465        p.set_stream_blob('T_BLOB')
6466        cur.execute(p)
6467        blob_reader = cur.fetchone()[0]
6468        value = blob_reader.read()
6469        self.assertEqual(value, blob_data)
6470    def test_pyfb_34(self):
6471        "(PYFB-34) Server resources not released on PreparedStatement destruction"
6472        cur = self.con.cursor()
6473        cur.execute("select * from RDB$Relations")
6474        cur.fetchall()
6475        del cur
6476    def test_pyfb_35(self):
6477        "(PYFB-35) Call to fetch after a sql statement without a result should raise exception"
6478        create_table = """
6479        Create Table table1  (
6480            ID Integer,
6481            C1 Integer NOT Null
6482        );
6483        """
6484
6485        c = self.con.cursor()
6486        c.execute(create_table)
6487        self.con.commit()
6488        del c
6489
6490        cur = self.con.cursor()
6491        with self.assertRaises(fdb.DatabaseError) as cm:
6492            cur.fetchall()
6493        self.assertTupleEqual(cm.exception.args,
6494                              ("Cannot fetch from this cursor because it has not executed a statement.",))
6495
6496        cur.execute("select * from RDB$DATABASE")
6497        cur.fetchall()
6498        cur.execute("CREATE SEQUENCE TEST_SEQ_1")
6499        with self.assertRaises(fdb.DatabaseError) as cm:
6500            cur.fetchall()
6501        self.assertTupleEqual(cm.exception.args,
6502                              ("Attempt to fetch row of results after statement that does not produce result set.",))
6503
6504        cur.execute("insert into table1 (ID,C1) values (1,1) returning ID")
6505        row = cur.fetchall()
6506        self.assertListEqual(row, [(1,)])
6507
6508    def test_pyfb_44(self):
6509        "(PYFB-44) Inserting a datetime.date into a TIMESTAMP column does not work"
6510        self.con2 = fdb.connect(host=FBTEST_HOST, database=os.path.join(self.dbpath, self.FBTEST_DB),
6511                                user=FBTEST_USER, password=FBTEST_PASSWORD)
6512        try:
6513            cur = self.con2.cursor()
6514            now = datetime.datetime(2011, 11, 13, 15, 00, 1, 200)
6515            cur.execute('insert into T2 (C1,C8) values (?,?)', [3, now.date()])
6516            self.con2.commit()
6517            cur.execute('select C1,C8 from T2 where C1 = 3')
6518            rows = cur.fetchall()
6519            self.assertListEqual(rows,
6520                                 [(3, datetime.datetime(2011, 11, 13, 0, 0, 0, 0))])
6521        finally:
6522            self.con2.execute_immediate("delete from t2")
6523            self.con2.commit()
6524            self.con2.close()
6525
6526
6527
6528class TestTraceParse(FDBTestBase):
6529    def setUp(self):
6530        super(TestTraceParse, self).setUp()
6531        self.dbfile = os.path.join(self.dbpath, self.FBTEST_DB)
6532    def test_linesplit_iter(self):
6533        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6534        /home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6535        /opt/firebird/bin/isql:8723
6536
6537"""
6538        for line in linesplit_iter(trace_lines):
6539            self.output.write(line + '\n')
6540        self.assertEqual(self.output.getvalue(), trace_lines)
6541    def _check_events(self, trace_lines, output):
6542        parser = fdb.trace.TraceParser()
6543        for obj in parser.parse(linesplit_iter(trace_lines)):
6544            self.printout(str(obj))
6545        #print(self.output.getvalue())
6546        self.assertEqual(self.output.getvalue(), output, "Parsed events do not match expected ones")
6547    def test_trace_init(self):
6548        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) TRACE_INIT
6549        SESSION_1
6550
6551"""
6552        output = "EventTraceInit(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), session_name='SESSION_1')\n"
6553        self._check_events(trace_lines, output)
6554    def test_trace_suspend(self):
6555        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) TRACE_INIT
6556        SESSION_1
6557
6558--- Session 1 is suspended as its log is full ---
65592014-05-23T12:01:01.1420 (3720:0000000000EFD9E8) TRACE_INIT
6560	SESSION_1
6561
6562"""
6563        output = """EventTraceInit(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), session_name='SESSION_1')
6564EventTraceSuspend(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), session_name='SESSION_1')
6565EventTraceInit(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 12, 1, 1, 142000), session_name='SESSION_1')
6566"""
6567        self._check_events(trace_lines, output)
6568    def test_trace_finish(self):
6569        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) TRACE_INIT
6570        SESSION_1
6571
65722014-05-23T11:01:24.8080 (3720:0000000000EFD9E8) TRACE_FINI
6573	SESSION_1
6574
6575"""
6576        output = """EventTraceInit(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), session_name='SESSION_1')
6577EventTraceFinish(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 1, 24, 808000), session_name='SESSION_1')
6578"""
6579        self._check_events(trace_lines, output)
6580    def test_create_database(self):
6581        trace_lines = """2018-03-29T14:20:55.1180 (6290:0x7f9bb00bb978) CREATE_DATABASE
6582	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6583	/opt/firebird/bin/isql:8723
6584
6585"""
6586        output = """EventCreate(event_id=1, timestamp=datetime.datetime(2018, 3, 29, 14, 20, 55, 118000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6587"""
6588        self._check_events(trace_lines, output)
6589    def test_drop_database(self):
6590        trace_lines = """2018-03-29T14:20:55.1180 (6290:0x7f9bb00bb978) DROP_DATABASE
6591	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6592	/opt/firebird/bin/isql:8723
6593
6594"""
6595        output = """EventDrop(event_id=1, timestamp=datetime.datetime(2018, 3, 29, 14, 20, 55, 118000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6596"""
6597        self._check_events(trace_lines, output)
6598    def test_attach(self):
6599        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6600	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6601	/opt/firebird/bin/isql:8723
6602"""
6603        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6604"""
6605        self._check_events(trace_lines, output)
6606    def test_attach_failed(self):
6607        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) FAILED ATTACH_DATABASE
6608	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6609	/opt/firebird/bin/isql:8723
6610
6611"""
6612        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status='F', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6613"""
6614        self._check_events(trace_lines, output)
6615    def test_unauthorized_attach(self):
6616        trace_lines = """2014-09-24T14:46:15.0350 (2453:0x7fed02a04910) UNAUTHORIZED ATTACH_DATABASE
6617	/home/employee.fdb (ATT_0, sysdba, NONE, TCPv4:127.0.0.1)
6618	/opt/firebird/bin/isql:8723
6619
6620"""
6621        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 9, 24, 14, 46, 15, 35000), status='U', attachment_id=0, database='/home/employee.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='sysdba', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6622"""
6623        self._check_events(trace_lines, output)
6624    def test_detach(self):
6625        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6626	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6627	/opt/firebird/bin/isql:8723
6628
66292014-05-23T11:01:24.8080 (3720:0000000000EFD9E8) DETACH_DATABASE
6630	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6631	/opt/firebird/bin/isql:8723
6632
6633"""
6634        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6635EventDetach(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 1, 24, 808000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6636"""
6637        self._check_events(trace_lines, output)
6638    def test_detach_without_attach(self):
6639        trace_lines = """2014-05-23T11:01:24.8080 (3720:0000000000EFD9E8) DETACH_DATABASE
6640	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6641	/opt/firebird/bin/isql:8723
6642
6643"""
6644        output = """EventDetach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 1, 24, 808000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6645"""
6646        self._check_events(trace_lines, output)
6647    def test_start_transaction(self):
6648        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6649	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6650	/opt/firebird/bin/isql:8723
6651
66522014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6653	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6654	/opt/firebird/bin/isql:8723
6655		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6656
6657"""
6658        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6659EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6660"""
6661        self._check_events(trace_lines, output)
6662    def test_start_transaction_without_attachment(self):
6663        trace_lines = """2014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6664	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6665	/opt/firebird/bin/isql:8723
6666		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6667
6668"""
6669        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6670EventTransactionStart(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6671"""
6672        self._check_events(trace_lines, output)
6673    def test_commit(self):
6674        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6675	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6676	/opt/firebird/bin/isql:8723
6677
66782014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6679	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6680	/opt/firebird/bin/isql:8723
6681		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6682
66832014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) COMMIT_TRANSACTION
6684	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6685	/opt/firebird/bin/isql:8723
6686		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6687      0 ms, 1 read(s), 1 write(s), 1 fetch(es), 1 mark(s)
6688
6689"""
6690        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6691EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6692EventCommit(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=1, writes=1, fetches=1, marks=1)
6693"""
6694        self._check_events(trace_lines, output)
6695    def test_commit_no_performance(self):
6696        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6697	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6698	/opt/firebird/bin/isql:8723
6699
67002014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6701	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6702	/opt/firebird/bin/isql:8723
6703		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6704
67052014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) COMMIT_TRANSACTION
6706	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6707	/opt/firebird/bin/isql:8723
6708		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6709
6710"""
6711        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6712EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6713EventCommit(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=None, reads=None, writes=None, fetches=None, marks=None)
6714"""
6715        self._check_events(trace_lines, output)
6716    def test_commit_without_attachment_and_start(self):
6717        trace_lines = """2014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) COMMIT_TRANSACTION
6718	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6719	/opt/firebird/bin/isql:8723
6720		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6721      0 ms, 1 read(s), 1 write(s), 1 fetch(es), 1 mark(s)
6722
6723"""
6724        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6725EventCommit(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=1, writes=1, fetches=1, marks=1)
6726"""
6727        self._check_events(trace_lines, output)
6728    def test_rollback(self):
6729        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6730	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6731	/opt/firebird/bin/isql:8723
6732
67332014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6734	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6735	/opt/firebird/bin/isql:8723
6736		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6737
67382014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) ROLLBACK_TRANSACTION
6739	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6740	/opt/firebird/bin/isql:8723
6741		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
67420 ms
6743
6744"""
6745        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6746EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6747EventRollback(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=None, writes=None, fetches=None, marks=None)
6748"""
6749        self._check_events(trace_lines, output)
6750    def test_rollback_no_performance(self):
6751        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6752	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6753	/opt/firebird/bin/isql:8723
6754
67552014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6756	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6757	/opt/firebird/bin/isql:8723
6758		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6759
67602014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) ROLLBACK_TRANSACTION
6761	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6762	/opt/firebird/bin/isql:8723
6763		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6764
6765"""
6766        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6767EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6768EventRollback(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=None, reads=None, writes=None, fetches=None, marks=None)
6769"""
6770        self._check_events(trace_lines, output)
6771    def test_rollback_attachment_and_start(self):
6772        trace_lines = """2014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) ROLLBACK_TRANSACTION
6773	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6774	/opt/firebird/bin/isql:8723
6775		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
67760 ms
6777
6778"""
6779        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6780EventRollback(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=None, writes=None, fetches=None, marks=None)
6781"""
6782        self._check_events(trace_lines, output)
6783    def test_commit_retaining(self):
6784        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6785	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6786	/opt/firebird/bin/isql:8723
6787
67882014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6789	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6790	/opt/firebird/bin/isql:8723
6791		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6792
67932014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) COMMIT_RETAINING
6794	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6795	/opt/firebird/bin/isql:8723
6796		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6797      0 ms, 1 read(s), 1 write(s), 1 fetch(es), 1 mark(s)
6798
6799"""
6800        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6801EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6802EventCommitRetaining(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=1, writes=1, fetches=1, marks=1)
6803"""
6804        self._check_events(trace_lines, output)
6805    def test_commit_retaining_no_performance(self):
6806        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6807	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6808	/opt/firebird/bin/isql:8723
6809
68102014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6811	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6812	/opt/firebird/bin/isql:8723
6813		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6814
68152014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) COMMIT_RETAINING
6816	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6817	/opt/firebird/bin/isql:8723
6818		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6819
6820"""
6821        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6822EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6823EventCommitRetaining(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=None, reads=None, writes=None, fetches=None, marks=None)
6824"""
6825        self._check_events(trace_lines, output)
6826    def test_commit_retaining_without_attachment_and_start(self):
6827        trace_lines = """2014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) COMMIT_RETAINING
6828	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6829	/opt/firebird/bin/isql:8723
6830		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6831      0 ms, 1 read(s), 1 write(s), 1 fetch(es), 1 mark(s)
6832
6833"""
6834        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6835EventCommitRetaining(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=1, writes=1, fetches=1, marks=1)
6836"""
6837        self._check_events(trace_lines, output)
6838    def test_rollback_retaining(self):
6839        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6840	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6841	/opt/firebird/bin/isql:8723
6842
68432014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6844	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6845	/opt/firebird/bin/isql:8723
6846		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6847
68482014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) ROLLBACK_RETAINING
6849	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6850	/opt/firebird/bin/isql:8723
6851		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
68520 ms
6853
6854"""
6855        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6856EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6857EventRollbackRetaining(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=None, writes=None, fetches=None, marks=None)
6858"""
6859        self._check_events(trace_lines, output)
6860    def test_rollback_retaining_no_performance(self):
6861        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6862	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6863	/opt/firebird/bin/isql:8723
6864
68652014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6866	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6867	/opt/firebird/bin/isql:8723
6868		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6869
68702014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) ROLLBACK_RETAINING
6871	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6872	/opt/firebird/bin/isql:8723
6873		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6874
6875"""
6876        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6877EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6878EventRollbackRetaining(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=None, reads=None, writes=None, fetches=None, marks=None)
6879"""
6880        self._check_events(trace_lines, output)
6881    def test_rollback_retaining_without_attachment_and_start(self):
6882        trace_lines = """2014-05-23T11:00:29.9570 (3720:0000000000EFD9E8) ROLLBACK_RETAINING
6883	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6884	/opt/firebird/bin/isql:8723
6885		(TRA_1568, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
68860 ms
6887
6888"""
6889        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6890EventRollbackRetaining(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 29, 957000), status=' ', attachment_id=8, transaction_id=1568, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'], run_time=0, reads=None, writes=None, fetches=None, marks=None)
6891"""
6892        self._check_events(trace_lines, output)
6893    def test_prepare_statement(self):
6894        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6895	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6896	/opt/firebird/bin/isql:8723
6897
68982014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6899	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6900	/opt/firebird/bin/isql:8723
6901		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6902
69032014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) PREPARE_STATEMENT
6904	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6905	/opt/firebird/bin/isql:8723
6906		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6907
6908Statement 181:
6909-------------------------------------------------------------------------------
6910SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE
6911^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6912PLAN (RDB$DATABASE NATURAL)
6913     13 ms
6914
6915"""
6916        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6917EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6918SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
6919EventPrepareStatement(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, prepare_time=13)
6920"""
6921        self._check_events(trace_lines, output)
6922    def test_prepare_statement_no_plan(self):
6923        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6924	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6925	/opt/firebird/bin/isql:8723
6926
69272014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6928	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6929	/opt/firebird/bin/isql:8723
6930		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6931
69322014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) PREPARE_STATEMENT
6933	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6934	/opt/firebird/bin/isql:8723
6935		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6936
6937Statement 181:
6938-------------------------------------------------------------------------------
6939SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE
6940     13 ms
6941
6942"""
6943        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6944EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6945SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE', plan=None)
6946EventPrepareStatement(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, prepare_time=13)
6947"""
6948        self._check_events(trace_lines, output)
6949    def test_prepare_statement_no_attachment(self):
6950        trace_lines = """2014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
6951	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6952	/opt/firebird/bin/isql:8723
6953		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6954
69552014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) PREPARE_STATEMENT
6956	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6957	/opt/firebird/bin/isql:8723
6958		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6959
6960Statement 181:
6961-------------------------------------------------------------------------------
6962SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE
6963^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6964PLAN (RDB$DATABASE NATURAL)
6965     13 ms
6966
6967"""
6968        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6969EventTransactionStart(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6970SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
6971EventPrepareStatement(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, prepare_time=13)
6972"""
6973        self._check_events(trace_lines, output)
6974    def test_prepare_statement_no_transaction(self):
6975        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
6976	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6977	/opt/firebird/bin/isql:8723
6978
69792014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) PREPARE_STATEMENT
6980	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
6981	/opt/firebird/bin/isql:8723
6982		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
6983
6984Statement 181:
6985-------------------------------------------------------------------------------
6986SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE
6987^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6988PLAN (RDB$DATABASE NATURAL)
6989     13 ms
6990
6991"""
6992        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
6993TransactionInfo(attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
6994SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
6995EventPrepareStatement(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, prepare_time=13)
6996"""
6997        self._check_events(trace_lines, output)
6998    def test_prepare_statement_no_attachment_no_transaction(self):
6999        trace_lines = """2014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) PREPARE_STATEMENT
7000	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7001	/opt/firebird/bin/isql:8723
7002		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7003
7004Statement 181:
7005-------------------------------------------------------------------------------
7006SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE
7007^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7008PLAN (RDB$DATABASE NATURAL)
7009     13 ms
7010
7011"""
7012        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7013TransactionInfo(attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7014SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
7015EventPrepareStatement(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, prepare_time=13)
7016"""
7017        self._check_events(trace_lines, output)
7018    def test_statement_start(self):
7019        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7020	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7021	/opt/firebird/bin/isql:8723
7022
70232014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7024	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7025	/opt/firebird/bin/isql:8723
7026		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7027
70282014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_START
7029	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7030	/opt/firebird/bin/isql:8723
7031		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7032
7033Statement 166353:
7034-------------------------------------------------------------------------------
7035UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7036
7037^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7038PLAN (TABLE_A INDEX (TABLE_A_PK))
7039
7040param0 = timestamp, "2017-11-09T11:23:52.1570"
7041param1 = integer, "100012829"
7042param2 = integer, "<NULL>"
7043param3 = varchar(20), "2810090906551"
7044param4 = integer, "4199300"
7045"""
7046        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7047EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7048ParamInfo(par_id=1, params=[('timestamp', datetime.datetime(2017, 11, 9, 11, 23, 52, 157000)), ('integer', 100012829), ('integer', None), ('varchar(20)', '2810090906551'), ('integer', 4199300)])
7049SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan='PLAN (TABLE_A INDEX (TABLE_A_PK))')
7050EventStatementStart(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1, param_id=1)
7051"""
7052        self._check_events(trace_lines, output)
7053    def test_statement_start_no_plan(self):
7054        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7055	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7056	/opt/firebird/bin/isql:8723
7057
70582014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7059	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7060	/opt/firebird/bin/isql:8723
7061		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7062
70632014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_START
7064	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7065	/opt/firebird/bin/isql:8723
7066		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7067
7068Statement 166353:
7069-------------------------------------------------------------------------------
7070UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7071param0 = timestamp, "2017-11-09T11:23:52.1570"
7072param1 = integer, "100012829"
7073param2 = integer, "<NULL>"
7074param3 = varchar(20), "2810090906551"
7075param4 = integer, "4199300"
7076"""
7077        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7078EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7079ParamInfo(par_id=1, params=[('timestamp', datetime.datetime(2017, 11, 9, 11, 23, 52, 157000)), ('integer', 100012829), ('integer', None), ('varchar(20)', '2810090906551'), ('integer', 4199300)])
7080SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan=None)
7081EventStatementStart(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1, param_id=1)
7082"""
7083        self._check_events(trace_lines, output)
7084    def test_statement_start_no_attachment(self):
7085        trace_lines = """2014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7086	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7087	/opt/firebird/bin/isql:8723
7088		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7089
70902014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_START
7091	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7092	/opt/firebird/bin/isql:8723
7093		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7094
7095Statement 166353:
7096-------------------------------------------------------------------------------
7097UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7098
7099^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7100PLAN (TABLE_A INDEX (TABLE_A_PK))
7101
7102param0 = timestamp, "2017-11-09T11:23:52.1570"
7103param1 = integer, "100012829"
7104param2 = integer, "<NULL>"
7105param3 = varchar(20), "2810090906551"
7106param4 = integer, "4199300"
7107"""
7108        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7109EventTransactionStart(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7110ParamInfo(par_id=1, params=[('timestamp', datetime.datetime(2017, 11, 9, 11, 23, 52, 157000)), ('integer', 100012829), ('integer', None), ('varchar(20)', '2810090906551'), ('integer', 4199300)])
7111SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan='PLAN (TABLE_A INDEX (TABLE_A_PK))')
7112EventStatementStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1, param_id=1)
7113"""
7114        self._check_events(trace_lines, output)
7115    def test_statement_start_no_transaction(self):
7116        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7117	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7118	/opt/firebird/bin/isql:8723
7119
71202014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_START
7121	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7122	/opt/firebird/bin/isql:8723
7123		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7124
7125Statement 166353:
7126-------------------------------------------------------------------------------
7127UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7128
7129^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7130PLAN (TABLE_A INDEX (TABLE_A_PK))
7131
7132param0 = timestamp, "2017-11-09T11:23:52.1570"
7133param1 = integer, "100012829"
7134param2 = integer, "<NULL>"
7135param3 = varchar(20), "2810090906551"
7136param4 = integer, "4199300"
7137"""
7138        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7139TransactionInfo(attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7140ParamInfo(par_id=1, params=[('timestamp', datetime.datetime(2017, 11, 9, 11, 23, 52, 157000)), ('integer', 100012829), ('integer', None), ('varchar(20)', '2810090906551'), ('integer', 4199300)])
7141SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan='PLAN (TABLE_A INDEX (TABLE_A_PK))')
7142EventStatementStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1, param_id=1)
7143"""
7144        self._check_events(trace_lines, output)
7145    def test_statement_start_no_attachment_no_transaction(self):
7146        trace_lines = """2014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_START
7147	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7148	/opt/firebird/bin/isql:8723
7149		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7150
7151Statement 166353:
7152-------------------------------------------------------------------------------
7153UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7154
7155^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7156PLAN (TABLE_A INDEX (TABLE_A_PK))
7157
7158param0 = timestamp, "2017-11-09T11:23:52.1570"
7159param1 = integer, "100012829"
7160param2 = integer, "<NULL>"
7161param3 = varchar(20), "2810090906551"
7162param4 = integer, "4199300"
7163"""
7164        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7165TransactionInfo(attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7166ParamInfo(par_id=1, params=[('timestamp', datetime.datetime(2017, 11, 9, 11, 23, 52, 157000)), ('integer', 100012829), ('integer', None), ('varchar(20)', '2810090906551'), ('integer', 4199300)])
7167SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan='PLAN (TABLE_A INDEX (TABLE_A_PK))')
7168EventStatementStart(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1, param_id=1)
7169"""
7170        self._check_events(trace_lines, output)
7171    def test_statement_finish(self):
7172        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7173	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7174	/opt/firebird/bin/isql:8723
7175
71762014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7177	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7178	/opt/firebird/bin/isql:8723
7179		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7180
71812014-05-23T11:00:45.5420 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_FINISH
7182	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7183	/opt/firebird/bin/isql:8723
7184		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7185
7186Statement 181:
7187-------------------------------------------------------------------------------
7188SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE
7189^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7190PLAN (RDB$DATABASE NATURAL)
71911 records fetched
7192      0 ms, 2 read(s), 14 fetch(es), 1 mark(s)
7193
7194Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7195***************************************************************************************************************
7196RDB$DATABASE                            1
7197RDB$CHARACTER_SETS                                1
7198RDB$COLLATIONS                                    1
7199"""
7200        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7201EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7202SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
7203EventStatementFinish(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 542000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, param_id=None, records=1, run_time=0, reads=2, writes=None, fetches=14, marks=1, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$CHARACTER_SETS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$COLLATIONS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7204"""
7205        self._check_events(trace_lines, output)
7206    def test_statement_finish_no_plan(self):
7207        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7208	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7209	/opt/firebird/bin/isql:8723
7210
72112014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7212	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7213	/opt/firebird/bin/isql:8723
7214		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7215
72162014-05-23T11:00:45.5420 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_FINISH
7217	/home/employee.fdb (ATT_8, EUROFLOW:NONE, ISO88591, TCPv4:192.168.1.5)
7218	/opt/firebird/bin/isql:8723
7219		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7220
7221Statement 181:
7222-------------------------------------------------------------------------------
7223SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE
72241 records fetched
7225      0 ms, 2 read(s), 14 fetch(es), 1 mark(s)
7226
7227Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7228***************************************************************************************************************
7229RDB$DATABASE                            1
7230RDB$CHARACTER_SETS                                1
7231RDB$COLLATIONS                                    1
7232"""
7233        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7234EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7235SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE', plan=None)
7236EventStatementFinish(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 542000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, param_id=None, records=1, run_time=0, reads=2, writes=None, fetches=14, marks=1, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$CHARACTER_SETS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$COLLATIONS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7237"""
7238        self._check_events(trace_lines, output)
7239    def test_statement_finish_no_attachment(self):
7240        trace_lines = """2014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7241	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7242	/opt/firebird/bin/isql:8723
7243		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7244
72452014-05-23T11:00:45.5420 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_FINISH
7246	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7247	/opt/firebird/bin/isql:8723
7248		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7249
7250Statement 181:
7251-------------------------------------------------------------------------------
7252SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE
7253^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7254PLAN (RDB$DATABASE NATURAL)
72551 records fetched
7256      0 ms, 2 read(s), 14 fetch(es), 1 mark(s)
7257
7258Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7259***************************************************************************************************************
7260RDB$DATABASE                            1
7261RDB$CHARACTER_SETS                                1
7262RDB$COLLATIONS                                    1
7263"""
7264        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7265EventTransactionStart(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7266SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
7267EventStatementFinish(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 542000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, param_id=None, records=1, run_time=0, reads=2, writes=None, fetches=14, marks=1, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$CHARACTER_SETS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$COLLATIONS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7268"""
7269        self._check_events(trace_lines, output)
7270    def test_statement_finish_no_transaction(self):
7271        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7272	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7273	/opt/firebird/bin/isql:8723
7274
72752014-05-23T11:00:45.5420 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_FINISH
7276	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7277	/opt/firebird/bin/isql:8723
7278		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7279
7280Statement 181:
7281-------------------------------------------------------------------------------
7282SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE
7283^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7284PLAN (RDB$DATABASE NATURAL)
72851 records fetched
7286      0 ms, 2 read(s), 14 fetch(es), 1 mark(s)
7287
7288Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7289***************************************************************************************************************
7290RDB$DATABASE                            1
7291RDB$CHARACTER_SETS                                1
7292RDB$COLLATIONS                                    1
7293"""
7294        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7295TransactionInfo(attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7296SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
7297EventStatementFinish(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 542000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, param_id=None, records=1, run_time=0, reads=2, writes=None, fetches=14, marks=1, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$CHARACTER_SETS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$COLLATIONS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7298"""
7299        self._check_events(trace_lines, output)
7300    def test_statement_finish_no_attachment_no_transaction(self):
7301        trace_lines = """2014-05-23T11:00:45.5420 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_FINISH
7302	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7303	/opt/firebird/bin/isql:8723
7304		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7305
7306Statement 181:
7307-------------------------------------------------------------------------------
7308SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE
7309^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7310PLAN (RDB$DATABASE NATURAL)
73111 records fetched
7312      0 ms, 2 read(s), 14 fetch(es), 1 mark(s)
7313
7314Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7315***************************************************************************************************************
7316RDB$DATABASE                            1
7317RDB$CHARACTER_SETS                                1
7318RDB$COLLATIONS                                    1
7319"""
7320        output = """AttachmentInfo(attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7321TransactionInfo(attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7322SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
7323EventStatementFinish(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 542000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, param_id=None, records=1, run_time=0, reads=2, writes=None, fetches=14, marks=1, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$CHARACTER_SETS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$COLLATIONS', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7324"""
7325        self._check_events(trace_lines, output)
7326    def test_statement_finish_no_performance(self):
7327        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7328	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7329	/opt/firebird/bin/isql:8723
7330
73312014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7332	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7333	/opt/firebird/bin/isql:8723
7334		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7335
73362014-05-23T11:00:45.5420 (3720:0000000000EFD9E8) EXECUTE_STATEMENT_FINISH
7337	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7338	/opt/firebird/bin/isql:8723
7339		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7340
7341Statement 181:
7342-------------------------------------------------------------------------------
7343SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE
7344^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7345PLAN (RDB$DATABASE NATURAL)
7346"""
7347        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7348EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7349SQLInfo(sql_id=1, sql='SELECT GEN_ID(GEN_NUM, 1) NUMS FROM RDB$DATABASE', plan='PLAN (RDB$DATABASE NATURAL)')
7350EventStatementFinish(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 542000), status=' ', attachment_id=8, transaction_id=1570, statement_id=181, sql_id=1, param_id=None, records=None, run_time=None, reads=None, writes=None, fetches=None, marks=None, access=None)
7351"""
7352        self._check_events(trace_lines, output)
7353    def test_statement_free(self):
7354        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7355	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7356	/opt/firebird/bin/isql:8723
7357
73582014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7359	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7360	/opt/firebird/bin/isql:8723
7361		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7362
73632014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) FREE_STATEMENT
7364	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7365	/opt/firebird/bin/isql:8723
7366		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7367
7368Statement 166353:
7369-------------------------------------------------------------------------------
7370UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7371^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7372PLAN (TABLE_A INDEX (TABLE_A_PK))
7373"""
7374        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7375EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7376SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan='PLAN (TABLE_A INDEX (TABLE_A_PK))')
7377EventFreeStatement(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1)
7378"""
7379        self._check_events(trace_lines, output)
7380    def test_close_cursor(self):
7381        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7382	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7383	/opt/firebird/bin/isql:8723
7384
73852014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7386	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7387	/opt/firebird/bin/isql:8723
7388		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7389
73902014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) CLOSE_CURSOR
7391	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7392	/opt/firebird/bin/isql:8723
7393		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7394
7395Statement 166353:
7396-------------------------------------------------------------------------------
7397UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?
7398^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7399PLAN (TABLE_A INDEX (TABLE_A_PK))
7400"""
7401        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7402EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7403SQLInfo(sql_id=1, sql='UPDATE TABLE_A SET VAL_1=?, VAL_2=?, VAL_3=?, VAL_4=? WHERE ID_EX=?', plan='PLAN (TABLE_A INDEX (TABLE_A_PK))')
7404EventCloseCursor(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), attachment_id=8, transaction_id=1570, statement_id=166353, sql_id=1)
7405"""
7406        self._check_events(trace_lines, output)
7407    def test_trigger_start(self):
7408        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7409	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7410	/opt/firebird/bin/isql:8723
7411
74122014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7413	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7414	/opt/firebird/bin/isql:8723
7415		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7416
74172014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_TRIGGER_START
7418	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7419	/opt/firebird/bin/isql:8723
7420		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7421        BI_TABLE_A FOR TABLE_A (BEFORE INSERT)
7422"""
7423        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7424EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7425EventTriggerStart(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, trigger='BI_TABLE_A', table='TABLE_A', event='BEFORE INSERT')
7426"""
7427        self._check_events(trace_lines, output)
7428    def test_trigger_finish(self):
7429        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7430	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7431	/opt/firebird/bin/isql:8723
7432
74332014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7434	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7435	/opt/firebird/bin/isql:8723
7436		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7437
74382014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_TRIGGER_FINISH
7439	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7440	/opt/firebird/bin/isql:8723
7441		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7442	AIU_TABLE_A FOR TABLE_A (AFTER INSERT)
7443   1118 ms, 681 read(s), 80 write(s), 1426 fetch(es), 80 mark(s)
7444
7445Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7446***************************************************************************************************************
7447RDB$DATABASE                            1
7448RDB$INDICES                                     107
7449RDB$RELATIONS                                    10
7450RDB$FORMATS                                       6
7451RDB$RELATION_CONSTRAINTS                         20
7452TABLE_A                                                              1
7453TABLE_B                                           2
7454TABLE_C                                           1
7455TABLE_D                                                              1
7456TABLE_E                                           3
7457TABLE_F                                          25
7458"""
7459        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7460EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7461EventTriggerFinish(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, trigger='AIU_TABLE_A', table='TABLE_A', event='AFTER INSERT', run_time=1118, reads=681, writes=80, fetches=1426, marks=80, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$INDICES', natural=0, index=107, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$RELATIONS', natural=0, index=10, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$FORMATS', natural=0, index=6, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='RDB$RELATION_CONSTRAINTS', natural=0, index=20, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_A', natural=0, index=0, update=0, insert=1, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_B', natural=0, index=2, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_C', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_D', natural=0, index=0, update=0, insert=1, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_E', natural=0, index=3, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_F', natural=0, index=25, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7462"""
7463        self._check_events(trace_lines, output)
7464    def test_procedure_start(self):
7465        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7466	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7467	/opt/firebird/bin/isql:8723
7468
74692014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7470	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7471	/opt/firebird/bin/isql:8723
7472		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7473
74742014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_PROCEDURE_START
7475	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7476	/opt/firebird/bin/isql:8723
7477		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7478
7479Procedure PROC_A:
7480param0 = varchar(50), "758749"
7481param1 = varchar(10), "XXX"
7482"""
7483        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7484EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7485ParamInfo(par_id=1, params=[('varchar(50)', '758749'), ('varchar(10)', 'XXX')])
7486EventProcedureStart(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, procedure='PROC_A', param_id=1)
7487"""
7488        self._check_events(trace_lines, output)
7489    def test_procedure_finish(self):
7490        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7491	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7492	/opt/firebird/bin/isql:8723
7493
74942014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7495	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7496	/opt/firebird/bin/isql:8723
7497		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7498
74992014-05-23T11:00:45.5260 (3720:0000000000EFD9E8) EXECUTE_PROCEDURE_FINISH
7500	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7501	/opt/firebird/bin/isql:8723
7502		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7503
7504Procedure PROC_A:
7505param0 = varchar(10), "XXX"
7506param1 = double precision, "313204"
7507param2 = double precision, "1"
7508param3 = varchar(20), "50031"
7509param4 = varchar(20), "GGG(1.25)"
7510param5 = varchar(10), "PP100X120"
7511param6 = varchar(20), "<NULL>"
7512param7 = double precision, "3.33333333333333"
7513param8 = double precision, "45"
7514param9 = integer, "3"
7515param10 = integer, "<NULL>"
7516param11 = double precision, "1"
7517param12 = integer, "0"
7518
7519      0 ms, 14 read(s), 14 fetch(es)
7520
7521Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7522***************************************************************************************************************
7523TABLE_A                                           1
7524TABLE_B                                           1
7525"""
7526        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7527EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7528ParamInfo(par_id=1, params=[('varchar(10)', 'XXX'), ('double precision', Decimal('313204')), ('double precision', Decimal('1')), ('varchar(20)', '50031'), ('varchar(20)', 'GGG(1.25)'), ('varchar(10)', 'PP100X120'), ('varchar(20)', None), ('double precision', Decimal('3.33333333333333')), ('double precision', Decimal('45')), ('integer', 3), ('integer', None), ('double precision', Decimal('1')), ('integer', 0)])
7529EventProcedureFinish(event_id=3, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 45, 526000), status=' ', attachment_id=8, transaction_id=1570, procedure='PROC_A', param_id=1, run_time=0, reads=14, writes=None, fetches=14, marks=None, access=[AccessTuple(table='TABLE_A', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0), AccessTuple(table='TABLE_B', natural=0, index=1, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
7530"""
7531        self._check_events(trace_lines, output)
7532    def test_service_attach(self):
7533        trace_lines = """2017-11-13T11:49:51.3110 (2500:0000000026C3C858) ATTACH_SERVICE
7534	service_mgr, (Service 0000000019993DC0, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:385)
7535"""
7536        output = """ServiceInfo(service_id=429473216, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=385)
7537EventServiceAttach(event_id=1, timestamp=datetime.datetime(2017, 11, 13, 11, 49, 51, 311000), status=' ', service_id=429473216)
7538"""
7539        self._check_events(trace_lines, output)
7540    def test_service_detach(self):
7541        trace_lines = """2017-11-13T22:50:09.3790 (2500:0000000026C39D70) DETACH_SERVICE
7542	service_mgr, (Service 0000000028290058, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:385)
7543"""
7544        output = """ServiceInfo(service_id=673775704, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=385)
7545EventServiceDetach(event_id=1, timestamp=datetime.datetime(2017, 11, 13, 22, 50, 9, 379000), status=' ', service_id=673775704)
7546"""
7547        self._check_events(trace_lines, output)
7548    def test_service_start(self):
7549        trace_lines = """2017-11-13T11:49:07.7860 (2500:0000000001A4DB68) START_SERVICE
7550	service_mgr, (Service 000000001F6F1CF8, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:385)
7551	"Start Trace Session"
7552	-TRUSTED_SVC SYSDBA -START -CONFIG <database %[\\/]TEST.FDB>
7553enabled true
7554log_connections true
7555log_transactions true
7556log_statement_prepare false
7557log_statement_free false
7558log_statement_start false
7559log_statement_finish false
7560print_plan false
7561print_perf false
7562time_threshold 1000
7563max_sql_length 300
7564max_arg_length 80
7565max_arg_count 30
7566log_procedure_start false
7567log_procedure_finish false
7568log_trigger_start false
7569log_trigger_finish false
7570log_context false
7571log_errors false
7572log_sweep false
7573log_blr_requests false
7574print_blr false
7575max_blr_length 500
7576log_dyn_requests false
7577print_dyn false
7578max_dyn_length 500
7579log_warnings false
7580log_initfini false
7581</database>
7582
7583<services>
7584enabled true
7585log_services true
7586log_errors false
7587log_warnings false
7588log_initfini false
7589</services>
7590"""
7591        output = """ServiceInfo(service_id=527375608, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=385)
7592EventServiceStart(event_id=1, timestamp=datetime.datetime(2017, 11, 13, 11, 49, 7, 786000), status=' ', service_id=527375608, action='Start Trace Session', parameters=['-TRUSTED_SVC SYSDBA -START -CONFIG <database %[\\\\/]TEST.FDB>', 'enabled true', 'log_connections true', 'log_transactions true', 'log_statement_prepare false', 'log_statement_free false', 'log_statement_start false', 'log_statement_finish false', 'print_plan false', 'print_perf false', 'time_threshold 1000', 'max_sql_length 300', 'max_arg_length 80', 'max_arg_count 30', 'log_procedure_start false', 'log_procedure_finish false', 'log_trigger_start false', 'log_trigger_finish false', 'log_context false', 'log_errors false', 'log_sweep false', 'log_blr_requests false', 'print_blr false', 'max_blr_length 500', 'log_dyn_requests false', 'print_dyn false', 'max_dyn_length 500', 'log_warnings false', 'log_initfini false', '</database>', '<services>', 'enabled true', 'log_services true', 'log_errors false', 'log_warnings false', 'log_initfini false', '</services>'])
7593"""
7594        self._check_events(trace_lines, output)
7595    def test_service_query(self):
7596        trace_lines = """2018-03-29T14:02:10.9180 (5924:0x7feab93f4978) QUERY_SERVICE
7597	service_mgr, (Service 0x7feabd3da548, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:385)
7598	"Start Trace Session"
7599	 Receive portion of the query:
7600		 retrieve 1 line of service output per call
7601
76022018-04-03T12:41:01.7970 (5831:0x7f748c054978) QUERY_SERVICE
7603	service_mgr, (Service 0x7f748f839540, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:4631)
7604	 Receive portion of the query:
7605		 retrieve the version of the server engine
7606
76072018-04-03T12:41:30.7840 (5831:0x7f748c054978) QUERY_SERVICE
7608	service_mgr, (Service 0x7f748f839540, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:4631)
7609	 Receive portion of the query:
7610		 retrieve the implementation of the Firebird server
7611
76122018-04-03T12:56:27.5590 (5831:0x7f748c054978) QUERY_SERVICE
7613	service_mgr, (Service 0x7f748f839540, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:4631)
7614	"Repair Database"
7615"""
7616        output = """ServiceInfo(service_id=140646174008648, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=385)
7617EventServiceQuery(event_id=1, timestamp=datetime.datetime(2018, 3, 29, 14, 2, 10, 918000), status=' ', service_id=140646174008648, action='Start Trace Session', parameters=['Receive portion of the query:', 'retrieve 1 line of service output per call'])
7618ServiceInfo(service_id=140138600699200, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=4631)
7619EventServiceQuery(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 12, 41, 1, 797000), status=' ', service_id=140138600699200, action=None, parameters=['retrieve the version of the server engine'])
7620EventServiceQuery(event_id=3, timestamp=datetime.datetime(2018, 4, 3, 12, 41, 30, 784000), status=' ', service_id=140138600699200, action=None, parameters=['retrieve the implementation of the Firebird server'])
7621EventServiceQuery(event_id=4, timestamp=datetime.datetime(2018, 4, 3, 12, 56, 27, 559000), status=' ', service_id=140138600699200, action='Repair Database', parameters=[])
7622"""
7623        if sys.version_info.major == 2 and sys.version_info.minor == 7 and sys.version_info.micro > 13:
7624            output = """ServiceInfo(service_id=140646174008648, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=385)
7625EventServiceQuery(event_id=1, timestamp=datetime.datetime(2018, 3, 29, 14, 2, 10, 918000), status=' ', service_id=140646174008648, action='Start Trace Session', parameters=['Receive portion of the query:', 'retrieve 1 line of service output per call'])
7626ServiceInfo(service_id=140138600699200, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=4631)
7627EventServiceQuery(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 12, 41, 1, 797000), status=' ', service_id=140138600699200, action=None, parameters=['retrieve the version of the server engine'])
7628EventServiceQuery(event_id=3, timestamp=datetime.datetime(2018, 4, 3, 12, 41, 30, 784000), status=' ', service_id=140138600699200, action=None, parameters=['retrieve the implementation of the Firebird server'])
7629EventServiceQuery(event_id=4, timestamp=datetime.datetime(2018, 4, 3, 12, 56, 27, 559000), status=' ', service_id=140138600699200, action='Repair Database', parameters=[])
7630"""
7631        self._check_events(trace_lines, output)
7632    def test_set_context(self):
7633        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) ATTACH_DATABASE
7634	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7635	/opt/firebird/bin/isql:8723
7636
76372014-05-23T11:00:28.6160 (3720:0000000000EFD9E8) START_TRANSACTION
7638	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7639	/opt/firebird/bin/isql:8723
7640		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7641
76422017-11-09T11:21:59.0270 (2500:0000000001A45B00) SET_CONTEXT
7643	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7644	/opt/firebird/bin/isql:8723
7645		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7646[USER_TRANSACTION] TRANSACTION_TIMESTAMP = "2017-11-09 11:21:59.0270"
7647
76482017-11-09T11:21:59.0300 (2500:0000000001A45B00) SET_CONTEXT
7649	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
7650	/opt/firebird/bin/isql:8723
7651		(TRA_1570, READ_COMMITTED | REC_VERSION | WAIT | READ_WRITE)
7652[USER_SESSION] MY_KEY = "1"
7653"""
7654        output = """EventAttach(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), status=' ', attachment_id=8, database='/home/employee.fdb', charset='ISO88591', protocol='TCPv4', address='192.168.1.5', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7655EventTransactionStart(event_id=2, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 616000), status=' ', attachment_id=8, transaction_id=1570, options=['READ_COMMITTED', 'REC_VERSION', 'WAIT', 'READ_WRITE'])
7656EventSetContext(event_id=3, timestamp=datetime.datetime(2017, 11, 9, 11, 21, 59, 27000), attachment_id=8, transaction_id=1570, context='USER_TRANSACTION', key='TRANSACTION_TIMESTAMP', value='"2017-11-09 11:21:59.0270"')
7657EventSetContext(event_id=4, timestamp=datetime.datetime(2017, 11, 9, 11, 21, 59, 30000), attachment_id=8, transaction_id=1570, context='USER_SESSION', key='MY_KEY', value='"1"')
7658"""
7659        self._check_events(trace_lines, output)
7660    def test_error(self):
7661        trace_lines = """2018-03-22T10:06:59.5090 (4992:0x7f92a22a4978) ERROR AT jrd8_attach_database
7662	/home/test.fdb (ATT_0, sysdba, NONE, TCPv4:127.0.0.1)
7663	/usr/bin/flamerobin:4985
7664335544344 : I/O error during "open" operation for file "/home/test.fdb"
7665335544734 : Error while trying to open file
7666        2 : No such file or directory
7667
76682018-03-22T11:00:59.5090 (2500:0000000022415DB8) ERROR AT jrd8_fetch
7669	/home/test.fdb (ATT_519417, SYSDBA:NONE, WIN1250, TCPv4:172.19.54.61)
7670	/usr/bin/flamerobin:4985
7671335544364 : request synchronization error
7672
76732018-04-03T12:49:28.5080 (5831:0x7f748c054978) ERROR AT jrd8_service_query
7674	service_mgr, (Service 0x7f748f839540, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:4631)
7675335544344 : I/O error during "open" operation for file "bug.fdb"
7676335544734 : Error while trying to open file
7677        2 : No such file or directory
7678"""
7679        output = """AttachmentInfo(attachment_id=0, database='/home/test.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='sysdba', role='NONE', remote_process='/usr/bin/flamerobin', remote_pid=4985)
7680EventError(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 10, 6, 59, 509000), attachment_id=0, place='jrd8_attach_database', details=['335544344 : I/O error during "open" operation for file "/home/test.fdb"', '335544734 : Error while trying to open file', '2 : No such file or directory'])
7681AttachmentInfo(attachment_id=519417, database='/home/test.fdb', charset='WIN1250', protocol='TCPv4', address='172.19.54.61', user='SYSDBA', role='NONE', remote_process='/usr/bin/flamerobin', remote_pid=4985)
7682EventError(event_id=2, timestamp=datetime.datetime(2018, 3, 22, 11, 0, 59, 509000), attachment_id=519417, place='jrd8_fetch', details=['335544364 : request synchronization error'])
7683ServiceInfo(service_id=140138600699200, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=4631)
7684EventServiceError(event_id=3, timestamp=datetime.datetime(2018, 4, 3, 12, 49, 28, 508000), service_id=140138600699200, place='jrd8_service_query', details=['335544344 : I/O error during "open" operation for file "bug.fdb"', '335544734 : Error while trying to open file', '2 : No such file or directory'])
7685"""
7686        if sys.version_info.major == 2 and sys.version_info.minor == 7 and sys.version_info.micro > 13:
7687            output = """AttachmentInfo(attachment_id=0, database='/home/test.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='sysdba', role='NONE', remote_process='/usr/bin/flamerobin', remote_pid=4985)
7688EventError(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 10, 6, 59, 509000), attachment_id=0, place='jrd8_attach_database', details=['335544344 : I/O error during "open" operation for file "/home/test.fdb"', '335544734 : Error while trying to open file', '2 : No such file or directory'])
7689AttachmentInfo(attachment_id=519417, database='/home/test.fdb', charset='WIN1250', protocol='TCPv4', address='172.19.54.61', user='SYSDBA', role='NONE', remote_process='/usr/bin/flamerobin', remote_pid=4985)
7690EventError(event_id=2, timestamp=datetime.datetime(2018, 3, 22, 11, 0, 59, 509000), attachment_id=519417, place='jrd8_fetch', details=['335544364 : request synchronization error'])
7691ServiceInfo(service_id=140138600699200, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=4631)
7692EventServiceError(event_id=3, timestamp=datetime.datetime(2018, 4, 3, 12, 49, 28, 508000), service_id=140138600699200, place='jrd8_service_query', details=['335544344 : I/O error during "open" operation for file "bug.fdb"', '335544734 : Error while trying to open file', '2 : No such file or directory'])
7693"""
7694        self._check_events(trace_lines, output)
7695    def test_warning(self):
7696        trace_lines = """2018-03-22T10:06:59.5090 (4992:0x7f92a22a4978) WARNING AT jrd8_attach_database
7697	/home/test.fdb (ATT_0, sysdba, NONE, TCPv4:127.0.0.1)
7698	/usr/bin/flamerobin:4985
7699Some reason for the warning.
7700
77012018-04-03T12:49:28.5080 (5831:0x7f748c054978) WARNING AT jrd8_service_query
7702	service_mgr, (Service 0x7f748f839540, SYSDBA, TCPv4:127.0.0.1, /job/fbtrace:4631)
7703Some reason for the warning.
7704"""
7705        output = """AttachmentInfo(attachment_id=0, database='/home/test.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='sysdba', role='NONE', remote_process='/usr/bin/flamerobin', remote_pid=4985)
7706EventWarning(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 10, 6, 59, 509000), attachment_id=0, place='jrd8_attach_database', details=['Some reason for the warning.'])
7707ServiceInfo(service_id=140138600699200, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=4631)
7708EventServiceWarning(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 12, 49, 28, 508000), service_id=140138600699200, place='jrd8_service_query', details=['Some reason for the warning.'])
7709"""
7710        if sys.version_info.major == 2 and sys.version_info.minor == 7 and sys.version_info.micro > 13:
7711            output = """AttachmentInfo(attachment_id=0, database='/home/test.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='sysdba', role='NONE', remote_process='/usr/bin/flamerobin', remote_pid=4985)
7712EventWarning(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 10, 6, 59, 509000), attachment_id=0, place='jrd8_attach_database', details=['Some reason for the warning.'])
7713ServiceInfo(service_id=140138600699200, user='SYSDBA', protocol='TCPv4', address='127.0.0.1', remote_process='/job/fbtrace', remote_pid=4631)
7714EventServiceWarning(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 12, 49, 28, 508000), service_id=140138600699200, place='jrd8_service_query', details=['Some reason for the warning.'])
7715"""
7716        self._check_events(trace_lines, output)
7717    def test_sweep_start(self):
7718        trace_lines = """2018-03-22T17:33:56.9690 (12351:0x7f0174bdd978) SWEEP_START
7719	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
7720
7721Transaction counters:
7722	Oldest interesting        155
7723	Oldest active             156
7724	Oldest snapshot           156
7725	Next transaction          156
7726
77272018-03-22T18:33:56.9690 (12351:0x7f0174bdd978) SWEEP_START
7728	/opt/firebird/examples/empbuild/employee.fdb (ATT_9, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
7729        /opt/firebird/bin/isql:8723
7730
7731Transaction counters:
7732	Oldest interesting        155
7733	Oldest active             156
7734	Oldest snapshot           156
7735	Next transaction          156
7736"""
7737        output = """AttachmentInfo(attachment_id=8, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
7738EventSweepStart(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 56, 969000), attachment_id=8, oit=155, oat=156, ost=156, next=156)
7739AttachmentInfo(attachment_id=9, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='SYSDBA', role='NONE', remote_process='/opt/firebird/bin/isql', remote_pid=8723)
7740EventSweepStart(event_id=2, timestamp=datetime.datetime(2018, 3, 22, 18, 33, 56, 969000), attachment_id=9, oit=155, oat=156, ost=156, next=156)
7741"""
7742        self._check_events(trace_lines, output)
7743    def test_sweep_progress(self):
7744        trace_lines = """2018-03-22T17:33:56.9820 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7745	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7746      0 ms, 5 fetch(es)
7747
77482018-03-22T17:33:56.9830 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7749	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7750      0 ms, 6 read(s), 409 fetch(es)
7751
77522018-03-22T17:33:56.9920 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7753	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7754      9 ms, 5 read(s), 345 fetch(es), 39 mark(s)
7755
77562018-03-22T17:33:56.9930 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7757	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7758      0 ms, 4 read(s), 251 fetch(es), 24 mark(s)
7759
77602018-03-22T17:33:57.0000 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7761	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7762      7 ms, 14 read(s), 877 fetch(es), 4 mark(s)
7763
77642018-03-22T17:33:57.0000 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7765	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7766      0 ms, 2 read(s), 115 fetch(es)
7767
77682018-03-22T17:33:57.0000 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7769	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7770      0 ms, 2 read(s), 7 fetch(es)
7771
77722018-03-22T17:33:57.0020 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7773	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7774      1 ms, 2 read(s), 25 fetch(es)
7775
77762018-03-22T17:33:57.0070 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7777	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7778      5 ms, 4 read(s), 1 write(s), 339 fetch(es), 97 mark(s)
7779
77802018-03-22T17:33:57.0090 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7781	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7782      2 ms, 6 read(s), 1 write(s), 467 fetch(es)
7783
77842018-03-22T17:33:57.0100 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7785	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7786      0 ms, 2 read(s), 149 fetch(es)
7787
77882018-03-22T17:33:57.0930 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7789	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7790     83 ms, 11 read(s), 8 write(s), 2307 fetch(es), 657 mark(s)
7791
77922018-03-22T17:33:57.1010 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7793	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7794      7 ms, 2 read(s), 1 write(s), 7 fetch(es)
7795
77962018-03-22T17:33:57.1010 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7797	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7798      0 ms, 2 read(s), 17 fetch(es)
7799
78002018-03-22T17:33:57.1010 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7801	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7802      0 ms, 2 read(s), 75 fetch(es)
7803
78042018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7805	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7806     10 ms, 5 read(s), 305 fetch(es)
7807
78082018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7809	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7810      0 ms, 2 read(s), 25 fetch(es)
7811
78122018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7813	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7814      0 ms, 2 read(s), 7 fetch(es)
7815
78162018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7817	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7818      0 ms, 1 read(s), 165 fetch(es)
7819
78202018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7821	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7822      0 ms, 2 read(s), 31 fetch(es)
7823
78242018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7825	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7826      0 ms, 1 read(s), 141 fetch(es)
7827
78282018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7829	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7830      0 ms, 5 read(s), 29 fetch(es)
7831
78322018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7833	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7834      0 ms, 2 read(s), 69 fetch(es)
7835
78362018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7837	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7838      0 ms, 107 fetch(es)
7839
78402018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7841	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7842      0 ms, 2 read(s), 303 fetch(es)
7843
78442018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7845	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7846      0 ms, 2 read(s), 13 fetch(es)
7847
78482018-03-22T17:33:57.1120 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7849	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7850      0 ms, 5 fetch(es)
7851
78522018-03-22T17:33:57.1130 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7853	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7854      0 ms, 2 read(s), 31 fetch(es)
7855
78562018-03-22T17:33:57.1130 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7857	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7858      0 ms, 6 read(s), 285 fetch(es), 60 mark(s)
7859
78602018-03-22T17:33:57.1350 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7861	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7862      8 ms, 2 read(s), 1 write(s), 45 fetch(es)
7863
78642018-03-22T17:33:57.1350 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7865	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7866      0 ms, 3 read(s), 89 fetch(es)
7867
78682018-03-22T17:33:57.1350 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7869	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7870      0 ms, 3 read(s), 61 fetch(es), 12 mark(s)
7871
78722018-03-22T17:33:57.1420 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7873	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7874      7 ms, 2 read(s), 1 write(s), 59 fetch(es)
7875
78762018-03-22T17:33:57.1480 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7877	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7878      5 ms, 3 read(s), 1 write(s), 206 fetch(es), 48 mark(s)
7879
78802018-03-22T17:33:57.1510 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7881	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7882      2 ms, 2 read(s), 1 write(s), 101 fetch(es)
7883
78842018-03-22T17:33:57.1510 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7885	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7886      0 ms, 2 read(s), 33 fetch(es)
7887
78882018-03-22T17:33:57.1510 (12351:0x7f0174bdd978) SWEEP_PROGRESS
7889	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
7890      0 ms, 2 read(s), 69 fetch(es)
7891"""
7892        output = """AttachmentInfo(attachment_id=8, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='<internal>', address='<internal>', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
7893EventSweepProgress(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 56, 982000), attachment_id=8, run_time=0, reads=None, writes=None, fetches=5, marks=None, access=None)
7894EventSweepProgress(event_id=2, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 56, 983000), attachment_id=8, run_time=0, reads=6, writes=None, fetches=409, marks=None, access=None)
7895EventSweepProgress(event_id=3, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 56, 992000), attachment_id=8, run_time=9, reads=5, writes=None, fetches=345, marks=39, access=None)
7896EventSweepProgress(event_id=4, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 56, 993000), attachment_id=8, run_time=0, reads=4, writes=None, fetches=251, marks=24, access=None)
7897EventSweepProgress(event_id=5, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57), attachment_id=8, run_time=7, reads=14, writes=None, fetches=877, marks=4, access=None)
7898EventSweepProgress(event_id=6, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57), attachment_id=8, run_time=0, reads=2, writes=None, fetches=115, marks=None, access=None)
7899EventSweepProgress(event_id=7, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57), attachment_id=8, run_time=0, reads=2, writes=None, fetches=7, marks=None, access=None)
7900EventSweepProgress(event_id=8, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 2000), attachment_id=8, run_time=1, reads=2, writes=None, fetches=25, marks=None, access=None)
7901EventSweepProgress(event_id=9, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 7000), attachment_id=8, run_time=5, reads=4, writes=1, fetches=339, marks=97, access=None)
7902EventSweepProgress(event_id=10, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 9000), attachment_id=8, run_time=2, reads=6, writes=1, fetches=467, marks=None, access=None)
7903EventSweepProgress(event_id=11, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 10000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=149, marks=None, access=None)
7904EventSweepProgress(event_id=12, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 93000), attachment_id=8, run_time=83, reads=11, writes=8, fetches=2307, marks=657, access=None)
7905EventSweepProgress(event_id=13, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 101000), attachment_id=8, run_time=7, reads=2, writes=1, fetches=7, marks=None, access=None)
7906EventSweepProgress(event_id=14, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 101000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=17, marks=None, access=None)
7907EventSweepProgress(event_id=15, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 101000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=75, marks=None, access=None)
7908EventSweepProgress(event_id=16, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=10, reads=5, writes=None, fetches=305, marks=None, access=None)
7909EventSweepProgress(event_id=17, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=25, marks=None, access=None)
7910EventSweepProgress(event_id=18, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=7, marks=None, access=None)
7911EventSweepProgress(event_id=19, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=1, writes=None, fetches=165, marks=None, access=None)
7912EventSweepProgress(event_id=20, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=31, marks=None, access=None)
7913EventSweepProgress(event_id=21, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=1, writes=None, fetches=141, marks=None, access=None)
7914EventSweepProgress(event_id=22, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=5, writes=None, fetches=29, marks=None, access=None)
7915EventSweepProgress(event_id=23, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=69, marks=None, access=None)
7916EventSweepProgress(event_id=24, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=None, writes=None, fetches=107, marks=None, access=None)
7917EventSweepProgress(event_id=25, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=303, marks=None, access=None)
7918EventSweepProgress(event_id=26, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=13, marks=None, access=None)
7919EventSweepProgress(event_id=27, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 112000), attachment_id=8, run_time=0, reads=None, writes=None, fetches=5, marks=None, access=None)
7920EventSweepProgress(event_id=28, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 113000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=31, marks=None, access=None)
7921EventSweepProgress(event_id=29, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 113000), attachment_id=8, run_time=0, reads=6, writes=None, fetches=285, marks=60, access=None)
7922EventSweepProgress(event_id=30, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 135000), attachment_id=8, run_time=8, reads=2, writes=1, fetches=45, marks=None, access=None)
7923EventSweepProgress(event_id=31, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 135000), attachment_id=8, run_time=0, reads=3, writes=None, fetches=89, marks=None, access=None)
7924EventSweepProgress(event_id=32, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 135000), attachment_id=8, run_time=0, reads=3, writes=None, fetches=61, marks=12, access=None)
7925EventSweepProgress(event_id=33, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 142000), attachment_id=8, run_time=7, reads=2, writes=1, fetches=59, marks=None, access=None)
7926EventSweepProgress(event_id=34, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 148000), attachment_id=8, run_time=5, reads=3, writes=1, fetches=206, marks=48, access=None)
7927EventSweepProgress(event_id=35, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 151000), attachment_id=8, run_time=2, reads=2, writes=1, fetches=101, marks=None, access=None)
7928EventSweepProgress(event_id=36, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 151000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=33, marks=None, access=None)
7929EventSweepProgress(event_id=37, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 151000), attachment_id=8, run_time=0, reads=2, writes=None, fetches=69, marks=None, access=None)
7930"""
7931        self._check_events(trace_lines, output)
7932    def test_sweep_progress_performance(self):
7933        trace_lines = """2018-03-29T15:23:01.3050 (7035:0x7fde644e4978) SWEEP_PROGRESS
7934	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7935      2 ms, 1 read(s), 11 fetch(es), 2 mark(s)
7936
7937Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7938***************************************************************************************************************
7939RDB$DATABASE                            1                                                           1
7940
79412018-03-29T15:23:01.3130 (7035:0x7fde644e4978) SWEEP_PROGRESS
7942	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7943      7 ms, 8 read(s), 436 fetch(es), 9 mark(s)
7944
7945Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7946***************************************************************************************************************
7947RDB$FIELDS                            199                                                                     3
7948
79492018-03-29T15:23:01.3150 (7035:0x7fde644e4978) SWEEP_PROGRESS
7950	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7951      1 ms, 4 read(s), 229 fetch(es)
7952
7953Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7954***************************************************************************************************************
7955RDB$INDEX_SEGMENTS                    111
7956
79572018-03-29T15:23:01.3150 (7035:0x7fde644e4978) SWEEP_PROGRESS
7958	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7959      0 ms, 3 read(s), 179 fetch(es)
7960
7961Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7962***************************************************************************************************************
7963RDB$INDICES                            87
7964
79652018-03-29T15:23:01.3370 (7035:0x7fde644e4978) SWEEP_PROGRESS
7966	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7967     21 ms, 18 read(s), 1 write(s), 927 fetch(es), 21 mark(s)
7968
7969Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7970***************************************************************************************************************
7971RDB$RELATION_FIELDS                   420                                                                     4
7972
79732018-03-29T15:23:01.3440 (7035:0x7fde644e4978) SWEEP_PROGRESS
7974	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7975      7 ms, 2 read(s), 1 write(s), 143 fetch(es), 10 mark(s)
7976
7977Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7978***************************************************************************************************************
7979RDB$RELATIONS                          53                                                                     2
7980
79812018-03-29T15:23:01.3610 (7035:0x7fde644e4978) SWEEP_PROGRESS
7982	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7983     17 ms, 2 read(s), 1 write(s), 7 fetch(es)
7984
7985Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7986***************************************************************************************************************
7987RDB$VIEW_RELATIONS                      2
7988
79892018-03-29T15:23:01.3610 (7035:0x7fde644e4978) SWEEP_PROGRESS
7990	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7991      0 ms, 2 read(s), 25 fetch(es)
7992
7993Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
7994***************************************************************************************************************
7995RDB$FORMATS                            11
7996
79972018-03-29T15:23:01.3860 (7035:0x7fde644e4978) SWEEP_PROGRESS
7998	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
7999     24 ms, 5 read(s), 1 write(s), 94 fetch(es), 4 mark(s)
8000
8001Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8002***************************************************************************************************************
8003RDB$SECURITY_CLASSES                   39                                                                     1
8004
80052018-03-29T15:23:01.3940 (7035:0x7fde644e4978) SWEEP_PROGRESS
8006	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8007      7 ms, 6 read(s), 467 fetch(es)
8008
8009Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8010***************************************************************************************************************
8011RDB$TYPES                             228
8012
80132018-03-29T15:23:01.3960 (7035:0x7fde644e4978) SWEEP_PROGRESS
8014	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8015      1 ms, 2 read(s), 149 fetch(es)
8016
8017Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8018***************************************************************************************************************
8019RDB$TRIGGERS                           67
8020
80212018-03-29T15:23:01.3980 (7035:0x7fde644e4978) SWEEP_PROGRESS
8022	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8023      1 ms, 8 read(s), 341 fetch(es)
8024
8025Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8026***************************************************************************************************************
8027RDB$DEPENDENCIES                      163
8028
80292018-03-29T15:23:01.3980 (7035:0x7fde644e4978) SWEEP_PROGRESS
8030	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8031      0 ms, 2 read(s), 7 fetch(es)
8032
8033Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8034***************************************************************************************************************
8035RDB$FUNCTIONS                           2
8036
80372018-03-29T15:23:01.3980 (7035:0x7fde644e4978) SWEEP_PROGRESS
8038	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8039      0 ms, 2 read(s), 17 fetch(es)
8040
8041Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8042***************************************************************************************************************
8043RDB$FUNCTION_ARGUMENTS                  7
8044
80452018-03-29T15:23:01.3980 (7035:0x7fde644e4978) SWEEP_PROGRESS
8046	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8047      0 ms, 2 read(s), 75 fetch(es)
8048
8049Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8050***************************************************************************************************************
8051RDB$TRIGGER_MESSAGES                   36
8052
80532018-03-29T15:23:01.3990 (7035:0x7fde644e4978) SWEEP_PROGRESS
8054	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8055      1 ms, 5 read(s), 305 fetch(es)
8056
8057Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8058***************************************************************************************************************
8059RDB$USER_PRIVILEGES                   148
8060
80612018-03-29T15:23:01.4230 (7035:0x7fde644e4978) SWEEP_PROGRESS
8062	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8063      0 ms, 2 read(s), 25 fetch(es)
8064
8065Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8066***************************************************************************************************************
8067RDB$GENERATORS                         11
8068
80692018-03-29T15:23:01.4230 (7035:0x7fde644e4978) SWEEP_PROGRESS
8070	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8071      0 ms, 2 read(s), 7 fetch(es)
8072
8073Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8074***************************************************************************************************************
8075RDB$FIELD_DIMENSIONS                    2
8076
80772018-03-29T15:23:01.4230 (7035:0x7fde644e4978) SWEEP_PROGRESS
8078	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8079      0 ms, 1 read(s), 165 fetch(es)
8080
8081Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8082***************************************************************************************************************
8083RDB$RELATION_CONSTRAINTS               80
8084
80852018-03-29T15:23:01.4230 (7035:0x7fde644e4978) SWEEP_PROGRESS
8086	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8087      0 ms, 2 read(s), 31 fetch(es)
8088
8089Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8090***************************************************************************************************************
8091RDB$REF_CONSTRAINTS                    14
8092
80932018-03-29T15:23:01.4290 (7035:0x7fde644e4978) SWEEP_PROGRESS
8094	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8095      5 ms, 1 read(s), 141 fetch(es)
8096
8097Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8098***************************************************************************************************************
8099RDB$CHECK_CONSTRAINTS                  68
8100
81012018-03-29T15:23:01.4300 (7035:0x7fde644e4978) SWEEP_PROGRESS
8102	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8103      0 ms, 5 read(s), 29 fetch(es)
8104
8105Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8106***************************************************************************************************************
8107RDB$PROCEDURES                         10
8108
81092018-03-29T15:23:01.4300 (7035:0x7fde644e4978) SWEEP_PROGRESS
8110	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8111      0 ms, 2 read(s), 69 fetch(es)
8112
8113Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8114***************************************************************************************************************
8115RDB$PROCEDURE_PARAMETERS               33
8116
81172018-03-29T15:23:01.4300 (7035:0x7fde644e4978) SWEEP_PROGRESS
8118	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8119      0 ms, 107 fetch(es)
8120
8121Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8122***************************************************************************************************************
8123RDB$CHARACTER_SETS                     52
8124
81252018-03-29T15:23:01.4300 (7035:0x7fde644e4978) SWEEP_PROGRESS
8126	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8127      0 ms, 2 read(s), 303 fetch(es)
8128
8129Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8130***************************************************************************************************************
8131RDB$COLLATIONS                        148
8132
81332018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8134	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8135      0 ms, 2 read(s), 13 fetch(es)
8136
8137Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8138***************************************************************************************************************
8139RDB$EXCEPTIONS                          5
8140
81412018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8142	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8143      0 ms, 5 fetch(es)
8144
8145Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8146***************************************************************************************************************
8147RDB$ROLES                               1
8148
81492018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8150	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8151      0 ms, 2 read(s), 31 fetch(es)
8152
8153Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8154***************************************************************************************************************
8155COUNTRY                                14
8156
81572018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8158	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8159      0 ms, 4 read(s), 69 fetch(es)
8160
8161Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8162***************************************************************************************************************
8163JOB                                    31
8164
81652018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8166	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8167      0 ms, 2 read(s), 45 fetch(es)
8168
8169Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8170***************************************************************************************************************
8171DEPARTMENT                             21
8172
81732018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8174	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8175      0 ms, 3 read(s), 89 fetch(es)
8176
8177Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8178***************************************************************************************************************
8179EMPLOYEE                               42
8180
81812018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8182	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8183      0 ms, 2 read(s), 15 fetch(es)
8184
8185Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8186***************************************************************************************************************
8187PROJECT                                 6
8188
81892018-03-29T15:23:01.4310 (7035:0x7fde644e4978) SWEEP_PROGRESS
8190	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8191      0 ms, 2 read(s), 59 fetch(es)
8192
8193Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8194***************************************************************************************************************
8195EMPLOYEE_PROJECT                       28
8196
81972018-03-29T15:23:01.4320 (7035:0x7fde644e4978) SWEEP_PROGRESS
8198	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8199      0 ms, 2 read(s), 51 fetch(es)
8200
8201Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8202***************************************************************************************************************
8203PROJ_DEPT_BUDGET                       24
8204
82052018-03-29T15:23:01.4320 (7035:0x7fde644e4978) SWEEP_PROGRESS
8206	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8207      0 ms, 2 read(s), 101 fetch(es)
8208
8209Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8210***************************************************************************************************************
8211SALARY_HISTORY                         49
8212
82132018-03-29T15:23:01.4320 (7035:0x7fde644e4978) SWEEP_PROGRESS
8214	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8215      0 ms, 2 read(s), 33 fetch(es)
8216
8217Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8218***************************************************************************************************************
8219CUSTOMER                               15
8220
82212018-03-29T15:23:01.4320 (7035:0x7fde644e4978) SWEEP_PROGRESS
8222	/opt/firebird/examples/empbuild/employee.fdb (ATT_24, SYSDBA:NONE, NONE, <internal>)
8223      0 ms, 2 read(s), 69 fetch(es)
8224
8225Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8226***************************************************************************************************************
8227SALES                                  33
8228"""
8229        output = """AttachmentInfo(attachment_id=24, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='<internal>', address='<internal>', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
8230EventSweepProgress(event_id=1, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 305000), attachment_id=24, run_time=2, reads=1, writes=None, fetches=11, marks=2, access=[AccessTuple(table='RDB$DATABASE', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=1, expunge=0)])
8231EventSweepProgress(event_id=2, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 313000), attachment_id=24, run_time=7, reads=8, writes=None, fetches=436, marks=9, access=[AccessTuple(table='RDB$FIELDS', natural=199, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=3)])
8232EventSweepProgress(event_id=3, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 315000), attachment_id=24, run_time=1, reads=4, writes=None, fetches=229, marks=None, access=[AccessTuple(table='RDB$INDEX_SEGMENTS', natural=111, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8233EventSweepProgress(event_id=4, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 315000), attachment_id=24, run_time=0, reads=3, writes=None, fetches=179, marks=None, access=[AccessTuple(table='RDB$INDICES', natural=87, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8234EventSweepProgress(event_id=5, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 337000), attachment_id=24, run_time=21, reads=18, writes=1, fetches=927, marks=21, access=[AccessTuple(table='RDB$RELATION_FIELDS', natural=420, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=4)])
8235EventSweepProgress(event_id=6, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 344000), attachment_id=24, run_time=7, reads=2, writes=1, fetches=143, marks=10, access=[AccessTuple(table='RDB$RELATIONS', natural=53, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=2)])
8236EventSweepProgress(event_id=7, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 361000), attachment_id=24, run_time=17, reads=2, writes=1, fetches=7, marks=None, access=[AccessTuple(table='RDB$VIEW_RELATIONS', natural=2, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8237EventSweepProgress(event_id=8, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 361000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=25, marks=None, access=[AccessTuple(table='RDB$FORMATS', natural=11, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8238EventSweepProgress(event_id=9, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 386000), attachment_id=24, run_time=24, reads=5, writes=1, fetches=94, marks=4, access=[AccessTuple(table='RDB$SECURITY_CLASSES', natural=39, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=1)])
8239EventSweepProgress(event_id=10, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 394000), attachment_id=24, run_time=7, reads=6, writes=None, fetches=467, marks=None, access=[AccessTuple(table='RDB$TYPES', natural=228, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8240EventSweepProgress(event_id=11, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 396000), attachment_id=24, run_time=1, reads=2, writes=None, fetches=149, marks=None, access=[AccessTuple(table='RDB$TRIGGERS', natural=67, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8241EventSweepProgress(event_id=12, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 398000), attachment_id=24, run_time=1, reads=8, writes=None, fetches=341, marks=None, access=[AccessTuple(table='RDB$DEPENDENCIES', natural=163, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8242EventSweepProgress(event_id=13, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 398000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=7, marks=None, access=[AccessTuple(table='RDB$FUNCTIONS', natural=2, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8243EventSweepProgress(event_id=14, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 398000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=17, marks=None, access=[AccessTuple(table='RDB$FUNCTION_ARGUMENTS', natural=7, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8244EventSweepProgress(event_id=15, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 398000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=75, marks=None, access=[AccessTuple(table='RDB$TRIGGER_MESSAGES', natural=36, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8245EventSweepProgress(event_id=16, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 399000), attachment_id=24, run_time=1, reads=5, writes=None, fetches=305, marks=None, access=[AccessTuple(table='RDB$USER_PRIVILEGES', natural=148, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8246EventSweepProgress(event_id=17, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 423000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=25, marks=None, access=[AccessTuple(table='RDB$GENERATORS', natural=11, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8247EventSweepProgress(event_id=18, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 423000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=7, marks=None, access=[AccessTuple(table='RDB$FIELD_DIMENSIONS', natural=2, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8248EventSweepProgress(event_id=19, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 423000), attachment_id=24, run_time=0, reads=1, writes=None, fetches=165, marks=None, access=[AccessTuple(table='RDB$RELATION_CONSTRAINTS', natural=80, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8249EventSweepProgress(event_id=20, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 423000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=31, marks=None, access=[AccessTuple(table='RDB$REF_CONSTRAINTS', natural=14, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8250EventSweepProgress(event_id=21, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 429000), attachment_id=24, run_time=5, reads=1, writes=None, fetches=141, marks=None, access=[AccessTuple(table='RDB$CHECK_CONSTRAINTS', natural=68, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8251EventSweepProgress(event_id=22, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 430000), attachment_id=24, run_time=0, reads=5, writes=None, fetches=29, marks=None, access=[AccessTuple(table='RDB$PROCEDURES', natural=10, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8252EventSweepProgress(event_id=23, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 430000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=69, marks=None, access=[AccessTuple(table='RDB$PROCEDURE_PARAMETERS', natural=33, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8253EventSweepProgress(event_id=24, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 430000), attachment_id=24, run_time=0, reads=None, writes=None, fetches=107, marks=None, access=[AccessTuple(table='RDB$CHARACTER_SETS', natural=52, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8254EventSweepProgress(event_id=25, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 430000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=303, marks=None, access=[AccessTuple(table='RDB$COLLATIONS', natural=148, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8255EventSweepProgress(event_id=26, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=13, marks=None, access=[AccessTuple(table='RDB$EXCEPTIONS', natural=5, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8256EventSweepProgress(event_id=27, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=None, writes=None, fetches=5, marks=None, access=[AccessTuple(table='RDB$ROLES', natural=1, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8257EventSweepProgress(event_id=28, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=31, marks=None, access=[AccessTuple(table='COUNTRY', natural=14, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8258EventSweepProgress(event_id=29, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=4, writes=None, fetches=69, marks=None, access=[AccessTuple(table='JOB', natural=31, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8259EventSweepProgress(event_id=30, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=45, marks=None, access=[AccessTuple(table='DEPARTMENT', natural=21, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8260EventSweepProgress(event_id=31, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=3, writes=None, fetches=89, marks=None, access=[AccessTuple(table='EMPLOYEE', natural=42, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8261EventSweepProgress(event_id=32, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=15, marks=None, access=[AccessTuple(table='PROJECT', natural=6, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8262EventSweepProgress(event_id=33, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 431000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=59, marks=None, access=[AccessTuple(table='EMPLOYEE_PROJECT', natural=28, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8263EventSweepProgress(event_id=34, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 432000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=51, marks=None, access=[AccessTuple(table='PROJ_DEPT_BUDGET', natural=24, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8264EventSweepProgress(event_id=35, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 432000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=101, marks=None, access=[AccessTuple(table='SALARY_HISTORY', natural=49, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8265EventSweepProgress(event_id=36, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 432000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=33, marks=None, access=[AccessTuple(table='CUSTOMER', natural=15, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8266EventSweepProgress(event_id=37, timestamp=datetime.datetime(2018, 3, 29, 15, 23, 1, 432000), attachment_id=24, run_time=0, reads=2, writes=None, fetches=69, marks=None, access=[AccessTuple(table='SALES', natural=33, index=0, update=0, insert=0, delete=0, backout=0, purge=0, expunge=0)])
8267"""
8268        self._check_events(trace_lines, output)
8269    def test_sweep_finish(self):
8270        trace_lines = """2018-03-22T17:33:57.2270 (12351:0x7f0174bdd978) SWEEP_FINISH
8271	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
8272
8273Transaction counters:
8274	Oldest interesting        156
8275	Oldest active             156
8276	Oldest snapshot           156
8277	Next transaction          157
8278    257 ms, 177 read(s), 30 write(s), 8279 fetch(es), 945 mark(s)
8279
8280"""
8281        output = """AttachmentInfo(attachment_id=8, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='<internal>', address='<internal>', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
8282EventSweepFinish(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 227000), attachment_id=8, oit=156, oat=156, ost=156, next=157, run_time=257, reads=177, writes=30, fetches=8279, marks=945)
8283"""
8284        self._check_events(trace_lines, output)
8285    def test_sweep_finish(self):
8286        trace_lines = """2018-03-22T17:33:57.2270 (12351:0x7f0174bdd978) SWEEP_FAILED
8287	/opt/firebird/examples/empbuild/employee.fdb (ATT_8, SYSDBA:NONE, NONE, <internal>)
8288"""
8289        output = """AttachmentInfo(attachment_id=8, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='<internal>', address='<internal>', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
8290EventSweepFailed(event_id=1, timestamp=datetime.datetime(2018, 3, 22, 17, 33, 57, 227000), attachment_id=8)
8291"""
8292        self._check_events(trace_lines, output)
8293    def test_blr_compile(self):
8294        trace_lines = """2018-04-03T17:00:43.4270 (9772:0x7f2c5004b978) COMPILE_BLR
8295	/home/data/db/employee.fdb (ATT_5, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
8296	/bin/python:9737
8297-------------------------------------------------------------------------------
8298   0 blr_version5,
8299   1 blr_begin,
8300   2    blr_message, 0, 4,0,
8301   6       blr_varying2, 0,0, 15,0,
8302  11       blr_varying2, 0,0, 10,0,
8303  16       blr_short, 0,
8304  18       blr_short, 0,
8305  20    blr_loop,
8306  21       blr_receive, 0,
8307  23          blr_store,
8308  24             blr_relation, 7, 'C','O','U','N','T','R','Y', 0,
8309  34             blr_begin,
8310  35                blr_assignment,
8311  36                   blr_parameter2, 0, 0,0, 2,0,
8312  42                   blr_field, 0, 7, 'C','O','U','N','T','R','Y',
8313  52                blr_assignment,
8314  53                   blr_parameter2, 0, 1,0, 3,0,
8315  59                   blr_field, 0, 8, 'C','U','R','R','E','N','C','Y',
8316  70                blr_end,
8317  71    blr_end,
8318  72 blr_eoc
8319
8320      0 ms
8321
83222018-04-03T17:00:43.4270 (9772:0x7f2c5004b978) COMPILE_BLR
8323	/home/data/db/employee.fdb (ATT_5, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
8324	/bin/python:9737
8325-------------------------------------------------------------------------------
8326   0 blr_version5,
8327   1 blr_begin,
8328   2    blr_message, 0, 4,0,
8329   6       blr_varying2, 0,0, 15,0,
8330  11       blr_varying2, 0,0, 10,0,
8331  16       blr_short, 0
8332...
8333      0 ms
8334
83352018-04-03T17:00:43.4270 (9772:0x7f2c5004b978) COMPILE_BLR
8336	/home/data/db/employee.fdb (ATT_5, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
8337	/bin/python:9737
8338
8339Statement 22:
8340      0 ms
8341"""
8342        output = """AttachmentInfo(attachment_id=5, database='/home/data/db/employee.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='SYSDBA', role='NONE', remote_process='/bin/python', remote_pid=9737)
8343EventBLRCompile(event_id=1, timestamp=datetime.datetime(2018, 4, 3, 17, 0, 43, 427000), status=' ', attachment_id=5, statement_id=None, content="0 blr_version5,\\n1 blr_begin,\\n2    blr_message, 0, 4,0,\\n6       blr_varying2, 0,0, 15,0,\\n11       blr_varying2, 0,0, 10,0,\\n16       blr_short, 0,\\n18       blr_short, 0,\\n20    blr_loop,\\n21       blr_receive, 0,\\n23          blr_store,\\n24             blr_relation, 7, 'C','O','U','N','T','R','Y', 0,\\n34             blr_begin,\\n35                blr_assignment,\\n36                   blr_parameter2, 0, 0,0, 2,0,\\n42                   blr_field, 0, 7, 'C','O','U','N','T','R','Y',\\n52                blr_assignment,\\n53                   blr_parameter2, 0, 1,0, 3,0,\\n59                   blr_field, 0, 8, 'C','U','R','R','E','N','C','Y',\\n70                blr_end,\\n71    blr_end,\\n72 blr_eoc", prepare_time=0)
8344EventBLRCompile(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 17, 0, 43, 427000), status=' ', attachment_id=5, statement_id=None, content='0 blr_version5,\\n1 blr_begin,\\n2    blr_message, 0, 4,0,\\n6       blr_varying2, 0,0, 15,0,\\n11       blr_varying2, 0,0, 10,0,\\n16       blr_short, 0\\n...', prepare_time=0)
8345EventBLRCompile(event_id=3, timestamp=datetime.datetime(2018, 4, 3, 17, 0, 43, 427000), status=' ', attachment_id=5, statement_id=22, content=None, prepare_time=0)
8346"""
8347        self._check_events(trace_lines, output)
8348    def test_blr_execute(self):
8349        trace_lines = """2018-04-03T17:00:43.4280 (9772:0x7f2c5004b978) EXECUTE_BLR
8350	/home/data/db/employee.fdb (ATT_5, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
8351	/home/job/python/envs/pyfirebird/bin/python:9737
8352		(TRA_9, CONCURRENCY | NOWAIT | READ_WRITE)
8353-------------------------------------------------------------------------------
8354   0 blr_version5,
8355   1 blr_begin,
8356   2    blr_message, 0, 4,0,
8357   6       blr_varying2, 0,0, 15,0,
8358  11       blr_varying2, 0,0, 10,0,
8359  16       blr_short, 0,
8360  18       blr_short, 0,
8361  20    blr_loop,
8362  21       blr_receive, 0,
8363  23          blr_store,
8364  24             blr_relation, 7, 'C','O','U','N','T','R','Y', 0,
8365  34             blr_begin,
8366  35                blr_assignment,
8367  36                   blr_parameter2, 0, 0,0, 2,0,
8368  42                   blr_field, 0, 7, 'C','O','U','N','T','R','Y',
8369  52                blr_assignment,
8370  53                   blr_parameter2, 0, 1,0, 3,0,
8371  59                   blr_field, 0, 8, 'C','U','R','R','E','N','C','Y',
8372  70                blr_end,
8373  71    blr_end,
8374  72 blr_eoc
8375
8376      0 ms, 3 read(s), 7 fetch(es), 5 mark(s)
8377
8378Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8379***************************************************************************************************************
8380COUNTRY                                                               1
8381
83822018-04-03T17:00:43.4280 (9772:0x7f2c5004b978) EXECUTE_BLR
8383	/home/data/db/employee.fdb (ATT_5, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
8384	/home/job/python/envs/pyfirebird/bin/python:9737
8385		(TRA_9, CONCURRENCY | NOWAIT | READ_WRITE)
8386-------------------------------------------------------------------------------
8387   0 blr_version5,
8388   1 blr_begin,
8389   2    blr_message, 0, 4,0,
8390   6       blr_varying2, 0,0, 15,0,
8391  11       blr_varying2, 0,0, 10,0,
8392  16       blr_short, 0,
8393  18       blr_short, 0...
8394      0 ms, 3 read(s), 7 fetch(es), 5 mark(s)
8395
8396Table                             Natural     Index    Update    Insert    Delete   Backout     Purge   Expunge
8397***************************************************************************************************************
8398COUNTRY                                                               1
8399
84002018-04-03T17:00:43.4280 (9772:0x7f2c5004b978) EXECUTE_BLR
8401	/home/data/db/employee.fdb (ATT_5, SYSDBA:NONE, NONE, TCPv4:127.0.0.1)
8402	/home/job/python/envs/pyfirebird/bin/python:9737
8403		(TRA_9, CONCURRENCY | NOWAIT | READ_WRITE)
8404Statement 22:
8405      0 ms, 3 read(s), 7 fetch(es), 5 mark(s)
8406"""
8407        output = """AttachmentInfo(attachment_id=5, database='/home/data/db/employee.fdb', charset='NONE', protocol='TCPv4', address='127.0.0.1', user='SYSDBA', role='NONE', remote_process='/home/job/python/envs/pyfirebird/bin/python', remote_pid=9737)
8408TransactionInfo(attachment_id=5, transaction_id=9, options=['CONCURRENCY', 'NOWAIT', 'READ_WRITE'])
8409EventBLRExecute(event_id=1, timestamp=datetime.datetime(2018, 4, 3, 17, 0, 43, 428000), status=' ', attachment_id=5, transaction_id=9, statement_id=None, content="0 blr_version5,\\n1 blr_begin,\\n2    blr_message, 0, 4,0,\\n6       blr_varying2, 0,0, 15,0,\\n11       blr_varying2, 0,0, 10,0,\\n16       blr_short, 0,\\n18       blr_short, 0,\\n20    blr_loop,\\n21       blr_receive, 0,\\n23          blr_store,\\n24             blr_relation, 7, 'C','O','U','N','T','R','Y', 0,\\n34             blr_begin,\\n35                blr_assignment,\\n36                   blr_parameter2, 0, 0,0, 2,0,\\n42                   blr_field, 0, 7, 'C','O','U','N','T','R','Y',\\n52                blr_assignment,\\n53                   blr_parameter2, 0, 1,0, 3,0,\\n59                   blr_field, 0, 8, 'C','U','R','R','E','N','C','Y',\\n70                blr_end,\\n71    blr_end,\\n72 blr_eoc", run_time=0, reads=3, writes=None, fetches=7, marks=5, access=[AccessTuple(table='COUNTRY', natural=0, index=0, update=0, insert=1, delete=0, backout=0, purge=0, expunge=0)])
8410EventBLRExecute(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 17, 0, 43, 428000), status=' ', attachment_id=5, transaction_id=9, statement_id=None, content='0 blr_version5,\\n1 blr_begin,\\n2    blr_message, 0, 4,0,\\n6       blr_varying2, 0,0, 15,0,\\n11       blr_varying2, 0,0, 10,0,\\n16       blr_short, 0,\\n18       blr_short, 0...', run_time=0, reads=3, writes=None, fetches=7, marks=5, access=[AccessTuple(table='COUNTRY', natural=0, index=0, update=0, insert=1, delete=0, backout=0, purge=0, expunge=0)])
8411EventBLRExecute(event_id=3, timestamp=datetime.datetime(2018, 4, 3, 17, 0, 43, 428000), status=' ', attachment_id=5, transaction_id=9, statement_id=22, content=None, run_time=0, reads=3, writes=None, fetches=7, marks=5, access=None)
8412"""
8413        self._check_events(trace_lines, output)
8414    def test_dyn_execute(self):
8415        trace_lines = """2018-04-03T17:42:53.5590 (10474:0x7f0d8b4f0978) EXECUTE_DYN
8416	/opt/firebird/examples/empbuild/employee.fdb (ATT_40, SYSDBA:NONE, NONE, <internal>)
8417		(TRA_221, CONCURRENCY | WAIT | READ_WRITE)
8418-------------------------------------------------------------------------------
8419   0 gds__dyn_version_1,
8420   1    gds__dyn_delete_rel, 1,0, 'T',
8421   5       gds__dyn_end,
8422   0 gds__dyn_eoc
8423     20 ms
84242018-04-03T17:43:21.3650 (10474:0x7f0d8b4f0978) EXECUTE_DYN
8425	/opt/firebird/examples/empbuild/employee.fdb (ATT_40, SYSDBA:NONE, NONE, <internal>)
8426		(TRA_222, CONCURRENCY | WAIT | READ_WRITE)
8427-------------------------------------------------------------------------------
8428   0 gds__dyn_version_1,
8429   1    gds__dyn_begin,
8430   2       gds__dyn_def_local_fld, 31,0, 'C','O','U','N','T','R','Y',32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
8431  36          gds__dyn_fld_source, 31,0, 'C','O','U','N','T','R','Y','N','A','M','E',32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
8432  70          gds__dyn_rel_name, 1,0, 'T',
8433  74          gds__dyn_fld_position, 2,0, 0,0,
8434  79          gds__dyn_update_flag, 2,0, 1,0,
8435  84          gds__dyn_system_flag, 2,0, 0,0,
8436  89          gds__dyn_end,
8437  90       gds__dyn_def_sql_fld, 31,0, 'C','U','R','R','E','N','C','Y',32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,
8438 124          gds__dyn_fld_type, 2,0, 37,0,
8439 129          gds__dyn_fld_length, 2,0, 10,0,
8440 134          gds__dyn_fld_scale, 2,0, 0,0,
8441 139          gds__dyn_rel_name, 1,0, 'T',
8442 143          gds__dyn_fld_position, 2,0, 1,0,
8443 148          gds__dyn_update_flag, 2,0, 1,0,
8444 153          gds__dyn_system_flag, 2,0, 0,0,
8445 158          gds__dyn_end,
8446 159       gds__dyn_end,
8447   0 gds__dyn_eoc
8448      0 ms
84492018-03-29T13:28:45.8910 (5265:0x7f71ed580978) EXECUTE_DYN
8450	/opt/firebird/examples/empbuild/employee.fdb (ATT_20, SYSDBA:NONE, NONE, <internal>)
8451		(TRA_189, CONCURRENCY | WAIT | READ_WRITE)
8452     26 ms
8453"""
8454        output = """AttachmentInfo(attachment_id=40, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='<internal>', address='<internal>', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
8455TransactionInfo(attachment_id=40, transaction_id=221, options=['CONCURRENCY', 'WAIT', 'READ_WRITE'])
8456EventDYNExecute(event_id=1, timestamp=datetime.datetime(2018, 4, 3, 17, 42, 53, 559000), status=' ', attachment_id=40, transaction_id=221, content="0 gds__dyn_version_1,\\n1    gds__dyn_delete_rel, 1,0, 'T',\\n5       gds__dyn_end,\\n0 gds__dyn_eoc", run_time=20)
8457TransactionInfo(attachment_id=40, transaction_id=222, options=['CONCURRENCY', 'WAIT', 'READ_WRITE'])
8458EventDYNExecute(event_id=2, timestamp=datetime.datetime(2018, 4, 3, 17, 43, 21, 365000), status=' ', attachment_id=40, transaction_id=222, content="0 gds__dyn_version_1,\\n1    gds__dyn_begin,\\n2       gds__dyn_def_local_fld, 31,0, 'C','O','U','N','T','R','Y',32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,\\n36          gds__dyn_fld_source, 31,0, 'C','O','U','N','T','R','Y','N','A','M','E',32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,\\n70          gds__dyn_rel_name, 1,0, 'T',\\n74          gds__dyn_fld_position, 2,0, 0,0,\\n79          gds__dyn_update_flag, 2,0, 1,0,\\n84          gds__dyn_system_flag, 2,0, 0,0,\\n89          gds__dyn_end,\\n90       gds__dyn_def_sql_fld, 31,0, 'C','U','R','R','E','N','C','Y',32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,\\n124          gds__dyn_fld_type, 2,0, 37,0,\\n129          gds__dyn_fld_length, 2,0, 10,0,\\n134          gds__dyn_fld_scale, 2,0, 0,0,\\n139          gds__dyn_rel_name, 1,0, 'T',\\n143          gds__dyn_fld_position, 2,0, 1,0,\\n148          gds__dyn_update_flag, 2,0, 1,0,\\n153          gds__dyn_system_flag, 2,0, 0,0,\\n158          gds__dyn_end,\\n159       gds__dyn_end,\\n0 gds__dyn_eoc", run_time=0)
8459AttachmentInfo(attachment_id=20, database='/opt/firebird/examples/empbuild/employee.fdb', charset='NONE', protocol='<internal>', address='<internal>', user='SYSDBA', role='NONE', remote_process=None, remote_pid=None)
8460TransactionInfo(attachment_id=20, transaction_id=189, options=['CONCURRENCY', 'WAIT', 'READ_WRITE'])
8461EventDYNExecute(event_id=3, timestamp=datetime.datetime(2018, 3, 29, 13, 28, 45, 891000), status=' ', attachment_id=20, transaction_id=189, content=None, run_time=26)
8462"""
8463        self._check_events(trace_lines, output)
8464    def test_unknown(self):
8465        # It could be an event unknown to trace plugin (case 1), or completelly new event unknown to trace parser (case 2)
8466        trace_lines = """2014-05-23T11:00:28.5840 (3720:0000000000EFD9E8) Unknown event in ATTACH_DATABASE
8467	/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)
8468	/opt/firebird/bin/isql:8723
8469
84702018-03-22T10:06:59.5090 (4992:0x7f92a22a4978) EVENT_FROM_THE_FUTURE
8471This event may contain
8472various information
8473which could span
8474multiple lines.
8475
8476Yes, it could be very long!
8477"""
8478        output = """EventUnknown(event_id=1, timestamp=datetime.datetime(2014, 5, 23, 11, 0, 28, 584000), data='Unknown event in ATTACH_DATABASE\\n/home/employee.fdb (ATT_8, SYSDBA:NONE, ISO88591, TCPv4:192.168.1.5)\\n/opt/firebird/bin/isql:8723')
8479EventUnknown(event_id=2, timestamp=datetime.datetime(2018, 3, 22, 10, 6, 59, 509000), data='EVENT_FROM_THE_FUTURE\\nThis event may contain\\nvarious information\\nwhich could span\\nmultiple lines.\\nYes, it could be very long!')
8480"""
8481        self._check_events(trace_lines, output)
8482
8483class TestUtils(FDBTestBase):
8484    def setUp(self):
8485        super(TestUtils, self).setUp()
8486        self.maxDiff = None
8487    def test_objectlist(self):
8488        Item = collections.namedtuple('Item', 'name,size,data')
8489        Point = collections.namedtuple('Point', 'x,y')
8490        data = [Item('A', 100, 'X' * 20),
8491                Item('Aaa', 95, 'X' * 50),
8492                Item('Abb', 90, 'Y' * 20),
8493                Item('B', 85, 'Y' * 50),
8494                Item('Baa', 80, 'Y' * 60),
8495                Item('Bab', 75, 'Z' * 20),
8496                Item('Bba', 65, 'Z' * 50),
8497                Item('Bbb', 70, 'Z' * 50),
8498                Item('C', 0, 'None'),]
8499        #
8500        olist = utils.ObjectList(data)
8501        # basic list operations
8502        self.assertEquals(len(data), len(olist))
8503        self.assertListEqual(data, olist)
8504        self.assertListEqual(data, olist)
8505        self.assertEqual(olist[0], data[0])
8506        self.assertEqual(olist.index(Item('B', 85, 'Y' * 50)), 3)
8507        del olist[3]
8508        self.assertEqual(len(olist), len(data) - 1)
8509        olist.insert(3, Item('B', 85, 'Y' * 50))
8510        self.assertEquals(len(data), len(olist))
8511        self.assertListEqual(data, olist)
8512        # sort - attrs
8513        olist.sort(['name'], reverse=True)
8514        self.assertListEqual(olist, [Item(name='C', size=0, data='None'),
8515                                     Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8516                                     Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8517                                     Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8518                                     Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8519                                     Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8520                                     Item(name='Abb', size=90, data='YYYYYYYYYYYYYYYYYYYY'),
8521                                     Item(name='Aaa', size=95, data='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),
8522                                     Item(name='A', size=100, data='XXXXXXXXXXXXXXXXXXXX')])
8523        olist.sort(['data'])
8524        self.assertListEqual(olist, [Item(name='C', size=0, data='None'),
8525                                     Item(name='A', size=100, data='XXXXXXXXXXXXXXXXXXXX'),
8526                                     Item(name='Aaa', size=95, data='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),
8527                                     Item(name='Abb', size=90, data='YYYYYYYYYYYYYYYYYYYY'),
8528                                     Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8529                                     Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8530                                     Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8531                                     Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8532                                     Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8533        olist.sort(['data', 'size'])
8534        self.assertListEqual(olist, [Item(name='C', size=0, data='None'),
8535                                     Item(name='A', size=100, data='XXXXXXXXXXXXXXXXXXXX'),
8536                                     Item(name='Aaa', size=95, data='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),
8537                                     Item(name='Abb', size=90, data='YYYYYYYYYYYYYYYYYYYY'),
8538                                     Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8539                                     Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8540                                     Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8541                                     Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8542                                     Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8543        olist.sort(['name'])
8544        self.assertListEqual(data, olist)
8545        # sort - expr
8546        olist = utils.ObjectList(data)
8547        olist.sort(expr='item.size * len(item.name)', reverse=True)
8548        self.assertListEqual(olist, [Item(name='Aaa', size=95, data='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),
8549                                     Item(name='Abb', size=90, data='YYYYYYYYYYYYYYYYYYYY'),
8550                                     Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8551                                     Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8552                                     Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8553                                     Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8554                                     Item(name='A', size=100, data='XXXXXXXXXXXXXXXXXXXX'),
8555                                     Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8556                                     Item(name='C', size=0, data='None')])
8557        olist.sort(expr=lambda x: x.size * len(x.name), reverse=True)
8558        self.assertListEqual(olist, [Item(name='Aaa', size=95, data='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'),
8559                                     Item(name='Abb', size=90, data='YYYYYYYYYYYYYYYYYYYY'),
8560                                     Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8561                                     Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8562                                     Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8563                                     Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8564                                     Item(name='A', size=100, data='XXXXXXXXXXXXXXXXXXXX'),
8565                                     Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8566                                     Item(name='C', size=0, data='None')])
8567        # filter/ifilter
8568        olist = utils.ObjectList(data)
8569        fc = olist.filter('item.name.startswith("B")')
8570        self.assertIsInstance(fc, utils.ObjectList)
8571        self.assertListEqual(fc, [Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8572                                   Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8573                                   Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8574                                   Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8575                                   Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8576        fc = olist.filter(lambda x: x.name.startswith("B"))
8577        self.assertListEqual(fc, [Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8578                                   Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8579                                   Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8580                                   Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8581                                   Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8582        self.assertListEqual(list(olist.ifilter('item.name.startswith("B")')),
8583                              [Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8584                               Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8585                               Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8586                               Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8587                               Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8588        self.assertListEqual(list(olist.ifilter(lambda x: x.name.startswith("B"))),
8589                              [Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8590                               Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8591                               Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8592                               Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8593                               Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8594        # report/ireport
8595        self.assertListEqual(olist.report('item.name', 'item.size'),
8596                             [('A', 100), ('Aaa', 95), ('Abb', 90), ('B', 85),
8597                              ('Baa', 80), ('Bab', 75), ('Bba', 65), ('Bbb', 70), ('C', 0)])
8598        self.assertListEqual(olist.report(lambda x: (x.name, x.size)),
8599                             [('A', 100), ('Aaa', 95), ('Abb', 90), ('B', 85),
8600                              ('Baa', 80), ('Bab', 75), ('Bba', 65), ('Bbb', 70), ('C', 0)])
8601        self.assertListEqual(list(olist.ireport('item.name', 'item.size')),
8602                             [('A', 100), ('Aaa', 95), ('Abb', 90), ('B', 85),
8603                              ('Baa', 80), ('Bab', 75), ('Bba', 65), ('Bbb', 70), ('C', 0)])
8604        self.assertListEqual(olist.report('"name: %s, size: %d" % (item.name, item.size)'),
8605                             ['name: A, size: 100', 'name: Aaa, size: 95', 'name: Abb, size: 90',
8606                              'name: B, size: 85', 'name: Baa, size: 80', 'name: Bab, size: 75',
8607                              'name: Bba, size: 65', 'name: Bbb, size: 70', 'name: C, size: 0'])
8608        self.assertListEqual(olist.report(lambda x: "name: %s, size: %d" % (x.name, x.size)),
8609                             ['name: A, size: 100', 'name: Aaa, size: 95', 'name: Abb, size: 90',
8610                              'name: B, size: 85', 'name: Baa, size: 80', 'name: Bab, size: 75',
8611                              'name: Bba, size: 65', 'name: Bbb, size: 70', 'name: C, size: 0'])
8612        # ecount
8613        self.assertEqual(olist.ecount('item.name.startswith("B")'), 5)
8614        self.assertEqual(olist.ecount(lambda x: x.name.startswith("B")), 5)
8615        # split
8616        truelist, falselist = olist.split('item.name.startswith("B")')
8617        self.assertIsInstance(truelist, utils.ObjectList)
8618        self.assertIsInstance(falselist, utils.ObjectList)
8619        self.assertListEqual(list(truelist), [Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8620                                               Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8621                                               Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8622                                               Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8623                                               Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ')])
8624        self.assertListEqual(list(falselist), [Item('A', 100, 'X' * 20), Item('Aaa', 95, 'X' * 50), Item('Abb', 90, 'Y' * 20),
8625                                                Item(name='C', size=0, data='None')])
8626        # extract
8627        truelist = olist.extract('item.name.startswith("A")')
8628        self.assertIsInstance(truelist, utils.ObjectList)
8629        self.assertListEqual(list(truelist), [Item('A', 100, 'X' * 20), Item('Aaa', 95, 'X' * 50), Item('Abb', 90, 'Y' * 20)])
8630        self.assertListEqual(olist, [Item(name='B', size=85, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8631                                      Item(name='Baa', size=80, data='YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'),
8632                                      Item(name='Bab', size=75, data='ZZZZZZZZZZZZZZZZZZZZ'),
8633                                      Item(name='Bba', size=65, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8634                                      Item(name='Bbb', size=70, data='ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'),
8635                                      Item(name='C', size=0, data='None')])
8636        # clear
8637        olist.clear()
8638        self.assertEqual(len(olist), 0, 'list is not empty')
8639        # get
8640        olist.extend(data)
8641        with self.assertRaises(TypeError) as cm:
8642            item = olist.get('Baa')
8643        exc = cm.exception
8644        self.assertEqual(exc.args[0], "Key expression required")
8645        self.assertIs(olist.get('Baa', 'item.name'), olist[4])
8646        olist = utils.ObjectList(data, Item, 'item.name')
8647        self.assertIs(olist.get('Baa'), olist[4])
8648        self.assertIs(olist.get(80, 'item.size'), olist[4])
8649        self.assertIs(olist.get(80, lambda x, value: x.size == value), olist[4])
8650        olist.freeze()  # Frozen list uses O(1) access via dict!
8651        self.assertIs(olist.get('Baa'), olist[4])
8652        # contains
8653        self.assertTrue(olist.contains('Baa'))
8654        self.assertFalse(olist.contains('FX'))
8655        self.assertTrue(olist.contains('Baa', 'item.name'))
8656        self.assertTrue(olist.contains(80, 'item.size'))
8657        self.assertTrue(olist.contains(80, lambda x, value: x.size == value))
8658        # immutability
8659        olist = utils.ObjectList(data)
8660        self.assertFalse(olist.frozen, "list is frozen")
8661        self.assertListEqual(olist, data)
8662        olist.freeze()
8663        self.assertTrue(olist.frozen, "list is not frozen")
8664        with self.assertRaises(TypeError) as cm:
8665            olist[0] = Point(1, 1)
8666        exc = cm.exception
8667        self.assertEqual(exc.args[0], "list is frozen")
8668        with self.assertRaises(TypeError) as cm:
8669            olist[0:2] = [Point(1, 1)]
8670        exc = cm.exception
8671        self.assertEqual(exc.args[0], "list is frozen")
8672        with self.assertRaises(TypeError) as cm:
8673            olist.append(Point(1, 1))
8674        exc = cm.exception
8675        self.assertEqual(exc.args[0], "list is frozen")
8676        with self.assertRaises(TypeError) as cm:
8677            olist.insert(0, [Point(1, 1)])
8678        exc = cm.exception
8679        self.assertEqual(exc.args[0], "list is frozen")
8680        with self.assertRaises(TypeError) as cm:
8681            olist.extend([Point(1, 1)])
8682        exc = cm.exception
8683        self.assertEqual(exc.args[0], "list is frozen")
8684        with self.assertRaises(TypeError) as cm:
8685            olist.clear()
8686        exc = cm.exception
8687        self.assertEqual(exc.args[0], "list is frozen")
8688        with self.assertRaises(TypeError) as cm:
8689            olist.extract('True')
8690        exc = cm.exception
8691        self.assertEqual(exc.args[0], "list is frozen")
8692        with self.assertRaises(TypeError) as cm:
8693            del olist[0]
8694        exc = cm.exception
8695        self.assertEqual(exc.args[0], "list is frozen")
8696        with self.assertRaises(TypeError) as cm:
8697            del olist[0:2]
8698        exc = cm.exception
8699        self.assertEqual(exc.args[0], "list is frozen")
8700        # Limit to class(es)
8701        olist = utils.ObjectList(data, Item)
8702        olist = utils.ObjectList(_cls=(Item, Point))
8703        olist.append(Point(1, 1))
8704        olist.insert(0, Item('A', 10, 'XXX'))
8705        olist[1] = Point(2, 2)
8706        self.assertListEqual(olist, [Item('A', 10, 'XXX'), Point(2, 2)])
8707        with self.assertRaises(TypeError) as cm:
8708            olist.append(list())
8709        exc = cm.exception
8710        self.assertEqual(exc.args[0], "Value is not an instance of allowed class")
8711        # Key
8712        olist = utils.ObjectList(data, Item, 'item.name')
8713        self.assertEqual(olist.get('A'), Item('A', 100, 'X' * 20))
8714        # any/all
8715        self.assertFalse(olist.all('item.size > 0'))
8716        self.assertTrue(olist.all('item.size < 200'))
8717        self.assertTrue(olist.any('item.size > 0'))
8718        self.assertTrue(olist.any('item.size < 200'))
8719        self.assertFalse(olist.any('item.size > 300'))
8720
8721class TestGstatParse(FDBTestBase):
8722    def setUp(self):
8723        super(TestGstatParse, self).setUp()
8724    def _parse_file(self, filename):
8725        with open(filename) as f:
8726            return gstat.parse(f)
8727    def test_locale(self):
8728        locale = getlocale(LC_ALL)
8729        if locale[0] is None:
8730            setlocale(LC_ALL,'')
8731            locale = getlocale(LC_ALL)
8732        try:
8733            db = self._parse_file(os.path.join(self.dbpath, 'gstat25-h.out'))
8734            self.assertEquals(locale, getlocale(LC_ALL), "Locale must not change")
8735            if sys.platform == 'win32':
8736                setlocale(LC_ALL, 'Czech_Czech Republic')
8737            else:
8738                setlocale(LC_ALL, 'cs_CZ')
8739            nlocale = getlocale(LC_ALL)
8740            db = self._parse_file(os.path.join(self.dbpath, 'gstat25-h.out'))
8741            self.assertEquals(nlocale, getlocale(LC_ALL), "Locale must not change")
8742        finally:
8743            pass
8744            #setlocale(LC_ALL, locale)
8745    def test_parse25_h(self):
8746        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-h.out'))
8747        data = {'attributes': (0,), 'backup_diff_file': None, 'backup_guid': None, 'bumped_transaction': 1,
8748                'checksum': 12345, 'completed': None, 'continuation_file': None, 'continuation_files': 0,
8749                'creation_date': datetime.datetime(2013, 5, 27, 23, 40, 53), 'database_dialect': 3,
8750                'encrypted_blob_pages': None, 'encrypted_data_pages': None, 'encrypted_index_pages': None,
8751                'executed': datetime.datetime(2018, 4, 4, 15, 29, 10), 'filename': '/home/fdb/test/fbtest25.fdb',
8752                'flags': 0, 'generation': 2844, 'gstat_version': 2, 'implementation': None, 'implementation_id': 24,
8753                'indices': 0, 'last_logical_page': None, 'next_attachment_id': 1067, 'next_header_page': 0, 'next_transaction': 1807,
8754                'oat': 1807, 'ods_version': '11.2', 'oit': 204, 'ost': 1807, 'page_buffers': 0, 'page_size': 4096,
8755                'replay_logging_file': None, 'root_filename': None, 'sequence_number': 0, 'shadow_count': 0, 'sweep_interval': 20000,
8756                'system_change_number': None, 'tables': 0}
8757        self.assertIsInstance(db, gstat.StatDatabase)
8758        self.assertDictEqual(data, get_object_data(db), 'Unexpected output from parser (database hdr)')
8759        #
8760        self.assertFalse(db.has_table_stats())
8761        self.assertFalse(db.has_index_stats())
8762        self.assertFalse(db.has_row_stats())
8763        self.assertFalse(db.has_encryption_stats())
8764        self.assertFalse(db.has_system())
8765    def test_parse25_a(self):
8766        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-a.out'))
8767        # Database
8768        data = {'attributes': (0,), 'backup_diff_file': None, 'backup_guid': None, 'bumped_transaction': 1,
8769                'checksum': 12345, 'completed': None, 'continuation_file': None, 'continuation_files': 0,
8770                'creation_date': datetime.datetime(2013, 5, 27, 23, 40, 53), 'database_dialect': 3,
8771                'encrypted_blob_pages': None, 'encrypted_data_pages': None, 'encrypted_index_pages': None,
8772                'executed': datetime.datetime(2018, 4, 4, 15, 30, 10), 'filename': '/home/fdb/test/fbtest25.fdb',
8773                'flags': 0, 'generation': 2844, 'gstat_version': 2, 'implementation': None, 'implementation_id': 24,
8774                'indices': 39, 'last_logical_page': None, 'next_attachment_id': 1067, 'next_header_page': 0, 'next_transaction': 1807,
8775                'oat': 1807, 'ods_version': '11.2', 'oit': 204, 'ost': 1807, 'page_buffers': 0, 'page_size': 4096,
8776                'replay_logging_file': None, 'root_filename': None, 'sequence_number': 0, 'shadow_count': 0, 'sweep_interval': 20000,
8777                'system_change_number': None, 'tables': 15}
8778        self.assertDictEqual(data, get_object_data(db), 'Unexpected output from parser (database hdr)')
8779        #
8780        self.assertTrue(db.has_table_stats())
8781        self.assertTrue(db.has_index_stats())
8782        self.assertFalse(db.has_row_stats())
8783        self.assertFalse(db.has_encryption_stats())
8784        self.assertFalse(db.has_system())
8785        # Tables
8786        data = [{'avg_fill': 86, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8787                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 210, 'indices': 0,
8788                 'max_versions': None, 'name': 'AR', 'primary_pointer_page': 209, 'table_id': 142, 'total_records': None,
8789                 'total_versions': None},
8790                {'avg_fill': 15, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8791                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 181, 'indices': 1,
8792                 'max_versions': None, 'name': 'COUNTRY', 'primary_pointer_page': 180, 'table_id': 128, 'total_records': None,
8793                 'total_versions': None},
8794                {'avg_fill': 53, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8795                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 189, 'indices': 4,
8796                 'max_versions': None, 'name': 'CUSTOMER', 'primary_pointer_page': 188, 'table_id': 132, 'total_records': None,
8797                 'total_versions': None},
8798                {'avg_fill': 47, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8799                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 185, 'indices': 5,
8800                 'max_versions': None, 'name': 'DEPARTMENT', 'primary_pointer_page': 184, 'table_id': 130, 'total_records': None,
8801                 'total_versions': None},
8802                {'avg_fill': 44, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 2, 'data_pages': 2,
8803                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 187, 'indices': 4,
8804                 'max_versions': None, 'name': 'EMPLOYEE', 'primary_pointer_page': 186, 'table_id': 131, 'total_records': None,
8805                 'total_versions': None},
8806                {'avg_fill': 20, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8807                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 196, 'indices': 3,
8808                 'max_versions': None, 'name': 'EMPLOYEE_PROJECT', 'primary_pointer_page': 195, 'table_id': 135, 'total_records': None,
8809                 'total_versions': None},
8810                {'avg_fill': 73, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 3, 'data_pages': 3,
8811                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=2), 'index_root_page': 183, 'indices': 4,
8812                 'max_versions': None, 'name': 'JOB', 'primary_pointer_page': 182, 'table_id': 129, 'total_records': None,
8813                 'total_versions': None},
8814                {'avg_fill': 29, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8815                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_root_page': 194, 'indices': 4,
8816                 'max_versions': None, 'name': 'PROJECT', 'primary_pointer_page': 193, 'table_id': 134, 'total_records': None,
8817                 'total_versions': None},
8818                {'avg_fill': 80, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8819                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 198, 'indices': 3,
8820                 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET', 'primary_pointer_page': 197, 'table_id': 136, 'total_records': None,
8821                 'total_versions': None},
8822                {'avg_fill': 58, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8823                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 200, 'indices': 4,
8824                 'max_versions': None, 'name': 'SALARY_HISTORY', 'primary_pointer_page': 199, 'table_id': 137, 'total_records': None,
8825                 'total_versions': None},
8826                {'avg_fill': 68, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8827                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 202, 'indices': 6,
8828                 'max_versions': None, 'name': 'SALES', 'primary_pointer_page': 201, 'table_id': 138, 'total_records': None,
8829                 'total_versions': None},
8830                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
8831                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 282, 'indices': 1,
8832                 'max_versions': None, 'name': 'T', 'primary_pointer_page': 205, 'table_id': 235, 'total_records': None,
8833                 'total_versions': None},
8834                {'avg_fill': 20, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8835                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 208, 'indices': 0,
8836                 'max_versions': None, 'name': 'T2', 'primary_pointer_page': 207, 'table_id': 141, 'total_records': None,
8837                 'total_versions': None},
8838                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
8839                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 204, 'indices': 0,
8840                 'max_versions': None, 'name': 'T3', 'primary_pointer_page': 203, 'table_id': 139, 'total_records': None,
8841                 'total_versions': None},
8842                {'avg_fill': 4, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8843                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 192, 'indices': 0,
8844                 'max_versions': None, 'name': 'T4', 'primary_pointer_page': 191, 'table_id': 133, 'total_records': None,
8845                 'total_versions': None}]
8846        i = 0
8847        while i < len(db.tables):
8848            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
8849            i += 1
8850        # Indices
8851        data = [{'avg_data_length': 6.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8852                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY1', 'nodes': 14, 'total_dup': 0},
8853                {'avg_data_length': 15.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8854                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTNAMEX', 'nodes': 15, 'total_dup': 0},
8855                {'avg_data_length': 17.27, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8856                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTREGION', 'nodes': 15, 'total_dup': 0},
8857                {'avg_data_length': 4.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8858                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN23', 'nodes': 15, 'total_dup': 4},
8859                {'avg_data_length': 1.13, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8860                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY22', 'nodes': 15, 'total_dup': 0},
8861                {'avg_data_length': 5.38, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8862                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'BUDGETX', 'nodes': 21, 'total_dup': 7},
8863                {'avg_data_length': 13.95, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8864                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$4', 'nodes': 21, 'total_dup': 0},
8865                {'avg_data_length': 1.14, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4,
8866                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'RDB$FOREIGN10', 'nodes': 21, 'total_dup': 3},
8867                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8868                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN6', 'nodes': 21, 'total_dup': 13},
8869                {'avg_data_length': 1.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8870                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY5', 'nodes': 21, 'total_dup': 0},
8871                {'avg_data_length': 15.52, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_id': 1,
8872                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'NAMEX', 'nodes': 42, 'total_dup': 0},
8873                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8874                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN8', 'nodes': 42, 'total_dup': 23},
8875                {'avg_data_length': 6.79, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8876                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN9', 'nodes': 42, 'total_dup': 15},
8877                {'avg_data_length': 1.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8878                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY7', 'nodes': 42, 'total_dup': 0},
8879                {'avg_data_length': 1.04, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8880                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN15', 'nodes': 28, 'total_dup': 6},
8881                {'avg_data_length': 0.86, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8882                 'leaf_buckets': 1, 'max_dup': 9, 'name': 'RDB$FOREIGN16', 'nodes': 28, 'total_dup': 23},
8883                {'avg_data_length': 9.11, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8884                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY14', 'nodes': 28, 'total_dup': 0},
8885                {'avg_data_length': 10.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8886                 'leaf_buckets': 1, 'max_dup': 1, 'name': 'MAXSALX', 'nodes': 31, 'total_dup': 5},
8887                {'avg_data_length': 10.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8888                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'MINSALX', 'nodes': 31, 'total_dup': 7},
8889                {'avg_data_length': 1.39, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8890                 'leaf_buckets': 1, 'max_dup': 20, 'name': 'RDB$FOREIGN3', 'nodes': 31, 'total_dup': 24},
8891                {'avg_data_length': 10.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8892                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY2', 'nodes': 31, 'total_dup': 0},
8893                {'avg_data_length': 22.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8894                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'PRODTYPEX', 'nodes': 6, 'total_dup': 0},
8895                {'avg_data_length': 13.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8896                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$11', 'nodes': 6, 'total_dup': 0},
8897                {'avg_data_length': 1.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8898                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$FOREIGN13', 'nodes': 6, 'total_dup': 0},
8899                {'avg_data_length': 4.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8900                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY12', 'nodes': 6, 'total_dup': 0},
8901                {'avg_data_length': 0.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8902                 'leaf_buckets': 1, 'max_dup': 5, 'name': 'RDB$FOREIGN18', 'nodes': 24, 'total_dup': 15},
8903                {'avg_data_length': 1.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8904                 'leaf_buckets': 1, 'max_dup': 8, 'name': 'RDB$FOREIGN19', 'nodes': 24, 'total_dup': 19},
8905                {'avg_data_length': 6.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8906                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY17', 'nodes': 24, 'total_dup': 0},
8907                {'avg_data_length': 0.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8908                 'leaf_buckets': 1, 'max_dup': 21, 'name': 'CHANGEX', 'nodes': 49, 'total_dup': 46},
8909                {'avg_data_length': 0.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8910                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN21', 'nodes': 49, 'total_dup': 16},
8911                {'avg_data_length': 18.29, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_id': 0,
8912                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY20', 'nodes': 49, 'total_dup': 0},
8913                {'avg_data_length': 0.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8914                 'leaf_buckets': 1, 'max_dup': 28, 'name': 'UPDATERX', 'nodes': 49, 'total_dup': 46},
8915                {'avg_data_length': 2.55, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
8916                 'leaf_buckets': 1, 'max_dup': 6, 'name': 'NEEDX', 'nodes': 33, 'total_dup': 11},
8917                {'avg_data_length': 1.85, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
8918                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'QTYX', 'nodes': 33, 'total_dup': 11},
8919                {'avg_data_length': 0.52, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4,
8920                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN25', 'nodes': 33, 'total_dup': 18},
8921                {'avg_data_length': 0.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 5,
8922                 'leaf_buckets': 1, 'max_dup': 7, 'name': 'RDB$FOREIGN26', 'nodes': 33, 'total_dup': 25},
8923                {'avg_data_length': 4.48, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8924                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY24', 'nodes': 33, 'total_dup': 0},
8925                {'avg_data_length': 0.97, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
8926                 'leaf_buckets': 1, 'max_dup': 14, 'name': 'SALESTATX', 'nodes': 33, 'total_dup': 27},
8927                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
8928                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY104', 'nodes': 0, 'total_dup': 0}]
8929        i = 0
8930        while i < len(db.tables):
8931            self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
8932            i += 1
8933    def test_parse25_d(self):
8934        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-d.out'))
8935        # Database
8936        data = {'attributes': (0,), 'backup_diff_file': None, 'backup_guid': None, 'bumped_transaction': 1,
8937                'checksum': 12345, 'completed': None, 'continuation_file': None, 'continuation_files': 0,
8938                'creation_date': datetime.datetime(2013, 5, 27, 23, 40, 53), 'database_dialect': 3,
8939                'encrypted_blob_pages': None, 'encrypted_data_pages': None, 'encrypted_index_pages': None,
8940                'executed': datetime.datetime(2018, 4, 4, 15, 32, 25), 'filename': '/home/fdb/test/fbtest25.fdb',
8941                'flags': 0, 'generation': 2856, 'gstat_version': 2, 'implementation': None, 'implementation_id': 24,
8942                'indices': 0, 'last_logical_page': None, 'next_attachment_id': 1071, 'next_header_page': 0, 'next_transaction': 1811,
8943                'oat': 1811, 'ods_version': '11.2', 'oit': 204, 'ost': 1811, 'page_buffers': 0, 'page_size': 4096,
8944                'replay_logging_file': None, 'root_filename': None, 'sequence_number': 0, 'shadow_count': 0, 'sweep_interval': 20000,
8945                'system_change_number': None, 'tables': 15}
8946        self.assertDictEqual(data, get_object_data(db), 'Unexpected output from parser (database hdr)')
8947        #
8948        self.assertTrue(db.has_table_stats())
8949        self.assertFalse(db.has_index_stats())
8950        self.assertFalse(db.has_row_stats())
8951        self.assertFalse(db.has_encryption_stats())
8952        self.assertFalse(db.has_system())
8953        # Tables
8954        data = [{'avg_fill': 86, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8955                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 210, 'indices': 0,
8956                 'max_versions': None, 'name': 'AR', 'primary_pointer_page': 209, 'table_id': 142, 'total_records': None,
8957                 'total_versions': None},
8958                {'avg_fill': 15, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8959                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 181, 'indices': 0,
8960                 'max_versions': None, 'name': 'COUNTRY', 'primary_pointer_page': 180, 'table_id': 128, 'total_records': None,
8961                 'total_versions': None},
8962                {'avg_fill': 53, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8963                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 189, 'indices': 0,
8964                 'max_versions': None, 'name': 'CUSTOMER', 'primary_pointer_page': 188, 'table_id': 132, 'total_records': None,
8965                 'total_versions': None},
8966                {'avg_fill': 47, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8967                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 185, 'indices': 0,
8968                 'max_versions': None, 'name': 'DEPARTMENT', 'primary_pointer_page': 184, 'table_id': 130, 'total_records': None,
8969                 'total_versions': None},
8970                {'avg_fill': 44, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 2, 'data_pages': 2,
8971                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 187, 'indices': 0,
8972                 'max_versions': None, 'name': 'EMPLOYEE', 'primary_pointer_page': 186, 'table_id': 131, 'total_records': None,
8973                 'total_versions': None},
8974                {'avg_fill': 20, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8975                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 196, 'indices': 0,
8976                 'max_versions': None, 'name': 'EMPLOYEE_PROJECT', 'primary_pointer_page': 195, 'table_id': 135, 'total_records': None,
8977                 'total_versions': None},
8978                {'avg_fill': 73, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 3, 'data_pages': 3,
8979                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=2), 'index_root_page': 183, 'indices': 0,
8980                 'max_versions': None, 'name': 'JOB', 'primary_pointer_page': 182, 'table_id': 129, 'total_records': None,
8981                 'total_versions': None},
8982                {'avg_fill': 29, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8983                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_root_page': 194, 'indices': 0,
8984                 'max_versions': None, 'name': 'PROJECT', 'primary_pointer_page': 193, 'table_id': 134, 'total_records': None,
8985                 'total_versions': None},
8986                {'avg_fill': 80, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8987                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 198, 'indices': 0,
8988                 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET', 'primary_pointer_page': 197, 'table_id': 136, 'total_records': None,
8989                 'total_versions': None},
8990                {'avg_fill': 58, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8991                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 200, 'indices': 0,
8992                 'max_versions': None, 'name': 'SALARY_HISTORY', 'primary_pointer_page': 199, 'table_id': 137, 'total_records': None,
8993                 'total_versions': None},
8994                {'avg_fill': 68, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
8995                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 202, 'indices': 0,
8996                 'max_versions': None, 'name': 'SALES', 'primary_pointer_page': 201, 'table_id': 138, 'total_records': None,
8997                 'total_versions': None},
8998                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
8999                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 282, 'indices': 0,
9000                 'max_versions': None, 'name': 'T', 'primary_pointer_page': 205, 'table_id': 235, 'total_records': None,
9001                 'total_versions': None},
9002                {'avg_fill': 20, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9003                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 208, 'indices': 0,
9004                 'max_versions': None, 'name': 'T2', 'primary_pointer_page': 207, 'table_id': 141, 'total_records': None,
9005                 'total_versions': None},
9006                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9007                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 204, 'indices': 0,
9008                 'max_versions': None, 'name': 'T3', 'primary_pointer_page': 203, 'table_id': 139, 'total_records': None,
9009                 'total_versions': None},
9010                {'avg_fill': 4, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9011                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 192, 'indices': 0,
9012                 'max_versions': None, 'name': 'T4', 'primary_pointer_page': 191, 'table_id': 133, 'total_records': None,
9013                 'total_versions': None}]
9014        i = 0
9015        while i < len(db.tables):
9016            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
9017            i += 1
9018        # Indices
9019        self.assertEqual(len(db.indices), 0)
9020    def test_parse25_f(self):
9021        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-f.out'))
9022        #
9023        self.assertTrue(db.has_table_stats())
9024        self.assertTrue(db.has_index_stats())
9025        self.assertTrue(db.has_row_stats())
9026        self.assertFalse(db.has_encryption_stats())
9027        self.assertTrue(db.has_system())
9028        # Check system tables
9029        data = ['RDB$BACKUP_HISTORY', 'RDB$CHARACTER_SETS', 'RDB$CHECK_CONSTRAINTS', 'RDB$COLLATIONS', 'RDB$DATABASE', 'RDB$DEPENDENCIES',
9030                'RDB$EXCEPTIONS', 'RDB$FIELDS', 'RDB$FIELD_DIMENSIONS', 'RDB$FILES', 'RDB$FILTERS', 'RDB$FORMATS', 'RDB$FUNCTIONS',
9031                'RDB$FUNCTION_ARGUMENTS', 'RDB$GENERATORS', 'RDB$INDEX_SEGMENTS', 'RDB$INDICES', 'RDB$LOG_FILES', 'RDB$PAGES',
9032                'RDB$PROCEDURES', 'RDB$PROCEDURE_PARAMETERS', 'RDB$REF_CONSTRAINTS', 'RDB$RELATIONS', 'RDB$RELATION_CONSTRAINTS',
9033                'RDB$RELATION_FIELDS', 'RDB$ROLES', 'RDB$SECURITY_CLASSES', 'RDB$TRANSACTIONS', 'RDB$TRIGGERS', 'RDB$TRIGGER_MESSAGES',
9034                'RDB$TYPES', 'RDB$USER_PRIVILEGES', 'RDB$VIEW_RELATIONS']
9035        for table in db.tables:
9036            if table.name.startswith('RDB$'):
9037                self.assertIn(table.name, data)
9038        # check system indices
9039        data = ['RDB$PRIMARY1', 'RDB$FOREIGN23', 'RDB$PRIMARY22', 'RDB$4', 'RDB$FOREIGN10', 'RDB$FOREIGN6', 'RDB$PRIMARY5', 'RDB$FOREIGN8',
9040                'RDB$FOREIGN9', 'RDB$PRIMARY7', 'RDB$FOREIGN15', 'RDB$FOREIGN16', 'RDB$PRIMARY14', 'RDB$FOREIGN3', 'RDB$PRIMARY2', 'RDB$11',
9041                'RDB$FOREIGN13', 'RDB$PRIMARY12', 'RDB$FOREIGN18', 'RDB$FOREIGN19', 'RDB$PRIMARY17', 'RDB$INDEX_44', 'RDB$INDEX_19',
9042                'RDB$INDEX_25', 'RDB$INDEX_14', 'RDB$INDEX_40', 'RDB$INDEX_20', 'RDB$INDEX_26', 'RDB$INDEX_27', 'RDB$INDEX_28',
9043                'RDB$INDEX_23', 'RDB$INDEX_24', 'RDB$INDEX_2', 'RDB$INDEX_36', 'RDB$INDEX_17', 'RDB$INDEX_45', 'RDB$INDEX_16', 'RDB$INDEX_9',
9044                'RDB$INDEX_10', 'RDB$INDEX_11', 'RDB$INDEX_46', 'RDB$INDEX_6', 'RDB$INDEX_31', 'RDB$INDEX_41', 'RDB$INDEX_5', 'RDB$INDEX_21',
9045                'RDB$INDEX_22', 'RDB$INDEX_18', 'RDB$INDEX_47', 'RDB$INDEX_48', 'RDB$INDEX_13', 'RDB$INDEX_0', 'RDB$INDEX_1', 'RDB$INDEX_12',
9046                'RDB$INDEX_42', 'RDB$INDEX_43', 'RDB$INDEX_15', 'RDB$INDEX_3', 'RDB$INDEX_4', 'RDB$INDEX_39', 'RDB$INDEX_7', 'RDB$INDEX_32',
9047                'RDB$INDEX_38', 'RDB$INDEX_8', 'RDB$INDEX_35', 'RDB$INDEX_37', 'RDB$INDEX_29', 'RDB$INDEX_30', 'RDB$INDEX_33', 'RDB$INDEX_34',
9048                'RDB$FOREIGN21', 'RDB$PRIMARY20', 'RDB$FOREIGN25', 'RDB$FOREIGN26', 'RDB$PRIMARY24', 'RDB$PRIMARY104']
9049        for index in db.indices:
9050            if index.name.startswith('RDB$'):
9051                self.assertIn(index.name, data)
9052    def test_parse25_i(self):
9053        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-i.out'))
9054        #
9055        self.assertFalse(db.has_table_stats())
9056        self.assertTrue(db.has_index_stats())
9057        self.assertFalse(db.has_row_stats())
9058        self.assertFalse(db.has_encryption_stats())
9059        # Tables
9060        data = [{'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9061                 'distribution': None, 'index_root_page': None, 'indices': 0, 'max_versions': None, 'name': 'AR',
9062                 'primary_pointer_page': None, 'table_id': 142, 'total_records': None, 'total_versions': None},
9063                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9064                 'distribution': None, 'index_root_page': None, 'indices': 1, 'max_versions': None, 'name': 'COUNTRY',
9065                 'primary_pointer_page': None, 'table_id': 128, 'total_records': None, 'total_versions': None},
9066                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9067                 'distribution': None, 'index_root_page': None, 'indices': 4, 'max_versions': None, 'name': 'CUSTOMER',
9068                 'primary_pointer_page': None, 'table_id': 132, 'total_records': None, 'total_versions': None},
9069                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9070                 'distribution': None, 'index_root_page': None, 'indices': 5, 'max_versions': None, 'name': 'DEPARTMENT',
9071                 'primary_pointer_page': None, 'table_id': 130, 'total_records': None, 'total_versions': None},
9072                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9073                 'distribution': None, 'index_root_page': None, 'indices': 4, 'max_versions': None, 'name': 'EMPLOYEE',
9074                 'primary_pointer_page': None, 'table_id': 131, 'total_records': None, 'total_versions': None},
9075                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9076                 'distribution': None, 'index_root_page': None, 'indices': 3, 'max_versions': None, 'name': 'EMPLOYEE_PROJECT',
9077                 'primary_pointer_page': None, 'table_id': 135, 'total_records': None, 'total_versions': None},
9078                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9079                 'distribution': None, 'index_root_page': None, 'indices': 4, 'max_versions': None, 'name': 'JOB',
9080                 'primary_pointer_page': None, 'table_id': 129, 'total_records': None, 'total_versions': None},
9081                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9082                 'distribution': None, 'index_root_page': None, 'indices': 4, 'max_versions': None, 'name': 'PROJECT',
9083                 'primary_pointer_page': None, 'table_id': 134, 'total_records': None, 'total_versions': None},
9084                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9085                 'distribution': None, 'index_root_page': None, 'indices': 3, 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET',
9086                 'primary_pointer_page': None, 'table_id': 136, 'total_records': None, 'total_versions': None},
9087                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9088                 'distribution': None, 'index_root_page': None, 'indices': 4, 'max_versions': None, 'name': 'SALARY_HISTORY',
9089                 'primary_pointer_page': None, 'table_id': 137, 'total_records': None, 'total_versions': None},
9090                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9091                 'distribution': None, 'index_root_page': None, 'indices': 6, 'max_versions': None, 'name': 'SALES',
9092                 'primary_pointer_page': None, 'table_id': 138, 'total_records': None, 'total_versions': None},
9093                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9094                 'distribution': None, 'index_root_page': None, 'indices': 1, 'max_versions': None, 'name': 'T',
9095                 'primary_pointer_page': None, 'table_id': 235, 'total_records': None, 'total_versions': None},
9096                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9097                 'distribution': None, 'index_root_page': None, 'indices': 0, 'max_versions': None, 'name': 'T2',
9098                 'primary_pointer_page': None, 'table_id': 141, 'total_records': None, 'total_versions': None},
9099                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9100                 'distribution': None, 'index_root_page': None, 'indices': 0, 'max_versions': None, 'name': 'T3',
9101                 'primary_pointer_page': None, 'table_id': 139, 'total_records': None, 'total_versions': None},
9102                {'avg_fill': None, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': None, 'data_pages': None,
9103                 'distribution': None, 'index_root_page': None, 'indices': 0, 'max_versions': None, 'name': 'T4',
9104                 'primary_pointer_page': None, 'table_id': 133, 'total_records': None, 'total_versions': None}]
9105        i = 0
9106        while i < len(db.tables):
9107            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
9108            i += 1
9109        # Indices
9110        #data = []
9111        #for t in db.indices:
9112            #data.append(get_object_data(t, ['table']))
9113        #pprint(data)
9114        data = [{'avg_data_length': 6.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9115                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY1', 'nodes': 14, 'total_dup': 0},
9116                {'avg_data_length': 15.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9117                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTNAMEX', 'nodes': 15, 'total_dup': 0},
9118                {'avg_data_length': 17.27, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9119                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTREGION', 'nodes': 15, 'total_dup': 0},
9120                {'avg_data_length': 4.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9121                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN23', 'nodes': 15, 'total_dup': 4},
9122                {'avg_data_length': 1.13, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9123                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY22', 'nodes': 15, 'total_dup': 0},
9124                {'avg_data_length': 5.38, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9125                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'BUDGETX', 'nodes': 21, 'total_dup': 7},
9126                {'avg_data_length': 13.95, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9127                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$4', 'nodes': 21, 'total_dup': 0},
9128                {'avg_data_length': 1.14, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4,
9129                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'RDB$FOREIGN10', 'nodes': 21, 'total_dup': 3},
9130                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9131                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN6', 'nodes': 21, 'total_dup': 13},
9132                {'avg_data_length': 1.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9133                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY5', 'nodes': 21, 'total_dup': 0},
9134                {'avg_data_length': 15.52, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_id': 1,
9135                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'NAMEX', 'nodes': 42, 'total_dup': 0},
9136                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9137                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN8', 'nodes': 42, 'total_dup': 23},
9138                {'avg_data_length': 6.79, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9139                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN9', 'nodes': 42, 'total_dup': 15},
9140                {'avg_data_length': 1.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9141                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY7', 'nodes': 42, 'total_dup': 0},
9142                {'avg_data_length': 1.04, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9143                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN15', 'nodes': 28, 'total_dup': 6},
9144                {'avg_data_length': 0.86, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9145                 'leaf_buckets': 1, 'max_dup': 9, 'name': 'RDB$FOREIGN16', 'nodes': 28, 'total_dup': 23},
9146                {'avg_data_length': 9.11, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9147                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY14', 'nodes': 28, 'total_dup': 0},
9148                {'avg_data_length': 10.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9149                 'leaf_buckets': 1, 'max_dup': 1, 'name': 'MAXSALX', 'nodes': 31, 'total_dup': 5},
9150                {'avg_data_length': 10.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9151                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'MINSALX', 'nodes': 31, 'total_dup': 7},
9152                {'avg_data_length': 1.39, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9153                 'leaf_buckets': 1, 'max_dup': 20, 'name': 'RDB$FOREIGN3', 'nodes': 31, 'total_dup': 24},
9154                {'avg_data_length': 10.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9155                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY2', 'nodes': 31, 'total_dup': 0},
9156                {'avg_data_length': 22.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9157                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'PRODTYPEX', 'nodes': 6, 'total_dup': 0},
9158                {'avg_data_length': 13.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9159                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$11', 'nodes': 6, 'total_dup': 0},
9160                {'avg_data_length': 1.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9161                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$FOREIGN13', 'nodes': 6, 'total_dup': 0},
9162                {'avg_data_length': 4.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9163                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY12', 'nodes': 6, 'total_dup': 0},
9164                {'avg_data_length': 0.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9165                 'leaf_buckets': 1, 'max_dup': 5, 'name': 'RDB$FOREIGN18', 'nodes': 24, 'total_dup': 15},
9166                {'avg_data_length': 1.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9167                 'leaf_buckets': 1, 'max_dup': 8, 'name': 'RDB$FOREIGN19', 'nodes': 24, 'total_dup': 19},
9168                {'avg_data_length': 6.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9169                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY17', 'nodes': 24, 'total_dup': 0},
9170                {'avg_data_length': 0.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9171                 'leaf_buckets': 1, 'max_dup': 21, 'name': 'CHANGEX', 'nodes': 49, 'total_dup': 46},
9172                {'avg_data_length': 0.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9173                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN21', 'nodes': 49, 'total_dup': 16},
9174                {'avg_data_length': 18.29, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_id': 0,
9175                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY20', 'nodes': 49, 'total_dup': 0},
9176                {'avg_data_length': 0.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9177                 'leaf_buckets': 1, 'max_dup': 28, 'name': 'UPDATERX', 'nodes': 49, 'total_dup': 46},
9178                {'avg_data_length': 2.55, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9179                 'leaf_buckets': 1, 'max_dup': 6, 'name': 'NEEDX', 'nodes': 33, 'total_dup': 11},
9180                {'avg_data_length': 1.85, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9181                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'QTYX', 'nodes': 33, 'total_dup': 11},
9182                {'avg_data_length': 0.52, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4,
9183                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN25', 'nodes': 33, 'total_dup': 18},
9184                {'avg_data_length': 0.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 5,
9185                 'leaf_buckets': 1, 'max_dup': 7, 'name': 'RDB$FOREIGN26', 'nodes': 33, 'total_dup': 25},
9186                {'avg_data_length': 4.48, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9187                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY24', 'nodes': 33, 'total_dup': 0},
9188                {'avg_data_length': 0.97, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9189                 'leaf_buckets': 1, 'max_dup': 14, 'name': 'SALESTATX', 'nodes': 33, 'total_dup': 27},
9190                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9191                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY104', 'nodes': 0, 'total_dup': 0}]
9192        i = 0
9193        while i < len(db.tables):
9194            self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
9195            i += 1
9196    def test_parse25_r(self):
9197        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-r.out'))
9198        #
9199        self.assertTrue(db.has_table_stats())
9200        self.assertTrue(db.has_index_stats())
9201        self.assertTrue(db.has_row_stats())
9202        self.assertFalse(db.has_encryption_stats())
9203        self.assertFalse(db.has_system())
9204        # Tables
9205        data = [{'avg_fill': 86, 'avg_record_length': 22.07, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9206                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 210, 'indices': 0,
9207                 'max_versions': 0, 'name': 'AR', 'primary_pointer_page': 209, 'table_id': 142, 'total_records': 15, 'total_versions': 0},
9208                {'avg_fill': 15, 'avg_record_length': 26.86, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9209                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 181, 'indices': 1,
9210                 'max_versions': 0, 'name': 'COUNTRY', 'primary_pointer_page': 180, 'table_id': 128, 'total_records': 14, 'total_versions': 0},
9211                {'avg_fill': 53, 'avg_record_length': 126.47, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9212                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 189, 'indices': 4,
9213                 'max_versions': 0, 'name': 'CUSTOMER', 'primary_pointer_page': 188, 'table_id': 132, 'total_records': 15, 'total_versions': 0},
9214                {'avg_fill': 47, 'avg_record_length': 73.62, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9215                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 185, 'indices': 5,
9216                 'max_versions': 0, 'name': 'DEPARTMENT', 'primary_pointer_page': 184, 'table_id': 130, 'total_records': 21, 'total_versions': 0},
9217                {'avg_fill': 44, 'avg_record_length': 68.86, 'avg_version_length': 0.0, 'data_page_slots': 2, 'data_pages': 2,
9218                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 187, 'indices': 4,
9219                 'max_versions': 0, 'name': 'EMPLOYEE', 'primary_pointer_page': 186, 'table_id': 131, 'total_records': 42, 'total_versions': 0},
9220                {'avg_fill': 20, 'avg_record_length': 12.0, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9221                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 196, 'indices': 3,
9222                 'max_versions': 0, 'name': 'EMPLOYEE_PROJECT', 'primary_pointer_page': 195, 'table_id': 135, 'total_records': 28, 'total_versions': 0},
9223                {'avg_fill': 73, 'avg_record_length': 67.13, 'avg_version_length': 0.0, 'data_page_slots': 3, 'data_pages': 3,
9224                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=2), 'index_root_page': 183, 'indices': 4,
9225                 'max_versions': 0, 'name': 'JOB', 'primary_pointer_page': 182, 'table_id': 129, 'total_records': 31, 'total_versions': 0},
9226                {'avg_fill': 29, 'avg_record_length': 48.83, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9227                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_root_page': 194, 'indices': 4,
9228                 'max_versions': 0, 'name': 'PROJECT', 'primary_pointer_page': 193, 'table_id': 134, 'total_records': 6, 'total_versions': 0},
9229                {'avg_fill': 80, 'avg_record_length': 30.96, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9230                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 198, 'indices': 3,
9231                 'max_versions': 0, 'name': 'PROJ_DEPT_BUDGET', 'primary_pointer_page': 197, 'table_id': 136, 'total_records': 24,
9232                 'total_versions': 0},
9233                {'avg_fill': 58, 'avg_record_length': 31.51, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9234                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 200, 'indices': 4,
9235                 'max_versions': 0, 'name': 'SALARY_HISTORY', 'primary_pointer_page': 199, 'table_id': 137, 'total_records': 49, 'total_versions': 0},
9236                {'avg_fill': 68, 'avg_record_length': 67.24, 'avg_version_length': 0.0, 'data_page_slots': 1, 'data_pages': 1,
9237                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 202, 'indices': 6,
9238                 'max_versions': 0, 'name': 'SALES', 'primary_pointer_page': 201, 'table_id': 138, 'total_records': 33, 'total_versions': 0},
9239                {'avg_fill': 0, 'avg_record_length': 0.0, 'avg_version_length': 0.0, 'data_page_slots': 0, 'data_pages': 0,
9240                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 282, 'indices': 1,
9241                 'max_versions': 0, 'name': 'T', 'primary_pointer_page': 205, 'table_id': 235, 'total_records': 0, 'total_versions': 0},
9242                {'avg_fill': 20, 'avg_record_length': 0.0, 'avg_version_length': 17.0, 'data_page_slots': 1, 'data_pages': 1,
9243                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 208, 'indices': 0,
9244                 'max_versions': 1, 'name': 'T2', 'primary_pointer_page': 207, 'table_id': 141, 'total_records': 2, 'total_versions': 2},
9245                {'avg_fill': 0, 'avg_record_length': 0.0, 'avg_version_length': 0.0, 'data_page_slots': 0, 'data_pages': 0,
9246                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 204, 'indices': 0,
9247                 'max_versions': 0, 'name': 'T3', 'primary_pointer_page': 203, 'table_id': 139, 'total_records': 0, 'total_versions': 0},
9248                {'avg_fill': 4, 'avg_record_length': 0.0, 'avg_version_length': 129.0, 'data_page_slots': 1, 'data_pages': 1,
9249                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 192, 'indices': 0,
9250                 'max_versions': 1, 'name': 'T4', 'primary_pointer_page': 191, 'table_id': 133, 'total_records': 1, 'total_versions': 1}]
9251        i = 0
9252        while i < len(db.tables):
9253            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
9254            i += 1
9255        # Indices
9256        data = [{'avg_data_length': 6.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9257                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY1', 'nodes': 14, 'total_dup': 0},
9258                {'avg_data_length': 15.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9259                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTNAMEX', 'nodes': 15, 'total_dup': 0},
9260                {'avg_data_length': 17.27, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9261                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTREGION', 'nodes': 15, 'total_dup': 0},
9262                {'avg_data_length': 4.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9263                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN23', 'nodes': 15, 'total_dup': 4},
9264                {'avg_data_length': 1.13, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9265                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY22', 'nodes': 15, 'total_dup': 0},
9266                {'avg_data_length': 5.38, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9267                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'BUDGETX', 'nodes': 21, 'total_dup': 7},
9268                {'avg_data_length': 13.95, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9269                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$4', 'nodes': 21, 'total_dup': 0},
9270                {'avg_data_length': 1.14, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4,
9271                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'RDB$FOREIGN10', 'nodes': 21, 'total_dup': 3},
9272                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9273                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN6', 'nodes': 21, 'total_dup': 13},
9274                {'avg_data_length': 1.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9275                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY5', 'nodes': 21, 'total_dup': 0},
9276                {'avg_data_length': 15.52, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_id': 1,
9277                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'NAMEX', 'nodes': 42, 'total_dup': 0},
9278                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9279                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN8', 'nodes': 42, 'total_dup': 23},
9280                {'avg_data_length': 6.79, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9281                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN9', 'nodes': 42, 'total_dup': 15},
9282                {'avg_data_length': 1.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9283                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY7', 'nodes': 42, 'total_dup': 0},
9284                {'avg_data_length': 1.04, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9285                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN15', 'nodes': 28, 'total_dup': 6},
9286                {'avg_data_length': 0.86, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9287                 'leaf_buckets': 1, 'max_dup': 9, 'name': 'RDB$FOREIGN16', 'nodes': 28, 'total_dup': 23},
9288                {'avg_data_length': 9.11, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9289                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY14', 'nodes': 28, 'total_dup': 0},
9290                {'avg_data_length': 10.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9291                 'leaf_buckets': 1, 'max_dup': 1, 'name': 'MAXSALX', 'nodes': 31, 'total_dup': 5},
9292                {'avg_data_length': 10.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9293                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'MINSALX', 'nodes': 31, 'total_dup': 7},
9294                {'avg_data_length': 1.39, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9295                 'leaf_buckets': 1, 'max_dup': 20, 'name': 'RDB$FOREIGN3', 'nodes': 31, 'total_dup': 24},
9296                {'avg_data_length': 10.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9297                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY2', 'nodes': 31, 'total_dup': 0},
9298                {'avg_data_length': 22.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9299                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'PRODTYPEX', 'nodes': 6, 'total_dup': 0},
9300                {'avg_data_length': 13.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9301                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$11', 'nodes': 6, 'total_dup': 0},
9302                {'avg_data_length': 1.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9303                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$FOREIGN13', 'nodes': 6, 'total_dup': 0},
9304                {'avg_data_length': 4.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9305                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY12', 'nodes': 6, 'total_dup': 0},
9306                {'avg_data_length': 0.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9307                 'leaf_buckets': 1, 'max_dup': 5, 'name': 'RDB$FOREIGN18', 'nodes': 24, 'total_dup': 15},
9308                {'avg_data_length': 1.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9309                 'leaf_buckets': 1, 'max_dup': 8, 'name': 'RDB$FOREIGN19', 'nodes': 24, 'total_dup': 19},
9310                {'avg_data_length': 6.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9311                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY17', 'nodes': 24, 'total_dup': 0},
9312                {'avg_data_length': 0.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9313                 'leaf_buckets': 1, 'max_dup': 21, 'name': 'CHANGEX', 'nodes': 49, 'total_dup': 46},
9314                {'avg_data_length': 0.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9315                 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN21', 'nodes': 49, 'total_dup': 16},
9316                {'avg_data_length': 18.29, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_id': 0,
9317                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY20', 'nodes': 49, 'total_dup': 0},
9318                {'avg_data_length': 0.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9319                 'leaf_buckets': 1, 'max_dup': 28, 'name': 'UPDATERX', 'nodes': 49, 'total_dup': 46},
9320                {'avg_data_length': 2.55, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1,
9321                 'leaf_buckets': 1, 'max_dup': 6, 'name': 'NEEDX', 'nodes': 33, 'total_dup': 11},
9322                {'avg_data_length': 1.85, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3,
9323                 'leaf_buckets': 1, 'max_dup': 3, 'name': 'QTYX', 'nodes': 33, 'total_dup': 11},
9324                {'avg_data_length': 0.52, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4,
9325                 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN25', 'nodes': 33, 'total_dup': 18},
9326                {'avg_data_length': 0.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 5,
9327                 'leaf_buckets': 1, 'max_dup': 7, 'name': 'RDB$FOREIGN26', 'nodes': 33, 'total_dup': 25},
9328                {'avg_data_length': 4.48, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9329                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY24', 'nodes': 33, 'total_dup': 0},
9330                {'avg_data_length': 0.97, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2,
9331                 'leaf_buckets': 1, 'max_dup': 14, 'name': 'SALESTATX', 'nodes': 33, 'total_dup': 27},
9332                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0,
9333                 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY104', 'nodes': 0, 'total_dup': 0}]
9334        i = 0
9335        while i < len(db.tables):
9336            self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
9337            i += 1
9338    def test_parse25_s(self):
9339        db = self._parse_file(os.path.join(self.dbpath, 'gstat25-s.out'))
9340        #
9341        self.assertTrue(db.has_table_stats())
9342        self.assertTrue(db.has_index_stats())
9343        self.assertFalse(db.has_row_stats())
9344        self.assertFalse(db.has_encryption_stats())
9345        self.assertTrue(db.has_system())
9346        # Tables
9347        data = [{'avg_fill': 86, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9348                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 210, 'indices': 0,
9349                 'max_versions': None, 'name': 'AR', 'primary_pointer_page': 209, 'table_id': 142, 'total_records': None, 'total_versions': None},
9350                {'avg_fill': 15, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9351                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 181, 'indices': 1,
9352                 'max_versions': None, 'name': 'COUNTRY', 'primary_pointer_page': 180, 'table_id': 128, 'total_records': None, 'total_versions': None},
9353                {'avg_fill': 53, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9354                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 189, 'indices': 4,
9355                 'max_versions': None, 'name': 'CUSTOMER', 'primary_pointer_page': 188, 'table_id': 132, 'total_records': None,
9356                 'total_versions': None},
9357                {'avg_fill': 47, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9358                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 185, 'indices': 5,
9359                 'max_versions': None, 'name': 'DEPARTMENT', 'primary_pointer_page': 184, 'table_id': 130, 'total_records': None,
9360                 'total_versions': None},
9361                {'avg_fill': 44, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 2, 'data_pages': 2,
9362                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 187, 'indices': 4,
9363                 'max_versions': None, 'name': 'EMPLOYEE', 'primary_pointer_page': 186, 'table_id': 131, 'total_records': None,
9364                 'total_versions': None},
9365                {'avg_fill': 20, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9366                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 196, 'indices': 3,
9367                 'max_versions': None, 'name': 'EMPLOYEE_PROJECT', 'primary_pointer_page': 195, 'table_id': 135, 'total_records': None,
9368                 'total_versions': None},
9369                {'avg_fill': 73, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 3, 'data_pages': 3,
9370                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=2), 'index_root_page': 183, 'indices': 4,
9371                 'max_versions': None, 'name': 'JOB', 'primary_pointer_page': 182, 'table_id': 129, 'total_records': None,
9372                 'total_versions': None},
9373                {'avg_fill': 29, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9374                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_root_page': 194, 'indices': 4,
9375                 'max_versions': None, 'name': 'PROJECT', 'primary_pointer_page': 193, 'table_id': 134, 'total_records': None,
9376                 'total_versions': None},
9377                {'avg_fill': 80, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9378                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=1), 'index_root_page': 198, 'indices': 3,
9379                 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET', 'primary_pointer_page': 197, 'table_id': 136, 'total_records': None,
9380                 'total_versions': None},
9381                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9382                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 69, 'indices': 1,
9383                 'max_versions': None, 'name': 'RDB$BACKUP_HISTORY', 'primary_pointer_page': 68, 'table_id': 32, 'total_records': None,
9384                 'total_versions': None},
9385                {'avg_fill': 69, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9386                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 61, 'indices': 2,
9387                 'max_versions': None, 'name': 'RDB$CHARACTER_SETS', 'primary_pointer_page': 60, 'table_id': 28, 'total_records': None,
9388                 'total_versions': None},
9389                {'avg_fill': 37, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 2, 'data_pages': 2,
9390                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 53, 'indices': 2,
9391                 'max_versions': None, 'name': 'RDB$CHECK_CONSTRAINTS', 'primary_pointer_page': 52, 'table_id': 24, 'total_records': None,
9392                 'total_versions': None},
9393                {'avg_fill': 55, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 3, 'data_pages': 3,
9394                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=2, d100=0), 'index_root_page': 63, 'indices': 2,
9395                 'max_versions': None, 'name': 'RDB$COLLATIONS', 'primary_pointer_page': 62, 'table_id': 29, 'total_records': None,
9396                 'total_versions': None},
9397                {'avg_fill': 1, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9398                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 7, 'indices': 0,
9399                 'max_versions': None, 'name': 'RDB$DATABASE', 'primary_pointer_page': 6, 'table_id': 1, 'total_records': None,
9400                 'total_versions': None},
9401                {'avg_fill': 49, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 6, 'data_pages': 5,
9402                 'distribution': FillDistribution(d20=1, d40=1, d50=1, d80=2, d100=0), 'index_root_page': 31, 'indices': 2,
9403                 'max_versions': None, 'name': 'RDB$DEPENDENCIES', 'primary_pointer_page': 30, 'table_id': 13, 'total_records': None,
9404                 'total_versions': None},
9405                {'avg_fill': 12, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9406                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 65, 'indices': 2,
9407                 'max_versions': None, 'name': 'RDB$EXCEPTIONS', 'primary_pointer_page': 64, 'table_id': 30, 'total_records': None,
9408                 'total_versions': None},
9409                {'avg_fill': 62, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 6, 'data_pages': 6,
9410                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=4, d100=1), 'index_root_page': 9, 'indices': 1,
9411                 'max_versions': None, 'name': 'RDB$FIELDS', 'primary_pointer_page': 8, 'table_id': 2, 'total_records': None,
9412                 'total_versions': None},
9413                {'avg_fill': 19, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9414                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 47, 'indices': 1,
9415                 'max_versions': None, 'name': 'RDB$FIELD_DIMENSIONS', 'primary_pointer_page': 46, 'table_id': 21, 'total_records': None,
9416                 'total_versions': None},
9417                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9418                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 25, 'indices': 0,
9419                 'max_versions': None, 'name': 'RDB$FILES', 'primary_pointer_page': 24, 'table_id': 10, 'total_records': None,
9420                 'total_versions': None},
9421                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9422                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 37, 'indices': 2,
9423                 'max_versions': None, 'name': 'RDB$FILTERS', 'primary_pointer_page': 36, 'table_id': 16, 'total_records': None,
9424                 'total_versions': None},
9425                {'avg_fill': 76, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9426                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 21, 'indices': 1,
9427                 'max_versions': None, 'name': 'RDB$FORMATS', 'primary_pointer_page': 20, 'table_id': 8, 'total_records': None,
9428                 'total_versions': None},
9429                {'avg_fill': 4, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9430                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 33, 'indices': 1,
9431                 'max_versions': None, 'name': 'RDB$FUNCTIONS', 'primary_pointer_page': 32, 'table_id': 14, 'total_records': None,
9432                 'total_versions': None},
9433                {'avg_fill': 9, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9434                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 35, 'indices': 1,
9435                 'max_versions': None, 'name': 'RDB$FUNCTION_ARGUMENTS', 'primary_pointer_page': 34, 'table_id': 15, 'total_records': None,
9436                 'total_versions': None},
9437                {'avg_fill': 22, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9438                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_root_page': 45, 'indices': 2,
9439                 'max_versions': None, 'name': 'RDB$GENERATORS', 'primary_pointer_page': 44, 'table_id': 20, 'total_records': None,
9440                 'total_versions': None},
9441                {'avg_fill': 79, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 3, 'data_pages': 3,
9442                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=2, d100=1), 'index_root_page': 11, 'indices': 1,
9443                 'max_versions': None, 'name': 'RDB$INDEX_SEGMENTS', 'primary_pointer_page': 10, 'table_id': 3, 'total_records': None,
9444                 'total_versions': None},
9445                {'avg_fill': 48, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 4, 'data_pages': 4,
9446                 'distribution': FillDistribution(d20=1, d40=1, d50=0, d80=2, d100=0), 'index_root_page': 13, 'indices': 3,
9447                 'max_versions': None, 'name': 'RDB$INDICES', 'primary_pointer_page': 12, 'table_id': 4, 'total_records': None,
9448                 'total_versions': None},
9449                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9450                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 55, 'indices': 0,
9451                 'max_versions': None, 'name': 'RDB$LOG_FILES', 'primary_pointer_page': 54, 'table_id': 25, 'total_records': None,
9452                 'total_versions': None},
9453                {'avg_fill': 38, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 2, 'data_pages': 2,
9454                 'distribution': FillDistribution(d20=1, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 4, 'indices': 0,
9455                 'max_versions': None, 'name': 'RDB$PAGES', 'primary_pointer_page': 3, 'table_id': 0, 'total_records': None,
9456                 'total_versions': None},
9457                {'avg_fill': 94, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 3, 'data_pages': 3,
9458                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=3), 'index_root_page': 57, 'indices': 2,
9459                 'max_versions': None, 'name': 'RDB$PROCEDURES', 'primary_pointer_page': 56, 'table_id': 26, 'total_records': None,
9460                 'total_versions': None},
9461                {'avg_fill': 48, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9462                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 59, 'indices': 3,
9463                 'max_versions': None, 'name': 'RDB$PROCEDURE_PARAMETERS', 'primary_pointer_page': 58, 'table_id': 27,
9464                 'total_records': None, 'total_versions': None},
9465                {'avg_fill': 25, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9466                 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0), 'index_root_page': 51, 'indices': 1,
9467                 'max_versions': None, 'name': 'RDB$REF_CONSTRAINTS', 'primary_pointer_page': 50, 'table_id': 23, 'total_records': None,
9468                 'total_versions': None},
9469                {'avg_fill': 71, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 6, 'data_pages': 5,
9470                 'distribution': FillDistribution(d20=0, d40=0, d50=2, d80=1, d100=2), 'index_root_page': 17, 'indices': 2,
9471                 'max_versions': None, 'name': 'RDB$RELATIONS', 'primary_pointer_page': 16, 'table_id': 6, 'total_records': None,
9472                 'total_versions': None},
9473                {'avg_fill': 67, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 2, 'data_pages': 2,
9474                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=2, d100=0), 'index_root_page': 49, 'indices': 3,
9475                 'max_versions': None, 'name': 'RDB$RELATION_CONSTRAINTS', 'primary_pointer_page': 48, 'table_id': 22,
9476                 'total_records': None, 'total_versions': None},
9477                {'avg_fill': 77, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 13, 'data_pages': 13,
9478                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=9, d100=3), 'index_root_page': 15, 'indices': 3,
9479                 'max_versions': None, 'name': 'RDB$RELATION_FIELDS', 'primary_pointer_page': 14, 'table_id': 5, 'total_records': None,
9480                 'total_versions': None},
9481                {'avg_fill': 2, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9482                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 67, 'indices': 1,
9483                 'max_versions': None, 'name': 'RDB$ROLES', 'primary_pointer_page': 66, 'table_id': 31, 'total_records': None,
9484                 'total_versions': None},
9485                {'avg_fill': 77, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 6, 'data_pages': 6,
9486                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=1, d100=4), 'index_root_page': 23, 'indices': 1,
9487                 'max_versions': None, 'name': 'RDB$SECURITY_CLASSES', 'primary_pointer_page': 22, 'table_id': 9, 'total_records': None,
9488                 'total_versions': None},
9489                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9490                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 43, 'indices': 1,
9491                 'max_versions': None, 'name': 'RDB$TRANSACTIONS', 'primary_pointer_page': 42, 'table_id': 19, 'total_records': None,
9492                 'total_versions': None},
9493                {'avg_fill': 90, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 7, 'data_pages': 7,
9494                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=2, d100=5), 'index_root_page': 29, 'indices': 2,
9495                 'max_versions': None, 'name': 'RDB$TRIGGERS', 'primary_pointer_page': 28, 'table_id': 12, 'total_records': None,
9496                 'total_versions': None},
9497                {'avg_fill': 68, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9498                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 39, 'indices': 1,
9499                 'max_versions': None, 'name': 'RDB$TRIGGER_MESSAGES', 'primary_pointer_page': 38, 'table_id': 17, 'total_records': None,
9500                 'total_versions': None},
9501                {'avg_fill': 70, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 5, 'data_pages': 5,
9502                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=5, d100=0), 'index_root_page': 27, 'indices': 1,
9503                 'max_versions': None, 'name': 'RDB$TYPES', 'primary_pointer_page': 26, 'table_id': 11, 'total_records': None,
9504                 'total_versions': None},
9505                {'avg_fill': 67, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 4, 'data_pages': 4,
9506                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=3, d100=0), 'index_root_page': 41, 'indices': 2,
9507                 'max_versions': None, 'name': 'RDB$USER_PRIVILEGES', 'primary_pointer_page': 40, 'table_id': 18, 'total_records': None,
9508                 'total_versions': None},
9509                {'avg_fill': 3, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9510                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 19, 'indices': 2,
9511                 'max_versions': None, 'name': 'RDB$VIEW_RELATIONS', 'primary_pointer_page': 18, 'table_id': 7, 'total_records': None,
9512                 'total_versions': None},
9513                {'avg_fill': 58, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9514                 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0), 'index_root_page': 200, 'indices': 4,
9515                 'max_versions': None, 'name': 'SALARY_HISTORY', 'primary_pointer_page': 199, 'table_id': 137, 'total_records': None,
9516                 'total_versions': None},
9517                {'avg_fill': 68, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9518                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0), 'index_root_page': 202, 'indices': 6,
9519                 'max_versions': None, 'name': 'SALES', 'primary_pointer_page': 201, 'table_id': 138, 'total_records': None,
9520                 'total_versions': None},
9521                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9522                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 282, 'indices': 1,
9523                 'max_versions': None, 'name': 'T', 'primary_pointer_page': 205, 'table_id': 235, 'total_records': None, 'total_versions': None},
9524                {'avg_fill': 20, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9525                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 208, 'indices': 0,
9526                 'max_versions': None, 'name': 'T2', 'primary_pointer_page': 207, 'table_id': 141, 'total_records': None, 'total_versions': None},
9527                {'avg_fill': 0, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 0, 'data_pages': 0,
9528                 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 204, 'indices': 0,
9529                 'max_versions': None, 'name': 'T3', 'primary_pointer_page': 203, 'table_id': 139, 'total_records': None, 'total_versions': None},
9530                {'avg_fill': 4, 'avg_record_length': None, 'avg_version_length': None, 'data_page_slots': 1, 'data_pages': 1,
9531                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_root_page': 192, 'indices': 0,
9532                 'max_versions': None, 'name': 'T4', 'primary_pointer_page': 191, 'table_id': 133, 'total_records': None, 'total_versions': None}]
9533        i = 0
9534        while i < len(db.tables):
9535            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
9536            i += 1
9537        # Indices
9538        data = [{'avg_data_length': 6.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9539                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY1', 'nodes': 14, 'total_dup': 0},
9540                {'avg_data_length': 15.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9541                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTNAMEX', 'nodes': 15, 'total_dup': 0},
9542                {'avg_data_length': 17.27, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9543                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'CUSTREGION', 'nodes': 15, 'total_dup': 0},
9544                {'avg_data_length': 4.87, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9545                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN23', 'nodes': 15, 'total_dup': 4},
9546                {'avg_data_length': 1.13, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9547                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY22', 'nodes': 15, 'total_dup': 0},
9548                {'avg_data_length': 5.38, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9549                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 3, 'name': 'BUDGETX', 'nodes': 21, 'total_dup': 7},
9550                {'avg_data_length': 13.95, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9551                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$4', 'nodes': 21, 'total_dup': 0},
9552                {'avg_data_length': 1.14, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9553                 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3, 'name': 'RDB$FOREIGN10', 'nodes': 21, 'total_dup': 3},
9554                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9555                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN6', 'nodes': 21, 'total_dup': 13},
9556                {'avg_data_length': 1.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9557                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY5', 'nodes': 21, 'total_dup': 0},
9558                {'avg_data_length': 15.52, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9559                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'NAMEX', 'nodes': 42, 'total_dup': 0},
9560                {'avg_data_length': 0.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9561                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN8', 'nodes': 42, 'total_dup': 23},
9562                {'avg_data_length': 6.79, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9563                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN9', 'nodes': 42, 'total_dup': 15},
9564                {'avg_data_length': 1.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9565                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY7', 'nodes': 42, 'total_dup': 0},
9566                {'avg_data_length': 1.04, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9567                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN15', 'nodes': 28, 'total_dup': 6},
9568                {'avg_data_length': 0.86, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9569                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 9, 'name': 'RDB$FOREIGN16', 'nodes': 28, 'total_dup': 23},
9570                {'avg_data_length': 9.11, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9571                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY14', 'nodes': 28, 'total_dup': 0},
9572                {'avg_data_length': 10.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9573                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 1, 'name': 'MAXSALX', 'nodes': 31, 'total_dup': 5},
9574                {'avg_data_length': 10.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9575                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2, 'name': 'MINSALX', 'nodes': 31, 'total_dup': 7},
9576                {'avg_data_length': 1.39, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9577                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 20, 'name': 'RDB$FOREIGN3', 'nodes': 31, 'total_dup': 24},
9578                {'avg_data_length': 10.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9579                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY2', 'nodes': 31, 'total_dup': 0},
9580                {'avg_data_length': 22.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9581                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'PRODTYPEX', 'nodes': 6, 'total_dup': 0},
9582                {'avg_data_length': 13.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9583                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$11', 'nodes': 6, 'total_dup': 0},
9584                {'avg_data_length': 1.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9585                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$FOREIGN13', 'nodes': 6, 'total_dup': 0},
9586                {'avg_data_length': 4.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9587                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY12', 'nodes': 6, 'total_dup': 0},
9588                {'avg_data_length': 0.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9589                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 5, 'name': 'RDB$FOREIGN18', 'nodes': 24, 'total_dup': 15},
9590                {'avg_data_length': 1.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9591                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 8, 'name': 'RDB$FOREIGN19', 'nodes': 24, 'total_dup': 19},
9592                {'avg_data_length': 6.83, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9593                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY17', 'nodes': 24, 'total_dup': 0},
9594                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9595                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_44', 'nodes': 0, 'total_dup': 0},
9596                {'avg_data_length': 2.98, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9597                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_19', 'nodes': 52, 'total_dup': 0},
9598                {'avg_data_length': 1.04, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9599                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_25', 'nodes': 52, 'total_dup': 0},
9600                {'avg_data_length': 0.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9601                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 1, 'name': 'RDB$INDEX_14', 'nodes': 70, 'total_dup': 14},
9602                {'avg_data_length': 3.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9603                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$INDEX_40', 'nodes': 70, 'total_dup': 11},
9604                {'avg_data_length': 3.77, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9605                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_20', 'nodes': 149, 'total_dup': 0},
9606                {'avg_data_length': 1.79, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9607                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_26', 'nodes': 149, 'total_dup': 0},
9608                {'avg_data_length': 1.18, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9609                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 13, 'name': 'RDB$INDEX_27', 'nodes': 163, 'total_dup': 118},
9610                {'avg_data_length': 1.01, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9611                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 36, 'name': 'RDB$INDEX_28', 'nodes': 163, 'total_dup': 145},
9612                {'avg_data_length': 14.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9613                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_23', 'nodes': 5, 'total_dup': 0},
9614                {'avg_data_length': 1.2, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9615                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_24', 'nodes': 5, 'total_dup': 0},
9616                {'avg_data_length': 4.58, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0),
9617                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_2', 'nodes': 245, 'total_dup': 0},
9618                {'avg_data_length': 1.26, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9619                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$INDEX_36', 'nodes': 19, 'total_dup': 3},
9620                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9621                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_17', 'nodes': 0, 'total_dup': 0},
9622                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9623                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_45', 'nodes': 0, 'total_dup': 0},
9624                {'avg_data_length': 4.63, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9625                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_16', 'nodes': 19, 'total_dup': 0},
9626                {'avg_data_length': 13.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9627                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_9', 'nodes': 2, 'total_dup': 0},
9628                {'avg_data_length': 3.71, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9629                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 3, 'name': 'RDB$INDEX_10', 'nodes': 7, 'total_dup': 5},
9630                {'avg_data_length': 11.91, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9631                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_11', 'nodes': 11, 'total_dup': 0},
9632                {'avg_data_length': 1.09, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9633                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_46', 'nodes': 11, 'total_dup': 0},
9634                {'avg_data_length': 1.5, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9635                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$INDEX_6', 'nodes': 150, 'total_dup': 24},
9636                {'avg_data_length': 4.23, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9637                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 5, 'name': 'RDB$INDEX_31', 'nodes': 88, 'total_dup': 48},
9638                {'avg_data_length': 0.19, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9639                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 73, 'name': 'RDB$INDEX_41', 'nodes': 88, 'total_dup': 81},
9640                {'avg_data_length': 2.09, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9641                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_5', 'nodes': 88, 'total_dup': 0},
9642                {'avg_data_length': 10.6, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9643                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_21', 'nodes': 10, 'total_dup': 0},
9644                {'avg_data_length': 1.1, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9645                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_22', 'nodes': 10, 'total_dup': 0},
9646                {'avg_data_length': 11.33, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9647                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_18', 'nodes': 33, 'total_dup': 0},
9648                {'avg_data_length': 1.24, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9649                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_47', 'nodes': 33, 'total_dup': 0},
9650                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9651                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 32, 'name': 'RDB$INDEX_48', 'nodes': 33, 'total_dup': 32},
9652                {'avg_data_length': 1.93, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9653                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_13', 'nodes': 14, 'total_dup': 0},
9654                {'avg_data_length': 8.81, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9655                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_0', 'nodes': 58, 'total_dup': 0},
9656                {'avg_data_length': 0.82, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9657                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 14, 'name': 'RDB$INDEX_1', 'nodes': 73, 'total_dup': 14},
9658                {'avg_data_length': 1.07, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9659                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_12', 'nodes': 82, 'total_dup': 0},
9660                {'avg_data_length': 6.43, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9661                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 8, 'name': 'RDB$INDEX_42', 'nodes': 82, 'total_dup': 43},
9662                {'avg_data_length': 0.6, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9663                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 54, 'name': 'RDB$INDEX_43', 'nodes': 82, 'total_dup': 54},
9664                {'avg_data_length': 20.92, 'depth': 2, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=1, d100=2),
9665                 'index_id': 2, 'leaf_buckets': 4, 'max_dup': 0, 'name': 'RDB$INDEX_15', 'nodes': 466, 'total_dup': 0},
9666                {'avg_data_length': 2.33, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=0),
9667                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 31, 'name': 'RDB$INDEX_3', 'nodes': 466, 'total_dup': 255},
9668                {'avg_data_length': 1.1, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0),
9669                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 27, 'name': 'RDB$INDEX_4', 'nodes': 466, 'total_dup': 408},
9670                {'avg_data_length': 9.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9671                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_39', 'nodes': 2, 'total_dup': 0},
9672                {'avg_data_length': 1.06, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9673                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_7', 'nodes': 182, 'total_dup': 0},
9674                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9675                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_32', 'nodes': 0, 'total_dup': 0},
9676                {'avg_data_length': 2.84, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9677                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 18, 'name': 'RDB$INDEX_38', 'nodes': 69, 'total_dup': 48},
9678                {'avg_data_length': 2.09, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9679                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_8', 'nodes': 69, 'total_dup': 0},
9680                {'avg_data_length': 1.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9681                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 5, 'name': 'RDB$INDEX_35', 'nodes': 36, 'total_dup': 12},
9682                {'avg_data_length': 4.22, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0),
9683                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 1, 'name': 'RDB$INDEX_37', 'nodes': 228, 'total_dup': 16},
9684                {'avg_data_length': 1.24, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9685                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 9, 'name': 'RDB$INDEX_29', 'nodes': 173, 'total_dup': 144},
9686                {'avg_data_length': 0.07, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9687                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 104, 'name': 'RDB$INDEX_30', 'nodes': 173, 'total_dup': 171},
9688                {'avg_data_length': 5.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9689                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 1, 'name': 'RDB$INDEX_33', 'nodes': 2, 'total_dup': 1},
9690                {'avg_data_length': 9.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9691                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$INDEX_34', 'nodes': 2, 'total_dup': 0},
9692                {'avg_data_length': 0.31, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9693                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 21, 'name': 'CHANGEX', 'nodes': 49, 'total_dup': 46},
9694                {'avg_data_length': 0.9, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9695                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 2, 'name': 'RDB$FOREIGN21', 'nodes': 49, 'total_dup': 16},
9696                {'avg_data_length': 18.29, 'depth': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9697                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY20', 'nodes': 49, 'total_dup': 0},
9698                {'avg_data_length': 0.29, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9699                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 28, 'name': 'UPDATERX', 'nodes': 49, 'total_dup': 46},
9700                {'avg_data_length': 2.55, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9701                 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 6, 'name': 'NEEDX', 'nodes': 33, 'total_dup': 11},
9702                {'avg_data_length': 1.85, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9703                 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 3, 'name': 'QTYX', 'nodes': 33, 'total_dup': 11},
9704                {'avg_data_length': 0.52, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9705                 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 4, 'name': 'RDB$FOREIGN25', 'nodes': 33, 'total_dup': 18},
9706                {'avg_data_length': 0.45, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9707                 'index_id': 5, 'leaf_buckets': 1, 'max_dup': 7, 'name': 'RDB$FOREIGN26', 'nodes': 33, 'total_dup': 25},
9708                {'avg_data_length': 4.48, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9709                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY24', 'nodes': 33, 'total_dup': 0},
9710                {'avg_data_length': 0.97, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9711                 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 14, 'name': 'SALESTATX', 'nodes': 33, 'total_dup': 27},
9712                {'avg_data_length': 0.0, 'depth': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9713                 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0, 'name': 'RDB$PRIMARY104', 'nodes': 0, 'total_dup': 0}]
9714        i = 0
9715        while i < len(db.tables):
9716            self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
9717            i += 1
9718    def test_parse30_h(self):
9719        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-h.out'))
9720        data = {'attributes': (0,), 'backup_diff_file': None, 'backup_guid': '{F978F787-7023-4C4A-F79D-8D86645B0487}',
9721                'bumped_transaction': None, 'checksum': 12345, 'completed': datetime.datetime(2018, 4, 4, 15, 41, 34),
9722                'continuation_file': None, 'continuation_files': 0, 'creation_date': datetime.datetime(2015, 11, 27, 11, 19, 39),
9723                'database_dialect': 3, 'encrypted_blob_pages': None, 'encrypted_data_pages': None, 'encrypted_index_pages': None,
9724                'executed': datetime.datetime(2018, 4, 4, 15, 41, 34), 'filename': '/home/fdb/test/FBTEST30.FDB', 'flags': 0,
9725                'generation': 2176, 'gstat_version': 3, 'implementation': 'HW=AMD/Intel/x64 little-endian OS=Linux CC=gcc',
9726                'implementation_id': 0, 'indices': 0, 'last_logical_page': None, 'next_attachment_id': 1199, 'next_header_page': 0,
9727                'next_transaction': 2141, 'oat': 2140, 'ods_version': '12.0', 'oit': 179, 'ost': 2140, 'page_buffers': 0,
9728                'page_size': 8192, 'replay_logging_file': None, 'root_filename': None, 'sequence_number': 0, 'shadow_count': 0,
9729                'sweep_interval': None, 'system_change_number': 24, 'tables': 0}
9730        self.assertIsInstance(db, gstat.StatDatabase)
9731        self.assertDictEqual(data, get_object_data(db), 'Unexpected output from parser (database hdr)')
9732        #
9733        self.assertFalse(db.has_table_stats())
9734        self.assertFalse(db.has_index_stats())
9735        self.assertFalse(db.has_row_stats())
9736        self.assertFalse(db.has_encryption_stats())
9737        self.assertFalse(db.has_system())
9738    def test_parse30_a(self):
9739        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-a.out'))
9740        # Database
9741        data = {'attributes': (0,), 'backup_diff_file': None, 'backup_guid': '{F978F787-7023-4C4A-F79D-8D86645B0487}',
9742                'bumped_transaction': None, 'checksum': 12345, 'completed': datetime.datetime(2018, 4, 4, 15, 42),
9743                'continuation_file': None, 'continuation_files': 0, 'creation_date': datetime.datetime(2015, 11, 27, 11, 19, 39),
9744                'database_dialect': 3, 'encrypted_blob_pages': None, 'encrypted_data_pages': None, 'encrypted_index_pages': None,
9745                'executed': datetime.datetime(2018, 4, 4, 15, 42), 'filename': '/home/fdb/test/FBTEST30.FDB', 'flags': 0,
9746                'generation': 2176, 'gstat_version': 3, 'implementation': 'HW=AMD/Intel/x64 little-endian OS=Linux CC=gcc',
9747                'implementation_id': 0, 'indices': 39, 'last_logical_page': None, 'next_attachment_id': 1199, 'next_header_page': 0,
9748                'next_transaction': 2141, 'oat': 2140, 'ods_version': '12.0', 'oit': 179, 'ost': 2140, 'page_buffers': 0,
9749                'page_size': 8192, 'replay_logging_file': None, 'root_filename': None, 'sequence_number': 0, 'shadow_count': 0,
9750                'sweep_interval': None, 'system_change_number': 24, 'tables': 16}
9751        self.assertDictEqual(data, get_object_data(db), 'Unexpected output from parser (database hdr)')
9752        #
9753        self.assertTrue(db.has_table_stats())
9754        self.assertTrue(db.has_index_stats())
9755        self.assertFalse(db.has_row_stats())
9756        self.assertFalse(db.has_encryption_stats())
9757        self.assertFalse(db.has_system())
9758        # Tables
9759        data = [{'avg_fill': 86, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9760                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9761                 'data_page_slots': 3, 'data_pages': 3, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=2),
9762                 'empty_pages': 0, 'full_pages': 1, 'index_root_page': 299, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
9763                 'max_fragments': None, 'max_versions': None, 'name': 'AR', 'pointer_pages': 1, 'primary_pages': 1,
9764                 'primary_pointer_page': 297, 'secondary_pages': 2, 'swept_pages': 0, 'table_id': 140, 'total_formats': None,
9765                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9766                {'avg_fill': 8, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9767                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9768                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9769                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 183, 'indices': 1, 'level_0': None, 'level_1': None, 'level_2': None,
9770                 'max_fragments': None, 'max_versions': None, 'name': 'COUNTRY', 'pointer_pages': 1, 'primary_pages': 1,
9771                 'primary_pointer_page': 182, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 128, 'total_formats': None,
9772                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9773                {'avg_fill': 26, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9774                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9775                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9776                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 262, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
9777                 'max_fragments': None, 'max_versions': None, 'name': 'CUSTOMER', 'pointer_pages': 1, 'primary_pages': 1,
9778                 'primary_pointer_page': 261, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 137, 'total_formats': None,
9779                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9780                {'avg_fill': 24, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9781                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9782                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9783                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 199, 'indices': 5, 'level_0': None, 'level_1': None, 'level_2': None,
9784                 'max_fragments': None, 'max_versions': None, 'name': 'DEPARTMENT', 'pointer_pages': 1, 'primary_pages': 1,
9785                 'primary_pointer_page': 198, 'secondary_pages': 0, 'swept_pages': 1, 'table_id': 130, 'total_formats': None,
9786                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9787                {'avg_fill': 44, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9788                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9789                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0),
9790                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 213, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
9791                 'max_fragments': None, 'max_versions': None, 'name': 'EMPLOYEE', 'pointer_pages': 1, 'primary_pages': 1,
9792                 'primary_pointer_page': 212, 'secondary_pages': 0, 'swept_pages': 1, 'table_id': 131, 'total_formats': None,
9793                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9794                {'avg_fill': 10, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9795                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9796                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9797                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 235, 'indices': 3, 'level_0': None, 'level_1': None, 'level_2': None,
9798                 'max_fragments': None, 'max_versions': None, 'name': 'EMPLOYEE_PROJECT', 'pointer_pages': 1, 'primary_pages': 1,
9799                 'primary_pointer_page': 234, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 134, 'total_formats': None,
9800                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9801                {'avg_fill': 54, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9802                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9803                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=1, d100=0),
9804                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 190, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
9805                 'max_fragments': None, 'max_versions': None, 'name': 'JOB', 'pointer_pages': 1, 'primary_pages': 1,
9806                 'primary_pointer_page': 189, 'secondary_pages': 1, 'swept_pages': 1, 'table_id': 129, 'total_formats': None,
9807                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9808                {'avg_fill': 7, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9809                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9810                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
9811                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 221, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
9812                 'max_fragments': None, 'max_versions': None, 'name': 'PROJECT', 'pointer_pages': 1, 'primary_pages': 1,
9813                 'primary_pointer_page': 220, 'secondary_pages': 1, 'swept_pages': 1, 'table_id': 133, 'total_formats': None,
9814                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9815                {'avg_fill': 20, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9816                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9817                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=1, d40=1, d50=0, d80=0, d100=0),
9818                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 248, 'indices': 3, 'level_0': None, 'level_1': None, 'level_2': None,
9819                 'max_fragments': None, 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET', 'pointer_pages': 1, 'primary_pages': 1,
9820                 'primary_pointer_page': 239, 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 135, 'total_formats': None,
9821                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9822                {'avg_fill': 30, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9823                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9824                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9825                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 254, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
9826                 'max_fragments': None, 'max_versions': None, 'name': 'SALARY_HISTORY', 'pointer_pages': 1, 'primary_pages': 1,
9827                 'primary_pointer_page': 253, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 136, 'total_formats': None,
9828                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9829                {'avg_fill': 35, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9830                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9831                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
9832                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 268, 'indices': 6, 'level_0': None, 'level_1': None, 'level_2': None,
9833                 'max_fragments': None, 'max_versions': None, 'name': 'SALES', 'pointer_pages': 1, 'primary_pages': 1,
9834                 'primary_pointer_page': 267, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 138, 'total_formats': None,
9835                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9836                {'avg_fill': 0, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9837                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9838                 'data_page_slots': 0, 'data_pages': 0, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0),
9839                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 324, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
9840                 'max_fragments': None, 'max_versions': None, 'name': 'T', 'pointer_pages': 1, 'primary_pages': 0,
9841                 'primary_pointer_page': 323, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 147, 'total_formats': None,
9842                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9843                {'avg_fill': 8, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9844                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9845                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
9846                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 303, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
9847                 'max_fragments': None, 'max_versions': None, 'name': 'T2', 'pointer_pages': 1, 'primary_pages': 1,
9848                 'primary_pointer_page': 302, 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 142, 'total_formats': None,
9849                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9850                {'avg_fill': 3, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9851                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9852                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
9853                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 306, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
9854                 'max_fragments': None, 'max_versions': None, 'name': 'T3', 'pointer_pages': 1, 'primary_pages': 1,
9855                 'primary_pointer_page': 305, 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 143, 'total_formats': None,
9856                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9857                {'avg_fill': 3, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9858                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9859                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
9860                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 308, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
9861                 'max_fragments': None, 'max_versions': None, 'name': 'T4', 'pointer_pages': 1, 'primary_pages': 1,
9862                 'primary_pointer_page': 307, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 144, 'total_formats': None,
9863                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
9864                {'avg_fill': 0, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
9865                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
9866                 'data_page_slots': 0, 'data_pages': 0, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0),
9867                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 316, 'indices': 1, 'level_0': None, 'level_1': None, 'level_2': None,
9868                 'max_fragments': None, 'max_versions': None, 'name': 'T5', 'pointer_pages': 1, 'primary_pages': 0,
9869                 'primary_pointer_page': 315, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 145, 'total_formats': None,
9870                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None}]
9871        i = 0
9872        while i < len(db.tables):
9873            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
9874            i += 1
9875        # Indices
9876        data = [{'avg_data_length': 6.44, 'avg_key_length': 8.63, 'avg_node_length': 10.44, 'avg_prefix_length': 0.44,
9877                 'clustering_factor': 1.0, 'compression_ratio': 0.8, 'depth': 1,
9878                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9879                 'name': 'RDB$PRIMARY1', 'nodes': 16, 'ratio': 0.06, 'root_page': 186, 'total_dup': 0},
9880                {'avg_data_length': 15.87, 'avg_key_length': 18.27, 'avg_node_length': 19.87, 'avg_prefix_length': 0.6,
9881                 'clustering_factor': 1.0, 'compression_ratio': 0.9, 'depth': 1,
9882                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0,
9883                 'name': 'CUSTNAMEX', 'nodes': 15, 'ratio': 0.07, 'root_page': 276, 'total_dup': 0},
9884                {'avg_data_length': 17.27, 'avg_key_length': 20.2, 'avg_node_length': 21.27, 'avg_prefix_length': 2.33,
9885                 'clustering_factor': 1.0, 'compression_ratio': 0.97, 'depth': 1,
9886                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
9887                 'name': 'CUSTREGION', 'nodes': 15, 'ratio': 0.07, 'root_page': 283, 'total_dup': 0},
9888                {'avg_data_length': 4.87, 'avg_key_length': 6.93, 'avg_node_length': 8.6, 'avg_prefix_length': 0.87,
9889                 'clustering_factor': 1.0, 'compression_ratio': 0.83, 'depth': 1,
9890                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
9891                 'name': 'RDB$FOREIGN23', 'nodes': 15, 'ratio': 0.07, 'root_page': 264, 'total_dup': 4},
9892                {'avg_data_length': 1.13, 'avg_key_length': 3.13, 'avg_node_length': 4.2, 'avg_prefix_length': 1.87,
9893                 'clustering_factor': 1.0, 'compression_ratio': 0.96, 'depth': 1,
9894                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9895                 'name': 'RDB$PRIMARY22', 'nodes': 15, 'ratio': 0.07, 'root_page': 263, 'total_dup': 0},
9896                {'avg_data_length': 5.38, 'avg_key_length': 8.0, 'avg_node_length': 9.05, 'avg_prefix_length': 3.62,
9897                 'clustering_factor': 1.0, 'compression_ratio': 1.13, 'depth': 1,
9898                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 3,
9899                 'name': 'BUDGETX', 'nodes': 21, 'ratio': 0.05, 'root_page': 284, 'total_dup': 7},
9900                {'avg_data_length': 13.95, 'avg_key_length': 16.57, 'avg_node_length': 17.95, 'avg_prefix_length': 5.29,
9901                 'clustering_factor': 1.0, 'compression_ratio': 1.16, 'depth': 1,
9902                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9903                 'name': 'RDB$4', 'nodes': 21, 'ratio': 0.05, 'root_page': 208, 'total_dup': 0},
9904                {'avg_data_length': 1.14, 'avg_key_length': 3.24, 'avg_node_length': 4.29, 'avg_prefix_length': 0.81,
9905                 'clustering_factor': 1.0, 'compression_ratio': 0.6, 'depth': 1,
9906                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3,
9907                 'name': 'RDB$FOREIGN10', 'nodes': 21, 'ratio': 0.05, 'root_page': 219, 'total_dup': 3},
9908                {'avg_data_length': 0.81, 'avg_key_length': 2.95, 'avg_node_length': 4.1, 'avg_prefix_length': 2.05,
9909                 'clustering_factor': 1.0, 'compression_ratio': 0.97, 'depth': 1,
9910                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4,
9911                 'name': 'RDB$FOREIGN6', 'nodes': 21, 'ratio': 0.05, 'root_page': 210, 'total_dup': 13},
9912                {'avg_data_length': 1.71, 'avg_key_length': 4.05, 'avg_node_length': 5.24, 'avg_prefix_length': 1.29,
9913                 'clustering_factor': 1.0, 'compression_ratio': 0.74, 'depth': 1,
9914                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0,
9915                 'name': 'RDB$PRIMARY5', 'nodes': 21, 'ratio': 0.05, 'root_page': 209, 'total_dup': 0},
9916                {'avg_data_length': 15.52, 'avg_key_length': 18.5, 'avg_node_length': 19.52, 'avg_prefix_length': 2.17,
9917                 'clustering_factor': 1.0, 'compression_ratio': 0.96, 'depth': 1,
9918                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
9919                 'name': 'NAMEX', 'nodes': 42, 'ratio': 0.02, 'root_page': 285, 'total_dup': 0},
9920                {'avg_data_length': 0.81, 'avg_key_length': 2.98, 'avg_node_length': 4.07, 'avg_prefix_length': 2.19,
9921                 'clustering_factor': 1.0, 'compression_ratio': 1.01, 'depth': 1,
9922                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
9923                 'name': 'RDB$FOREIGN8', 'nodes': 42, 'ratio': 0.02, 'root_page': 215, 'total_dup': 23},
9924                {'avg_data_length': 6.79, 'avg_key_length': 9.4, 'avg_node_length': 10.43, 'avg_prefix_length': 9.05,
9925                 'clustering_factor': 1.0, 'compression_ratio': 1.68, 'depth': 1,
9926                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4,
9927                 'name': 'RDB$FOREIGN9', 'nodes': 42, 'ratio': 0.02, 'root_page': 216, 'total_dup': 15},
9928                {'avg_data_length': 1.31, 'avg_key_length': 3.6, 'avg_node_length': 4.62, 'avg_prefix_length': 1.17,
9929                 'clustering_factor': 1.0, 'compression_ratio': 0.69, 'depth': 1,
9930                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9931                 'name': 'RDB$PRIMARY7', 'nodes': 42, 'ratio': 0.02, 'root_page': 214, 'total_dup': 0},
9932                {'avg_data_length': 1.04, 'avg_key_length': 3.25, 'avg_node_length': 4.29, 'avg_prefix_length': 1.36,
9933                 'clustering_factor': 1.0, 'compression_ratio': 0.74, 'depth': 1,
9934                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2,
9935                 'name': 'RDB$FOREIGN15', 'nodes': 28, 'ratio': 0.04, 'root_page': 237, 'total_dup': 6},
9936                {'avg_data_length': 0.86, 'avg_key_length': 2.89, 'avg_node_length': 4.04, 'avg_prefix_length': 4.14,
9937                 'clustering_factor': 1.0, 'compression_ratio': 1.73, 'depth': 1,
9938                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 9,
9939                 'name': 'RDB$FOREIGN16', 'nodes': 28, 'ratio': 0.04, 'root_page': 238, 'total_dup': 23},
9940                {'avg_data_length': 9.11, 'avg_key_length': 12.07, 'avg_node_length': 13.11, 'avg_prefix_length': 2.89,
9941                 'clustering_factor': 1.0, 'compression_ratio': 0.99, 'depth': 1,
9942                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9943                 'name': 'RDB$PRIMARY14', 'nodes': 28, 'ratio': 0.04, 'root_page': 236, 'total_dup': 0},
9944                {'avg_data_length': 10.9, 'avg_key_length': 13.71, 'avg_node_length': 14.74, 'avg_prefix_length': 7.87,
9945                 'clustering_factor': 1.0, 'compression_ratio': 1.37, 'depth': 1,
9946                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 1,
9947                 'name': 'MAXSALX', 'nodes': 31, 'ratio': 0.03, 'root_page': 286, 'total_dup': 5},
9948                {'avg_data_length': 10.29, 'avg_key_length': 13.03, 'avg_node_length': 14.06, 'avg_prefix_length': 8.48,
9949                 'clustering_factor': 1.0, 'compression_ratio': 1.44, 'depth': 1,
9950                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 2,
9951                 'name': 'MINSALX', 'nodes': 31, 'ratio': 0.03, 'root_page': 287, 'total_dup': 7},
9952                {'avg_data_length': 1.39, 'avg_key_length': 3.39, 'avg_node_length': 4.61, 'avg_prefix_length': 2.77,
9953                 'clustering_factor': 1.0, 'compression_ratio': 1.23, 'depth': 1,
9954                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 20,
9955                 'name': 'RDB$FOREIGN3', 'nodes': 31, 'ratio': 0.03, 'root_page': 192, 'total_dup': 24},
9956                {'avg_data_length': 10.45, 'avg_key_length': 13.42, 'avg_node_length': 14.45, 'avg_prefix_length': 6.19,
9957                 'clustering_factor': 1.0, 'compression_ratio': 1.24, 'depth': 1,
9958                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9959                 'name': 'RDB$PRIMARY2', 'nodes': 31, 'ratio': 0.03, 'root_page': 191, 'total_dup': 0},
9960                {'avg_data_length': 22.5, 'avg_key_length': 25.33, 'avg_node_length': 26.5, 'avg_prefix_length': 4.17,
9961                 'clustering_factor': 1.0, 'compression_ratio': 1.05, 'depth': 1,
9962                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
9963                 'name': 'PRODTYPEX', 'nodes': 6, 'ratio': 0.17, 'root_page': 288, 'total_dup': 0},
9964                {'avg_data_length': 13.33, 'avg_key_length': 15.5, 'avg_node_length': 17.33, 'avg_prefix_length': 0.33,
9965                 'clustering_factor': 1.0, 'compression_ratio': 0.88, 'depth': 1,
9966                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9967                 'name': 'RDB$11', 'nodes': 6, 'ratio': 0.17, 'root_page': 222, 'total_dup': 0},
9968                {'avg_data_length': 1.33, 'avg_key_length': 3.5, 'avg_node_length': 4.67, 'avg_prefix_length': 0.67,
9969                 'clustering_factor': 1.0, 'compression_ratio': 0.57, 'depth': 1,
9970                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0,
9971                 'name': 'RDB$FOREIGN13', 'nodes': 6, 'ratio': 0.17, 'root_page': 232, 'total_dup': 0},
9972                {'avg_data_length': 4.83, 'avg_key_length': 7.0, 'avg_node_length': 8.83, 'avg_prefix_length': 0.17,
9973                 'clustering_factor': 1.0, 'compression_ratio': 0.71, 'depth': 1,
9974                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0,
9975                 'name': 'RDB$PRIMARY12', 'nodes': 6, 'ratio': 0.17, 'root_page': 223, 'total_dup': 0},
9976                {'avg_data_length': 0.71, 'avg_key_length': 2.79, 'avg_node_length': 3.92, 'avg_prefix_length': 2.29,
9977                 'clustering_factor': 1.0, 'compression_ratio': 1.07, 'depth': 1,
9978                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 5,
9979                 'name': 'RDB$FOREIGN18', 'nodes': 24, 'ratio': 0.04, 'root_page': 250, 'total_dup': 15},
9980                {'avg_data_length': 1.0, 'avg_key_length': 3.04, 'avg_node_length': 4.21, 'avg_prefix_length': 4.0,
9981                 'clustering_factor': 1.0, 'compression_ratio': 1.64, 'depth': 1,
9982                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 8,
9983                 'name': 'RDB$FOREIGN19', 'nodes': 24, 'ratio': 0.04, 'root_page': 251, 'total_dup': 19},
9984                {'avg_data_length': 6.83, 'avg_key_length': 9.67, 'avg_node_length': 10.71, 'avg_prefix_length': 12.17,
9985                 'clustering_factor': 1.0, 'compression_ratio': 1.97, 'depth': 1,
9986                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9987                 'name': 'RDB$PRIMARY17', 'nodes': 24, 'ratio': 0.04, 'root_page': 249, 'total_dup': 0},
9988                {'avg_data_length': 0.31, 'avg_key_length': 2.35, 'avg_node_length': 3.37, 'avg_prefix_length': 6.69,
9989                 'clustering_factor': 1.0, 'compression_ratio': 2.98, 'depth': 1,
9990                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 21,
9991                 'name': 'CHANGEX', 'nodes': 49, 'ratio': 0.02, 'root_page': 289, 'total_dup': 46},
9992                {'avg_data_length': 0.9, 'avg_key_length': 3.1, 'avg_node_length': 4.12, 'avg_prefix_length': 1.43,
9993                 'clustering_factor': 1.0, 'compression_ratio': 0.75, 'depth': 1,
9994                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2,
9995                 'name': 'RDB$FOREIGN21', 'nodes': 49, 'ratio': 0.02, 'root_page': 256, 'total_dup': 16},
9996                {'avg_data_length': 18.29, 'avg_key_length': 21.27, 'avg_node_length': 22.29, 'avg_prefix_length': 4.31,
9997                 'clustering_factor': 1.0, 'compression_ratio': 1.06, 'depth': 1,
9998                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
9999                 'name': 'RDB$PRIMARY20', 'nodes': 49, 'ratio': 0.02, 'root_page': 255, 'total_dup': 0},
10000                {'avg_data_length': 0.29, 'avg_key_length': 2.29, 'avg_node_length': 3.35, 'avg_prefix_length': 5.39,
10001                 'clustering_factor': 1.0, 'compression_ratio': 2.48, 'depth': 1,
10002                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 28,
10003                 'name': 'UPDATERX', 'nodes': 49, 'ratio': 0.02, 'root_page': 290, 'total_dup': 46},
10004                {'avg_data_length': 2.55, 'avg_key_length': 4.94, 'avg_node_length': 5.97, 'avg_prefix_length': 2.88,
10005                 'clustering_factor': 1.0, 'compression_ratio': 1.1, 'depth': 1,
10006                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 6,
10007                 'name': 'NEEDX', 'nodes': 33, 'ratio': 0.03, 'root_page': 291, 'total_dup': 11},
10008                {'avg_data_length': 1.85, 'avg_key_length': 4.03, 'avg_node_length': 5.06, 'avg_prefix_length': 11.18,
10009                 'clustering_factor': 1.0, 'compression_ratio': 3.23, 'depth': 1,
10010                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3,
10011                 'name': 'QTYX', 'nodes': 33, 'ratio': 0.03, 'root_page': 292, 'total_dup': 11},
10012                {'avg_data_length': 0.52, 'avg_key_length': 2.52, 'avg_node_length': 3.55, 'avg_prefix_length': 2.48,
10013                 'clustering_factor': 1.0, 'compression_ratio': 1.19, 'depth': 1,
10014                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10015                 'name': 'RDB$FOREIGN25', 'nodes': 33, 'ratio': 0.03, 'root_page': 270, 'total_dup': 18},
10016                {'avg_data_length': 0.45, 'avg_key_length': 2.64, 'avg_node_length': 3.67, 'avg_prefix_length': 2.21,
10017                 'clustering_factor': 1.0, 'compression_ratio': 1.01, 'depth': 1,
10018                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 7,
10019                 'name': 'RDB$FOREIGN26', 'nodes': 33, 'ratio': 0.03, 'root_page': 271, 'total_dup': 25},
10020                {'avg_data_length': 4.48, 'avg_key_length': 7.42, 'avg_node_length': 8.45, 'avg_prefix_length': 3.52,
10021                 'clustering_factor': 1.0, 'compression_ratio': 1.08, 'depth': 1,
10022                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10023                 'name': 'RDB$PRIMARY24', 'nodes': 33, 'ratio': 0.03, 'root_page': 269, 'total_dup': 0},
10024                {'avg_data_length': 0.97, 'avg_key_length': 3.03, 'avg_node_length': 4.06, 'avg_prefix_length': 9.82,
10025                 'clustering_factor': 1.0, 'compression_ratio': 3.56, 'depth': 1,
10026                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 5, 'leaf_buckets': 1, 'max_dup': 14,
10027                 'name': 'SALESTATX', 'nodes': 33, 'ratio': 0.03, 'root_page': 293, 'total_dup': 27},
10028                {'avg_data_length': 0.0, 'avg_key_length': 0.0, 'avg_node_length': 0.0, 'avg_prefix_length': 0.0,
10029                 'clustering_factor': 0.0, 'compression_ratio': 0.0, 'depth': 1,
10030                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10031                 'name': 'RDB$PRIMARY28', 'nodes': 0, 'ratio': 0.0, 'root_page': 317, 'total_dup': 0}]
10032        i = 0
10033        while i < len(db.tables):
10034            self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
10035            i += 1
10036    def test_parse30_d(self):
10037        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-d.out'))
10038        #
10039        self.assertTrue(db.has_table_stats())
10040        self.assertFalse(db.has_index_stats())
10041        self.assertFalse(db.has_row_stats())
10042        self.assertFalse(db.has_encryption_stats())
10043        self.assertFalse(db.has_system())
10044        # Tables
10045        data = [{'avg_fill': 86, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10046                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10047                 'data_page_slots': 3, 'data_pages': 3, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=2),
10048                 'empty_pages': 0, 'full_pages': 1, 'index_root_page': 299, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10049                 'max_fragments': None, 'max_versions': None, 'name': 'AR', 'pointer_pages': 1, 'primary_pages': 1,
10050                 'primary_pointer_page': 297, 'secondary_pages': 2, 'swept_pages': 0, 'table_id': 140, 'total_formats': None,
10051                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10052                {'avg_fill': 8, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10053                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10054                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
10055                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 183, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10056                 'max_fragments': None, 'max_versions': None, 'name': 'COUNTRY', 'pointer_pages': 1, 'primary_pages': 1,
10057                 'primary_pointer_page': 182, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 128, 'total_formats': None,
10058                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10059                {'avg_fill': 26, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10060                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10061                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10062                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 262, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10063                 'max_fragments': None, 'max_versions': None, 'name': 'CUSTOMER', 'pointer_pages': 1, 'primary_pages': 1,
10064                 'primary_pointer_page': 261, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 137, 'total_formats': None,
10065                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10066                {'avg_fill': 24, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10067                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10068                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10069                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 199, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10070                 'max_fragments': None, 'max_versions': None, 'name': 'DEPARTMENT', 'pointer_pages': 1, 'primary_pages': 1,
10071                 'primary_pointer_page': 198, 'secondary_pages': 0, 'swept_pages': 1, 'table_id': 130, 'total_formats': None,
10072                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10073                {'avg_fill': 44, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10074                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10075                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0),
10076                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 213, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10077                 'max_fragments': None, 'max_versions': None, 'name': 'EMPLOYEE', 'pointer_pages': 1, 'primary_pages': 1,
10078                 'primary_pointer_page': 212, 'secondary_pages': 0, 'swept_pages': 1, 'table_id': 131, 'total_formats': None,
10079                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10080                {'avg_fill': 10, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10081                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10082                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
10083                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 235, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10084                 'max_fragments': None, 'max_versions': None, 'name': 'EMPLOYEE_PROJECT', 'pointer_pages': 1, 'primary_pages': 1,
10085                 'primary_pointer_page': 234, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 134, 'total_formats': None,
10086                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10087                {'avg_fill': 54, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10088                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10089                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=1, d100=0),
10090                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 190, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10091                 'max_fragments': None, 'max_versions': None, 'name': 'JOB', 'pointer_pages': 1, 'primary_pages': 1,
10092                 'primary_pointer_page': 189, 'secondary_pages': 1, 'swept_pages': 1, 'table_id': 129, 'total_formats': None,
10093                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10094                {'avg_fill': 7, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10095                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10096                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
10097                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 221, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10098                 'max_fragments': None, 'max_versions': None, 'name': 'PROJECT', 'pointer_pages': 1, 'primary_pages': 1,
10099                 'primary_pointer_page': 220, 'secondary_pages': 1, 'swept_pages': 1, 'table_id': 133, 'total_formats': None,
10100                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10101                {'avg_fill': 20, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10102                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10103                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=1, d40=1, d50=0, d80=0, d100=0),
10104                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 248, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10105                 'max_fragments': None, 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET', 'pointer_pages': 1, 'primary_pages': 1,
10106                 'primary_pointer_page': 239, 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 135, 'total_formats': None,
10107                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10108                {'avg_fill': 30, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10109                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10110                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10111                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 254, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10112                 'max_fragments': None, 'max_versions': None, 'name': 'SALARY_HISTORY', 'pointer_pages': 1, 'primary_pages': 1,
10113                 'primary_pointer_page': 253, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 136, 'total_formats': None,
10114                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10115                {'avg_fill': 35, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10116                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10117                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10118                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 268, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10119                 'max_fragments': None, 'max_versions': None, 'name': 'SALES', 'pointer_pages': 1, 'primary_pages': 1,
10120                 'primary_pointer_page': 267, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 138, 'total_formats': None,
10121                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10122                {'avg_fill': 0, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10123                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10124                 'data_page_slots': 0, 'data_pages': 0, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0),
10125                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 324, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10126                 'max_fragments': None, 'max_versions': None, 'name': 'T', 'pointer_pages': 1, 'primary_pages': 0,
10127                 'primary_pointer_page': 323, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 147, 'total_formats': None,
10128                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10129                {'avg_fill': 8, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10130                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10131                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
10132                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 303, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10133                 'max_fragments': None, 'max_versions': None, 'name': 'T2', 'pointer_pages': 1, 'primary_pages': 1,
10134                 'primary_pointer_page': 302, 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 142, 'total_formats': None,
10135                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10136                {'avg_fill': 3, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10137                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10138                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
10139                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 306, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10140                 'max_fragments': None, 'max_versions': None, 'name': 'T3', 'pointer_pages': 1, 'primary_pages': 1,
10141                 'primary_pointer_page': 305, 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 143, 'total_formats': None,
10142                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10143                {'avg_fill': 3, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10144                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10145                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
10146                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 308, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10147                 'max_fragments': None, 'max_versions': None, 'name': 'T4', 'pointer_pages': 1, 'primary_pages': 1,
10148                 'primary_pointer_page': 307, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 144, 'total_formats': None,
10149                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10150                {'avg_fill': 0, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10151                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10152                 'data_page_slots': 0, 'data_pages': 0, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0),
10153                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 316, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10154                 'max_fragments': None, 'max_versions': None, 'name': 'T5', 'pointer_pages': 1, 'primary_pages': 0,
10155                 'primary_pointer_page': 315, 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 145, 'total_formats': None,
10156                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None}]
10157        i = 0
10158        while i < len(db.tables):
10159            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
10160            i += 1
10161        # Indices
10162        self.assertEqual(len(db.indices), 0)
10163    def test_parse30_e(self):
10164        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-e.out'))
10165        data = {'attributes': (0,), 'backup_diff_file': None, 'backup_guid': '{F978F787-7023-4C4A-F79D-8D86645B0487}',
10166                'bumped_transaction': None, 'checksum': 12345, 'completed': datetime.datetime(2018, 4, 4, 15, 45, 6),
10167                'continuation_file': None, 'continuation_files': 0, 'creation_date': datetime.datetime(2015, 11, 27, 11, 19, 39),
10168                'database_dialect': 3, 'encrypted_blob_pages': Encryption(pages=11, encrypted=0, unencrypted=11),
10169                'encrypted_data_pages': Encryption(pages=121, encrypted=0, unencrypted=121),
10170                'encrypted_index_pages': Encryption(pages=96, encrypted=0, unencrypted=96),
10171                'executed': datetime.datetime(2018, 4, 4, 15, 45, 6), 'filename': '/home/fdb/test/FBTEST30.FDB', 'flags': 0,
10172                'generation': 2181, 'gstat_version': 3, 'implementation': 'HW=AMD/Intel/x64 little-endian OS=Linux CC=gcc',
10173                'implementation_id': 0, 'indices': 0, 'last_logical_page': None, 'next_attachment_id': 1214,
10174                'next_header_page': 0, 'next_transaction': 2146, 'oat': 2146, 'ods_version': '12.0', 'oit': 179, 'ost': 2146,
10175                'page_buffers': 0, 'page_size': 8192, 'replay_logging_file': None, 'root_filename': None, 'sequence_number': 0,
10176                'shadow_count': 0, 'sweep_interval': None, 'system_change_number': 24, 'tables': 0}
10177        self.assertIsInstance(db, gstat.StatDatabase)
10178        self.assertDictEqual(data, get_object_data(db), 'Unexpected output from parser (database hdr)')
10179        #
10180        self.assertFalse(db.has_table_stats())
10181        self.assertFalse(db.has_index_stats())
10182        self.assertFalse(db.has_row_stats())
10183        self.assertTrue(db.has_encryption_stats())
10184        self.assertFalse(db.has_system())
10185    def test_parse30_f(self):
10186        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-f.out'))
10187        #
10188        self.assertTrue(db.has_table_stats())
10189        self.assertTrue(db.has_index_stats())
10190        self.assertTrue(db.has_row_stats())
10191        self.assertFalse(db.has_encryption_stats())
10192        self.assertTrue(db.has_system())
10193    def test_parse30_i(self):
10194        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-i.out'))
10195        #
10196        self.assertFalse(db.has_table_stats())
10197        self.assertTrue(db.has_index_stats())
10198        self.assertFalse(db.has_row_stats())
10199        self.assertFalse(db.has_encryption_stats())
10200        # Tables
10201        data = [{'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10202                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10203                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10204                 'index_root_page': None, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10205                 'max_versions': None, 'name': 'AR', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10206                 'secondary_pages': None, 'swept_pages': None, 'table_id': 140, 'total_formats': None, 'total_fragments': None,
10207                 'total_records': None, 'total_versions': None, 'used_formats': None},
10208                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10209                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10210                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10211                 'index_root_page': None, 'indices': 1, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10212                 'max_versions': None, 'name': 'COUNTRY', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10213                 'secondary_pages': None, 'swept_pages': None, 'table_id': 128, 'total_formats': None, 'total_fragments': None,
10214                 'total_records': None, 'total_versions': None, 'used_formats': None},
10215                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10216                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10217                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10218                 'index_root_page': None, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10219                 'max_versions': None, 'name': 'CUSTOMER', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10220                 'secondary_pages': None, 'swept_pages': None, 'table_id': 137, 'total_formats': None, 'total_fragments': None,
10221                 'total_records': None, 'total_versions': None, 'used_formats': None},
10222                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10223                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10224                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10225                 'index_root_page': None, 'indices': 5, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10226                 'max_versions': None, 'name': 'DEPARTMENT', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10227                 'secondary_pages': None, 'swept_pages': None, 'table_id': 130, 'total_formats': None, 'total_fragments': None,
10228                 'total_records': None, 'total_versions': None, 'used_formats': None},
10229                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10230                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10231                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10232                 'index_root_page': None, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10233                 'max_versions': None, 'name': 'EMPLOYEE', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10234                 'secondary_pages': None, 'swept_pages': None, 'table_id': 131, 'total_formats': None, 'total_fragments': None,
10235                 'total_records': None, 'total_versions': None, 'used_formats': None},
10236                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10237                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10238                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10239                 'index_root_page': None, 'indices': 3, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10240                 'max_versions': None, 'name': 'EMPLOYEE_PROJECT', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10241                 'secondary_pages': None, 'swept_pages': None, 'table_id': 134, 'total_formats': None, 'total_fragments': None,
10242                 'total_records': None, 'total_versions': None, 'used_formats': None},
10243                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10244                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10245                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10246                 'index_root_page': None, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10247                 'max_versions': None, 'name': 'JOB', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10248                 'secondary_pages': None, 'swept_pages': None, 'table_id': 129, 'total_formats': None, 'total_fragments': None,
10249                 'total_records': None, 'total_versions': None, 'used_formats': None},
10250                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10251                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10252                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10253                 'index_root_page': None, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10254                 'max_versions': None, 'name': 'PROJECT', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10255                 'secondary_pages': None, 'swept_pages': None, 'table_id': 133, 'total_formats': None, 'total_fragments': None,
10256                 'total_records': None, 'total_versions': None, 'used_formats': None},
10257                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10258                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10259                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10260                 'index_root_page': None, 'indices': 3, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10261                 'max_versions': None, 'name': 'PROJ_DEPT_BUDGET', 'pointer_pages': None, 'primary_pages': None,
10262                 'primary_pointer_page': None, 'secondary_pages': None, 'swept_pages': None, 'table_id': 135, 'total_formats': None,
10263                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10264                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10265                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10266                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10267                 'index_root_page': None, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10268                 'max_versions': None, 'name': 'SALARY_HISTORY', 'pointer_pages': None, 'primary_pages': None,
10269                 'primary_pointer_page': None, 'secondary_pages': None, 'swept_pages': None, 'table_id': 136, 'total_formats': None,
10270                 'total_fragments': None, 'total_records': None, 'total_versions': None, 'used_formats': None},
10271                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10272                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10273                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10274                 'index_root_page': None, 'indices': 6, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10275                 'max_versions': None, 'name': 'SALES', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10276                 'secondary_pages': None, 'swept_pages': None, 'table_id': 138, 'total_formats': None, 'total_fragments': None,
10277                 'total_records': None, 'total_versions': None, 'used_formats': None},
10278                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10279                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10280                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10281                 'index_root_page': None, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10282                 'max_versions': None, 'name': 'T', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10283                 'secondary_pages': None, 'swept_pages': None, 'table_id': 147, 'total_formats': None, 'total_fragments': None,
10284                 'total_records': None, 'total_versions': None, 'used_formats': None},
10285                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10286                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10287                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10288                 'index_root_page': None, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10289                 'max_versions': None, 'name': 'T2', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10290                 'secondary_pages': None, 'swept_pages': None, 'table_id': 142, 'total_formats': None, 'total_fragments': None,
10291                 'total_records': None, 'total_versions': None, 'used_formats': None},
10292                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10293                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10294                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10295                 'index_root_page': None, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10296                 'max_versions': None, 'name': 'T3', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10297                 'secondary_pages': None, 'swept_pages': None, 'table_id': 143, 'total_formats': None, 'total_fragments': None,
10298                 'total_records': None, 'total_versions': None, 'used_formats': None},
10299                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10300                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10301                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10302                 'index_root_page': None, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10303                 'max_versions': None, 'name': 'T4', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10304                 'secondary_pages': None, 'swept_pages': None, 'table_id': 144, 'total_formats': None, 'total_fragments': None,
10305                 'total_records': None, 'total_versions': None, 'used_formats': None},
10306                {'avg_fill': None, 'avg_fragment_length': None, 'avg_record_length': None, 'avg_unpacked_length': None,
10307                 'avg_version_length': None, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': None,
10308                 'data_page_slots': None, 'data_pages': None, 'distribution': None, 'empty_pages': None, 'full_pages': None,
10309                 'index_root_page': None, 'indices': 1, 'level_0': None, 'level_1': None, 'level_2': None, 'max_fragments': None,
10310                 'max_versions': None, 'name': 'T5', 'pointer_pages': None, 'primary_pages': None, 'primary_pointer_page': None,
10311                 'secondary_pages': None, 'swept_pages': None, 'table_id': 145, 'total_formats': None, 'total_fragments': None,
10312                 'total_records': None, 'total_versions': None, 'used_formats': None}]
10313        i = 0
10314        while i < len(db.tables):
10315            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
10316            i += 1
10317        # Indices
10318        data = [{'avg_data_length': 6.44, 'avg_key_length': 8.63, 'avg_node_length': 10.44, 'avg_prefix_length': 0.44,
10319                 'clustering_factor': 1.0, 'compression_ratio': 0.8, 'depth': 1,
10320                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10321                 'name': 'RDB$PRIMARY1', 'nodes': 16, 'ratio': 0.06, 'root_page': 186, 'total_dup': 0},
10322                {'avg_data_length': 15.87, 'avg_key_length': 18.27, 'avg_node_length': 19.87, 'avg_prefix_length': 0.6,
10323                 'clustering_factor': 1.0, 'compression_ratio': 0.9, 'depth': 1,
10324                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0,
10325                 'name': 'CUSTNAMEX', 'nodes': 15, 'ratio': 0.07, 'root_page': 276, 'total_dup': 0},
10326                {'avg_data_length': 17.27, 'avg_key_length': 20.2, 'avg_node_length': 21.27, 'avg_prefix_length': 2.33,
10327                 'clustering_factor': 1.0, 'compression_ratio': 0.97, 'depth': 1,
10328                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
10329                 'name': 'CUSTREGION', 'nodes': 15, 'ratio': 0.07, 'root_page': 283, 'total_dup': 0},
10330                {'avg_data_length': 4.87, 'avg_key_length': 6.93, 'avg_node_length': 8.6, 'avg_prefix_length': 0.87,
10331                 'clustering_factor': 1.0, 'compression_ratio': 0.83, 'depth': 1,
10332                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10333                 'name': 'RDB$FOREIGN23', 'nodes': 15, 'ratio': 0.07, 'root_page': 264, 'total_dup': 4},
10334                {'avg_data_length': 1.13, 'avg_key_length': 3.13, 'avg_node_length': 4.2, 'avg_prefix_length': 1.87,
10335                 'clustering_factor': 1.0, 'compression_ratio': 0.96, 'depth': 1,
10336                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10337                 'name': 'RDB$PRIMARY22', 'nodes': 15, 'ratio': 0.07, 'root_page': 263, 'total_dup': 0},
10338                {'avg_data_length': 5.38, 'avg_key_length': 8.0, 'avg_node_length': 9.05, 'avg_prefix_length': 3.62,
10339                 'clustering_factor': 1.0, 'compression_ratio': 1.13, 'depth': 1,
10340                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 3,
10341                 'name': 'BUDGETX', 'nodes': 21, 'ratio': 0.05, 'root_page': 284, 'total_dup': 7},
10342                {'avg_data_length': 13.95, 'avg_key_length': 16.57, 'avg_node_length': 17.95, 'avg_prefix_length': 5.29,
10343                 'clustering_factor': 1.0, 'compression_ratio': 1.16, 'depth': 1,
10344                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10345                 'name': 'RDB$4', 'nodes': 21, 'ratio': 0.05, 'root_page': 208, 'total_dup': 0},
10346                {'avg_data_length': 1.14, 'avg_key_length': 3.24, 'avg_node_length': 4.29, 'avg_prefix_length': 0.81,
10347                 'clustering_factor': 1.0, 'compression_ratio': 0.6, 'depth': 1,
10348                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3,
10349                 'name': 'RDB$FOREIGN10', 'nodes': 21, 'ratio': 0.05, 'root_page': 219, 'total_dup': 3},
10350                {'avg_data_length': 0.81, 'avg_key_length': 2.95, 'avg_node_length': 4.1, 'avg_prefix_length': 2.05,
10351                 'clustering_factor': 1.0, 'compression_ratio': 0.97, 'depth': 1,
10352                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4,
10353                 'name': 'RDB$FOREIGN6', 'nodes': 21, 'ratio': 0.05, 'root_page': 210, 'total_dup': 13},
10354                {'avg_data_length': 1.71, 'avg_key_length': 4.05, 'avg_node_length': 5.24, 'avg_prefix_length': 1.29,
10355                 'clustering_factor': 1.0, 'compression_ratio': 0.74, 'depth': 1,
10356                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0,
10357                 'name': 'RDB$PRIMARY5', 'nodes': 21, 'ratio': 0.05, 'root_page': 209, 'total_dup': 0},
10358                {'avg_data_length': 15.52, 'avg_key_length': 18.5, 'avg_node_length': 19.52, 'avg_prefix_length': 2.17,
10359                 'clustering_factor': 1.0, 'compression_ratio': 0.96, 'depth': 1,
10360                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
10361                 'name': 'NAMEX', 'nodes': 42, 'ratio': 0.02, 'root_page': 285, 'total_dup': 0},
10362                {'avg_data_length': 0.81, 'avg_key_length': 2.98, 'avg_node_length': 4.07, 'avg_prefix_length': 2.19,
10363                 'clustering_factor': 1.0, 'compression_ratio': 1.01, 'depth': 1,
10364                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10365                 'name': 'RDB$FOREIGN8', 'nodes': 42, 'ratio': 0.02, 'root_page': 215, 'total_dup': 23},
10366                {'avg_data_length': 6.79, 'avg_key_length': 9.4, 'avg_node_length': 10.43, 'avg_prefix_length': 9.05,
10367                 'clustering_factor': 1.0, 'compression_ratio': 1.68, 'depth': 1,
10368                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4,
10369                 'name': 'RDB$FOREIGN9', 'nodes': 42, 'ratio': 0.02, 'root_page': 216, 'total_dup': 15},
10370                {'avg_data_length': 1.31, 'avg_key_length': 3.6, 'avg_node_length': 4.62, 'avg_prefix_length': 1.17,
10371                 'clustering_factor': 1.0, 'compression_ratio': 0.69, 'depth': 1,
10372                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10373                 'name': 'RDB$PRIMARY7', 'nodes': 42, 'ratio': 0.02, 'root_page': 214, 'total_dup': 0},
10374                {'avg_data_length': 1.04, 'avg_key_length': 3.25, 'avg_node_length': 4.29, 'avg_prefix_length': 1.36,
10375                 'clustering_factor': 1.0, 'compression_ratio': 0.74, 'depth': 1,
10376                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2,
10377                 'name': 'RDB$FOREIGN15', 'nodes': 28, 'ratio': 0.04, 'root_page': 237, 'total_dup': 6},
10378                {'avg_data_length': 0.86, 'avg_key_length': 2.89, 'avg_node_length': 4.04, 'avg_prefix_length': 4.14,
10379                 'clustering_factor': 1.0, 'compression_ratio': 1.73, 'depth': 1,
10380                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 9,
10381                 'name': 'RDB$FOREIGN16', 'nodes': 28, 'ratio': 0.04, 'root_page': 238, 'total_dup': 23},
10382                {'avg_data_length': 9.11, 'avg_key_length': 12.07, 'avg_node_length': 13.11, 'avg_prefix_length': 2.89,
10383                 'clustering_factor': 1.0, 'compression_ratio': 0.99, 'depth': 1,
10384                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10385                 'name': 'RDB$PRIMARY14', 'nodes': 28, 'ratio': 0.04, 'root_page': 236, 'total_dup': 0},
10386                {'avg_data_length': 10.9, 'avg_key_length': 13.71, 'avg_node_length': 14.74, 'avg_prefix_length': 7.87,
10387                 'clustering_factor': 1.0, 'compression_ratio': 1.37, 'depth': 1,
10388                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 1,
10389                 'name': 'MAXSALX', 'nodes': 31, 'ratio': 0.03, 'root_page': 286, 'total_dup': 5},
10390                {'avg_data_length': 10.29, 'avg_key_length': 13.03, 'avg_node_length': 14.06, 'avg_prefix_length': 8.48,
10391                 'clustering_factor': 1.0, 'compression_ratio': 1.44, 'depth': 1,
10392                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 2,
10393                 'name': 'MINSALX', 'nodes': 31, 'ratio': 0.03, 'root_page': 287, 'total_dup': 7},
10394                {'avg_data_length': 1.39, 'avg_key_length': 3.39, 'avg_node_length': 4.61, 'avg_prefix_length': 2.77,
10395                 'clustering_factor': 1.0, 'compression_ratio': 1.23, 'depth': 1,
10396                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 20,
10397                 'name': 'RDB$FOREIGN3', 'nodes': 31, 'ratio': 0.03, 'root_page': 192, 'total_dup': 24},
10398                {'avg_data_length': 10.45, 'avg_key_length': 13.42, 'avg_node_length': 14.45, 'avg_prefix_length': 6.19,
10399                 'clustering_factor': 1.0, 'compression_ratio': 1.24, 'depth': 1,
10400                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10401                 'name': 'RDB$PRIMARY2', 'nodes': 31, 'ratio': 0.03, 'root_page': 191, 'total_dup': 0},
10402                {'avg_data_length': 22.5, 'avg_key_length': 25.33, 'avg_node_length': 26.5, 'avg_prefix_length': 4.17,
10403                 'clustering_factor': 1.0, 'compression_ratio': 1.05, 'depth': 1,
10404                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
10405                 'name': 'PRODTYPEX', 'nodes': 6, 'ratio': 0.17, 'root_page': 288, 'total_dup': 0},
10406                {'avg_data_length': 13.33, 'avg_key_length': 15.5, 'avg_node_length': 17.33, 'avg_prefix_length': 0.33,
10407                 'clustering_factor': 1.0, 'compression_ratio': 0.88, 'depth': 1,
10408                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10409                 'name': 'RDB$11', 'nodes': 6, 'ratio': 0.17, 'root_page': 222, 'total_dup': 0},
10410                {'avg_data_length': 1.33, 'avg_key_length': 3.5, 'avg_node_length': 4.67, 'avg_prefix_length': 0.67,
10411                 'clustering_factor': 1.0, 'compression_ratio': 0.57, 'depth': 1,
10412                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0,
10413                 'name': 'RDB$FOREIGN13', 'nodes': 6, 'ratio': 0.17, 'root_page': 232, 'total_dup': 0},
10414                {'avg_data_length': 4.83, 'avg_key_length': 7.0, 'avg_node_length': 8.83, 'avg_prefix_length': 0.17,
10415                 'clustering_factor': 1.0, 'compression_ratio': 0.71, 'depth': 1,
10416                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0,
10417                 'name': 'RDB$PRIMARY12', 'nodes': 6, 'ratio': 0.17, 'root_page': 223, 'total_dup': 0},
10418                {'avg_data_length': 0.71, 'avg_key_length': 2.79, 'avg_node_length': 3.92, 'avg_prefix_length': 2.29,
10419                 'clustering_factor': 1.0, 'compression_ratio': 1.07, 'depth': 1,
10420                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 5,
10421                 'name': 'RDB$FOREIGN18', 'nodes': 24, 'ratio': 0.04, 'root_page': 250, 'total_dup': 15},
10422                {'avg_data_length': 1.0, 'avg_key_length': 3.04, 'avg_node_length': 4.21, 'avg_prefix_length': 4.0,
10423                 'clustering_factor': 1.0, 'compression_ratio': 1.64, 'depth': 1,
10424                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 8,
10425                 'name': 'RDB$FOREIGN19', 'nodes': 24, 'ratio': 0.04, 'root_page': 251, 'total_dup': 19},
10426                {'avg_data_length': 6.83, 'avg_key_length': 9.67, 'avg_node_length': 10.71, 'avg_prefix_length': 12.17,
10427                 'clustering_factor': 1.0, 'compression_ratio': 1.97, 'depth': 1,
10428                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10429                 'name': 'RDB$PRIMARY17', 'nodes': 24, 'ratio': 0.04, 'root_page': 249, 'total_dup': 0},
10430                {'avg_data_length': 0.31, 'avg_key_length': 2.35, 'avg_node_length': 3.37, 'avg_prefix_length': 6.69,
10431                 'clustering_factor': 1.0, 'compression_ratio': 2.98, 'depth': 1,
10432                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 21,
10433                 'name': 'CHANGEX', 'nodes': 49, 'ratio': 0.02, 'root_page': 289, 'total_dup': 46},
10434                {'avg_data_length': 0.9, 'avg_key_length': 3.1, 'avg_node_length': 4.12, 'avg_prefix_length': 1.43,
10435                 'clustering_factor': 1.0, 'compression_ratio': 0.75, 'depth': 1,
10436                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2,
10437                 'name': 'RDB$FOREIGN21', 'nodes': 49, 'ratio': 0.02, 'root_page': 256, 'total_dup': 16},
10438                {'avg_data_length': 18.29, 'avg_key_length': 21.27, 'avg_node_length': 22.29, 'avg_prefix_length': 4.31,
10439                 'clustering_factor': 1.0, 'compression_ratio': 1.06, 'depth': 1,
10440                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10441                 'name': 'RDB$PRIMARY20', 'nodes': 49, 'ratio': 0.02, 'root_page': 255, 'total_dup': 0},
10442                {'avg_data_length': 0.29, 'avg_key_length': 2.29, 'avg_node_length': 3.35, 'avg_prefix_length': 5.39,
10443                 'clustering_factor': 1.0, 'compression_ratio': 2.48, 'depth': 1,
10444                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 28,
10445                 'name': 'UPDATERX', 'nodes': 49, 'ratio': 0.02, 'root_page': 290, 'total_dup': 46},
10446                {'avg_data_length': 2.55, 'avg_key_length': 4.94, 'avg_node_length': 5.97, 'avg_prefix_length': 2.88,
10447                 'clustering_factor': 1.0, 'compression_ratio': 1.1, 'depth': 1,
10448                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 6,
10449                 'name': 'NEEDX', 'nodes': 33, 'ratio': 0.03, 'root_page': 291, 'total_dup': 11},
10450                {'avg_data_length': 1.85, 'avg_key_length': 4.03, 'avg_node_length': 5.06, 'avg_prefix_length': 11.18,
10451                 'clustering_factor': 1.0, 'compression_ratio': 3.23, 'depth': 1,
10452                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3,
10453                 'name': 'QTYX', 'nodes': 33, 'ratio': 0.03, 'root_page': 292, 'total_dup': 11},
10454                {'avg_data_length': 0.52, 'avg_key_length': 2.52, 'avg_node_length': 3.55, 'avg_prefix_length': 2.48,
10455                 'clustering_factor': 1.0, 'compression_ratio': 1.19, 'depth': 1,
10456                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10457                 'name': 'RDB$FOREIGN25', 'nodes': 33, 'ratio': 0.03, 'root_page': 270, 'total_dup': 18},
10458                {'avg_data_length': 0.45, 'avg_key_length': 2.64, 'avg_node_length': 3.67, 'avg_prefix_length': 2.21,
10459                 'clustering_factor': 1.0, 'compression_ratio': 1.01, 'depth': 1,
10460                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 7,
10461                 'name': 'RDB$FOREIGN26', 'nodes': 33, 'ratio': 0.03, 'root_page': 271, 'total_dup': 25},
10462                {'avg_data_length': 4.48, 'avg_key_length': 7.42, 'avg_node_length': 8.45, 'avg_prefix_length': 3.52,
10463                 'clustering_factor': 1.0, 'compression_ratio': 1.08, 'depth': 1,
10464                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10465                 'name': 'RDB$PRIMARY24', 'nodes': 33, 'ratio': 0.03, 'root_page': 269, 'total_dup': 0},
10466                {'avg_data_length': 0.97, 'avg_key_length': 3.03, 'avg_node_length': 4.06, 'avg_prefix_length': 9.82,
10467                 'clustering_factor': 1.0, 'compression_ratio': 3.56, 'depth': 1,
10468                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 5, 'leaf_buckets': 1, 'max_dup': 14,
10469                 'name': 'SALESTATX', 'nodes': 33, 'ratio': 0.03, 'root_page': 293, 'total_dup': 27},
10470                {'avg_data_length': 0.0, 'avg_key_length': 0.0, 'avg_node_length': 0.0, 'avg_prefix_length': 0.0,
10471                 'clustering_factor': 0.0, 'compression_ratio': 0.0, 'depth': 1,
10472                 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10473                 'name': 'RDB$PRIMARY28', 'nodes': 0, 'ratio': 0.0, 'root_page': 317, 'total_dup': 0}]
10474        i = 0
10475        while i < len(db.tables):
10476            self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
10477            i += 1
10478    def test_parse30_r(self):
10479        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-r.out'))
10480        #
10481        self.assertTrue(db.has_table_stats())
10482        self.assertTrue(db.has_index_stats())
10483        self.assertTrue(db.has_row_stats())
10484        self.assertFalse(db.has_encryption_stats())
10485        self.assertFalse(db.has_system())
10486        # Tables
10487        data = [{'avg_fill': 86, 'avg_fragment_length': 0.0, 'avg_record_length': 2.79, 'avg_unpacked_length': 120.0,
10488                 'avg_version_length': 16.61, 'blob_pages': 0, 'blobs': 125, 'blobs_total_length': 11237, 'compression_ratio': 42.99,
10489                 'data_page_slots': 3, 'data_pages': 3, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=1, d100=2),
10490                 'empty_pages': 0, 'full_pages': 1, 'index_root_page': 299, 'indices': 0, 'level_0': 125, 'level_1': 0, 'level_2': 0,
10491                 'max_fragments': 0, 'max_versions': 1, 'name': 'AR', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 297,
10492                 'secondary_pages': 2, 'swept_pages': 0, 'table_id': 140, 'total_formats': 1, 'total_fragments': 0, 'total_records': 120,
10493                 'total_versions': 105, 'used_formats': 1},
10494                {'avg_fill': 8, 'avg_fragment_length': 0.0, 'avg_record_length': 25.94, 'avg_unpacked_length': 34.0,
10495                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 1.31,
10496                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
10497                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 183, 'indices': 1, 'level_0': None, 'level_1': None, 'level_2': None,
10498                 'max_fragments': 0, 'max_versions': 0, 'name': 'COUNTRY', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 182,
10499                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 128, 'total_formats': 1, 'total_fragments': 0, 'total_records': 16,
10500                 'total_versions': 0, 'used_formats': 1},
10501                {'avg_fill': 26, 'avg_fragment_length': 0.0, 'avg_record_length': 125.47, 'avg_unpacked_length': 241.0,
10502                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 1.92,
10503                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10504                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 262, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
10505                 'max_fragments': 0, 'max_versions': 0, 'name': 'CUSTOMER', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 261,
10506                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 137, 'total_formats': 1, 'total_fragments': 0, 'total_records': 15,
10507                 'total_versions': 0, 'used_formats': 1},
10508                {'avg_fill': 24, 'avg_fragment_length': 0.0, 'avg_record_length': 74.62, 'avg_unpacked_length': 88.0,
10509                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 1.18,
10510                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10511                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 199, 'indices': 5, 'level_0': None, 'level_1': None, 'level_2': None,
10512                 'max_fragments': 0, 'max_versions': 0, 'name': 'DEPARTMENT', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 198,
10513                 'secondary_pages': 0, 'swept_pages': 1, 'table_id': 130, 'total_formats': 1, 'total_fragments': 0, 'total_records': 21,
10514                 'total_versions': 0, 'used_formats': 1},
10515                {'avg_fill': 44, 'avg_fragment_length': 0.0, 'avg_record_length': 69.02, 'avg_unpacked_length': 39.0,
10516                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.57,
10517                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=0, d50=1, d80=0, d100=0),
10518                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 213, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
10519                 'max_fragments': 0, 'max_versions': 0, 'name': 'EMPLOYEE', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 212,
10520                 'secondary_pages': 0, 'swept_pages': 1, 'table_id': 131, 'total_formats': 1, 'total_fragments': 0, 'total_records': 42,
10521                 'total_versions': 0, 'used_formats': 1},
10522                {'avg_fill': 10, 'avg_fragment_length': 0.0, 'avg_record_length': 12.0, 'avg_unpacked_length': 11.0,
10523                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.92,
10524                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
10525                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 235, 'indices': 3, 'level_0': None, 'level_1': None, 'level_2': None,
10526                 'max_fragments': 0, 'max_versions': 0, 'name': 'EMPLOYEE_PROJECT', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 234,
10527                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 134, 'total_formats': 1, 'total_fragments': 0, 'total_records': 28,
10528                 'total_versions': 0, 'used_formats': 1},
10529                {'avg_fill': 54, 'avg_fragment_length': 0.0, 'avg_record_length': 66.13, 'avg_unpacked_length': 96.0,
10530                 'avg_version_length': 0.0, 'blob_pages': 0, 'blobs': 39, 'blobs_total_length': 4840, 'compression_ratio': 1.45,
10531                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=1, d100=0),
10532                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 190, 'indices': 4, 'level_0': 39, 'level_1': 0, 'level_2': 0,
10533                 'max_fragments': 0, 'max_versions': 0, 'name': 'JOB', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 189,
10534                 'secondary_pages': 1, 'swept_pages': 1, 'table_id': 129, 'total_formats': 1, 'total_fragments': 0, 'total_records': 31,
10535                 'total_versions': 0, 'used_formats': 1},
10536                {'avg_fill': 7, 'avg_fragment_length': 0.0, 'avg_record_length': 49.67, 'avg_unpacked_length': 56.0,
10537                 'avg_version_length': 0.0, 'blob_pages': 0, 'blobs': 6, 'blobs_total_length': 548, 'compression_ratio': 1.13,
10538                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
10539                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 221, 'indices': 4, 'level_0': 6, 'level_1': 0, 'level_2': 0,
10540                 'max_fragments': 0, 'max_versions': 0, 'name': 'PROJECT', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 220,
10541                 'secondary_pages': 1, 'swept_pages': 1, 'table_id': 133, 'total_formats': 1, 'total_fragments': 0, 'total_records': 6,
10542                 'total_versions': 0, 'used_formats': 1},
10543                {'avg_fill': 20, 'avg_fragment_length': 0.0, 'avg_record_length': 30.58, 'avg_unpacked_length': 32.0,
10544                 'avg_version_length': 0.0, 'blob_pages': 0, 'blobs': 24, 'blobs_total_length': 1344, 'compression_ratio': 1.05,
10545                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=1, d40=1, d50=0, d80=0, d100=0),
10546                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 248, 'indices': 3, 'level_0': 24, 'level_1': 0, 'level_2': 0,
10547                 'max_fragments': 0, 'max_versions': 0, 'name': 'PROJ_DEPT_BUDGET', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 239,
10548                 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 135, 'total_formats': 1, 'total_fragments': 0, 'total_records': 24,
10549                 'total_versions': 0, 'used_formats': 1},
10550                {'avg_fill': 30, 'avg_fragment_length': 0.0, 'avg_record_length': 33.29, 'avg_unpacked_length': 8.0,
10551                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.24,
10552                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10553                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 254, 'indices': 4, 'level_0': None, 'level_1': None, 'level_2': None,
10554                 'max_fragments': 0, 'max_versions': 0, 'name': 'SALARY_HISTORY', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 253,
10555                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 136, 'total_formats': 1, 'total_fragments': 0, 'total_records': 49,
10556                 'total_versions': 0, 'used_formats': 1},
10557                {'avg_fill': 35, 'avg_fragment_length': 0.0, 'avg_record_length': 68.82, 'avg_unpacked_length': 8.0,
10558                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.12,
10559                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=0, d40=1, d50=0, d80=0, d100=0),
10560                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 268, 'indices': 6, 'level_0': None, 'level_1': None, 'level_2': None,
10561                 'max_fragments': 0, 'max_versions': 0, 'name': 'SALES', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 267,
10562                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 138, 'total_formats': 1, 'total_fragments': 0, 'total_records': 33,
10563                 'total_versions': 0, 'used_formats': 1},
10564                {'avg_fill': 0, 'avg_fragment_length': 0.0, 'avg_record_length': 0.0, 'avg_unpacked_length': 0.0,
10565                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.0,
10566                 'data_page_slots': 0, 'data_pages': 0, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0),
10567                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 324, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10568                 'max_fragments': 0, 'max_versions': 0, 'name': 'T', 'pointer_pages': 1, 'primary_pages': 0, 'primary_pointer_page': 323,
10569                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 147, 'total_formats': 1, 'total_fragments': 0, 'total_records': 0,
10570                 'total_versions': 0, 'used_formats': 0},
10571                {'avg_fill': 8, 'avg_fragment_length': 0.0, 'avg_record_length': 0.0, 'avg_unpacked_length': 120.0,
10572                 'avg_version_length': 14.25, 'blob_pages': 0, 'blobs': 3, 'blobs_total_length': 954, 'compression_ratio': 0.0,
10573                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
10574                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 303, 'indices': 0, 'level_0': 3, 'level_1': 0, 'level_2': 0,
10575                 'max_fragments': 0, 'max_versions': 1, 'name': 'T2', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 302,
10576                 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 142, 'total_formats': 1, 'total_fragments': 0, 'total_records': 4,
10577                 'total_versions': 4, 'used_formats': 1},
10578                {'avg_fill': 3, 'avg_fragment_length': 0.0, 'avg_record_length': 0.0, 'avg_unpacked_length': 112.0,
10579                 'avg_version_length': 22.67, 'blob_pages': 0, 'blobs': 2, 'blobs_total_length': 313, 'compression_ratio': 0.0,
10580                 'data_page_slots': 2, 'data_pages': 2, 'distribution': FillDistribution(d20=2, d40=0, d50=0, d80=0, d100=0),
10581                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 306, 'indices': 0, 'level_0': 2, 'level_1': 0, 'level_2': 0,
10582                 'max_fragments': 0, 'max_versions': 1, 'name': 'T3', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 305,
10583                 'secondary_pages': 1, 'swept_pages': 0, 'table_id': 143, 'total_formats': 1, 'total_fragments': 0, 'total_records': 3,
10584                 'total_versions': 3, 'used_formats': 1},
10585                {'avg_fill': 3, 'avg_fragment_length': 0.0, 'avg_record_length': 0.0, 'avg_unpacked_length': 264.0,
10586                 'avg_version_length': 75.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.0,
10587                 'data_page_slots': 1, 'data_pages': 1, 'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0),
10588                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 308, 'indices': 0, 'level_0': None, 'level_1': None, 'level_2': None,
10589                 'max_fragments': 0, 'max_versions': 1, 'name': 'T4', 'pointer_pages': 1, 'primary_pages': 1, 'primary_pointer_page': 307,
10590                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 144, 'total_formats': 1, 'total_fragments': 0, 'total_records': 2,
10591                 'total_versions': 2, 'used_formats': 1},
10592                {'avg_fill': 0, 'avg_fragment_length': 0.0, 'avg_record_length': 0.0, 'avg_unpacked_length': 0.0,
10593                 'avg_version_length': 0.0, 'blob_pages': None, 'blobs': None, 'blobs_total_length': None, 'compression_ratio': 0.0,
10594                 'data_page_slots': 0, 'data_pages': 0, 'distribution': FillDistribution(d20=0, d40=0, d50=0, d80=0, d100=0),
10595                 'empty_pages': 0, 'full_pages': 0, 'index_root_page': 316, 'indices': 1, 'level_0': None, 'level_1': None, 'level_2': None,
10596                 'max_fragments': 0, 'max_versions': 0, 'name': 'T5', 'pointer_pages': 1, 'primary_pages': 0, 'primary_pointer_page': 315,
10597                 'secondary_pages': 0, 'swept_pages': 0, 'table_id': 145, 'total_formats': 1, 'total_fragments': 0, 'total_records': 0,
10598                 'total_versions': 0, 'used_formats': 0}]
10599        i = 0
10600        while i < len(db.tables):
10601            self.assertDictEqual(data[i], get_object_data(db.tables[i]), 'Unexpected output from parser (tables)')
10602            i += 1
10603            # Indices
10604            data = [{'avg_data_length': 6.44, 'avg_key_length': 8.63, 'avg_node_length': 10.44, 'avg_prefix_length': 0.44,
10605                     'clustering_factor': 1.0, 'compression_ratio': 0.8, 'depth': 1,
10606                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10607                     'name': 'RDB$PRIMARY1', 'nodes': 16, 'ratio': 0.06, 'root_page': 186, 'total_dup': 0},
10608                    {'avg_data_length': 15.87, 'avg_key_length': 18.27, 'avg_node_length': 19.87, 'avg_prefix_length': 0.6,
10609                     'clustering_factor': 1.0, 'compression_ratio': 0.9, 'depth': 1,
10610                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0,
10611                     'name': 'CUSTNAMEX', 'nodes': 15, 'ratio': 0.07, 'root_page': 276, 'total_dup': 0},
10612                    {'avg_data_length': 17.27, 'avg_key_length': 20.2, 'avg_node_length': 21.27, 'avg_prefix_length': 2.33,
10613                     'clustering_factor': 1.0, 'compression_ratio': 0.97, 'depth': 1,
10614                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
10615                     'name': 'CUSTREGION', 'nodes': 15, 'ratio': 0.07, 'root_page': 283, 'total_dup': 0},
10616                    {'avg_data_length': 4.87, 'avg_key_length': 6.93, 'avg_node_length': 8.6, 'avg_prefix_length': 0.87,
10617                     'clustering_factor': 1.0, 'compression_ratio': 0.83, 'depth': 1,
10618                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10619                     'name': 'RDB$FOREIGN23', 'nodes': 15, 'ratio': 0.07, 'root_page': 264, 'total_dup': 4},
10620                    {'avg_data_length': 1.13, 'avg_key_length': 3.13, 'avg_node_length': 4.2, 'avg_prefix_length': 1.87,
10621                     'clustering_factor': 1.0, 'compression_ratio': 0.96, 'depth': 1,
10622                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10623                     'name': 'RDB$PRIMARY22', 'nodes': 15, 'ratio': 0.07, 'root_page': 263, 'total_dup': 0},
10624                    {'avg_data_length': 5.38, 'avg_key_length': 8.0, 'avg_node_length': 9.05, 'avg_prefix_length': 3.62,
10625                     'clustering_factor': 1.0, 'compression_ratio': 1.13, 'depth': 1,
10626                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 3,
10627                     'name': 'BUDGETX', 'nodes': 21, 'ratio': 0.05, 'root_page': 284, 'total_dup': 7},
10628                    {'avg_data_length': 13.95, 'avg_key_length': 16.57, 'avg_node_length': 17.95, 'avg_prefix_length': 5.29,
10629                     'clustering_factor': 1.0, 'compression_ratio': 1.16, 'depth': 1,
10630                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10631                     'name': 'RDB$4', 'nodes': 21, 'ratio': 0.05, 'root_page': 208, 'total_dup': 0},
10632                    {'avg_data_length': 1.14, 'avg_key_length': 3.24, 'avg_node_length': 4.29, 'avg_prefix_length': 0.81,
10633                     'clustering_factor': 1.0, 'compression_ratio': 0.6, 'depth': 1,
10634                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3,
10635                     'name': 'RDB$FOREIGN10', 'nodes': 21, 'ratio': 0.05, 'root_page': 219, 'total_dup': 3},
10636                    {'avg_data_length': 0.81, 'avg_key_length': 2.95, 'avg_node_length': 4.1, 'avg_prefix_length': 2.05,
10637                     'clustering_factor': 1.0, 'compression_ratio': 0.97, 'depth': 1,
10638                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4,
10639                     'name': 'RDB$FOREIGN6', 'nodes': 21, 'ratio': 0.05, 'root_page': 210, 'total_dup': 13},
10640                    {'avg_data_length': 1.71, 'avg_key_length': 4.05, 'avg_node_length': 5.24, 'avg_prefix_length': 1.29,
10641                     'clustering_factor': 1.0, 'compression_ratio': 0.74, 'depth': 1,
10642                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0,
10643                     'name': 'RDB$PRIMARY5', 'nodes': 21, 'ratio': 0.05, 'root_page': 209, 'total_dup': 0},
10644                    {'avg_data_length': 15.52, 'avg_key_length': 18.5, 'avg_node_length': 19.52, 'avg_prefix_length': 2.17,
10645                     'clustering_factor': 1.0, 'compression_ratio': 0.96, 'depth': 1,
10646                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
10647                     'name': 'NAMEX', 'nodes': 42, 'ratio': 0.02, 'root_page': 285, 'total_dup': 0},
10648                    {'avg_data_length': 0.81, 'avg_key_length': 2.98, 'avg_node_length': 4.07, 'avg_prefix_length': 2.19,
10649                     'clustering_factor': 1.0, 'compression_ratio': 1.01, 'depth': 1,
10650                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10651                     'name': 'RDB$FOREIGN8', 'nodes': 42, 'ratio': 0.02, 'root_page': 215, 'total_dup': 23},
10652                    {'avg_data_length': 6.79, 'avg_key_length': 9.4, 'avg_node_length': 10.43, 'avg_prefix_length': 9.05,
10653                     'clustering_factor': 1.0, 'compression_ratio': 1.68, 'depth': 1,
10654                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 4,
10655                     'name': 'RDB$FOREIGN9', 'nodes': 42, 'ratio': 0.02, 'root_page': 216, 'total_dup': 15},
10656                    {'avg_data_length': 1.31, 'avg_key_length': 3.6, 'avg_node_length': 4.62, 'avg_prefix_length': 1.17,
10657                     'clustering_factor': 1.0, 'compression_ratio': 0.69, 'depth': 1,
10658                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10659                     'name': 'RDB$PRIMARY7', 'nodes': 42, 'ratio': 0.02, 'root_page': 214, 'total_dup': 0},
10660                    {'avg_data_length': 1.04, 'avg_key_length': 3.25, 'avg_node_length': 4.29, 'avg_prefix_length': 1.36,
10661                     'clustering_factor': 1.0, 'compression_ratio': 0.74, 'depth': 1,
10662                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2,
10663                     'name': 'RDB$FOREIGN15', 'nodes': 28, 'ratio': 0.04, 'root_page': 237, 'total_dup': 6},
10664                    {'avg_data_length': 0.86, 'avg_key_length': 2.89, 'avg_node_length': 4.04, 'avg_prefix_length': 4.14,
10665                     'clustering_factor': 1.0, 'compression_ratio': 1.73, 'depth': 1,
10666                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 9,
10667                     'name': 'RDB$FOREIGN16', 'nodes': 28, 'ratio': 0.04, 'root_page': 238, 'total_dup': 23},
10668                    {'avg_data_length': 9.11, 'avg_key_length': 12.07, 'avg_node_length': 13.11, 'avg_prefix_length': 2.89,
10669                     'clustering_factor': 1.0, 'compression_ratio': 0.99, 'depth': 1,
10670                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10671                     'name': 'RDB$PRIMARY14', 'nodes': 28, 'ratio': 0.04, 'root_page': 236, 'total_dup': 0},
10672                    {'avg_data_length': 10.9, 'avg_key_length': 13.71, 'avg_node_length': 14.74, 'avg_prefix_length': 7.87,
10673                     'clustering_factor': 1.0, 'compression_ratio': 1.37, 'depth': 1,
10674                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 1,
10675                     'name': 'MAXSALX', 'nodes': 31, 'ratio': 0.03, 'root_page': 286, 'total_dup': 5},
10676                    {'avg_data_length': 10.29, 'avg_key_length': 13.03, 'avg_node_length': 14.06, 'avg_prefix_length': 8.48,
10677                     'clustering_factor': 1.0, 'compression_ratio': 1.44, 'depth': 1,
10678                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 2,
10679                     'name': 'MINSALX', 'nodes': 31, 'ratio': 0.03, 'root_page': 287, 'total_dup': 7},
10680                    {'avg_data_length': 1.39, 'avg_key_length': 3.39, 'avg_node_length': 4.61, 'avg_prefix_length': 2.77,
10681                     'clustering_factor': 1.0, 'compression_ratio': 1.23, 'depth': 1,
10682                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 20,
10683                     'name': 'RDB$FOREIGN3', 'nodes': 31, 'ratio': 0.03, 'root_page': 192, 'total_dup': 24},
10684                    {'avg_data_length': 10.45, 'avg_key_length': 13.42, 'avg_node_length': 14.45, 'avg_prefix_length': 6.19,
10685                     'clustering_factor': 1.0, 'compression_ratio': 1.24, 'depth': 1,
10686                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10687                     'name': 'RDB$PRIMARY2', 'nodes': 31, 'ratio': 0.03, 'root_page': 191, 'total_dup': 0},
10688                    {'avg_data_length': 22.5, 'avg_key_length': 25.33, 'avg_node_length': 26.5, 'avg_prefix_length': 4.17,
10689                     'clustering_factor': 1.0, 'compression_ratio': 1.05, 'depth': 1,
10690                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 0,
10691                     'name': 'PRODTYPEX', 'nodes': 6, 'ratio': 0.17, 'root_page': 288, 'total_dup': 0},
10692                    {'avg_data_length': 13.33, 'avg_key_length': 15.5, 'avg_node_length': 17.33, 'avg_prefix_length': 0.33,
10693                     'clustering_factor': 1.0, 'compression_ratio': 0.88, 'depth': 1,
10694                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10695                     'name': 'RDB$11', 'nodes': 6, 'ratio': 0.17, 'root_page': 222, 'total_dup': 0},
10696                    {'avg_data_length': 1.33, 'avg_key_length': 3.5, 'avg_node_length': 4.67, 'avg_prefix_length': 0.67,
10697                     'clustering_factor': 1.0, 'compression_ratio': 0.57, 'depth': 1,
10698                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 0,
10699                     'name': 'RDB$FOREIGN13', 'nodes': 6, 'ratio': 0.17, 'root_page': 232, 'total_dup': 0},
10700                    {'avg_data_length': 4.83, 'avg_key_length': 7.0, 'avg_node_length': 8.83, 'avg_prefix_length': 0.17,
10701                     'clustering_factor': 1.0, 'compression_ratio': 0.71, 'depth': 1,
10702                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 0,
10703                     'name': 'RDB$PRIMARY12', 'nodes': 6, 'ratio': 0.17, 'root_page': 223, 'total_dup': 0},
10704                    {'avg_data_length': 0.71, 'avg_key_length': 2.79, 'avg_node_length': 3.92, 'avg_prefix_length': 2.29,
10705                     'clustering_factor': 1.0, 'compression_ratio': 1.07, 'depth': 1,
10706                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 5,
10707                     'name': 'RDB$FOREIGN18', 'nodes': 24, 'ratio': 0.04, 'root_page': 250, 'total_dup': 15},
10708                    {'avg_data_length': 1.0, 'avg_key_length': 3.04, 'avg_node_length': 4.21, 'avg_prefix_length': 4.0,
10709                     'clustering_factor': 1.0, 'compression_ratio': 1.64, 'depth': 1,
10710                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 8,
10711                     'name': 'RDB$FOREIGN19', 'nodes': 24, 'ratio': 0.04, 'root_page': 251, 'total_dup': 19},
10712                    {'avg_data_length': 6.83, 'avg_key_length': 9.67, 'avg_node_length': 10.71, 'avg_prefix_length': 12.17,
10713                     'clustering_factor': 1.0, 'compression_ratio': 1.97, 'depth': 1,
10714                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10715                     'name': 'RDB$PRIMARY17', 'nodes': 24, 'ratio': 0.04, 'root_page': 249, 'total_dup': 0},
10716                    {'avg_data_length': 0.31, 'avg_key_length': 2.35, 'avg_node_length': 3.37, 'avg_prefix_length': 6.69,
10717                     'clustering_factor': 1.0, 'compression_ratio': 2.98, 'depth': 1,
10718                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 21,
10719                     'name': 'CHANGEX', 'nodes': 49, 'ratio': 0.02, 'root_page': 289, 'total_dup': 46},
10720                    {'avg_data_length': 0.9, 'avg_key_length': 3.1, 'avg_node_length': 4.12, 'avg_prefix_length': 1.43,
10721                     'clustering_factor': 1.0, 'compression_ratio': 0.75, 'depth': 1,
10722                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 2,
10723                     'name': 'RDB$FOREIGN21', 'nodes': 49, 'ratio': 0.02, 'root_page': 256, 'total_dup': 16},
10724                    {'avg_data_length': 18.29, 'avg_key_length': 21.27, 'avg_node_length': 22.29, 'avg_prefix_length': 4.31,
10725                     'clustering_factor': 1.0, 'compression_ratio': 1.06, 'depth': 1,
10726                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10727                     'name': 'RDB$PRIMARY20', 'nodes': 49, 'ratio': 0.02, 'root_page': 255, 'total_dup': 0},
10728                    {'avg_data_length': 0.29, 'avg_key_length': 2.29, 'avg_node_length': 3.35, 'avg_prefix_length': 5.39,
10729                     'clustering_factor': 1.0, 'compression_ratio': 2.48, 'depth': 1,
10730                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 28,
10731                     'name': 'UPDATERX', 'nodes': 49, 'ratio': 0.02, 'root_page': 290, 'total_dup': 46},
10732                    {'avg_data_length': 2.55, 'avg_key_length': 4.94, 'avg_node_length': 5.97, 'avg_prefix_length': 2.88,
10733                     'clustering_factor': 1.0, 'compression_ratio': 1.1, 'depth': 1,
10734                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 3, 'leaf_buckets': 1, 'max_dup': 6,
10735                     'name': 'NEEDX', 'nodes': 33, 'ratio': 0.03, 'root_page': 291, 'total_dup': 11},
10736                    {'avg_data_length': 1.85, 'avg_key_length': 4.03, 'avg_node_length': 5.06, 'avg_prefix_length': 11.18,
10737                     'clustering_factor': 1.0, 'compression_ratio': 3.23, 'depth': 1,
10738                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 4, 'leaf_buckets': 1, 'max_dup': 3,
10739                     'name': 'QTYX', 'nodes': 33, 'ratio': 0.03, 'root_page': 292, 'total_dup': 11},
10740                    {'avg_data_length': 0.52, 'avg_key_length': 2.52, 'avg_node_length': 3.55, 'avg_prefix_length': 2.48,
10741                     'clustering_factor': 1.0, 'compression_ratio': 1.19, 'depth': 1,
10742                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 1, 'leaf_buckets': 1, 'max_dup': 4,
10743                     'name': 'RDB$FOREIGN25', 'nodes': 33, 'ratio': 0.03, 'root_page': 270, 'total_dup': 18},
10744                    {'avg_data_length': 0.45, 'avg_key_length': 2.64, 'avg_node_length': 3.67, 'avg_prefix_length': 2.21,
10745                     'clustering_factor': 1.0, 'compression_ratio': 1.01, 'depth': 1,
10746                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 2, 'leaf_buckets': 1, 'max_dup': 7,
10747                     'name': 'RDB$FOREIGN26', 'nodes': 33, 'ratio': 0.03, 'root_page': 271, 'total_dup': 25},
10748                    {'avg_data_length': 4.48, 'avg_key_length': 7.42, 'avg_node_length': 8.45, 'avg_prefix_length': 3.52,
10749                     'clustering_factor': 1.0, 'compression_ratio': 1.08, 'depth': 1,
10750                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10751                     'name': 'RDB$PRIMARY24', 'nodes': 33, 'ratio': 0.03, 'root_page': 269, 'total_dup': 0},
10752                    {'avg_data_length': 0.97, 'avg_key_length': 3.03, 'avg_node_length': 4.06, 'avg_prefix_length': 9.82,
10753                     'clustering_factor': 1.0, 'compression_ratio': 3.56, 'depth': 1,
10754                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 5, 'leaf_buckets': 1, 'max_dup': 14,
10755                     'name': 'SALESTATX', 'nodes': 33, 'ratio': 0.03, 'root_page': 293, 'total_dup': 27},
10756                    {'avg_data_length': 0.0, 'avg_key_length': 0.0, 'avg_node_length': 0.0, 'avg_prefix_length': 0.0,
10757                     'clustering_factor': 0.0, 'compression_ratio': 0.0, 'depth': 1,
10758                     'distribution': FillDistribution(d20=1, d40=0, d50=0, d80=0, d100=0), 'index_id': 0, 'leaf_buckets': 1, 'max_dup': 0,
10759                     'name': 'RDB$PRIMARY28', 'nodes': 0, 'ratio': 0.0, 'root_page': 317, 'total_dup': 0}]
10760            i = 0
10761            while i < len(db.tables):
10762                self.assertDictEqual(data[i], get_object_data(db.indices[i], ['table']), 'Unexpected output from parser (indices)')
10763                i += 1
10764    def test_parse30_s(self):
10765        db = self._parse_file(os.path.join(self.dbpath, 'gstat30-s.out'))
10766        #
10767        self.assertTrue(db.has_table_stats())
10768        self.assertTrue(db.has_index_stats())
10769        self.assertFalse(db.has_row_stats())
10770        self.assertFalse(db.has_encryption_stats())
10771        self.assertTrue(db.has_system())
10772        # Check system tables
10773        data = ['RDB$AUTH_MAPPING', 'RDB$BACKUP_HISTORY', 'RDB$CHARACTER_SETS', 'RDB$CHECK_CONSTRAINTS', 'RDB$COLLATIONS', 'RDB$DATABASE',
10774                'RDB$DB_CREATORS', 'RDB$DEPENDENCIES', 'RDB$EXCEPTIONS', 'RDB$FIELDS', 'RDB$FIELD_DIMENSIONS', 'RDB$FILES', 'RDB$FILTERS',
10775                'RDB$FORMATS', 'RDB$FUNCTIONS', 'RDB$FUNCTION_ARGUMENTS', 'RDB$GENERATORS', 'RDB$INDEX_SEGMENTS', 'RDB$INDICES',
10776                'RDB$LOG_FILES', 'RDB$PACKAGES', 'RDB$PAGES', 'RDB$PROCEDURES', 'RDB$PROCEDURE_PARAMETERS', 'RDB$REF_CONSTRAINTS',
10777                'RDB$RELATIONS', 'RDB$RELATION_CONSTRAINTS', 'RDB$RELATION_FIELDS', 'RDB$ROLES', 'RDB$SECURITY_CLASSES', 'RDB$TRANSACTIONS',
10778                'RDB$TRIGGERS', 'RDB$TRIGGER_MESSAGES', 'RDB$TYPES', 'RDB$USER_PRIVILEGES', 'RDB$VIEW_RELATIONS']
10779        for table in db.tables:
10780            if table.name.startswith('RDB$'):
10781                self.assertIn(table.name, data)
10782        # check system indices
10783        data = ['RDB$PRIMARY1', 'RDB$FOREIGN23', 'RDB$PRIMARY22', 'RDB$4', 'RDB$FOREIGN10', 'RDB$FOREIGN6', 'RDB$PRIMARY5', 'RDB$FOREIGN8',
10784                'RDB$FOREIGN9', 'RDB$PRIMARY7', 'RDB$FOREIGN15', 'RDB$FOREIGN16', 'RDB$PRIMARY14', 'RDB$FOREIGN3', 'RDB$PRIMARY2', 'RDB$11',
10785                'RDB$FOREIGN13', 'RDB$PRIMARY12', 'RDB$FOREIGN18', 'RDB$FOREIGN19', 'RDB$PRIMARY17', 'RDB$INDEX_52', 'RDB$INDEX_44',
10786                'RDB$INDEX_19', 'RDB$INDEX_25', 'RDB$INDEX_14', 'RDB$INDEX_40', 'RDB$INDEX_20', 'RDB$INDEX_26', 'RDB$INDEX_27',
10787                'RDB$INDEX_28', 'RDB$INDEX_23', 'RDB$INDEX_24', 'RDB$INDEX_2', 'RDB$INDEX_36', 'RDB$INDEX_17', 'RDB$INDEX_45',
10788                'RDB$INDEX_16', 'RDB$INDEX_53', 'RDB$INDEX_9', 'RDB$INDEX_10', 'RDB$INDEX_49', 'RDB$INDEX_51', 'RDB$INDEX_11',
10789                'RDB$INDEX_46', 'RDB$INDEX_6', 'RDB$INDEX_31', 'RDB$INDEX_41', 'RDB$INDEX_5', 'RDB$INDEX_47', 'RDB$INDEX_21', 'RDB$INDEX_22',
10790                'RDB$INDEX_18', 'RDB$INDEX_48', 'RDB$INDEX_50', 'RDB$INDEX_13', 'RDB$INDEX_0', 'RDB$INDEX_1', 'RDB$INDEX_12', 'RDB$INDEX_42',
10791                'RDB$INDEX_43', 'RDB$INDEX_15', 'RDB$INDEX_3', 'RDB$INDEX_4', 'RDB$INDEX_39', 'RDB$INDEX_7', 'RDB$INDEX_32', 'RDB$INDEX_38',
10792                'RDB$INDEX_8', 'RDB$INDEX_35', 'RDB$INDEX_37', 'RDB$INDEX_29', 'RDB$INDEX_30', 'RDB$INDEX_33', 'RDB$INDEX_34',
10793                'RDB$FOREIGN21', 'RDB$PRIMARY20', 'RDB$FOREIGN25', 'RDB$FOREIGN26', 'RDB$PRIMARY24', 'RDB$PRIMARY28']
10794        for index in db.indices:
10795            if index.name.startswith('RDB$'):
10796                self.assertIn(index.name, data)
10797
10798class TestLogParse(FDBTestBase):
10799    def setUp(self):
10800        super(TestLogParse, self).setUp()
10801    def _check_events(self, trace_lines, output):
10802        for obj in log.parse(linesplit_iter(trace_lines)):
10803            self.printout(str(obj))
10804        self.assertEqual(self.output.getvalue(), output, "Parsed events do not match expected ones")
10805    def test_locale(self):
10806        data = """
10807
10808SRVDB1  Tue Apr 04 21:25:40 2017
10809        INET/inet_error: read errno = 10054
10810
10811"""
10812        output = """LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 25, 40), message='INET/inet_error: read errno = 10054')
10813"""
10814        locale = getlocale(LC_ALL)
10815        if locale[0] is None:
10816            setlocale(LC_ALL,'')
10817            locale = getlocale(LC_ALL)
10818        try:
10819            self._check_events(data, output)
10820            self.assertEquals(locale, getlocale(LC_ALL), "Locale must not change")
10821            self.clear_output()
10822            if sys.platform == 'win32':
10823                setlocale(LC_ALL, 'Czech_Czech Republic')
10824            else:
10825                setlocale(LC_ALL, 'cs_CZ')
10826            nlocale = getlocale(LC_ALL)
10827            self._check_events(data, output)
10828            self.assertEquals(nlocale, getlocale(LC_ALL), "Locale must not change")
10829        finally:
10830            setlocale(LC_ALL, locale)
10831    def TestWindowsService(self):
10832        data = """
10833
10834SRVDB1  Tue Apr 04 21:25:40 2017
10835        INET/inet_error: read errno = 10054
10836
10837
10838SRVDB1  Tue Apr 04 21:25:41 2017
10839        Unable to complete network request to host "SRVDB1".
10840        Error reading data from the connection.
10841
10842
10843SRVDB1  Tue Apr 04 21:25:42 2017
10844        INET/inet_error: read errno = 10054
10845
10846
10847SRVDB1  Tue Apr 04 21:25:43 2017
10848        Unable to complete network request to host "SRVDB1".
10849        Error reading data from the connection.
10850
10851
10852SRVDB1  Tue Apr 04 21:28:48 2017
10853        INET/inet_error: read errno = 10054
10854
10855
10856SRVDB1  Tue Apr 04 21:28:50 2017
10857        Unable to complete network request to host "SRVDB1".
10858        Error reading data from the connection.
10859
10860
10861SRVDB1  Tue Apr 04 21:28:51 2017
10862        Sweep is started by SYSDBA
10863        Database "Mydatabase"
10864        OIT 551120654, OAT 551120655, OST 551120655, Next 551121770
10865
10866
10867SRVDB1  Tue Apr 04 21:28:52 2017
10868        INET/inet_error: read errno = 10054
10869
10870
10871SRVDB1  Tue Apr 04 21:28:53 2017
10872        Unable to complete network request to host "SRVDB1".
10873        Error reading data from the connection.
10874
10875
10876SRVDB1  Tue Apr 04 21:28:54 2017
10877        Sweep is finished
10878        Database "Mydatabase"
10879        OIT 551234848, OAT 551234849, OST 551234849, Next 551235006
10880
10881
10882SRVDB1  Tue Apr 04 21:28:55 2017
10883        Sweep is started by SWEEPER
10884        Database "Mydatabase"
10885        OIT 551243753, OAT 551846279, OST 551846279, Next 551846385
10886
10887
10888SRVDB1  Tue Apr 04 21:28:56 2017
10889        INET/inet_error: read errno = 10054
10890
10891
10892SRVDB1  Tue Apr 04 21:28:57 2017
10893        Sweep is finished
10894        Database "Mydatabase"
10895        OIT 551846278, OAT 551976724, OST 551976724, Next 551976730
10896
10897
10898SRVDB1  Tue Apr 04 21:28:58 2017
10899        Unable to complete network request to host "(unknown)".
10900        Error reading data from the connection.
10901
10902
10903SRVDB1  Thu Apr 06 12:52:56 2017
10904        Shutting down the server with 1 active connection(s) to 1 database(s), 0 active service(s)
10905
10906
10907"""
10908        output = """LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 25, 40), message='INET/inet_error: read errno = 10054')
10909LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 25, 41), message='Unable to complete network request to host "SRVDB1".\\nError reading data from the connection.')
10910LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 25, 42), message='INET/inet_error: read errno = 10054')
10911LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 25, 43), message='Unable to complete network request to host "SRVDB1".\\nError reading data from the connection.')
10912LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 48), message='INET/inet_error: read errno = 10054')
10913LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 50), message='Unable to complete network request to host "SRVDB1".\\nError reading data from the connection.')
10914LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 51), message='Sweep is started by SYSDBA\\nDatabase "Mydatabase"\\nOIT 551120654, OAT 551120655, OST 551120655, Next 551121770')
10915LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 52), message='INET/inet_error: read errno = 10054')
10916LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 53), message='Unable to complete network request to host "SRVDB1".\\nError reading data from the connection.')
10917LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 54), message='Sweep is finished\\nDatabase "Mydatabase"\\nOIT 551234848, OAT 551234849, OST 551234849, Next 551235006')
10918LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 55), message='Sweep is started by SWEEPER\\nDatabase "Mydatabase"\\nOIT 551243753, OAT 551846279, OST 551846279, Next 551846385')
10919LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 56), message='INET/inet_error: read errno = 10054')
10920LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 57), message='Sweep is finished\\nDatabase "Mydatabase"\\nOIT 551846278, OAT 551976724, OST 551976724, Next 551976730')
10921LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 4, 21, 28, 58), message='Unable to complete network request to host "(unknown)".\\nError reading data from the connection.')
10922LogEntry(source_id='SRVDB1', timestamp=datetime.datetime(2017, 4, 6, 12, 52, 56), message='Shutting down the server with 1 active connection(s) to 1 database(s), 0 active service(s)')
10923"""
10924        self._check_events(data, output)
10925    def TestLinuxDirect(self):
10926        data = """
10927MyServer (Client)	Fri Apr  6 16:35:46 2018
10928	INET/inet_error: connect errno = 111
10929
10930
10931MyServer (Client)	Fri Apr  6 16:51:31 2018
10932	/opt/firebird/bin/fbguard: guardian starting /opt/firebird/bin/fbserver
10933
10934
10935
10936MyServer (Server)	Fri Apr  6 16:55:23 2018
10937	activating shadow file /home/db/test_employee.fdb
10938
10939
10940MyServer (Server)	Fri Apr  6 16:55:31 2018
10941	Sweep is started by SYSDBA
10942	Database "/home/db/test_employee.fdb"
10943	OIT 1, OAT 0, OST 0, Next 1
10944
10945
10946MyServer (Server)	Fri Apr  6 16:55:31 2018
10947	Sweep is finished
10948	Database "/home/db/test_employee.fdb"
10949	OIT 1, OAT 0, OST 0, Next 2
10950
10951
10952MyServer (Client)	Fri Apr  6 20:18:52 2018
10953	/opt/firebird/bin/fbguard: /opt/firebird/bin/fbserver normal shutdown.
10954
10955
10956
10957MyServer (Client)	Mon Apr  9 08:28:29 2018
10958	/opt/firebird/bin/fbguard: guardian starting /opt/firebird/bin/fbserver
10959
10960
10961
10962MyServer (Server)	Tue Apr 17 15:01:27 2018
10963	INET/inet_error: invalid socket in packet_receive errno = 22
10964
10965
10966MyServer (Client)	Tue Apr 17 19:42:55 2018
10967	/opt/firebird/bin/fbguard: /opt/firebird/bin/fbserver normal shutdown.
10968
10969
10970
10971"""
10972        output = """LogEntry(source_id='MyServer (Client)', timestamp=datetime.datetime(2018, 4, 6, 16, 35, 46), message='INET/inet_error: connect errno = 111')
10973LogEntry(source_id='MyServer (Client)', timestamp=datetime.datetime(2018, 4, 6, 16, 51, 31), message='/opt/firebird/bin/fbguard: guardian starting /opt/firebird/bin/fbserver')
10974LogEntry(source_id='MyServer (Server)', timestamp=datetime.datetime(2018, 4, 6, 16, 55, 23), message='activating shadow file /home/db/test_employee.fdb')
10975LogEntry(source_id='MyServer (Server)', timestamp=datetime.datetime(2018, 4, 6, 16, 55, 31), message='Sweep is started by SYSDBA\\nDatabase "/home/db/test_employee.fdb"\\nOIT 1, OAT 0, OST 0, Next 1')
10976LogEntry(source_id='MyServer (Server)', timestamp=datetime.datetime(2018, 4, 6, 16, 55, 31), message='Sweep is finished\\nDatabase "/home/db/test_employee.fdb"\\nOIT 1, OAT 0, OST 0, Next 2')
10977LogEntry(source_id='MyServer (Client)', timestamp=datetime.datetime(2018, 4, 6, 20, 18, 52), message='/opt/firebird/bin/fbguard: /opt/firebird/bin/fbserver normal shutdown.')
10978LogEntry(source_id='MyServer (Client)', timestamp=datetime.datetime(2018, 4, 9, 8, 28, 29), message='/opt/firebird/bin/fbguard: guardian starting /opt/firebird/bin/fbserver')
10979LogEntry(source_id='MyServer (Server)', timestamp=datetime.datetime(2018, 4, 17, 15, 1, 27), message='INET/inet_error: invalid socket in packet_receive errno = 22')
10980LogEntry(source_id='MyServer (Client)', timestamp=datetime.datetime(2018, 4, 17, 19, 42, 55), message='/opt/firebird/bin/fbguard: /opt/firebird/bin/fbserver normal shutdown.')
10981"""
10982        self._check_events(data, output)
10983
10984if __name__ == '__main__':
10985    unittest.main()
10986
10987