db.py revision cf22150eaeeb72431bf1cf65c309a431454fb22b
# -*- coding: utf-8 -*-
# $Id$
"""
Test Manager - Database Interface.
"""
__copyright__ = \
"""
Copyright (C) 2012-2014 Oracle Corporation
This file is part of VirtualBox Open Source Edition (OSE), as
available from http://www.virtualbox.org. This file is free software;
General Public License (GPL) as published by the Free Software
Foundation, in version 2 as it comes in the "COPYING" file of the
VirtualBox OSE distribution. VirtualBox OSE is distributed in the
hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
The contents of this file may alternatively be used under the terms
of the Common Development and Distribution License Version 1.0
(CDDL) only, as it comes in the "COPYING.CDDL" file of the
VirtualBox OSE distribution, in which case the provisions of the
CDDL are applicable instead of those of the GPL.
You may elect to license modified versions of this file under the
terms and conditions of either the GPL or the CDDL or both.
"""
__version__ = "$Revision$"
# Standard python imports.
import datetime;
import os;
import psycopg2;
import psycopg2.extensions;
import sys;
# Validation Kit imports.
from testmanager import config;
# Fix psycho unicode handling in psycopg2 with python 2.x.
def isDbTimestampInfinity(tsValue):
"""
Checks if tsValue is an infinity timestamp.
"""
## @todo improve this test...
def isDbTimestamp(oValue):
"""
Checks if oValue is a DB timestamp object.
"""
return True;
## @todo detect strings as well.
return False;
def dbTimestampToDatetime(oValue):
"""
Converts a database timestamp to a datetime instance.
"""
return oValue;
raise Exception('TODO');
return oValue.pydatetime();
"""
Converts a database timestamp to a zulu datetime instance.
"""
"""UTC TZ Info Class"""
return "UTC";
return tsValue;
"""
Herolds a database integrity error up the callstack.
Do NOT use directly, only thru TMDatabaseConnection.integrityException.
Otherwise, we won't be able to log the issue.
"""
pass;
class TMDatabaseCursor(object):
""" Cursor wrapper class. """
""" See TMDatabaseConnection.execute()"""
""" See TMDatabaseConnection.callProc()"""
"""Wrapper around Psycopg2.cursor.fetchone."""
"""Wrapper around Psycopg2.cursor.fetchmany."""
"""Wrapper around Psycopg2.cursor.fetchall."""
def getRowCount(self):
"""Wrapper around Psycopg2.cursor.rowcount."""
"""Wrapper around Psycopg2.cursor.mogrify."""
return oRet;
def isTsInfinity(tsValue):
""" Checks if tsValue is an infinity timestamp. """
return isDbTimestampInfinity(tsValue);
class TMDatabaseConnection(object):
"""
Test Manager Database Access class.
This class contains no logic, just raw access abstraction and utilities,
as well as some debug help and some statistics.
"""
"""
Database connection wrapper.
The fnDPrint is for debug logging of all database activity.
Raises an exception on failure.
"""
dArgs = \
{ \
# 'application_name': sAppName, - Darn stale debian! :/
};
if config.g_ksDatabaseAddress is not None:
if config.g_ksDatabasePort is not None:
self._oExplainConn = None;
self._oExplainCursor = None;
self._tsCurrent = None;
self._tsCurrentMinusOne = None;
# Debug and introspection.
self._aoTraceBack = [];
# Exception class handles.
if oSrvGlue is not None:
def isAutoCommitting(self):
""" Work around missing autocommit attribute in older versions."""
"""
Closes the connection and renders all cursors useless.
"""
if self._oExplainCursor is not None:
self._oExplainCursor = None;
if self._oExplainConn is not None:
self._oExplainConn = None;
def _startedTransaction(self):
"""
Called to work the _fTransaction and related variables when starting
a transaction.
"""
self._tsCurrent = None;
self._tsCurrentMinusOne = None;
return None;
def _endedTransaction(self):
"""
Called to work the _fTransaction and related variables when ending
a transaction.
"""
self._tsCurrent = None;
self._tsCurrentMinusOne = None;
return None;
"""
Currently just for marking where a transaction starts in the code.
"""
self._aoTraceBack.append([utils.timestampNano(), 'START TRANSACTION', 0, 0, utils.getCallerName(), None]);
return True;
""" Wrapper around Psycopg2.connection.commit."""
if sCallerName is None:
return oRc;
"""
Commits if fCommit is True.
Returns True if committed, False if not.
"""
return True;
return False;
""" Wrapper around Psycopg2.connection.rollback."""
return oRc;
#
# Internal cursor workers.
#
"""
Execute a query or command.
Mostly a wrapper around the psycopg2 cursor method with the same name,
but collect data for traceback.
"""
if aoArgs is None:
aasExplain = None;
if self._oExplainCursor is not None:
try:
else:
except: pass;
else:
try:
self._aoTraceBack.append([nsStart, 'oXcpt=%s; Statement: %s' % (oXcpt, sBound), cNsElapsed, 0, sCallerName, None]);
raise;
if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ.
if self.isAutoCommitting():
return oRc;
"""
Call a stored procedure.
Mostly a wrapper around the psycopg2 cursor method 'callproc', but
collect data for traceback.
"""
if aoArgs is None:
try:
raise;
if self._fTransaction is False and not self.isAutoCommitting(): # Even SELECTs starts transactions with psycopg2, see FAQ.
self._aoTraceBack.append([nsStart, '%s(%s)' % (sProcedure, aoArgs), cNsElapsed, oCursor.rowcount, sCallerName, None]);
self._fnDPrint('db::callproc %u ns, caller %s: "%s(%s)"' % (cNsElapsed, sCallerName, sProcedure, aoArgs));
if self.isAutoCommitting():
return oRc;
"""Wrapper around Psycopg2.cursor.fetchone."""
return oRow;
"""Wrapper around Psycopg2.cursor.fetchmany."""
"""Wrapper around Psycopg2.cursor.fetchall."""
"""Wrapper around Psycopg2.cursor.rowcount."""
#
# Default cursor access.
#
"""
Execute a query or command.
Mostly a wrapper around the psycopg2 cursor method with the same name,
but collect data for traceback.
"""
"""
Call a stored procedure.
Mostly a wrapper around the psycopg2 cursor method 'callproc', but
collect data for traceback.
"""
"""Wrapper around Psycopg2.cursor.fetchone."""
"""Wrapper around Psycopg2.cursor.fetchmany."""
"""Wrapper around Psycopg2.cursor.fetchall."""
def getRowCount(self):
"""Wrapper around Psycopg2.cursor.rowcount."""
"""Wrapper around Psycopg2.cursor.mogrify."""
return oRet;
def getCurrentTimestamps(self):
"""
Returns the current timestamp and the current timestamp minus one tick.
This will start a transaction if necessary.
"""
if self._tsCurrent is None:
def getCurrentTimestamp(self):
"""
Returns the current timestamp.
This will start a transaction if necessary.
"""
if self._tsCurrent is None:
return self._tsCurrent;
"""
Returns the current timestamp minus one tick.
This will start a transaction if necessary.
"""
if self._tsCurrentMinusOne is None:
return self._tsCurrentMinusOne;
#
# Additional cursors.
#
def openCursor(self):
"""
Opens a new cursor (TMDatabaseCursor).
"""
#
# Utilities.
#
def isTsInfinity(tsValue):
""" Checks if tsValue is an infinity timestamp. """
return isDbTimestampInfinity(tsValue);
#
# Error stuff.
#
"""
Database integrity reporter and exception factory.
Returns an TMDatabaseIntegrityException which the caller can raise.
"""
## @todo Create a new database connection and log the issue in the SystemLog table.
## Alternatively, rollback whatever is going on and do it using the current one.
return TMDatabaseIntegrityException(sMessage);
#
# Debugging.
#
"""
Debug output.
"""
return False;
return True;
"""
Used to get a SQL activity dump as HTML, usually for WuiBase._sDebug.
"""
cNsElapsed = 0;
sDebug = '<h3>SQL Debug Log (total time %s ns):</h3>\n' \
'<table class="tmsqltable">\n' \
' <tr>\n' \
' <th>No.</th>\n' \
' <th>Timestamp (ns)</th>\n' \
' <th>Elapsed (ns)</th>\n' \
' <th>Rows Returned</th>\n' \
' <th>Command</th>\n' \
' <th>Caller</th>\n' \
' </tr>\n' \
iEntry = 0;
iEntry += 1;
sDebug += ' <tr>\n' \
' <td align="right">%s</td>\n' \
' <td align="right">%s</td>\n' \
' <td align="right">%s</td>\n' \
' <td align="right">%s</td>\n' \
' <td><pre>%s</pre></td>\n' \
' <td>%s</td>\n' \
' </tr>\n' \
% (iEntry,
);
if aEntry[5] is not None:
sDebug += ' <tr>\n' \
' <td colspan="6"><pre style="white-space: pre-wrap;">%s</pre></td>\n' \
' </tr>\n' \
sDebug += '</table>';
return sDebug;
"""
Used to get a SQL activity dump as text.
"""
cNsElapsed = 0;
iEntry = 0;
iEntry += 1;
sHdr = 'Query #%s Timestamp: %s ns Elapsed: %s ns Rows: %s Caller: %s' \
% ( iEntry,
aEntry[4], );
sDebug += '\n';
if aEntry[5] is not None:
sDebug += 'Explain:\n' \
' %s\n' \
return sDebug;
""" Called back by the glue code on error. """
return True;
def debugEnableExplain(self):
""" Enabled explain. """
if self._oExplainConn is None:
dArgs = \
{ \
# 'application_name': sAppName, - Darn stale debian! :/
};
if config.g_ksDatabaseAddress is not None:
if config.g_ksDatabasePort is not None:
return True;