VirtualBox

source: vbox/trunk/src/VBox/ValidationKit/testmanager/core/db.py@ 61266

Last change on this file since 61266 was 61254, checked in by vboxsync, 9 years ago

testmanager: Added report on faillure reasons, listing first and last build with each.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 24.2 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: db.py 61254 2016-05-28 01:38:58Z vboxsync $
3
4"""
5Test Manager - Database Interface.
6"""
7
8__copyright__ = \
9"""
10Copyright (C) 2012-2015 Oracle Corporation
11
12This file is part of VirtualBox Open Source Edition (OSE), as
13available from http://www.virtualbox.org. This file is free software;
14you can redistribute it and/or modify it under the terms of the GNU
15General Public License (GPL) as published by the Free Software
16Foundation, in version 2 as it comes in the "COPYING" file of the
17VirtualBox OSE distribution. VirtualBox OSE is distributed in the
18hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
19
20The contents of this file may alternatively be used under the terms
21of the Common Development and Distribution License Version 1.0
22(CDDL) only, as it comes in the "COPYING.CDDL" file of the
23VirtualBox OSE distribution, in which case the provisions of the
24CDDL are applicable instead of those of the GPL.
25
26You may elect to license modified versions of this file under the
27terms and conditions of either the GPL or the CDDL or both.
28"""
29__version__ = "$Revision: 61254 $"
30
31
32# Standard python imports.
33import datetime;
34import os;
35import sys;
36import psycopg2;
37import psycopg2.extensions;
38
39# Validation Kit imports.
40from common import utils, webutils;
41from testmanager import config;
42
43# Fix psycho unicode handling in psycopg2 with python 2.x.
44if sys.version_info[0] < 3:
45 psycopg2.extensions.register_type(psycopg2.extensions.UNICODE);
46 psycopg2.extensions.register_type(psycopg2.extensions.UNICODEARRAY);
47
48
49def isDbTimestampInfinity(tsValue):
50 """
51 Checks if tsValue is an infinity timestamp.
52 """
53 ## @todo improve this test...
54 return tsValue.year >= 9999;
55
56def isDbTimestamp(oValue):
57 """
58 Checks if oValue is a DB timestamp object.
59 """
60 if isinstance(oValue, datetime.datetime):
61 return True;
62 if utils.isString(oValue):
63 ## @todo detect strings as well.
64 return False;
65 return getattr(oValue, 'pydatetime', None) != None;
66
67def dbTimestampToDatetime(oValue):
68 """
69 Converts a database timestamp to a datetime instance.
70 """
71 if isinstance(oValue, datetime.datetime):
72 return oValue;
73 if utils.isString(oValue):
74 raise Exception('TODO');
75 return oValue.pydatetime();
76
77def dbTimestampToZuluDatetime(oValue):
78 """
79 Converts a database timestamp to a zulu datetime instance.
80 """
81 tsValue = dbTimestampToDatetime(oValue);
82
83 if tsValue.tzinfo is not None:
84 class UTC(datetime.tzinfo):
85 """UTC TZ Info Class"""
86 def utcoffset(self, _):
87 return datetime.timedelta(0);
88 def tzname(self, _):
89 return "UTC";
90 def dst(self, _):
91 return datetime.timedelta(0);
92 tsValue = tsValue.astimezone(UTC());
93
94 return tsValue;
95
96def isDbInterval(oValue):
97 """
98 Checks if oValue is a DB interval object.
99 """
100 if isinstance(oValue, datetime.timedelta):
101 return True;
102 return False;
103
104
105class TMDatabaseIntegrityException(Exception):
106 """
107 Herolds a database integrity error up the callstack.
108
109 Do NOT use directly, only thru TMDatabaseConnection.integrityException.
110 Otherwise, we won't be able to log the issue.
111 """
112 pass;
113
114
115class TMDatabaseCursor(object):
116 """ Cursor wrapper class. """
117
118 def __init__(self, oDb, oCursor):
119 self._oDb = oDb;
120 self._oCursor = oCursor;
121
122 def execute(self, sOperation, aoArgs = None):
123 """ See TMDatabaseConnection.execute()"""
124 return self._oDb.executeInternal(self._oCursor, sOperation, aoArgs, utils.getCallerName());
125
126 def callProc(self, sProcedure, aoArgs = None):
127 """ See TMDatabaseConnection.callProc()"""
128 return self._oDb.callProcInternal(self._oCursor, sProcedure, aoArgs, utils.getCallerName());
129
130 def insertList(self, sInsertSql, aoList, fnEntryFmt):
131 """ See TMDatabaseConnection.insertList. """
132 return self._oDb.insertListInternal(self._oCursor, sInsertSql, aoList, fnEntryFmt, utils.getCallerName());
133
134 def fetchOne(self):
135 """Wrapper around Psycopg2.cursor.fetchone."""
136 return self._oCursor.fetchone();
137
138 def fetchMany(self, cRows = None):
139 """Wrapper around Psycopg2.cursor.fetchmany."""
140 return self._oCursor.fetchmany(cRows if cRows is not None else self._oCursor.arraysize);
141
142 def fetchAll(self):
143 """Wrapper around Psycopg2.cursor.fetchall."""
144 return self._oCursor.fetchall();
145
146 def getRowCount(self):
147 """Wrapper around Psycopg2.cursor.rowcount."""
148 return self._oCursor.rowcount;
149
150 def formatBindArgs(self, sStatement, aoArgs):
151 """Wrapper around Psycopg2.cursor.mogrify."""
152 oRet = self._oCursor.mogrify(sStatement, aoArgs);
153 if sys.version_info[0] >= 3 and not isinstance(oRet, str):
154 oRet = oRet.decode('utf-8');
155 return oRet;
156
157 def copyExpert(self, sSqlCopyStmt, oFile, cbBuf = 8192):
158 """ See TMDatabaseConnection.copyExpert()"""
159 return self._oCursor.copy_expert(sSqlCopyStmt, oFile, cbBuf);
160
161 @staticmethod
162 def isTsInfinity(tsValue):
163 """ Checks if tsValue is an infinity timestamp. """
164 return isDbTimestampInfinity(tsValue);
165
166
167class TMDatabaseConnection(object):
168 """
169 Test Manager Database Access class.
170
171 This class contains no logic, just raw access abstraction and utilities,
172 as well as some debug help and some statistics.
173 """
174
175 def __init__(self, fnDPrint = None, oSrvGlue = None):
176 """
177 Database connection wrapper.
178 The fnDPrint is for debug logging of all database activity.
179
180 Raises an exception on failure.
181 """
182
183 sAppName = '%s-%s' % (os.getpid(), os.path.basename(sys.argv[0]),)
184 if len(sAppName) >= 64:
185 sAppName = sAppName[:64];
186 os.environ['PGAPPNAME'] = sAppName;
187
188 dArgs = \
189 { \
190 'database': config.g_ksDatabaseName,
191 'user': config.g_ksDatabaseUser,
192 'password': config.g_ksDatabasePassword,
193 # 'application_name': sAppName, - Darn stale debian! :/
194 };
195 if config.g_ksDatabaseAddress is not None:
196 dArgs['host'] = config.g_ksDatabaseAddress;
197 if config.g_ksDatabasePort is not None:
198 dArgs['port'] = config.g_ksDatabasePort;
199 self._oConn = psycopg2.connect(**dArgs); # pylint: disable=W0142
200 self._oConn.set_client_encoding('UTF-8');
201 self._oCursor = self._oConn.cursor();
202 self._oExplainConn = None;
203 self._oExplainCursor = None;
204 if config.g_kfWebUiSqlTraceExplain and config.g_kfWebUiSqlTrace:
205 self._oExplainConn = psycopg2.connect(**dArgs); # pylint: disable=W0142
206 self._oExplainConn.set_client_encoding('UTF-8');
207 self._oExplainCursor = self._oExplainConn.cursor();
208 self._fTransaction = False;
209 self._tsCurrent = None;
210 self._tsCurrentMinusOne = None;
211
212 assert self.isAutoCommitting() is False;
213
214 # Debug and introspection.
215 self._fnDPrint = fnDPrint;
216 self._aoTraceBack = [];
217
218 # Exception class handles.
219 self.oXcptError = psycopg2.Error;
220
221 if oSrvGlue is not None:
222 oSrvGlue.registerDebugInfoCallback(self.debugInfoCallback);
223
224 # Object caches (used by database logic classes).
225 self.ddCaches = dict();
226
227 def isAutoCommitting(self):
228 """ Work around missing autocommit attribute in older versions."""
229 return getattr(self._oConn, 'autocommit', False);
230
231 def close(self):
232 """
233 Closes the connection and renders all cursors useless.
234 """
235 if self._oCursor is not None:
236 self._oCursor.close();
237 self._oCursor = None;
238
239 if self._oConn is not None:
240 self._oConn.close();
241 self._oConn = None;
242
243 if self._oExplainCursor is not None:
244 self._oExplainCursor.close();
245 self._oExplainCursor = None;
246
247 if self._oExplainConn is not None:
248 self._oExplainConn.close();
249 self._oExplainConn = None;
250
251
252 def _startedTransaction(self):
253 """
254 Called to work the _fTransaction and related variables when starting
255 a transaction.
256 """
257 self._fTransaction = True;
258 self._tsCurrent = None;
259 self._tsCurrentMinusOne = None;
260 return None;
261
262 def _endedTransaction(self):
263 """
264 Called to work the _fTransaction and related variables when ending
265 a transaction.
266 """
267 self._fTransaction = False;
268 self._tsCurrent = None;
269 self._tsCurrentMinusOne = None;
270 return None;
271
272 def begin(self):
273 """
274 Currently just for marking where a transaction starts in the code.
275 """
276 assert self._oConn is not None;
277 assert self.isAutoCommitting() is False;
278 self._aoTraceBack.append([utils.timestampNano(), 'START TRANSACTION', 0, 0, utils.getCallerName(), None]);
279 self._startedTransaction();
280 return True;
281
282 def commit(self, sCallerName = None):
283 """ Wrapper around Psycopg2.connection.commit."""
284 assert self._fTransaction is True;
285
286 nsStart = utils.timestampNano();
287 oRc = self._oConn.commit();
288 cNsElapsed = utils.timestampNano() - nsStart;
289
290 if sCallerName is None:
291 sCallerName = utils.getCallerName();
292 self._aoTraceBack.append([nsStart, 'COMMIT', cNsElapsed, 0, sCallerName, None]);
293 self._endedTransaction();
294 return oRc;
295
296 def maybeCommit(self, fCommit):
297 """
298 Commits if fCommit is True.
299 Returns True if committed, False if not.
300 """
301 if fCommit is True:
302 self.commit(utils.getCallerName());
303 return True;
304 return False;
305
306 def rollback(self):
307 """ Wrapper around Psycopg2.connection.rollback."""
308 nsStart = utils.timestampNano();
309 oRc = self._oConn.rollback();
310 cNsElapsed = utils.timestampNano() - nsStart;
311
312 self._aoTraceBack.append([nsStart, 'ROLLBACK', cNsElapsed, 0, utils.getCallerName(), None]);
313 self._endedTransaction();
314 return oRc;
315
316 #
317 # Internal cursor workers.
318 #
319
320 def executeInternal(self, oCursor, sOperation, aoArgs, sCallerName):
321 """
322 Execute a query or command.
323
324 Mostly a wrapper around the psycopg2 cursor method with the same name,
325 but collect data for traceback.
326 """
327 if aoArgs is None:
328 aoArgs = list();
329
330 sBound = oCursor.mogrify(unicode(sOperation), aoArgs);
331 if sys.version_info[0] >= 3 and not isinstance(sBound, str):
332 sBound = sBound.decode('utf-8');
333
334 aasExplain = None;
335 if self._oExplainCursor is not None and not sBound.startswith('DROP'):
336 try:
337 if config.g_kfWebUiSqlTraceExplainTiming:
338 self._oExplainCursor.execute('EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE, TIMING) ' + sBound);
339 else:
340 self._oExplainCursor.execute('EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE) ' + sBound);
341 except Exception as oXcpt:
342 aasExplain = [ ['Explain exception: '], [str(oXcpt)]];
343 try: self._oExplainConn.rollback();
344 except: pass;
345 else:
346 aasExplain = self._oExplainCursor.fetchall();
347
348 nsStart = utils.timestampNano();
349 try:
350 oRc = oCursor.execute(sBound);
351 except Exception as oXcpt:
352 cNsElapsed = utils.timestampNano() - nsStart;
353 self._aoTraceBack.append([nsStart, 'oXcpt=%s; Statement: %s' % (oXcpt, sBound), cNsElapsed, 0, sCallerName, None]);
354 if self._fnDPrint is not None:
355 self._fnDPrint('db::execute %u ns, caller %s: oXcpt=%s; Statement: %s'
356 % (cNsElapsed, sCallerName, oXcpt, sBound));
357 raise;
358 cNsElapsed = utils.timestampNano() - nsStart;
359
360 if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ.
361 self._aoTraceBack.append([nsStart, '[START TRANSACTION]', 0, 0, sCallerName, None]);
362 self._startedTransaction();
363 self._aoTraceBack.append([nsStart, sBound, cNsElapsed, oCursor.rowcount, sCallerName, aasExplain]);
364 if self._fnDPrint is not None:
365 self._fnDPrint('db::execute %u ns, caller %s: "\n%s"' % (cNsElapsed, sCallerName, sBound));
366 if self.isAutoCommitting():
367 self._aoTraceBack.append([nsStart, '[AUTO COMMIT]', 0, 0, sCallerName, None]);
368
369 return oRc;
370
371 def callProcInternal(self, oCursor, sProcedure, aoArgs, sCallerName):
372 """
373 Call a stored procedure.
374
375 Mostly a wrapper around the psycopg2 cursor method 'callproc', but
376 collect data for traceback.
377 """
378 if aoArgs is None:
379 aoArgs = list();
380
381 nsStart = utils.timestampNano();
382 try:
383 oRc = oCursor.callproc(sProcedure, aoArgs);
384 except Exception as oXcpt:
385 cNsElapsed = utils.timestampNano() - nsStart;
386 self._aoTraceBack.append([nsStart, 'oXcpt=%s; Calling: %s(%s)' % (oXcpt, sProcedure, aoArgs),
387 cNsElapsed, 0, sCallerName, None]);
388 if self._fnDPrint is not None:
389 self._fnDPrint('db::callproc %u ns, caller %s: oXcpt=%s; Calling: %s(%s)'
390 % (cNsElapsed, sCallerName, oXcpt, sProcedure, aoArgs));
391 raise;
392 cNsElapsed = utils.timestampNano() - nsStart;
393
394 if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ.
395 self._aoTraceBack.append([nsStart, '[START TRANSACTION]', 0, 0, sCallerName, None]);
396 self._startedTransaction();
397 self._aoTraceBack.append([nsStart, '%s(%s)' % (sProcedure, aoArgs), cNsElapsed, oCursor.rowcount, sCallerName, None]);
398 if self._fnDPrint is not None:
399 self._fnDPrint('db::callproc %u ns, caller %s: "%s(%s)"' % (cNsElapsed, sCallerName, sProcedure, aoArgs));
400 if self.isAutoCommitting():
401 self._aoTraceBack.append([nsStart, '[AUTO COMMIT]', 0, 0, sCallerName, sCallerName, None]);
402
403 return oRc;
404
405 def insertListInternal(self, oCursor, sInsertSql, aoList, fnEntryFmt, sCallerName):
406 """
407 Optimizes the insertion of a list of values.
408 """
409 oRc = None;
410 asValues = [];
411 for aoEntry in aoList:
412 asValues.append(fnEntryFmt(aoEntry));
413 if len(asValues) > 256:
414 oRc = self.executeInternal(oCursor, sInsertSql + 'VALUES' + ', '.join(asValues), None, sCallerName);
415 asValues = [];
416 if len(asValues) > 0:
417 oRc = self.executeInternal(oCursor, sInsertSql + 'VALUES' + ', '.join(asValues), None, sCallerName);
418 return oRc
419
420 def _fetchOne(self, oCursor):
421 """Wrapper around Psycopg2.cursor.fetchone."""
422 oRow = oCursor.fetchone()
423 if self._fnDPrint is not None:
424 self._fnDPrint('db:fetchOne returns: %s' % (oRow,));
425 return oRow;
426
427 def _fetchMany(self, oCursor, cRows):
428 """Wrapper around Psycopg2.cursor.fetchmany."""
429 return oCursor.fetchmany(cRows if cRows is not None else oCursor.arraysize);
430
431 def _fetchAll(self, oCursor):
432 """Wrapper around Psycopg2.cursor.fetchall."""
433 return oCursor.fetchall()
434
435 def _getRowCountWorker(self, oCursor):
436 """Wrapper around Psycopg2.cursor.rowcount."""
437 return oCursor.rowcount;
438
439
440 #
441 # Default cursor access.
442 #
443
444 def execute(self, sOperation, aoArgs = None):
445 """
446 Execute a query or command.
447
448 Mostly a wrapper around the psycopg2 cursor method with the same name,
449 but collect data for traceback.
450 """
451 return self.executeInternal(self._oCursor, sOperation, aoArgs, utils.getCallerName());
452
453 def callProc(self, sProcedure, aoArgs = None):
454 """
455 Call a stored procedure.
456
457 Mostly a wrapper around the psycopg2 cursor method 'callproc', but
458 collect data for traceback.
459 """
460 return self.callProcInternal(self._oCursor, sProcedure, aoArgs, utils.getCallerName());
461
462 def insertList(self, sInsertSql, aoList, fnEntryFmt):
463 """
464 Optimizes the insertion of a list of values.
465 """
466 return self.insertListInternal(self._oCursor, sInsertSql, aoList, fnEntryFmt, utils.getCallerName());
467
468 def fetchOne(self):
469 """Wrapper around Psycopg2.cursor.fetchone."""
470 return self._oCursor.fetchone();
471
472 def fetchMany(self, cRows = None):
473 """Wrapper around Psycopg2.cursor.fetchmany."""
474 return self._oCursor.fetchmany(cRows if cRows is not None else self._oCursor.arraysize);
475
476 def fetchAll(self):
477 """Wrapper around Psycopg2.cursor.fetchall."""
478 return self._oCursor.fetchall();
479
480 def getRowCount(self):
481 """Wrapper around Psycopg2.cursor.rowcount."""
482 return self._oCursor.rowcount;
483
484 def formatBindArgs(self, sStatement, aoArgs):
485 """Wrapper around Psycopg2.cursor.mogrify."""
486 oRet = self._oCursor.mogrify(sStatement, aoArgs);
487 if sys.version_info[0] >= 3 and not isinstance(oRet, str):
488 oRet = oRet.decode('utf-8');
489 return oRet;
490
491 def copyExpert(self, sSqlCopyStmt, oFile, cbBuf = 8192):
492 """ Wrapper around Psycopg2.cursor.copy_expert. """
493 return self._oCursor.copy_expert(sSqlCopyStmt, oFile, cbBuf);
494
495 def getCurrentTimestamps(self):
496 """
497 Returns the current timestamp and the current timestamp minus one tick.
498 This will start a transaction if necessary.
499 """
500 if self._tsCurrent is None:
501 self.execute('SELECT CURRENT_TIMESTAMP, CURRENT_TIMESTAMP - INTERVAL \'1 microsecond\'');
502 (self._tsCurrent, self._tsCurrentMinusOne) = self.fetchOne();
503 return (self._tsCurrent, self._tsCurrentMinusOne);
504
505 def getCurrentTimestamp(self):
506 """
507 Returns the current timestamp.
508 This will start a transaction if necessary.
509 """
510 if self._tsCurrent is None:
511 self.getCurrentTimestamps();
512 return self._tsCurrent;
513
514 def getCurrentTimestampMinusOne(self):
515 """
516 Returns the current timestamp minus one tick.
517 This will start a transaction if necessary.
518 """
519 if self._tsCurrentMinusOne is None:
520 self.getCurrentTimestamps();
521 return self._tsCurrentMinusOne;
522
523
524 #
525 # Additional cursors.
526 #
527 def openCursor(self):
528 """
529 Opens a new cursor (TMDatabaseCursor).
530 """
531 oCursor = self._oConn.cursor();
532 return TMDatabaseCursor(self, oCursor);
533
534 #
535 # Cache support.
536 #
537 def getCache(self, sType):
538 """ Returns the cache dictionary for this data type. """
539 dRet = self.ddCaches.get(sType, None);
540 if dRet is None:
541 dRet = dict();
542 self.ddCaches[sType] = dRet;
543 return dRet;
544
545
546 #
547 # Utilities.
548 #
549
550 @staticmethod
551 def isTsInfinity(tsValue):
552 """ Checks if tsValue is an infinity timestamp. """
553 return isDbTimestampInfinity(tsValue);
554
555 #
556 # Error stuff.
557 #
558 def integrityException(self, sMessage):
559 """
560 Database integrity reporter and exception factory.
561 Returns an TMDatabaseIntegrityException which the caller can raise.
562 """
563 ## @todo Create a new database connection and log the issue in the SystemLog table.
564 ## Alternatively, rollback whatever is going on and do it using the current one.
565 return TMDatabaseIntegrityException(sMessage);
566
567
568 #
569 # Debugging.
570 #
571
572 def dprint(self, sText):
573 """
574 Debug output.
575 """
576 if not self._fnDPrint:
577 return False;
578 self._fnDPrint(sText);
579 return True;
580
581 def debugHtmlReport(self, tsStart = 0):
582 """
583 Used to get a SQL activity dump as HTML, usually for WuiBase._sDebug.
584 """
585 cNsElapsed = 0;
586 for aEntry in self._aoTraceBack:
587 cNsElapsed += aEntry[2];
588
589 sDebug = '<h3>SQL Debug Log (total time %s ns):</h3>\n' \
590 '<table class="tmsqltable">\n' \
591 ' <tr>\n' \
592 ' <th>No.</th>\n' \
593 ' <th>Timestamp (ns)</th>\n' \
594 ' <th>Elapsed (ns)</th>\n' \
595 ' <th>Rows Returned</th>\n' \
596 ' <th>Command</th>\n' \
597 ' <th>Caller</th>\n' \
598 ' </tr>\n' \
599 % (utils.formatNumber(cNsElapsed, '&nbsp;'),);
600
601 iEntry = 0;
602 for aEntry in self._aoTraceBack:
603 iEntry += 1;
604 sDebug += ' <tr>\n' \
605 ' <td align="right">%s</td>\n' \
606 ' <td align="right">%s</td>\n' \
607 ' <td align="right">%s</td>\n' \
608 ' <td align="right">%s</td>\n' \
609 ' <td><pre>%s</pre></td>\n' \
610 ' <td>%s</td>\n' \
611 ' </tr>\n' \
612 % (iEntry,
613 utils.formatNumber(aEntry[0] - tsStart, '&nbsp;'),
614 utils.formatNumber(aEntry[2], '&nbsp;'),
615 utils.formatNumber(aEntry[3], '&nbsp;'),
616 webutils.escapeElem(aEntry[1]),
617 webutils.escapeElem(aEntry[4]),
618 );
619 if aEntry[5] is not None:
620 sDebug += ' <tr>\n' \
621 ' <td colspan="6"><pre style="white-space: pre-wrap;">%s</pre></td>\n' \
622 ' </tr>\n' \
623 % (webutils.escapeElem('\n'.join([aoRow[0] for aoRow in aEntry[5]])),);
624
625 sDebug += '</table>';
626 return sDebug;
627
628 def debugTextReport(self, tsStart = 0):
629 """
630 Used to get a SQL activity dump as text.
631 """
632 cNsElapsed = 0;
633 for aEntry in self._aoTraceBack:
634 cNsElapsed += aEntry[2];
635
636 sHdr = 'SQL Debug Log (total time %s ns)' % (utils.formatNumber(cNsElapsed),);
637 sDebug = sHdr + '\n' + '-' * len(sHdr) + '\n';
638
639 iEntry = 0;
640 for aEntry in self._aoTraceBack:
641 iEntry += 1;
642 sHdr = 'Query #%s Timestamp: %s ns Elapsed: %s ns Rows: %s Caller: %s' \
643 % ( iEntry,
644 utils.formatNumber(aEntry[0] - tsStart),
645 utils.formatNumber(aEntry[2]),
646 utils.formatNumber(aEntry[3]),
647 aEntry[4], );
648 sDebug += '\n' + sHdr + '\n' + '-' * len(sHdr) + '\n';
649
650 sDebug += aEntry[1];
651 if sDebug[-1] != '\n':
652 sDebug += '\n';
653
654 if aEntry[5] is not None:
655 sDebug += 'Explain:\n' \
656 ' %s\n' \
657 % ( '\n'.join([aoRow[0] for aoRow in aEntry[5]]),);
658
659 return sDebug;
660
661 def debugInfoCallback(self, oGlue, fHtml):
662 """ Called back by the glue code on error. """
663 oGlue.write('\n');
664 if not fHtml: oGlue.write(self.debugTextReport());
665 else: oGlue.write(self.debugHtmlReport());
666 oGlue.write('\n');
667 return True;
668
669 def debugEnableExplain(self):
670 """ Enabled explain. """
671 if self._oExplainConn is None:
672 dArgs = \
673 { \
674 'database': config.g_ksDatabaseName,
675 'user': config.g_ksDatabaseUser,
676 'password': config.g_ksDatabasePassword,
677 # 'application_name': sAppName, - Darn stale debian! :/
678 };
679 if config.g_ksDatabaseAddress is not None:
680 dArgs['host'] = config.g_ksDatabaseAddress;
681 if config.g_ksDatabasePort is not None:
682 dArgs['port'] = config.g_ksDatabasePort;
683 self._oExplainConn = psycopg2.connect(**dArgs); # pylint: disable=W0142
684 self._oExplainCursor = self._oExplainConn.cursor();
685 return True;
686
687 def debugDisableExplain(self):
688 """ Disables explain. """
689 self._oExplainCursor = None;
690 self._oExplainConn = None
691 return True;
692
693 def debugIsExplainEnabled(self):
694 """ Check if explaining of SQL statements is enabled. """
695 return self._oExplainConn is not None;
696
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette