VirtualBox

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

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

core/db.py: copyExpert method wrapping copy_exper.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.8 KB
Line 
1# -*- coding: utf-8 -*-
2# $Id: db.py 61185 2016-05-25 03:06:08Z 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: 61185 $"
30
31
32# Standard python imports.
33import datetime;
34import os;
35import psycopg2;
36import psycopg2.extensions;
37import sys;
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 def isAutoCommitting(self):
225 """ Work around missing autocommit attribute in older versions."""
226 return getattr(self._oConn, 'autocommit', False);
227
228 def close(self):
229 """
230 Closes the connection and renders all cursors useless.
231 """
232 if self._oCursor is not None:
233 self._oCursor.close();
234 self._oCursor = None;
235
236 if self._oConn is not None:
237 self._oConn.close();
238 self._oConn = None;
239
240 if self._oExplainCursor is not None:
241 self._oExplainCursor.close();
242 self._oExplainCursor = None;
243
244 if self._oExplainConn is not None:
245 self._oExplainConn.close();
246 self._oExplainConn = None;
247
248
249 def _startedTransaction(self):
250 """
251 Called to work the _fTransaction and related variables when starting
252 a transaction.
253 """
254 self._fTransaction = True;
255 self._tsCurrent = None;
256 self._tsCurrentMinusOne = None;
257 return None;
258
259 def _endedTransaction(self):
260 """
261 Called to work the _fTransaction and related variables when ending
262 a transaction.
263 """
264 self._fTransaction = False;
265 self._tsCurrent = None;
266 self._tsCurrentMinusOne = None;
267 return None;
268
269 def begin(self):
270 """
271 Currently just for marking where a transaction starts in the code.
272 """
273 assert self._oConn is not None;
274 assert self.isAutoCommitting() is False;
275 self._aoTraceBack.append([utils.timestampNano(), 'START TRANSACTION', 0, 0, utils.getCallerName(), None]);
276 self._startedTransaction();
277 return True;
278
279 def commit(self, sCallerName = None):
280 """ Wrapper around Psycopg2.connection.commit."""
281 assert self._fTransaction is True;
282
283 nsStart = utils.timestampNano();
284 oRc = self._oConn.commit();
285 cNsElapsed = utils.timestampNano() - nsStart;
286
287 if sCallerName is None:
288 sCallerName = utils.getCallerName();
289 self._aoTraceBack.append([nsStart, 'COMMIT', cNsElapsed, 0, sCallerName, None]);
290 self._endedTransaction();
291 return oRc;
292
293 def maybeCommit(self, fCommit):
294 """
295 Commits if fCommit is True.
296 Returns True if committed, False if not.
297 """
298 if fCommit is True:
299 self.commit(utils.getCallerName());
300 return True;
301 return False;
302
303 def rollback(self):
304 """ Wrapper around Psycopg2.connection.rollback."""
305 nsStart = utils.timestampNano();
306 oRc = self._oConn.rollback();
307 cNsElapsed = utils.timestampNano() - nsStart;
308
309 self._aoTraceBack.append([nsStart, 'ROLLBACK', cNsElapsed, 0, utils.getCallerName(), None]);
310 self._endedTransaction();
311 return oRc;
312
313 #
314 # Internal cursor workers.
315 #
316
317 def executeInternal(self, oCursor, sOperation, aoArgs, sCallerName):
318 """
319 Execute a query or command.
320
321 Mostly a wrapper around the psycopg2 cursor method with the same name,
322 but collect data for traceback.
323 """
324 if aoArgs is None:
325 aoArgs = list();
326
327 sBound = oCursor.mogrify(unicode(sOperation), aoArgs);
328 if sys.version_info[0] >= 3 and not isinstance(sBound, str):
329 sBound = sBound.decode('utf-8');
330
331 aasExplain = None;
332 if self._oExplainCursor is not None:
333 try:
334 if config.g_kfWebUiSqlTraceExplainTiming:
335 self._oExplainCursor.execute('EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE, TIMING) ' + sBound);
336 else:
337 self._oExplainCursor.execute('EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE) ' + sBound);
338 except Exception as oXcpt:
339 aasExplain = [ ['Explain exception: '], [str(oXcpt)]];
340 try: self._oExplainConn.rollback();
341 except: pass;
342 else:
343 aasExplain = self._oExplainCursor.fetchall();
344
345 nsStart = utils.timestampNano();
346 try:
347 oRc = oCursor.execute(sBound);
348 except Exception as oXcpt:
349 cNsElapsed = utils.timestampNano() - nsStart;
350 self._aoTraceBack.append([nsStart, 'oXcpt=%s; Statement: %s' % (oXcpt, sBound), cNsElapsed, 0, sCallerName, None]);
351 if self._fnDPrint is not None:
352 self._fnDPrint('db::execute %u ns, caller %s: oXcpt=%s; Statement: %s'
353 % (cNsElapsed, sCallerName, oXcpt, sBound));
354 raise;
355 cNsElapsed = utils.timestampNano() - nsStart;
356
357 if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ.
358 self._aoTraceBack.append([nsStart, '[START TRANSACTION]', 0, 0, sCallerName, None]);
359 self._startedTransaction();
360 self._aoTraceBack.append([nsStart, sBound, cNsElapsed, oCursor.rowcount, sCallerName, aasExplain]);
361 if self._fnDPrint is not None:
362 self._fnDPrint('db::execute %u ns, caller %s: "\n%s"' % (cNsElapsed, sCallerName, sBound));
363 if self.isAutoCommitting():
364 self._aoTraceBack.append([nsStart, '[AUTO COMMIT]', 0, 0, sCallerName, None]);
365
366 return oRc;
367
368 def callProcInternal(self, oCursor, sProcedure, aoArgs, sCallerName):
369 """
370 Call a stored procedure.
371
372 Mostly a wrapper around the psycopg2 cursor method 'callproc', but
373 collect data for traceback.
374 """
375 if aoArgs is None:
376 aoArgs = list();
377
378 nsStart = utils.timestampNano();
379 try:
380 oRc = oCursor.callproc(sProcedure, aoArgs);
381 except Exception as oXcpt:
382 cNsElapsed = utils.timestampNano() - nsStart;
383 self._aoTraceBack.append([nsStart, 'oXcpt=%s; Calling: %s(%s)' % (oXcpt, sProcedure, aoArgs),
384 cNsElapsed, 0, sCallerName, None]);
385 if self._fnDPrint is not None:
386 self._fnDPrint('db::callproc %u ns, caller %s: oXcpt=%s; Calling: %s(%s)'
387 % (cNsElapsed, sCallerName, oXcpt, sProcedure, aoArgs));
388 raise;
389 cNsElapsed = utils.timestampNano() - nsStart;
390
391 if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ.
392 self._aoTraceBack.append([nsStart, '[START TRANSACTION]', 0, 0, sCallerName, None]);
393 self._startedTransaction();
394 self._aoTraceBack.append([nsStart, '%s(%s)' % (sProcedure, aoArgs), cNsElapsed, oCursor.rowcount, sCallerName, None]);
395 if self._fnDPrint is not None:
396 self._fnDPrint('db::callproc %u ns, caller %s: "%s(%s)"' % (cNsElapsed, sCallerName, sProcedure, aoArgs));
397 if self.isAutoCommitting():
398 self._aoTraceBack.append([nsStart, '[AUTO COMMIT]', 0, 0, sCallerName, sCallerName, None]);
399
400 return oRc;
401
402 def insertListInternal(self, oCursor, sInsertSql, aoList, fnEntryFmt, sCallerName):
403 """
404 Optimizes the insertion of a list of values.
405 """
406 oRc = None;
407 asValues = [];
408 for aoEntry in aoList:
409 asValues.append(fnEntryFmt(aoEntry));
410 if len(asValues) > 256:
411 oRc = self.executeInternal(oCursor, sInsertSql + 'VALUES' + ', '.join(asValues), None, sCallerName);
412 asValues = [];
413 if len(asValues) > 0:
414 oRc = self.executeInternal(oCursor, sInsertSql + 'VALUES' + ', '.join(asValues), None, sCallerName);
415 return oRc
416
417 def _fetchOne(self, oCursor):
418 """Wrapper around Psycopg2.cursor.fetchone."""
419 oRow = oCursor.fetchone()
420 if self._fnDPrint is not None:
421 self._fnDPrint('db:fetchOne returns: %s' % (oRow,));
422 return oRow;
423
424 def _fetchMany(self, oCursor, cRows):
425 """Wrapper around Psycopg2.cursor.fetchmany."""
426 return oCursor.fetchmany(cRows if cRows is not None else oCursor.arraysize);
427
428 def _fetchAll(self, oCursor):
429 """Wrapper around Psycopg2.cursor.fetchall."""
430 return oCursor.fetchall()
431
432 def _getRowCountWorker(self, oCursor):
433 """Wrapper around Psycopg2.cursor.rowcount."""
434 return oCursor.rowcount;
435
436
437 #
438 # Default cursor access.
439 #
440
441 def execute(self, sOperation, aoArgs = None):
442 """
443 Execute a query or command.
444
445 Mostly a wrapper around the psycopg2 cursor method with the same name,
446 but collect data for traceback.
447 """
448 return self.executeInternal(self._oCursor, sOperation, aoArgs, utils.getCallerName());
449
450 def callProc(self, sProcedure, aoArgs = None):
451 """
452 Call a stored procedure.
453
454 Mostly a wrapper around the psycopg2 cursor method 'callproc', but
455 collect data for traceback.
456 """
457 return self.callProcInternal(self._oCursor, sProcedure, aoArgs, utils.getCallerName());
458
459 def insertList(self, sInsertSql, aoList, fnEntryFmt):
460 """
461 Optimizes the insertion of a list of values.
462 """
463 return self.insertListInternal(self._oCursor, sInsertSql, aoList, fnEntryFmt, utils.getCallerName());
464
465 def fetchOne(self):
466 """Wrapper around Psycopg2.cursor.fetchone."""
467 return self._oCursor.fetchone();
468
469 def fetchMany(self, cRows = None):
470 """Wrapper around Psycopg2.cursor.fetchmany."""
471 return self._oCursor.fetchmany(cRows if cRows is not None else self._oCursor.arraysize);
472
473 def fetchAll(self):
474 """Wrapper around Psycopg2.cursor.fetchall."""
475 return self._oCursor.fetchall();
476
477 def getRowCount(self):
478 """Wrapper around Psycopg2.cursor.rowcount."""
479 return self._oCursor.rowcount;
480
481 def formatBindArgs(self, sStatement, aoArgs):
482 """Wrapper around Psycopg2.cursor.mogrify."""
483 oRet = self._oCursor.mogrify(sStatement, aoArgs);
484 if sys.version_info[0] >= 3 and not isinstance(oRet, str):
485 oRet = oRet.decode('utf-8');
486 return oRet;
487
488 def copyExpert(self, sSqlCopyStmt, oFile, cbBuf = 8192):
489 """ Wrapper around Psycopg2.cursor.copy_expert. """
490 return self._oCursor.copy_expert(sSqlCopyStmt, oFile, cbBuf);
491
492 def getCurrentTimestamps(self):
493 """
494 Returns the current timestamp and the current timestamp minus one tick.
495 This will start a transaction if necessary.
496 """
497 if self._tsCurrent is None:
498 self.execute('SELECT CURRENT_TIMESTAMP, CURRENT_TIMESTAMP - INTERVAL \'1 microsecond\'');
499 (self._tsCurrent, self._tsCurrentMinusOne) = self.fetchOne();
500 return (self._tsCurrent, self._tsCurrentMinusOne);
501
502 def getCurrentTimestamp(self):
503 """
504 Returns the current timestamp.
505 This will start a transaction if necessary.
506 """
507 if self._tsCurrent is None:
508 self.getCurrentTimestamps();
509 return self._tsCurrent;
510
511 def getCurrentTimestampMinusOne(self):
512 """
513 Returns the current timestamp minus one tick.
514 This will start a transaction if necessary.
515 """
516 if self._tsCurrentMinusOne is None:
517 self.getCurrentTimestamps();
518 return self._tsCurrentMinusOne;
519
520
521 #
522 # Additional cursors.
523 #
524 def openCursor(self):
525 """
526 Opens a new cursor (TMDatabaseCursor).
527 """
528 oCursor = self._oConn.cursor();
529 return TMDatabaseCursor(self, oCursor);
530
531 #
532 # Utilities.
533 #
534
535 @staticmethod
536 def isTsInfinity(tsValue):
537 """ Checks if tsValue is an infinity timestamp. """
538 return isDbTimestampInfinity(tsValue);
539
540 #
541 # Error stuff.
542 #
543 def integrityException(self, sMessage):
544 """
545 Database integrity reporter and exception factory.
546 Returns an TMDatabaseIntegrityException which the caller can raise.
547 """
548 ## @todo Create a new database connection and log the issue in the SystemLog table.
549 ## Alternatively, rollback whatever is going on and do it using the current one.
550 return TMDatabaseIntegrityException(sMessage);
551
552
553 #
554 # Debugging.
555 #
556
557 def dprint(self, sText):
558 """
559 Debug output.
560 """
561 if not self._fnDPrint:
562 return False;
563 self._fnDPrint(sText);
564 return True;
565
566 def debugHtmlReport(self, tsStart = 0):
567 """
568 Used to get a SQL activity dump as HTML, usually for WuiBase._sDebug.
569 """
570 cNsElapsed = 0;
571 for aEntry in self._aoTraceBack:
572 cNsElapsed += aEntry[2];
573
574 sDebug = '<h3>SQL Debug Log (total time %s ns):</h3>\n' \
575 '<table class="tmsqltable">\n' \
576 ' <tr>\n' \
577 ' <th>No.</th>\n' \
578 ' <th>Timestamp (ns)</th>\n' \
579 ' <th>Elapsed (ns)</th>\n' \
580 ' <th>Rows Returned</th>\n' \
581 ' <th>Command</th>\n' \
582 ' <th>Caller</th>\n' \
583 ' </tr>\n' \
584 % (utils.formatNumber(cNsElapsed, '&nbsp;'),);
585
586 iEntry = 0;
587 for aEntry in self._aoTraceBack:
588 iEntry += 1;
589 sDebug += ' <tr>\n' \
590 ' <td align="right">%s</td>\n' \
591 ' <td align="right">%s</td>\n' \
592 ' <td align="right">%s</td>\n' \
593 ' <td align="right">%s</td>\n' \
594 ' <td><pre>%s</pre></td>\n' \
595 ' <td>%s</td>\n' \
596 ' </tr>\n' \
597 % (iEntry,
598 utils.formatNumber(aEntry[0] - tsStart, '&nbsp;'),
599 utils.formatNumber(aEntry[2], '&nbsp;'),
600 utils.formatNumber(aEntry[3], '&nbsp;'),
601 webutils.escapeElem(aEntry[1]),
602 webutils.escapeElem(aEntry[4]),
603 );
604 if aEntry[5] is not None:
605 sDebug += ' <tr>\n' \
606 ' <td colspan="6"><pre style="white-space: pre-wrap;">%s</pre></td>\n' \
607 ' </tr>\n' \
608 % (webutils.escapeElem('\n'.join([aoRow[0] for aoRow in aEntry[5]])),);
609
610 sDebug += '</table>';
611 return sDebug;
612
613 def debugTextReport(self, tsStart = 0):
614 """
615 Used to get a SQL activity dump as text.
616 """
617 cNsElapsed = 0;
618 for aEntry in self._aoTraceBack:
619 cNsElapsed += aEntry[2];
620
621 sHdr = 'SQL Debug Log (total time %s ns)' % (utils.formatNumber(cNsElapsed),);
622 sDebug = sHdr + '\n' + '-' * len(sHdr) + '\n';
623
624 iEntry = 0;
625 for aEntry in self._aoTraceBack:
626 iEntry += 1;
627 sHdr = 'Query #%s Timestamp: %s ns Elapsed: %s ns Rows: %s Caller: %s' \
628 % ( iEntry,
629 utils.formatNumber(aEntry[0] - tsStart),
630 utils.formatNumber(aEntry[2]),
631 utils.formatNumber(aEntry[3]),
632 aEntry[4], );
633 sDebug += '\n' + sHdr + '\n' + '-' * len(sHdr) + '\n';
634
635 sDebug += aEntry[1];
636 if sDebug[-1] != '\n':
637 sDebug += '\n';
638
639 if aEntry[5] is not None:
640 sDebug += 'Explain:\n' \
641 ' %s\n' \
642 % ( '\n'.join([aoRow[0] for aoRow in aEntry[5]]),);
643
644 return sDebug;
645
646 def debugInfoCallback(self, oGlue, fHtml):
647 """ Called back by the glue code on error. """
648 oGlue.write('\n');
649 if not fHtml: oGlue.write(self.debugTextReport());
650 else: oGlue.write(self.debugHtmlReport());
651 oGlue.write('\n');
652 return True;
653
654 def debugEnableExplain(self):
655 """ Enabled explain. """
656 if self._oExplainConn is None:
657 dArgs = \
658 { \
659 'database': config.g_ksDatabaseName,
660 'user': config.g_ksDatabaseUser,
661 'password': config.g_ksDatabasePassword,
662 # 'application_name': sAppName, - Darn stale debian! :/
663 };
664 if config.g_ksDatabaseAddress is not None:
665 dArgs['host'] = config.g_ksDatabaseAddress;
666 if config.g_ksDatabasePort is not None:
667 dArgs['port'] = config.g_ksDatabasePort;
668 self._oExplainConn = psycopg2.connect(**dArgs); # pylint: disable=W0142
669 self._oExplainCursor = self._oExplainConn.cursor();
670 return True;
671
672 def debugDisableExplain(self):
673 """ Disables explain. """
674 self._oExplainCursor = None;
675 self._oExplainConn = None
676 return True;
677
678 def debugIsExplainEnabled(self):
679 """ Check if explaining of SQL statements is enabled. """
680 return self._oExplainConn is not None;
681
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