diff --git a/mysql/CompatMysqldb.py b/mysql/CompatMysqldb.py new file mode 100755 index 0000000..fb3ec85 --- /dev/null +++ b/mysql/CompatMysqldb.py @@ -0,0 +1,306 @@ +#author: James Henstridge +#adapted to _mysql by Andy Dustman + +"""This is a class that implements an interface to mySQL databases, conforming +to the API published by the Python db-sig at +http://www.python.org/sigs/db-sig/DatabaseAPI.html + +It is really just a wrapper for an older python interface to mySQL databases +called mySQL, which I modified to facilitate use of a cursor. That module was +Joseph Skinner's port of the mSQL module by David Gibson, which was a modified +version of Anthony Baxter's msql module. + +As an example, to add some extra (unprivelledged) users to your database system, +and delete them again: + +>>> import Mysqldb +>>> conn = Mysqldb.mysqldb('mysql@localhost root rootpasswd') +>>> curs = conn.cursor() +>>> curs.execute("insert into user (host, user) values ('%s', '%s')", +... [('localhost', 'linus'), ('somewhere.com.au', 'james')]) +2 +>>> curs.execute("select * from user") +>>> curs.fetchall() + -- record listing -- +>>> curs.execute("delete from user where host = 'somewhere.com.au' or user = 'linus'") +2 +>>> curs.close() +>>> conn.close() + +The argument to mysqldb.mysqldb is of the form 'db@host user pass', +'db@host user', 'db@host', 'db', 'db user pass' or 'db user'. + +As always, the source is a good manual :-) + +James Henstridge +""" + +import _mysql +MySQL = _mysql +from string import upper, split, join + +error = 'mysqldb.error' + +_type = {} +for a in ('char', 'varchar', 'string', 'unhandled', '????'): + _type[a] = 'STRING' +for a in ('tiny blob', 'medium blob', 'long blob', 'blob'): + _type[a] = 'RAW' +for a in ('short', 'long', 'float', 'double', 'decimal'): + _type[a] = 'NUMBER' +for a in ('date', 'time', 'datetime', 'timestamp'): + _type[a] = 'DATE' +del a + +from _mysql import FIELD_TYPE +_type_conv = { FIELD_TYPE.TINY: int, + FIELD_TYPE.SHORT: int, + FIELD_TYPE.LONG: int, + FIELD_TYPE.FLOAT: float, + FIELD_TYPE.DOUBLE: float, + FIELD_TYPE.LONGLONG: long, + FIELD_TYPE.INT24: int, + FIELD_TYPE.YEAR: int } + +def isDDL(q): + return upper(split(q)[0]) in ('CREATE', 'ALTER', 'GRANT', 'REVOKE', + 'DROP', 'SET') +def isDML(q): + return upper(split(q)[0]) in ('DELETE', 'INSERT', 'UPDATE', 'LOAD') +def isDQL(q): + return upper(split(q)[0]) in ('SELECT', 'SHOW', 'DESC', 'DESCRIBE') + +class Connection: + """This is the connection object for the mySQL database interface.""" + def __init__(self, host, user, passwd, db): + kwargs = {} + kwargs['conv'] = _type_conv + if host: kwargs['host'] = host + if user: kwargs['user'] = user + if passwd: kwargs['passwd'] = passwd + if db: kwargs['db'] = db + try: + self.__conn = apply(MySQL.connect, (), kwargs) + except MySQL.Error, msg: + raise error, msg + self.__curs = Cursor(self.__conn) + self._server_info = i = self.__conn.get_server_info() + self._server_version = int(i[0])*10000 + int(i[2:4])*100 + int(i[5:7]) + + + def __del__(self): + self.close() + + def __getattr__(self, key): + return getattr(self.__curs, key) + + def __setattr__(self, key, val): + if key in ('arraysize', 'description', 'insert_id'): + setattr(self.__curs, key, val) + else: + self.__dict__[key] = val + + def close(self): + self.__conn = None + + def cursor(self): + if self.__conn == None: raise error, "Connection is closed." + return Cursor(self.__conn) + + def commit(self): + """Commit the current transaction.""" + if self._server_version > 32315: self.__conn.query("COMMIT") + + def rollback(self): + """Rollback the current transaction.""" + if self._server_version > 32315: self.__conn.query("ROLLBACK") + else: raise error, "Not supported by server" + + + def callproc(self, params=None): pass + + # These functions are just here so that every action that is + # covered by mySQL is covered by mysqldb. They are not standard + # DB API. The list* methods are not included, since they can be + # done with the SQL SHOW command. + def create(self, dbname): + """This is not a standard part of Python DB API.""" + self.__conn.query("CREATE DATABASE %s" % dbname) + self.__conn.store_result() + return None + def drop(self, dbname): + """This is not a standard part of Python DB API.""" + self.__conn.query("DROP DATABASE %s" % dbname) + self.__conn.store_result() + return None + def reload(self): + """This is not a standard part of Python DB API.""" + self.__conn.query("RELOAD TABLES") + self.__conn.store_result() + return None + def shutdown(self): + """This is not a standard part of Python DB API.""" + return self.__conn.shutdown() + + +class Cursor: + """A cursor object for use with connecting to mySQL databases.""" + def __init__(self, conn): + self.__conn = conn + self.__res = None + self.arraysize = 1 + self.__dict__['description'] = None + self.__open = 1 + self.insert_id = 0 + + def __del__(self): + self.close() + + def __setattr__(self, key, val): + if key == 'description': + raise error, "description is a read-only attribute." + else: + self.__dict__[key] = val + + def __delattr__(self, key): + if key in ('description', 'arraysize', 'insert_id'): + raise error, "%s can't be deleted." % (key,) + else: + del self.__dict__[key] + + def close(self): + self.__conn = None + self.__res = None + self.__open = 0 + + def execute(self, op, params=None): + if not self.__open: raise error, "Cursor has been closed." + if params: + if type(params[0]) not in (type(()), type([])): + params = [params] + if isDDL(op): + self.__dict__['description'] = None + try: + for x in params: + self.__res = \ + self.__conn.query(op % x) + self.insert_id = self.__res.insert_id() + except MySQL.Error, msg: + raise error, msg + return 1 + if isDML(op): + self.__dict__['description'] = None + af = 0 + try: + for x in params: + self.__res = \ + self.__conn.query(op % x) + af =af+self.__res.affectedrows() + self.insert_id = self.__res.insert_id() + except MySQL.Error, msg: + raise error, msg + return af + if isDQL(op): + try: + self.__res = self.__conn.query( + op % params[-1]) + self.insert_id = self.__res.insert_id() + f = self.__res.fields() + except MySQL.Error, msg: + raise error, msg + self.__dict__['description'] = tuple(map( + lambda x: (x[0], _type[x[2]], x[3], + x[3]), f)) + return None + else: + try: + self.__conn.query(op) + self.__res = self.__conn.store_result() + self.insert_id = self.__conn.insert_id() + except MySQL.Error, msg: + raise error, msg + self.__dict__['description'] = None + if isDDL(op): + return 1 + elif self.__conn.affected_rows() != -1: + return self.__conn.affected_rows() + else: + self.__dict__['description'] = self.__res.describe() + return None + def fetchone(self): + if not self.__res: raise error, "no query made yet." + try: + return self.__res.fetch_row(1)[0] + except MySQL.Error, msg: + raise error, msg + + def fetchmany(self, size=None): + if not self.__res: raise error, "no query made yet." + try: + return self.__res.fetch_row(size or self.arraysize) + except MySQL.Error, msg: + raise error, msg + + def fetchall(self): + if not self.__res: raise error, "no query made yet." + try: + rows = r = list(self.__res.fetch_row(self.arraysize)) + while 1: + rows = list(self.__res.fetch_row(self.arraysize)) + if not rows: break + r.extend(rows) + return r + except MySQL.Error, msg: + raise error, msg + + + + def fetchoneDict(self): + """This is not a standard part of Python DB API.""" + if not self.__res: raise error, "no query made yet." + try: + return self.__res.fetch_row(1, 2)[0] + except MySQL.Error, msg: + raise error, msg + + def fetchmanyDict(self, size=None): + """This is not a standard part of Python DB API.""" + if not self.__res: raise error, "no query made yet." + try: + return self.__res.fetch_row(size or self.arraysize, 2) + except MySQL.Error, msg: + raise error, msg + + def fetchallDict(self): + """This is not a standard part of Python DB API.""" + if not self.__res: raise error, "no query made yet." + try: + rows = r = list(self.__res.fetch_row(self.arraysize, 2)) + while 1: + rows = list(self.__res.fetch_row(self.arraysize, 2)) + if not rows: break + r.extend(rows) + return r + except MySQL.Error, msg: + raise error, msg + + + + def setinputsizes(self, sizes): pass + def setoutputsize(self, size, col=None): pass + + +def mysqldb(connect_string): + """Makes a connection to the MySQL server. The Argument should be of + the form 'db@host user pass' or 'db@host user' or 'db@host' or 'db' + or 'db user pass' or 'db user', where db is the database name, host + is the server's host name, user is your user name, and pass is your + password.""" + val = split(connect_string) + if len(val) == 0: raise error, "no database specified" + while len(val) < 3: val.append('') + dh = split(val[0], '@') + if len(dh) == 0: raise error, "no database specified" + while len(dh) < 2: dh.append('') + return Connection(dh[1], val[1], val[2], dh[0]) + diff --git a/mysql/MySQL-python.spec b/mysql/MySQL-python.spec index 2def822..c04c61c 100644 --- a/mysql/MySQL-python.spec +++ b/mysql/MySQL-python.spec @@ -1,5 +1,5 @@ %define ver 0.2.2 -%define rel 1 +%define rel 3 Summary: Python interface to MySQL-3.22 and 3.23 Name: MySQL-python Version: %ver @@ -56,7 +56,7 @@ cp MySQLdb.py{,c,o} $RPM_BUILD_ROOT/usr/lib/python1.5/site-packages %clean %files %defattr(-, root, root) -%doc license.py examples/* doc/* +%doc license.py examples/* doc/* CompatMysqldb.py /usr/lib/python1.5/site-packages/MySQLdb.py /usr/lib/python1.5/site-packages/MySQLdb.pyc /usr/lib/python1.5/site-packages/MySQLdb.pyo diff --git a/mysql/Setup.in b/mysql/Setup.in index 853500a..1eaecba 100644 --- a/mysql/Setup.in +++ b/mysql/Setup.in @@ -1,11 +1,15 @@ *shared* # Only one line should be uncommented. # Adjust -L and -I as necessary for your local configuration. -# If you have dynamic MySQL libraries, they will need to be on your -# LD_LIBRARY_PATH at runtime. # I find that 3.23 requires libz. It probably won't hurt earlier versions. _mysql _mysqlmodule.c -L/usr/lib/mysql -I/usr/include/mysql -lmysqlclient -lz +# If you have dynamic MySQL libraries, they will need to be on your +# LD_LIBRARY_PATH at runtime. But you are probably better off linking +# against the static mysqlclient library. If the above line doesn't work +# for you (it works for me with the standard MySQL RPMs), try this: +#_mysql _mysqlmodule.c -I/usr/include/mysql /usr/lib/mysql/libmysqlclient.a -lz + # Uncomment for Windows #_mysql _mysqlmodule.c -L/mysql/lib/opt -I/mysql/include -lmysqlclient -lwsock32 diff --git a/mysql/setup.py b/mysql/setup.py new file mode 100644 index 0000000..e814545 --- /dev/null +++ b/mysql/setup.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +"""Setup script for the MySQLdb module distribution.""" + +import os +from distutils.core import setup +from distutils.extension import Extension + +# You may need to edit this script to point to the location of your +# MySQL installation. It should be sufficient to change the value of +# the MYSQL_DIR variable below. +MYSQL_INCLUDE_DIR = '/usr/include/mysql' +MYSQL_LIB_DIR = '/usr/lib/mysql' + +setup (# Distribution meta-data + name = "MySQLdb", + version = "0.2.2", + description = "An interface to MySQL", + author = "Andy Dustman", + author_email = "andy@dustman.net", + url = "http://dustman.net/andy/python/MySQLdb", + + # Description of the modules and packages in the distribution + + py_modules = ["MySQLdb", "CompatMysqldb"], + + ext_modules = [Extension( + name='_mysqlmodule', + sources=['_mysqlmodule.c'], + include_dirs=[MYSQL_INCLUDE_DIR], + # maybe comment to force dynamic libraries + # library_dirs=[MYSQL_LIB_DIR], + # uncomment if linking against dynamic libraries + runtime_library_dirs=[MYSQL_LIB_DIR], + libraries=['mysqlclient', + 'z', # needed for MyZQL-3.23 + # Some C++ compiler setups "forget" to + # link to the c++ libs (notably on + # Solaris), so you might succeed in + # linking them in by hand: + # 'stdc++', 'gcc', + ], + # uncomment to force use of the static library + # extra_objects=[`MYSQL_LIB_DIR`+'libmysqlclient.a'], + )] +)