diff --git a/MySQLdb/MySQLdb/connections.py b/MySQLdb/MySQLdb/connections.py index 4831201..e47e45d 100644 --- a/MySQLdb/MySQLdb/connections.py +++ b/MySQLdb/MySQLdb/connections.py @@ -96,6 +96,11 @@ class Connection(_mysql.connection): normal strings. Unicode objects will always be encoded to the connection's character set regardless of this setting. + charset + If supplied, the connection character set will be changed + to this character set (MySQL-4.1 and newer). This implies + use_unicode=True. + client_flag integer, flags to use or 0 (see MySQL docs or constants/CLIENTS.py) @@ -127,10 +132,18 @@ class Connection(_mysql.connection): del kwargs2['cursorclass'] else: self.cursorclass = self.default_cursor - use_unicode = kwargs.get('use_unicode', 0) + + charset = kwargs.get('charset', '') + if kwargs.has_key('charset'): + del kwargs2['charset'] + if charset: + use_unicode = True + else: + use_unicode = False + use_unicode = kwargs.get('use_unicode', use_unicode) if kwargs.has_key('use_unicode'): del kwargs2['use_unicode'] - + client_flag = kwargs.get('client_flag', 0) client_version = tuple([ int(n) for n in _mysql.get_client_info().split('.')[:2] ]) if client_version >= (4, 1): @@ -143,11 +156,20 @@ class Connection(_mysql.connection): super(Connection, self).__init__(*args, **kwargs2) self._server_version = tuple([ int(n) for n in self.get_server_info().split('.')[:2] ]) - self.charset = self.character_set_name().split('_')[0] + self.charset = self.character_set_name() + + if charset and self.charset != charset: + if self._server_version < (4, 1): + raise UnsupportedError, "server is too old to change charset" + self.set_character_set(charset) + self.charset = charset if use_unicode: def u(s): - return s.decode(self.charset) + # can't refer to self.character_set_name() + # because this results in reference cycles + # and memory leaks + return s.decode(charset) conv[FIELD_TYPE.STRING].insert(-1, (None, u)) conv[FIELD_TYPE.VAR_STRING].insert(-1, (None, u)) conv[FIELD_TYPE.BLOB].insert(-1, (None, u)) @@ -211,6 +233,17 @@ class Connection(_mysql.connection): else: return 0 + if not hasattr(_mysql.connection, 'set_character_set'): + + def set_character_set(self, charset): + """Set the connection character set. This version + uses the SET NAMES SQL statement. + + You probably shouldn't try to change character sets + after opening the connection.""" + self.query('SET NAMES %s' % charset) + self.store_result() + def show_warnings(self): """Return detailed information about warnings as a sequence of tuples of (Level, Code, Message). This diff --git a/MySQLdb/_mysql.c b/MySQLdb/_mysql.c index 97e0dc5..7f322be 100644 --- a/MySQLdb/_mysql.c +++ b/MySQLdb/_mysql.c @@ -1485,6 +1485,78 @@ _mysql_ConnectionObject_character_set_name( return PyString_FromString(s); } +#if MYSQL_VERSION_ID >= 50007 +static char _mysql_ConnectionObject_set_character_set__doc__[] = +"Sets the default character set for the current connection.\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_set_character_set( + _mysql_ConnectionObject *self, + PyObject *args) +{ + const char *s; + int err; + if (!PyArg_ParseTuple(args, "s", &s)) return NULL; + check_connection(self); + Py_BEGIN_ALLOW_THREADS + err = mysql_set_character_set(&(self->connection), s); + Py_END_ALLOW_THREADS + if (err) return _mysql_Exception(self); + Py_INCREF(Py_None); + return Py_None; +} +#endif + +#if MYSQL_VERSION_ID >= 50010 +static char _mysql_ConnectionObject_get_character_set_info__doc__[] = +"Returns a dict with information about the current character set:\n\ +\n\ +collation\n\ + collation name\n\ +name\n\ + character set name\n\ +comment\n\ + comment or descriptive name\n\ +dir\n\ + character set directory\n\ +mbminlen\n\ + min. length for multibyte string\n\ +mbmaxlen\n\ + max. length for multibyte string\n\ +\n\ +Not all keys may be present, particularly dir.\n\ +\n\ +Non-standard.\n\ +"; + +static PyObject * +_mysql_ConnectionObject_get_character_set_info( + _mysql_ConnectionObject *self, + PyObject *args) +{ + PyObject *result; + MY_CHARSET_INFO cs; + + if (!PyArg_ParseTuple(args, "")) return NULL; + check_connection(self); + mysql_get_character_set_info(&(self->connection), &cs); + if (!(result = PyDict_New())) return NULL; + if (cs.csname) + PyDict_SetItemString(result, "name", PyString_FromString(cs.csname)); + if (cs.name) + PyDict_SetItemString(result, "collation", PyString_FromString(cs.name)); + if (cs.comment) + PyDict_SetItemString(result, "comment", PyString_FromString(cs.comment)); + if (cs.dir) + PyDict_SetItemString(result, "dir", PyString_FromString(cs.dir)); + PyDict_SetItemString(result, "mbminlen", PyInt_FromLong(cs.mbminlen)); + PyDict_SetItemString(result, "mbmaxlen", PyInt_FromLong(cs.mbmaxlen)); + return result; +} +#endif + static char _mysql_get_client_info__doc__[] = "get_client_info() -- Returns a string that represents\n\ the client library version."; @@ -2063,6 +2135,22 @@ static PyMethodDef _mysql_ConnectionObject_methods[] = { METH_VARARGS, _mysql_ConnectionObject_character_set_name__doc__ }, +#if MYSQL_VERSION_ID >= 50007 + { + "set_character_set", + (PyCFunction)_mysql_ConnectionObject_set_character_set, + METH_VARARGS, + _mysql_ConnectionObject_set_character_set__doc__ + }, +#endif +#if MYSQL_VERSION_ID >= 50010 + { + "get_character_set_info", + (PyCFunction)_mysql_ConnectionObject_get_character_set_info, + METH_VARARGS, + _mysql_ConnectionObject_get_character_set_info__doc__ + }, +#endif { "close", (PyCFunction)_mysql_ConnectionObject_close, diff --git a/MySQLdb/doc/FAQ.txt b/MySQLdb/doc/FAQ.txt new file mode 100644 index 0000000..b6a4ce9 --- /dev/null +++ b/MySQLdb/doc/FAQ.txt @@ -0,0 +1,143 @@ +==================================== + MySQLdb Frequently Asked Questions +==================================== + +.. contents:: +.. + + +Build Errors +------------ + + ld: fatal: library -lmysqlclient_r: not found + +mysqlclient_r is the thread-safe library. It's not available on +all platforms, or all installations, apparently. You'll need to +reconfigure site.cfg (in MySQLdb-1.2.1 and newer) to have +threadsafe = False. + + mysql.h: No such file or directory + +This almost always mean you don't have development packages +installed. On some systems, C headers for various things (like MySQL) +are distributed as a seperate package. You'll need to figure out +what that is and install it, but often the name ends with -devel. + +Another possibility: Some older versions of mysql_config behave oddly +and may throw quotes around some of the path names, which confused +MySQLdb-1.2.0. 1.2.1 works around these problems. If you see things +like -I'/usr/local/include/mysql' in your compile command, that's +probably the issue, but it shouldn't happen any more. + + +ImportError +----------- + + ImportError: No module named _mysql + +If you see this, it's likely you did some wrong when installing +MySQLdb; re-read (or read) README. _mysql is the low-level C module +that interfaces with the MySQL client library. + +Various versions of MySQLdb in the past have had build issues on +"weird" platforms; "weird" in this case means "not Linux", though +generally there aren't problems on Unix/POSIX platforms, including +BSDs and Mac OS X. Windows has been more problematic, in part because +there is no `mysql_config` available in the Windows installation of +MySQL. 1.2.1 solves most, if not all, of these problems, but you will +still have to edit a configuration file so that the setup knows where +to find MySQL and what libraries to include. + + + ImportError: libmysqlclient_r.so.14: cannot open shared object file: No such file or directory + +The number after .so may vary, but this means you have a version of +MySQLdb compiled against one version of MySQL, and are now trying to +run it against a different version. The shared library version tends +to change between major releases. + +Solution: Rebuilt MySQLdb, or get the matching version of MySQL. + +Another thing that can cause this: The MySQL libraries may not be on +your system path. + +Solutions: + +* set the LD_LIBRARY_PATH environment variable so that it includes + the path to the MySQL libraries. + +* set static=True in site.cfg for static linking + +* reconfigure your system so that the MySQL libraries are on the + default loader path. In Linux, you edit /etc/ld.so.conf and run + ldconfig. For Solaris, see `Linker and Libraries Guide + `_. + + + ImportError: ld.so.1: python: fatal: libmtmalloc.so.1: DF_1_NOOPEN tagged object may not be dlopen()'ed + +This is a weird one from Solaris. What does it mean? I have no idea. +However, things like this can happen if there is some sort of a compiler +or environment mismatch between Python and MySQL. For example, on some +commercial systems, you might have some code compiled with their own +compiler, and other things compiled with GCC. They don't always mesh +together. One way to encounter this is by getting binary packages from +different vendors. + +Solution: Rebuild Python or MySQL (or maybe both) from source. + + ImportError: dlopen(./_mysql.so, 2): Symbol not found: _sprintf$LDBLStub + Referenced from: ./_mysql.so + Expected in: dynamic lookup + +This is one from Mac OS X. It seems to have been a compiler mismatch, +but this time between two different versions of GCC. It seems nearly +every major release of GCC changes the ABI in some why, so linking +code compiled with GCC-3.3 and GCC-4.0, for example, can be +problematic. + + +My data disappeared! (or won't go away!) +---------------------------------------- + +Starting with 1.2.0, MySQLdb disables autocommit by default, as +required by the DB-API standard (`PEP-249`_). If you are using InnoDB +tables or some other type of transactional table type, you'll need +to do connection.commit() before closing the connection, or else +none of your changes will be written to the database. + +Conversely, you can also use connection.rollback() to throw away +any changes you've made since the last commit. + +Important note: Some SQL statements -- specifically DDL statements +like CREATE TABLE -- are non-transactional, so they can't be +rolled back, and they cause pending transactions to commit. + + +Other Errors +------------ + + OperationalError: (1251, 'Client does not support authentication protocol requested by server; consider upgrading MySQL client') + +This means your server and client libraries are not the same version. +More specifically, it probably means you have a 4.1 or newer server +and 4.0 or older client. You can either upgrade the client side, or +try some of the workarounds in `Password Hashing as of MySQL 4.1 +`_. + + +Other Resources +--------------- + +* Help forum. Please search before posting. + +* `Google `_ + +* READ README! + +* Read the User's Guide + +* Read `PEP-249`_ + +.. _`PEP-249`: http://www.python.org/peps/pep-0249.html + diff --git a/MySQLdb/doc/MySQLdb.txt b/MySQLdb/doc/MySQLdb.txt index 0919a43..077ddd0 100644 --- a/MySQLdb/doc/MySQLdb.txt +++ b/MySQLdb/doc/MySQLdb.txt @@ -51,45 +51,53 @@ non-portable. MySQL C API function mapping ............................ -================================= ================================== - C API ``_mysql`` -================================= ================================== - ``mysql_affected_rows()`` ``conn.affected_rows()`` - ``mysql_close()`` ``conn.close()`` - ``mysql_connect()`` ``_mysql.connect()`` - ``mysql_data_seek()`` ``result.data_seek()`` - ``mysql_debug()`` ``_mysql.debug()`` - ``mysql_dump_debug_info`` ``conn.dump_debug_info()`` - ``mysql_escape_string()`` ``_mysql.escape_string()`` - ``mysql_fetch_row()`` ``result.fetch_row()`` - ``mysql_get_client_info()`` ``_mysql.get_client_info()`` - ``mysql_get_host_info()`` ``conn.get_host_info()`` - ``mysql_get_proto_info()`` ``conn.get_proto_info()`` - ``mysql_get_server_info()`` ``conn.get_server_info()`` - ``mysql_info()`` ``conn.info()`` - ``mysql_insert_id()`` ``conn.insert_id()`` - ``mysql_num_fields()`` ``result.num_fields()`` - ``mysql_num_rows()`` ``result.num_rows()`` - ``mysql_options()`` ``_mysql.connect()`` - ``mysql_ping()`` ``conn.ping()`` - ``mysql_query()`` ``conn.query()`` - ``mysql_real_connect()`` ``_mysql.connect()`` - ``mysql_real_query()`` ``conn.query()`` - ``mysql_real_escape_string()`` ``conn.escape_string()`` - ``mysql_row_seek()`` ``result.row_seek()`` - ``mysql_row_tell()`` ``result.row_tell()`` - ``mysql_select_db()`` ``conn.select_db()`` - ``mysql_stat()`` ``conn.stat()`` - ``mysql_store_result()`` ``conn.store_result()`` - ``mysql_thread_id()`` ``conn.thread_id()`` - ``mysql_thread_safe_client()`` ``conn.thread_safe_client()`` - ``mysql_use_result()`` ``conn.use_result()`` - ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` - ``CR_*`` ``MySQLdb.constants.CR.*`` - ``ER_*`` ``MySQLdb.constants.ER.*`` - ``FIELD_TYPE_*`` ``MySQLdb.constants.FIELD_TYPE.*`` - ``FLAG_*`` ``MySQLdb.constants.FLAG.*`` -================================= ================================== +=================================== ================================== + C API ``_mysql`` +=================================== ================================== + ``mysql_affected_rows()`` ``conn.affected_rows()`` + ``mysql_autocommit()`` ``conn.autocommit()`` + ``mysql_character_set_name()`` ``conn.character_set_name()`` + ``mysql_close()`` ``conn.close()`` + ``mysql_commit()`` ``conn.commit()`` + ``mysql_connect()`` ``_mysql.connect()`` + ``mysql_data_seek()`` ``result.data_seek()`` + ``mysql_debug()`` ``_mysql.debug()`` + ``mysql_dump_debug_info`` ``conn.dump_debug_info()`` + ``mysql_escape_string()`` ``_mysql.escape_string()`` + ``mysql_fetch_row()`` ``result.fetch_row()`` + ``mysql_get_character_set_info()`` ``conn.get_character_set_info()`` + ``mysql_get_client_info()`` ``_mysql.get_client_info()`` + ``mysql_get_host_info()`` ``conn.get_host_info()`` + ``mysql_get_proto_info()`` ``conn.get_proto_info()`` + ``mysql_get_server_info()`` ``conn.get_server_info()`` + ``mysql_info()`` ``conn.info()`` + ``mysql_insert_id()`` ``conn.insert_id()`` + ``mysql_num_fields()`` ``result.num_fields()`` + ``mysql_num_rows()`` ``result.num_rows()`` + ``mysql_options()`` various options to ``_mysql.connect()`` + ``mysql_ping()`` ``conn.ping()`` + ``mysql_query()`` ``conn.query()`` + ``mysql_real_connect()`` ``_mysql.connect()`` + ``mysql_real_query()`` ``conn.query()`` + ``mysql_real_escape_string()`` ``conn.escape_string()`` + ``mysql_rollback()`` ``conn.rollback()`` + ``mysql_row_seek()`` ``result.row_seek()`` + ``mysql_row_tell()`` ``result.row_tell()`` + ``mysql_select_db()`` ``conn.select_db()`` + ``mysql_set_character_set()`` ``conn.set_character_set()`` + ``mysql_ssl_set()`` ``ssl`` option to ``_mysql.connect()`` + ``mysql_stat()`` ``conn.stat()`` + ``mysql_store_result()`` ``conn.store_result()`` + ``mysql_thread_id()`` ``conn.thread_id()`` + ``mysql_thread_safe_client()`` ``conn.thread_safe_client()`` + ``mysql_use_result()`` ``conn.use_result()`` + ``mysql_warning_count()`` ``conn.warning_count()`` + ``CLIENT_*`` ``MySQLdb.constants.CLIENT.*`` + ``CR_*`` ``MySQLdb.constants.CR.*`` + ``ER_*`` ``MySQLdb.constants.ER.*`` + ``FIELD_TYPE_*`` ``MySQLdb.constants.FIELD_TYPE.*`` + ``FLAG_*`` ``MySQLdb.constants.FLAG.*`` +=================================== ================================== Some _mysql examples @@ -328,6 +336,19 @@ connect(parameters...) *This must be a keyword parameter.* + charset + If present, the connection character set will be changed + to this character set, if they are not equal. Support for + changing the character set requires MySQL-4.1 and later + server; if the server is too old, UnsupportedError will be + raised. This option implies use_unicode=True, but you can + override this with use_unicode=False, though you probably + shouldn't. + + If not present, the default character set is used. + + *This must be a keyword parameter.* + ssl This parameter takes a dictionary or mapping, where the keys are parameter names used by the mysql_ssl_set_ MySQL @@ -482,6 +503,12 @@ callproc(procname, args) accessible by a SELECT statement as @_foo_0, @_foo_1, and @_foo_2. + **Compatibility note:** It appears that the mere act of + executing the CALL statement produces an empty result set, which + appears after any result sets which might be generated by the + stored procedure. Thus, you will always need to use nextset() to + advance result sets. + close() Closes the cursor. Future operations raise ``ProgrammingError``. If you are using server-side cursors, it is very important to diff --git a/MySQLdb/metadata.cfg b/MySQLdb/metadata.cfg index b139101..a15e6ce 100644 --- a/MySQLdb/metadata.cfg +++ b/MySQLdb/metadata.cfg @@ -1,6 +1,6 @@ [metadata] -version: 1.2.1c5 -version_info: (1,2,1,'gamma',5) +version: 1.2.1c6 +version_info: (1,2,1,'gamma',6) description: Python interface to MySQL long_description: ========================= diff --git a/MySQLdb/test_MySQLdb_capabilities.py b/MySQLdb/test_MySQLdb_capabilities.py index bcda1b7..5b80d49 100644 --- a/MySQLdb/test_MySQLdb_capabilities.py +++ b/MySQLdb/test_MySQLdb_capabilities.py @@ -11,7 +11,7 @@ class test_MySQLdb(test_capabilities.DatabaseTest): db_module = MySQLdb connect_args = () connect_kwargs = dict(db='test', read_default_file='~/.my.cnf', - use_unicode=True) + charset='utf8') create_table_extra = "ENGINE=INNODB CHARACTER SET UTF8" def quote_identifier(self, ident):