History

perf scripts python: exported-sql-viewer.py: Add ability to display all the database tables

Displaying all the database tables can help make the database easier to understand. Committer testing: Opened all the tables, even the sqlite master table, which I selected everything and used control+C, lets see if it works... CREATE VIEW threads_view AS SELECT id,machine_id,(SELECT host_or_guest FROM machines_view WHERE id = machine_id) AS host_or_guest,process_id,pid,tid FROM threads Humm, nope, just one of the cells got copied, even with everything selected :-) Anyway, works as advertised, useful for perusing the data.

Adrian Hunter committed 8 years ago

Commit 8392b741 parent 82f68e21 file · +694 −0Browse files

Showing the changes to tools/perf/scripts/python (1 of 1 file). Show all

Unified · Split

tools/perf/scripts/python/exported-sql-viewer.py+694−0
@@ -50,10 +50,15 @@ import sys · 49 unmodified lines
5050 import weakref
5151 import threading
5252 import string
53+import cPickle
54+import re
55+import os
5356 from PySide.QtCore import *
5457 from PySide.QtGui import *
5558 from PySide.QtSql import *
5659 from decimal import *
60+from ctypes import *
61+from multiprocessing import Process, Array, Value, Event
5762
5863 # Data formatting helpers
5964
@@ -146,6 +151,68 @@ class TreeModel(QAbstractItemModel): · 86 unmodified lines
146151 def DisplayData(self, item, index):
147152 return item.getData(index.column())
148153
154+ def FetchIfNeeded(self, row):
155+ if row > self.last_row_read:
156+ self.last_row_read = row
157+ if row + 10 >= self.root.child_count:
158+ self.fetcher.Fetch(glb_chunk_sz)
159+
160+ def columnAlignment(self, column):
161+ return Qt.AlignLeft
162+
163+ def columnFont(self, column):
164+ return None
165+
166+ def data(self, index, role):
167+ if role == Qt.TextAlignmentRole:
168+ return self.columnAlignment(index.column())
169+ if role == Qt.FontRole:
170+ return self.columnFont(index.column())
171+ if role != Qt.DisplayRole:
172+ return None
173+ item = index.internalPointer()
174+ return self.DisplayData(item, index)
175+
176+# Table data model
177+
178+class TableModel(QAbstractTableModel):
179+
180+ def __init__(self, parent=None):
181+ super(TableModel, self).__init__(parent)
182+ self.child_count = 0
183+ self.child_items = []
184+ self.last_row_read = 0
185+
186+ def Item(self, parent):
187+ if parent.isValid():
188+ return parent.internalPointer()
189+ else:
190+ return self
191+
192+ def rowCount(self, parent):
193+ return self.child_count
194+
195+ def headerData(self, section, orientation, role):
196+ if role == Qt.TextAlignmentRole:
197+ return self.columnAlignment(section)
198+ if role != Qt.DisplayRole:
199+ return None
200+ if orientation != Qt.Horizontal:
201+ return None
202+ return self.columnHeader(section)
203+
204+ def index(self, row, column, parent):
205+ return self.createIndex(row, column, self.child_items[row])
206+
207+ def DisplayData(self, item, index):
208+ return item.getData(index.column())
209+
210+ def FetchIfNeeded(self, row):
211+ if row > self.last_row_read:
212+ self.last_row_read = row
213+ if row + 10 >= self.child_count:
214+ self.fetcher.Fetch(glb_chunk_sz)
215+
149216 def columnAlignment(self, column):
150217 return Qt.AlignLeft
151218
@@ -620,6 +687,601 @@ class CallGraphWindow(QMdiSubWindow): · 468 unmodified lines
620687 if not found:
621688 self.find_bar.NotFound()
622689
690+# Child data item finder
691+
692+class ChildDataItemFinder():
693+
694+ def __init__(self, root):
695+ self.root = root
696+ self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (None,) * 5
697+ self.rows = []
698+ self.pos = 0
699+
700+ def FindSelect(self):
701+ self.rows = []
702+ if self.pattern:
703+ pattern = re.compile(self.value)
704+ for child in self.root.child_items:
705+ for column_data in child.data:
706+ if re.search(pattern, str(column_data)) is not None:
707+ self.rows.append(child.row)
708+ break
709+ else:
710+ for child in self.root.child_items:
711+ for column_data in child.data:
712+ if self.value in str(column_data):
713+ self.rows.append(child.row)
714+ break
715+
716+ def FindValue(self):
717+ self.pos = 0
718+ if self.last_value != self.value or self.pattern != self.last_pattern:
719+ self.FindSelect()
720+ if not len(self.rows):
721+ return -1
722+ return self.rows[self.pos]
723+
724+ def FindThread(self):
725+ if self.direction == 0 or self.value != self.last_value or self.pattern != self.last_pattern:
726+ row = self.FindValue()
727+ elif len(self.rows):
728+ if self.direction > 0:
729+ self.pos += 1
730+ if self.pos >= len(self.rows):
731+ self.pos = 0
732+ else:
733+ self.pos -= 1
734+ if self.pos < 0:
735+ self.pos = len(self.rows) - 1
736+ row = self.rows[self.pos]
737+ else:
738+ row = -1
739+ return (True, row)
740+
741+ def Find(self, value, direction, pattern, context, callback):
742+ self.value, self.direction, self.pattern, self.last_value, self.last_pattern = (value, direction,pattern, self.value, self.pattern)
743+ # Use a thread so the UI is not blocked
744+ thread = Thread(self.FindThread)
745+ thread.done.connect(lambda row, t=thread, c=callback: self.FindDone(t, c, row), Qt.QueuedConnection)
746+ thread.start()
747+
748+ def FindDone(self, thread, callback, row):
749+ callback(row)
750+
751+# Number of database records to fetch in one go
752+
753+glb_chunk_sz = 10000
754+
755+# size of pickled integer big enough for record size
756+
757+glb_nsz = 8
758+
759+# Background process for SQL data fetcher
760+
761+class SQLFetcherProcess():
762+
763+ def __init__(self, dbref, sql, buffer, head, tail, fetch_count, fetching_done, process_target, wait_event, fetched_event, prep):
764+ # Need a unique connection name
765+ conn_name = "SQLFetcher" + str(os.getpid())
766+ self.db, dbname = dbref.Open(conn_name)
767+ self.sql = sql
768+ self.buffer = buffer
769+ self.head = head
770+ self.tail = tail
771+ self.fetch_count = fetch_count
772+ self.fetching_done = fetching_done
773+ self.process_target = process_target
774+ self.wait_event = wait_event
775+ self.fetched_event = fetched_event
776+ self.prep = prep
777+ self.query = QSqlQuery(self.db)
778+ self.query_limit = 0 if "$$last_id$$" in sql else 2
779+ self.last_id = -1
780+ self.fetched = 0
781+ self.more = True
782+ self.local_head = self.head.value
783+ self.local_tail = self.tail.value
784+
785+ def Select(self):
786+ if self.query_limit:
787+ if self.query_limit == 1:
788+ return
789+ self.query_limit -= 1
790+ stmt = self.sql.replace("$$last_id$$", str(self.last_id))
791+ QueryExec(self.query, stmt)
792+
793+ def Next(self):
794+ if not self.query.next():
795+ self.Select()
796+ if not self.query.next():
797+ return None
798+ self.last_id = self.query.value(0)
799+ return self.prep(self.query)
800+
801+ def WaitForTarget(self):
802+ while True:
803+ self.wait_event.clear()
804+ target = self.process_target.value
805+ if target > self.fetched or target < 0:
806+ break
807+ self.wait_event.wait()
808+ return target
809+
810+ def HasSpace(self, sz):
811+ if self.local_tail <= self.local_head:
812+ space = len(self.buffer) - self.local_head
813+ if space > sz:
814+ return True
815+ if space >= glb_nsz:
816+ # Use 0 (or space < glb_nsz) to mean there is no more at the top of the buffer
817+ nd = cPickle.dumps(0, cPickle.HIGHEST_PROTOCOL)
818+ self.buffer[self.local_head : self.local_head + len(nd)] = nd
819+ self.local_head = 0
820+ if self.local_tail - self.local_head > sz:
821+ return True
822+ return False
823+
824+ def WaitForSpace(self, sz):
825+ if self.HasSpace(sz):
826+ return
827+ while True:
828+ self.wait_event.clear()
829+ self.local_tail = self.tail.value
830+ if self.HasSpace(sz):
831+ return
832+ self.wait_event.wait()
833+
834+ def AddToBuffer(self, obj):
835+ d = cPickle.dumps(obj, cPickle.HIGHEST_PROTOCOL)
836+ n = len(d)
837+ nd = cPickle.dumps(n, cPickle.HIGHEST_PROTOCOL)
838+ sz = n + glb_nsz
839+ self.WaitForSpace(sz)
840+ pos = self.local_head
841+ self.buffer[pos : pos + len(nd)] = nd
842+ self.buffer[pos + glb_nsz : pos + sz] = d
843+ self.local_head += sz
844+
845+ def FetchBatch(self, batch_size):
846+ fetched = 0
847+ while batch_size > fetched:
848+ obj = self.Next()
849+ if obj is None:
850+ self.more = False
851+ break
852+ self.AddToBuffer(obj)
853+ fetched += 1
854+ if fetched:
855+ self.fetched += fetched
856+ with self.fetch_count.get_lock():
857+ self.fetch_count.value += fetched
858+ self.head.value = self.local_head
859+ self.fetched_event.set()
860+
861+ def Run(self):
862+ while self.more:
863+ target = self.WaitForTarget()
864+ if target < 0:
865+ break
866+ batch_size = min(glb_chunk_sz, target - self.fetched)
867+ self.FetchBatch(batch_size)
868+ self.fetching_done.value = True
869+ self.fetched_event.set()
870+
871+def SQLFetcherFn(*x):
872+ process = SQLFetcherProcess(*x)
873+ process.Run()
874+
875+# SQL data fetcher
876+
877+class SQLFetcher(QObject):
878+
879+ done = Signal(object)
880+
881+ def __init__(self, glb, sql, prep, process_data, parent=None):
882+ super(SQLFetcher, self).__init__(parent)
883+ self.process_data = process_data
884+ self.more = True
885+ self.target = 0
886+ self.last_target = 0
887+ self.fetched = 0
888+ self.buffer_size = 16 * 1024 * 1024
889+ self.buffer = Array(c_char, self.buffer_size, lock=False)
890+ self.head = Value(c_longlong)
891+ self.tail = Value(c_longlong)
892+ self.local_tail = 0
893+ self.fetch_count = Value(c_longlong)
894+ self.fetching_done = Value(c_bool)
895+ self.last_count = 0
896+ self.process_target = Value(c_longlong)
897+ self.wait_event = Event()
898+ self.fetched_event = Event()
899+ glb.AddInstanceToShutdownOnExit(self)
900+ self.process = Process(target=SQLFetcherFn, args=(glb.dbref, sql, self.buffer, self.head, self.tail, self.fetch_count, self.fetching_done, self.process_target, self.wait_event, self.fetched_event, prep))
901+ self.process.start()
902+ self.thread = Thread(self.Thread)
903+ self.thread.done.connect(self.ProcessData, Qt.QueuedConnection)
904+ self.thread.start()
905+
906+ def Shutdown(self):
907+ # Tell the thread and process to exit
908+ self.process_target.value = -1
909+ self.wait_event.set()
910+ self.more = False
911+ self.fetching_done.value = True
912+ self.fetched_event.set()
913+
914+ def Thread(self):
915+ if not self.more:
916+ return True, 0
917+ while True:
918+ self.fetched_event.clear()
919+ fetch_count = self.fetch_count.value
920+ if fetch_count != self.last_count:
921+ break
922+ if self.fetching_done.value:
923+ self.more = False
924+ return True, 0
925+ self.fetched_event.wait()
926+ count = fetch_count - self.last_count
927+ self.last_count = fetch_count
928+ self.fetched += count
929+ return False, count
930+
931+ def Fetch(self, nr):
932+ if not self.more:
933+ # -1 inidcates there are no more
934+ return -1
935+ result = self.fetched
936+ extra = result + nr - self.target
937+ if extra > 0:
938+ self.target += extra
939+ # process_target < 0 indicates shutting down
940+ if self.process_target.value >= 0:
941+ self.process_target.value = self.target
942+ self.wait_event.set()
943+ return result
944+
945+ def RemoveFromBuffer(self):
946+ pos = self.local_tail
947+ if len(self.buffer) - pos < glb_nsz:
948+ pos = 0
949+ n = cPickle.loads(self.buffer[pos : pos + glb_nsz])
950+ if n == 0:
951+ pos = 0
952+ n = cPickle.loads(self.buffer[0 : glb_nsz])
953+ pos += glb_nsz
954+ obj = cPickle.loads(self.buffer[pos : pos + n])
955+ self.local_tail = pos + n
956+ return obj
957+
958+ def ProcessData(self, count):
959+ for i in xrange(count):
960+ obj = self.RemoveFromBuffer()
961+ self.process_data(obj)
962+ self.tail.value = self.local_tail
963+ self.wait_event.set()
964+ self.done.emit(count)
965+
966+# Fetch more records bar
967+
968+class FetchMoreRecordsBar():
969+
970+ def __init__(self, model, parent):
971+ self.model = model
972+
973+ self.label = QLabel("Number of records (x " + "{:,}".format(glb_chunk_sz) + ") to fetch:")
974+ self.label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
975+
976+ self.fetch_count = QSpinBox()
977+ self.fetch_count.setRange(1, 1000000)
978+ self.fetch_count.setValue(10)
979+ self.fetch_count.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
980+
981+ self.fetch = QPushButton("Go!")
982+ self.fetch.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
983+ self.fetch.released.connect(self.FetchMoreRecords)
984+
985+ self.progress = QProgressBar()
986+ self.progress.setRange(0, 100)
987+ self.progress.hide()
988+
989+ self.done_label = QLabel("All records fetched")
990+ self.done_label.hide()
991+
992+ self.spacer = QLabel("")
993+
994+ self.close_button = QToolButton()
995+ self.close_button.setIcon(parent.style().standardIcon(QStyle.SP_DockWidgetCloseButton))
996+ self.close_button.released.connect(self.Deactivate)
997+
998+ self.hbox = QHBoxLayout()
999+ self.hbox.setContentsMargins(0, 0, 0, 0)
1000+
1001+ self.hbox.addWidget(self.label)
1002+ self.hbox.addWidget(self.fetch_count)
1003+ self.hbox.addWidget(self.fetch)
1004+ self.hbox.addWidget(self.spacer)
1005+ self.hbox.addWidget(self.progress)
1006+ self.hbox.addWidget(self.done_label)
1007+ self.hbox.addWidget(self.close_button)
1008+
1009+ self.bar = QWidget()
1010+ self.bar.setLayout(self.hbox);
1011+ self.bar.show()
1012+
1013+ self.in_progress = False
1014+ self.model.progress.connect(self.Progress)
1015+
1016+ self.done = False
1017+
1018+ if not model.HasMoreRecords():
1019+ self.Done()
1020+
1021+ def Widget(self):
1022+ return self.bar
1023+
1024+ def Activate(self):
1025+ self.bar.show()
1026+ self.fetch.setFocus()
1027+
1028+ def Deactivate(self):
1029+ self.bar.hide()
1030+
1031+ def Enable(self, enable):
1032+ self.fetch.setEnabled(enable)
1033+ self.fetch_count.setEnabled(enable)
1034+
1035+ def Busy(self):
1036+ self.Enable(False)
1037+ self.fetch.hide()
1038+ self.spacer.hide()
1039+ self.progress.show()
1040+
1041+ def Idle(self):
1042+ self.in_progress = False
1043+ self.Enable(True)
1044+ self.progress.hide()
1045+ self.fetch.show()
1046+ self.spacer.show()
1047+
1048+ def Target(self):
1049+ return self.fetch_count.value() * glb_chunk_sz
1050+
1051+ def Done(self):
1052+ self.done = True
1053+ self.Idle()
1054+ self.label.hide()
1055+ self.fetch_count.hide()
1056+ self.fetch.hide()
1057+ self.spacer.hide()
1058+ self.done_label.show()
1059+
1060+ def Progress(self, count):
1061+ if self.in_progress:
1062+ if count:
1063+ percent = ((count - self.start) * 100) / self.Target()
1064+ if percent >= 100:
1065+ self.Idle()
1066+ else:
1067+ self.progress.setValue(percent)
1068+ if not count:
1069+ # Count value of zero means no more records
1070+ self.Done()
1071+
1072+ def FetchMoreRecords(self):
1073+ if self.done:
1074+ return
1075+ self.progress.setValue(0)
1076+ self.Busy()
1077+ self.in_progress = True
1078+ self.start = self.model.FetchMoreRecords(self.Target())
1079+
1080+# SQL data preparation
1081+
1082+def SQLTableDataPrep(query, count):
1083+ data = []
1084+ for i in xrange(count):
1085+ data.append(query.value(i))
1086+ return data
1087+
1088+# SQL table data model item
1089+
1090+class SQLTableItem():
1091+
1092+ def __init__(self, row, data):
1093+ self.row = row
1094+ self.data = data
1095+
1096+ def getData(self, column):
1097+ return self.data[column]
1098+
1099+# SQL table data model
1100+
1101+class SQLTableModel(TableModel):
1102+
1103+ progress = Signal(object)
1104+
1105+ def __init__(self, glb, sql, column_count, parent=None):
1106+ super(SQLTableModel, self).__init__(parent)
1107+ self.glb = glb
1108+ self.more = True
1109+ self.populated = 0
1110+ self.fetcher = SQLFetcher(glb, sql, lambda x, y=column_count: SQLTableDataPrep(x, y), self.AddSample)
1111+ self.fetcher.done.connect(self.Update)
1112+ self.fetcher.Fetch(glb_chunk_sz)
1113+
1114+ def DisplayData(self, item, index):
1115+ self.FetchIfNeeded(item.row)
1116+ return item.getData(index.column())
1117+
1118+ def AddSample(self, data):
1119+ child = SQLTableItem(self.populated, data)
1120+ self.child_items.append(child)
1121+ self.populated += 1
1122+
1123+ def Update(self, fetched):
1124+ if not fetched:
1125+ self.more = False
1126+ self.progress.emit(0)
1127+ child_count = self.child_count
1128+ count = self.populated - child_count
1129+ if count > 0:
1130+ parent = QModelIndex()
1131+ self.beginInsertRows(parent, child_count, child_count + count - 1)
1132+ self.insertRows(child_count, count, parent)
1133+ self.child_count += count
1134+ self.endInsertRows()
1135+ self.progress.emit(self.child_count)
1136+
1137+ def FetchMoreRecords(self, count):
1138+ current = self.child_count
1139+ if self.more:
1140+ self.fetcher.Fetch(count)
1141+ else:
1142+ self.progress.emit(0)
1143+ return current
1144+
1145+ def HasMoreRecords(self):
1146+ return self.more
1147+
1148+# SQL automatic table data model
1149+
1150+class SQLAutoTableModel(SQLTableModel):
1151+
1152+ def __init__(self, glb, table_name, parent=None):
1153+ sql = "SELECT * FROM " + table_name + " WHERE id > $$last_id$$ ORDER BY id LIMIT " + str(glb_chunk_sz)
1154+ if table_name == "comm_threads_view":
1155+ # For now, comm_threads_view has no id column
1156+ sql = "SELECT * FROM " + table_name + " WHERE comm_id > $$last_id$$ ORDER BY comm_id LIMIT " + str(glb_chunk_sz)
1157+ self.column_headers = []
1158+ query = QSqlQuery(glb.db)
1159+ if glb.dbref.is_sqlite3:
1160+ QueryExec(query, "PRAGMA table_info(" + table_name + ")")
1161+ while query.next():
1162+ self.column_headers.append(query.value(1))
1163+ if table_name == "sqlite_master":
1164+ sql = "SELECT * FROM " + table_name
1165+ else:
1166+ if table_name[:19] == "information_schema.":
1167+ sql = "SELECT * FROM " + table_name
1168+ select_table_name = table_name[19:]
1169+ schema = "information_schema"
1170+ else:
1171+ select_table_name = table_name
1172+ schema = "public"
1173+ QueryExec(query, "SELECT column_name FROM information_schema.columns WHERE table_schema = '" + schema + "' and table_name = '" + select_table_name + "'")
1174+ while query.next():
1175+ self.column_headers.append(query.value(0))
1176+ super(SQLAutoTableModel, self).__init__(glb, sql, len(self.column_headers), parent)
1177+
1178+ def columnCount(self, parent=None):
1179+ return len(self.column_headers)
1180+
1181+ def columnHeader(self, column):
1182+ return self.column_headers[column]
1183+
1184+# Base class for custom ResizeColumnsToContents
1185+
1186+class ResizeColumnsToContentsBase(QObject):
1187+
1188+ def __init__(self, parent=None):
1189+ super(ResizeColumnsToContentsBase, self).__init__(parent)
1190+
1191+ def ResizeColumnToContents(self, column, n):
1192+ # Using the view's resizeColumnToContents() here is extrememly slow
1193+ # so implement a crude alternative
1194+ font = self.view.font()
1195+ metrics = QFontMetrics(font)
1196+ max = 0
1197+ for row in xrange(n):
1198+ val = self.data_model.child_items[row].data[column]
1199+ len = metrics.width(str(val) + "MM")
1200+ max = len if len > max else max
1201+ val = self.data_model.columnHeader(column)
1202+ len = metrics.width(str(val) + "MM")
1203+ max = len if len > max else max
1204+ self.view.setColumnWidth(column, max)
1205+
1206+ def ResizeColumnsToContents(self):
1207+ n = min(self.data_model.child_count, 100)
1208+ if n < 1:
1209+ # No data yet, so connect a signal to notify when there is
1210+ self.data_model.rowsInserted.connect(self.UpdateColumnWidths)
1211+ return
1212+ columns = self.data_model.columnCount()
1213+ for i in xrange(columns):
1214+ self.ResizeColumnToContents(i, n)
1215+
1216+ def UpdateColumnWidths(self, *x):
1217+ # This only needs to be done once, so disconnect the signal now
1218+ self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
1219+ self.ResizeColumnsToContents()
1220+
1221+# Table window
1222+
1223+class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
1224+
1225+ def __init__(self, glb, table_name, parent=None):
1226+ super(TableWindow, self).__init__(parent)
1227+
1228+ self.data_model = LookupCreateModel(table_name + " Table", lambda: SQLAutoTableModel(glb, table_name))
1229+
1230+ self.model = QSortFilterProxyModel()
1231+ self.model.setSourceModel(self.data_model)
1232+
1233+ self.view = QTableView()
1234+ self.view.setModel(self.model)
1235+ self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
1236+ self.view.verticalHeader().setVisible(False)
1237+ self.view.sortByColumn(-1, Qt.AscendingOrder)
1238+ self.view.setSortingEnabled(True)
1239+
1240+ self.ResizeColumnsToContents()
1241+
1242+ self.find_bar = FindBar(self, self, True)
1243+
1244+ self.finder = ChildDataItemFinder(self.data_model)
1245+
1246+ self.fetch_bar = FetchMoreRecordsBar(self.data_model, self)
1247+
1248+ self.vbox = VBox(self.view, self.find_bar.Widget(), self.fetch_bar.Widget())
1249+
1250+ self.setWidget(self.vbox.Widget())
1251+
1252+ AddSubWindow(glb.mainwindow.mdi_area, self, table_name + " Table")
1253+
1254+ def Find(self, value, direction, pattern, context):
1255+ self.view.setFocus()
1256+ self.find_bar.Busy()
1257+ self.finder.Find(value, direction, pattern, context, self.FindDone)
1258+
1259+ def FindDone(self, row):
1260+ self.find_bar.Idle()
1261+ if row >= 0:
1262+ self.view.setCurrentIndex(self.model.index(row, 0, QModelIndex()))
1263+ else:
1264+ self.find_bar.NotFound()
1265+
1266+# Table list
1267+
1268+def GetTableList(glb):
1269+ tables = []
1270+ query = QSqlQuery(glb.db)
1271+ if glb.dbref.is_sqlite3:
1272+ QueryExec(query, "SELECT name FROM sqlite_master WHERE type IN ( 'table' , 'view' ) ORDER BY name")
1273+ else:
1274+ QueryExec(query, "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type IN ( 'BASE TABLE' , 'VIEW' ) ORDER BY table_name")
1275+ while query.next():
1276+ tables.append(query.value(0))
1277+ if glb.dbref.is_sqlite3:
1278+ tables.append("sqlite_master")
1279+ else:
1280+ tables.append("information_schema.tables")
1281+ tables.append("information_schema.views")
1282+ tables.append("information_schema.columns")
1283+ return tables
1284+
6231285 # Action Definition
6241286
6251287 def CreateAction(label, tip, callback, parent=None, shortcut=None):
@@ -779,12 +1441,15 @@ class MainWindow(QMainWindow): · 153 unmodified lines
7791441
7801442 edit_menu = menu.addMenu("&Edit")
7811443 edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
1444+ edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
7821445 edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
7831446 edit_menu.addAction(CreateAction("&Enlarge Font", "Make text bigger", self.EnlargeFont, self, [QKeySequence("Ctrl++")]))
7841447
7851448 reports_menu = menu.addMenu("&Reports")
7861449 reports_menu.addAction(CreateAction("Context-Sensitive Call &Graph", "Create a new window containing a context-sensitive call graph", self.NewCallGraph, self))
7871450
1451+ self.TableMenu(GetTableList(glb), menu)
1452+
7881453 self.window_menu = WindowMenu(self.mdi_area, menu)
7891454
7901455 def Find(self):
@@ -795,6 +1460,14 @@ class MainWindow(QMainWindow): · 4 unmodified lines
7951460 except:
7961461 pass
7971462
1463+ def FetchMoreRecords(self):
1464+ win = self.mdi_area.activeSubWindow()
1465+ if win:
1466+ try:
1467+ win.fetch_bar.Activate()
1468+ except:
1469+ pass
1470+
7981471 def ShrinkFont(self):
7991472 win = self.mdi_area.activeSubWindow()
8001473 ShrinkFont(win.view)
@@ -803,9 +1476,17 @@ class MainWindow(QMainWindow): · 2 unmodified lines
8031476 win = self.mdi_area.activeSubWindow()
8041477 EnlargeFont(win.view)
8051478
1479+ def TableMenu(self, tables, menu):
1480+ table_menu = menu.addMenu("&Tables")
1481+ for table in tables:
1482+ table_menu.addAction(CreateAction(table, "Create a new window containing a table view", lambda t=table: self.NewTableView(t), self))
1483+
8061484 def NewCallGraph(self):
8071485 CallGraphWindow(self.glb, self)
8081486
1487+ def NewTableView(self, table_name):
1488+ TableWindow(self.glb, table_name, self)
1489+
8091490 # Global data
8101491
8111492 class Glb():
@@ -816,6 +1497,18 @@ class Glb(): · 4 unmodified lines
8161497 self.dbname = dbname
8171498 self.app = None
8181499 self.mainwindow = None
1500+ self.instances_to_shutdown_on_exit = weakref.WeakSet()
1501+
1502+ def AddInstanceToShutdownOnExit(self, instance):
1503+ self.instances_to_shutdown_on_exit.add(instance)
1504+
1505+ # Shutdown any background processes or threads
1506+ def ShutdownInstances(self):
1507+ for x in self.instances_to_shutdown_on_exit:
1508+ try:
1509+ x.Shutdown()
1510+ except:
1511+ pass
8191512
8201513 # Database reference
8211514
@@ -880,6 +1573,7 @@ def Main(): · 58 unmodified lines
8801573 glb.mainwindow = mainwindow
8811574 mainwindow.show()
8821575 err = app.exec_()
1576+ glb.ShutdownInstances()
8831577 db.close()
8841578 sys.exit(err)
8851579