VirtualBox

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

Last change on this file since 83383 was 83382, checked in by vboxsync, 5 years ago

TestManager/db.py: Added dbTimestampPlusOneTick to complement dbTimestampMinusOneTick.

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