X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2FPostgreSQL.py;h=6286a1a6d73b4ac34573542a2e5c16f32b7e5cda;hb=002a2f5c84731b49c88089a5edbea2935d60c826;hp=9eff58499747623a0ef269e5cf67a6ef230e5c5a;hpb=4104c224401c26638b0be7ac0d9447fbbc25cc2c;p=plcapi.git diff --git a/PLC/PostgreSQL.py b/PLC/PostgreSQL.py index 9eff584..6286a1a 100644 --- a/PLC/PostgreSQL.py +++ b/PLC/PostgreSQL.py @@ -5,46 +5,90 @@ # Mark Huang # Copyright (C) 2006 The Trustees of Princeton University # -# $Id: PostgreSQL.py,v 1.1 2006/09/06 15:36:07 mlhuang Exp $ +# $Id: PostgreSQL.py,v 1.10 2006/11/09 19:34:04 mlhuang Exp $ # +import psycopg2 +import psycopg2.extensions +psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) +# UNICODEARRAY not exported yet +psycopg2.extensions.register_type(psycopg2._psycopg.UNICODEARRAY) + import pgdb from types import StringTypes, NoneType import traceback import commands +import re from pprint import pformat from PLC.Debug import profile, log from PLC.Faults import * +if not psycopg2: + is8bit = re.compile("[\x80-\xff]").search + + def unicast(typecast): + """ + pgdb returns raw UTF-8 strings. This function casts strings that + appear to contain non-ASCII characters to unicode objects. + """ + + def wrapper(*args, **kwds): + value = typecast(*args, **kwds) + + # pgdb always encodes unicode objects as UTF-8 regardless of + # the DB encoding (and gives you no option for overriding + # the encoding), so always decode 8-bit objects as UTF-8. + if isinstance(value, str) and is8bit(value): + value = unicode(value, "utf-8") + + return value + + return wrapper + + pgdb.pgdbTypeCache.typecast = unicast(pgdb.pgdbTypeCache.typecast) + class PostgreSQL: def __init__(self, api): self.api = api # Initialize database connection - self.db = pgdb.connect(user = api.config.PLC_DB_USER, - password = api.config.PLC_DB_PASSWORD, - host = "%s:%d" % (api.config.PLC_DB_HOST, api.config.PLC_DB_PORT), - database = api.config.PLC_DB_NAME) + if psycopg2: + try: + # Try UNIX socket first + self.db = psycopg2.connect(user = api.config.PLC_DB_USER, + password = api.config.PLC_DB_PASSWORD, + database = api.config.PLC_DB_NAME) + except psycopg2.OperationalError: + # Fall back on TCP + self.db = psycopg2.connect(user = api.config.PLC_DB_USER, + password = api.config.PLC_DB_PASSWORD, + database = api.config.PLC_DB_NAME, + host = api.config.PLC_DB_HOST, + port = api.config.PLC_DB_PORT) + self.db.set_client_encoding("UNICODE") + else: + self.db = pgdb.connect(user = api.config.PLC_DB_USER, + password = api.config.PLC_DB_PASSWORD, + host = "%s:%d" % (api.config.PLC_DB_HOST, api.config.PLC_DB_PORT), + database = api.config.PLC_DB_NAME) + self.cursor = self.db.cursor() (self.rowcount, self.description, self.lastrowid) = \ (None, None, None) - def quote(self, params): + def quote(self, value): """ - Returns quoted version(s) of the specified parameter(s). + Returns quoted version of the specified value. """ - # pgdb._quote functions are good enough for general SQL quoting - if hasattr(params, 'has_key'): - params = pgdb._quoteitem(params) - elif isinstance(params, list) or isinstance(params, tuple): - params = map(pgdb._quote, params) + # The pgdb._quote function is good enough for general SQL + # quoting, except for array types. + if isinstance(value, (list, tuple, set)): + return "ARRAY[%s]" % ", ".join(map, self.quote, value) else: - params = pgdb._quote(params) - - return params + return pgdb._quote(value) quote = classmethod(quote) @@ -80,8 +124,15 @@ class PostgreSQL: self.execute(query, params) return self.rowcount - def last_insert_id(self): - return self.lastrowid + def last_insert_id(self, table_name, primary_key): + if isinstance(self.lastrowid, int): + sql = "SELECT %s FROM %s WHERE oid = %d" % \ + (primary_key, table_name, self.lastrowid) + rows = self.selectall(sql, hashref = False) + if rows: + return rows[0][0] + + return None def execute(self, query, params = None): self.execute_array(query, (params,)) @@ -89,11 +140,19 @@ class PostgreSQL: def execute_array(self, query, param_seq): cursor = self.cursor try: + # psycopg2 requires %()s format for all parameters, + # regardless of type. + if psycopg2: + query = re.sub(r'(%\([^)]*\)|%)[df]', r'\1s', query) + cursor.executemany(query, param_seq) (self.rowcount, self.description, self.lastrowid) = \ (cursor.rowcount, cursor.description, cursor.lastrowid) - except pgdb.DatabaseError, e: - self.rollback() + except Exception, e: + try: + self.rollback() + except: + pass uuid = commands.getoutput("uuidgen") print >> log, "Database error %s:" % uuid print >> log, e @@ -121,7 +180,7 @@ class PostgreSQL: self.execute(query, params) rows = self.cursor.fetchall() - if hashref: + if hashref or key_field is not None: # Return each row as a dictionary keyed on field name # (like DBI selectrow_hashref()). labels = [column[0] for column in self.description] @@ -134,15 +193,21 @@ class PostgreSQL: else: return rows - def fields(self, table): + def fields(self, table, notnull = None, hasdef = None): """ Return the names of the fields of the specified table. """ - rows = self.selectall("SELECT attname FROM pg_attribute, pg_class" \ - " WHERE pg_class.oid = attrelid" \ - " AND attnum > 0 AND relname = %(table)s", - locals(), - hashref = False) + sql = "SELECT attname FROM pg_attribute, pg_class" \ + " WHERE pg_class.oid = attrelid" \ + " AND attnum > 0 AND relname = %(table)s" + + if notnull is not None: + sql += " AND attnotnull is %(notnull)s" + + if hasdef is not None: + sql += " AND atthasdef is %(hasdef)s" + + rows = self.selectall(sql, locals(), hashref = False) return [row[0] for row in rows]