VirtualBox

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

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

testmanager/db.py: Added insertList methods for optimized multi row insertion. Added debugDisableExplain and debugIsExplainEnabled methods for avoid deadlocks in the schulding queue regenration code.

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