X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2FPostgreSQL.py;h=965d5f9e7b0fee3807c4cd753a890caa1139fe1b;hb=6d1d60c9c2620e2fd725590a981537ea6f580994;hp=57321f541435445c13bf729689fece77b2871665;hpb=c4b7fddda7252f72f052010ab8878f2025d9e07f;p=plcapi.git diff --git a/PLC/PostgreSQL.py b/PLC/PostgreSQL.py index 57321f5..965d5f9 100644 --- a/PLC/PostgreSQL.py +++ b/PLC/PostgreSQL.py @@ -5,7 +5,8 @@ # Mark Huang # Copyright (C) 2006 The Trustees of Princeton University # -# $Id: PostgreSQL.py,v 1.12 2007/02/08 15:15:21 mlhuang Exp $ +# $Id$ +# $URL$ # import psycopg2 @@ -24,61 +25,46 @@ 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 self.debug = False +# self.debug = True + self.connection = None - # Initialize database connection - if psycopg2: + def cursor(self): + if self.connection is None: + # (Re)initialize database connection 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) + self.connection = psycopg2.connect(user = self.api.config.PLC_DB_USER, + password = self.api.config.PLC_DB_PASSWORD, + database = self.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.connection = psycopg2.connect(user = self.api.config.PLC_DB_USER, + password = self.api.config.PLC_DB_PASSWORD, + database = self.api.config.PLC_DB_NAME, + host = self.api.config.PLC_DB_HOST, + port = self.api.config.PLC_DB_PORT) + self.connection.set_client_encoding("UNICODE") (self.rowcount, self.description, self.lastrowid) = \ (None, None, None) + return self.connection.cursor() + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + + # join insists on getting strings + @classmethod + def quote_string(self, value): + return str(PostgreSQL.quote(value)) + + @classmethod def quote(self, value): """ Returns quoted version of the specified value. @@ -87,12 +73,11 @@ class PostgreSQL: # 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) + return "ARRAY[%s]" % ", ".join(map (PostgreSQL.quote_string, value)) else: return pgdb._quote(value) - quote = classmethod(quote) - + @classmethod def param(self, name, value): # None is converted to the unquoted string NULL if isinstance(value, NoneType): @@ -109,22 +94,30 @@ class PostgreSQL: return '%(' + name + ')' + conversion - param = classmethod(param) - def begin_work(self): # Implicit in pgdb.connect() pass def commit(self): - self.db.commit() + self.connection.commit() def rollback(self): - self.db.rollback() + self.connection.rollback() def do(self, query, params = None): - self.execute(query, params) + cursor = self.execute(query, params) + cursor.close() return self.rowcount + def next_id(self, table_name, primary_key): + sequence = "%(table_name)s_%(primary_key)s_seq" % locals() + sql = "SELECT nextval('%(sequence)s')" % locals() + rows = self.selectall(sql, hashref = False) + if rows: + return rows[0][0] + + return None + def last_insert_id(self, table_name, primary_key): if isinstance(self.lastrowid, int): sql = "SELECT %s FROM %s WHERE oid = %d" % \ @@ -135,31 +128,44 @@ class PostgreSQL: return None + # modified for psycopg2-2.0.7 + # executemany is undefined for SELECT's + # see http://www.python.org/dev/peps/pep-0249/ + # accepts either None, a single dict, a tuple of single dict - in which case it execute's + # or a tuple of several dicts, in which case it executemany's def execute(self, query, params = None): - self.execute_array(query, (params,)) - def execute_array(self, query, param_seq): - cursor = self.cursor + cursor = self.cursor() try: - if self.debug: - for params in param_seq: - if params: - print >> log, query % params - else: - print >> log, query # psycopg2 requires %()s format for all parameters, # regardless of type. + # this needs to be done carefully though as with pattern-based filters + # we might have percents embedded in the query + # so e.g. GetPersons({'email':'*fake*'}) was resulting in .. LIKE '%sake%' if psycopg2: query = re.sub(r'(%\([^)]*\)|%)[df]', r'\1s', query) - - try: - cursor.executemany(query, param_seq) - except InterfaceError: - # Try one more time with another cursor - cursor = self.cursor = self.db.cursor() + # rewrite wildcards set by Filter.py as '***' into '%' + query = query.replace ('***','%') + + if not params: + if self.debug: + print >> log,'execute0',query + cursor.execute(query) + elif isinstance(params,dict): + if self.debug: + print >> log,'execute-dict: params',params,'query',query%params + cursor.execute(query,params) + elif isinstance(params,tuple) and len(params)==1: + if self.debug: + print >> log,'execute-tuple',query%params[0] + cursor.execute(query,params[0]) + else: + param_seq=(params,) + if self.debug: + for params in param_seq: + print >> log,'executemany',query%params cursor.executemany(query, param_seq) - (self.rowcount, self.description, self.lastrowid) = \ (cursor.rowcount, cursor.description, cursor.lastrowid) except Exception, e: @@ -173,12 +179,14 @@ class PostgreSQL: print >> log, "Query:" print >> log, query print >> log, "Params:" - print >> log, pformat(param_seq[0]) + print >> log, pformat(params) raise PLCDBError("Please contact " + \ self.api.config.PLC_NAME + " Support " + \ "<" + self.api.config.PLC_MAIL_SUPPORT_ADDRESS + ">" + \ " and reference " + uuid) + return cursor + def selectall(self, query, params = None, hashref = True, key_field = None): """ Return each row as a dictionary keyed on field name (like DBI @@ -187,13 +195,13 @@ class PostgreSQL: selectall_hashref()). If params is specified, the specified parameters will be bound - to the query (see PLC.DB.parameterize() and - pgdb.cursor.execute()). + to the query. """ - self.execute(query, params) - rows = self.cursor.fetchall() - + cursor = self.execute(query, params) + rows = cursor.fetchall() + cursor.close() + self.commit() if hashref or key_field is not None: # Return each row as a dictionary keyed on field name # (like DBI selectrow_hashref()).