aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/python/pgdb.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/interfaces/python/pgdb.py')
-rw-r--r--src/interfaces/python/pgdb.py401
1 files changed, 401 insertions, 0 deletions
diff --git a/src/interfaces/python/pgdb.py b/src/interfaces/python/pgdb.py
new file mode 100644
index 00000000000..f5859713f03
--- /dev/null
+++ b/src/interfaces/python/pgdb.py
@@ -0,0 +1,401 @@
+""" pgdb - DB-SIG compliant module for PygreSQL.
+
+ (c) 1999, Pascal Andre <andre@via.ecp.fr>.
+ See package documentation for further information on copyright.
+
+ Even though this file is distributed with a release version of
+ PyGreSQL, this is beta software. Inline documentation is sparse.
+ See DB-SIG 2.0 specification for usage information.
+
+ basic usage:
+
+ pgdb.connect(connect_string) -> connection
+ connect_string = 'host:database:user:password:opt:tty'
+ All parts are optional. You may also pass host through
+ password as keyword arguments. To pass a port, pass it in
+ the host keyword parameter:
+ pgdb.connect(host='localhost:5432')
+
+ connection.cursor() -> cursor
+
+ connection.commit()
+
+ connection.close()
+
+ connection.rollback()
+
+ cursor.execute(query[, params])
+ execute a query, binding params (a dictionary) if it is
+ passed. The binding syntax is the same as the % operator
+ for dictionaries, and no quoting is done.
+
+ cursor.executemany(query, list of params)
+ execute a query many times, binding each param dictionary
+ from the list.
+
+ cursor.fetchone() -> [value, value, ...]
+
+ cursor.fetchall() -> [[value, value, ...], ...]
+
+ cursor.fetchmany([size]) -> [[value, value, ...], ...]
+ returns size or cursor.arraysize number of rows from result
+ set. Default cursor.arraysize is 1.
+
+ cursor.description -> [(column_name, type_name, display_size,
+ internal_size, precision, scale, null_ok), ...]
+
+ Note that precision, scale and null_ok are not implemented.
+
+ cursor.rowcount
+ number of rows available in the result set. Available after
+ a call to execute.
+
+ cursor.close()
+
+"""
+
+import _pg
+import string
+import exceptions
+import types
+import DateTime
+import time
+
+### module constants
+
+# compliant with DB SIG 2.0
+apilevel = '2.0'
+
+# module may be shared, but not connections
+threadsafety = 1
+
+# this module use extended python format codes
+paramstyle = 'pyformat'
+
+### exception hierarchy
+
+class Warning(StandardError):
+ pass
+
+class Error(StandardError):
+ pass
+
+class InterfaceError(Error):
+ pass
+
+class DatabaseError(Error):
+ pass
+
+class DataError(DatabaseError):
+ pass
+
+class OperationalError(DatabaseError):
+ pass
+
+class IntegrityError(DatabaseError):
+ pass
+
+class InternalError(DatabaseError):
+ pass
+
+class ProgrammingError(DatabaseError):
+ pass
+
+class NotSupportedError(DatabaseError):
+ pass
+
+### internal type handling class
+class pgdbTypeCache:
+
+ def __init__(self, cnx):
+ self.__source = cnx.source()
+ self.__type_cache = {}
+
+ def typecast(self, typ, value):
+ # for NULL values, no typecast is necessary
+ if value == None:
+ return value
+
+ if typ == STRING:
+ pass
+ elif typ == BINARY:
+ pass
+ elif typ == INTEGER:
+ value = int(value)
+ elif typ == LONG:
+ value = long(value)
+ elif typ == FLOAT:
+ value = float(value)
+ elif typ == MONEY:
+ value = string.replace(value, "$", "")
+ value = string.replace(value, ",", "")
+ value = float(value)
+ elif typ == DATETIME:
+ # format may differ ... we'll give string
+ pass
+ elif typ == ROWID:
+ value = long(value)
+ return value
+
+ def getdescr(self, oid):
+ try:
+ return self.__type_cache[oid]
+ except:
+ self.__source.execute(
+ "SELECT typname, typprtlen, typlen "
+ "FROM pg_type WHERE oid = %s" % oid
+ )
+ res = self.__source.fetch(1)[0]
+ # column name is omitted from the return value. It will
+ # have to be prepended by the caller.
+ res = (
+ res[0],
+ string.atoi(res[1]), string.atoi(res[2]),
+ None, None, None
+ )
+ self.__type_cache[oid] = res
+ return res
+
+### cursor object
+
+class pgdbCursor:
+
+ def __init__(self, src, cache):
+ self.__cache = cache
+ self.__source = src
+ self.description = None
+ self.rowcount = -1
+ self.arraysize = 5
+
+ def close(self):
+ self.__source.close()
+ self.description = None
+ self.rowcount = -1
+
+ def execute(self, operation, params = None):
+ if type(params) == types.TupleType or type(params) == types.ListType:
+ self.executemany(operation, params)
+ else:
+ self.executemany(operation, (params,))
+
+ def executemany(self, operation, param_seq):
+ self.description = None
+ self.rowcount = -1
+
+ # first try to execute all queries
+ totrows = 0
+ sql = "INIT"
+ try:
+ for params in param_seq:
+ if params != None:
+ sql = operation % params
+ else:
+ sql = operation
+ rows = self.__source.execute(sql)
+ if rows != None: # true is __source is NOT a DQL
+ totrows = totrows + rows
+ except _pg.error, msg:
+ raise DatabaseError, "error '%s' in '%s'" % ( msg, sql )
+ except:
+ raise OperationalError, "internal error in '%s'" % sql
+
+ # then initialize result raw count and description
+ if self.__source.resulttype == _pg.RESULT_DQL:
+ self.rowcount = self.__source.ntuples
+ d = []
+ for typ in self.__source.listinfo():
+ # listinfo is a sequence of
+ # (index, column_name, type_oid)
+ # getdescr returns all items needed for a
+ # description tuple except the column_name.
+ desc = typ[1:2]+self.__cache.getdescr(typ[2])
+ d.append(desc)
+ self.description = d
+ else:
+ self.rowcount = totrows
+ self.description = None
+
+ def fetchone(self):
+ res = self.fetchmany(1, 0)
+ try:
+ return res[0]
+ except:
+ return None
+
+ def fetchall(self):
+ return self.fetchmany(-1, 0)
+
+ def fetchmany(self, size = None, keep = 1):
+ if size == None:
+ size = self.arraysize
+ if keep == 1:
+ self.arraysize = size
+ res = self.__source.fetch(size)
+ result = []
+ for r in res:
+ row = []
+ for i in range(len(r)):
+ row.append(self.__cache.typecast(
+ self.description[i][1],
+ r[i]
+ )
+ )
+ result.append(row)
+ return result
+
+ def setinputsizes(self, sizes):
+ pass
+
+ def setoutputsize(self, size, col = 0):
+ pass
+
+### connection object
+
+class pgdbCnx:
+
+ def __init__(self, cnx):
+ self.__cnx = cnx
+ self.__cache = pgdbTypeCache(cnx)
+ try:
+ src = self.__cnx.source()
+ src.execute("BEGIN")
+ except:
+ raise OperationalError, "invalid connection."
+
+ def close(self):
+ self.__cnx.close()
+
+ def commit(self):
+ try:
+ src = self.__cnx.source()
+ src.execute("COMMIT")
+ src.execute("BEGIN")
+ except:
+ raise OperationalError, "can't commit."
+
+ def rollback(self):
+ try:
+ src = self.__cnx.source()
+ src.execute("ROLLBACK")
+ src.execute("BEGIN")
+ except:
+ raise OperationalError, "can't rollback."
+
+ def cursor(self):
+ try:
+ src = self.__cnx.source()
+ return pgdbCursor(src, self.__cache)
+ except:
+ raise pgOperationalError, "invalid connection."
+
+### module interface
+
+# connects to a database
+def connect(dsn = None, user = None, password = None, host = None, database = None):
+ # first get params from DSN
+ dbport = -1
+ dbhost = ""
+ dbbase = ""
+ dbuser = ""
+ dbpasswd = ""
+ dbopt = ""
+ dbtty = ""
+ try:
+ params = string.split(dsn, ":")
+ dbhost = params[0]
+ dbbase = params[1]
+ dbuser = params[2]
+ dbpasswd = params[3]
+ dbopt = params[4]
+ dbtty = params[5]
+ except:
+ pass
+
+ # override if necessary
+ if user != None:
+ dbuser = user
+ if password != None:
+ dbpasswd = password
+ if database != None:
+ dbbase = database
+ if host != None:
+ try:
+ params = string.split(host, ":")
+ dbhost = params[0]
+ dbport = params[1]
+ except:
+ pass
+
+ # empty host is localhost
+ if dbhost == "":
+ dbhost = None
+ if dbuser == "":
+ dbuser = None
+
+ # open the connection
+ cnx = _pg.connect(host = dbhost, dbname = dbbase, port = dbport,
+ opt = dbopt, tty = dbtty,
+ user = dbuser, passwd = dbpasswd)
+ return pgdbCnx(cnx)
+
+### types handling
+
+# PostgreSQL is object-oriented: types are dynamic. We must thus use type names
+# as internal type codes.
+
+class pgdbType:
+
+ def __init__(self, *values):
+ self.values= values
+
+ def __cmp__(self, other):
+ if other in self.values:
+ return 0
+ if other < self.values:
+ return 1
+ else:
+ return -1
+
+STRING = pgdbType(
+ 'char', 'name', 'text', 'varchar'
+)
+
+# BLOB support is pg specific
+BINARY = pgdbType()
+INTEGER = pgdbType('int2', 'int4', 'serial')
+LONG = pgdbType('int8')
+FLOAT = pgdbType('float4', 'float8', 'numeric')
+BOOL = pgdbType('bool')
+MONEY = pgdbType('money')
+
+# this may be problematic as type are quite different ... I hope it won't hurt
+DATETIME = pgdbType(
+ 'abstime', 'reltime', 'tinterval', 'date', 'time', 'timespan', 'timestamp'
+)
+
+# OIDs are used for everything (types, tables, BLOBs, rows, ...). This may cause
+# confusion, but we are unable to find out what exactly is behind the OID (at
+# least not easily enough). Should this be undefined as BLOBs ?
+ROWID = pgdbType(
+ 'oid', 'oid8'
+)
+
+# mandatory type helpers
+def Date(year, month, day):
+ return DateTime.DateTime(year, month, day)
+
+def Time(hour, minute, second):
+ return DateTime.TimeDelta(hour, minute, second)
+
+def Timestamp(year, month, day, hour, minute, second):
+ return DateTime.DateTime(year, month, day, hour, minute, second)
+
+def DateFromTicks(ticks):
+ return apply(Date, time.localtime(ticks)[:3])
+
+def TimeFromTicks(ticks):
+ return apply(Time, time.localtime(ticks)[3:6])
+
+def TimestampFromTicks(ticks):
+ return apply(Timestamp, time.localtime(ticks)[:6])
+