diff --git a/MySQLdb/MySQLdb/__init__.py b/MySQLdb/MySQLdb/__init__.py index 192a5f7..b5b3645 100644 --- a/MySQLdb/MySQLdb/__init__.py +++ b/MySQLdb/MySQLdb/__init__.py @@ -20,7 +20,7 @@ version_info = ( 9, 0, "beta", - 1) + 2) if version_info[3] == "final": __version__ = "%d.%d.%d" % version_info[:3] else: __version__ = "%d.%d.%d%1.1s%d" % version_info[:5] @@ -31,46 +31,28 @@ if __version__ != getattr(_mysql, '__version__', None): (__version__, _mysql.__version__) -threadsafety = 2 +threadsafety = 1 apilevel = "2.0" paramstyle = "format" - -class DBAPITypeObject: - - """Helper class for determining column types; required by DB API.""" - - 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 - - -_Set = DBAPITypeObject -from constants import FIELD_TYPE - -STRING = _Set(FIELD_TYPE.CHAR, FIELD_TYPE.ENUM, FIELD_TYPE.INTERVAL, - FIELD_TYPE.SET, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING) -BINARY = _Set(FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.MEDIUM_BLOB, - FIELD_TYPE.TINY_BLOB) -NUMBER = _Set(FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, - FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, - FIELD_TYPE.TINY, FIELD_TYPE.YEAR) -DATE = _Set(FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE) -TIME = _Set(FIELD_TYPE.TIME) -TIMESTAMP = _Set(FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME) -ROWID = _Set() - -def Binary(x): return str(x) - from _mysql import * from connections import Connection +from converters import * +from constants import FIELD_TYPE + +STRING = Set(FIELD_TYPE.CHAR, FIELD_TYPE.ENUM, FIELD_TYPE.STRING, + FIELD_TYPE.VAR_STRING) +BINARY = Set(FIELD_TYPE.BLOB, FIELD_TYPE.LONG_BLOB, FIELD_TYPE.MEDIUM_BLOB, + FIELD_TYPE.TINY_BLOB) +NUMBER = Set(FIELD_TYPE.DECIMAL, FIELD_TYPE.DOUBLE, FIELD_TYPE.FLOAT, + FIELD_TYPE.INT24, FIELD_TYPE.LONG, FIELD_TYPE.LONGLONG, + FIELD_TYPE.TINY, FIELD_TYPE.YEAR) +DATE = Set(FIELD_TYPE.DATE, FIELD_TYPE.NEWDATE) +TIME = Set(FIELD_TYPE.TIME) +TIMESTAMP = Set(FIELD_TYPE.TIMESTAMP, FIELD_TYPE.DATETIME) +ROWID = Set() + +def Binary(x): return str(x) def Connect(*args, **kwargs): """Factory function for connections.Connection.""" @@ -78,5 +60,17 @@ def Connect(*args, **kwargs): connect = Connect -__all__ = ['BINARY', 'Binary', 'Connect', 'Connection', 'DATE', 'DataError', 'DatabaseError', 'Error', 'FIELD_TYPE', 'IntegrityError', 'InterfaceError', 'InternalError', 'MySQLError', 'NULL', 'NUMBER', 'NotSupportedError', 'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME', 'TIMESTAMP', 'Warning', 'apilevel', 'connect', 'connections', 'constants', 'cursors', 'debug', 'escape', 'escape_dict', 'escape_sequence', 'escape_string', 'get_client_info', 'paramstyle', 'string_literal', 'threadsafety', 'version_info'] +__all__ = [ 'BINARY', 'Binary', 'Connect', 'Connection', 'DATE', + 'Date', 'Time', 'Timestamp', 'DateFromTicks', 'TimeFromTicks', + 'TimestampFromTicks', 'DataError', 'DatabaseError', 'Error', + 'FIELD_TYPE', 'IntegrityError', 'InterfaceError', 'InternalError', + 'MySQLError', 'NULL', 'NUMBER', 'NotSupportedError', + 'OperationalError', 'ProgrammingError', 'ROWID', 'STRING', 'TIME', + 'TIMESTAMP', 'Set', 'Warning', 'apilevel', 'connect', 'connections', + 'constants', 'cursors', 'debug', 'escape', 'escape_dict', + 'escape_sequence', 'escape_string', 'get_client_info', + 'paramstyle', 'string_literal', 'threadsafety', 'version_info'] + + + diff --git a/MySQLdb/MySQLdb/connections.py b/MySQLdb/MySQLdb/connections.py index dc04054..e6cf2a0 100644 --- a/MySQLdb/MySQLdb/connections.py +++ b/MySQLdb/MySQLdb/connections.py @@ -33,8 +33,6 @@ class Connection: read_default_group -- see the MySQL documentation for mysql_options() cursorclass -- class object, used to create cursors or cursors.Cursor. This parameter MUST be specified as a keyword parameter. - threads -- boolean, if false threading is disabled, otherwise threads - are enabled by default. (MUST be a keyword parameter.) Returns a Connection object. @@ -50,9 +48,6 @@ class Connection: from converters import conversions import types kwargs2 = kwargs.copy() - self.__threads = kwargs.get('threads',1) - if kwargs.has_key('threads'): - del kwargs2['threads'] if not kwargs.has_key('conv'): kwargs2['conv'] = conversions.copy() if kwargs.has_key('cursorclass'): @@ -64,69 +59,9 @@ class Connection: self._db.converter[types.StringType] = self._db.string_literal self._transactional = self._db.server_capabilities & CLIENT.TRANSACTIONS self._autocommit = 1 - if self.__threads: - # __c_lock: connection lock. Cursors must always obtain the - # connection lock before doing any queries. A blocking - # call is used. If the same thread tries to acquire the - # lock, produce an error. - # - # _t_lock: transaction lock. If operating transactionally, - # Cursors must acquire the transaction lock on the first - # query. The same thread may acquire the lock more than - # once. commit() or rollback() or an error releases this - # lock. - import threading - self.__c_lock = threading.Lock() - self.__c_locker = None - self.__t_lock = threading.Lock() - self.__t_locker = None def __del__(self): - self.close() - - def _begin(self): - """Obtain the transaction lock. A thread may try to obtain this - lock multiple times.""" - if not self.__threads: return - import threading - me = threading.currentThread() - if self.__t_locker == me: return - self.__t_lock.acquire() - self.__t_locker = me - - def _end(self): - """Release the transaction lock. If a thread tries to release this - lock when it is not currently locking it, it does nothing.""" - if not self.__threads: return - import threading - me = threading.currentThread() - if self.__t_locker != me: return - self.__t_locker = None - self.__t_lock.release() - - def _acquire(self): - """Acquire the connection. ProgrammingError is raised if the - thread has already acquired the connection.""" - if not self.__threads: return - import threading - me = threading.currentThread() - if self.__c_locker == me: - raise ProgrammingError, "would produce deadlock" - self.__c_lock.acquire() - self.__c_locker = me - - def _release(self): - """Release the connection. If a thread tries to release this - lock when it is not currently locking it, ProgrammingError - is raised (this shouldn't happen).""" - if not self.__threads: return - import threading - me = threading.currentThread() - if self.__c_locker != me: - if not self.__c_locker: return - raise ProgrammingError, "tried to release another %s's lock" % self.__c_locker - self.__c_locker = None - self.__c_lock.release() + if hasattr(self, '_db'): self.close() def close(self): """Close the connection. No further activity possible.""" @@ -149,7 +84,6 @@ class Connection: if self._transactional: self._db.query("COMMIT") finally: - self._end() self._transactional = not self._autocommit def rollback(self): @@ -160,7 +94,6 @@ class Connection: else: raise NotSupportedError, "Not supported by server" finally: - self._end() self._transactional = not self._autocommit def cursor(self, cursorclass=None): diff --git a/MySQLdb/MySQLdb/converters.py b/MySQLdb/MySQLdb/converters.py index 8f8a044..218eb7b 100644 --- a/MySQLdb/MySQLdb/converters.py +++ b/MySQLdb/MySQLdb/converters.py @@ -27,9 +27,15 @@ MySQL.connect(). from _mysql import string_literal, escape_sequence, escape_dict, escape, NULL from constants import FIELD_TYPE -from time import localtime, strftime +from data import * +from string import split import types + +def Str2Set(s): + values = split(s, ',') + return apply(Set, tuple(values)) + def Thing2Str(s, d): """Convert something into a string via str().""" return str(s) @@ -63,8 +69,7 @@ def Instance2Str(o, d): converter. If the exact class is not found in d, it will use the first class it can find for which o is an instance.""" - if d.has_key(o.__class__): return - d[o.__class__](o, d) + if d.has_key(o.__class__): return d[o.__class__](o, d) cl = filter(lambda x,o=o: type(x)==types.ClassType and isinstance(o,x), d.keys()) if not cl: @@ -82,6 +87,8 @@ conversions = { types.DictType: escape_dict, types.InstanceType: Instance2Str, types.StringType: Thing2Literal, # default + DateTimeType: DateTime2literal, + DateTimeDeltaType: DateTimeDelta2literal, FIELD_TYPE.TINY: int, FIELD_TYPE.SHORT: int, FIELD_TYPE.LONG: long, @@ -89,99 +96,11 @@ conversions = { FIELD_TYPE.DOUBLE: float, FIELD_TYPE.LONGLONG: long, FIELD_TYPE.INT24: int, - FIELD_TYPE.YEAR: int + FIELD_TYPE.YEAR: int, + FIELD_TYPE.SET: Str2Set, + FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter, + FIELD_TYPE.DATETIME: DateTime_or_None, + FIELD_TYPE.TIME: TimeDelta_or_None, + FIELD_TYPE.DATE: Date_or_None, } -try: - try: - # new packaging - from mx.DateTime import Date, Time, Timestamp, ISO, \ - DateTimeType, DateTimeDeltaType - except ImportError: - # old packaging, deprecated - from DateTime import Date, Time, Timestamp, ISO, \ - DateTimeType, DateTimeDeltaType - - def DateFromTicks(ticks): - """Convert UNIX ticks into a mx.DateTime.Date.""" - return apply(Date, localtime(ticks)[:3]) - - def TimeFromTicks(ticks): - """Convert UNIX ticks into a mx.DateTime.Time.""" - return apply(Time, localtime(ticks)[3:6]) - - def TimestampFromTicks(ticks): - """Convert UNIX ticks into a mx.DateTime.Timestamp.""" - return apply(Timestamp, localtime(ticks)[:6]) - - def format_DATE(d): - """Format a DateTime object as an ISO date.""" - return d.strftime("%Y-%m-%d") - - def format_TIME(d): - """Format a DateTime object as a time value.""" - return d.strftime("%H:%M:%S") - - def format_TIMESTAMP(d): - """Format a DateTime object as an ISO timestamp.""" - return d.strftime("%Y-%m-%d %H:%M:%S") - - def mysql_timestamp_converter(s): - """Convert a MySQL TIMESTAMP to a mx.DateTime.Timestamp.""" - parts = map(int, filter(None, (s[:4],s[4:6],s[6:8], - s[8:10],s[10:12],s[12:14]))) - try: return apply(Timestamp, tuple(parts)) - except: return None - - def DateTime_or_None(s): - try: return ISO.ParseDateTime(s) - except: return None - - def TimeDelta_or_None(s): - try: return ISO.ParseTimeDelta(s) - except: return None - - def Date_or_None(s): - try: return ISO.ParseDate(s) - except: return None - - conversions[FIELD_TYPE.TIMESTAMP] = mysql_timestamp_converter - conversions[FIELD_TYPE.DATETIME] = DateTime_or_None - conversions[FIELD_TYPE.TIME] = TimeDelta_or_None - conversions[FIELD_TYPE.DATE] = Date_or_None - - def DateTime2literal(d, c): - """Format a DateTime object as an ISO timestamp.""" - return escape(format_TIMESTAMP(d),c) - - def DateTimeDelta2literal(d, c): - """Format a DateTimeDelta object as a time.""" - return escape(format_TIME(d),c) - - conversions[DateTimeType] = DateTime2literal - conversions[DateTimeDeltaType] = DateTimeDelta2literal - -except ImportError: - # no DateTime? We'll muddle through somehow. - - def DateFromTicks(ticks): - """Convert UNIX ticks to ISO date format.""" - return strftime("%Y-%m-%d", localtime(ticks)) - - def TimeFromTicks(ticks): - """Convert UNIX ticks to time format.""" - return strftime("%H:%M:%S", localtime(ticks)) - - def TimestampFromTicks(ticks): - """Convert UNIX ticks to ISO timestamp format.""" - return strftime("%Y-%m-%d %H:%M:%S", localtime(ticks)) - - def format_DATE(d): - """Format a date as a date (does nothing, you don't have mx.DateTime).""" - return d - - format_TIME = format_TIMESTAMP = format_DATE - -__all__ = [ 'conversions', 'DateFromTicks', 'TimeFromTicks', - 'TimestampFromTicks', 'format_DATE', 'format_TIME', - 'format_TIMESTAMP' ] diff --git a/MySQLdb/MySQLdb/cursors.py b/MySQLdb/MySQLdb/cursors.py index f250190..49c4a90 100644 --- a/MySQLdb/MySQLdb/cursors.py +++ b/MySQLdb/MySQLdb/cursors.py @@ -24,8 +24,6 @@ class BaseCursor: self.rowcount = -1 self.arraysize = 100 self._executed = None - self._transaction = 0 - self.__c_locked = 0 def __del__(self): self.close() @@ -33,10 +31,7 @@ class BaseCursor: def close(self): """Close the cursor. No further queries will be possible.""" if not self.__conn: return - self._end() self.__conn = None - self._executed = None - self._transaction = None def _check_executed(self): if not self._executed: @@ -58,12 +53,12 @@ class BaseCursor: """Execute a query. query -- string, query to execute on server - args -- sequence or mapping, parameters to use with query. + args -- optional sequence or mapping, parameters to use with query. returns long integer rows affected, if any""" from types import ListType, TupleType qc = self._get_db().converter - if not args: + if args is None: r = self._query(query) elif type(args) is ListType and type(args[0]) is TupleType: r = self.executemany(query, args) # deprecated @@ -105,7 +100,7 @@ class BaseCursor: except TypeError, msg: if msg.args[0] in ("not enough arguments for format string", "not all arguments converted"): - raise ProgrammingError, (0, msg.args[0]) + raise ProgrammingError, msg.args[0] else: raise r = self._query(join(q,',\n')) @@ -113,20 +108,16 @@ class BaseCursor: return r def __do_query(self, q): + from string import split, atoi db = self._get_db() - if self._transaction: self._begin() - try: - db.query(q) - self._result = self._get_result() - self.rowcount = db.affected_rows() - self.description = self._result and self._result.describe() or None - self._insert_id = db.insert_id() - self._info = db.info() - self._check_for_warnings() - except: - self._end() - raise + db.query(q) + self._result = self._get_result() + self.rowcount = db.affected_rows() + self.description = self._result and self._result.describe() or None + self._insert_id = db.insert_id() + self._info = db.info() + self._check_for_warnings() return self.rowcount def _check_for_warnings(self): pass @@ -143,35 +134,9 @@ class BaseCursor: self._check_executed() return self._insert_id - def nextset(self): - """Does nothing. Required by DB API.""" - self._check_executed() - return None - def _fetch_row(self, size=1): return self._result.fetch_row(size, self._fetch_type) - def _begin(self): - self.__conn._begin() - self._transaction = 1 - - def _end(self): - self._transaction = 0 - self.__conn._end() - - def _acquire(self): - if self.__c_locked: return - self.__conn._acquire() - self.__c_locked = 1 - - def _release(self): - if not self.__conn: return - self.__conn._release() - self.__c_locked = 0 - - def _is_transactional(self): - return self.__conn._transactional - class CursorWarningMixIn: @@ -194,10 +159,6 @@ class CursorStoreResultMixIn: result set can be very large, consider adding a LIMIT clause to your query, or using CursorUseResultMixIn instead.""" - def __init__(self, connection): - BaseCursor.__init__(self, connection) - self._acquire() - def _get_result(self): return self._get_db().store_result() def close(self): @@ -206,16 +167,11 @@ class CursorStoreResultMixIn: BaseCursor.close(self) def _query(self, q): - self._acquire() - try: - rowcount = self._BaseCursor__do_query(q) - self._rows = self._result and self._fetch_row(0) or () - self._pos = 0 - del self._result - if not self._is_transactional: self._end() - return rowcount - finally: - self._release() + rowcount = self._BaseCursor__do_query(q) + self._rows = self._result and self._fetch_row(0) or () + self._pos = 0 + del self._result + return rowcount def fetchone(self): """Fetches a single row from the cursor.""" @@ -269,7 +225,6 @@ class CursorUseResultMixIn: def close(self): """Close the cursor. No further queries can be executed.""" - self._release() self._result = None BaseCursor.close(self) @@ -279,19 +234,22 @@ class CursorUseResultMixIn: """Fetches a single row from the cursor.""" self._check_executed() r = self._fetch_row(1) - return r and r[0] or None + if r: return r[0] + return None def fetchmany(self, size=None): """Fetch up to size rows from the cursor. Result set may be smaller than size. If size is not defined, cursor.arraysize is used.""" self._check_executed() - return self._fetch_row(size or self.arraysize) + r = self._fetch_row(size or self.arraysize) + return r def fetchall(self): """Fetchs all available rows from the cursor.""" - self._check_open() - return self._fetch_row(0) - + self._check_executed() + r = self._fetch_row(0) + return r + class CursorTupleRowsMixIn: diff --git a/MySQLdb/MySQLdb/data.py b/MySQLdb/MySQLdb/data.py new file mode 100644 index 0000000..876faa3 --- /dev/null +++ b/MySQLdb/MySQLdb/data.py @@ -0,0 +1,186 @@ +"""data module + +This module provides some useful classes for dealing with MySQL data. +""" + +from time import strftime, localtime +from _mysql import string_literal + +class Set: + + """A simple class for handling sets. Sets are immutable in the same + way numbers are.""" + + def __init__(self, *values): + """Use values to initialize the Set.""" + self._values = values + + def contains(self, value): + """Returns true if the value is contained within the Set.""" + return value in self._values + + def __str__(self): + """Returns the values as a comma-separated string.""" + from string import join + return join(map(str, self._values),',') + + def __repr__(self): + return "Set%s" % `self._values` + + def __add__(self, other): + """Union.""" + if isinstance(other, Set): + for v in other._values: + self = self + v + elif other not in self._values: + self = apply(Set, self._values+(other,)) + return self + + def __sub__(self, other): + if isinstance(other, Set): + for v in other._values: + if v in self: + self = self - v + elif other in self: + values = list(self._values) + values.remove(other) + return apply(Set, tuple(values)) + return self + + def __mul__(self, other): + "Intersection." + intersection = Set() + if isinstance(other, Set): + union = self + other + intersection = union + for v in union._values: + if v not in self or v not in other: + intersection = intersection - v + elif other in self: + intersection = apply(Set, (other,)) + return intersection + + def __mod__(self, other): + "Disjoint." + return (self+other)-(self*other) + + def __getitem__(self, n): + return self._values[n] + + def __len__(self): + return len(self._values) + + def __hash__(self): + return hash(self._values) + + def __cmp__(self, other): + if isinstance(other, Set): + d = self % other + if d._values: + return 1 + else: + return 0 + if other in self._values: + return 0 + return -1 + +try: + try: + # new packaging + from mx.DateTime import Date, Time, Timestamp, ISO, \ + DateTimeType, DateTimeDeltaType + except ImportError: + # old packaging, deprecated + from DateTime import Date, Time, Timestamp, ISO, \ + DateTimeType, DateTimeDeltaType + + def DateFromTicks(ticks): + """Convert UNIX ticks into a mx.DateTime.Date.""" + return apply(Date, localtime(ticks)[:3]) + + def TimeFromTicks(ticks): + """Convert UNIX ticks into a mx.DateTime.Time.""" + return apply(Time, localtime(ticks)[3:6]) + + def TimestampFromTicks(ticks): + """Convert UNIX ticks into a mx.DateTime.Timestamp.""" + return apply(Timestamp, localtime(ticks)[:6]) + + def format_DATE(d): + """Format a DateTime object as an ISO date.""" + return d.strftime("%Y-%m-%d") + + def format_TIME(d): + """Format a DateTime object as a time value.""" + return d.strftime("%H:%M:%S") + + def format_TIMESTAMP(d): + """Format a DateTime object as an ISO timestamp.""" + return d.strftime("%Y-%m-%d %H:%M:%S") + + def DateTime_or_None(s): + try: return ISO.ParseDateTime(s) + except: return None + + def TimeDelta_or_None(s): + try: return ISO.ParseTimeDelta(s) + except: return None + + def Date_or_None(s): + try: return ISO.ParseDate(s) + except: return None + + +except ImportError: + # no DateTime? We'll muddle through somehow. + + DateTimeDeltaType = "DateTimeDeltaType" + DateTimeType = "DateTimeType" + + def Date(year, month, day): + """Construct an ISO date string.""" + return "%04d-%02d-%02d" % (year, month, day) + + def Time(hour, min, sec): + """Construct a TIME string.""" + return "%02d:%02d:%02d" % (hour, min, sec) + + def Timestamp(year, month, day, hour, min, sec): + """Construct an ISO timestamp.""" + return "%04d-%02d-%02d %02d:%02d:%02d" % \ + (year, month, day, hour, min, sec) + + def DateFromTicks(ticks): + """Convert UNIX ticks to ISO date format.""" + return strftime("%Y-%m-%d", localtime(ticks)) + + def TimeFromTicks(ticks): + """Convert UNIX ticks to time format.""" + return strftime("%H:%M:%S", localtime(ticks)) + + def TimestampFromTicks(ticks): + """Convert UNIX ticks to ISO timestamp format.""" + return strftime("%Y-%m-%d %H:%M:%S", localtime(ticks)) + + def format_DATE(d): + """Format a date as a date (does nothing, you don't have mx.DateTime).""" + return d + + format_TIME = format_TIMESTAMP = format_DATE = \ + TimeDelta_or_None = Date_or_None + +def DateTime2literal(d, c): + """Format a DateTime object as an ISO timestamp.""" + return string_literal(format_TIMESTAMP(d),c) + +def DateTimeDelta2literal(d, c): + """Format a DateTimeDelta object as a time.""" + return string_literal(format_TIME(d),c) + +def mysql_timestamp_converter(s): + """Convert a MySQL TIMESTAMP to a Timestamp object.""" + s = s + "0"*(14-len(s)) # padding + parts = map(int, filter(None, (s[:4],s[4:6],s[6:8], + s[8:10],s[10:12],s[12:14]))) + try: return apply(Timestamp, tuple(parts)) + except: return None diff --git a/MySQLdb/_mysql_version.h b/MySQLdb/_mysql_version.h index 45cf1f2..3a39f85 100644 --- a/MySQLdb/_mysql_version.h +++ b/MySQLdb/_mysql_version.h @@ -1 +1 @@ -static char _mysql__version__[] = "0.9.0b1"; +static char _mysql__version__[] = "0.9.0b2"; diff --git a/MySQLdb/doc/MySQLdb.sgml b/MySQLdb/doc/MySQLdb.sgml index ae8861d..1663f0f 100644 --- a/MySQLdb/doc/MySQLdb.sgml +++ b/MySQLdb/doc/MySQLdb.sgml @@ -15,8 +15,7 @@ others. However, the older version is a) not thread-friendly (database operations could cause all other threads to block), b) written for MySQL 3.21 (does not compile against newer versions without patches), c) apparently not actively maintained. MySQLdb is a completely new -module, distributed free of charge under a license derived from the -Python license. +module, distributed free of charge under the GNU Public License.
If you have the