diff --git a/ChangeLog b/ChangeLog index 17b5c28..20f1974 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,48 +1,426 @@ MySQL Connector/Python - Release Notes / ChangeLog -Copyright (c) 2009,2011, Oracle and/or its affiliates. All rights reserved. -Use is subject to license terms. (See COPYING) +Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +1.0.12 (2013-07-24) ----------------------------------------------------------------------- -0.3.2 +- The fetchall() methods for buffered cursors were returning all rows after + fetchone() or fetchmany() were used. fetchall() now correctly returns all + or remaining, just like the nonbuffered cursors. (Bug #16662920) + +- Following fetchone() or fetchmany(), the result returned by fetchall() was + missing one row. (Bug #17041412) + +- LOAD DATA LOCAL INFILE failed for files approximately 14MB or + larger. (Bug #17002411) + +- Python 2.6 and 2.7 raised a UnicodeDecodeError when unicode_literals was + used and a database name contained nonlatin Unicode + characters. (Bug #16655208) + +1.0.11 (2013-06-26) ----------------------------------------------------------------------- -Bugs fixed: -o lp:701081 - Doesn't install with Python 2.4 +- Added Distutils commands for creating Debian packages (WL#7080) + +- Updated error codes and message using MySQL 5.7.1 (BUG#16896702) +1.0.10 (2013-05-02) ----------------------------------------------------------------------- -0.3.1 +- Fixed MySQLConnection.cmd_shutdown() method to accept shutdown types. A + new constants class ShutdownType was added. (BUG#16234441) + +- Added support for LOAD DATA LOCAL INFILE. (BUG#16369511) + +- Removed non-GPL documentation from GPL packages. Added README_DOCS.txt + with URL of the online manual and downloads. (BUG#16430013) + +- Fixed SSL certificate checking and error reporting. We introduced + a new connection argument ssl_verify_cert. When set to True, + it will verify the server certificate (using ssl.CERT_REQUIRED). + (BUG#16400735) + +1.0.9 (2013-02-21) ----------------------------------------------------------------------- -Bugs fixed: -o lp:695514 - Infinite recursion when setting connection client_flags -o lp:691836 - Incorrect substitution by cursor.execute when tuple args - contains '%s' +- Fixed the 'build' DistUtils command to copy version.py. This makes + sure that the version of Connector/Python inside the + build-directory is fully functional. (BUG#16236136) +- Fixed connecting using SSL: we now set the ClientFlag.SSL + automatically whenever SSL arguments are used when setting + up the connection. (BUG#16217667) + +- Fixed passing string parameters to stored routines. (BUG#16217743) + +- Fixed mail address of FSF in copyright notice. (BUG#16249347) + +- Fixed IPv6 for older MS Windows versions. We now use + socket.getaddrinfo() instead of inet_pton() to check whether we are + connecting using IPv4 or IPv6. A new connection option 'force_ipv6' + has been introduced. When set to True, IPv6 will be used when an + address resolves to both IPv4 and IPv6. (BUG#16209119) + +1.0.8 (2012-12-17) ----------------------------------------------------------------------- -0.3.0 +- Fixed MySQLConnection.ping() so it only reconnects when the reconnect + argument is set to True. (BUG#15915243) + +- Fixed storing multiple results after calling stored procedures which + are using arguments. (BUG#15916486) + +- Fixed support for connecting to MySQL using IPv6 addresses. (BUG#15876886) + +- Fixed handling MySQL errors when doing handshake. (BUG#15836979) + +- Fixed support for connecting to MySQL using IPv6 addresses. (BUG#15876886, + BUG#15927825) + +- Fixed reading the header of MySQL packets. Previously, we were reading + the MySQL packet headers as 4 (or 7) bytes long buffers from the socket. + When we did have the correct length, we would immediately raise an exception. + We now keep reading data from the socket until we got the full header + or bail out on errors. (BUG#14829471) + +- Fixed the error message when the TCP/IP port number is not a valid integer. + Strings are first converted to integer and raise an error with a proper + error when this fails. (BUG#13808727) + +- Fixed authenticating for Python v2 using usernames and/or passwords which + contain Unicode characters using Python v2. (BUG#14843456) + +- Fixed reporting of socket errors. (BUG#14802017) + +- Fixed executemany() to support the pyformat parameter style. (BUG#14754894) + * The regular expression parsing the INSERT statement did not work when + pyformat markers where used, that is, '%(c1)s' for example. This is now + fixed so both ANSI C printf and Python extended format codes can be + used by MySQLCursor.executemany(). + +1.0.7 (2012-08-19) +----------------------------------------------------------------------- +- Fixed formatting of client errors changing numeric to string + placeholders. (BUG#14548043) + * Client and server errors have been regenerated using latest + development release of MySQL v5.6.6. + +1.0.6-beta (2012-08-28) ----------------------------------------------------------------------- -Highlights: -o Python v2.4 support is back. -o Support for compressed protocol. -o Support for SSL connections (when Python's ssl module is available) -o Support for packets which are bigger than 16MB. -o Max allowed packetsize defaults to 1GB. -o Some performance improvements. +- Fixed Python v3 code so it works with Python v3.3. (BUG#14524942) + +- Fixed MySQLCursorRaw.fetchall() to not raise when results are available. + This was only a problem in the Python v3 code. (BUG#14517262, BUG#66465) + +- Changed name and version of distributions to align with other MySQL + projects (WL#6450) + * The version now includes the suffix 'b' for beta and 'a' for alpha + followed by a number. This version is used in the source and built + distributions. GA versions will have no suffix. + * The RPM spec files have been updated to create packages which names + are aligned with RPMs from other MySQL projects. + +- Fixed installation of version.py on OS X. (BUG#14483142) + * version.py is now correctly installed on OS X in the mysql.connector + package. Previously it was installed through data_files, and version.py + ended up in the system wide package location of Python from where it + could not be imported. + * data_files is not used any longer in setup.py and is removed. Extra + files like version.py are now copied in the custom Distutils + commands. + +- Fixed SSL unit testing for source distributions. (BUG#14402737) + * The SSL keys and certificates were missing and have been added to + the source distribution. Now SSL testing works properly. + * Additionally for the Windows platform, forward slashes had to + be added to the option file creation so the MySQL server can + pickup the needed SSL files. + +- Timeout for unit tests has been set to 10 seconds. Test cases can + individually adjust it to be higher or lower. (BUG#14487502) + +- Fixed test cases in test_mysql_database.py which failed when using + using YEAR(2) with MySQL v5.6.6 and greater. (BUG#14460680) + +- Changed how MySQL server errors are mapped to Python exceptions. We now + use the SQLState (when available) to raise a better error. (WL#6412) + * Incompatibility: some server errors are now raised with a different + exception. + * It is possible to override how errors are raised using the + mysql.connector.custom_error_exception() function, defined in + the mysql.connector.errors module. This can be useful for certain + frameworks to align with other database drivers. + +1.0.5-beta (2012-07-17) +----------------------------------------------------------------------- +- Added SQL Modes as constants, making it easier to use them (WL#6411) + * Setting the SQL Modes meant that developers had to make sure the + string passed to MySQL was correctly formatted and contains the correct + modes. To make it easier, a new class constants.SQLMode containing all + the SQL Modes was added: for example, constants.SQLMode.TRADITIONAL will + return the string 'TRADITIONAL'. + * Additionally, MySQLConnection.sql_mode property now accepts a sequence of + SQL Modes. For example, to set 2 SQL Modes, you can do the following + cnx.sql_mode = [SQLMode.REAL_AS_FLOAT, SQLMode.NO_ZERO_DATE] + +- Added descriptive error codes for both client and server errors (WL#6351) + * The errorcode module contains client and server error codes which can + be used instead of the error numbers. For example, + errorcode.CR_CONNECTION_ERROR is 2002. + * A new locales sub-package has been created in mysql.connector. This will + provided localized content. The first support language is the default + English and contains the client error messages: locales.eng.client_error. + To retrieve a client error based on the error number or code (name) you + can use the get_client_error() function importing it from locales: + from mysql.connector.locales import get_client_error() + * Error messages and error codes are automatically fetch from the latest + development release of MySQL. The errorcode.py and all files in the + locales package are generated. You can see the generation date and the + version of MySQL which was used in those files. + +1.0.4-alpha (2012-07-08) +----------------------------------------------------------------------- +- Redundant MySQLConnection methods unset_client_flag() and set_client_flag() + have been removed. Use set_client_flags() using a sequence of flags to + change the client flags. (BUG#14259996) + +- Greatly simplified setting and retrieving character set and collation + information (BUG#14260052): + * Incompatible change: MySQLConnection's set_charset() has been replaced + by the method set_charset_collation() with which it is possible to set + character set and collation. The properties collation and charset are + now read-only. + +- Fixed MySQLCursor.executemany() when INSERT statements use the + 'ON DUPLICATE KEY'-clause with a function like VALUES() (BUG#14259954) + +- Fixing unit testing on the MS Windows platform (BUG#14236592) + * tests/mysqld.py has been updated to make sure Windows binaries are + looked up. Also, backslashes are now replaced by double forward + slashes in the option file. + * Bootstrapping is done using --no-defaults and we make sure that + --standalone is used when launching mysqld.exe on Windows. + * examples/dates.py has been updated to produce more predictable + output and to work in environments where SQL Modes are set by + default, for example, MySQL installation on Windows. + * Some unit test have been update for Windows. + +- Converting a datetime.time for MySQL failed for Python v2.4/2.5 + because strftime() has no support for the %f mark (BUG#14231941) + +- cursor.CursorBase attributes description, lastrowid and rowcount are now + read-only properties. This change was needed to to comply with Python's + DB API v2.0 specification or PEP-249. (BUG#14231160) + +- Server error 1426 has been classified as ProgrammingError for + MySQL 5.6.4 and later (BUG#14201459) + * Additionally, the unit tests will now show the actual version of the + MySQL server being bootstrapped and started. The version is also + available to all tests. + +- MySQLConnection.cmd_query() could and can not handle multiple statements. + There for, a new method cmd_query_iter() has been introduced which will + return an generator object to iterate through results. To prevent + duplication of functionality/code, we made a few changes to the + cursors in the cursor module. Here is a list of what is new and + incompatible changes (BUG#14208326): + * MySQLConnection.cmd_query() will raise an error when multiple + statements are given; cmd_query_iter() should be used. + * MySQLCursor.execute() returns a generator object with which you can + iterate over results when executing multiple statements. Previously + it returned -1 or the row count. MySQLCursor.execute() yields True + when there are rows available (for example, after a SELECT), or False + when no rows need to be or can be fetched. + * The method MySQLCursor.next_resultset() has been removed since you + can now iterate through results using MySQLCursor.execute(). + * The method MySQLCursor.next_proc_result() has been renamed to + MySQLCursor.proc_results() and returns a generator object. + * MySQLCursor.statement will return the executed statement. + * MySQLCursor.with_rows returns True when there is result which could + return rows (which has columns definitions returned by MySQL). + * The multiple_resultset.py example shows how to go through results + produced by sending multiple statements. + +- MySQLConnection.cmd_query() and other methods sending server commands + did not take into consideration that there might be unread results. This + could possibly lead to wrong results and the application waiting forever. + (BUG#14184643) + +1.0.3-alpha (2012-06-08) +----------------------------------------------------------------------- +- Facilitate the creation of source and built distributions (WL#6250) + * New Distutils commands for creating packages. Every command can be + given with the setup script, `python setup.py `. These + commands are a good base for later adding more distribution types. + All commands are either specific to a particular Python minor version + or major version (that is Python 2.x or Python 3.x). + * sdist_gpl: GPLv2 distribution created in a folder in dist/. This is + the base command for other GPLv2 source distributions. + * bdist_com: Commercial distribution created in a folder in dist/. This + the base command for other commercial built distributions. Before + the source byte-compiled and removed, the GPL license is removed. + * sdist_gpl_msi: Based on command sdist_gpl, it creates a Windows + Installer installing a source distribution using WiX v3.5 (see + support/MSWindows). + * bdist_com_msi: Based on command bdist_com, it creates a Windows + Installer installing a commercial built distribution + using WiX v3.5 (see support/MSWindows). + * sdist_gpl_rpm: Based on command sdist_gpl, it creates an architecture + independent, but Python version specific, source distribution + using a RPM spec file (see support/RPM). + * bdist_com_rpm: Based on command bdist_com, it creates a architecture + independent, but Python version specific, built distribution + using a RPM spec file (see support/RPM). + * There is an additional command to make Egg, but this proved to be not + working due to the fact that Connector/Python is installed in a folder + or package used by other projects (like MySQL Utilities). We, however, + leave the code as it might be useful later. + * The support/ directory is not part of any distribution and the setup.py + will silently ignore when the extra Distutils commands are not + available. + * The distribution name of the Connector/Python (metasetupinfo.py) has + changed from MySQL-Connector-Python to MySQL_Connector_Python to avoid + problems installing using easy_install and others. + * The docs/ directory now contains placeholder files for various + documentation formats which will be part of the distribution later. We + added these to facilitate the development and testing of the extra + Distutils commands. + * We removed the support/make_release.py script because we now use the + Distutils. The file _version.py has been renamed to version.py so it + can be imported without to much problems as it is also part of the + distributions. + +- Adding support for time values with a fractional part (WL#6149) + * In MySQL 5.6.4, the fractional part of DATETIME, TIME, and TIMESTAMP + values can be stored (provided the table schema supports it). + Connector/Python will send the fractional part and read it from results. + Support is transparent and there is no extra option needed to enable + this feature. + * The new example script microseconds.py was added showing how to use + Connector/Python to save and read TIME values with a fractional part. + +1.0.2-alpha (2012-05-19) +----------------------------------------------------------------------- +- Adding missing unittests (WL#6069): + * Adding missing unit tests for important modules like connection and + network. + * SSL wasn't tested and the bootstrapped MySQL server can now setup + secure connections using certificates and keys found in the + /support/ssl folder of the source. + * Some bugs around SSL have been found and fixed together with some + refactoring. + * Some code in mysql.connector.connection has been refactored or removed. + +- Fixing and refactoring the mysql.connector.errors module (BUG#14039339): + * class ClientError has been removed. The client error messages are + now found in errors._CLIENT_ERROR and the function returning the + MySQL Client error message is named errors._get_mysql_client_error. + * Some server errors were incorrectly or classified twice. + * errors.raise_error will raise a ValueError when the packet does not + contain a MySQL error. + * Removed Python mapping keys from client error messages + in _CLIENT_ERROR. + * The SQLState returned by MySQL errors were not reported in the + raised exceptions. + * Unit tests were missing for the errors module and have been added. + +- Bootstrapping MySQL v5.6 running unittests have been fixed. (BUG#14048685) + * A semicolon was missing issuing the USE command when loading the + system table when bootstrapping. It seems that this is a problem + with more recent MySQL versions. + * STDOUT and STDERR when bootstrapping and starting the MySQL server + for unittest testing is being redirected to devnull. + * There is now an info logger line when we bootstrap and when we start + the MySQL server. This should help showing at which state has been + reached. + + +1.0.1-alpha (2012-04-26) +----------------------------------------------------------------------- +- Updating copyright notice and license: the FLOSS License Exception is + now explicitly mentioned in all source files. (WL#6273) + +- The version does only contain integers now. The 'a' or 'alpha' suffix will + not be present in packages, but it will be mentioned in the _version.py + module since metasetupinfo.py uses this information to set, for example, + the Trove classifiers dynamically. + +1.0.0-alpha (2012-04-22) +----------------------------------------------------------------------- +- Preparation for first Alpha release in the MySQL Connector/Python v1.0 + series (WL#6273): + * _version.py was removed from the mysql.connector package and moved + now only used when installing and making packages. + * The README and AUTHORS files have been updated. The ChangeLog has + been revamped to included only v1.0 releases. + * Documentation is moving to the MySQL manual: contains of docs/ folder + has been removed and for now replaced by a README. + * metasetupinfo.py has been updated. + * The make_release.py script has been updated and moved to the subfolder + support/. + * MANIFEST.in does not include setup.cfg anymore. + +- The metasetupinfo.py was using the already installed modules of + Connector/Python instead of the ones going to be installed. This meant that + upgrading would haven't worked when new modules would have been added. + The metasetupinfo.py now alters the sys.path to make sure the to be + installed modules are loaded first. (BUG#13962765) + +- Refactoring the modules connection and protocol (WL#6196): + * Moving socket classes connection to the new module + mysql.connector.network. + * The MySQLProtocol class does not keep a reference to a + MySQLConnection-object any more. MySQLProtocol is now only dealing + with creating and parsing MySQL packets. Network interaction is now + done only by the MySQLConnection objects with the exception of the + MySQLProtocol.read_text_result, which needs a socket object to be + passed. + * MySQLConnection handles handshaking, authentication, + OK/EOF packets and Error packets, all which was done previously by the + MySQLProtocol class. + * Packet numbers are now tracked by network.BaseMySQLSocket. This was + previously done by MySQLProtocol. + +- MySQLCursor.description now stores column names as Unicode (BUG#13792575). + +- The dbapi.Binary is now bytes for Python v3 (BUG#13780676) + +- Fixed automatic garbage collection which caused memory usage to grow + over time. (Bug#13435186) + * MySQLConverter uses getattr() instead of mapping types and methods in + a dictionary. + * MySQLCursor keeps a weak reference to the MySQLConnection object that + created it. + * MySQLConnection does not keep track of its cursors any longer. + * Some changes where reworked with refactoring the connection and + protocol modules. (WL#6196) + +- Implemented reconnect and fixed the ping()-method (BUG#13392739) + * MySQLConnection.reconnect() can be used to reconnect to the MySQL + server. It accepts number of retries and an optional delay between + attempts. + * MySQLConnectiong.ping() is now a method and works the way the MySQL + C API mysql_ping() function works: it raises an error. It can also + optionally reconnect. + * MySQLConnection.is_connected() now returns True when connection is + available, False otherwise. + * ping() and is_connected() are backwards incompatible. + +- Fixed setting time zone for current MySQL session. (BUG#13395083) + +- Fixed setting and retrieving character set and collation. (Bug#13375632) + * Setting the character set using the MySQLConnection charset property + or set_charset() method can now be done in 3 ways: using MySQL's ID, + using a name or using a tuple with second element the collation. + * Setting the collation will retrieve and reset MySQLConnection's + _charset_id attribute to the correct character set. + * The MySQLConnection.charset_name and MySQLConnection.collation_name + properties can be used to retrieve respectively the character set and + the collation name. -Changes: -o Changing code in python2/ and some unittest code to support Python v2.4. -o Max allowed packetsize is now set to the default of 1GB when authenticating. -o It was not possible to read and send big packets (>=16MB). -o (Py2) MySQLConnection.recv() has been refactor and simplified. -o (Py3) MySQLConnection.recv() refactored to use bytearray() instead - of doing string like operations with bytes. -o Adding utils.int8store() -o Fixing utils.read_lc_string() to read big strings. -o (Py3) Optimized utils.read_lc_string_list() using memoryview -o (Py3) Refactored utils.read_lc_int() -o Adding support for the MySQL compressed protocol. To use you need to set -the client constants.ClientFlag.COMPRESS -o Supporting SSL connections when Python's ssl module is available. SSL -certificates and keys can be given during connection using the ssl_ca, ssl_cert, ssl_key=None arguments for MySQLConnection.connect(). +- Fixed setting sql_mode to a list of modes. (BUG#13365985) +- Fixed handling of errors after authentication for Python v3 (BUG#13364285) -Bugs fixed: +- Various improvements around receiving and sending MySQL packets: + * Refactored MySQLBaseSocket.recv_plain and + MySQLBaseSocket.recv_compressed: they are now simpler and handle socket + errors better. + * Using sendall() for sending packets. diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..fcb58d0 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,34 @@ +Metadata-Version: 1.0 +Name: mysql-connector-python +Version: 1.0.12 +Summary: MySQL driver written in Python +Home-page: http://dev.mysql.com/doc/connector-python/en/index.html +Author: Oracle and/or its affiliates +Author-email: UNKNOWN +License: GNU GPLv2 (with FOSS License Exception) +Download-URL: http://dev.mysql.com/downloads/connector/python/ +Description: MySQL driver written in Python which does not depend on MySQL C client + libraries and implements the DB API v2.0 specification (PEP-249). + +Keywords: mysql db +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Other Environment +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: GNU General Public License (GPL) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.1 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Topic :: Database +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules diff --git a/README b/README index d3e5dd0..1d864f7 100644 --- a/README +++ b/README @@ -1,80 +1,44 @@ +MySQL Connector/Python 1.0 + +This is a release of MySQL Connector/Python, Oracle's dual- +license Python Driver for MySQL. For the avoidance of +doubt, this particular copy of the software is released +under the version 2 of the GNU General Public License. +MySQL Connector/Python is brought to you by Oracle. + +Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + +License information can be found in the COPYING file. + +MySQL FOSS License Exception +We want free and open source software applications under +certain licenses to be able to use the GPL-licensed MySQL +Connector/Python (specified GPL-licensed MySQL client libraries) +despite the fact that not all such FOSS licenses are +compatible with version 2 of the GNU General Public License. +Therefore there are special exceptions to the terms and +conditions of the GPLv2 as applied to these client libraries, +which are identified and described in more detail in the +FOSS License Exception at + + +This software is OSI Certified Open Source Software. +OSI Certified is a certification mark of the Open Source Initiative. + +This distribution may include materials developed by third +parties. For license and attribution notices for these +materials, please refer to the documentation that accompanies +this distribution (see the "Licenses for Third-Party Components" +appendix) or view the online documentation at + +A copy of the license/notices is also reproduced below. + +GPLv2 Disclaimer +For the avoidance of doubt, except that if any license choice +other than GPL or LGPL is available it will apply instead, +Oracle elects to use only the General Public License version 2 +(GPLv2) at this time for any software where a choice of GPL +license versions is made available with the language indicating +that GPLv2 or any later version may be used, or where a choice +of which version of the GPL is applied is otherwise unspecified. -MySQL Connector/Python MySQL driver written in Python -============================================================================== -Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -Use is subject to license terms. (See COPYING) - -MySQL Connector/Python is implementing the MySQL Client/Server protocol -completely in Python. This means you don't have to compile anything or MySQL -(client library) doesn't even have to be installed on the machine. - -Disclaimer -===================== - -!!!!!!!!!!! THIS IS STILL IN DEVELOPMENT !!!!!!!!!!!!!!!!!!! -!!!!!!!! EXPECT THING TO NOT WORK OR GO WRONG !!!!!!!!!!!!!! -!!!! DO NOT USE IN PRODUCTION etc.. etc... !!!!!!!!!!!!!!!!! - -Ah, and make backups! - -Dependencies -===================== - -* Python 2.5 or greater -* Python 3.1 or greater - -Connects to: -* MySQL 4.1 or greater -* Will not work with MySQL servers using 'old passwords'! - -Installation -===================== - -To install MySQL Connector/Python, first unpack the ZIP archive or -'tar ball' you download: - - shell> unzip mysql-connector-python-0.2.0-devel.zip - or - shell> tar xzf mysql-connector-python-0.2.0-devel.tar.gz - -Then simply execute the setup.py script with the 'install' argument: - - shell> cd mysql-connector-python-0.2.0/ - shell> python setup.py install - -setup.py will figure out which Python version you have and install the -correct modules. - -Usage -===================== - -There are examples provided for both Python v2 and v3. Please check -the following directories which comes with the MySQL Connector/Python -distribution: - -* For Python v2: python2/examples/ -* For Python v3: python3/examples/ - -Simply execute the examples as follows after installation: - - shell> python engines.py - -Testing -===================== - -Unit tests are provided and can be run using the unittests.py script. Mind -that you best edit the file so the connection parameters are correct for your -MySQL Server. - -Report problems -===================== - -Report problems using LaunchPad's bug system here: - https://bugs.launchpad.net/myconnpy - -Credits -===================== - -* Andy Dustman - we owe you big time for MySQLdb -* Jess Balint - early development of native Python driver, helped a bit -* Everyone reporting bugs through Launchpad diff --git a/addon.xml b/addon.xml index 619c757..44c5a44 100644 --- a/addon.xml +++ b/addon.xml @@ -1,16 +1,17 @@ + version="1.0.12" + provider-name="MySQL"> - + - MySQL Connector/Python - MySQL Connector/Python is implementing the MySQL Client/Server protocol completely in Python. This means you don't have to compile anything or MySQL (client library) doesn't even have to be installed on the machine. + MySQL Connector/Python + MySQL Connector/Python is implementing the MySQL Client/Server protocol completely in Python. This means you don't have to compile anything or MySQL (client library) doesn't even have to be installed on the machine. GPLv2 all + http://dev.mysql.com/doc/connector-python/en/index.html diff --git a/download.sh b/download.sh deleted file mode 100644 index 6b16e7a..0000000 --- a/download.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -URL="http://launchpad.net/myconnpy/0.3/0.3.2/+download/mysql-connector-python-0.3.2-devel.tar.gz" -wget $URL -tar xf mysql-connector-python-*.tar.gz -cp -r mysql-connector-python-*/* . -rm -r mysql-connector-python-* - -# rename python2 folder -rm -r lib -mv python2 lib -mv COPYING LICENSE.txt - -# delete stuff we don't need -rm -r python3 -rm metasetupinfo.py PKG-INFO setup.cfg setup.py unittests.py - diff --git a/lib/examples/client.py b/lib/examples/client.py index b17ba8c..45d8393 100644 --- a/lib/examples/client.py +++ b/lib/examples/client.py @@ -1,27 +1,28 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ Simple CLI using the Connector/Python. It does not take arguments so diff --git a/lib/examples/config.py b/lib/examples/config.py index ae06bf7..71ada54 100644 --- a/lib/examples/config.py +++ b/lib/examples/config.py @@ -1,5 +1,28 @@ # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + class Config(object): """Configure me so examples work diff --git a/lib/examples/dates.py b/lib/examples/dates.py index a923a92..ccc3e04 100644 --- a/lib/examples/dates.py +++ b/lib/examples/dates.py @@ -1,10 +1,32 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os -from datetime import datetime -import time +from datetime import datetime, tzinfo, timedelta import mysql.connector @@ -13,14 +35,27 @@ Example using MySQL Connector/Python showing: * How to get datetime, date and time types * Shows also invalid dates returned and handled - +* Force sql_mode to be not set for the active session """ +# Note that by default MySQL takes invalid timestamps. This is for +# backward compatibility. As of 5.0, use sql modes NO_ZERO_IN_DATE,NO_ZERO_DATE +# to prevent this. +_adate = datetime(1977,6,14,21,10,00) +DATA = [ + (_adate.date(), _adate, _adate.time()), + ('0000-00-00', '0000-00-00 00:00:00', '00:00:00'), + ('1000-00-00', '9999-00-00 00:00:00', '00:00:00'), + ] + def main(config): + output = [] db = mysql.connector.Connect(**config) cursor = db.cursor() tbl = 'myconnpy_dates' + + cursor.execute('SET sql_mode = ""') # Drop table if exists, and create it new stmt_drop = "DROP TABLE IF EXISTS %s" % (tbl) @@ -30,54 +65,41 @@ def main(config): CREATE TABLE %s ( `id` tinyint(4) NOT NULL AUTO_INCREMENT, `c1` date DEFAULT NULL, - `c2` datetime DEFAULT NULL, + `c2` datetime NOT NULL, `c3` time DEFAULT NULL, - `c4` timestamp DEFAULT 0, `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) )""" % (tbl) cursor.execute(stmt_create) - - # Note that by default MySQL takes invalid timestamps. This is for - # backward compatibility. As of 5.0, use sql modes NO_ZERO_IN_DATE,NO_ZERO_DATE - # to prevent this. - data = [ - (datetime.now().date(),datetime.now(),time.localtime(),int(time.mktime(datetime.now().timetuple()))), - ('0000-00-00','0000-00-00 00:00:00','00:00:00',0), - ('1000-00-00','9999-00-00 00:00:00','00:00:00',0), - ] # not using executemany to handle errors better - for d in data: - stmt_insert = "INSERT INTO %s (c1,c2,c3,c4) VALUES (%%s,%%s,%%s,FROM_UNIXTIME(%%s))" % (tbl) + stmt_insert = ("INSERT INTO %s (c1,c2,c3) VALUES " + "(%%s,%%s,%%s)" % (tbl)) + for d in DATA: try: cursor.execute(stmt_insert, d) - except (mysql.connector.errors.InterfaceError, TypeError), e: - print "Failed inserting %s\nError: %s\n" % (d,e) + except (mysql.connector.errors.Error, TypeError), e: + output.append("Failed inserting %s\nError: %s\n" % (d,e)) raise - - warnings = cursor.fetchwarnings() - if warnings: - print warnings # Read the names again and print them stmt_select = "SELECT * FROM %s ORDER BY id" % (tbl) cursor.execute(stmt_select) for row in cursor.fetchall(): - print "%3s | %10s | %19s | %8s | %19s |" % ( + output.append("%3s | %10s | %19s | %8s |" % ( row[0], row[1], row[2], row[3], - row[4], - ) + )) # Cleaning up, dropping the table again cursor.execute(stmt_drop) cursor.close() db.close() + return output if __name__ == '__main__': # @@ -85,4 +107,5 @@ def main(config): # from config import Config config = Config.dbinfo().copy() - main(config) + out = main(config) + print '\n'.join(out) diff --git a/lib/examples/engines.py b/lib/examples/engines.py index 082f616..b38ae61 100644 --- a/lib/examples/engines.py +++ b/lib/examples/engines.py @@ -1,6 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os import mysql.connector @@ -13,6 +36,7 @@ """ def main(config): + output = [] db = mysql.connector.Connect(**config) cursor = db.cursor() @@ -22,9 +46,10 @@ def main(config): rows = cursor.fetchall() for row in rows: - print row + output.append(repr(row)) db.close() + return output if __name__ == '__main__': # @@ -32,4 +57,5 @@ def main(config): # from config import Config config = Config.dbinfo().copy() - main(config) + out = main(config) + print '\n'.join(out) diff --git a/lib/examples/inserts.py b/lib/examples/inserts.py index 3a4ded3..e6aba3f 100644 --- a/lib/examples/inserts.py +++ b/lib/examples/inserts.py @@ -1,6 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os import mysql.connector @@ -15,6 +38,7 @@ """ def main(config): + output = [] db = mysql.connector.Connect(**config) cursor = db.cursor() @@ -38,10 +62,6 @@ def main(config): names = ( ('Geert',info), ('Jan',info), ('Michel',info) ) stmt_insert = "INSERT INTO names (name,info) VALUES (%s,%s)" cursor.executemany(stmt_insert, names) - - warnings = cursor.fetchwarnings() - if warnings: - print warnings db.commit() # Read the names again and print them @@ -49,12 +69,15 @@ def main(config): cursor.execute(stmt_select) for row in cursor.fetchall(): - print "%d | %s | %d\nInfo: %s..\n" % (row[0], row[1], row[3], row[2][20]) + output.append("%d | %s | %d\nInfo: %s..\n" % + (row[0], row[1], row[3], row[2][20])) # Cleaning up, dropping the table again cursor.execute(stmt_drop) - + + cursor.close() db.close() + return output if __name__ == '__main__': # @@ -62,5 +85,6 @@ def main(config): # from config import Config config = Config.dbinfo().copy() - main(config) + out = main(config) + print '\n'.join(out) \ No newline at end of file diff --git a/lib/examples/microseconds.py b/lib/examples/microseconds.py new file mode 100644 index 0000000..e6e09f5 --- /dev/null +++ b/lib/examples/microseconds.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from datetime import time + +import mysql.connector + +""" +Example using MySQL Connector/Python showing: +* How to save timestamps including microseconds +* Check the MySQL server version + +NOTE: This only works with MySQL 5.6.4 or greater. This example will work +with earlier versions, but the microseconds information will be lost. + +Story: We keep track of swimmers in a freestyle 4x 100m relay swimming event +with millisecond precision. +""" + +CREATE_TABLE = """ +CREATE TABLE relay_laps ( +teamid TINYINT UNSIGNED NOT NULL, +swimmer TINYINT UNSIGNED NOT NULL, +lap TIME(3), +PRIMARY KEY (teamid, swimmer) +) ENGINE=InnoDB +""" + +def main(config): + output = [] + cnx = mysql.connector.Connect(**config) + if cnx.get_server_version() < (5,6,4): + output.append( + "MySQL %s does not support fractional precision"\ + " for timestamps." % cnx.get_server_info()) + return output + cursor = cnx.cursor() + + try: + cursor.execute("DROP TABLE IF EXISTS relay_laps") + except: + # Ignoring the fact that it was not there + pass + cursor.execute(CREATE_TABLE) + + teams = {} + teams[1] = [ + (1, time(second=47, microsecond=510000)), + (2, time(second=47, microsecond=20000)), + (3, time(second=47, microsecond=650000)), + (4, time(second=46, microsecond=60000)), + ] + + insert = "INSERT INTO relay_laps (teamid,swimmer,lap) VALUES (%s,%s,%s)" + for team, swimmers in teams.items(): + for swimmer in swimmers: + cursor.execute(insert, (team, swimmer[0], swimmer[1])) + cnx.commit() + + cursor.execute("SELECT * FROM relay_laps") + fmt = "%2s | %2s | %s" + for row in cursor: + output.append(fmt % row) + + try: + cursor.execute("DROP TABLE IF EXISTS relay_laps") + except: + # Ignoring the fact that it was not there + pass + + cursor.close() + cnx.close() + + return output + +if __name__ == '__main__': + # + # Configure MySQL login and database to use in config.py + # + from config import Config + config = Config.dbinfo().copy() + out = main(config) + print '\n'.join(out) + diff --git a/lib/examples/multi_resultsets.py b/lib/examples/multi_resultsets.py index 21d1406..7980c3b 100644 --- a/lib/examples/multi_resultsets.py +++ b/lib/examples/multi_resultsets.py @@ -1,6 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os import mysql.connector @@ -8,11 +31,12 @@ """ Example using MySQL Connector/Python showing: -* sending multiple statements and retriving their results +* sending multiple statements and iterating over the results """ def main(config): + output = [] db = mysql.connector.Connect(**config) cursor = db.cursor() @@ -39,25 +63,26 @@ def main(config): "SELECT name FROM names", ] - cursor.execute(' ; '.join(stmts)) - # 1st INSERT-statement - print "Inserted %d row" % (cursor.rowcount) - - # 1st SELECT - if cursor.next_resultset(): - print "Number of rows: %d" % cursor.fetchone()[0] - - # 2nd INSERT - if cursor.next_resultset(): - print "Inserted %d rows" % (cursor.rowcount) - - # 2nd SELECT - if cursor.next_resultset(): - print "Names in table:", ' '.join([ n[0] for n in cursor.fetchall()]) + # Note 'multi=True' when calling cursor.execute() + for result in cursor.execute(' ; '.join(stmts), multi=True): + if result.with_rows: + if result.statement == stmts[3]: + output.append("Names in table: " + + ' '.join([ name[0] for name in result])) + else: + output.append("Number of rows: %d" % result.fetchone()[0]) + else: + if result.rowcount > 1: + tmp = 's' + else: + tmp = '' + output.append("Inserted %d row%s" % (result.rowcount, tmp)) cursor.execute(stmt_drop) + cursor.close() db.close() + return output if __name__ == '__main__': # @@ -65,4 +90,5 @@ def main(config): # from config import Config config = Config.dbinfo().copy() - main(config) + out = main(config) + print '\n'.join(out) diff --git a/lib/examples/transaction.py b/lib/examples/transaction.py index d276a67..dc37a1c 100644 --- a/lib/examples/transaction.py +++ b/lib/examples/transaction.py @@ -1,6 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os import mysql.connector @@ -15,6 +38,7 @@ """ def main(config): + output = [] db = mysql.connector.Connect(**config) cursor = db.cursor() @@ -34,24 +58,24 @@ def main(config): warnings = cursor.fetchwarnings() if warnings: ids = [ i for l,i,m in warnings] - print "Oh oh.. we got warnings.." + output.append("Oh oh.. we got warnings..") if 1266L in ids: - print """ + output.append(""" Table was created as MYISAM, no transaction support. Bailing out, no use to continue. Make sure InnoDB is available! - """ + """) db.close() return # Insert 3 records - print "Inserting data" + output.append("Inserting data") names = ( ('Geert',), ('Jan',), ('Michel',) ) stmt_insert = "INSERT INTO names (name) VALUES (%s)" cursor.executemany(stmt_insert, names) # Roll back!!!! - print "Rolling back transaction" + output.append("Rolling back transaction") db.rollback() # There should be no data! @@ -64,10 +88,10 @@ def main(config): raise if rows == []: - print("No data, all is fine.") + output.append("No data, all is fine.") else: - print("Something is wrong, we have data although we rolled back!") - print(rows) + output.append("Something is wrong, we have data although we rolled back!") + output.append([repr(r) for r in rows]) raise # Do the insert again. @@ -75,22 +99,23 @@ def main(config): # Data should be already there cursor.execute(stmt_select) - print "Data before commit:" + output.append("Data before commit:") for row in cursor.fetchall(): - print "%d | %s" % (row[0], row[1]) + output.append("%d | %s" % (row[0], row[1])) # Do a commit db.commit() cursor.execute(stmt_select) - print "Data after commit:" + output.append("Data after commit:") for row in cursor.fetchall(): - print "%d | %s" % (row[0], row[1]) + output.append("%d | %s" % (row[0], row[1])) # Cleaning up, dropping the table again - #cursor.execute(stmt_drop) + cursor.execute(stmt_drop) db.close() + return output if __name__ == '__main__': # @@ -98,4 +123,5 @@ def main(config): # from config import Config config = Config.dbinfo().copy() - main(config) + out = main(config) + print '\n'.join(out) diff --git a/lib/examples/unicode.py b/lib/examples/unicode.py index a028697..4d86e81 100644 --- a/lib/examples/unicode.py +++ b/lib/examples/unicode.py @@ -1,6 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os import mysql.connector @@ -20,12 +43,13 @@ """ def main(config): + output = [] db = mysql.connector.Connect(**config) cursor = db.cursor() # Show the unicode string we're going to use unistr = u"\u00bfHabla espa\u00f1ol?" - print "Unicode string: %s" % unistr.encode('utf8') + output.append("Unicode string: %s" % unistr.encode('utf8')) # Drop table if exists, and create it new stmt_drop = "DROP TABLE IF EXISTS unicode" @@ -48,11 +72,14 @@ def main(config): cursor.execute(stmt_select, (1,)) row = cursor.fetchone() - print "Unicode string coming from db: %s" % row[0].encode('utf8') + output.append("Unicode string coming from db: %s" % row[0].encode('utf8')) # Cleaning up, dropping the table again cursor.execute(stmt_drop) + + cursor.close() db.close() + return output if __name__ == '__main__': # @@ -61,4 +88,5 @@ def main(config): from config import Config config = Config.dbinfo().copy() print info - main(config) + out = main(config) + print '\n'.join(out) diff --git a/lib/examples/warnings.py b/lib/examples/warnings.py index 18363d0..eccc69d 100644 --- a/lib/examples/warnings.py +++ b/lib/examples/warnings.py @@ -1,6 +1,29 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys, os import mysql.connector @@ -12,33 +35,35 @@ """ +STMT = "SELECT 'abc'+1" + def main(config): + output = [] config['get_warnings'] = True db = mysql.connector.Connect(**config) cursor = db.cursor() + cursor.sql_mode = '' - stmt_select = "SELECT 'abc'+1" - - print "Remove all sql modes.." - cursor.execute("SET sql_mode = ''") # Make sure we don't have strict on - - print "Execute '%s'" % stmt_select - cursor.execute(stmt_select) + output.append("Executing '%s'" % STMT) + cursor.execute(STMT) cursor.fetchall() warnings = cursor.fetchwarnings() if warnings: - print warnings + for w in warnings: + output.append("%d: %s" % (w[1],w[2])) else: - print "We should have got warnings." raise StandardError("Got no warnings") + cursor.close() db.close() - + return output + if __name__ == '__main__': # # Configure MySQL login and database to use in config.py # from config import Config config = Config.dbinfo().copy() - main(config) + out = main(config) + print '\n'.join(out) \ No newline at end of file diff --git a/lib/mysql/connector/__init__.py b/lib/mysql/connector/__init__.py index da47cd5..f09e2a8 100644 --- a/lib/mysql/connector/__init__.py +++ b/lib/mysql/connector/__init__.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ MySQL Connector/Python - MySQL drive written in Python @@ -30,15 +30,14 @@ threadsafety = 1 paramstyle = 'pyformat' -# Read the version from an generated file -import _version -__version__ = _version.version - -from connection import MySQLConnection -from errors import * -from constants import FieldFlag, FieldType, CharacterSet,\ - RefreshOption, ClientFlag -from dbapi import * +from mysql.connector.connection import MySQLConnection +from mysql.connector.errors import ( + Error, Warning, InterfaceError, DatabaseError, + NotSupportedError, DataError, IntegrityError, ProgrammingError, + OperationalError, InternalError, custom_error_exception) +from mysql.connector.constants import (FieldFlag, FieldType, CharacterSet, + RefreshOption, ClientFlag) +from mysql.connector.dbapi import * def Connect(*args, **kwargs): """Shortcut for creating a connection.MySQLConnection object.""" @@ -46,7 +45,7 @@ def Connect(*args, **kwargs): connect = Connect __all__ = [ - 'MySQLConnection', 'Connect', + 'MySQLConnection', 'Connect', 'custom_error_exception', # Some useful constants 'FieldType','FieldFlag','ClientFlag','CharacterSet','RefreshOption', diff --git a/lib/mysql/connector/_version.py b/lib/mysql/connector/_version.py deleted file mode 100644 index 204633b..0000000 --- a/lib/mysql/connector/_version.py +++ /dev/null @@ -1,28 +0,0 @@ -# MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2011, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -"""Holds version of MySQL Connector/Python -""" - -# Next line is generated -version = (0, 3, 2, 'devel', 292) diff --git a/lib/mysql/connector/connection.py b/lib/mysql/connector/connection.py index e8679e9..1e1d591 100644 --- a/lib/mysql/connector/connection.py +++ b/lib/mysql/connector/connection.py @@ -1,418 +1,300 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2011, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) - +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -"""Implementing communication to MySQL servers +"""Implementing communication with MySQL servers. """ -import socket -import struct import os -import weakref -from collections import deque -import zlib -try: - import ssl -except ImportError: - pass - -import constants -import conversion -import protocol -import errors -import utils -import cursor - -MAX_PACKET_LENGTH = 16777215 - -class MySQLBaseSocket(object): - """Base class for MySQL Connections subclasses. - - Should not be used directly but overloaded, changing the - open_connection part. Examples of subclasses are - MySQLTCPSocket - MySQLUnixSocket - """ - def __init__(self): - self.sock = None # holds the socket connection - self.connection_timeout = None - self.buffer = deque() - self.recvsize = 8192 - self.send = self.send_plain - self.recv = self.recv_plain - - def open_connection(self): - pass - - def close_connection(self): - try: - self.sock.close() - except: - pass - - def get_address(self): - pass - - def _prepare_packets(self, buf, pktnr): - pkts = [] - pllen = len(buf) - while pllen > MAX_PACKET_LENGTH: - pkts.append('\xff\xff\xff' + struct.pack(' 16777215: - pkts = self._prepare_packets(buf,pktnr) - tmpbuf = ''.join(pkts) - del pkts - seqid = 0 - zbuf = zlib.compress(tmpbuf[:16384]) - zpkts.append(struct.pack(' MAX_PACKET_LENGTH: - zbuf = zlib.compress(tmpbuf[:MAX_PACKET_LENGTH]) - zpkts.append(struct.pack(' 50: - zbuf = zlib.compress(pkt) - zpkts.append(struct.pack('= 4: - pktsize = struct.unpack(" 0 and totalsize >= pktsize+4: - size = pktsize+4 - self.buffer.append(buf[0:size]) - buf = buf[size:] - pktsize = 0 - if not buf: - try: - buf = self.buffer.popleft() - if buf[4] == '\xff': - errors.raise_error(buf) - else: - return buf - except IndexError, e: - break - elif totalsize < pktsize+4: - buf += self.sock.recv(self.recvsize) - except socket.timeout, e: - raise errors.InterfaceError(errno=2013) - except socket.error, e: - raise errors.InterfaceError(errno=2055, - values=dict(socketaddr=self.get_address(),errno=e.errno)) - except: - raise +class MySQLConnection(object): + """Connection to a MySQL Server""" + def __init__(self, **kwargs): + self._protocol = None + self._socket = None + self._handshake = None + self._server_version = None + self.converter = None + self._converter_class = None - def recv_compressed(self): - try: - return self.buffer.popleft() - except IndexError: - pass - - pkts = [] - zpktsize = 0 - try: - buf = self.sock.recv(self.recvsize) - while buf: - totalsize = len(buf) - if zpktsize == 0 and totalsize >= 7: - zpktsize = struct.unpack(" 0 and totalsize >= zpktsize+7: - size = zpktsize+7 - pkts.append(buf[0:size]) - buf = buf[size:] - zpktsize = 0 - # Keep reading for packets that were to big - if pktsize == 16384: - buf = self.sock.recv(self.recvsize) - elif not buf: - break - zpktsize = 0 - elif totalsize < pktsize+7: - buf += self.sock.recv(self.recvsize) - except socket.timeout, e: - raise errors.InterfaceError(errno=2013) - except socket.error, e: - raise errors.InterfaceError(errno=2055, - values=dict(socketaddr=self.get_address(),errno=e.errno)) - except: - raise - - bigbuf = '' - tmp = [] - for pkt in pkts: - pktsize = struct.unpack(" 0: + self.connect(**kwargs) + + def _get_self(self): + """Return self for weakref.proxy - def switch_to_ssl(self): + This method is used when the original object is needed when using + weakref.proxy. + """ + return self + + def _do_handshake(self): + """Get the handshake from the MySQL server""" + packet = self._socket.recv() + if packet[4] == '\xff': + raise errors.get_exception(packet) + try: - self.sock = ssl.wrap_socket(self.sock, - keyfile=self._ssl_key, certfile=self._ssl_cert, - ca_certs=self._ssl_ca, cert_reqs=ssl.CERT_REQUIRED, - do_handshake_on_connect=False, - ssl_version=ssl.PROTOCOL_TLSv1) - self.sock.do_handshake() - except NameError: + handshake = self._protocol.parse_handshake(packet) + except Exception, err: + raise errors.InterfaceError('Failed parsing handshake; %s' % err) + + regex_ver = re.compile("^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") + match = regex_ver.match(handshake['server_version_original']) + if not match: + raise errors.InterfaceError("Failed parsing MySQL version") + + version = tuple([ int(v) for v in match.groups()[0:3]]) + if version < (4, 1): + raise errors.InterfaceError( + "MySQL Version '%s' is not supported." % \ + handshake['server_version_original']) + + self._handshake = handshake + self._server_version = version + + def _do_auth(self, username=None, password=None, database=None, + client_flags=0, charset=33, ssl_options=None): + """Authenticate with the MySQL server + """ + if client_flags & ClientFlag.SSL and ssl_options: + packet = self._protocol.make_auth_ssl(charset=charset, + client_flags=client_flags) + self._socket.send(packet) + self._socket.switch_to_ssl(**ssl_options) + + packet = self._protocol.make_auth( + seed=self._handshake['scramble'], + username=username, password=password, database=database, + charset=charset, client_flags=client_flags) + self._socket.send(packet) + packet = self._socket.recv() + + if packet[4] == '\xfe': raise errors.NotSupportedError( - "Python installation has no SSL support") - except ssl.SSLError, e: - raise errors.InterfaceError("SSL error: %s" % e) - -class MySQLUnixSocket(MySQLBaseSocket): - """Opens a connection through the UNIX socket of the MySQL Server.""" - - def __init__(self, unix_socket='/tmp/mysql.sock'): - MySQLBaseSocket.__init__(self) - self.unix_socket = unix_socket - - def get_address(self): - return self.unix_socket - - def open_connection(self): - """Opens a UNIX socket and checks the MySQL handshake.""" - try: - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.sock.settimeout(self.connection_timeout) - self.sock.connect(self.unix_socket) - except socket.error, e: - try: - m = e.errno - except: - m = e - raise errors.InterfaceError(errno=2002, - values=dict(socketaddr=self.get_address(),errno=m)) - except StandardError, e: - raise errors.InterfaceError('%s' % e) - -class MySQLTCPSocket(MySQLBaseSocket): - """Opens a TCP connection to the MySQL Server.""" - - def __init__(self, host='127.0.0.1', port=3306): - MySQLBaseSocket.__init__(self) - self.server_host = host - self.server_port = port - - def get_address(self): - return "%s:%s" % (self.server_host,self.server_port) - - def open_connection(self): - """Opens a TCP Connection and checks the MySQL handshake.""" + "Authentication with old (insecure) passwords "\ + "is not supported. For more information, lookup "\ + "Password Hashing in the latest MySQL manual") + elif packet[4] == '\xff': + raise errors.get_exception(packet) + try: - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.settimeout(self.connection_timeout) - self.sock.connect( (self.server_host, self.server_port) ) - except socket.error, e: - try: - m = e.errno - except: - m = e - raise errors.InterfaceError(errno=2003, - values=dict(socketaddr=self.get_address(),errno=m)) - except StandardError, e: - raise errors.InterfaceError('%s' % e) + if (not (client_flags & ClientFlag.CONNECT_WITH_DB) + and database): + self.cmd_init_db(database) except: raise -class MySQLConnection(object): - """MySQL""" + return True - def __init__(self, *args, **kwargs): - """Initializing""" - self.protocol = None - self.converter = None - self.cursors = [] + def config(self, **kwargs): + """Configure the MySQL Connection - self.client_flags = constants.ClientFlag.get_default() - self._charset = 33 + This method allows you to configure the MySQLConnection instance. - self._username = '' - self._database = '' - self._server_host = '127.0.0.1' - self._server_port = 3306 - self._unix_socket = None - self.client_host = '' - self.client_port = 0 - - self.affected_rows = 0 - self.server_status = 0 - self.warning_count = 0 - self.field_count = 0 - self.insert_id = 0 - self.info_msg = '' - self.use_unicode = True - self.get_warnings = False - self.raise_on_warnings = False - self.connection_timeout = None - self.buffered = False - self.unread_result = False - self.raw = False - - if len(kwargs) > 0: - self.connect(*args, **kwargs) - - def connect(self, database=None, user='', password='', - host='127.0.0.1', port=3306, unix_socket=None, - use_unicode=True, charset='utf8', collation=None, - autocommit=False, - time_zone=None, sql_mode=None, - get_warnings=False, raise_on_warnings=False, - connection_timeout=None, client_flags=0, - buffered=False, raw=False, - ssl_ca=None, ssl_cert=None, ssl_key=None, - passwd=None, db=None, connect_timeout=None, dsn=None): - if db and not database: - database = db - if passwd and not password: - password = passwd - if connect_timeout and not connection_timeout: - connection_timeout = connect_timeout - - if dsn is not None: - errors.NotSupportedError("Data source name is not supported") - - self._server_host = host - self._server_port = port - self._unix_socket = unix_socket - if database is not None: - self._database = database.strip() - else: - self._database = None - self._username = user - - self.set_warnings(get_warnings,raise_on_warnings) - self.connection_timeout = connection_timeout - self.buffered = buffered - self.raw = raw - self.use_unicode = use_unicode - self.set_client_flags(client_flags) - self._charset = constants.CharacterSet.get_charset_info(charset)[0] - - if user or password: + Raises on errors. + """ + config = kwargs.copy() + if 'dsn' in config: + raise errors.NotSupportedError("Data source name is not supported") + + # Configure how we handle MySQL warnings + try: + self.get_warnings = config['get_warnings'] + del config['get_warnings'] + except KeyError: + pass # Leave what was set or default + try: + self.raise_on_warnings = config['raise_on_warnings'] + del config['raise_on_warnings'] + except KeyError: + pass # Leave what was set or default + + # Configure client flags + try: + default = ClientFlag.get_default() + self.set_client_flags(config['client_flags'] or default) + del config['client_flags'] + except KeyError: + pass # Missing client_flags-argument is OK + + # Configure character set and collation + if ('charset' in config or 'collation' in config): + try: + charset = config['charset'] + del config['charset'] + except KeyError: + charset = None + try: + collation = config['collation'] + del config['collation'] + except KeyError: + collation = None + self._charset_id = CharacterSet.get_charset_info(charset, + collation)[0] + + # Compatible configuration with other drivers + compat_map = [ + # (,) + ('db','database'), + ('passwd','password'), + ('connect_timeout','connection_timeout'), + ] + for compat, translate in compat_map: + try: + if translate not in config: + config[translate] = config[compat] + del config[compat] + except KeyError: + pass # Missing compat argument is OK + + # Configure login information + if ('user' in config or 'password' in config): + try: + user = config['user'] + del config['user'] + except KeyError: + user = self._user + try: + password = config['password'] + del config['password'] + except KeyError: + password = self._password self.set_login(user, password) - self.disconnect() - self._open_connection(username=user, password=password, database=database, - client_flags=self.client_flags, charset=charset, - ssl=(ssl_ca, ssl_cert, ssl_key)) - self._post_connection(time_zone=time_zone, sql_mode=sql_mode, - collation=collation) + # Check network locations + try: + self._port = int(config['port']) + del config['port'] + except KeyError: + pass # Missing port argument is OK + except ValueError: + raise errors.InterfaceError( + "TCP/IP port number should be an integer") - def _get_connection(self, prtcls=None): + # Other configuration + set_ssl_flag = False + for key, value in config.items(): + try: + DEFAULT_CONFIGURATION[key] + except KeyError: + raise AttributeError("Unsupported argument '%s'" % key) + # SSL Configuration + if key == 'ssl_verify_cert': + set_ssl_flag = True + self._ssl.update({'verify_cert': value}) + elif key.startswith('ssl_') and value: + set_ssl_flag = True + self._ssl.update({key.replace('ssl_', ''): value}) + else: + attribute = '_' + key + try: + setattr(self, attribute, value.strip()) + except AttributeError: + setattr(self, attribute, value) + + if set_ssl_flag: + if 'verify_cert' not in self._ssl: + self._ssl['verify_cert'] = \ + DEFAULT_CONFIGURATION['ssl_verify_cert'] + required_keys = set(['ca', 'cert', 'key']) + diff = list(required_keys - set(self._ssl.keys())) + if diff: + missing_attrs = [ "ssl_" + val for val in diff ] + raise AttributeError("Missing SSL argument(s): %s" % ( + ', '.join(missing_attrs))) + self.set_client_flags([ClientFlag.SSL]) + + def _get_connection(self): """Get connection based on configuration This method will return the appropriated connection object using @@ -425,105 +307,554 @@ def _get_connection(self, prtcls=None): conn = MySQLUnixSocket(unix_socket=self.unix_socket) else: conn = MySQLTCPSocket(host=self.server_host, - port=self.server_port) - conn.set_connection_timeout(self.connection_timeout) + port=self.server_port, + force_ipv6=self._force_ipv6) + conn.set_connection_timeout(self._connection_timeout) return conn - def _open_connection(self, username=None, password=None, database=None, - client_flags=None, charset=None, ssl=None): - """Opens the connection + def _open_connection(self): + """Open the connection to the MySQL server + + This method sets up and opens the connection to the MySQL server. + """ + self._socket = self._get_connection() + self._socket.open_connection() + self._do_handshake() + self._do_auth(self._user, self._password, + self._database, self._client_flags, self._charset_id, + self._ssl) + self.set_converter_class(MySQLConverter) + if self._client_flags & ClientFlag.COMPRESS: + self._socket.recv = self._socket.recv_compressed + self._socket.send = self._socket.send_compressed + + def _post_connection(self): + """Executes commands after connection has been established + + This method executes commands after the connection has been + established. Some setting like autocommit, character set, and SQL mode + are set using this method. + """ + self.set_charset_collation(charset=self._charset_id) + self.autocommit = self._autocommit + if self._time_zone: + self.time_zone = self._time_zone + if self._sql_mode: + self.sql_mode = self._sql_mode + + def connect(self, **kwargs): + """Connect to the MySQL server + + This method sets up the connection to the MySQL server. If no + arguments are given, it will use the already configured or default + values. + """ + if len(kwargs) > 0: + self.config(**kwargs) + + self._protocol = MySQLProtocol() + + self.disconnect() + self._open_connection() + self._post_connection() + + def disconnect(self): + """Disconnect from the MySQL server + """ + if not self._socket: + return - Open the connection, check the MySQL version, and set the - protocol. - """ try: - self.protocol = protocol.MySQLProtocol(self._get_connection()) - self.protocol.do_handshake() - version = self.protocol.server_version - if version < (4,1): - raise errors.InterfaceError( - "MySQL Version %s is not supported." % version) - if client_flags & constants.ClientFlag.SSL: - self.protocol.conn.set_ssl(*ssl) - self.protocol.do_auth(username, password, database, client_flags, - self._charset) - (self._charset, self.charset_name, c) = \ - constants.CharacterSet.get_charset_info(charset) - self.set_converter_class(conversion.MySQLConverter) - if client_flags & constants.ClientFlag.COMPRESS: - self.protocol.conn.recv = self.protocol.conn.recv_compressed - self.protocol.conn.send = self.protocol.conn.send_compressed - except: - raise + self.cmd_quit() + self._socket.close_connection() + except errors.Error: + pass # Getting an exception would mean we are disconnected. + close = disconnect + + def _send_cmd(self, command, argument=None, packet_number=0): + """Send a command to the MySQL server - def _post_connection(self, time_zone=None, autocommit=False, - sql_mode=None, collation=None): - """Post connection session setup + This method sends a command with an optional argument. - Should be called after a connection was established""" + Returns a MySQL packet + """ + if self.unread_result: + raise errors.InternalError("Unread result found.") + try: - if collation is not None: - self.collation = collation - self.autocommit = autocommit - if time_zone is not None: - self.time_zone = time_zone - if sql_mode is not None: - self.sql_mode = sql_mode - except: - raise + self._socket.send(self._protocol.make_command(command, argument), + packet_number) + except AttributeError: + raise errors.OperationalError("MySQL Connection not available.") - def is_connected(self): + return self._socket.recv() + + def _send_data(self, fp, send_empty_packet=False): + """Send data to the MySQL server + + This method accepts a file-like object and sends its data + as is to the MySQL server. If the send_empty_packet is + True, it will send an extra empty package (for example + when using LOAD LOCAL DATA INFILE). + + Returns a MySQL packet. """ - Check whether we are connected to the MySQL server. + if self.unread_result: + raise errors.InternalError("Unread result found.") + + if not hasattr(fp, 'read'): + raise ValueError("expecting a file-like object") + + try: + buf = fp.read(NET_BUFFER_LENGTH-16) + while buf: + self._socket.send(buf) + buf = fp.read(NET_BUFFER_LENGTH-16) + except AttributeError: + raise errors.OperationalError("MySQL Connection not available.") + + if send_empty_packet: + try: + self._socket.send('') + except AttributeError: + raise errors.OperationalError( + "MySQL Connection not available.") + + return self._socket.recv() + + def _toggle_have_next_result(self, flags): + """Toggle whether there more results + + This method checks the whether MORE_RESULTS_EXISTS is set in flags. """ - return self.protocol.cmd_ping() - ping = is_connected + if flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, flags): + self._have_next_result = True + else: + self._have_next_result = False - def disconnect(self): + def _handle_ok(self, packet): + """Handle a MySQL OK packet + + This method handles a MySQL OK packet. When the packet is found to + be an Error packet, an error will be raised. If the packet is neither + an OK or an Error packet, errors.InterfaceError will be raised. + + Returns a dict() """ - Disconnect from the MySQL server. + if packet[4] == '\x00': + ok = self._protocol.parse_ok(packet) + self._toggle_have_next_result(ok['server_status']) + return ok + elif packet[4] == '\xff': + raise errors.get_exception(packet) + raise errors.InterfaceError('Expected OK packet') + + def _handle_eof(self, packet): + """Handle a MySQL EOF packet + + This method handles a MySQL EOF packet. When the packet is found to + be an Error packet, an error will be raised. If the packet is neither + and OK or an Error packet, errors.InterfaceError will be raised. + + Returns a dict() """ - if not self.protocol: - return + if packet[4] == '\xfe': + eof = self._protocol.parse_eof(packet) + self._toggle_have_next_result(eof['status_flag']) + return eof + elif packet[4] == '\xff': + raise errors.get_exception(packet) + raise errors.InterfaceError('Expected EOF packet') + + def _handle_load_data_infile(self, filename): + """Handle a LOAD DATA INFILE LOCAL request""" + try: + fp = open(filename, 'rb') + except IOError: + # Send a empty packet to cancel the operation + try: + self._socket.send('') + except AttributeError: + raise errors.OperationalError( + "MySQL Connection not available.") + raise errors.InterfaceError("File '%s' could not be read" % + filename) + + return self._handle_ok(self._send_data(fp, send_empty_packet=True)) + + def _handle_result(self, packet): + """Handle a MySQL Result + + This method handles a MySQL result, for example, after sending the + query command. OK and EOF packets will be handled and returned. If + the packet is an Error packet, an errors.Error-exception will be + raised. + + The dictionary returned of: + - columns: column information + - eof: the EOF-packet information + + Returns a dict() + """ + if not packet or len(packet) < 4: + raise errors.InterfaceError('Empty response') + elif packet[4] == '\x00': + return self._handle_ok(packet) + elif packet[4] == '\xfb': + return self._handle_load_data_infile(packet[5:]) + elif packet[4] == '\xfe': + return self._handle_eof(packet) + elif packet[4] == '\xff': + raise errors.get_exception(packet) + + # We have a text result set + column_count = self._protocol.parse_column_count(packet) + if not column_count or not isinstance(column_count, int): + raise errors.InterfaceError('Illegal result set.') + + columns = [None,]*column_count + for i in xrange(0, column_count): + columns[i] = self._protocol.parse_column(self._socket.recv()) + + eof = self._handle_eof(self._socket.recv()) + self.unread_result = True + return {'columns': columns, 'eof': eof} + + def get_rows(self, count=None): + """Get all rows returned by the MySQL server + + This method gets all rows returned by the MySQL server after sending, + for example, the query command. The result is a tuple consisting of + a list of rows and the EOF packet. + + Returns a tuple() + """ + if not self.unread_result: + raise errors.InternalError("No result set available.") + + rows = self._protocol.read_text_result(self._socket, count) + if rows[-1] is not None: + self._toggle_have_next_result(rows[-1]['status_flag']) + self.unread_result = False + + return rows + + def get_row(self): + """Get the next rows returned by the MySQL server + + This method gets one row from the result set after sending, for + example, the query command. The result is a tuple consisting of the + row and the EOF packet. + If no row was available in the result set, the row data will be None. + + Returns a tuple. + """ + (rows, eof) = self.get_rows(count=1) + if len(rows): + return (rows[0], eof) + return (None, eof) + + def cmd_init_db(self, database): + """Change the current database + + This method changes the current (default) database by sending the + INIT_DB command. The result is a dictionary containing the OK packet + information. + + Returns a dict() + """ + return self._handle_ok(self._send_cmd(ServerCmd.INIT_DB, database)) + + def cmd_query(self, statement): + """Send a statement to the MySQL server + + This method sends the given statement to the MySQL server and + returns a result. If you need to send multiple statements, you + have to use the cmd_query_iter() method. + + The returned dictionary contains information depending on what kind + of query was executed. If the query is a SELECT statement, the result + will contain information about columns. Other statements will return + a dictionary containing an OK or EOF packet. + + Errors received from the MySQL server will be raised as exceptions. + An InterfaceError is raised when multiple results were found. + + Returns a dictionary. + """ + result = self._handle_result(self._send_cmd(ServerCmd.QUERY, + statement)) + + if self._have_next_result: + raise errors.InterfaceError( + 'Use cmd_query_iter for statements with multiple queries.') + return result + + def cmd_query_iter(self, statements): + """Send one or more statements to the MySQL server + + Similar to the cmd_query method, but instead returns a generator + object to iterate through results. It sends the statements to the + MySQL server and through the iterator you can get the results. + + statement = 'SELECT 1; INSERT INTO t1 VALUES (); SELECT 2' + for result in cnx.cmd_query(statement, iterate=True): + if 'columns' in result: + columns = result['columns'] + rows = cnx.get_rows() + else: + # do something useful with INSERT result + + Returns a generator. + """ + # Handle the first query result + yield self._handle_result(self._send_cmd(ServerCmd.QUERY, statements)) + + # Handle next results, if any + while self._have_next_result: + if self.unread_result: + raise errors.InternalError("Unread result found.") + else: + result = self._handle_result(self._socket.recv()) + yield result + + def cmd_refresh(self, options): + """Send the Refresh command to the MySQL server + + This method sends the Refresh command to the MySQL server. The options + argument should be a bitwise value using contants.RefreshOption. + Usage example: + RefreshOption = mysql.connector.RefreshOption + refresh = RefreshOption.LOG | RefreshOption.THREADS + cnx.cmd_refresh(refresh) + + The result is a dictionary with the OK packat information. + + Returns a dict() + """ + return self._handle_ok( + self._send_cmd(ServerCmd.REFRESH, int4store(options))) + + def cmd_quit(self): + """Close the current connection with the server + + This method sends the QUIT command to the MySQL server, closing the + current connection. Since the no response can be returned to the + client, cmd_quit() will return the packet it send. - if self.protocol.conn.sock is not None: - self.protocol.cmd_quit() + Returns a str() + """ + if self.unread_result: + raise errors.InternalError("Unread result found.") + + packet = self._protocol.make_command(ServerCmd.QUIT) + self._socket.send(packet, 0) + return packet + + def cmd_shutdown(self, shutdown_type=None): + """Shut down the MySQL Server + + This method sends the SHUTDOWN command to the MySQL server and is only + possible if the current user has SUPER privileges. The result is a + dictionary containing the OK packet information. + + Note: Most applications and scripts do not the SUPER privilege. + + Returns a dict() + """ + atype = None + if shutdown_type: + if not ShutdownType.get_info(shutdown_type): + raise errors.InterfaceError("Invalid shutdown type") + atype = int1store(shutdown_type) + return self._handle_eof(self._send_cmd(ServerCmd.SHUTDOWN, atype)) + + def cmd_statistics(self): + """Send the statistics command to the MySQL Server + + This method sends the STATISTICS command to the MySQL server. The + result is a dictionary with various statistical information. + + Returns a dict() + """ + if self.unread_result: + raise errors.InternalError("Unread result found.") + + packet = self._protocol.make_command(ServerCmd.STATISTICS) + self._socket.send(packet, 0) + return self._protocol.parse_statistics(self._socket.recv()) + + def cmd_process_info(self): + """Get the process list of the MySQL Server + + This method is a placeholder to notify that the PROCESS_INFO command + is not supported by raising the NotSupportedError. The command + "SHOW PROCESSLIST" should be send using the cmd_query()-method or + using the INFORMATION_SCHEMA database. + + Raises NotSupportedError exception + """ + raise errors.NotSupportedError( + "Not implemented. Use SHOW PROCESSLIST or INFORMATION_SCHEMA") + + def cmd_process_kill(self, mysql_pid): + """Kill a MySQL process + + This method send the PROCESS_KILL command to the server along with + the process ID. The result is a dictionary with the OK packet + information. + + Returns a dict() + """ + return self._handle_ok( + self._send_cmd(ServerCmd.PROCESS_KILL, int4store(mysql_pid))) + + def cmd_debug(self): + """Send the DEBUG command + + This method sends the DEBUG command to the MySQL server, which + requires the MySQL user to have SUPER privilege. The output will go + to the MySQL server error log and the result of this method is a + dictionary with EOF packet information. + + Returns a dict() + """ + return self._handle_eof(self._send_cmd(ServerCmd.DEBUG)) + + def cmd_ping(self): + """Send the PING command + + This method sends the PING command to the MySQL server. It is used to + check if the the connection is still valid. The result of this + method is dictionary with OK packet information. + + Returns a dict() + """ + return self._handle_ok(self._send_cmd(ServerCmd.PING)) + + def cmd_change_user(self, username='', password='', database='', + charset=33): + """Change the current logged in user + + This method allows to change the current logged in user information. + The result is a dictionary with OK packet information. + + Returns a dict() + """ + if self.unread_result: + raise errors.InternalError("Unread result found.") + + packet = self._protocol.make_change_user( + seed=self._handshake['scramble'], + username=username, password=password, database=database, + charset=charset, client_flags=self._client_flags) + self._socket.send(packet, 0) + return self._handle_ok(self._socket.recv()) + + def is_connected(self): + """Reports whether the connection to MySQL Server is available + + This method checks whether the connection to MySQL is available. + It is similar to ping(), but unlike the ping()-method, either True + or False is returned and no exception is raised. + + Returns True or False. + """ + try: + self.cmd_ping() + except errors.Error: + return False # This method does not raise + return True + + def reconnect(self, attempts=1, delay=0): + """Attempt to reconnect to the MySQL server + + The argument attempts should be the number of times a reconnect + is tried. The delay argument is the number of seconds to wait between + each retry. + + You may want to set the number of attempts higher and use delay when + you expect the MySQL server to be down for maintenance or when you + expect the network to be temporary unavailable. + + Raises InterfaceError on errors. + """ + counter = 0 + while counter != attempts: + counter = counter + 1 try: - self.protocol.conn.close_connection() - except: - pass - self.protocol = None + self.disconnect() + self.connect() + except Exception, err: + if counter == attempts: + raise errors.InterfaceError("Can not reconnect to MySQL " + "after %d attempt(s): %s" % ( + attempts, str(err))) + if delay > 0: + time.sleep(delay) + + def ping(self, reconnect=False, attempts=1, delay=0): + """Check availability to the MySQL server + + When reconnect is set to True, one or more attempts are made to try + to reconnect to the MySQL server using the reconnect()-method. + + delay is the number of seconds to wait between each retry. + + When the connection is not available, an InterfaceError is raised. Use + the is_connected()-method if you just want to check the connection + without raising an error. + + Raises InterfaceError on errors. + """ + try: + self.cmd_ping() + except errors.Error: + if reconnect: + self.reconnect(attempts=attempts, delay=delay) + else: + raise errors.InterfaceError("Connection to MySQL is" + " not available.") def set_converter_class(self, convclass): """ Set the converter class to be used. This should be a class overloading methods and members of conversion.MySQLConverter. """ - self.converter_class = convclass - self.converter = convclass(self.charset_name, self.use_unicode) + charset_name = CharacterSet.get_info(self._charset_id)[0] + self._converter_class = convclass + self.converter = convclass(charset_name, self._use_unicode) def get_server_version(self): - """Returns the server version as a tuple""" - try: - return self.protocol.server_version - except: - pass + """Get the MySQL version - return None + This method returns the MySQL server version as a tuple. If not + perviously connected, it will return None. + + Returns a tuple or None. + """ + return self._server_version def get_server_info(self): - """Returns the server version as a string""" - return self.protocol.server_version_original + """Get the original MySQL version information + + This method returns the original MySQL server as text. If not + previously connected, it will return None. + + Returns a string or None. + """ + try: + return self._handshake['server_version_original'] + except (TypeError, KeyError): + return None @property def connection_id(self): """MySQL connection ID""" - threadid = None try: - threadid = self.protocol.server_threadid - except: - pass - return threadid + return self._handshake['server_threadid'] + except KeyError: + return None def set_login(self, username=None, password=None): """Set login information for MySQL @@ -532,13 +863,13 @@ def set_login(self, username=None, password=None): the MySQL Server. """ if username is not None: - self.username = username.strip() + self._user = username.strip() else: - self.username = '' + self._user = '' if password is not None: - self.password = password.strip() + self._password = password.strip() else: - self.password = '' + self._password = '' def set_unicode(self, value=True): """Toggle unicode mode @@ -546,52 +877,69 @@ def set_unicode(self, value=True): Set whether we return string fields as unicode or not. Default is True. """ - self.use_unicode = value + self._use_unicode = value if self.converter: self.converter.set_unicode(value) - def set_charset(self, charset): - try: - (idx, charset_name, c) = \ - constants.CharacterSet.get_charset_info(charset) - self._execute_query("SET NAMES '%s'" % charset_name) - except: - raise - else: - self._charset = idx - self.charset_name = charset_name - self.converter.set_charset(charset_name) - def get_charset(self): - return self._info_query( - "SELECT @@session.character_set_connection")[0] - charset = property(get_charset, set_charset, - doc="Character set for this connection") - - def set_collation(self, collation): - try: - self._execute_query( - "SET @@session.collation_connection = '%s'" % collation) - except: - raise - def get_collation(self): - return self._info_query( - "SELECT @@session.collation_connection")[0] - collation = property(get_collation, set_collation, - doc="Collation for this connection") - - def set_warnings(self, fetch=False, raise_on_warnings=False): - """Set how to handle warnings coming from MySQL - - Set wheter we should get warnings whenever an operation produced some. - If you set raise_on_warnings to True, any warning will be raised - as a DataError exception. - """ - if raise_on_warnings is True: - self.get_warnings = True - self.raise_on_warnings = True - else: - self.get_warnings = fetch - self.raise_on_warnings = False + def set_charset_collation(self, charset=None, collation=None): + """Sets the character set and collation for the current connection + + This method sets the character set and collation to be used for + the current connection. The charset argument can be either the + name of a character set as a string, or the numerical equivalent + as defined in constants.CharacterSet. + + When the collation is not given, the default will be looked up and + used. + + For example, the following will set the collation for the latin1 + character set to latin1_general_ci: + + set_charset('latin1','latin1_general_ci') + + """ + if charset: + if isinstance(charset, int): + self._charset_id = charset + (charset_name, collation_name) = CharacterSet.get_info( + self._charset_id) + elif isinstance(charset, str): + (self._charset_id, charset_name, collation_name) = \ + CharacterSet.get_charset_info(charset, collation) + else: + raise ValueError( + "charset should be either integer, string or None") + elif collation: + (self._charset_id, charset_name, collation_name) = \ + CharacterSet.get_charset_info(collation=collation) + + self._execute_query("SET NAMES '%s' COLLATE '%s'" % ( + charset_name, collation_name)) + self.converter.set_charset(charset_name) + + @property + def charset(self): + """Returns the character set for current connection + + This property returns the character set name of the current connection. + Note that the value returned is based on what is previously set by + the set_charset_collation(). + + Returns a string. + """ + return CharacterSet.get_info(self._charset_id)[0] + + @property + def collation(self): + """Returns the collation for current connection + + This property returns the collation name of the current connection. + Note that the value returned is based on what is previously set by + the set_charset_collation(). + + Returns a string. + """ + return CharacterSet.get_charset_info(self._charset_id)[2] def set_client_flags(self, flags): """Set the client flags @@ -603,58 +951,80 @@ def set_client_flags(self, flags): when it's negative. set_client_flags([ClientFlag.FOUND_ROWS,-ClientFlag.LONG_FLAG]) + + Raises ProgrammingError when the flags argument is not a set or + an integer bigger than 0. - Returns self.client_flags + Returns self._client_flags """ - if isinstance(flags,int) and flags > 0: - self.client_flags = flags + if isinstance(flags, int) and flags > 0: + self._client_flags = flags + elif isinstance(flags, (tuple, list)): + for flag in flags: + if flag < 0: + self._client_flags &= ~abs(flag) + else: + self._client_flags |= flag else: - if isinstance(flags,(tuple,list)): - for f in flags: - if f < 0: - self.unset_client_flag(abs(f)) - else: - self.set_client_flag(f) - return self.client_flags - - def set_client_flag(self, flag): - if flag > 0: - self.client_flags |= flag - - def unset_client_flag(self, flag): - if flag > 0: - self.client_flags &= ~flag + raise errors.ProgrammingError( + "set_client_flags expect int (>0) or set") + + return self._client_flags def isset_client_flag(self, flag): - if (self.client_flags & flag) > 0: + """Check if a client flag is set""" + if (self._client_flags & flag) > 0: return True return False @property def user(self): """User used while connecting to MySQL""" - return self._username + return self._user @property def server_host(self): """MySQL server IP address or name""" - return self._server_host + return self._host @property def server_port(self): "MySQL server TCP/IP port" - return self._server_port + return self._port @property def unix_socket(self): "MySQL Unix socket file location" return self._unix_socket + def _set_unread_result(self, toggle): + """Set whether there is an unread resultself._port + + This method is used by cursors to let other cursors know there is + still a result set that needs to be retrieved. + + Raises ValueError on errors. + """ + if not isinstance(toggle, bool): + raise ValueError("Expected a boolean type") + self._unread_result = toggle + + def _get_unread_result(self): + """Get whether there is an unread result + + This method is used by cursors to check whether another cursor still + needs to retrieve its result set. + + Returns True, or False when there is no unread result. + """ + return self._unread_result + + unread_result = property(_get_unread_result, _set_unread_result, + doc="Unread result for this MySQL connection") + def set_database(self, value): - try: - self.protocol.cmd_query("USE %s" % value) - except: - raise + """Set the current database""" + self.cmd_query("USE %s" % value) def get_database(self): """Get the current database""" return self._info_query("SELECT DATABASE()")[0] @@ -662,35 +1032,45 @@ def get_database(self): doc="Current database") def set_time_zone(self, value): - try: - self.protocol.cmd_query("SET @@session.time_zone = %s" % value) - except: - raise + """Set the time zone""" + self.cmd_query("SET @@session.time_zone = '%s'" % value) + self._time_zone = value def get_time_zone(self): + """Get the current time zone""" return self._info_query("SELECT @@session.time_zone")[0] time_zone = property(get_time_zone, set_time_zone, doc="time_zone value for current MySQL session") def set_sql_mode(self, value): - try: - self.protocol.cmd_query("SET @@session.sql_mode = %s" % value) - except: - raise + """Set the SQL mode + + This method sets the SQL Mode for the current connection. The value + argument can be either a string with comma sepearate mode names, or + a sequence of mode names. + + It is good practice to use the constants class SQLMode: + from mysql.connector.constants import SQLMode + cnx.sql_mode = [SQLMode.NO_ZERO_DATE, SQLMode.REAL_AS_FLOAT] + """ + if isinstance(value, (list, tuple)): + value = ','.join(value) + self.cmd_query("SET @@session.sql_mode = '%s'" % value) + self._sql_mode = value def get_sql_mode(self): + """Get the SQL mode""" return self._info_query("SELECT @@session.sql_mode")[0] sql_mode = property(get_sql_mode, set_sql_mode, doc="sql_mode value for current MySQL session") def set_autocommit(self, value): - try: - if value: - s = 'ON' - else: - s = 'OFF' - self._execute_query("SET @@session.autocommit = %s" % s) - except: - raise + """Toggle autocommit""" + if value: + switch = 'ON' + else: + switch = 'OFF' + self._execute_query("SET @@session.autocommit = %s" % switch) def get_autocommit(self): + """Get whether autocommit is on or off""" value = self._info_query("SELECT @@session.autocommit")[0] if value == 1: return True @@ -698,16 +1078,62 @@ def get_autocommit(self): autocommit = property(get_autocommit, set_autocommit, doc="autocommit value for current MySQL session") - def close(self): - del self.cursors[:] - self.disconnect() + def _set_getwarnings(self, toggle): + """Set whether warnings should be automatically retrieved - def remove_cursor(self, c): - try: - self.cursors.remove(c) - except ValueError: - raise errors.ProgrammingError( - "Cursor could not be removed.") + The toggle-argument must be a boolean. When True, cursors for this + connection will retrieve information about warnings (if any). + + Raises ValueError on error. + """ + if not isinstance(toggle, bool): + raise ValueError("Expected a boolean type") + self._get_warnings = toggle + + def _get_getwarnings(self): + """Get whether this connection retrieves warnings automatically + + This method returns whether this connection retrieves warnings + automatically. + + Returns True, or False when warnings are not retrieved. + """ + return self._get_warnings + get_warnings = property(_get_getwarnings, _set_getwarnings, + doc="Toggle and check wheter to retrieve "\ + "warnings automatically") + + def _set_raise_on_warnings(self, toggle): + """Set whether warnings raise an error + + The toggle-argument must be a boolean. When True, cursors for this + connection will raise an error when MySQL reports warnings. + + Raising on warnings implies retrieving warnings automatically. In + other words: warnings will be set to True. If set to False, warnings + will be also set to False. + + Raises ValueError on error. + """ + if not isinstance(toggle, bool): + raise ValueError("Expected a boolean type") + self._raise_on_warnings = toggle + self._get_warnings = toggle + + def _get_raise_on_warnings(self): + """Get whether this connection raises an error on warnings + + This method returns whether this connection will raise errors when + MySQL reports warnings. + + Returns True or False. + """ + return self._raise_on_warnings + + raise_on_warnings = property(_get_raise_on_warnings, + _set_raise_on_warnings, + doc="Toggle wheter to raise on warnings "\ + "(emplies retrieving warnings).") def cursor(self, buffered=None, raw=None, cursor_class=None): """Instantiates and returns a cursor @@ -722,32 +1148,32 @@ def cursor(self, buffered=None, raw=None, cursor_class=None): Returns a cursor-object """ + if self._unread_result is True: + raise errors.InternalError("Unread result found.") + if not self.is_connected(): + raise errors.OperationalError("MySQL Connection not available.") if cursor_class is not None: - if not issubclass(cursor_class, cursor.CursorBase): + if not issubclass(cursor_class, CursorBase): raise errors.ProgrammingError( - "Cursor class needs be subclass of cursor.CursorBase") - c = (cursor_class)(self) - else: - buffered = buffered or self.buffered - raw = raw or self.raw - - t = 0 - if buffered is True: - t |= 1 - if raw is True: - t |= 2 - - types = { - 0 : cursor.MySQLCursor, - 1 : cursor.MySQLCursorBuffered, - 2 : cursor.MySQLCursorRaw, - 3 : cursor.MySQLCursorBufferedRaw, - } - c = (types[t])(self) - - if c not in self.cursors: - self.cursors.append(c) - return c + "Cursor class needs to be subclass of cursor.CursorBase") + return (cursor_class)(self) + + buffered = buffered or self._buffered + raw = raw or self._raw + + cursor_type = 0 + if buffered is True: + cursor_type |= 1 + if raw is True: + cursor_type |= 2 + + types = ( + MySQLCursor, # 0 + MySQLCursorBuffered, + MySQLCursorRaw, + MySQLCursorBufferedRaw, + ) + return (types[cursor_type ])(self) def commit(self): """Commit current transaction""" @@ -758,17 +1184,23 @@ def rollback(self): self._execute_query("ROLLBACK") def _execute_query(self, query): - if self.unread_result is True: + """Execute a query + + This method simply calles cmd_query() after checking for unread + result. If there are still unread result, an errors.InterfaceError + is raised. Otherwise whatever cmd_query() returns is returned. + + Returns a dict() + """ + if self._unread_result is True: raise errors.InternalError("Unread result found.") - self.protocol.cmd_query(query) + self.cmd_query(query) def _info_query(self, query): - try: - cur = self.cursor(buffered=True) - cur.execute(query) - row = cur.fetchone() - cur.close() - except: - raise - return row + """Send a query which only returns 1 row""" + cursor = self.cursor(buffered=True) + cursor.execute(query) + return cursor.fetchone() + + diff --git a/lib/mysql/connector/constants.py b/lib/mysql/connector/constants.py index 71c3d87..6fa5dfa 100644 --- a/lib/mysql/connector/constants.py +++ b/lib/mysql/connector/constants.py @@ -1,30 +1,35 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Various MySQL constants and character sets """ -from errors import ProgrammingError +from mysql.connector.errors import ProgrammingError + +MAX_PACKET_LENGTH = 16777215 +NET_BUFFER_LENGTH = 8192 +RESULT_WITHOUT_ROWS = 0 +RESULT_WITH_ROWS = 1 def flag_is_set(flag, flags): """Checks if the flag is set @@ -51,13 +56,10 @@ def get_desc(cls,name): @classmethod def get_info(cls,n): - try: - res = {} - for v in cls.desc.items(): - res[v[1][0]] = v[0] - return res[n] - except: - return None + for name, info in cls.desc.items(): + if info[0] == n: + return name + return None @classmethod def get_full_info(cls): @@ -414,7 +416,39 @@ class RefreshOption(_constants): 'THREADS': (1 << 5, 'Flush thread cache'), 'SLAVE': (1 << 6, 'Reset master info and restart slave thread'), } - + + +class ShutdownType(_constants): + """Shutdown types used by the COM_SHUTDOWN server command.""" + _prefix = '' + SHUTDOWN_DEFAULT = 0 + SHUTDOWN_WAIT_CONNECTIONS = 1 + SHUTDOWN_WAIT_TRANSACTIONS = 2 + SHUTDOWN_WAIT_UPDATES = 8 + SHUTDOWN_WAIT_ALL_BUFFERS = 10 + SHUTDOWN_WAIT_CRITICAL_BUFFERS = 11 + KILL_QUERY = 254 + KILL_CONNECTION = 255 + + desc = { + 'SHUTDOWN_DEFAULT': (0, + "defaults to SHUTDOWN_WAIT_ALL_BUFFERS"), + 'SHUTDOWN_WAIT_CONNECTIONS': (1, + "wait for existing connections to finish"), + 'SHUTDOWN_WAIT_TRANSACTIONS': (2, + "wait for existing trans to finish"), + 'SHUTDOWN_WAIT_UPDATES': (8, + "wait for existing updates to finish"), + 'SHUTDOWN_WAIT_ALL_BUFFERS': (10, + "flush InnoDB and other storage engine buffers"), + 'SHUTDOWN_WAIT_CRITICAL_BUFFERS': (11, + "don't flush InnoDB buffers, " + "flush other storage engines' buffers"), + 'KILL_QUERY': (254, "(no description)"), + 'KILL_CONNECTION': (255, "(no description)"), + } + + class CharacterSet(_constants): """MySQL supported character sets and collations @@ -697,42 +731,48 @@ def get_default_collation(cls, charset): raise ProgrammingError("Character set '%s' unsupported." % (charset)) @classmethod - def get_charset_info(cls, charset, collation=None): - """Retrieves character set information as tuple using a name + def get_charset_info(cls, charset=None, collation=None): + """Get character set information using charset name and/or collation - Retrieves character set and collation information based on the - given a valid name. If charset is an integer, it will look up - the character set based on the MySQL's ID. + Retrieves character set and collation information given character + set name and/or a collation name. + If charset is an integer, it will look up the character set based + on the MySQL's ID. + For example: + get_charset_info('utf8',None) + get_charset_info(collation='utf8_general_ci') + get_charset_info(47) Raises ProgrammingError when character set is not supported. - Returns a tuple. + Returns a tuple with (id, characterset name, collation) """ idx = None if isinstance(charset, int): try: - c = cls.desc[charset] - return charset, c[0], c[1] - except: - ProgrammingError("Character set ID '%s' unsupported." % ( - charset)) + info = cls.desc[charset] + return (charset, info[0], info[1]) + except IndexError: + ProgrammingError("Character set ID %s unknown." % (charset)) - if collation is None: - collation, charset, idx = cls.get_default_collation(charset) + if charset is not None and collation is None: + info = cls.get_default_collation(charset) + return (info[2], info[1], info[0]) + elif charset is None and collation is not None: + for cid, info in enumerate(cls.desc): + if info is None: + continue + if collation == info[1]: + return (cid, info[0], info[1]) + raise ProgrammingError("Collation '%s' unknown." % (collation)) else: - for cid, c in enumerate(cls.desc): - if c is None: - continue - if c[0] == charset and c[1] == collation: - idx = cid - break - - if idx is not None: - return (idx,charset,collation) - else: - raise ProgrammingError("Character set '%s' unsupported." % ( - charset)) + for cid, info in enumerate(cls.desc): + if info is None: + continue + if info[0] == charset and info[1] == collation: + return (cid, info[0], info[1]) + raise ProgrammingError("Character set '%s' unknown." % (charset)) @classmethod def get_supported(cls): @@ -746,4 +786,69 @@ def get_supported(cls): res.append(info[0]) return tuple(res) - +class SQLMode(_constants): + """MySQL SQL Modes + + The numeric values of SQL Modes are not interesting, only the names + are used when setting the SQL_MODE system variable using the MySQL + SET command. + + See http://dev.mysql.com/doc/refman/5.6/en/server-sql-mode.html + """ + _prefix = 'MODE_' + REAL_AS_FLOAT = 'REAL_AS_FLOAT' + PIPES_AS_CONCAT = 'PIPES_AS_CONCAT' + ANSI_QUOTES = 'ANSI_QUOTES' + IGNORE_SPACE = 'IGNORE_SPACE' + NOT_USED = 'NOT_USED' + ONLY_FULL_GROUP_BY = 'ONLY_FULL_GROUP_BY' + NO_UNSIGNED_SUBTRACTION = 'NO_UNSIGNED_SUBTRACTION' + NO_DIR_IN_CREATE = 'NO_DIR_IN_CREATE' + POSTGRESQL = 'POSTGRESQL' + ORACLE = 'ORACLE' + MSSQL = 'MSSQL' + DB2 = 'DB2' + MAXDB = 'MAXDB' + NO_KEY_OPTIONS = 'NO_KEY_OPTIONS' + NO_TABLE_OPTIONS = 'NO_TABLE_OPTIONS' + NO_FIELD_OPTIONS = 'NO_FIELD_OPTIONS' + MYSQL323 = 'MYSQL323' + MYSQL40 = 'MYSQL40' + ANSI = 'ANSI' + NO_AUTO_VALUE_ON_ZERO = 'NO_AUTO_VALUE_ON_ZERO' + NO_BACKSLASH_ESCAPES = 'NO_BACKSLASH_ESCAPES' + STRICT_TRANS_TABLES = 'STRICT_TRANS_TABLES' + STRICT_ALL_TABLES = 'STRICT_ALL_TABLES' + NO_ZERO_IN_DATE = 'NO_ZERO_IN_DATE' + NO_ZERO_DATE = 'NO_ZERO_DATE' + INVALID_DATES = 'INVALID_DATES' + ERROR_FOR_DIVISION_BY_ZERO = 'ERROR_FOR_DIVISION_BY_ZERO' + TRADITIONAL = 'TRADITIONAL' + NO_AUTO_CREATE_USER = 'NO_AUTO_CREATE_USER' + HIGH_NOT_PRECEDENCE = 'HIGH_NOT_PRECEDENCE' + NO_ENGINE_SUBSTITUTION = 'NO_ENGINE_SUBSTITUTION' + PAD_CHAR_TO_FULL_LENGTH = 'PAD_CHAR_TO_FULL_LENGTH' + + @classmethod + def get_desc(cls, name): + raise NotImplementedError + + @classmethod + def get_info(cls, number): + raise NotImplementedError + + @classmethod + def get_full_info(cls): + """Returns a sequence of all availble SQL Modes + + This class method returns a tuple containing all SQL Mode names. The + names will be alphabetically sorted. + + Returns a tuple. + """ + res = [] + for key in vars(cls).keys(): + if not key.startswith('_') and not callable(getattr(cls, key)): + res.append(key) + return tuple(sorted(res)) + diff --git a/lib/mysql/connector/conversion.py b/lib/mysql/connector/conversion.py index 3272780..4e3389e 100644 --- a/lib/mysql/connector/conversion.py +++ b/lib/mysql/connector/conversion.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Converting MySQL and Python types """ @@ -29,8 +29,8 @@ import time from decimal import Decimal -import errors -from constants import FieldType, FieldFlag +from mysql.connector import errors +from mysql.connector.constants import FieldType, FieldFlag class ConverterBase(object): @@ -79,45 +79,6 @@ class MySQLConverter(ConverterBase): def __init__(self, charset=None, use_unicode=True): ConverterBase.__init__(self, charset, use_unicode) - self.python_types = { - int : int, - str : self._str_to_mysql, - long : long, - float : float, - unicode : self._unicode_to_mysql, - bool : self._bool_to_mysql, - type(None) : self._none_to_mysql, - datetime.datetime : self._datetime_to_mysql, - datetime.date : self._date_to_mysql, - datetime.time : self._time_to_mysql, - time.struct_time : self._struct_time_to_mysql, - datetime.timedelta : self._timedelta_to_mysql, - Decimal : self._decimal_to_mysql, - } - - self.mysql_types = { - FieldType.TINY : self._int, - FieldType.SHORT : self._int, - FieldType.INT24 : self._int, - FieldType.LONG : self._long, - FieldType.LONGLONG : self._long, - FieldType.FLOAT : self._float, - FieldType.DOUBLE : self._float, - FieldType.DECIMAL : self._decimal, - FieldType.NEWDECIMAL : self._decimal, - FieldType.VAR_STRING : self._STRING_to_python, - FieldType.STRING : self._STRING_to_python, - FieldType.SET : self._SET_to_python, - FieldType.TIME : self._TIME_to_python, - FieldType.DATE : self._DATE_to_python, - FieldType.NEWDATE : self._DATE_to_python, - FieldType.DATETIME : self._DATETIME_to_python, - FieldType.TIMESTAMP : self._DATETIME_to_python, - FieldType.BLOB : self._BLOB_to_python, - FieldType.YEAR: self._YEAR_to_python, - FieldType.BIT: self._BIT_to_python, - } - def escape(self, value): """ Escapes special characters as they are expected to by when MySQL @@ -157,8 +118,17 @@ def quote(self, buf): return "'%s'" % buf def to_mysql(self, value): - vtype = type(value) - return self.python_types[vtype](value) + type_name = value.__class__.__name__.lower() + return getattr(self, "_%s_to_mysql" % str(type_name))(value) + + def _int_to_mysql(self, value): + return int(value) + + def _long_to_mysql(self, value): + return long(value) + + def _float_to_mysql(self, value): + return float(value) def _str_to_mysql(self, value): return str(value) @@ -176,10 +146,10 @@ def _bool_to_mysql(self, value): else: return 0 - def _none_to_mysql(self, value): + def _nonetype_to_mysql(self, value): """ This would return what None would be in MySQL, but instead we - leave it None and return it right away. The actual convertion + leave it None and return it right away. The actual conversion from None to NULL happens in the quoting functionality. Return None. @@ -189,12 +159,17 @@ def _none_to_mysql(self, value): def _datetime_to_mysql(self, value): """ Converts a datetime instance to a string suitable for MySQL. - The returned string has format: %Y-%m-%d %H:%M:%S + The returned string has format: %Y-%m-%d %H:%M:%S[.%f] If the instance isn't a datetime.datetime type, it return None. Returns a string. """ + if value.microsecond: + return '%d-%02d-%02d %02d:%02d:%02d.%06d' % ( + value.year, value.month, value.day, + value.hour, value.minute, value.second, + value.microsecond) return '%d-%02d-%02d %02d:%02d:%02d' % ( value.year, value.month, value.day, value.hour, value.minute, value.second) @@ -213,12 +188,14 @@ def _date_to_mysql(self, value): def _time_to_mysql(self, value): """ Converts a time instance to a string suitable for MySQL. - The returned string has format: %H:%M:%S + The returned string has format: %H:%M:%S[.%f] If the instance isn't a datetime.time type, it return None. Returns a string or None when not valid. """ + if value.microsecond: + return value.strftime('%H:%M:%S.%%06d') % value.microsecond return value.strftime('%H:%M:%S') def _struct_time_to_mysql(self, value): @@ -229,7 +206,7 @@ def _struct_time_to_mysql(self, value): Returns a string or None when not valid. """ - return time.strftime('%Y-%m-%d %H:%M:%S',value) + return time.strftime('%Y-%m-%d %H:%M:%S', value) def _timedelta_to_mysql(self, value): """ @@ -241,7 +218,10 @@ def _timedelta_to_mysql(self, value): (hours, r) = divmod(value.seconds, 3600) (mins, secs) = divmod(r, 60) hours = hours + (value.days * 24) - return '%02d:%02d:%02d' % (hours,mins,secs) + if value.microseconds: + return '%02d:%02d:%02d.%06d' % (hours, mins, secs, + value.microseconds) + return '%02d:%02d:%02d' % (hours, mins, secs) def _decimal_to_mysql(self, value): """ @@ -271,8 +251,9 @@ def to_python(self, flddsc, value): if value is None: return None + type_name = FieldType.get_info(flddsc[1]) try: - res = self.mysql_types[flddsc[1]](value, flddsc) + return getattr(self, '_%s_to_python' % type_name)(value, flddsc) except KeyError: # If one type is not defined, we just return the value as str return str(value) @@ -282,32 +263,36 @@ def to_python(self, flddsc, value): raise TypeError, "%s (field %s)" % (e, flddsc[0]) except: raise - - return res - def _float(self, v, desc=None): + def _FLOAT_to_python(self, v, desc=None): """ Returns v as float type. """ return float(v) + _DOUBLE_to_python = _FLOAT_to_python - def _int(self, v, desc=None): + def _INT_to_python(self, v, desc=None): """ Returns v as int type. """ return int(v) - - def _long(self, v, desc=None): + _TINY_to_python = _INT_to_python + _SHORT_to_python = _INT_to_python + _INT24_to_python = _INT_to_python + + def _LONG_to_python(self, v, desc=None): """ Returns v as long type. """ return int(v) + _LONGLONG_to_python = _LONG_to_python - def _decimal(self, v, desc=None): + def _DECIMAL_to_python(self, v, desc=None): """ Returns v as a decimal.Decimal. """ return Decimal(v) + _NEWDECIMAL_to_python = _DECIMAL_to_python def _str(self, v, desc=None): """ @@ -333,6 +318,7 @@ def _DATE_to_python(self, v, dsc=None): return None else: return pv + _NEWDATE_to_python = _DATE_to_python def _TIME_to_python(self, v, dsc=None): """ @@ -340,10 +326,18 @@ def _TIME_to_python(self, v, dsc=None): """ pv = None try: - (h, m, s) = [ int(s) for s in v.split(':')] - pv = datetime.timedelta(hours=h,minutes=m,seconds=s) + (hms, fs) = v.split('.') + fs = int(fs.ljust(6, '0')) except ValueError: - raise ValueError, "Could not convert %s to python datetime.timedelta" % v + hms = v + fs = 0 + try: + (h, m, s) = [ int(d) for d in hms.split(':')] + pv = datetime.timedelta(hours=h, minutes=m, seconds=s, + microseconds=fs) + except ValueError, err: + raise ValueError( + "Could not convert %s to python datetime.timedelta" % v) else: return pv @@ -353,14 +347,21 @@ def _DATETIME_to_python(self, v, dsc=None): """ pv = None try: - (sd,st) = v.split(' ') + (sd, st) = v.split(' ') + if len(st) > 8: + (hms, fs) = st.split('.') + fs = int(fs.ljust(6, '0')) + else: + hms = st + fs = 0 dt = [ int(v) for v in sd.split('-') ] +\ - [ int(v) for v in st.split(':') ] + [ int(v) for v in hms.split(':') ] + [fs,] pv = datetime.datetime(*dt) except ValueError: pv = None return pv + _TIMESTAMP_to_python = _DATETIME_to_python def _YEAR_to_python(self, v, desc=None): """Returns YEAR column type as integer""" @@ -406,6 +407,7 @@ def _STRING_to_python(self, v, dsc=None): except: raise return str(v) + _VAR_STRING_to_python = _STRING_to_python def _BLOB_to_python(self, v, dsc=None): if dsc is not None: @@ -413,4 +415,6 @@ def _BLOB_to_python(self, v, dsc=None): return v return self._STRING_to_python(v, dsc) - + _LONG_BLOB_to_python = _BLOB_to_python + _MEDIUM_BLOB_to_python = _BLOB_to_python + _TINY_BLOB_to_python = _BLOB_to_python diff --git a/lib/mysql/connector/cursor.py b/lib/mysql/connector/cursor.py index 978b345..1d1edeb 100644 --- a/lib/mysql/connector/cursor.py +++ b/lib/mysql/connector/cursor.py @@ -1,42 +1,42 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Cursor classes """ import sys -from collections import deque import weakref import re +import itertools -import constants -import protocol -import errors -import utils +from mysql.connector import errors RE_SQL_COMMENT = re.compile("\/\*.*\*\/") -RE_SQL_INSERT_VALUES = re.compile(r'\sVALUES\s*(\(.*\))', re.I) +RE_SQL_ON_DUPLICATE = re.compile(r'\s*ON DUPLICATE KEY.*$') +RE_SQL_INSERT_VALUES = re.compile(r'.*VALUES\s*(\(.*\)).*', re.I | re.M | re.S) RE_SQL_INSERT_STMT = re.compile(r'INSERT\s+INTO', re.I) +RE_SQL_SPLIT_STMTS = re.compile( + r''';(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''') class CursorBase(object): """ @@ -48,13 +48,11 @@ class CursorBase(object): """ def __init__(self): - self.description = None - self.rowcount = -1 + self._description = None + self._rowcount = -1 + self._last_insert_id = None self.arraysize = 1 - def __del__(self): - self.close() - def callproc(self, procname, args=()): pass @@ -87,39 +85,80 @@ def setoutputsize(self, size, column=None): def reset(self): pass + + @property + def description(self): + """Returns description of columns in a result + + This property returns a list of tuples describing the columns in + in a result set. A tuple is described as follows:: + + (column_name, + type, + None, + None, + None, + None, + null_ok, + column_flags) # Addition to PEP-249 specs + + Returns a list of tuples. + """ + return self._description + + @property + def rowcount(self): + """Returns the number of rows produced or affected + + This property returns the number of rows produced by queries + such as a SELECT, or affected rows when executing DML statements + like INSERT or UPDATE. + Note that for non-buffered cursors it is impossible to know the + number of rows produced before having fetched them all. For those, + the number of rows will be -1 right after execution, and + incremented when fetching rows. + + Returns an integer. + """ + return self._rowcount + + @property + def lastrowid(self): + """Returns the value generated for an AUTO_INCREMENT column + + Returns the value generated for an AUTO_INCREMENT column by + the previous INSERT or UPDATE statement or None when there is + no such value available. + + Returns a long value or None. + """ + return self._last_insert_id + class MySQLCursor(CursorBase): - """ - Default cursor which fetches all rows and stores it for later - usage. It uses the converter set for the MySQLConnection to map - MySQL types to Python types automatically. + """Default cursor for interacting with MySQL - This class should be inherited whenever other functionallity is + This cursor will execute statements and handle the result. It will + not automatically fetch all rows. + + MySQLCursor should be inherited whenever other functionallity is required. An example would to change the fetch* member functions to return dictionaries instead of lists of values. - Implements the Python Database API Specification v2.0. - - Possible parameters are: - - db - A MySQLConnection instance. + Implements the Python Database API Specification v2.0 (PEP-249) """ - - def __init__(self, db=None): + def __init__(self, connection=None): CursorBase.__init__(self) - self.db = None - self._more_results = False - self._results = deque() + self._connection = None + self._stored_results = [] self._nextrow = (None, None) - self.lastrowid = None self._warnings = None self._warning_count = 0 self._executed = None - self._have_result = False - self._raise_on_warnings = True - if db is not None: - self.set_connection(db) + self._executed_list = [] + + if connection is not None: + self._set_connection(connection) def __iter__(self): """ @@ -128,27 +167,31 @@ def __iter__(self): """ return iter(self.fetchone, None) - def set_connection(self, db): + def _set_connection(self, connection): try: - if isinstance(db.protocol,protocol.MySQLProtocol): - self.db = weakref.ref(db) - if self not in self.db().cursors: - self.db().cursors.append(self) - except: + self._connection = weakref.proxy(connection) + self._connection._protocol + except (AttributeError, TypeError): raise errors.InterfaceError(errno=2048) def _reset_result(self): - self.rowcount = -1 + self._rowcount = -1 + self._lastrowid = None self._nextrow = (None, None) - self._have_result = False - try: - self.db().unread_result = False - except: - pass + self._stored_results = [] self._warnings = None self._warning_count = 0 - self.description = () + self._description = None + self._executed = None + self._executed_list = [] self.reset() + + def _have_unread_result(self): + """Check whether there is an unread result""" + try: + return self._connection.unread_result + except AttributeError: + return False def next(self): """ @@ -164,28 +207,25 @@ def next(self): return row def close(self): - """ - Close the cursor, disconnecting it from the MySQL object. + """Close the cursor - Returns True when succesful, otherwise False. + Returns True when successful, otherwise False. """ - if self.db is None: - return False - - try: - self._reset_result() - self.db().remove_cursor(self) - self.db = None - except: + if self._connection is None: return False + if self._have_unread_result(): + raise errors.InternalError("Unread result found.") + + self._reset_result() + self._connection = None return True - + def _process_params_dict(self, params): try: - to_mysql = self.db().converter.to_mysql - escape = self.db().converter.escape - quote = self.db().converter.quote + to_mysql = self._connection.converter.to_mysql + escape = self._connection.converter.escape + quote = self._connection.converter.quote res = {} for k,v in params.items(): c = v @@ -216,14 +256,9 @@ def _process_params(self, params): try: res = params - - to_mysql = self.db().converter.to_mysql - escape = self.db().converter.escape - quote = self.db().converter.quote - - res = map(to_mysql,res) - res = map(escape,res) - res = map(quote,res) + res = map(self._connection.converter.to_mysql,res) + res = map(self._connection.converter.escape,res) + res = map(self._connection.converter.quote,res) except StandardError, e: raise errors.ProgrammingError( "Failed processing format-parameters; %s" % e) @@ -234,12 +269,11 @@ def _process_params(self, params): def _row_to_python(self, rowdata, desc=None): res = () try: - to_python = self.db().converter.to_python if not desc: desc = self.description for idx,v in enumerate(rowdata): flddsc = desc[idx] - res += (to_python(flddsc, v),) + res += (self._connection.converter.to_python(flddsc, v),) except StandardError, e: raise errors.InterfaceError( "Failed converting row to Python types; %s" % e) @@ -252,47 +286,83 @@ def _handle_noresultset(self, res): """Handles result of execute() when there is no result set """ try: - self.rowcount = res['affected_rows'] - self.lastrowid = res['insert_id'] + self._rowcount = res['affected_rows'] + self._last_insert_id = res['insert_id'] self._warning_count = res['warning_count'] - if self.db().get_warnings is True and self._warning_count: - self._warnings = self._fetch_warnings() - self._set_more_results(res['server_status']) - except errors.Error: - raise - except StandardError, e: + except (KeyError, TypeError), err: raise errors.ProgrammingError( - "Failed handling non-resultset; %s" % e) - + "Failed handling non-resultset; %s" % err) + + if self._connection.get_warnings is True and self._warning_count: + self._warnings = self._fetch_warnings() + def _handle_resultset(self): pass - def _handle_result(self, res): - if isinstance(res, dict): - self.db().unread_result = False - self._have_result = False - self._handle_noresultset(res) - else: - self.description = res[1] - self.db().unread_result = True - self._have_result = True + def _handle_result(self, result): + """ + Handle the result after a command was send. The result can be either + an OK-packet or a dictionary containing column/eof information. + + Raises InterfaceError when result is not a dict() or result is + invalid. + """ + if not isinstance(result, dict): + raise errors.InterfaceError('Result was not a dict()') + + if 'columns' in result: + # Weak test, must be column/eof information + self._description = result['columns'] + self._connection.unread_result = True self._handle_resultset() + elif 'affected_rows' in result: + # Weak test, must be an OK-packet + self._connection.unread_result = False + self._handle_noresultset(result) + else: + raise errors.InterfaceError('Invalid result') + + def _execute_iter(self, query_iter): + """Generator returns MySQLCursor objects for multiple statements - def execute(self, operation, params=None): + This method is only used when multiple statements are executed + by the execute() method. It uses itertools.izip to iterate over the + given query_iter (result of MySQLConnection.cmd_query_iter()) and + the list of statements that were executed. + + Yields a MySQLCursor instance. """ - Executes the given operation. The parameters given through params - are used to substitute %%s in the operation string. + if not self._executed_list: + self._executed_list = RE_SQL_SPLIT_STMTS.split(self._executed) + + for result, stmt in itertools.izip(query_iter, + iter(self._executed_list)): + self._reset_result() + self._handle_result(result) + self._executed = stmt + yield self + + def execute(self, operation, params=None, multi=False): + """Executes the given operation + + Executes the given operation substituting any markers with + the given parameters. + For example, getting all rows where id is 5: cursor.execute("SELECT * FROM t1 WHERE id = %s", (5,)) - If warnings where generated, and db.get_warnings is True, then + The multi argument should be set to True when executing multiple + statements in one operation. If not set and multiple results are + found, an InterfaceError will be raised. + + If warnings where generated, and connection.get_warnings is True, then self._warnings will be a list containing these warnings. - Raises exceptions when any error happens. + Returns an iterator when multi is True, otherwise None. """ if not operation: - return 0 - if self.db().unread_result is True: + return + if self._have_unread_result(): raise errors.InternalError("Unread result found.") self._reset_result() @@ -300,105 +370,106 @@ def execute(self, operation, params=None): try: if isinstance(operation, unicode): - operation = operation.encode(self.db().charset_name) - - if params is not None: - try: - stmt = operation % self._process_params(params) - except TypeError: - raise errors.ProgrammingError( - "Wrong number of arguments during string formatting") - else: - stmt = operation - - res = self.db().protocol.cmd_query(stmt) - self._handle_result(res) - except (UnicodeDecodeError,UnicodeEncodeError), e: + operation = operation.encode(self._connection.charset) + except (UnicodeDecodeError, UnicodeEncodeError), e: raise errors.ProgrammingError(str(e)) - except errors.Error: - raise - except StandardError, e: - raise errors.InterfaceError, errors.InterfaceError( - "Failed executing the operation; %s" % e), sys.exc_info()[2] + + if params is not None: + try: + stmt = operation % self._process_params(params) + except TypeError: + raise errors.ProgrammingError( + "Wrong number of arguments during string formatting") else: + stmt = operation + + if multi: self._executed = stmt - return self.rowcount - - return 0 - + self._executed_list = [] + return self._execute_iter(self._connection.cmd_query_iter(stmt)) + else: + self._executed = stmt + try: + self._handle_result(self._connection.cmd_query(stmt)) + except errors.InterfaceError, err: + if self._connection._have_next_result: + raise errors.InterfaceError( + "Use multi=True when executing multiple statements") + raise + return None + def executemany(self, operation, seq_params): - """Loops over seq_params and calls execute() + """Execute the given operation multiple times + + The executemany() method will execute the operation iterating + over the list of parameters in seq_params. + + Example: Inserting 3 new employees and their phone number + + data = [ + ('Jane','555-001'), + ('Joe', '555-001'), + ('John', '555-003') + ] + stmt = "INSERT INTO employees (name, phone) VALUES ('%s','%s')" + cursor.executemany(stmt, data) INSERT statements are optimized by batching the data, that is using the MySQL multiple rows syntax. + + Results are discarded. If they are needed, consider looping over + data using the execute() method. """ if not operation: - return 0 - if self.db().unread_result is True: + return + if self._have_unread_result(): raise errors.InternalError("Unread result found.") + elif len(RE_SQL_SPLIT_STMTS.split(operation)) > 1: + raise errors.InternalError( + "executemany() does not support multiple statements") # Optimize INSERTs by batching them if re.match(RE_SQL_INSERT_STMT,operation): - opnocom = re.sub(RE_SQL_COMMENT,'',operation) - m = re.search(RE_SQL_INSERT_VALUES,opnocom) + tmp = re.sub(RE_SQL_ON_DUPLICATE, '', + re.sub(RE_SQL_COMMENT, '', operation)) + m = re.search(RE_SQL_INSERT_VALUES, tmp) + if not m: + raise errors.InterfaceError( + "Failed rewriting statement for multi-row INSERT. " + "Check SQL syntax." + ) fmt = m.group(1) values = [] for params in seq_params: values.append(fmt % self._process_params(params)) - operation = operation.replace(m.group(1),','.join(values),1) - self.execute(operation) - else: - rowcnt = 0 - try: - for params in seq_params: - self.execute(operation, params) - if self._have_result: - self.fetchall() - rowcnt += self.rowcount - except (ValueError,TypeError), e: - raise errors.InterfaceError( - "Failed executing the operation; %s" % e) - except: - # Raise whatever execute() raises - raise - self.rowcount = rowcnt - return self.rowcount - - def _set_more_results(self, flags): - flag = constants.ServerFlag.MORE_RESULTS_EXISTS - self._more_results = constants.flag_is_set(flag, flags) - - def next_resultset(self): - """Gets next result after executing multiple statements - - When more results are available, this function will reset the - current result and advance to the next set. - - This is useful when executing multiple statements. If you need - to retrieve multiple results after executing a stored procedure - using callproc(), use next_proc_resultset() instead. - """ - if self._more_results is True: - buf = self.db().protocol.conn.recv() - res = self.db().protocol.handle_cmd_result(buf) - self._reset_result() - self._handle_result(res) - return True - - return None - - def next_proc_resultset(self): - """Get the next result set after calling a stored procedure - - Returns a MySQLCursorBuffered-object""" + operation = operation.replace(m.group(1), ','.join(values), 1) + return self.execute(operation) + + rowcnt = 0 try: - return self._results.popleft() - except IndexError: - return None + for params in seq_params: + self.execute(operation, params) + if self.with_rows and self._have_unread_result(): + self.fetchall() + rowcnt += self._rowcount + except (ValueError, TypeError), err: + raise errors.InterfaceError( + "Failed executing the operation; %s" % err) except: + # Raise whatever execute() raises raise + self._rowcount = rowcnt + + def stored_results(self): + """Returns an iterator for stored results - return None + This method returns an iterator over results which are stored when + callproc() is called. The iterator will provide MySQLCursorBuffered + instances. + + Returns a iterator. + """ + return iter(self._stored_results) def callproc(self, procname, args=()): """Calls a stored procedue with the given arguments @@ -416,52 +487,63 @@ def callproc(self, procname, args=()): 2) Executing in Python: args = (5,5,0) # 0 is to hold pprod - cursor.callproc(multiply, args) + cursor.callproc('multiply', args) print cursor.fetchone() - The last print should output ('5', '5', 25L) - Does not return a value, but a result set will be - available when the CALL-statement execute succesfully. + available when the CALL-statement execute successfully. Raises exceptions when something is wrong. """ + if not procname or not isinstance(procname, str): + raise ValueError("procname must be a string") + + if not isinstance(args, (tuple, list)): + raise ValueError("args must be a sequence") + argfmt = "@_%s_arg%d" - self._results = deque() + self._stored_results = [] + results = [] try: - procargs = self._process_params(args) argnames = [] - for idx,arg in enumerate(procargs): - argname = argfmt % (procname, idx+1) - argnames.append(argname) - setquery = "SET %s=%%s" % argname - self.execute(setquery, (arg,)) + if args: + for idx,arg in enumerate(args): + argname = argfmt % (procname, idx+1) + argnames.append(argname) + self.execute("SET %s=%%s" % (argname), (arg,)) call = "CALL %s(%s)" % (procname,','.join(argnames)) - res = self.db().protocol.cmd_query(call) - while not isinstance(res, dict): - tmp = MySQLCursorBuffered(self.db()) - tmp.description = res[1] - tmp._handle_resultset() - self._results.append(tmp) - buf = self.db().protocol.conn.recv() - res = self.db().protocol.handle_cmd_result(buf) - try: + for result in self._connection.cmd_query_iter(call): + if 'columns' in result: + tmp = MySQLCursorBuffered(self._connection._get_self()) + tmp._handle_result(result) + results.append(tmp) + + if argnames: select = "SELECT %s" % ','.join(argnames) self.execute(select) + self._stored_results = results return self.fetchone() - except: - raise + else: + self._stored_results = results + return () except errors.Error: raise except StandardError, e: raise errors.InterfaceError( "Failed calling stored routine; %s" % e) - + def getlastrowid(self): + """Returns the value generated for an AUTO_INCREMENT column + + This method is kept for backward compatibility. Please use the + property lastrowid instead. + + Returns a long value or None. + """ return self.lastrowid def _fetch_warnings(self): @@ -473,15 +555,15 @@ def _fetch_warnings(self): """ res = [] try: - c = self.db().cursor() + c = self._connection.cursor() cnt = c.execute("SHOW WARNINGS") res = c.fetchall() c.close() except StandardError, e: - raise errors.InterfaceError( - "Failed getting warnings; %s" % e) + raise errors.InterfaceError, errors.InterfaceError( + "Failed getting warnings; %s" % e), sys.exc_info()[2] - if self.db().raise_on_warnings is True: + if self._connection.raise_on_warnings is True: msg = '; '.join([ "(%s) %s" % (r[1],r[2]) for r in res]) raise errors.get_mysql_exception(res[0][1],res[0][2]) else: @@ -491,32 +573,29 @@ def _fetch_warnings(self): return None def _handle_eof(self, eof): - self._have_result = False - self.db().unread_result = False + self._connection.unread_result = False self._nextrow = (None, None) self._warning_count = eof['warning_count'] - if self.db().get_warnings is True and eof['warning_count']: + if self._connection.get_warnings is True and eof['warning_count']: self._warnings = self._fetch_warnings() - self._set_more_results(eof['status_flag']) def _fetch_row(self): - if self._have_result is False: + if not self._have_unread_result(): return None row = None try: if self._nextrow == (None, None): - (row, eof) = self.db().protocol.get_row() + (row, eof) = self._connection.get_row() else: (row, eof) = self._nextrow if row: - (foo, eof) = self._nextrow = \ - self.db().protocol.get_row() + (foo, eof) = self._nextrow = self._connection.get_row() if eof is not None: self._handle_eof(eof) - if self.rowcount == -1: - self.rowcount = 1 + if self._rowcount == -1: + self._rowcount = 1 else: - self.rowcount += 1 + self._rowcount += 1 if eof: self._handle_eof(eof) except: @@ -538,7 +617,7 @@ def fetchone(self): def fetchmany(self,size=None): res = [] cnt = (size or self.arraysize) - while cnt > 0 and self._have_result: + while cnt > 0 and self._have_unread_result(): cnt -= 1 row = self.fetchone() if row: @@ -547,19 +626,53 @@ def fetchmany(self,size=None): return res def fetchall(self): - if self._have_result is False: + if not self._have_unread_result(): raise errors.InterfaceError("No result set to fetch from.") - res = [] - (rows, eof) = self.db().protocol.get_rows() - self.rowcount = len(rows) - for i in xrange(0,self.rowcount): - res.append(self._row_to_python(rows[i])) + (rows, eof) = self._connection.get_rows() + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) + res = [self._row_to_python(row) for row in rows] self._handle_eof(eof) + rowcount = len(rows) + if rowcount >= 0 and self._rowcount == -1: + self._rowcount = 0 + self._rowcount += rowcount return res @property def column_names(self): + """Returns column names + + This property returns the columns names as a tuple. + + Returns a tuple. + """ + if not self.description: + return () return tuple( [d[0].decode('utf8') for d in self.description] ) + + @property + def statement(self): + """Returns the executed statement + + This property returns the executed statement. When multiple + statements were executed, the current statement in the iterator + will be returned. + """ + return self._executed.strip() + + @property + def with_rows(self): + """Returns whether the cursor could have rows returned + + This property returns True when column descriptions are available + and possibly also rows, which will need to be fetched. + + Returns True or False. + """ + if not self.description: + return False + return True def __unicode__(self): fmt = "MySQLCursor: %s" @@ -578,18 +691,18 @@ def __str__(self): class MySQLCursorBuffered(MySQLCursor): """Cursor which fetches rows within execute()""" - def __init__(self, db=None): - MySQLCursor.__init__(self, db) + def __init__(self, connection=None): + MySQLCursor.__init__(self, connection) self._rows = None self._next_row = 0 def _handle_resultset(self): - (self._rows, eof) = self.db().protocol.get_rows() - self.rowcount = len(self._rows) + (self._rows, eof) = self._connection.get_rows() + self._rowcount = len(self._rows) self._handle_eof(eof) self._next_row = 0 try: - self.db().unread_result = False + self._connection.unread_result = False except: pass @@ -611,7 +724,7 @@ def fetchall(self): if self._rows is None: raise errors.InterfaceError("No result set to fetch from.") res = [] - for row in self._rows: + for row in self._rows[self._next_row:]: res.append(self._row_to_python(row)) self._next_row = len(self._rows) return res @@ -627,6 +740,10 @@ def fetchmany(self,size=None): return res + @property + def with_rows(self): + return self._rows is not None + class MySQLCursorRaw(MySQLCursor): def fetchone(self): @@ -636,11 +753,16 @@ def fetchone(self): return None def fetchall(self): - if self._have_result is False: + if not self._have_unread_result(): raise errors.InterfaceError("No result set to fetch from.") - (rows, eof) = self.db().protocol.get_rows() - self.rowcount = len(rows) + (rows, eof) = self._connection.get_rows() + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) self._handle_eof(eof) + rowcount = len(rows) + if rowcount >= 0 and self._rowcount == -1: + self._rowcount = 0 + self._rowcount += rowcount return rows class MySQLCursorBufferedRaw(MySQLCursorBuffered): @@ -654,5 +776,4 @@ def fetchone(self): def fetchall(self): if self._rows is None: raise errors.InterfaceError("No result set to fetch from.") - return [ r for r in self._rows ] - + return self._rows[self._next_row:] diff --git a/lib/mysql/connector/dbapi.py b/lib/mysql/connector/dbapi.py index 57fdcf1..1af5a2f 100644 --- a/lib/mysql/connector/dbapi.py +++ b/lib/mysql/connector/dbapi.py @@ -1,40 +1,42 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -"""DB API v2.0 required +""" +This module implements some constructors and singletons as required by the +DB API v2.0 (PEP-249). """ import time import datetime -import constants +from mysql.connector import constants class _DBAPITypeObject: - def __init__(self,*values): + def __init__(self, *values): self.values = values - def __cmp__(self,other): + def __cmp__(self, other): if other in self.values: return 0 if other < self.values: diff --git a/lib/mysql/connector/errorcode.py b/lib/mysql/connector/errorcode.py new file mode 100644 index 0000000..2ba3189 --- /dev/null +++ b/lib/mysql/connector/errorcode.py @@ -0,0 +1,968 @@ +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# This file was auto-generated. +_GENERATED_ON = '2013-06-19' +_MYSQL_VERSION = (5, 7, 1) + +"""This module contains the MySQL Server and Client error codes""" + +# Start MySQL Errors +ER_HASHCHK = 1000 +ER_NISAMCHK = 1001 +ER_NO = 1002 +ER_YES = 1003 +ER_CANT_CREATE_FILE = 1004 +ER_CANT_CREATE_TABLE = 1005 +ER_CANT_CREATE_DB = 1006 +ER_DB_CREATE_EXISTS = 1007 +ER_DB_DROP_EXISTS = 1008 +ER_DB_DROP_DELETE = 1009 +ER_DB_DROP_RMDIR = 1010 +ER_CANT_DELETE_FILE = 1011 +ER_CANT_FIND_SYSTEM_REC = 1012 +ER_CANT_GET_STAT = 1013 +ER_CANT_GET_WD = 1014 +ER_CANT_LOCK = 1015 +ER_CANT_OPEN_FILE = 1016 +ER_FILE_NOT_FOUND = 1017 +ER_CANT_READ_DIR = 1018 +ER_CANT_SET_WD = 1019 +ER_CHECKREAD = 1020 +ER_DISK_FULL = 1021 +ER_DUP_KEY = 1022 +ER_ERROR_ON_CLOSE = 1023 +ER_ERROR_ON_READ = 1024 +ER_ERROR_ON_RENAME = 1025 +ER_ERROR_ON_WRITE = 1026 +ER_FILE_USED = 1027 +ER_FILSORT_ABORT = 1028 +ER_FORM_NOT_FOUND = 1029 +ER_GET_ERRNO = 1030 +ER_ILLEGAL_HA = 1031 +ER_KEY_NOT_FOUND = 1032 +ER_NOT_FORM_FILE = 1033 +ER_NOT_KEYFILE = 1034 +ER_OLD_KEYFILE = 1035 +ER_OPEN_AS_READONLY = 1036 +ER_OUTOFMEMORY = 1037 +ER_OUT_OF_SORTMEMORY = 1038 +ER_UNEXPECTED_EOF = 1039 +ER_CON_COUNT_ERROR = 1040 +ER_OUT_OF_RESOURCES = 1041 +ER_BAD_HOST_ERROR = 1042 +ER_HANDSHAKE_ERROR = 1043 +ER_DBACCESS_DENIED_ERROR = 1044 +ER_ACCESS_DENIED_ERROR = 1045 +ER_NO_DB_ERROR = 1046 +ER_UNKNOWN_COM_ERROR = 1047 +ER_BAD_NULL_ERROR = 1048 +ER_BAD_DB_ERROR = 1049 +ER_TABLE_EXISTS_ERROR = 1050 +ER_BAD_TABLE_ERROR = 1051 +ER_NON_UNIQ_ERROR = 1052 +ER_SERVER_SHUTDOWN = 1053 +ER_BAD_FIELD_ERROR = 1054 +ER_WRONG_FIELD_WITH_GROUP = 1055 +ER_WRONG_GROUP_FIELD = 1056 +ER_WRONG_SUM_SELECT = 1057 +ER_WRONG_VALUE_COUNT = 1058 +ER_TOO_LONG_IDENT = 1059 +ER_DUP_FIELDNAME = 1060 +ER_DUP_KEYNAME = 1061 +ER_DUP_ENTRY = 1062 +ER_WRONG_FIELD_SPEC = 1063 +ER_PARSE_ERROR = 1064 +ER_EMPTY_QUERY = 1065 +ER_NONUNIQ_TABLE = 1066 +ER_INVALID_DEFAULT = 1067 +ER_MULTIPLE_PRI_KEY = 1068 +ER_TOO_MANY_KEYS = 1069 +ER_TOO_MANY_KEY_PARTS = 1070 +ER_TOO_LONG_KEY = 1071 +ER_KEY_COLUMN_DOES_NOT_EXITS = 1072 +ER_BLOB_USED_AS_KEY = 1073 +ER_TOO_BIG_FIELDLENGTH = 1074 +ER_WRONG_AUTO_KEY = 1075 +ER_READY = 1076 +ER_NORMAL_SHUTDOWN = 1077 +ER_GOT_SIGNAL = 1078 +ER_SHUTDOWN_COMPLETE = 1079 +ER_FORCING_CLOSE = 1080 +ER_IPSOCK_ERROR = 1081 +ER_NO_SUCH_INDEX = 1082 +ER_WRONG_FIELD_TERMINATORS = 1083 +ER_BLOBS_AND_NO_TERMINATED = 1084 +ER_TEXTFILE_NOT_READABLE = 1085 +ER_FILE_EXISTS_ERROR = 1086 +ER_LOAD_INFO = 1087 +ER_ALTER_INFO = 1088 +ER_WRONG_SUB_KEY = 1089 +ER_CANT_REMOVE_ALL_FIELDS = 1090 +ER_CANT_DROP_FIELD_OR_KEY = 1091 +ER_INSERT_INFO = 1092 +ER_UPDATE_TABLE_USED = 1093 +ER_NO_SUCH_THREAD = 1094 +ER_KILL_DENIED_ERROR = 1095 +ER_NO_TABLES_USED = 1096 +ER_TOO_BIG_SET = 1097 +ER_NO_UNIQUE_LOGFILE = 1098 +ER_TABLE_NOT_LOCKED_FOR_WRITE = 1099 +ER_TABLE_NOT_LOCKED = 1100 +ER_BLOB_CANT_HAVE_DEFAULT = 1101 +ER_WRONG_DB_NAME = 1102 +ER_WRONG_TABLE_NAME = 1103 +ER_TOO_BIG_SELECT = 1104 +ER_UNKNOWN_ERROR = 1105 +ER_UNKNOWN_PROCEDURE = 1106 +ER_WRONG_PARAMCOUNT_TO_PROCEDURE = 1107 +ER_WRONG_PARAMETERS_TO_PROCEDURE = 1108 +ER_UNKNOWN_TABLE = 1109 +ER_FIELD_SPECIFIED_TWICE = 1110 +ER_INVALID_GROUP_FUNC_USE = 1111 +ER_UNSUPPORTED_EXTENSION = 1112 +ER_TABLE_MUST_HAVE_COLUMNS = 1113 +ER_RECORD_FILE_FULL = 1114 +ER_UNKNOWN_CHARACTER_SET = 1115 +ER_TOO_MANY_TABLES = 1116 +ER_TOO_MANY_FIELDS = 1117 +ER_TOO_BIG_ROWSIZE = 1118 +ER_STACK_OVERRUN = 1119 +ER_WRONG_OUTER_JOIN = 1120 +ER_NULL_COLUMN_IN_INDEX = 1121 +ER_CANT_FIND_UDF = 1122 +ER_CANT_INITIALIZE_UDF = 1123 +ER_UDF_NO_PATHS = 1124 +ER_UDF_EXISTS = 1125 +ER_CANT_OPEN_LIBRARY = 1126 +ER_CANT_FIND_DL_ENTRY = 1127 +ER_FUNCTION_NOT_DEFINED = 1128 +ER_HOST_IS_BLOCKED = 1129 +ER_HOST_NOT_PRIVILEGED = 1130 +ER_PASSWORD_ANONYMOUS_USER = 1131 +ER_PASSWORD_NOT_ALLOWED = 1132 +ER_PASSWORD_NO_MATCH = 1133 +ER_UPDATE_INFO = 1134 +ER_CANT_CREATE_THREAD = 1135 +ER_WRONG_VALUE_COUNT_ON_ROW = 1136 +ER_CANT_REOPEN_TABLE = 1137 +ER_INVALID_USE_OF_NULL = 1138 +ER_REGEXP_ERROR = 1139 +ER_MIX_OF_GROUP_FUNC_AND_FIELDS = 1140 +ER_NONEXISTING_GRANT = 1141 +ER_TABLEACCESS_DENIED_ERROR = 1142 +ER_COLUMNACCESS_DENIED_ERROR = 1143 +ER_ILLEGAL_GRANT_FOR_TABLE = 1144 +ER_GRANT_WRONG_HOST_OR_USER = 1145 +ER_NO_SUCH_TABLE = 1146 +ER_NONEXISTING_TABLE_GRANT = 1147 +ER_NOT_ALLOWED_COMMAND = 1148 +ER_SYNTAX_ERROR = 1149 +ER_UNUSED1 = 1150 +ER_UNUSED2 = 1151 +ER_ABORTING_CONNECTION = 1152 +ER_NET_PACKET_TOO_LARGE = 1153 +ER_NET_READ_ERROR_FROM_PIPE = 1154 +ER_NET_FCNTL_ERROR = 1155 +ER_NET_PACKETS_OUT_OF_ORDER = 1156 +ER_NET_UNCOMPRESS_ERROR = 1157 +ER_NET_READ_ERROR = 1158 +ER_NET_READ_INTERRUPTED = 1159 +ER_NET_ERROR_ON_WRITE = 1160 +ER_NET_WRITE_INTERRUPTED = 1161 +ER_TOO_LONG_STRING = 1162 +ER_TABLE_CANT_HANDLE_BLOB = 1163 +ER_TABLE_CANT_HANDLE_AUTO_INCREMENT = 1164 +ER_UNUSED3 = 1165 +ER_WRONG_COLUMN_NAME = 1166 +ER_WRONG_KEY_COLUMN = 1167 +ER_WRONG_MRG_TABLE = 1168 +ER_DUP_UNIQUE = 1169 +ER_BLOB_KEY_WITHOUT_LENGTH = 1170 +ER_PRIMARY_CANT_HAVE_NULL = 1171 +ER_TOO_MANY_ROWS = 1172 +ER_REQUIRES_PRIMARY_KEY = 1173 +ER_NO_RAID_COMPILED = 1174 +ER_UPDATE_WITHOUT_KEY_IN_SAFE_MODE = 1175 +ER_KEY_DOES_NOT_EXITS = 1176 +ER_CHECK_NO_SUCH_TABLE = 1177 +ER_CHECK_NOT_IMPLEMENTED = 1178 +ER_CANT_DO_THIS_DURING_AN_TRANSACTION = 1179 +ER_ERROR_DURING_COMMIT = 1180 +ER_ERROR_DURING_ROLLBACK = 1181 +ER_ERROR_DURING_FLUSH_LOGS = 1182 +ER_ERROR_DURING_CHECKPOINT = 1183 +ER_NEW_ABORTING_CONNECTION = 1184 +ER_DUMP_NOT_IMPLEMENTED = 1185 +ER_FLUSH_MASTER_BINLOG_CLOSED = 1186 +ER_INDEX_REBUILD = 1187 +ER_MASTER = 1188 +ER_MASTER_NET_READ = 1189 +ER_MASTER_NET_WRITE = 1190 +ER_FT_MATCHING_KEY_NOT_FOUND = 1191 +ER_LOCK_OR_ACTIVE_TRANSACTION = 1192 +ER_UNKNOWN_SYSTEM_VARIABLE = 1193 +ER_CRASHED_ON_USAGE = 1194 +ER_CRASHED_ON_REPAIR = 1195 +ER_WARNING_NOT_COMPLETE_ROLLBACK = 1196 +ER_TRANS_CACHE_FULL = 1197 +ER_SLAVE_MUST_STOP = 1198 +ER_SLAVE_NOT_RUNNING = 1199 +ER_BAD_SLAVE = 1200 +ER_MASTER_INFO = 1201 +ER_SLAVE_THREAD = 1202 +ER_TOO_MANY_USER_CONNECTIONS = 1203 +ER_SET_CONSTANTS_ONLY = 1204 +ER_LOCK_WAIT_TIMEOUT = 1205 +ER_LOCK_TABLE_FULL = 1206 +ER_READ_ONLY_TRANSACTION = 1207 +ER_DROP_DB_WITH_READ_LOCK = 1208 +ER_CREATE_DB_WITH_READ_LOCK = 1209 +ER_WRONG_ARGUMENTS = 1210 +ER_NO_PERMISSION_TO_CREATE_USER = 1211 +ER_UNION_TABLES_IN_DIFFERENT_DIR = 1212 +ER_LOCK_DEADLOCK = 1213 +ER_TABLE_CANT_HANDLE_FT = 1214 +ER_CANNOT_ADD_FOREIGN = 1215 +ER_NO_REFERENCED_ROW = 1216 +ER_ROW_IS_REFERENCED = 1217 +ER_CONNECT_TO_MASTER = 1218 +ER_QUERY_ON_MASTER = 1219 +ER_ERROR_WHEN_EXECUTING_COMMAND = 1220 +ER_WRONG_USAGE = 1221 +ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT = 1222 +ER_CANT_UPDATE_WITH_READLOCK = 1223 +ER_MIXING_NOT_ALLOWED = 1224 +ER_DUP_ARGUMENT = 1225 +ER_USER_LIMIT_REACHED = 1226 +ER_SPECIFIC_ACCESS_DENIED_ERROR = 1227 +ER_LOCAL_VARIABLE = 1228 +ER_GLOBAL_VARIABLE = 1229 +ER_NO_DEFAULT = 1230 +ER_WRONG_VALUE_FOR_VAR = 1231 +ER_WRONG_TYPE_FOR_VAR = 1232 +ER_VAR_CANT_BE_READ = 1233 +ER_CANT_USE_OPTION_HERE = 1234 +ER_NOT_SUPPORTED_YET = 1235 +ER_MASTER_FATAL_ERROR_READING_BINLOG = 1236 +ER_SLAVE_IGNORED_TABLE = 1237 +ER_INCORRECT_GLOBAL_LOCAL_VAR = 1238 +ER_WRONG_FK_DEF = 1239 +ER_KEY_REF_DO_NOT_MATCH_TABLE_REF = 1240 +ER_OPERAND_COLUMNS = 1241 +ER_SUBQUERY_NO_1_ROW = 1242 +ER_UNKNOWN_STMT_HANDLER = 1243 +ER_CORRUPT_HELP_DB = 1244 +ER_CYCLIC_REFERENCE = 1245 +ER_AUTO_CONVERT = 1246 +ER_ILLEGAL_REFERENCE = 1247 +ER_DERIVED_MUST_HAVE_ALIAS = 1248 +ER_SELECT_REDUCED = 1249 +ER_TABLENAME_NOT_ALLOWED_HERE = 1250 +ER_NOT_SUPPORTED_AUTH_MODE = 1251 +ER_SPATIAL_CANT_HAVE_NULL = 1252 +ER_COLLATION_CHARSET_MISMATCH = 1253 +ER_SLAVE_WAS_RUNNING = 1254 +ER_SLAVE_WAS_NOT_RUNNING = 1255 +ER_TOO_BIG_FOR_UNCOMPRESS = 1256 +ER_ZLIB_Z_MEM_ERROR = 1257 +ER_ZLIB_Z_BUF_ERROR = 1258 +ER_ZLIB_Z_DATA_ERROR = 1259 +ER_CUT_VALUE_GROUP_CONCAT = 1260 +ER_WARN_TOO_FEW_RECORDS = 1261 +ER_WARN_TOO_MANY_RECORDS = 1262 +ER_WARN_NULL_TO_NOTNULL = 1263 +ER_WARN_DATA_OUT_OF_RANGE = 1264 +WARN_DATA_TRUNCATED = 1265 +ER_WARN_USING_OTHER_HANDLER = 1266 +ER_CANT_AGGREGATE_2COLLATIONS = 1267 +ER_DROP_USER = 1268 +ER_REVOKE_GRANTS = 1269 +ER_CANT_AGGREGATE_3COLLATIONS = 1270 +ER_CANT_AGGREGATE_NCOLLATIONS = 1271 +ER_VARIABLE_IS_NOT_STRUCT = 1272 +ER_UNKNOWN_COLLATION = 1273 +ER_SLAVE_IGNORED_SSL_PARAMS = 1274 +ER_SERVER_IS_IN_SECURE_AUTH_MODE = 1275 +ER_WARN_FIELD_RESOLVED = 1276 +ER_BAD_SLAVE_UNTIL_COND = 1277 +ER_MISSING_SKIP_SLAVE = 1278 +ER_UNTIL_COND_IGNORED = 1279 +ER_WRONG_NAME_FOR_INDEX = 1280 +ER_WRONG_NAME_FOR_CATALOG = 1281 +ER_WARN_QC_RESIZE = 1282 +ER_BAD_FT_COLUMN = 1283 +ER_UNKNOWN_KEY_CACHE = 1284 +ER_WARN_HOSTNAME_WONT_WORK = 1285 +ER_UNKNOWN_STORAGE_ENGINE = 1286 +ER_WARN_DEPRECATED_SYNTAX = 1287 +ER_NON_UPDATABLE_TABLE = 1288 +ER_FEATURE_DISABLED = 1289 +ER_OPTION_PREVENTS_STATEMENT = 1290 +ER_DUPLICATED_VALUE_IN_TYPE = 1291 +ER_TRUNCATED_WRONG_VALUE = 1292 +ER_TOO_MUCH_AUTO_TIMESTAMP_COLS = 1293 +ER_INVALID_ON_UPDATE = 1294 +ER_UNSUPPORTED_PS = 1295 +ER_GET_ERRMSG = 1296 +ER_GET_TEMPORARY_ERRMSG = 1297 +ER_UNKNOWN_TIME_ZONE = 1298 +ER_WARN_INVALID_TIMESTAMP = 1299 +ER_INVALID_CHARACTER_STRING = 1300 +ER_WARN_ALLOWED_PACKET_OVERFLOWED = 1301 +ER_CONFLICTING_DECLARATIONS = 1302 +ER_SP_NO_RECURSIVE_CREATE = 1303 +ER_SP_ALREADY_EXISTS = 1304 +ER_SP_DOES_NOT_EXIST = 1305 +ER_SP_DROP_FAILED = 1306 +ER_SP_STORE_FAILED = 1307 +ER_SP_LILABEL_MISMATCH = 1308 +ER_SP_LABEL_REDEFINE = 1309 +ER_SP_LABEL_MISMATCH = 1310 +ER_SP_UNINIT_VAR = 1311 +ER_SP_BADSELECT = 1312 +ER_SP_BADRETURN = 1313 +ER_SP_BADSTATEMENT = 1314 +ER_UPDATE_LOG_DEPRECATED_IGNORED = 1315 +ER_UPDATE_LOG_DEPRECATED_TRANSLATED = 1316 +ER_QUERY_INTERRUPTED = 1317 +ER_SP_WRONG_NO_OF_ARGS = 1318 +ER_SP_COND_MISMATCH = 1319 +ER_SP_NORETURN = 1320 +ER_SP_NORETURNEND = 1321 +ER_SP_BAD_CURSOR_QUERY = 1322 +ER_SP_BAD_CURSOR_SELECT = 1323 +ER_SP_CURSOR_MISMATCH = 1324 +ER_SP_CURSOR_ALREADY_OPEN = 1325 +ER_SP_CURSOR_NOT_OPEN = 1326 +ER_SP_UNDECLARED_VAR = 1327 +ER_SP_WRONG_NO_OF_FETCH_ARGS = 1328 +ER_SP_FETCH_NO_DATA = 1329 +ER_SP_DUP_PARAM = 1330 +ER_SP_DUP_VAR = 1331 +ER_SP_DUP_COND = 1332 +ER_SP_DUP_CURS = 1333 +ER_SP_CANT_ALTER = 1334 +ER_SP_SUBSELECT_NYI = 1335 +ER_STMT_NOT_ALLOWED_IN_SF_OR_TRG = 1336 +ER_SP_VARCOND_AFTER_CURSHNDLR = 1337 +ER_SP_CURSOR_AFTER_HANDLER = 1338 +ER_SP_CASE_NOT_FOUND = 1339 +ER_FPARSER_TOO_BIG_FILE = 1340 +ER_FPARSER_BAD_HEADER = 1341 +ER_FPARSER_EOF_IN_COMMENT = 1342 +ER_FPARSER_ERROR_IN_PARAMETER = 1343 +ER_FPARSER_EOF_IN_UNKNOWN_PARAMETER = 1344 +ER_VIEW_NO_EXPLAIN = 1345 +ER_FRM_UNKNOWN_TYPE = 1346 +ER_WRONG_OBJECT = 1347 +ER_NONUPDATEABLE_COLUMN = 1348 +ER_VIEW_SELECT_DERIVED = 1349 +ER_VIEW_SELECT_CLAUSE = 1350 +ER_VIEW_SELECT_VARIABLE = 1351 +ER_VIEW_SELECT_TMPTABLE = 1352 +ER_VIEW_WRONG_LIST = 1353 +ER_WARN_VIEW_MERGE = 1354 +ER_WARN_VIEW_WITHOUT_KEY = 1355 +ER_VIEW_INVALID = 1356 +ER_SP_NO_DROP_SP = 1357 +ER_SP_GOTO_IN_HNDLR = 1358 +ER_TRG_ALREADY_EXISTS = 1359 +ER_TRG_DOES_NOT_EXIST = 1360 +ER_TRG_ON_VIEW_OR_TEMP_TABLE = 1361 +ER_TRG_CANT_CHANGE_ROW = 1362 +ER_TRG_NO_SUCH_ROW_IN_TRG = 1363 +ER_NO_DEFAULT_FOR_FIELD = 1364 +ER_DIVISION_BY_ZERO = 1365 +ER_TRUNCATED_WRONG_VALUE_FOR_FIELD = 1366 +ER_ILLEGAL_VALUE_FOR_TYPE = 1367 +ER_VIEW_NONUPD_CHECK = 1368 +ER_VIEW_CHECK_FAILED = 1369 +ER_PROCACCESS_DENIED_ERROR = 1370 +ER_RELAY_LOG_FAIL = 1371 +ER_PASSWD_LENGTH = 1372 +ER_UNKNOWN_TARGET_BINLOG = 1373 +ER_IO_ERR_LOG_INDEX_READ = 1374 +ER_BINLOG_PURGE_PROHIBITED = 1375 +ER_FSEEK_FAIL = 1376 +ER_BINLOG_PURGE_FATAL_ERR = 1377 +ER_LOG_IN_USE = 1378 +ER_LOG_PURGE_UNKNOWN_ERR = 1379 +ER_RELAY_LOG_INIT = 1380 +ER_NO_BINARY_LOGGING = 1381 +ER_RESERVED_SYNTAX = 1382 +ER_WSAS_FAILED = 1383 +ER_DIFF_GROUPS_PROC = 1384 +ER_NO_GROUP_FOR_PROC = 1385 +ER_ORDER_WITH_PROC = 1386 +ER_LOGGING_PROHIBIT_CHANGING_OF = 1387 +ER_NO_FILE_MAPPING = 1388 +ER_WRONG_MAGIC = 1389 +ER_PS_MANY_PARAM = 1390 +ER_KEY_PART_0 = 1391 +ER_VIEW_CHECKSUM = 1392 +ER_VIEW_MULTIUPDATE = 1393 +ER_VIEW_NO_INSERT_FIELD_LIST = 1394 +ER_VIEW_DELETE_MERGE_VIEW = 1395 +ER_CANNOT_USER = 1396 +ER_XAER_NOTA = 1397 +ER_XAER_INVAL = 1398 +ER_XAER_RMFAIL = 1399 +ER_XAER_OUTSIDE = 1400 +ER_XAER_RMERR = 1401 +ER_XA_RBROLLBACK = 1402 +ER_NONEXISTING_PROC_GRANT = 1403 +ER_PROC_AUTO_GRANT_FAIL = 1404 +ER_PROC_AUTO_REVOKE_FAIL = 1405 +ER_DATA_TOO_LONG = 1406 +ER_SP_BAD_SQLSTATE = 1407 +ER_STARTUP = 1408 +ER_LOAD_FROM_FIXED_SIZE_ROWS_TO_VAR = 1409 +ER_CANT_CREATE_USER_WITH_GRANT = 1410 +ER_WRONG_VALUE_FOR_TYPE = 1411 +ER_TABLE_DEF_CHANGED = 1412 +ER_SP_DUP_HANDLER = 1413 +ER_SP_NOT_VAR_ARG = 1414 +ER_SP_NO_RETSET = 1415 +ER_CANT_CREATE_GEOMETRY_OBJECT = 1416 +ER_FAILED_ROUTINE_BREAK_BINLOG = 1417 +ER_BINLOG_UNSAFE_ROUTINE = 1418 +ER_BINLOG_CREATE_ROUTINE_NEED_SUPER = 1419 +ER_EXEC_STMT_WITH_OPEN_CURSOR = 1420 +ER_STMT_HAS_NO_OPEN_CURSOR = 1421 +ER_COMMIT_NOT_ALLOWED_IN_SF_OR_TRG = 1422 +ER_NO_DEFAULT_FOR_VIEW_FIELD = 1423 +ER_SP_NO_RECURSION = 1424 +ER_TOO_BIG_SCALE = 1425 +ER_TOO_BIG_PRECISION = 1426 +ER_M_BIGGER_THAN_D = 1427 +ER_WRONG_LOCK_OF_SYSTEM_TABLE = 1428 +ER_CONNECT_TO_FOREIGN_DATA_SOURCE = 1429 +ER_QUERY_ON_FOREIGN_DATA_SOURCE = 1430 +ER_FOREIGN_DATA_SOURCE_DOESNT_EXIST = 1431 +ER_FOREIGN_DATA_STRING_INVALID_CANT_CREATE = 1432 +ER_FOREIGN_DATA_STRING_INVALID = 1433 +ER_CANT_CREATE_FEDERATED_TABLE = 1434 +ER_TRG_IN_WRONG_SCHEMA = 1435 +ER_STACK_OVERRUN_NEED_MORE = 1436 +ER_TOO_LONG_BODY = 1437 +ER_WARN_CANT_DROP_DEFAULT_KEYCACHE = 1438 +ER_TOO_BIG_DISPLAYWIDTH = 1439 +ER_XAER_DUPID = 1440 +ER_DATETIME_FUNCTION_OVERFLOW = 1441 +ER_CANT_UPDATE_USED_TABLE_IN_SF_OR_TRG = 1442 +ER_VIEW_PREVENT_UPDATE = 1443 +ER_PS_NO_RECURSION = 1444 +ER_SP_CANT_SET_AUTOCOMMIT = 1445 +ER_MALFORMED_DEFINER = 1446 +ER_VIEW_FRM_NO_USER = 1447 +ER_VIEW_OTHER_USER = 1448 +ER_NO_SUCH_USER = 1449 +ER_FORBID_SCHEMA_CHANGE = 1450 +ER_ROW_IS_REFERENCED_2 = 1451 +ER_NO_REFERENCED_ROW_2 = 1452 +ER_SP_BAD_VAR_SHADOW = 1453 +ER_TRG_NO_DEFINER = 1454 +ER_OLD_FILE_FORMAT = 1455 +ER_SP_RECURSION_LIMIT = 1456 +ER_SP_PROC_TABLE_CORRUPT = 1457 +ER_SP_WRONG_NAME = 1458 +ER_TABLE_NEEDS_UPGRADE = 1459 +ER_SP_NO_AGGREGATE = 1460 +ER_MAX_PREPARED_STMT_COUNT_REACHED = 1461 +ER_VIEW_RECURSIVE = 1462 +ER_NON_GROUPING_FIELD_USED = 1463 +ER_TABLE_CANT_HANDLE_SPKEYS = 1464 +ER_NO_TRIGGERS_ON_SYSTEM_SCHEMA = 1465 +ER_REMOVED_SPACES = 1466 +ER_AUTOINC_READ_FAILED = 1467 +ER_USERNAME = 1468 +ER_HOSTNAME = 1469 +ER_WRONG_STRING_LENGTH = 1470 +ER_NON_INSERTABLE_TABLE = 1471 +ER_ADMIN_WRONG_MRG_TABLE = 1472 +ER_TOO_HIGH_LEVEL_OF_NESTING_FOR_SELECT = 1473 +ER_NAME_BECOMES_EMPTY = 1474 +ER_AMBIGUOUS_FIELD_TERM = 1475 +ER_FOREIGN_SERVER_EXISTS = 1476 +ER_FOREIGN_SERVER_DOESNT_EXIST = 1477 +ER_ILLEGAL_HA_CREATE_OPTION = 1478 +ER_PARTITION_REQUIRES_VALUES_ERROR = 1479 +ER_PARTITION_WRONG_VALUES_ERROR = 1480 +ER_PARTITION_MAXVALUE_ERROR = 1481 +ER_PARTITION_SUBPARTITION_ERROR = 1482 +ER_PARTITION_SUBPART_MIX_ERROR = 1483 +ER_PARTITION_WRONG_NO_PART_ERROR = 1484 +ER_PARTITION_WRONG_NO_SUBPART_ERROR = 1485 +ER_WRONG_EXPR_IN_PARTITION_FUNC_ERROR = 1486 +ER_NO_CONST_EXPR_IN_RANGE_OR_LIST_ERROR = 1487 +ER_FIELD_NOT_FOUND_PART_ERROR = 1488 +ER_LIST_OF_FIELDS_ONLY_IN_HASH_ERROR = 1489 +ER_INCONSISTENT_PARTITION_INFO_ERROR = 1490 +ER_PARTITION_FUNC_NOT_ALLOWED_ERROR = 1491 +ER_PARTITIONS_MUST_BE_DEFINED_ERROR = 1492 +ER_RANGE_NOT_INCREASING_ERROR = 1493 +ER_INCONSISTENT_TYPE_OF_FUNCTIONS_ERROR = 1494 +ER_MULTIPLE_DEF_CONST_IN_LIST_PART_ERROR = 1495 +ER_PARTITION_ENTRY_ERROR = 1496 +ER_MIX_HANDLER_ERROR = 1497 +ER_PARTITION_NOT_DEFINED_ERROR = 1498 +ER_TOO_MANY_PARTITIONS_ERROR = 1499 +ER_SUBPARTITION_ERROR = 1500 +ER_CANT_CREATE_HANDLER_FILE = 1501 +ER_BLOB_FIELD_IN_PART_FUNC_ERROR = 1502 +ER_UNIQUE_KEY_NEED_ALL_FIELDS_IN_PF = 1503 +ER_NO_PARTS_ERROR = 1504 +ER_PARTITION_MGMT_ON_NONPARTITIONED = 1505 +ER_FOREIGN_KEY_ON_PARTITIONED = 1506 +ER_DROP_PARTITION_NON_EXISTENT = 1507 +ER_DROP_LAST_PARTITION = 1508 +ER_COALESCE_ONLY_ON_HASH_PARTITION = 1509 +ER_REORG_HASH_ONLY_ON_SAME_NO = 1510 +ER_REORG_NO_PARAM_ERROR = 1511 +ER_ONLY_ON_RANGE_LIST_PARTITION = 1512 +ER_ADD_PARTITION_SUBPART_ERROR = 1513 +ER_ADD_PARTITION_NO_NEW_PARTITION = 1514 +ER_COALESCE_PARTITION_NO_PARTITION = 1515 +ER_REORG_PARTITION_NOT_EXIST = 1516 +ER_SAME_NAME_PARTITION = 1517 +ER_NO_BINLOG_ERROR = 1518 +ER_CONSECUTIVE_REORG_PARTITIONS = 1519 +ER_REORG_OUTSIDE_RANGE = 1520 +ER_PARTITION_FUNCTION_FAILURE = 1521 +ER_PART_STATE_ERROR = 1522 +ER_LIMITED_PART_RANGE = 1523 +ER_PLUGIN_IS_NOT_LOADED = 1524 +ER_WRONG_VALUE = 1525 +ER_NO_PARTITION_FOR_GIVEN_VALUE = 1526 +ER_FILEGROUP_OPTION_ONLY_ONCE = 1527 +ER_CREATE_FILEGROUP_FAILED = 1528 +ER_DROP_FILEGROUP_FAILED = 1529 +ER_TABLESPACE_AUTO_EXTEND_ERROR = 1530 +ER_WRONG_SIZE_NUMBER = 1531 +ER_SIZE_OVERFLOW_ERROR = 1532 +ER_ALTER_FILEGROUP_FAILED = 1533 +ER_BINLOG_ROW_LOGGING_FAILED = 1534 +ER_BINLOG_ROW_WRONG_TABLE_DEF = 1535 +ER_BINLOG_ROW_RBR_TO_SBR = 1536 +ER_EVENT_ALREADY_EXISTS = 1537 +ER_EVENT_STORE_FAILED = 1538 +ER_EVENT_DOES_NOT_EXIST = 1539 +ER_EVENT_CANT_ALTER = 1540 +ER_EVENT_DROP_FAILED = 1541 +ER_EVENT_INTERVAL_NOT_POSITIVE_OR_TOO_BIG = 1542 +ER_EVENT_ENDS_BEFORE_STARTS = 1543 +ER_EVENT_EXEC_TIME_IN_THE_PAST = 1544 +ER_EVENT_OPEN_TABLE_FAILED = 1545 +ER_EVENT_NEITHER_M_EXPR_NOR_M_AT = 1546 +ER_OBSOLETE_COL_COUNT_DOESNT_MATCH_CORRUPTED = 1547 +ER_OBSOLETE_CANNOT_LOAD_FROM_TABLE = 1548 +ER_EVENT_CANNOT_DELETE = 1549 +ER_EVENT_COMPILE_ERROR = 1550 +ER_EVENT_SAME_NAME = 1551 +ER_EVENT_DATA_TOO_LONG = 1552 +ER_DROP_INDEX_FK = 1553 +ER_WARN_DEPRECATED_SYNTAX_WITH_VER = 1554 +ER_CANT_WRITE_LOCK_LOG_TABLE = 1555 +ER_CANT_LOCK_LOG_TABLE = 1556 +ER_FOREIGN_DUPLICATE_KEY_OLD_UNUSED = 1557 +ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE = 1558 +ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR = 1559 +ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1560 +ER_NDB_CANT_SWITCH_BINLOG_FORMAT = 1561 +ER_PARTITION_NO_TEMPORARY = 1562 +ER_PARTITION_CONST_DOMAIN_ERROR = 1563 +ER_PARTITION_FUNCTION_IS_NOT_ALLOWED = 1564 +ER_DDL_LOG_ERROR = 1565 +ER_NULL_IN_VALUES_LESS_THAN = 1566 +ER_WRONG_PARTITION_NAME = 1567 +ER_CANT_CHANGE_TX_CHARACTERISTICS = 1568 +ER_DUP_ENTRY_AUTOINCREMENT_CASE = 1569 +ER_EVENT_MODIFY_QUEUE_ERROR = 1570 +ER_EVENT_SET_VAR_ERROR = 1571 +ER_PARTITION_MERGE_ERROR = 1572 +ER_CANT_ACTIVATE_LOG = 1573 +ER_RBR_NOT_AVAILABLE = 1574 +ER_BASE64_DECODE_ERROR = 1575 +ER_EVENT_RECURSION_FORBIDDEN = 1576 +ER_EVENTS_DB_ERROR = 1577 +ER_ONLY_INTEGERS_ALLOWED = 1578 +ER_UNSUPORTED_LOG_ENGINE = 1579 +ER_BAD_LOG_STATEMENT = 1580 +ER_CANT_RENAME_LOG_TABLE = 1581 +ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT = 1582 +ER_WRONG_PARAMETERS_TO_NATIVE_FCT = 1583 +ER_WRONG_PARAMETERS_TO_STORED_FCT = 1584 +ER_NATIVE_FCT_NAME_COLLISION = 1585 +ER_DUP_ENTRY_WITH_KEY_NAME = 1586 +ER_BINLOG_PURGE_EMFILE = 1587 +ER_EVENT_CANNOT_CREATE_IN_THE_PAST = 1588 +ER_EVENT_CANNOT_ALTER_IN_THE_PAST = 1589 +ER_SLAVE_INCIDENT = 1590 +ER_NO_PARTITION_FOR_GIVEN_VALUE_SILENT = 1591 +ER_BINLOG_UNSAFE_STATEMENT = 1592 +ER_SLAVE_FATAL_ERROR = 1593 +ER_SLAVE_RELAY_LOG_READ_FAILURE = 1594 +ER_SLAVE_RELAY_LOG_WRITE_FAILURE = 1595 +ER_SLAVE_CREATE_EVENT_FAILURE = 1596 +ER_SLAVE_MASTER_COM_FAILURE = 1597 +ER_BINLOG_LOGGING_IMPOSSIBLE = 1598 +ER_VIEW_NO_CREATION_CTX = 1599 +ER_VIEW_INVALID_CREATION_CTX = 1600 +ER_SR_INVALID_CREATION_CTX = 1601 +ER_TRG_CORRUPTED_FILE = 1602 +ER_TRG_NO_CREATION_CTX = 1603 +ER_TRG_INVALID_CREATION_CTX = 1604 +ER_EVENT_INVALID_CREATION_CTX = 1605 +ER_TRG_CANT_OPEN_TABLE = 1606 +ER_CANT_CREATE_SROUTINE = 1607 +ER_NEVER_USED = 1608 +ER_NO_FORMAT_DESCRIPTION_EVENT_BEFORE_BINLOG_STATEMENT = 1609 +ER_SLAVE_CORRUPT_EVENT = 1610 +ER_LOAD_DATA_INVALID_COLUMN = 1611 +ER_LOG_PURGE_NO_FILE = 1612 +ER_XA_RBTIMEOUT = 1613 +ER_XA_RBDEADLOCK = 1614 +ER_NEED_REPREPARE = 1615 +ER_DELAYED_NOT_SUPPORTED = 1616 +WARN_NO_MASTER_INFO = 1617 +WARN_OPTION_IGNORED = 1618 +WARN_PLUGIN_DELETE_BUILTIN = 1619 +WARN_PLUGIN_BUSY = 1620 +ER_VARIABLE_IS_READONLY = 1621 +ER_WARN_ENGINE_TRANSACTION_ROLLBACK = 1622 +ER_SLAVE_HEARTBEAT_FAILURE = 1623 +ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE = 1624 +ER_NDB_REPLICATION_SCHEMA_ERROR = 1625 +ER_CONFLICT_FN_PARSE_ERROR = 1626 +ER_EXCEPTIONS_WRITE_ERROR = 1627 +ER_TOO_LONG_TABLE_COMMENT = 1628 +ER_TOO_LONG_FIELD_COMMENT = 1629 +ER_FUNC_INEXISTENT_NAME_COLLISION = 1630 +ER_DATABASE_NAME = 1631 +ER_TABLE_NAME = 1632 +ER_PARTITION_NAME = 1633 +ER_SUBPARTITION_NAME = 1634 +ER_TEMPORARY_NAME = 1635 +ER_RENAMED_NAME = 1636 +ER_TOO_MANY_CONCURRENT_TRXS = 1637 +WARN_NON_ASCII_SEPARATOR_NOT_IMPLEMENTED = 1638 +ER_DEBUG_SYNC_TIMEOUT = 1639 +ER_DEBUG_SYNC_HIT_LIMIT = 1640 +ER_DUP_SIGNAL_SET = 1641 +ER_SIGNAL_WARN = 1642 +ER_SIGNAL_NOT_FOUND = 1643 +ER_SIGNAL_EXCEPTION = 1644 +ER_RESIGNAL_WITHOUT_ACTIVE_HANDLER = 1645 +ER_SIGNAL_BAD_CONDITION_TYPE = 1646 +WARN_COND_ITEM_TRUNCATED = 1647 +ER_COND_ITEM_TOO_LONG = 1648 +ER_UNKNOWN_LOCALE = 1649 +ER_SLAVE_IGNORE_SERVER_IDS = 1650 +ER_QUERY_CACHE_DISABLED = 1651 +ER_SAME_NAME_PARTITION_FIELD = 1652 +ER_PARTITION_COLUMN_LIST_ERROR = 1653 +ER_WRONG_TYPE_COLUMN_VALUE_ERROR = 1654 +ER_TOO_MANY_PARTITION_FUNC_FIELDS_ERROR = 1655 +ER_MAXVALUE_IN_VALUES_IN = 1656 +ER_TOO_MANY_VALUES_ERROR = 1657 +ER_ROW_SINGLE_PARTITION_FIELD_ERROR = 1658 +ER_FIELD_TYPE_NOT_ALLOWED_AS_PARTITION_FIELD = 1659 +ER_PARTITION_FIELDS_TOO_LONG = 1660 +ER_BINLOG_ROW_ENGINE_AND_STMT_ENGINE = 1661 +ER_BINLOG_ROW_MODE_AND_STMT_ENGINE = 1662 +ER_BINLOG_UNSAFE_AND_STMT_ENGINE = 1663 +ER_BINLOG_ROW_INJECTION_AND_STMT_ENGINE = 1664 +ER_BINLOG_STMT_MODE_AND_ROW_ENGINE = 1665 +ER_BINLOG_ROW_INJECTION_AND_STMT_MODE = 1666 +ER_BINLOG_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1667 +ER_BINLOG_UNSAFE_LIMIT = 1668 +ER_UNUSED4 = 1669 +ER_BINLOG_UNSAFE_SYSTEM_TABLE = 1670 +ER_BINLOG_UNSAFE_AUTOINC_COLUMNS = 1671 +ER_BINLOG_UNSAFE_UDF = 1672 +ER_BINLOG_UNSAFE_SYSTEM_VARIABLE = 1673 +ER_BINLOG_UNSAFE_SYSTEM_FUNCTION = 1674 +ER_BINLOG_UNSAFE_NONTRANS_AFTER_TRANS = 1675 +ER_MESSAGE_AND_STATEMENT = 1676 +ER_SLAVE_CONVERSION_FAILED = 1677 +ER_SLAVE_CANT_CREATE_CONVERSION = 1678 +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_FORMAT = 1679 +ER_PATH_LENGTH = 1680 +ER_WARN_DEPRECATED_SYNTAX_NO_REPLACEMENT = 1681 +ER_WRONG_NATIVE_TABLE_STRUCTURE = 1682 +ER_WRONG_PERFSCHEMA_USAGE = 1683 +ER_WARN_I_S_SKIPPED_TABLE = 1684 +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1685 +ER_STORED_FUNCTION_PREVENTS_SWITCH_BINLOG_DIRECT = 1686 +ER_SPATIAL_MUST_HAVE_GEOM_COL = 1687 +ER_TOO_LONG_INDEX_COMMENT = 1688 +ER_LOCK_ABORTED = 1689 +ER_DATA_OUT_OF_RANGE = 1690 +ER_WRONG_SPVAR_TYPE_IN_LIMIT = 1691 +ER_BINLOG_UNSAFE_MULTIPLE_ENGINES_AND_SELF_LOGGING_ENGINE = 1692 +ER_BINLOG_UNSAFE_MIXED_STATEMENT = 1693 +ER_INSIDE_TRANSACTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1694 +ER_STORED_FUNCTION_PREVENTS_SWITCH_SQL_LOG_BIN = 1695 +ER_FAILED_READ_FROM_PAR_FILE = 1696 +ER_VALUES_IS_NOT_INT_TYPE_ERROR = 1697 +ER_ACCESS_DENIED_NO_PASSWORD_ERROR = 1698 +ER_SET_PASSWORD_AUTH_PLUGIN = 1699 +ER_GRANT_PLUGIN_USER_EXISTS = 1700 +ER_TRUNCATE_ILLEGAL_FK = 1701 +ER_PLUGIN_IS_PERMANENT = 1702 +ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MIN = 1703 +ER_SLAVE_HEARTBEAT_VALUE_OUT_OF_RANGE_MAX = 1704 +ER_STMT_CACHE_FULL = 1705 +ER_MULTI_UPDATE_KEY_CONFLICT = 1706 +ER_TABLE_NEEDS_REBUILD = 1707 +WARN_OPTION_BELOW_LIMIT = 1708 +ER_INDEX_COLUMN_TOO_LONG = 1709 +ER_ERROR_IN_TRIGGER_BODY = 1710 +ER_ERROR_IN_UNKNOWN_TRIGGER_BODY = 1711 +ER_INDEX_CORRUPT = 1712 +ER_UNDO_RECORD_TOO_BIG = 1713 +ER_BINLOG_UNSAFE_INSERT_IGNORE_SELECT = 1714 +ER_BINLOG_UNSAFE_INSERT_SELECT_UPDATE = 1715 +ER_BINLOG_UNSAFE_REPLACE_SELECT = 1716 +ER_BINLOG_UNSAFE_CREATE_IGNORE_SELECT = 1717 +ER_BINLOG_UNSAFE_CREATE_REPLACE_SELECT = 1718 +ER_BINLOG_UNSAFE_UPDATE_IGNORE = 1719 +ER_PLUGIN_NO_UNINSTALL = 1720 +ER_PLUGIN_NO_INSTALL = 1721 +ER_BINLOG_UNSAFE_WRITE_AUTOINC_SELECT = 1722 +ER_BINLOG_UNSAFE_CREATE_SELECT_AUTOINC = 1723 +ER_BINLOG_UNSAFE_INSERT_TWO_KEYS = 1724 +ER_TABLE_IN_FK_CHECK = 1725 +ER_UNSUPPORTED_ENGINE = 1726 +ER_BINLOG_UNSAFE_AUTOINC_NOT_FIRST = 1727 +ER_CANNOT_LOAD_FROM_TABLE_V2 = 1728 +ER_MASTER_DELAY_VALUE_OUT_OF_RANGE = 1729 +ER_ONLY_FD_AND_RBR_EVENTS_ALLOWED_IN_BINLOG_STATEMENT = 1730 +ER_PARTITION_EXCHANGE_DIFFERENT_OPTION = 1731 +ER_PARTITION_EXCHANGE_PART_TABLE = 1732 +ER_PARTITION_EXCHANGE_TEMP_TABLE = 1733 +ER_PARTITION_INSTEAD_OF_SUBPARTITION = 1734 +ER_UNKNOWN_PARTITION = 1735 +ER_TABLES_DIFFERENT_METADATA = 1736 +ER_ROW_DOES_NOT_MATCH_PARTITION = 1737 +ER_BINLOG_CACHE_SIZE_GREATER_THAN_MAX = 1738 +ER_WARN_INDEX_NOT_APPLICABLE = 1739 +ER_PARTITION_EXCHANGE_FOREIGN_KEY = 1740 +ER_NO_SUCH_KEY_VALUE = 1741 +ER_RPL_INFO_DATA_TOO_LONG = 1742 +ER_NETWORK_READ_EVENT_CHECKSUM_FAILURE = 1743 +ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE = 1744 +ER_BINLOG_STMT_CACHE_SIZE_GREATER_THAN_MAX = 1745 +ER_CANT_UPDATE_TABLE_IN_CREATE_TABLE_SELECT = 1746 +ER_PARTITION_CLAUSE_ON_NONPARTITIONED = 1747 +ER_ROW_DOES_NOT_MATCH_GIVEN_PARTITION_SET = 1748 +ER_NO_SUCH_PARTITION__UNUSED = 1749 +ER_CHANGE_RPL_INFO_REPOSITORY_FAILURE = 1750 +ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_CREATED_TEMP_TABLE = 1751 +ER_WARNING_NOT_COMPLETE_ROLLBACK_WITH_DROPPED_TEMP_TABLE = 1752 +ER_MTS_FEATURE_IS_NOT_SUPPORTED = 1753 +ER_MTS_UPDATED_DBS_GREATER_MAX = 1754 +ER_MTS_CANT_PARALLEL = 1755 +ER_MTS_INCONSISTENT_DATA = 1756 +ER_FULLTEXT_NOT_SUPPORTED_WITH_PARTITIONING = 1757 +ER_DA_INVALID_CONDITION_NUMBER = 1758 +ER_INSECURE_PLAIN_TEXT = 1759 +ER_INSECURE_CHANGE_MASTER = 1760 +ER_FOREIGN_DUPLICATE_KEY_WITH_CHILD_INFO = 1761 +ER_FOREIGN_DUPLICATE_KEY_WITHOUT_CHILD_INFO = 1762 +ER_SQLTHREAD_WITH_SECURE_SLAVE = 1763 +ER_TABLE_HAS_NO_FT = 1764 +ER_VARIABLE_NOT_SETTABLE_IN_SF_OR_TRIGGER = 1765 +ER_VARIABLE_NOT_SETTABLE_IN_TRANSACTION = 1766 +ER_GTID_NEXT_IS_NOT_IN_GTID_NEXT_LIST = 1767 +ER_CANT_CHANGE_GTID_NEXT_IN_TRANSACTION_WHEN_GTID_NEXT_LIST_IS_NULL = 1768 +ER_SET_STATEMENT_CANNOT_INVOKE_FUNCTION = 1769 +ER_GTID_NEXT_CANT_BE_AUTOMATIC_IF_GTID_NEXT_LIST_IS_NON_NULL = 1770 +ER_SKIPPING_LOGGED_TRANSACTION = 1771 +ER_MALFORMED_GTID_SET_SPECIFICATION = 1772 +ER_MALFORMED_GTID_SET_ENCODING = 1773 +ER_MALFORMED_GTID_SPECIFICATION = 1774 +ER_GNO_EXHAUSTED = 1775 +ER_BAD_SLAVE_AUTO_POSITION = 1776 +ER_AUTO_POSITION_REQUIRES_GTID_MODE_ON = 1777 +ER_CANT_DO_IMPLICIT_COMMIT_IN_TRX_WHEN_GTID_NEXT_IS_SET = 1778 +ER_GTID_MODE_2_OR_3_REQUIRES_ENFORCE_GTID_CONSISTENCY_ON = 1779 +ER_GTID_MODE_REQUIRES_BINLOG = 1780 +ER_CANT_SET_GTID_NEXT_TO_GTID_WHEN_GTID_MODE_IS_OFF = 1781 +ER_CANT_SET_GTID_NEXT_TO_ANONYMOUS_WHEN_GTID_MODE_IS_ON = 1782 +ER_CANT_SET_GTID_NEXT_LIST_TO_NON_NULL_WHEN_GTID_MODE_IS_OFF = 1783 +ER_FOUND_GTID_EVENT_WHEN_GTID_MODE_IS_OFF = 1784 +ER_GTID_UNSAFE_NON_TRANSACTIONAL_TABLE = 1785 +ER_GTID_UNSAFE_CREATE_SELECT = 1786 +ER_GTID_UNSAFE_CREATE_DROP_TEMPORARY_TABLE_IN_TRANSACTION = 1787 +ER_GTID_MODE_CAN_ONLY_CHANGE_ONE_STEP_AT_A_TIME = 1788 +ER_MASTER_HAS_PURGED_REQUIRED_GTIDS = 1789 +ER_CANT_SET_GTID_NEXT_WHEN_OWNING_GTID = 1790 +ER_UNKNOWN_EXPLAIN_FORMAT = 1791 +ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION = 1792 +ER_TOO_LONG_TABLE_PARTITION_COMMENT = 1793 +ER_SLAVE_CONFIGURATION = 1794 +ER_INNODB_FT_LIMIT = 1795 +ER_INNODB_NO_FT_TEMP_TABLE = 1796 +ER_INNODB_FT_WRONG_DOCID_COLUMN = 1797 +ER_INNODB_FT_WRONG_DOCID_INDEX = 1798 +ER_INNODB_ONLINE_LOG_TOO_BIG = 1799 +ER_UNKNOWN_ALTER_ALGORITHM = 1800 +ER_UNKNOWN_ALTER_LOCK = 1801 +ER_MTS_CHANGE_MASTER_CANT_RUN_WITH_GAPS = 1802 +ER_MTS_RECOVERY_FAILURE = 1803 +ER_MTS_RESET_WORKERS = 1804 +ER_COL_COUNT_DOESNT_MATCH_CORRUPTED_V2 = 1805 +ER_SLAVE_SILENT_RETRY_TRANSACTION = 1806 +ER_DISCARD_FK_CHECKS_RUNNING = 1807 +ER_TABLE_SCHEMA_MISMATCH = 1808 +ER_TABLE_IN_SYSTEM_TABLESPACE = 1809 +ER_IO_READ_ERROR = 1810 +ER_IO_WRITE_ERROR = 1811 +ER_TABLESPACE_MISSING = 1812 +ER_TABLESPACE_EXISTS = 1813 +ER_TABLESPACE_DISCARDED = 1814 +ER_INTERNAL_ERROR = 1815 +ER_INNODB_IMPORT_ERROR = 1816 +ER_INNODB_INDEX_CORRUPT = 1817 +ER_INVALID_YEAR_COLUMN_LENGTH = 1818 +ER_NOT_VALID_PASSWORD = 1819 +ER_MUST_CHANGE_PASSWORD = 1820 +ER_FK_NO_INDEX_CHILD = 1821 +ER_FK_NO_INDEX_PARENT = 1822 +ER_FK_FAIL_ADD_SYSTEM = 1823 +ER_FK_CANNOT_OPEN_PARENT = 1824 +ER_FK_INCORRECT_OPTION = 1825 +ER_FK_DUP_NAME = 1826 +ER_PASSWORD_FORMAT = 1827 +ER_FK_COLUMN_CANNOT_DROP = 1828 +ER_FK_COLUMN_CANNOT_DROP_CHILD = 1829 +ER_FK_COLUMN_NOT_NULL = 1830 +ER_DUP_INDEX = 1831 +ER_FK_COLUMN_CANNOT_CHANGE = 1832 +ER_FK_COLUMN_CANNOT_CHANGE_CHILD = 1833 +ER_FK_CANNOT_DELETE_PARENT = 1834 +ER_MALFORMED_PACKET = 1835 +ER_READ_ONLY_MODE = 1836 +ER_GTID_NEXT_TYPE_UNDEFINED_GROUP = 1837 +ER_VARIABLE_NOT_SETTABLE_IN_SP = 1838 +ER_CANT_SET_GTID_PURGED_WHEN_GTID_MODE_IS_OFF = 1839 +ER_CANT_SET_GTID_PURGED_WHEN_GTID_EXECUTED_IS_NOT_EMPTY = 1840 +ER_CANT_SET_GTID_PURGED_WHEN_OWNED_GTIDS_IS_NOT_EMPTY = 1841 +ER_GTID_PURGED_WAS_CHANGED = 1842 +ER_GTID_EXECUTED_WAS_CHANGED = 1843 +ER_BINLOG_STMT_MODE_AND_NO_REPL_TABLES = 1844 +ER_ALTER_OPERATION_NOT_SUPPORTED = 1845 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON = 1846 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COPY = 1847 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION = 1848 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME = 1849 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_COLUMN_TYPE = 1850 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_CHECK = 1851 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_IGNORE = 1852 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOPK = 1853 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_AUTOINC = 1854 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_HIDDEN_FTS = 1855 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_CHANGE_FTS = 1856 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FTS = 1857 +ER_SQL_SLAVE_SKIP_COUNTER_NOT_SETTABLE_IN_GTID_MODE = 1858 +ER_DUP_UNKNOWN_IN_INDEX = 1859 +ER_IDENT_CAUSES_TOO_LONG_PATH = 1860 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL = 1861 +ER_MUST_CHANGE_PASSWORD_LOGIN = 1862 +ER_ROW_IN_WRONG_PARTITION = 1863 +ER_FILE_CORRUPT = 1864 +ER_ERROR_ON_MASTER = 1865 +ER_INCONSISTENT_ERROR = 1866 +ER_STORAGE_ENGINE_NOT_LOADED = 1867 +ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 1868 +ER_WARN_LEGACY_SYNTAX_CONVERTED = 1869 +ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 1870 +ER_CANNOT_DISCARD_TEMPORARY_TABLE = 1871 +CR_UNKNOWN_ERROR = 2000 +CR_SOCKET_CREATE_ERROR = 2001 +CR_CONNECTION_ERROR = 2002 +CR_CONN_HOST_ERROR = 2003 +CR_IPSOCK_ERROR = 2004 +CR_UNKNOWN_HOST = 2005 +CR_SERVER_GONE_ERROR = 2006 +CR_VERSION_ERROR = 2007 +CR_OUT_OF_MEMORY = 2008 +CR_WRONG_HOST_INFO = 2009 +CR_LOCALHOST_CONNECTION = 2010 +CR_TCP_CONNECTION = 2011 +CR_SERVER_HANDSHAKE_ERR = 2012 +CR_SERVER_LOST = 2013 +CR_COMMANDS_OUT_OF_SYNC = 2014 +CR_NAMEDPIPE_CONNECTION = 2015 +CR_NAMEDPIPEWAIT_ERROR = 2016 +CR_NAMEDPIPEOPEN_ERROR = 2017 +CR_NAMEDPIPESETSTATE_ERROR = 2018 +CR_CANT_READ_CHARSET = 2019 +CR_NET_PACKET_TOO_LARGE = 2020 +CR_EMBEDDED_CONNECTION = 2021 +CR_PROBE_SLAVE_STATUS = 2022 +CR_PROBE_SLAVE_HOSTS = 2023 +CR_PROBE_SLAVE_CONNECT = 2024 +CR_PROBE_MASTER_CONNECT = 2025 +CR_SSL_CONNECTION_ERROR = 2026 +CR_MALFORMED_PACKET = 2027 +CR_WRONG_LICENSE = 2028 +CR_NULL_POINTER = 2029 +CR_NO_PREPARE_STMT = 2030 +CR_PARAMS_NOT_BOUND = 2031 +CR_DATA_TRUNCATED = 2032 +CR_NO_PARAMETERS_EXISTS = 2033 +CR_INVALID_PARAMETER_NO = 2034 +CR_INVALID_BUFFER_USE = 2035 +CR_UNSUPPORTED_PARAM_TYPE = 2036 +CR_SHARED_MEMORY_CONNECTION = 2037 +CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038 +CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039 +CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040 +CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041 +CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042 +CR_SHARED_MEMORY_MAP_ERROR = 2043 +CR_SHARED_MEMORY_EVENT_ERROR = 2044 +CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045 +CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046 +CR_CONN_UNKNOW_PROTOCOL = 2047 +CR_INVALID_CONN_HANDLE = 2048 +CR_SECURE_AUTH = 2049 +CR_FETCH_CANCELED = 2050 +CR_NO_DATA = 2051 +CR_NO_STMT_METADATA = 2052 +CR_NO_RESULT_SET = 2053 +CR_NOT_IMPLEMENTED = 2054 +CR_SERVER_LOST_EXTENDED = 2055 +CR_STMT_CLOSED = 2056 +CR_NEW_STMT_METADATA = 2057 +CR_ALREADY_CONNECTED = 2058 +CR_AUTH_PLUGIN_CANNOT_LOAD = 2059 +CR_DUPLICATE_CONNECTION_ATTR = 2060 +CR_AUTH_PLUGIN_ERR = 2061 +# End MySQL Errors + diff --git a/lib/mysql/connector/errors.py b/lib/mysql/connector/errors.py index 825ae9e..18d7508 100644 --- a/lib/mysql/connector/errors.py +++ b/lib/mysql/connector/errors.py @@ -1,226 +1,250 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) - +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -"""Python exceptions +"""This module implements Exception classes """ -import logging -import utils +from mysql.connector import utils +from mysql.connector import errorcode +from mysql.connector.locales import get_client_error + +_CUSTOM_ERROR_EXCEPTIONS = {} + +def custom_error_exception(error=None, exception=None): + """Define custom exceptions for MySQL server errors + + This function defines custom exceptions for MySQL server errors and + returns the current set customizations. + + If error is a MySQL Server error number, then you have to pass also the + exception class. + + The error argument can also be a dictionary in which case the key is + the server error number, and value the exception to be raised. + + If none of the arguments are given, then custom_error_exception() will + simply return the current set customizations. + + To reset the customizations, simply supply an empty dictionary. + + Examples: + import mysql.connector + from mysql.connector import errorcode + + # Server error 1028 should raise a DatabaseError + mysql.connector.custom_error_exception( + 1028, mysql.connector.DatabaseError) + + # Or using a dictionary: + mysql.connector.custom_error_exception({ + 1028: mysql.connector.DatabaseError, + 1029: mysql.connector.OperationalError, + }) + + # Reset + mysql.connector.custom_error_exception({}) -logger = logging.getLogger('myconnpy') + Returns a dictionary. + """ + global _CUSTOM_ERROR_EXCEPTIONS -# see get_mysql_exceptions method for errno ranges and smaller lists -__programming_errors = ( - 1083,1084,1090,1091,1093,1096,1097,1101,1102,1103,1107,1108,1110,1111, - 1113,1120,1124,1125,1128,1136,1366,1139,1140,1146,1149,) -__operational_errors = ( - 1028,1029,1030,1053,1077,1078,1079,1080,1081,1095,1104,1106,1114,1116, - 1117,1119,1122,1123,1126,1133,1135,1137,1145,1147,) + if isinstance(error, dict) and not len(error): + _CUSTOM_ERROR_EXCEPTIONS = {} + return _CUSTOM_ERROR_EXCEPTIONS + + if not error and not exception: + return _CUSTOM_ERROR_EXCEPTIONS + + if not isinstance(error, (int, dict)): + raise ValueError( + "The error argument should be either an integer or dictionary") + + if isinstance(error, int): + error = { error: exception } + + for errno, exception in error.items(): + if not isinstance(errno, int): + raise ValueError("error number should be an integer") + try: + if not issubclass(exception, Exception): + raise TypeError + except TypeError: + raise ValueError("exception should be subclass of Exception") + _CUSTOM_ERROR_EXCEPTIONS[errno] = exception + + return _CUSTOM_ERROR_EXCEPTIONS + +def get_mysql_exception(errno, msg, sqlstate=None): + """Get the exception matching the MySQL error -def get_mysql_exception(errno,msg): + This function will return an exception based on the SQLState. The given + message will be passed on in the returned exception. + + The exception returned can be customized using the + mysql.connector.custom_error_exception() function. - exception = OperationalError + Returns an Exception + """ + try: + return _CUSTOM_ERROR_EXCEPTIONS[errno]( + msg=msg, errno=errno, sqlstate=sqlstate) + except KeyError: + # Error was not mapped to particular exception + pass + + if not sqlstate: + return DatabaseError(msg=msg, errno=errno) + + try: + return _SQLSTATE_CLASS_EXCEPTION[sqlstate[0:2]]( + msg=msg, errno=errno, sqlstate=sqlstate) + except KeyError: + # Return default InterfaceError + return DatabaseError(msg=msg, errno=errno, sqlstate=sqlstate) + +def get_exception(packet): + """Returns an exception object based on the MySQL error - if (errno >= 1046 and errno <= 1052) or \ - (errno >= 1054 and errno <= 1061) or \ - (errno >= 1063 and errno <= 1075) or \ - errno in __programming_errors: - exception = ProgrammingError - elif errno in (1097,1109,1118,1121,1138,1292): - exception = DataError - elif errno in (1031,1089,1112,1115,1127,1148,1149): - exception = NotSupportedError - elif errno in (1062,1082,1099,1100): - exception = IntegrityError - elif errno in (1085,1086,1094,1098): - exception = InternalError - elif (errno >= 1004 and errno <= 1030) or \ - (errno >= 1132 and errno <= 1045) or \ - (errno >= 1141 and errno <= 1145) or \ - (errno >= 1129 and errno <= 1133) or \ - errno in __operational_errors: - exception = OperationalError + Returns an exception object based on the MySQL error in the given + packet. - return exception(msg,errno=errno) - -def raise_error(buf): - """Raise an errors.Error when buffer has a MySQL error""" + Returns an Error-Object. + """ errno = errmsg = None + + if packet[4] != '\xff': + raise ValueError("Packet is not an error packet") + + sqlstate = None try: - buf = buf[5:] - (buf,errno) = utils.read_int(buf, 2) - if buf[0] != '\x23': + packet = packet[5:] + (packet, errno) = utils.read_int(packet, 2) + if packet[0] != '\x23': # Error without SQLState - errmsg = buf + errmsg = packet else: - (buf,sqlstate) = utils.read_bytes(buf[1:],5) - errmsg = buf - except Exception, e: - raise InterfaceError("Failed getting Error information (%r)"\ - % e) + (packet, sqlstate) = utils.read_bytes(packet[1:], 5) + errmsg = packet + except Exception, err: + return InterfaceError("Failed getting Error information (%r)" % err) else: - raise get_mysql_exception(errno,errmsg) + return get_mysql_exception(errno, errmsg, sqlstate) -class ClientError(object): - - client_errors = { - 2000: "Unknown MySQL error", - 2001: "Can't create UNIX socket (%(socketaddr)d)", - 2002: "Can't connect to local MySQL server through socket '%(socketaddr)s' (%(errno)s)", - 2003: "Can't connect to MySQL server on '%(socketaddr)s' (%(errno)s)", - 2004: "Can't create TCP/IP socket (%s)", - 2005: "Unknown MySQL server host '%(socketaddr)s' (%s)", - 2006: "MySQL server has gone away", - 2007: "Protocol mismatch; server version = %(server_version)d, client version = %(client_version)d", - 2008: "MySQL client ran out of memory", - 2009: "Wrong host info", - 2010: "Localhost via UNIX socket", - 2011: "%(misc)s via TCP/IP", - 2012: "Error in server handshake", - 2013: "Lost connection to MySQL server during query", - 2014: "Commands out of sync; you can't run this command now", - 2015: "Named pipe: %(socketaddr)s", - 2016: "Can't wait for named pipe to host: %(host)s pipe: %(socketaddr)s (%(errno)d)", - 2017: "Can't open named pipe to host: %s pipe: %s (%(errno)d)", - 2018: "Can't set state of named pipe to host: %(host)s pipe: %(socketaddr)s (%(errno)d)", - 2019: "Can't initialize character set %(charset)s (path: %(misc)s)", - 2020: "Got packet bigger than 'max_allowed_packet' bytes", - 2021: "Embedded server", - 2022: "Error on SHOW SLAVE STATUS:", - 2023: "Error on SHOW SLAVE HOSTS:", - 2024: "Error connecting to slave:", - 2025: "Error connecting to master:", - 2026: "SSL connection error", - 2027: "Malformed packet", - 2028: "This client library is licensed only for use with MySQL servers having '%s' license", - 2029: "Invalid use of null pointer", - 2030: "Statement not prepared", - 2031: "No data supplied for parameters in prepared statement", - 2032: "Data truncated", - 2033: "No parameters exist in the statement", - 2034: "Invalid parameter number", - 2035: "Can't send long data for non-string/non-binary data types (parameter: %d)", - 2036: "Using unsupported buffer type: %d (parameter: %d)", - 2037: "Shared memory: %s", - 2038: "Can't open shared memory; client could not create request event (%d)", - 2039: "Can't open shared memory; no answer event received from server (%d)", - 2040: "Can't open shared memory; server could not allocate file mapping (%d)", - 2041: "Can't open shared memory; server could not get pointer to file mapping (%d)", - 2042: "Can't open shared memory; client could not allocate file mapping (%d)", - 2043: "Can't open shared memory; client could not get pointer to file mapping (%d)", - 2044: "Can't open shared memory; client could not create %s event (%d)", - 2045: "Can't open shared memory; no answer from server (%d)", - 2046: "Can't open shared memory; cannot send request event to server (%d)", - 2047: "Wrong or unknown protocol", - 2048: "Invalid connection handle", - 2049: "Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)", - 2050: "Row retrieval was canceled by mysql_stmt_close() call", - 2051: "Attempt to read column without prior row fetch", - 2052: "Prepared statement contains no metadata", - 2053: "Attempt to read a row while there is no result set associated with the statement", - 2054: "This feature is not implemented yet", - 2055: "Lost connection to MySQL server at '%(socketaddr)s', system error: %(errno)d", - 2056: "Statement closed indirectly because of a preceeding %s() call", - 2057: "The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again", - } - - def __new__(cls): - raise TypeError, "Can not instanciate from %s" % cls.__name__ - - @classmethod - def get_error_msg(cls,errno,values=None): - res = None - if cls.client_errors.has_key(errno): +class Error(StandardError): + """Exception that is base class for all other error exceptions""" + def __init__(self, msg=None, errno=None, values=None, sqlstate=None): + self.msg = msg + self.errno = errno or -1 + self.sqlstate = sqlstate + + if not self.msg and (2000 <= self.errno < 3000): + errmsg = get_client_error(self.errno) if values is not None: try: - res = cls.client_errors[errno] % values - except: - logger.debug("Missing values for errno %d" % errno) - res = cls.client_errors[errno], "(missing values!)" - else: - res = cls.client_errors[errno] - if res is None: - res = "Unknown client error %d" % errno - logger.debug(res) - return res - -class Error(StandardError): - - def __init__(self, m, errno=None, values=None): - try: - # process MySQL error packet - self._process_packet(m) - except: - self.errno = errno or -1 - self.sqlstate = -1 - if m is None and (errno >= 2000 and errno < 3000): - m = ClientError.get_error_msg(errno,values) - elif m is None: - m = 'Unknown error' - if self.errno != -1: - self.msg = "%s: %s" % (self.errno,m) + errmsg = errmsg % values + except TypeError, err: + errmsg = errmsg + " (Warning: %s)" % err + self.msg = errmsg + elif not self.msg: + self.msg = 'Unknown error' + + if self.msg and self.errno != -1: + if self.sqlstate: + self.msg = '%d (%s): %s' % (self.errno, self.sqlstate, + self.msg) else: - self.msg = m - - def _process_packet(self, packet): - self.errno = packet.errno - self.sqlstate = packet.sqlstate - if self.sqlstate: - self.msg = '%d (%s): %s' % (self.errno,self.sqlstate,packet.errmsg) - else: - self.msg = '%d: %s' % (self.errno, packet.errmsg) + self.msg = '%d: %s' % (self.errno, self.msg) def __str__(self): return self.msg - def __unicode__(self): - return self.msg - class Warning(StandardError): + """Exception for important warnings""" pass class InterfaceError(Error): - def __init__(self, m=None, errno=None, values=None): - Error.__init__(self, m, errno, values) + """Exception for errors related to the interface""" + pass class DatabaseError(Error): - def __init__(self, m=None, errno=None, values=None): - Error.__init__(self, m, errno, values) + """Exception for errors related to the database""" + pass class InternalError(DatabaseError): + """Exception for errors internal database errors""" pass class OperationalError(DatabaseError): + """Exception for errors related to the database's operation""" pass class ProgrammingError(DatabaseError): + """Exception for errors programming errors""" pass class IntegrityError(DatabaseError): + """Exception for errors regarding relational integrity""" pass class DataError(DatabaseError): + """Exception for errors reporting problems with processed data""" pass class NotSupportedError(DatabaseError): + """Exception for errors when an unsupported database feature was used""" pass + +_SQLSTATE_CLASS_EXCEPTION = { + '02': DataError, # no data + '07': DatabaseError, # dynamic SQL error + '08': OperationalError, # connection exception + '0A': NotSupportedError, # feature not supported + '21': DataError, # cardinality violation + '22': DataError, # data exception + '23': IntegrityError, # integrity constraint violation + '24': ProgrammingError, # invalid cursor state + '25': ProgrammingError, # invalid transaction state + '26': ProgrammingError, # invalid SQL statement name + '27': ProgrammingError, # triggered data change violation + '28': ProgrammingError, # invalid authorization specification + '2A': ProgrammingError, # direct SQL syntax error or access rule violation + '2B': DatabaseError, # dependent privilege descriptors still exist + '2C': ProgrammingError, # invalid character set name + '2D': DatabaseError, # invalid transaction termination + '2E': DatabaseError, # invalid connection name + '33': DatabaseError, # invalid SQL descriptor name + '34': ProgrammingError, # invalid cursor name + '35': ProgrammingError, # invalid condition number + '37': ProgrammingError, # dynamic SQL syntax error or access rule violation + '3C': ProgrammingError, # ambiguous cursor name + '3D': ProgrammingError, # invalid catalog name + '3F': ProgrammingError, # invalid schema name + '40': InternalError, # transaction rollback + '42': ProgrammingError, # syntax error or access rule violation + '44': InternalError, # with check option violation + 'HZ': OperationalError, # remote database access + 'XA': IntegrityError, + '0K': OperationalError, + 'HY': DatabaseError, # default when no SQLState provided by MySQL server +} + diff --git a/lib/mysql/connector/locales/__init__.py b/lib/mysql/connector/locales/__init__.py new file mode 100644 index 0000000..7ce0692 --- /dev/null +++ b/lib/mysql/connector/locales/__init__.py @@ -0,0 +1,69 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +__all__ = [ + 'get_client_error' +] + +from mysql.connector import errorcode + +def get_client_error(error, language='eng'): + """Lookup client error + + This function will lookup the client error message based on the given + error and return the error message. If the error was not found, + None will be returned. + + Error can be either an integer or a string. For example: + error: 2000 + error: CR_UNKNOWN_ERROR + + The language attribute can be used to retrieve a localized message, when + available. + + Returns a string or None. + """ + try: + tmp = __import__('mysql.connector.locales.%s' % language, + globals(), locals(), ['client_error']) + except ImportError: + raise ImportError("No localization support for language '%s'" % ( + language)) + client_error = tmp.client_error + + if isinstance(error, int): + errno = error + for key, value in errorcode.__dict__.items(): + if value == errno: + error = key + break + + if isinstance(error, (str)): + try: + return getattr(client_error, error) + except AttributeError: + return None + + raise ValueError("error argument needs to be either an integer or string") + diff --git a/lib/mysql/connector/locales/eng/__init__.py b/lib/mysql/connector/locales/eng/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/mysql/connector/locales/eng/client_error.py b/lib/mysql/connector/locales/eng/client_error.py new file mode 100644 index 0000000..b81dc5c --- /dev/null +++ b/lib/mysql/connector/locales/eng/client_error.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# This file was auto-generated. +_GENERATED_ON = '2013-06-19' +_MYSQL_VERSION = (5, 7, 1) + +# Start MySQL Error messages +CR_UNKNOWN_ERROR = u"Unknown MySQL error" +CR_SOCKET_CREATE_ERROR = u"Can't create UNIX socket (%s)" +CR_CONNECTION_ERROR = u"Can't connect to local MySQL server through socket '%-.100s' (%s)" +CR_CONN_HOST_ERROR = u"Can't connect to MySQL server on '%-.100s' (%s)" +CR_IPSOCK_ERROR = u"Can't create TCP/IP socket (%s)" +CR_UNKNOWN_HOST = u"Unknown MySQL server host '%-.100s' (%s)" +CR_SERVER_GONE_ERROR = u"MySQL server has gone away" +CR_VERSION_ERROR = u"Protocol mismatch; server version = %s, client version = %s" +CR_OUT_OF_MEMORY = u"MySQL client ran out of memory" +CR_WRONG_HOST_INFO = u"Wrong host info" +CR_LOCALHOST_CONNECTION = u"Localhost via UNIX socket" +CR_TCP_CONNECTION = u"%-.100s via TCP/IP" +CR_SERVER_HANDSHAKE_ERR = u"Error in server handshake" +CR_SERVER_LOST = u"Lost connection to MySQL server during query" +CR_COMMANDS_OUT_OF_SYNC = u"Commands out of sync; you can't run this command now" +CR_NAMEDPIPE_CONNECTION = u"Named pipe: %-.32s" +CR_NAMEDPIPEWAIT_ERROR = u"Can't wait for named pipe to host: %-.64s pipe: %-.32s (%s)" +CR_NAMEDPIPEOPEN_ERROR = u"Can't open named pipe to host: %-.64s pipe: %-.32s (%s)" +CR_NAMEDPIPESETSTATE_ERROR = u"Can't set state of named pipe to host: %-.64s pipe: %-.32s (%s)" +CR_CANT_READ_CHARSET = u"Can't initialize character set %-.32s (path: %-.100s)" +CR_NET_PACKET_TOO_LARGE = u"Got packet bigger than 'max_allowed_packet' bytes" +CR_EMBEDDED_CONNECTION = u"Embedded server" +CR_PROBE_SLAVE_STATUS = u"Error on SHOW SLAVE STATUS:" +CR_PROBE_SLAVE_HOSTS = u"Error on SHOW SLAVE HOSTS:" +CR_PROBE_SLAVE_CONNECT = u"Error connecting to slave:" +CR_PROBE_MASTER_CONNECT = u"Error connecting to master:" +CR_SSL_CONNECTION_ERROR = u"SSL connection error: %-.100s" +CR_MALFORMED_PACKET = u"Malformed packet" +CR_WRONG_LICENSE = u"This client library is licensed only for use with MySQL servers having '%s' license" +CR_NULL_POINTER = u"Invalid use of null pointer" +CR_NO_PREPARE_STMT = u"Statement not prepared" +CR_PARAMS_NOT_BOUND = u"No data supplied for parameters in prepared statement" +CR_DATA_TRUNCATED = u"Data truncated" +CR_NO_PARAMETERS_EXISTS = u"No parameters exist in the statement" +CR_INVALID_PARAMETER_NO = u"Invalid parameter number" +CR_INVALID_BUFFER_USE = u"Can't send long data for non-string/non-binary data types (parameter: %s)" +CR_UNSUPPORTED_PARAM_TYPE = u"Using unsupported buffer type: %s (parameter: %s)" +CR_SHARED_MEMORY_CONNECTION = u"Shared memory: %-.100s" +CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = u"Can't open shared memory; client could not create request event (%s)" +CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = u"Can't open shared memory; no answer event received from server (%s)" +CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = u"Can't open shared memory; server could not allocate file mapping (%s)" +CR_SHARED_MEMORY_CONNECT_MAP_ERROR = u"Can't open shared memory; server could not get pointer to file mapping (%s)" +CR_SHARED_MEMORY_FILE_MAP_ERROR = u"Can't open shared memory; client could not allocate file mapping (%s)" +CR_SHARED_MEMORY_MAP_ERROR = u"Can't open shared memory; client could not get pointer to file mapping (%s)" +CR_SHARED_MEMORY_EVENT_ERROR = u"Can't open shared memory; client could not create %s event (%s)" +CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = u"Can't open shared memory; no answer from server (%s)" +CR_SHARED_MEMORY_CONNECT_SET_ERROR = u"Can't open shared memory; cannot send request event to server (%s)" +CR_CONN_UNKNOW_PROTOCOL = u"Wrong or unknown protocol" +CR_INVALID_CONN_HANDLE = u"Invalid connection handle" +CR_SECURE_AUTH = u"Connection using old (pre-4.1.1) authentication protocol refused (client option 'secure_auth' enabled)" +CR_FETCH_CANCELED = u"Row retrieval was canceled by mysql_stmt_close() call" +CR_NO_DATA = u"Attempt to read column without prior row fetch" +CR_NO_STMT_METADATA = u"Prepared statement contains no metadata" +CR_NO_RESULT_SET = u"Attempt to read a row while there is no result set associated with the statement" +CR_NOT_IMPLEMENTED = u"This feature is not implemented yet" +CR_SERVER_LOST_EXTENDED = u"Lost connection to MySQL server at '%s', system error: %s" +CR_STMT_CLOSED = u"Statement closed indirectly because of a preceeding %s() call" +CR_NEW_STMT_METADATA = u"The number of columns in the result set differs from the number of bound buffers. You must reset the statement, rebind the result set columns, and execute the statement again" +CR_ALREADY_CONNECTED = u"This handle is already connected. Use a separate handle for each connection." +CR_AUTH_PLUGIN_CANNOT_LOAD = u"Authentication plugin '%s' cannot be loaded: %s" +CR_DUPLICATE_CONNECTION_ATTR = u"There is an attribute with the same name already" +CR_AUTH_PLUGIN_ERR = u"Authentication plugin '%s' reported error: %s" +# End MySQL Error messages + diff --git a/lib/mysql/connector/network.py b/lib/mysql/connector/network.py new file mode 100644 index 0000000..02c2a39 --- /dev/null +++ b/lib/mysql/connector/network.py @@ -0,0 +1,399 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Module implementing low-level socket communication with MySQL servers. +""" + +import os +import socket +import struct +from collections import deque +import zlib +try: + import ssl +except ImportError: + # If import fails, we don't have SSL support. + pass + +from mysql.connector import constants, errors, utils + +def _prepare_packets(buf, pktnr): + """Prepare a packet for sending to the MySQL server""" + pkts = [] + buflen = len(buf) + maxpktlen = constants.MAX_PACKET_LENGTH + while buflen > maxpktlen: + pkts.append('\xff\xff\xff' + struct.pack(' 255: + self._packet_number = 0 + return self._packet_number + + def open_connection(self): + """Open the socket""" + raise NotImplementedError + + def get_address(self): + """Get the location of the socket""" + raise NotImplementedError + + def close_connection(self): + """Close the socket""" + try: + self.sock.close() + del self._packet_queue + except (socket.error, AttributeError): + pass + + def send_plain(self, buf, packet_number=None): + """Send packets to the MySQL server""" + if packet_number is None: + self.next_packet_number + else: + self._packet_number = packet_number + packets = _prepare_packets(buf, self._packet_number) + for packet in packets: + try: + self.sock.sendall(packet) + except Exception, err: + raise errors.OperationalError(str(err)) + send = send_plain + + def send_compressed(self, buf, packet_number=None): + """Send compressed packets to the MySQL server""" + if packet_number is None: + self.next_packet_number + else: + self._packet_number = packet_number + pktnr = self._packet_number + pllen = len(buf) + zpkts = [] + maxpktlen = constants.MAX_PACKET_LENGTH + if pllen > maxpktlen: + pkts = _prepare_packets(buf, pktnr) + tmpbuf = ''.join(pkts) + del pkts + seqid = 0 + zbuf = zlib.compress(tmpbuf[:16384]) + zpkts.append(struct.pack(' maxpktlen: + zbuf = zlib.compress(tmpbuf[:maxpktlen]) + zpkts.append(struct.pack(' 50: + zbuf = zlib.compress(pkt) + zpkts.append(struct.pack(' 0: + chunk = self.sock.recv(rest) + if not chunk: + raise errors.InterfaceError(errno=2013) + packet += chunk + rest = packet_totlen - len(packet) + + return packet + except socket.timeout, err: + raise errors.InterfaceError(errno=2013) + except socket.error, err: + try: + msg = err.errno + if msg is None: + msg = str(err) + except AttributeError: + msg = str(err) + raise errors.InterfaceError(errno=2055, + values=(self.get_address(), msg)) + recv = recv_plain + + def _split_zipped_payload(self, packet_bunch): + """Split compressed payload""" + while packet_bunch: + payload_length = struct.unpack(", like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USAs +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Implementing the MySQL Client/Server protocol """ -import re import struct +from decimal import Decimal try: from hashlib import sha1 except ImportError: from sha import new as sha1 -from datetime import datetime -from time import strptime -from decimal import Decimal - -from constants import * -import errors -import utils - -def packet_is_error(idx=0,label=None): - def deco(func): - def call(*args, **kwargs): - try: - if label: - pktdata = kwargs[label] - else: - pktdata = args[idx] - except Exception, e: - raise errors.InterfaceError( - "Can't check for Error packet; %s" % e) - - try: - if pktdata and pktdata[4] == '\xff': - errors.raise_error(pktdata) - except errors.Error: - raise - except: - pass - return func(*args, **kwargs) - return call - return deco - -def packet_is_ok(idx=0,label=None): - def deco(func): - def call(*args, **kwargs): - try: - if label: - pktdata = kwargs[label] - else: - pktdata = args[idx] - except: - raise errors.InterfaceError("Can't check for OK packet.") - - try: - if pktdata and pktdata[4] == '\x00': - return func(*args, **kwargs) - else: - raise - except: - raise errors.InterfaceError("Expected OK packet") - return call - return deco - -def packet_is_eof(idx=0,label=None): - def deco(func): - def call(*args, **kwargs): - try: - if label: - pktdata = kwargs[label] - else: - pktdata = args[idx] - except: - raise errors.InterfaceError("Can't check for EOF packet.") - if pktdata[4] == '\xfe' and len(pktdata) == 9: - return func(*args, **kwargs) - else: - raise errors.InterfaceError("Expected EOF packet") - - return call - return deco - -def set_pktnr(idx=1,label=None): - def deco(func): - def call(*args, **kwargs): - try: - if label: - pktdata = kwargs[label] - else: - pktdata = args[idx] - except: - raise errors.InterfaceError("Can't check for EOF packet.") - - try: - args[0].pktnr = ord(pktdata[3]) - pktdata = pktdata[4:] - if label: - kwargs[label] = pktdata - else: - args = list(args) - args[idx] = pktdata - except: - raise errors.InterfaceError("Failed getting Packet Number.") - return func(*args,**kwargs) - return call - return deco - -def reset_pktnr(func): - def deco(*args, **kwargs): - try: - args[0].pktnr = -1 - except: - pass - return func(*args, **kwargs) - - return deco - -class MySQLProtocolBase(object): - pass - -class MySQLProtocol(MySQLProtocolBase): +from mysql.connector.constants import (FieldFlag, ServerCmd) +from mysql.connector import (errors, utils) - def __init__(self, conn): - self.client_flags = 0 - self.conn = conn - self.pktnr = -1 - - @property - def next_pktnr(self): - self.pktnr = self.pktnr + 1 - return self.pktnr - +class MySQLProtocol(object): def _scramble_password(self, passwd, seed): """Scramble a password ready to send to MySQL""" hash4 = None - try: + try: hash1 = sha1(passwd).digest() hash2 = sha1(hash1).digest() # Password as found in mysql.user() hash3 = sha1(seed + hash2).digest() - xored = [ utils.intread(h1) ^ utils.intread(h3) + xored = [ utils.intread(h1) ^ utils.intread(h3) for (h1,h3) in zip(hash1, hash3) ] hash4 = struct.pack('20B', *xored) - except Exception, e: - raise errors.InterfaceError('Failed scrambling password; %s' % e) - + except Exception, err: + raise errors.InterfaceError( + 'Failed scrambling password; %s' % err) + return hash4 def _prepare_auth(self, usr, pwd, db, flags, seed): - + """Prepare elements of the authentication packet""" + if usr is not None and len(usr) > 0: + if isinstance(usr, unicode): + usr = usr.encode('utf8') _username = usr + '\x00' else: _username = '\x00' - + if pwd is not None and len(pwd) > 0: + if isinstance(pwd, unicode): + pwd = pwd.encode('utf8') _password = utils.int1store(20) +\ self._scramble_password(pwd,seed) else: _password = '\x00' - + if db is not None and len(db): - _database = db + '\x00' + _database = db.encode('utf8') + '\x00' else: _database = '\x00' - + return (_username, _password, _database) - def _pkt_make_auth(self, username=None, password=None, database=None, - seed=None, charset=33, client_flags=0, max_allowed_packet=None): + def make_auth(self, seed, username=None, password=None, database=None, + charset=33, client_flags=0, + max_allowed_packet=1073741824): """Make a MySQL Authentication packet""" - try: - seed = seed or self.scramble - except: - raise errors.ProgrammingError('Seed missing') - - if max_allowed_packet is None: - max_allowed_packet = 1073741824 # 1Gb - - (_username, _password, _database) = self._prepare_auth( - username, password, database, client_flags, seed) - data = utils.int4store(client_flags) +\ - utils.int4store(max_allowed_packet) +\ - utils.int1store(charset) +\ - '\x00'*23 +\ - _username +\ - _password +\ - _database - return data - - def _pkt_make_auth_ssl(self, username=None, password=None, database=None, - seed=None, charset=33, client_flags=0, max_allowed_packet=None): - try: - seed = seed or self.scramble - except: + if not seed: raise errors.ProgrammingError('Seed missing') - - if max_allowed_packet is None: - max_allowed_packet = 1073741824 # 1Gb - (_username, _password, _database) = self._prepare_auth( - username, password, database, client_flags, seed) - data = utils.int4store(client_flags) +\ - utils.int4store(max_allowed_packet) +\ - utils.int1store(charset) +\ - '\x00'*23 - return data - - def _pkt_make_command(self, command, argument=None): + auth = self._prepare_auth(username, password, database, + client_flags, seed) + return utils.int4store(client_flags) +\ + utils.int4store(max_allowed_packet) +\ + utils.int1store(charset) +\ + '\x00' * 23 + auth[0] + auth[1] + auth[2] + + def make_auth_ssl(self, charset=33, client_flags=0, + max_allowed_packet=1073741824): + """Make a SSL authentication packet""" + return utils.int4store(client_flags) +\ + utils.int4store(max_allowed_packet) +\ + utils.int1store(charset) +\ + '\x00' * 23 + + def make_command(self, command, argument=None): """Make a MySQL packet containing a command""" data = utils.int1store(command) if argument is not None: data += str(argument) return data - - def _pkt_make_changeuser(self, username=None, password=None, - database=None, charset=8, seed=None): + + def make_change_user(self, seed, username=None, password=None, + database=None, charset=33, client_flags=0): """Make a MySQL packet with the Change User command""" - try: - seed = seed or self.scramble - except: + if not seed: raise errors.ProgrammingError('Seed missing') - - (_username, _password, _database) = self._prepare_auth( - username, password, database, self.client_flags, seed) + + auth = self._prepare_auth(username, password, database, + client_flags, seed) data = utils.int1store(ServerCmd.CHANGE_USER) +\ - _username +\ - _password +\ - _database +\ - utils.int2store(charset) + auth[0] + auth[1] + auth[2] + utils.int2store(charset) return data - - @set_pktnr(1) - def _pkt_parse_handshake(self, buf): + + def parse_handshake(self, packet): """Parse a MySQL Handshake-packet""" res = {} - (buf,res['protocol']) = utils.read_int(buf,1) - (buf,res['server_version_original']) = utils.read_string(buf,end='\x00') - (buf,res['server_threadid']) = utils.read_int(buf,4) - (buf,res['scramble']) = utils.read_bytes(buf, 8) - buf = buf[1:] # Filler 1 * \x00 - (buf,res['capabilities']) = utils.read_int(buf,2) - (buf,res['charset']) = utils.read_int(buf,1) - (buf,res['server_status']) = utils.read_int(buf,2) - buf = buf[13:] # Filler 13 * \x00 - (buf,scramble_next) = utils.read_bytes(buf,12) + (packet, res['protocol']) = utils.read_int(packet[4:], 1) + (packet, res['server_version_original']) = utils.read_string( + packet, end='\x00') + (packet, res['server_threadid']) = utils.read_int(packet, 4) + (packet, res['scramble']) = utils.read_bytes(packet, 8) + packet = packet[1:] # Filler 1 * \x00 + (packet, res['capabilities']) = utils.read_int(packet, 2) + (packet, res['charset']) = utils.read_int(packet, 1) + (packet, res['server_status']) = utils.read_int(packet, 2) + packet = packet[13:] # Filler 13 * \x00 + (packet, scramble_next) = utils.read_bytes(packet, 12) res['scramble'] += scramble_next return res - - @set_pktnr(1) - def _pkt_parse_ok(self, buf): + + def parse_ok(self, packet): """Parse a MySQL OK-packet""" + if not packet[4] == '\x00': + raise errors.InterfaceError("Failed parsing OK packet.") + ok = {} - (buf,ok['field_count']) = utils.read_int(buf,1) - (buf,ok['affected_rows']) = utils.read_lc_int(buf) - (buf,ok['insert_id']) = utils.read_lc_int(buf) - (buf,ok['server_status']) = utils.read_int(buf,2) - (buf,ok['warning_count']) = utils.read_int(buf,2) - if buf: - (buf,ok['info_msg']) = utils.read_lc_string(buf) + try: + (packet, ok['field_count']) = utils.read_int(packet[4:], 1) + (packet, ok['affected_rows']) = utils.read_lc_int(packet) + (packet, ok['insert_id']) = utils.read_lc_int(packet) + (packet, ok['server_status']) = utils.read_int(packet, 2) + (packet, ok['warning_count']) = utils.read_int(packet, 2) + if packet: + (packet, ok['info_msg']) = utils.read_lc_string(packet) + except ValueError: + raise errors.InterfaceError("Failed parsing OK packet.") return ok - - @set_pktnr(1) - def _pkt_parse_field(self, buf): - """Parse a MySQL Field-packet""" - field = {} - (buf,field['catalog']) = utils.read_lc_string(buf) - (buf,field['db']) = utils.read_lc_string(buf) - (buf,field['table']) = utils.read_lc_string(buf) - (buf,field['org_table']) = utils.read_lc_string(buf) - (buf,field['name']) = utils.read_lc_string(buf) - (buf,field['org_name']) = utils.read_lc_string(buf) - buf = buf[1:] # filler 1 * \x00 - (buf,field['charset']) = utils.read_int(buf, 2) - (buf,field['length']) = utils.read_int(buf, 4) - (buf,field['type']) = utils.read_int(buf, 1) - (buf,field['flags']) = utils.read_int(buf, 2) - (buf,field['decimal']) = utils.read_int(buf, 1) - buf = buf[2:] # filler 2 * \x00 - - res = ( - field['name'], - field['type'], + + def parse_column_count(self, packet): + """Parse a MySQL packet with the number of columns in result set""" + return utils.read_lc_int(packet[4:])[1] + + def parse_column(self, packet): + """Parse a MySQL column-packet""" + column = {} + (packet, column['catalog']) = utils.read_lc_string(packet[4:]) + (packet, column['db']) = utils.read_lc_string(packet) + (packet, column['table']) = utils.read_lc_string(packet) + (packet, column['org_table']) = utils.read_lc_string(packet) + (packet, column['name']) = utils.read_lc_string(packet) + (packet, column['org_name']) = utils.read_lc_string(packet) + packet = packet[1:] # filler 1 * \x00 + (packet, column['charset']) = utils.read_int(packet, 2) + (packet, column['length']) = utils.read_int(packet, 4) + (packet, column['type']) = utils.read_int(packet, 1) + (packet, column['flags']) = utils.read_int(packet, 2) + (packet, column['decimal']) = utils.read_int(packet, 1) + packet = packet[2:] # filler 2 * \x00 + + return ( + column['name'], + column['type'], None, # display_size None, # internal_size None, # precision None, # scale - ~field['flags'] & FieldFlag.NOT_NULL, # null_ok - field['flags'], # MySQL specific + ~column['flags'] & FieldFlag.NOT_NULL, # null_ok + column['flags'], # MySQL specific ) - return res - - @set_pktnr(1) - def _pkt_parse_eof(self, buf): + + def parse_eof(self, packet): """Parse a MySQL EOF-packet""" - res = {} - buf = buf[1:] # disregard the first checking byte - (buf, res['warning_count']) = utils.read_int(buf, 2) - (buf, res['status_flag']) = utils.read_int(buf, 2) - return res - - def do_handshake(self): - """Get the handshake from the MySQL server""" - try: - self.conn.open_connection() - buf = self.conn.recv() - self.handle_handshake(buf) - except: - raise - - def do_auth(self, username=None, password=None, database=None, - client_flags=0, charset=33): - """Authenticate with the MySQL server - """ - if client_flags & ClientFlag.SSL: - pkt = self._pkt_make_auth_ssl(username=username, - password=password, database=database, charset=charset, - client_flags=client_flags) - self.conn.send(pkt,self.next_pktnr) - self.conn.switch_to_ssl() - - pkt = self._pkt_make_auth(username=username, password=password, - database=database, charset=charset, - client_flags=client_flags) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - if buf[4] == '\xfe': - raise errors.NotSupportedError( - "Authentication with old (insecure) passwords "\ - "is not supported: "\ - "http://dev.mysql.com/doc/refman/5.1/en/password-hashing.html") - - try: - if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database: - self.cmd_init_db(database) - except: - raise - - return True - - def handle_handshake(self, buf): - """Check and handle the MySQL server's handshake - - Check whether the buffer is a valid handshake. If it is, we set some - member variables for later usage. The handshake packet is returned for later - usuage, e.g. authentication. - """ - try: - res = self._pkt_parse_handshake(buf) - for k,v in res.items(): - self.__dict__[k] = v - - regex_ver = re.compile("^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") - m = regex_ver.match(self.server_version_original) - if not m: - raise errors.InterfaceError("Failed parsing MySQL version number.") - self.server_version = tuple([ int(v) for v in m.groups()[0:3]]) - except errors.Error: - raise - except Exception, e: - raise errors.InterfaceError('Failed handling handshake; %s' % e) - - @packet_is_ok(1) - def _handle_ok(self, buf): - try: - return self._pkt_parse_ok(buf) - except: - raise errors.InterfaceError("Failed parsing OK packet.") - - @packet_is_eof(1) - def _handle_eof(self, buf): - try: - return self._pkt_parse_eof(buf) - except: + if not (packet[4] == '\xfe' and len(packet) <= 9): raise errors.InterfaceError("Failed parsing EOF packet.") - - @packet_is_error(1) - @set_pktnr(1) - def _handle_resultset(self, buf): - (buf,nrflds) = utils.read_lc_int(buf) - if nrflds == 0: - raise errors.InterfaceError('Empty result set.') - - fields = [] - for i in xrange(0,nrflds): - buf = self.conn.recv() - fields.append(self._pkt_parse_field(buf)) - - buf = self.conn.recv() - eof = self._handle_eof(buf) - return (nrflds, fields, eof) - - - def get_rows(self, cnt=None): - """Get all rows - - Returns a tuple with 2 elements: a list with all rows and - the EOF packet. - """ - rows = [] - eof = None - rowdata = None - i = 0 - while True: - if eof is not None: - break - if i == cnt: - break - buf = self.conn.recv() - if buf[0:3] == '\xff\xff\xff': - data = buf[4:] - buf = self.conn.recv() - while buf[0:3] == '\xff\xff\xff': - data += buf[4:] - buf = self.conn.recv() - if buf[4] == '\xfe': - eof = self._handle_eof(buf) - else: - data += buf[4:] - rowdata = utils.read_lc_string_list(data) - elif buf[4] == '\xfe': - eof = self._handle_eof(buf) - rowdata = None - else: - eof = None - rowdata = utils.read_lc_string_list(buf[4:]) - if eof is None and rowdata is not None: - rows.append(rowdata) - i += 1 - return (rows,eof) - - def get_row(self): - (rows,eof) = self.get_rows(cnt=1) - if len(rows): - return (rows[0],eof) - return (None,eof) - - def handle_cmd_result(self, buf): - if buf[4] == '\x00': - return self._handle_ok(buf) - else: - return self._handle_resultset(buf)[0:2] - - @reset_pktnr - def cmd_query(self, query): - """Sends a query to the MySQL server - - Returns a tuple, when the query returns a result. The tuple - consist number of fields and a list containing their descriptions. - If the query doesn't return a result set, a dictionary with - information contained in an OKResult packet will be returned. - """ - nrflds = 0 - fields = None - try: - pkt = self._pkt_make_command(ServerCmd.QUERY,query) - self.conn.send(pkt,self.next_pktnr) - return self.handle_cmd_result(self.conn.recv()) - except: - raise - - @reset_pktnr - def cmd_refresh(self, opts): - """Send the Refresh command to the MySQL server - - The argument should be a bitwise value using contants.RefreshOption. - Usage example: - - RefreshOption = mysql.connector.RefreshOption - refresh = RefreshOption.LOG | RefreshOption.THREADS - db.protocol().cmd_refresh(refresh) - - Returns a dict() with OK-packet information. - """ - pkt = self._pkt_make_command(ServerCmd.REFRESH, opts) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_ok(buf) - - @reset_pktnr - def cmd_quit(self): - """Closes the current connection with the server - - Returns the packet that was send. - """ - pkt = self._pkt_make_command(ServerCmd.QUIT) - self.conn.send(pkt,self.next_pktnr) - return pkt - - @reset_pktnr - def cmd_init_db(self, database): - """Change the current database - - Change the current (default) database. - - Returns a dict() with OK-packet information. - """ - pkt = self._pkt_make_command(ServerCmd.INIT_DB, database) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_ok(buf) - - @reset_pktnr - def cmd_shutdown(self): - """Shuts down the MySQL Server - - Careful with this command if you have SUPER privileges! (Which your - scripts probably don't need!) - Returns a dict() with OK-packet information. - """ - pkt = self._pkt_make_command(ServerCmd.SHUTDOWN) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_eof(buf) - - @reset_pktnr - def cmd_statistics(self): - """Sends statistics command to the MySQL Server + res = {} + packet = packet[5:] # disregard the first checking byte + (packet, res['warning_count']) = utils.read_int(packet, 2) + (packet, res['status_flag']) = utils.read_int(packet, 2) + return res - Returns a dictionary with various statistical information. - """ - pkt = self._pkt_make_command(ServerCmd.STATISTICS) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - buf = buf[4:] + def parse_statistics(self, packet): + """Parse the statistics packet""" errmsg = "Failed getting COM_STATISTICS information" res = {} # Information is separated by 2 spaces - pairs = buf.split('\x20\x20') + pairs = packet[4:].split('\x20\x20') for pair in pairs: try: - (lbl,val) = [ v.strip() for v in pair.split(':',2) ] + (lbl, val) = [ v.strip() for v in pair.split(':', 2) ] except: raise errors.InterfaceError(errmsg) - + # It's either an integer or a decimal try: res[lbl] = long(val) @@ -566,65 +219,42 @@ def cmd_statistics(self): "%s (%s:%s)." % (errmsg, lbl, val)) return res - @reset_pktnr - def cmd_process_info(self): - """Gets the process list from the MySQL Server - - (Unsupported) - """ - raise errors.NotSupportedError( - "Not implemented. Use a cursor to get processlist information.") - - @reset_pktnr - def cmd_process_kill(self, mypid): - """Kills a MySQL process using it's ID - - Returns a dict() with OK-packet information. - """ - pkt = self._pkt_make_command(ServerCmd.PROCESS_KILL, - utils.int4store(mypid)) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_ok(buf) - - @reset_pktnr - def cmd_debug(self): - """Send DEBUG command to the MySQL Server - - Needs SUPER privileges. The output will go to the MySQL server error - log. - - Returns a dict() with EOF-packet information. - """ - pkt = self._pkt_make_command(ServerCmd.DEBUG) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_eof(buf) + def read_text_result(self, sock, count=1): + """Read MySQL text result - @reset_pktnr - def cmd_ping(self): - """Ping the MySQL server to check if the connection is still alive - - Raises errors.Error or an error derived from it when it fails - to ping the MySQL server. - - Returns a dict() with OK-packet information. - """ - pkt = self._pkt_make_command(ServerCmd.PING) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_ok(buf) + Reads all or given number of rows from the socket. - @reset_pktnr - def cmd_change_user(self, username='', password='', database=''): - """Change the user and optionally the current database - - Returns a dict() with OK-packet information. + Returns a tuple with 2 elements: a list with all rows and + the EOF packet. """ - _charset = self.charset or 33 - - pkt = self._pkt_make_changeuser(username=username, password=password, - database=database, charset=_charset, seed=self.scramble) - self.conn.send(pkt,self.next_pktnr) - buf = self.conn.recv() - return self._handle_ok(buf) + rows = [] + eof = None + rowdata = None + i = 0 + while True: + if eof is not None: + break + if i == count: + break + packet = sock.recv() + if packet[0:3] == '\xff\xff\xff': + data = packet[4:] + packet = sock.recv() + while packet[0:3] == '\xff\xff\xff': + data += packet[4:] + packet = sock.recv() + if packet[4] == '\xfe': + eof = self.parse_eof(packet) + else: + data += packet[4:] + rowdata = utils.read_lc_string_list(data) + elif packet[4] == '\xfe': + eof = self.parse_eof(packet) + rowdata = None + else: + eof = None + rowdata = utils.read_lc_string_list(packet[4:]) + if eof is None and rowdata is not None: + rows.append(rowdata) + i += 1 + return (rows, eof) diff --git a/lib/mysql/connector/utils.py b/lib/mysql/connector/utils.py index 2cf0b18..d15de3b 100644 --- a/lib/mysql/connector/utils.py +++ b/lib/mysql/connector/utils.py @@ -1,32 +1,31 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Utilities """ __MYSQL_DEBUG__ = False -import constants import struct def intread(b): @@ -285,30 +284,3 @@ def read_lc_int(buf): def _digest_buffer(buf): return ''.join([ "\\x%02x" % ord(c) for c in buf ]) -def digest_auth_packet(buf): - d = [] - try: - d = [ - ('Cabilities (ClientFlags)', - constants.ClientFlag.get_bit_info( - struct.unpack(", like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""MySQL Connector/Python version information + +The file version.py gets installed and is available after installation +as mysql.connector.version. +""" + +VERSION = (1, 0, 12, None, 0) +LICENSE = 'GPLv2 with FOSS License Exception' diff --git a/lib/tests/__init__.py b/lib/tests/__init__.py index edddff8..6004ab4 100644 --- a/lib/tests/__init__.py +++ b/lib/tests/__init__.py @@ -1,34 +1,59 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests """ +import os import sys +import errno +import socket +import datetime import logging import inspect +import platform import unittest +SSL_AVAILABLE = True +try: + import ssl +except ImportError: + SSL_AVAILABLE = False + +# Note that IPv6 support for Python is checked here, but it can be disabled +# when the bind_address of MySQL was not set to '::1'. +IPV6_AVAILABLE = socket.has_ipv6 + +if os.name == 'nt': + WINDOWS_VERSION = platform.win32_ver()[1] + WINDOWS_VERSION_INFO = [0] * 2 + for i, value in enumerate(WINDOWS_VERSION.split('.')[0:2]): + WINDOWS_VERSION_INFO[i] = int(value) + WINDOWS_VERSION_INFO = tuple(WINDOWS_VERSION_INFO) +else: + WINDOWS_VERSION = None + WINDOWS_VERSION_INFO = () + MYSQL_CONFIG = { 'host' : '127.0.0.1', 'port' : 33770, @@ -36,8 +61,19 @@ 'user' : 'root', 'password' : '', 'database' : 'myconnpy', + 'connection_timeout': 10, } +# Following dictionary holds messages which were added by test cases +# but only logged at the end. +MESSAGES = { + 'WARNINGS': [], + 'INFO': [], +} + +MYSQL_VERSION = None +SSL_DIR = os.path.join("support", "ssl") + LOGGER_NAME = "myconnpy_tests" __all__ = [ @@ -45,6 +81,8 @@ 'get_test_names','printmsg', 'active_testcases', 'LOGGER_NAME', + 'DummySocket', + 'SSL_DIR', ] active_testcases = [ @@ -53,17 +91,91 @@ 'tests.test_constants', 'tests.test_conversion', 'tests.test_connection', + 'tests.test_network', 'tests.test_cursor', 'tests.test_pep249', 'tests.test_bugs', 'tests.test_examples', 'tests.test_mysql_datatypes', + 'tests.test_errors', + 'tests.test_errorcode', + 'tests.test_locales', ] +if sys.version_info >= (2, 6): + active_testcases.append('tests.test_bugs_future') + +class UTCTimeZone(datetime.tzinfo): + def utcoffset(self,dt): + return datetime.timedelta(0) + def dst(self,dt): + return datetime.timedelta(0) + +class TestTimeZone(datetime.tzinfo): + def __init__(self, hours=0): + self._offset = datetime.timedelta(hours=hours) + def utcoffset(self,dt): + return self._offset + def dst(self,dt): + return datetime.timedelta(0) + +class DummySocket(object): + """Dummy socket class + + This class helps to test socket connection without actually making any + network activity. It is a proxy class using socket.socket. + """ + def __init__(self, *args): + self._socket = socket.socket(*args) + self._server_replies = '' + self._client_sends = [] + self._raise_socket_error = 0 + + def __getattr__(self, attr): + return getattr(self._socket, attr) + + def raise_socket_error(self, err=errno.EPERM): + self._raise_socket_error = err + + def recv(self, bufsize=4096, flags=0): + if self._raise_socket_error: + raise socket.error(self._raise_socket_error) + res = self._server_replies[0:bufsize] + self._server_replies = self._server_replies[bufsize:] + return res + + def send(self, string, flags=0): + if self._raise_socket_error: + raise socket.error(self._raise_socket_error) + self._client_sends.append(string) + return len(string) + + def sendall(self, string, flags=0): + self._client_sends.append(string) + return None + + def add_packet(self, packet): + self._server_replies += packet + + def add_packets(self, packets): + for packet in packets: + self._server_replies += packet + + def reset(self): + self._raise_socket_error = 0 + self._server_replies = '' + self._client_sends = [] + + def get_address(self): + return 'dummy' + class MySQLConnectorTests(unittest.TestCase): def getMySQLConfig(self): return MYSQL_CONFIG.copy() + + def getFakeHostname(self): + return ''.join([ "%02x" % ord(c) for c in os.urandom(4)]) def checkAttr(self, obj, attrname, default): cls_name = obj.__class__.__name__ @@ -82,6 +194,20 @@ def checkMethod(self, obj, method): "%s object defines %s, but is not a method" % ( cls_name, method)) + + def checkArguments(self, function, supported_arguments): + argspec = inspect.getargspec(function) + function_arguments = dict(zip(argspec[0][1:],argspec[3])) + for argument,default in function_arguments.items(): + try: + self.assertEqual(supported_arguments[argument], + default, msg="Argument '%s' has wrong default" % argument) + except KeyError: + self.fail("Found unsupported or new argument '%s'" % argument) + for argument,default in supported_arguments.items(): + if not argument in function_arguments: + self.fail("Supported argument '%s' fails" % argument) + def haveEngine(self, db, engine): """Check if the given storage engine is supported""" have = False @@ -104,7 +230,7 @@ def haveEngine(self, db, engine): c.close() except: pass - return have + return have def cmpResult(self, res1, res2): """Compare results (list of tuples) comming from MySQL diff --git a/lib/tests/mysqld.py b/lib/tests/mysqld.py index 1ef5cc6..401c83f 100644 --- a/lib/tests/mysqld.py +++ b/lib/tests/mysqld.py @@ -1,5 +1,34 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + import sys import os +import signal +try: + import ctypes +except: + pass +import re from shutil import rmtree import tempfile import subprocess @@ -10,6 +39,13 @@ logger = logging.getLogger(tests.LOGGER_NAME) +if os.name == 'nt': + EXEC_MYSQLD = 'mysqld.exe' + EXEC_MYSQL = 'mysql.exe' +else: + EXEC_MYSQLD = 'mysqld' + EXEC_MYSQL = 'mysql' + class MySQLInstallError(Exception): def __init__(self, m): @@ -46,21 +82,21 @@ def _init_mysql_install(self): Raises MySQLBootstrapError when something fails. """ - locs = ('libexec/','bin/','sbin/') + locs = ('libexec', 'bin', 'sbin') for loc in locs: d = os.path.join(self._basedir,loc) - if os.access(os.path.join(d,'mysqld'),0): + if os.access(os.path.join(d, EXEC_MYSQLD), 0): self._sbindir = d - if os.access(os.path.join(d,'mysql'),0): + if os.access(os.path.join(d, EXEC_MYSQL), 0): self._bindir = d if self._bindir is None or self._sbindir is None: raise MySQLBootstrapError("MySQL binaries not found under %s" %\ self._basedir) - locs = ('share/','share/mysql') + locs = ('share', 'share/mysql') for loc in locs: - d = os.path.join(self._basedir,loc) + d = os.path.normpath(os.path.join(self._basedir,loc)) if os.access(os.path.join(d,'mysql_system_tables.sql'),0): self._sharedir = d break @@ -87,7 +123,7 @@ def _create_directories(self): Create the directories needed for bootstrapping a MySQL installation, i.e. 'mysql' directory. - The 'test' database is deliberatly not created. + The 'test' database is deliberately not created. Raises MySQLBootstrapError when something fails. """ @@ -110,12 +146,13 @@ def _get_bootstrap_cmd(self): Returns a list (used with subprocess.Popen) """ cmd = [ - os.path.join(self._sbindir,'mysqld'), + os.path.join(self._sbindir, EXEC_MYSQLD), + '--no-defaults', '--bootstrap', '--basedir=%s' % self._basedir, '--datadir=%s' % self._datadir, '--log-warnings=0', - '--loose-skip-innodb', + #'--loose-skip-innodb', '--loose-skip-ndbcluster', '--max_allowed_packet=8M', '--default-storage-engine=myisam', @@ -127,7 +164,7 @@ def _get_bootstrap_cmd(self): def bootstrap(self): """Bootstrap a MySQL installation - Bootstarp a MySQL installation using the mysqld executable + Bootstrap a MySQL installation using the mysqld executable and the --bootstrap option. Arguments are defined by reading the defaults file and options set in the _get_bootstrap_cmd() method. @@ -148,14 +185,16 @@ def bootstrap(self): try: cmd = self._get_bootstrap_cmd() sql = list() - sql.append("USE mysql") + sql.append("USE mysql;") for f in script_files: logger.debug("Reading SQL from '%s'" % f) fp = open(os.path.join(self._sharedir,f),'r') - sql += fp.readlines() + sql += [ line.strip() for line in fp.readlines() ] fp.close() sql += self.extra_sql - prc = subprocess.Popen(cmd, stdin=subprocess.PIPE) + devnull = open(os.devnull, 'w') + prc = subprocess.Popen(cmd, stdin=subprocess.PIPE, + stderr=devnull, stdout=devnull) prc.communicate('\n'.join(sql)) except Exception, e: raise MySQLBootstrapError(e) @@ -165,31 +204,76 @@ class MySQLd(MySQLInstallBase): def __init__(self, basedir, optionFile): self._process = None super(MySQLd, self).__init__(basedir, optionFile) + self._version = self._get_version() def _get_cmd(self): cmd = [ - os.path.join(self._sbindir,'mysqld'), + os.path.join(self._sbindir, EXEC_MYSQLD), "--defaults-file=%s" % (self._optionFile) ] + + if os.name == 'nt': + cmd.append('--standalone') + return cmd + + def _get_version(self): + """Get the MySQL server version + + This method executes mysqld with the --version argument. It parses + the output looking for the version number and returns it as a + tuple with integer values: (major,minor,patch) + + Returns a tuple. + """ + cmd = [ + os.path.join(self._sbindir, EXEC_MYSQLD), + '--version' + ] + + prc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + verstr = str(prc.communicate()[0]) + matches = re.match(r'.*Ver (\d)\.(\d).(\d{1,2}).*', verstr) + if matches: + return tuple([int(v) for v in matches.groups()]) + else: + raise MySQLdError('Failed reading version from mysqld --version') + + @property + def version(self): + """Returns the MySQL server version + + Returns a tuple. + """ + return self._version def start(self): try: cmd = self._get_cmd() - self._process = subprocess.Popen(cmd) + devnull = open(os.devnull, 'w') + self._process = subprocess.Popen(cmd, stdout=devnull, + stderr=devnull) except Exception, e: raise MySQLdError(e) def stop(self): try: - self._process.terminate() + try: + self._process.terminate() + except AttributeError: + # Python 2.5 and earlier + if os.name == 'nt': + ctypes.windll.kernel32.TerminateProcess( + int(self._process._handle), -1) + else: + os.kill(self._process.pid, signal.SIGKILL) except Exception, e: raise MySQLdError(e) class MySQLInit(object): def __init__(self, basedir, topdir, cnf, option_file, bind_address, port, - unix_socket): + unix_socket, ssldir): self._cnf = cnf self._option_file = option_file self._unix_socket = unix_socket @@ -197,10 +281,35 @@ def __init__(self, basedir, topdir, cnf, option_file, bind_address, port, self._port = port self._topdir = topdir self._basedir = basedir + self._ssldir = ssldir self._install = None self._server = None self._debug = False + + self._server = MySQLd(self._basedir, self._option_file) + + @property + def version(self): + """Returns the MySQL server version + + Returns a tuple. + """ + return self._server.version + + def _slashes(self, path): + """Convert forward slashes with backslashes + + This method replaces forward slashes with backslashes. This + is necessary using Microsoft Windows for location of files in + the option files. + + Returns a string. + """ + if os.name == 'nt': + nmpath = os.path.normpath(path) + return path.replace('\\', '\\\\') + return path def bootstrap(self): """Bootstrap a MySQL server""" @@ -218,21 +327,24 @@ def bootstrap(self): def start(self): """Start a MySQL server""" + options = { + 'mysqld_basedir': self._slashes(self._basedir), + 'mysqld_datadir': self._slashes(self._install._datadir), + 'mysqld_tmpdir': self._slashes(self._install._tmpdir), + 'mysqld_bind_address': self._bind_address, + 'mysqld_port': self._port, + 'mysqld_socket': self._slashes(self._unix_socket), + 'ssl_dir': self._slashes(self._ssldir), + } try: fp = open(self._option_file,'w') - fp.write(self._cnf % dict( - mysqld_basedir=self._basedir, - mysqld_datadir=self._install._datadir, - mysqld_bind_address=self._bind_address, - mysqld_port=self._port, - mysqld_socket=self._unix_socket, - )) + fp.write(self._cnf % options) fp.close() - self._server = MySQLd(self._basedir,self._option_file) + self._server = MySQLd(self._basedir, self._option_file) self._server.start() time.sleep(3) - except MySQLdError, e: - logger.error("Failed starting MySQL server: %s" % e) + except MySQLdError, err: + logger.error("Failed starting MySQL server: %s" % err) if self._debug is True: raise sys.exit(1) @@ -256,4 +368,4 @@ def remove(self): raise else: logger.info("Removed %s" % self._topdir) - + diff --git a/lib/tests/test_bugs.py b/lib/tests/test_bugs.py index 8898d34..2422df4 100644 --- a/lib/tests/test_bugs.py +++ b/lib/tests/test_bugs.py @@ -1,36 +1,43 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2011, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for bugs """ -import sys -import struct import os +import sys +import gc +import tempfile +import logging +from datetime import datetime import time +from threading import Thread import tests -from mysql.connector import connection, cursor, conversion, protocol, utils, errors, constants +from mysql.connector import (connection, cursor, conversion, protocol, + utils, errors, constants) + +logger = logging.getLogger(tests.LOGGER_NAME) class Bug328998Tests(tests.MySQLConnectorTests): @@ -39,7 +46,7 @@ def test_set_connection_timetout(self): config['connection_timeout'] = 5 self.db = connection.MySQLConnection(**config) self.assertEqual(config['connection_timeout'], - self.db.protocol.conn.connection_timeout) + self.db._socket._connection_timeout) if self.db: self.db.disconnect() @@ -81,13 +88,13 @@ def test_execute_return(self): tbl = "buglp44130" c.execute("DROP TABLE IF EXISTS %s" % tbl) c.execute("CREATE TABLE %s (id INT)" % tbl) - res = c.execute("INSERT INTO %s VALUES (%%s),(%%s)" % tbl, (1,2,)) - self.assertEqual(2,res) + c.execute("INSERT INTO %s VALUES (%%s),(%%s)" % tbl, (1,2,)) + self.assertEqual(2, c.rowcount) stmt = "INSERT INTO %s VALUES (%%s)" % tbl res = c.executemany(stmt,[(3,),(4,),(5,),(6,),(7,),(8,)]) - self.assertEqual(6,res) + self.assertEqual(6, c.rowcount) res = c.execute("UPDATE %s SET id = id + %%s" % tbl , (10,)) - self.assertEqual(8,res) + self.assertEqual(8, c.rowcount) c.close() db.close() @@ -116,8 +123,12 @@ def test_pyformat(self): c.execute("SELECT %(name)s,%(year)s", data) self.assertEqual((u'Geert',1977L),c.fetchone()) - data = [{'name': 'Geert','year':1977},{'name':'Marta','year':1980}] - self.assertEqual(2,c.executemany("SELECT %(name)s,%(year)s", data)) + data = [ + {'name': 'Geert', 'year': 1977}, + {'name': 'Marta', 'year': 1980} + ] + c.executemany("SELECT %(name)s,%(year)s", data) + self.assertEqual(2, c.rowcount) c.close() db.close() @@ -140,11 +151,15 @@ class Bug380528(tests.MySQLConnectorTests): def test_old_password(self): """lp:380528: we do not support old passwords.""" + if tests.MYSQL_VERSION >= (5, 6, 6): + # Test not valid for MySQL 5.6.6 and later. + return + config = self.getMySQLConfig() db = connection.MySQLConnection(**config) c = db.cursor() - if config['unix_socket']: + if config['unix_socket'] and os.name != 'nt': user = "'myconnpy'@'localhost'" else: user = "'myconnpy'@'%s'" % (config['host']) @@ -162,7 +177,8 @@ def test_old_password(self): test_config['user'] = 'myconnpy' test_config['password'] = 'fubar' - self.assertRaises(errors.NotSupportedError,connection.MySQLConnection,**test_config) + self.assertRaises(errors.NotSupportedError, + connection.MySQLConnection,**test_config) db = connection.MySQLConnection(**config) c = db.cursor() @@ -216,7 +232,7 @@ def test_charset(self): c.execute(stmt, varlst) res1 = c.fetchall() - db.set_charset('latin2') + db.set_charset_collation('latin2') c.execute(stmt, varlst) res2 = c.fetchall() @@ -234,7 +250,7 @@ def test_use_unicode(self): config['use_unicode'] = False db = connection.MySQLConnection(**config) - self.assertEqual(False, db.use_unicode) + self.assertEqual(False, db._use_unicode) db.close() def test_charset(self): @@ -297,34 +313,27 @@ def tearDown(self): def test_default(self): """lp:501290 Check default client flags""" - self.assertEqual(self.db.client_flags, + self.assertEqual(self.db._client_flags, constants.ClientFlag.get_default()) - def test_set_client_flag(self): - """lp:501290 Set one flag, check if set""" + def test_set_unset(self): + """lp:501290 Set/unset one flag, check if set/unset""" + orig = self.db._client_flags + exp = constants.ClientFlag.get_default() | \ - constants.ClientFlag.COMPRESS - - self.db.set_client_flag(constants.ClientFlag.COMPRESS) - self.assertEqual(self.db.client_flags,exp) - - def test_unset_client_flag(self): - """lp:501290 Unset a client flag""" - data = constants.ClientFlag.get_default() | \ - constants.ClientFlag.COMPRESS - exp = constants.ClientFlag.get_default() - - self.db.client_flags = data - self.db.unset_client_flag(constants.ClientFlag.COMPRESS) - - self.assertEqual(self.db.client_flags,exp) + constants.ClientFlag.COMPRESS + self.db.set_client_flags([constants.ClientFlag.COMPRESS]) + self.assertEqual(self.db._client_flags,exp) + + self.db.set_client_flags([-constants.ClientFlag.COMPRESS]) + self.assertEqual(self.db._client_flags, orig) def test_isset_client_flag(self): """lp:501290 Check if client flag is set""" data = constants.ClientFlag.get_default() | \ constants.ClientFlag.COMPRESS - self.db.client_flags = data + self.db._client_flags = data self.assertEqual(True, self.db.isset_client_flag(constants.ClientFlag.COMPRESS)) @@ -337,7 +346,8 @@ def setUp(self): def tearDown(self): try: - c = db.cursor("DROP TABLE IF EXISTS myconnpy_bits") + c = self.db.cursor() + c.execute("DROP TABLE IF EXISTS myconnpy_bits") except: pass self.db.close() @@ -394,8 +404,8 @@ def setUp(self): def tearDown(self): try: - self.c = db.cursor("DROP TABLE IF EXISTS %s" % (self.tbl)) - self.c.close() + c = self.db.cursor() + c.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) except: pass self.db.close() @@ -424,21 +434,20 @@ class Bug519301(tests.MySQLConnectorTests): """lp:519301 Temporary connection failures with 2 exceptions""" def test_auth(self): - config = self.getMySQLConfig() config['user'] = 'ham' config['password'] = 'spam' - db = None for i in xrange(1,100): - pass try: db = connection.MySQLConnection(**config) - except errors.OperationalError, e: - pass except errors.ProgrammingError, e: + pass + except errors.Error, e: self.fail("Failing authenticating") break + except: + raise else: db.close() @@ -454,12 +463,9 @@ def test_handshake(self): '\x2c\xa2\x08\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72\x59\x48\x00' - class Cnx(object): - pass - - p = protocol.MySQLProtocol(Cnx()) + p = protocol.MySQLProtocol() try: - p.handle_handshake(handshake) + p.parse_handshake(handshake) except: raise self.fail("Failed handling handshake") @@ -481,8 +487,8 @@ def setUp(self): def tearDown(self): try: - self.c = db.cursor("DROP TABLE IF EXISTS %s" % (self.tbl)) - self.c.close() + c = self.db.cursor() + c.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) except: pass self.db.close() @@ -495,22 +501,20 @@ def test_multistmts(self): "INSERT INTO %s (c1) VALUES (10),(20)" % (self.tbl), "SELECT * FROM %s" % (self.tbl), ] - self.c.execute(';'.join(stmts)) + result_iter = self.c.execute(';'.join(stmts), multi=True) - self.assertEqual(None,self.c.fetchone()) - self.assertEqual(True,self.c.next_resultset()) - self.assertEqual(2,self.c.rowcount) - self.assertEqual(True,self.c.next_resultset()) + self.assertEqual(None, result_iter.next().fetchone()) + self.assertEqual(2, result_iter.next().rowcount) exp = [(1, 10), (2, 20)] - self.assertEqual(exp,self.c.fetchall()) - self.assertEqual(None,self.c.next_resultset()) + self.assertEqual(exp, result_iter.next().fetchall()) + self.assertRaises(StopIteration, result_iter.next) class Bug551533and586003(tests.MySQLConnectorTests): """lp: 551533, 586003: impossible to retrieve big result sets""" def setUp(self): config = self.getMySQLConfig() - config['connection_timeout'] = 2 + config['connection_timeout'] = 20 self.db = connection.MySQLConnection(**config) self.c = self.db.cursor() @@ -519,12 +523,12 @@ def setUp(self): self.c.execute("""CREATE TABLE %s ( id INT AUTO_INCREMENT KEY, c1 VARCHAR(100) DEFAULT 'abcabcabcabcabcabcabcabcabcabc' - )""" % (self.tbl)) + ) ENGINE=InnoDB""" % (self.tbl)) def tearDown(self): try: - self.c = db.cursor("DROP TABLE IF EXISTS %s" % (self.tbl)) - self.c.close() + c = self.db.cursor() + c.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) except: pass self.db.close() @@ -532,18 +536,16 @@ def tearDown(self): def test_select(self): """lp: 551533, 586003: impossible to retrieve big result sets""" - insert = "INSERT INTO %s VALUES ()" % (self.tbl) + insert = "INSERT INTO %s (id) VALUES (%%s)" % (self.tbl) exp = 20000 - i = exp - while i > 0: - self.c.execute(insert) - i -= 1 + self.c.executemany(insert, [(None,)]*exp) + self.db.commit() self.c.execute('SELECT * FROM %s LIMIT 20000' % (self.tbl)) try: rows = self.c.fetchall() - except: - self.fail("Failed retrieving big result set") + except Exception, e: + self.fail("Failed retrieving big result set: %s" % e) else: self.assertEqual(exp, self.c.rowcount) @@ -563,11 +565,10 @@ class Bug675425(tests.MySQLConnectorTests): def setUp(self): config = self.getMySQLConfig() - config['connection_timeout'] = 2 self.db = connection.MySQLConnection(**config) self.c = self.db.cursor() - self.tbl = 'Bug551533' + self.tbl = 'Bug675425' self.c.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) self.c.execute("""CREATE TABLE %s ( c1 VARCHAR(30), @@ -576,8 +577,8 @@ def setUp(self): def tearDown(self): try: - self.c = db.cursor("DROP TABLE IF EXISTS %s" % (self.tbl)) - self.c.close() + c = self.db.cursor() + c.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) except: pass self.db.close() @@ -596,14 +597,994 @@ def test_executemany_escape(self): class Bug695514(tests.MySQLConnectorTests): """lp: 695514: Infinite recursion when setting connection client_flags""" - + def test_client_flags(self): """lp: 695514: Infinite recursion when setting connection client_flags """ try: config = self.getMySQLConfig() + config['connection_timeout'] = 2 config['client_flags'] = constants.ClientFlag.get_default() - self.db = connection.MySQLConnection(**config) + db = connection.MySQLConnection(**config) + db.close() except: self.fail("Failed setting client_flags using integer") - \ No newline at end of file + +class Bug809033(tests.MySQLConnectorTests): + """lp: 809033: Lost connection causes infinite loop""" + + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + + self.table_name = 'Bug809033' + self.cnx.cmd_query("DROP TABLE IF EXISTS %s" % self.table_name) + table = ( + "CREATE TABLE %s (" + " id INT UNSIGNED NOT NULL AUTO_INCREMENT," + " c1 VARCHAR(255) DEFAULT '%s'," + " PRIMARY KEY (id)" + ")" + ) % (self.table_name, 'a'*255) + self.cnx.cmd_query(table) + + stmt = "INSERT INTO %s (id) VALUES %s" % ( + self.table_name, ','.join(['(NULL)']*1024) + ) + self.cnx.cmd_query(stmt) + + def tearDown(self): + try: + self.cnx_kill.cmd_query("DROP TABLE IF EXISTS %s" % self.table_name) + self.cnx.close() + except: + pass + + def test_lost_connection(self): + """lp: 809033: Lost connection causes infinite loop""" + def kill(connection_id): + killer = connection.MySQLConnection(**self.getMySQLConfig()) + time.sleep(1) + killer.cmd_query("KILL %d" % connection_id) + killer.close() + + def sleepy_select(cnx): + cur = cnx.cursor() + # Ugly query ahead! + cur.execute( + "SELECT x1.*, x2.* from %s as x1, %s as x2" % ( + self.table_name, self.table_name) + ) + # Save the error so we can check in the calling thread + cnx._test_error = None + try: + cur.fetchall() + except errors.InterfaceError, err: + cnx._test_error = err + + worker = Thread(target=sleepy_select, args=[self.cnx]) + killer = Thread(target=kill, args=[self.cnx.connection_id]) + worker.start() + killer.start() + worker.join() + killer.join() + + self.assertTrue(isinstance(self.cnx._test_error, errors.InterfaceError)) + +class Bug865859(tests.MySQLConnectorTests): + """lp: 865859: sock.recv fails to return in some cases (infinite wait)""" + + def test_reassign_connection(self): + """lp: 865859: sock.recv fails to return in some cases (infinite wait) + """ + config = self.getMySQLConfig() + config['connection_timeout'] = 1 + cnx = connection.MySQLConnection(**config) + cur = cnx.cursor() + cur.execute("DROP TABLE IF EXISTS t1") + cur.execute("CREATE TABLE t1 (c1 INT)") + cur.execute("INSERT INTO t1 (c1) VALUES (1)") + + try: + cnx = connection.MySQLConnection(**config) + cur = cnx.cursor() + cur.execute("DROP TABLE IF EXISTS t1") + except errors.InterfaceError, e: + self.fail("Connection was not closed, we got timeout: %s" % e) + +class BugOra13395083(tests.MySQLConnectorTests): + def test_time_zone(self): + """BUG#13395083: Using time zones""" + config = self.getMySQLConfig() + + utc = tests.UTCTimeZone() + testzone = tests.TestTimeZone(+2) + + # Store a datetime in UTC into a TIMESTAMP column + config['time_zone'] = "+00:00" + now_utc = datetime.utcnow().replace(microsecond=0,tzinfo=utc) + + cnx = connection.MySQLConnection(**config) + cur = cnx.cursor() + cur.execute("DROP TABLE IF EXISTS t1") + cur.execute("CREATE TABLE t1 (c1 TIMESTAMP)") + cur.execute("INSERT INTO t1 (c1) VALUES (%s)", (now_utc,)) + cnx.commit() + + cur.execute("SELECT c1 FROM t1") + row = cur.fetchone() + self.assertEqual(now_utc,row[0].replace(tzinfo=utc)) + + cnx.set_time_zone("+02:00") + cur.execute("SELECT c1 FROM t1") + row = cur.fetchone() + self.assertEqual(now_utc.astimezone(testzone), + row[0].replace(tzinfo=testzone)) + + cnx.close() + +class BugOra13392739(tests.MySQLConnectorTests): + def test_ping(self): + """BUG#13392739: MySQLConnection.ping()""" + config = self.getMySQLConfig() + config['connection_timeout'] = 2 + config['unix_socket'] = None + + cnx = connection.MySQLConnection() + self.assertRaises(errors.InterfaceError,cnx.ping) + + cnx = connection.MySQLConnection(**config) + try: + cnx.ping() + except Exception, e: + self.fail("Error raised although connection should be " + "available (%s)." % e) + + cnx.disconnect() + self.assertRaises(errors.InterfaceError,cnx.ping) + + try: + cnx.ping(reconnect=True) + except Exception, e: + self.fail("Error raised although ping should reconnect. (%s)" % e) + + # Temper with the host to which we reconnect to simulate the + # MySQL not being available. + cnx.disconnect() + cnx._host = 'some-unknown-host-somwhere-on.mars' + self.assertRaises(errors.InterfaceError, cnx.ping, reconnect=True) + + def test_reconnect(self): + """BUG#13392739: MySQLConnection.reconnect()""" + config = self.getMySQLConfig() + config['connection_timeout'] = 1 + config['unix_socket'] = None + + cnx = connection.MySQLConnection(**config) + cnx.disconnect() + self.assertRaises(errors.InterfaceError,cnx.ping) + try: + cnx.reconnect() + except: + self.fail("Errors raised although connection should have been " + "reconnected.") + + cnx.disconnect() + # Temper with the host to which we reconnect to simulate the + # MySQL not being available. + cnx._host = 'some-unknown-host-somwhere-on.mars' + self.assertRaises(errors.InterfaceError,cnx.reconnect) + try: + cnx.reconnect(attempts=3) + except errors.InterfaceError, e: + self.assertTrue('3 attempt(s)' in str(e)) + +class BugOra13435186(tests.MySQLConnectorTests): + def setUp(self): + self.sample_size = 4 + self._reset_samples() + gc.collect() + + def _reset_samples(self): + self.samples = [0,] * self.sample_size + + def _assert_flat_line(self, samples): + for i in range(0, len(samples)-1): + try: + if samples[i] != samples[i+1]: + self.fail("No flat-line for number of collected objects") + except IndexError: + pass # We are at the end. + + def test_converter(self): + for i in range(0, self.sample_size): + conv = conversion.MySQLConverter() + self.samples[i] = len(gc.get_objects()) + + self._assert_flat_line(self.samples) + + def test_connection(self): + config = self.getMySQLConfig() + + # Create a connection and close using close()-method + for i in range(0, self.sample_size): + cnx = connection.MySQLConnection(**config) + cnx.close() + self.samples[i] = len(gc.get_objects()) + + self._assert_flat_line(self.samples) + + self._reset_samples() + # Create a connection and rely on destructor to close + for i in range(0, self.sample_size): + cnx = connection.MySQLConnection(**config) + self.samples[i] = len(gc.get_objects()) + + self._assert_flat_line(self.samples) + + def test_cursor(self): + config = self.getMySQLConfig() + cnx = connection.MySQLConnection(**config) + + # Create a cursor and close using close()-method + for i in range(0, self.sample_size): + cursor = cnx.cursor() + cursor.close() + self.samples[i] = len(gc.get_objects()) + + self._assert_flat_line(self.samples) + + self._reset_samples() + # Create a cursor and rely on destructor to close + for i in range(0, self.sample_size): + cursor = cnx.cursor() + self.samples[i] = len(gc.get_objects()) + + self._assert_flat_line(self.samples) + +class BugOra14184643(tests.MySQLConnectorTests): + """BUG#14184643: cmd_query() disregards waiting results""" + def setUp(self): + config = self.getMySQLConfig() + config['connection_timeout'] = 5 + self.cnx = connection.MySQLConnection(**config) + + def test_cmd_query(self): + """BUG#14184643: cmd_query()""" + + self.cnx.cmd_query('SELECT 1') + self.assertRaises(errors.InternalError, self.cnx.cmd_query, + 'SELECT 2') + + def test_get_rows(self): + """BUG#14184643: get_row() and get_rows()""" + self.cnx.cmd_query('SELECT 1') + self.cnx.get_rows() + self.assertRaises(errors.InternalError, self.cnx.get_rows) + + self.cnx.cmd_query('SELECT 1') + self.cnx.get_row() + self.assertEqual(None, self.cnx.get_row()[0]) + self.assertRaises(errors.InternalError, self.cnx.get_row) + + def test_cmd_statistics(self): + """BUG#14184643: other command after cmd_query()""" + self.cnx.cmd_query('SELECT 1') + self.assertRaises(errors.InternalError, self.cnx.cmd_statistics) + self.cnx.get_rows() + +class BugOra14208326(tests.MySQLConnectorTests): + """BUG#14208326: cmd_query() does not handle multiple statements""" + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + self.cursor = self.cnx.cursor() + + self.table = "BugOra14208326" + self.cnx.cmd_query("DROP TABLE IF EXISTS %s" % self.table) + self.cnx.cmd_query("CREATE TABLE %s (id INT)" % self.table) + + def test_cmd_query(self): + """BUG#14208326: cmd_query() should not allow multiple results""" + self.assertRaises(errors.InterfaceError, + self.cnx.cmd_query, 'SELECT 1; SELECT 2') + + def test_cmd_query_iter(self): + stmt = 'SELECT 1; INSERT INTO %s VALUES (1),(2); SELECT 3' + results = [] + for result in self.cnx.cmd_query_iter(stmt % self.table): + results.append(result) + if 'columns' in result: + results.append(self.cnx.get_rows()) + +class BugOra14201459(tests.MySQLConnectorTests): + """BUG#14201459: Server error 1426 should raise ProgrammingError""" + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + self.cursor = self.cnx.cursor() + + self.tbl = 'Bug14201459' + self.cursor.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) + + def test_error1426(self): + create = "CREATE TABLE %s (c1 TIME(7))" % self.tbl + try: + self.cursor.execute(create) + except errors.ProgrammingError, exception: + if tests.MYSQL_VERSION < (5, 6, 4) and exception.errno != 1064: + self.fail("ProgrammingError is not Error 1064") + elif tests.MYSQL_VERSION >= (5, 6, 4) and exception.errno != 1426: + self.fail("ProgrammingError is not Error 1426") + else: + self.fail("ProgrammingError not raised") + +class BugOra14231160(tests.MySQLConnectorTests): + """BUG#14231160: lastrowid, description and rowcount read-only""" + def test_readonly_properties(self): + cur = cursor.MySQLCursor() + for attr in ('description', 'rowcount', 'lastrowid'): + try: + setattr(cur, attr, 'spam') + except AttributeError, err: + # It's readonly, that's OK + pass + else: + self.fail('Need read-only property: %s' % attr) + +class BugOra14259954(tests.MySQLConnectorTests): + """BUG#14259954: ON DUPLICATE KEY UPDATE VALUE FAILS REGEX""" + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + self.cursor = self.cnx.cursor() + + self.tbl = 'Bug14259954' + self.cursor.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) + create = ("CREATE TABLE %s ( " + "`id` int(11) NOT NULL AUTO_INCREMENT, " + "`c1` int(11) NOT NULL DEFAULT '0', " + "PRIMARY KEY (`id`,`c1`))" % (self.tbl)) + self.cursor.execute(create) + + def test_executemany(self): + query = ("INSERT INTO %s (id,c1) VALUES (%%s,%%s) " + "ON DUPLICATE KEY UPDATE c1=VALUES(c1)") % self.tbl + try: + self.cursor.executemany(query, [(1,1),(2,2)]) + except errors.ProgrammingError, err: + self.fail("Regular expression fails with executemany(): %s" % + err) + + +class BugOra14548043(tests.MySQLConnectorTests): + """BUG#14548043: ERROR MESSAGE SHOULD BE IMPROVED TO DIAGNOSE THE PROBLEM + """ + def test_unix_socket(self): + config = self.getMySQLConfig() + config['unix_socket'] = os.path.join( + tempfile.gettempdir(), 'a'*100 + 'myconnpy_bug14548043.test') + + exp = ("2002: Can't connect to local MySQL " + "server through socket '%s' " + "(AF_UNIX path too long)" % config['unix_socket'][0:100]) + + try: + cnx = connection.MySQLConnection(**config) + except errors.InterfaceError, err: + self.assertEqual(exp, str(err)) + +class BugOra14754894(tests.MySQLConnectorTests): + """ + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + self.cursor = self.cnx.cursor() + + self.tbl = 'BugOra14754894' + self.cursor.execute("DROP TABLE IF EXISTS %s" % (self.tbl)) + self.cursor.execute("CREATE TABLE %s (c1 INT)" % (self.tbl)) + + def test_executemany(self): + insert = "INSERT INTO %s (c1) VALUES (%%(c1)s)" % (self.tbl) + data = [{'c1': 1}] + self.cursor.executemany(insert, [{'c1': 1}]) + + try: + self.cursor.executemany(insert, [{'c1': 1}]) + except ValueError, err: + self.fail(err) + + self.cursor.execute("SELECT c1 FROM %s" % self.tbl) + self.assertEqual(data[0]['c1'], self.cursor.fetchone()[0]) + +class BugOra14843456(tests.MySQLConnectorTests): + """BUG#14843456: UNICODE USERNAME AND/OR PASSWORD FAILS + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + self.cursor = self.cnx.cursor() + + if config['unix_socket'] and os.name != 'nt': + host = 'localhost' + else: + host = config['host'] + + grant = (u"CREATE USER '%s'@'%s' " + u"IDENTIFIED BY '%s'") + + self._credentials = [ + (u'Herne', u'Herne'), + (u'\u0141owicz', u'\u0141owicz'), + ] + for user, password in self._credentials: + self.cursor.execute(grant % (user, host, password)) + + def test_unicode_credentials(self): + config = self.getMySQLConfig() + for user, password in self._credentials: + config['user'] = user + config['password'] = password + config['database'] = None + try: + cnx = connection.MySQLConnection(**config) + except (UnicodeDecodeError, errors.InterfaceError): + self.fail('Failed using unicode username or password') + +class BugOra13808727(tests.MySQLConnectorTests): + """BUG#13808727: ERROR UNCLEAR WHEN TCP PORT IS NOT AN INTEGER + """ + def test_portnumber(self): + config = self.getMySQLConfig() + try: + config['port'] = str(config['port']) + connection.MySQLConnection(**config) + except: + self.fail("Port number as string is not accepted.") + + self.assertRaises(errors.InterfaceError, + connection.MySQLConnection, port="spam") + +class BugOra15876886(tests.MySQLConnectorTests): + """BUG#15876886: CONNECTOR/PYTHON CAN NOT CONNECT TO MYSQL THROUGH IPV6 + """ + def test_ipv6(self): + if not tests.IPV6_AVAILABLE: + tests.MESSAGES['WARNINGS'].append( + "Could not test IPv6. Use options " + "--bind-address=:: --host=::1 and" + " make sure the OS and Python supports IPv6.") + return + + config = self.getMySQLConfig() + config['host'] = '::1' + config['unix_socket'] = None + try: + cnx = connection.MySQLConnection(**config) + except errors.InterfaceError: + self.fail("Can not connect using IPv6") + +class BugOra15915243(tests.MySQLConnectorTests): + """BUG#15915243: PING COMMAND ALWAYS RECONNECTS TO THE DATABASE + """ + def test_ping(self): + config = self.getMySQLConfig() + + cnx = connection.MySQLConnection(**config) + cid = cnx.connection_id + cnx.ping() + # Do not reconnect + self.assertEqual(cid, cnx.connection_id) + cnx.close() + # Do not reconnect + self.assertRaises(errors.InterfaceError, cnx.ping) + # Do reconnect + cnx.ping(reconnect=True) + self.assertNotEqual(cid, cnx.connection_id) + cnx.close() + +class BugOra15916486(tests.MySQLConnectorTests): + """BUG#15916486: RESULTS AFTER STORED PROCEDURE WITH ARGUMENTS ARE NOT KEPT + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.cur.execute("DROP PROCEDURE IF EXISTS sp1") + self.cur.execute("DROP PROCEDURE IF EXISTS sp2") + sp1 = ("CREATE PROCEDURE sp1(IN pIn INT, OUT pOut INT)" + " BEGIN SELECT 1; SET pOut := pIn; SELECT 2; END") + sp2 = ("CREATE PROCEDURE sp2 ()" + " BEGIN SELECT 1; SELECT 2; END") + + self.cur.execute(sp1) + self.cur.execute(sp2) + + def tearDown(self): + try: + self.cur.execute("DROP PROCEDURE IF EXISTS sp1") + self.cur.execute("DROP PROCEDURE IF EXISTS sp2") + except: + pass # Clean up fail is acceptable for this test + + self.cnx.close() + + def test_callproc_with_args(self): + exp = (5, 5) + self.assertEqual(exp, self.cur.callproc('sp1', (5, 0))) + + exp = [[(1,)], [(2,)]] + results = [] + for result in self.cur.stored_results(): + results.append(result.fetchall()) + self.assertEqual(exp, results) + + def test_callproc_without_args(self): + exp = () + self.assertEqual(exp, self.cur.callproc('sp2')) + + exp = [[(1,)], [(2,)]] + results = [] + for result in self.cur.stored_results(): + results.append(result.fetchall()) + self.assertEqual(exp, results) + +class BugOra15836979(tests.MySQLConnectorTests): + """BUG#15836979: UNCLEAR ERROR MESSAGE CONNECTING USING UNALLOWED IP ADDRESS + """ + def setUp(self): + if os.name == 'nt': + return + config = self.getMySQLConfig() + cnx = connection.MySQLConnection(**config) + cnx.cmd_query("DROP USER 'root'@'127.0.0.1'") + try: + cnx.cmd_query("DROP USER 'root'@'::1'") + except errors.DatabaseError: + # Some MySQL servers have no IPv6 entry + pass + cnx.close() + + def tearDown(self): + if os.name == 'nt': + return + config = self.getMySQLConfig() + cnx = connection.MySQLConnection(**config) + cnx.cmd_query( + "GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' " + "WITH GRANT OPTION") + cnx.cmd_query( + "GRANT ALL PRIVILEGES ON *.* TO 'root'@'::1' " + "WITH GRANT OPTION") + cnx.close() + + def test_handshake(self): + if os.name == 'nt': + tests.MESSAGES['WARNINGS'].append( + "Can't test error handling when doing handshake " + "on Windows (lacking named pipe support)") + return + config = self.getMySQLConfig() + config['host'] = '127.0.0.1' + config['unix_socket'] = None + self.assertRaises(errors.DatabaseError, + connection.MySQLConnection, **config) + +class BugOra16217743(tests.MySQLConnectorTests): + """BUG#16217743: CALLPROC FUNCTION WITH STRING PARAMETERS + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + + self.cnx.cmd_query("DROP TABLE IF EXISTS bug16217743") + self.cnx.cmd_query("DROP PROCEDURE IF EXISTS sp_bug16217743") + self.cnx.cmd_query("CREATE TABLE bug16217743 (c1 VARCHAR(20), c2 INT)") + self.cnx.cmd_query( + "CREATE PROCEDURE sp_bug16217743 (p1 VARCHAR(20), p2 INT) " + "BEGIN INSERT INTO bug16217743 (c1, c2) " + "VALUES (p1, p2); END;") + + def tearDown(self): + self.cnx.cmd_query("DROP TABLE IF EXISTS bug16217743") + self.cnx.cmd_query("DROP PROCEDURE IF EXISTS sp_bug16217743") + + def test_procedure(self): + exp = (u'ham', 42) + cur = self.cnx.cursor() + cur.callproc('sp_bug16217743', ('ham', 42)) + cur.execute("SELECT c1, c2 FROM bug16217743") + self.assertEqual(exp, cur.fetchone()) + + +class BugOra16217667(tests.MySQLConnectorTests): + """BUG#16217667: PYTHON CONNECTOR 3.2 SSL CONNECTION FAILS + """ + def setUp(self): + config = self.getMySQLConfig() + self.admin_cnx = connection.MySQLConnection(**config) + + self.admin_cnx.cmd_query( + "GRANT ALL ON %s.* TO 'ssluser'@'%s' REQUIRE X509" % ( + config['database'], tests.MYSQL_CONFIG['host'])) + + def tearDown(self): + self.admin_cnx.cmd_query("DROP USER 'ssluser'@'%s'" % ( + tests.MYSQL_CONFIG['host'])) + + def test_sslauth(self): + if not tests.SSL_AVAILABLE: + tests.MESSAGES['WARNINGS'].append( + "BugOra16217667 test failed. Python lacks SSL support.") + return + + config = self.getMySQLConfig() + config['user'] = 'ssluser' + config['password'] = '' + config['unix_socket'] = None + config['ssl_verify_cert'] = True + config.update({ + 'ssl_ca': os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_CA_cert.pem')), + 'ssl_cert': os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_client_cert.pem')), + 'ssl_key': os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_client_key.pem')), + }) + + try: + cnx = connection.MySQLConnection(**config) + except errors.ProgrammingError: + self.fail("Failed authentication with SSL") + + cnx.cmd_query("SHOW STATUS LIKE 'Ssl_cipher'") + self.assertTrue(cnx.get_rows()[0][0] != u'') + + +class BugOra16316049(tests.MySQLConnectorTests): + """ SSL ERROR: [SSL: TLSV1_ALERT_UNKNOWN_CA] AFTER FIX 6217667""" + def setUp(self): + config = self.getMySQLConfig() + self.admin_cnx = connection.MySQLConnection(**config) + + self.admin_cnx.cmd_query( + "GRANT ALL ON %s.* TO 'ssluser'@'%s' REQUIRE SSL" % ( + config['database'], tests.MYSQL_CONFIG['host'])) + + def tearDown(self): + self.admin_cnx.cmd_query("DROP USER 'ssluser'@'%s'" % ( + tests.MYSQL_CONFIG['host'])) + + def test_ssl(self): + if not tests.SSL_AVAILABLE: + tests.MESSAGES['WARNINGS'].append( + "BugOra16217667 test failed. Python lacks SSL support.") + return + + ssl_ca = os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_CA_cert.pem')) + ssl_cert = os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_client_cert.pem')) + ssl_key = os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_client_key.pem')) + + config = self.getMySQLConfig() + config['user'] = 'ssluser' + config['password'] = '' + config['unix_socket'] = None + config.update({ + 'ssl_ca': None, + 'ssl_cert': None, + 'ssl_key': None, + }) + + # User requires SSL, but no values for ssl argument given + self.assertRaises(errors.ProgrammingError, + connection.MySQLConnection, **config) + + # Use wrong value for ssl_ca + config['ssl_ca'] = os.path.abspath( + os.path.join(tests.SSL_DIR, 'tests_c123a_cert.pem')) + config['ssl_cert'] = ssl_cert + config['ssl_key'] = ssl_key + config['ssl_verify_cert'] = True + self.assertRaises(errors.InterfaceError, + connection.MySQLConnection, **config) + + # Use correct value + config['ssl_ca'] = ssl_ca + try: + cnx = connection.MySQLConnection(**config) + except errors.ProgrammingError: + self.fail("Failed authentication with SSL") + + cnx.cmd_query("SHOW STATUS LIKE 'Ssl_cipher'") + self.assertTrue(cnx.get_rows()[0][0] != u'') + + +class BugOra16369511(tests.MySQLConnectorTests): + """BUG#16369511: LOAD DATA LOCAL INFILE IS MISSING + """ + def setUp(self): + config = self.getMySQLConfig() + config['client_flags'] = [constants.ClientFlag.LOCAL_FILES] + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.data_file = os.path.join( + 'python%s' % sys.version_info[0], + 'tests', 'data', 'local_data.csv') + + self.cur.execute("DROP TABLE IF EXISTS local_data") + self.cur.execute( + "CREATE TABLE local_data (id int, c1 VARCHAR(6), c2 VARCHAR(6))") + + def tearDown(self): + self.cur.execute("DROP TABLE IF EXISTS local_data") + + def test_load_csv(self): + sql = "LOAD DATA LOCAL INFILE %s INTO TABLE local_data" + self.cur.execute(sql, (self.data_file,)) + self.cur.execute("SELECT * FROM local_data") + + exp = [ + (1, u'c1_1', u'c2_1'), (2, u'c1_2', u'c2_2'), + (3, u'c1_3', u'c2_3'), (4, u'c1_4', u'c2_4'), + (5, u'c1_5', u'c2_5'), (6, u'c1_6', u'c2_6')] + self.assertEqual(exp, self.cur.fetchall()) + + def test_filenotfound(self): + sql = "LOAD DATA LOCAL INFILE %s INTO TABLE local_data" + self.assertRaises(errors.InterfaceError, + self.cur.execute, sql, (self.data_file + '_spam',)) + + +class BugOra16662920(tests.MySQLConnectorTests): + """BUG#16662920: FETCHALL() IGNORES NEXT_ROW FOR BUFFERED CURSORS + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + cur = self.cnx.cursor() + + cur.execute("DROP TABLE IF EXISTS t1") + cur.execute( + "CREATE TABLE t1 (id INT AUTO_INCREMENT, c1 VARCHAR(20), " + "PRIMARY KEY (id)) ENGINE=InnoDB" + ) + + data = [ ('a',), ('c',), ('e',), ('d',), ('g',), ('f',) ] + cur.executemany("INSERT INTO t1 (c1) VALUES (%s)", data) + cur.close() + self.cnx.commit() + + def tearDown(self): + try: + cur = self.cnx.cursor() + cur.execute("DROP TABLE IF EXISTS t1") + self.cnx.close() + except Exception: + pass + + def test_buffered(self): + cur = self.cnx.cursor(buffered=True) + cur.execute("SELECT * FROM t1 ORDER BY c1") + self.assertEqual((1, u'a'), cur.fetchone()) + exp = [(2, u'c'), (4, u'd'), (3, u'e')] + self.assertEqual(exp, cur.fetchmany(3)) + exp = [(6, u'f'), (5, u'g')] + self.assertEqual(exp, cur.fetchall()) + cur.close() + + def test_buffered_raw(self): + cur = self.cnx.cursor(buffered=True, raw=True) + cur.execute("SELECT * FROM t1 ORDER BY c1") + self.assertEqual(('1', 'a'), cur.fetchone()) + exp = [('2', 'c'), ('4', 'd'), ('3', 'e')] + self.assertEqual(exp, cur.fetchmany(3)) + exp = [('6', 'f'), ('5', 'g')] + self.assertEqual(exp, cur.fetchall()) + cur.close() + + +class BugOra17002411(tests.MySQLConnectorTests): + """BUG#17002411: LOAD DATA LOCAL INFILE FAILS WITH BIGGER FILES + """ + def setUp(self): + config = self.getMySQLConfig() + config['client_flags'] = [constants.ClientFlag.LOCAL_FILES] + self.cnx = connection.MySQLConnection(**config) + self.cur = self.cnx.cursor() + + self.data_file = os.path.join( + 'python%s' % sys.version_info[0], + 'tests', 'data', 'local_data_big.csv') + + self.cur.execute("DROP TABLE IF EXISTS local_data") + self.cur.execute( + "CREATE TABLE local_data (" + "id INT AUTO_INCREMENT KEY, " + "c1 VARCHAR(255), c2 VARCHAR(255))" + ) + + self.exp_rows = 33000 + + fp = open(self.data_file, 'w') + i = 0 + while i < self.exp_rows: + fp.write("%s\t%s\n" % ('a'*255, 'b'*255)) + i += 1 + fp.close() + + def tearDown(self): + self.cur.execute("DROP TABLE IF EXISTS local_data") + os.unlink(self.data_file) + + def test_load_csv(self): + sql = "LOAD DATA LOCAL INFILE %s INTO TABLE local_data (c1, c2)" + self.cur.execute(sql, (self.data_file,)) + self.cur.execute("SELECT COUNT(*) FROM local_data") + self.assertEqual(self.exp_rows, self.cur.fetchone()[0]) + + +class BugOra17041412(tests.MySQLConnectorTests): + """BUG#17041412: FETCHALL() DOES NOT RETURN SELF._NEXTROW IF AVAILABLE + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + cur = self.cnx.cursor() + self.table_name = 'BugOra17041240' + self.data = [(1,), (2,), (3,)] + self.data_raw = [('1',), ('2',), ('3',)] + cur.execute("DROP TABLE IF EXISTS %s" % self.table_name) + cur.execute("CREATE TABLE %s (c1 INT)" % self.table_name) + cur.executemany( + "INSERT INTO %s (c1) VALUES (%%s)" % self.table_name, + self.data) + self.cnx.commit() + + def tearDown(self): + cur = self.cnx.cursor() + cur.execute("DROP TABLE IF EXISTS %s" % self.table_name) + + def test_one_all(self): + cur = self.cnx.cursor() + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data[0], cur.fetchone()) + self.assertEqual(1, cur.rowcount) + self.assertEqual(self.data[1:], cur.fetchall()) + self.assertEqual(3, cur.rowcount) + + def test_many_all(self): + cur = self.cnx.cursor() + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data[0:2], cur.fetchmany(2)) + self.assertEqual(2, cur.rowcount) + self.assertEqual(self.data[2:], cur.fetchall()) + self.assertEqual(3, cur.rowcount) + + def test_many(self): + cur = self.cnx.cursor() + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data, cur.fetchall()) + self.assertEqual(3, cur.rowcount) + + cur.execute("SELECT * FROM %s WHERE c1 > %%s" % self.table_name, + (self.data[-1][0] + 100,)) + self.assertEqual([], cur.fetchall()) + + def test_raw_one_all(self): + cur = self.cnx.cursor(raw=True) + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data_raw[0], cur.fetchone()) + self.assertEqual(1, cur.rowcount) + self.assertEqual(self.data_raw[1:], cur.fetchall()) + self.assertEqual(3, cur.rowcount) + + def test_raw_many_all(self): + cur = self.cnx.cursor(raw=True) + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data_raw[0:2], cur.fetchmany(2)) + self.assertEqual(2, cur.rowcount) + self.assertEqual(self.data_raw[2:], cur.fetchall()) + self.assertEqual(3, cur.rowcount) + + def test_raw_many(self): + cur = self.cnx.cursor(raw=True) + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data_raw, cur.fetchall()) + self.assertEqual(3, cur.rowcount) + + cur.execute("SELECT * FROM %s WHERE c1 > 1000" % self.table_name) + self.assertEqual([], cur.fetchall()) + + +class BugOra17041240(tests.MySQLConnectorTests): + """BUG#17041240: UNCLEAR ERROR CLOSING CURSOR WITH UNREAD RESULTS + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + cur = self.cnx.cursor() + self.table_name = 'BugOra17041240' + self.data = [(1,),(2,),(3,)] + cur.execute("DROP TABLE IF EXISTS %s" % self.table_name) + cur.execute("CREATE TABLE %s (c1 INT)" % self.table_name) + cur.executemany( + "INSERT INTO %s (c1) VALUES (%%s)" % self.table_name, + self.data) + self.cnx.commit() + + def tearDown(self): + cur = self.cnx.cursor() + cur.execute("DROP TABLE IF EXISTS %s" % self.table_name) + self.cnx.close() + + def test_cursor_close(self): + cur = self.cnx.cursor() + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data[0], cur.fetchone()) + self.assertEqual(self.data[1], cur.fetchone()) + self.assertRaises(errors.InternalError, cur.close) + self.assertEqual(self.data[2], cur.fetchone()) + + def test_cursor_new(self): + cur = self.cnx.cursor() + cur.execute("SELECT * FROM %s ORDER BY c1" % self.table_name) + self.assertEqual(self.data[0], cur.fetchone()) + self.assertEqual(self.data[1], cur.fetchone()) + self.assertRaises(errors.InternalError, self.cnx.cursor) + self.assertEqual(self.data[2], cur.fetchone()) + + +class BugOra17065366(tests.MySQLConnectorTests): + """BUG#17065366: EXECUTEMANY FAILS USING MYSQL FUNCTION FOR INSERTS + """ + def setUp(self): + config = self.getMySQLConfig() + self.cnx = connection.MySQLConnection(**config) + cur = self.cnx.cursor() + self.table_name = 'BugOra17065366' + cur.execute("DROP TABLE IF EXISTS %s" % self.table_name) + cur.execute( + "CREATE TABLE %s ( " + "id INT UNSIGNED NOT NULL AUTO_INCREMENT KEY, " + "c1 INT, c2 DATETIME) ENGINE=INNODB" % self.table_name) + + def tearDown(self): + cur = self.cnx.cursor() + cur.execute("DROP TABLE IF EXISTS %s" % self.table_name) + + def test_executemany(self): + cur = self.cnx.cursor() + + adate = datetime(2012, 9, 30) + stmt = ( + "INSERT INTO %s (id, c1, c2) " + "VALUES (%%s, %%s, DATE('%s 13:07:00'))" + "/* Using DATE() */ ON DUPLICATE KEY UPDATE c1 = id" + ) % (self.table_name, adate.strftime('%Y-%m-%d')) + + exp = [ + (1, 0, datetime(2012, 9, 30, 0, 0)), + (2, 0, datetime(2012, 9, 30, 0, 0)) + ] + cur.executemany(stmt, [(None, 0), (None, 0)]) + self.cnx.commit() + cur.execute("SELECT * FROM %s" % self.table_name) + rows = cur.fetchall() + self.assertEqual(exp, rows) + + exp = [ + (1, 1, datetime(2012, 9, 30, 0, 0)), + (2, 2, datetime(2012, 9, 30, 0, 0)) + ] + cur.executemany(stmt, [(1, 1), (2, 2)]) + self.cnx.commit() + cur.execute("SELECT * FROM %s" % self.table_name) + rows = cur.fetchall() + self.assertEqual(exp, rows) diff --git a/lib/tests/test_bugs_future.py b/lib/tests/test_bugs_future.py new file mode 100644 index 0000000..1feabc4 --- /dev/null +++ b/lib/tests/test_bugs_future.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +"""Unittests for bugs using __future__ +""" + +from __future__ import unicode_literals +import tests + +from mysql.connector import (connection, errors) + + +class BugOra16655208(tests.MySQLConnectorTests): + """BUG#16655208:UNICODE DATABASE NAMES FAILS WHEN USING UNICODE_LITERALS + """ + def test_unicode_database(self): + config = self.getMySQLConfig() + config['database'] = 'データベース' + self.assertRaises(errors.DatabaseError, + connection.MySQLConnection, **config) + diff --git a/lib/tests/test_connection.py b/lib/tests/test_connection.py index 33f7b30..25cd227 100644 --- a/lib/tests/test_connection.py +++ b/lib/tests/test_connection.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for mysql.connector.connection """ @@ -27,208 +27,1452 @@ import os import socket import logging -from collections import deque - -try: - from hashlib import md5 -except ImportError: - from md5 import new as md5 +import inspect +import timeit +from decimal import Decimal +import StringIO import tests from tests import mysqld -from mysql.connector import connection, errors +from mysql.connector import (connection, network, errors, constants, utils, + cursor) logger = logging.getLogger(tests.LOGGER_NAME) -class MySQLBaseSocketTests(tests.MySQLConnectorTests): - +OK_PACKET = '\x07\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00' +OK_PACKET_RESULT = { + 'insert_id': 0, + 'affected_rows': 0, + 'field_count': 0, + 'warning_count': 0, + 'server_status': 0 + } + +ERR_PACKET = '\x47\x00\x00\x02\xff\x15\x04\x23\x32\x38\x30\x30\x30'\ + '\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69\x65\x64'\ + '\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20\x27\x68\x61'\ + '\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74'\ + '\x27\x20\x28\x75\x73\x69\x6e\x67\x20\x70\x61\x73\x73'\ + '\x77\x6f\x72\x64\x3a\x20\x59\x45\x53\x29' + +EOF_PACKET = '\x05\x00\x00\x00\xfe\x00\x00\x00\x00' +EOF_PACKET_RESULT = {'status_flag': 0, 'warning_count': 0} + +class _DummyMySQLConnection(connection.MySQLConnection): + def _open_connection(self, *args, **kwargs): + pass + def _post_connection(self, *args, **kwargs): + pass + +class ConnectionTests(tests.MySQLConnectorTests): + def test_DEFAULT_CONFIGURATION(self): + exp = { + 'database': None, + 'user': '', + 'password': '', + 'host': '127.0.0.1', + 'port': 3306, + 'unix_socket': None, + 'use_unicode': True, + 'charset': 'utf8', + 'collation': None, + 'autocommit': False, + 'time_zone': None, + 'sql_mode': None, + 'get_warnings': False, + 'raise_on_warnings': False, + 'connection_timeout': None, + 'client_flags': 0, + 'buffered': False, + 'raw': False, + 'ssl_ca': None, + 'ssl_cert': None, + 'ssl_key': None, + 'ssl_verify_cert': False, + 'passwd': None, + 'db': None, + 'connect_timeout': None, + 'dsn': None, + 'force_ipv6': False, + } + self.assertEqual(exp, connection.DEFAULT_CONFIGURATION) + +class MySQLConnectionTests(tests.MySQLConnectorTests): def setUp(self): config = self.getMySQLConfig() - self._host = config['host'] - self._port = config['port'] - self.cnx = connection.MySQLBaseSocket() + self.cnx = connection.MySQLConnection(**config) def tearDown(self): try: - self.cnx.close_connection() + self.cnx.close() except: pass - def _get_socket(self): - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - logger.debug("Get socket for %s:%d" % (self._host,self._port)) - sock.connect((self._host, self._port)) - return sock - - def test_init(self): - """MySQLBaseSocket initialization""" - exp = dict( - sock = None, - connection_timeout = None, - buffer = deque(), - recvsize = 1024*8, - ) + def test___init__(self): + """MySQLConnection initialization""" + cnx = connection.MySQLConnection() + exp = { + 'converter': None, + '_client_flags': constants.ClientFlag.get_default(), + '_charset_id': 33, + '_user': '', + '_password': '', + '_database': '', + '_host': '127.0.0.1', + '_port': 3306, + '_unix_socket': None, + '_use_unicode': True, + '_get_warnings': False, + '_raise_on_warnings': False, + '_connection_timeout': None, + '_buffered': False, + '_unread_result': False, + '_have_next_result': False, + '_raw': False, + '_client_host': '', + '_client_port': 0, + '_ssl': {}, + '_force_ipv6': False, + } + for key, value in exp.items(): + self.assertEqual(value, getattr(cnx, key), + msg="Default for '%s' did not match." % key) - for k,v in exp.items(): - self.assertEqual(v, self.cnx.__dict__[k]) - - def test_open_connection(self): - """Opening a connection""" - try: - self.cnx.open_connection() - except: - self.fail() - - def test_close_connection(self): - """Closing a connection""" - self.cnx.close_connection() - self.assertEqual(None, self.cnx.sock) + # Make sure that when at least one argument is given, + # connect() is called + class FakeMySQLConnection(connection.MySQLConnection): + def connect(self, *args, **kwargs): + self._database = kwargs['database'] + exp = 'test' + cnx = FakeMySQLConnection(database=exp) + self.assertEqual(exp, cnx._database) - def test_get_address(self): - """Get the address of a connection""" - try: - self.cnx.get_address() - except: - self.fail() + def test__get_self(self): + """Return self""" + self.assertEqual(self.cnx, self.cnx._get_self()) - def test_send(self): - """Send data through the socket""" - data = "abcdefghijklmnopq" - self.assertRaises(errors.OperationalError, self.cnx.send, data, 0) + def test__send_cmd(self): + """Send a command to MySQL""" + cmd = constants.ServerCmd.QUERY + arg = 'SELECT 1' + pktnr = 2 - self.cnx.sock = self._get_socket() - try: - self.cnx.send(data,0) - except: - self.fail() + self.cnx._socket.sock = None + self.assertRaises(errors.OperationalError, self.cnx._send_cmd, + cmd, arg, pktnr) + + self.cnx._socket.sock = tests.DummySocket() + exp = '\x07\x00\x00\x03\x00\x00\x00\x02\x00\x00\x00' + self.cnx._socket.sock.add_packet(exp) + res = self.cnx._send_cmd(cmd, arg, pktnr) + self.assertEqual(exp, res) + + # Send an unknown command, the result should be an error packet + exp = '\x18\x00\x00\x01\xff\x17\x04\x23\x30\x38\x53\x30\x31\x55'\ + '\x6e\x6b\x6e\x6f\x77\x6e\x20\x63\x6f\x6d\x6d\x61\x6e\x64' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(exp) + res = self.cnx._send_cmd(90, 'spam', 0) + + def test__toggle_have_next_result(self): + """Toggle _have_next_results based on server/status flags""" + flags = constants.ServerFlag.MORE_RESULTS_EXISTS + self.cnx._have_next_result = False + self.cnx._toggle_have_next_result(flags) + self.assertTrue(self.cnx._have_next_result) + self.cnx._toggle_have_next_result(0) + self.assertFalse(self.cnx._have_next_result) - def test_recv(self): - """Receive data from the socket""" - self.cnx.sock = self._get_socket() + def test__handle_ok(self): + """Handle an OK-packet sent by MySQL""" + self.assertEqual(OK_PACKET_RESULT, self.cnx._handle_ok(OK_PACKET)) + self.assertRaises(errors.ProgrammingError, + self.cnx._handle_ok, ERR_PACKET) + self.assertRaises(errors.InterfaceError, + self.cnx._handle_ok, EOF_PACKET) - # Handshake - buf = self.cnx.recv() - self.assertEqual((0,10), (ord(buf[3]),ord(buf[4]))) + # Test for multiple results + self.cnx._have_next_result = False + packet = OK_PACKET[:-4] + '\x08' + OK_PACKET[-3:] + self.cnx._handle_ok(packet) + self.assertTrue(self.cnx._have_next_result) + + def test__handle_eof(self): + """Handle an EOF-packet sent by MySQL""" + self.assertEqual(EOF_PACKET_RESULT, self.cnx._handle_eof(EOF_PACKET)) + self.assertRaises(errors.ProgrammingError, + self.cnx._handle_eof, ERR_PACKET) + self.assertRaises(errors.InterfaceError, + self.cnx._handle_eof, OK_PACKET) + + # Test for multiple results + self.cnx._have_next_result = False + packet = EOF_PACKET[:-2] + '\x08' + EOF_PACKET[-1:] + self.cnx._handle_eof(packet) + self.assertTrue(self.cnx._have_next_result) + + def test__handle_result(self): + """Handle the result after sending a command to MySQL""" + self.assertRaises(errors.InterfaceError, self.cnx._handle_result, + '\x00') + self.assertRaises(errors.InterfaceError, self.cnx._handle_result, + None) + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packets([ + '\x17\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x01'\ + '\x31\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ + '\x00\x00\x00', + '\x05\x00\x00\x03\xfe\x00\x00\x00\x00']) - self.cnx.sock.settimeout(1) - self.assertRaises(errors.InterfaceError,self.cnx.recv) + exp = { + 'eof': {'status_flag': 0, 'warning_count': 0}, + 'columns': [('1', 8, None, None, None, None, 0, 129)] + } + res = self.cnx._handle_result('\x01\x00\x00\x01\x01') + self.assertEqual(exp, res) + + self.assertEqual(EOF_PACKET_RESULT, + self.cnx._handle_result(EOF_PACKET)) + self.cnx._unread_result = False + + # Handle LOAD DATA INFILE + self.cnx._socket.sock.reset() + packet = ( + '\x22\x00\x00\x01\xfb\x70\x79\x74\x68\x6f\x6e\x32\x2f' + '\x74\x65\x73\x74\x73\x2f\x64\x61\x74\x61\x2f\x6c\x6f' + '\x63\x61\x6c\x5f\x64\x61\x74\x61\x2e\x63\x73\x76') + self.cnx._socket.sock.add_packet( + '\x37\x00\x00\x04\x00\x06\x00\x01\x00\x00\x00\x2f\x52' + '\x65\x63\x6f\x72\x64\x73\x3a\x20\x36\x20\x20\x44\x65' + '\x6c\x65\x74\x65\x64\x3a\x20\x30\x20\x20\x53\x6b\x69' + '\x70\x70\x65\x64\x3a\x20\x30\x20\x20\x57\x61\x72\x6e' + '\x69\x6e\x67\x73\x3a\x20\x30') + exp = { + 'info_msg': 'Records: 6 Deleted: 0 Skipped: 0 Warnings: 0', + 'insert_id': 0, 'field_count': 0, 'warning_count': 0, + 'server_status': 1, 'affected_rows': 6} + self.assertEqual(exp, self.cnx._handle_result(packet)) + exp = [ + ('\x47\x00\x00\x04\x31\x09\x63\x31\x5f\x31\x09\x63\x32' + '\x5f\x31\x0a\x32\x09\x63\x31\x5f\x32\x09\x63\x32\x5f' + '\x32\x0a\x33\x09\x63\x31\x5f\x33\x09\x63\x32\x5f\x33' + '\x0a\x34\x09\x63\x31\x5f\x34\x09\x63\x32\x5f\x34\x0a' + '\x35\x09\x63\x31\x5f\x35\x09\x63\x32\x5f\x35\x0a\x36' + '\x09\x63\x31\x5f\x36\x09\x63\x32\x5f\x36'), + '\x00\x00\x00\x05'] + self.assertEqual(exp, self.cnx._socket.sock._client_sends) + + # Column count is invalid (is None) + packet = '\x01\x00\x00\x01\xfa\xa3\x00\xa3' + self.assertRaises(errors.InterfaceError, + self.cnx._handle_result, packet) + + # First byte in first packet is wrong + self.cnx._socket.sock.add_packets([ + '\x00\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x01'\ + '\x31\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ + '\x00\x00\x00', + '\x05\x00\x00\x03\xfe\x00\x00\x00\x00']) + + self.assertRaises(errors.InterfaceError, + self.cnx._handle_result, '\x01\x00\x00\x01\x00') + + def __helper_get_rows_buffer(self, pkts=None, toggle_next_result=False): + self.cnx._socket.sock.reset() - # Authenticate - req = \ - '\x0d\xa2\x03\x00\x00\x00\xa0\x00\x21\x00\x00\x00'\ - '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - '\x00\x00\x00\x00\x72\x6f\x6f\x74\x00\x00\x6d\x79\x73\x71\x6c\x00' - exp = '\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00' + packets = [ + '\x07\x00\x00\x04\x06\x4d\x79\x49\x53\x41\x4d', + '\x07\x00\x00\x05\x06\x49\x6e\x6e\x6f\x44\x42', + '\x0a\x00\x00\x06\x09\x42\x4c\x41\x43\x4b\x48\x4f\x4c\x45', + '\x04\x00\x00\x07\x03\x43\x53\x56', + '\x07\x00\x00\x08\x06\x4d\x45\x4d\x4f\x52\x59', + '\x0a\x00\x00\x09\x09\x46\x45\x44\x45\x52\x41\x54\x45\x44', + '\x08\x00\x00\x0a\x07\x41\x52\x43\x48\x49\x56\x45', + '\x0b\x00\x00\x0b\x0a\x4d\x52\x47\x5f\x4d\x59\x49\x53\x41\x4d', + '\x05\x00\x00\x0c\xfe\x00\x00\x20\x00', + ] - self.cnx.send(req,1) - self.assertEqual(exp, self.cnx.recv()) + if toggle_next_result: + packets[-1] = packets[-1][:-2] + '\x08' + packets[-1][-1:] - # Execute: SELECT "Ham" - req = \ - '\x03\x53\x45\x4c\x45\x43\x54\x20\x22\x48\x61\x6d\x22' + self.cnx._socket.sock.add_packets(packets) + self.cnx.unread_result = True + + def test_get_rows(self): + """Get rows from the MySQL resultset""" + self.cnx._socket.sock = tests.DummySocket() + self.__helper_get_rows_buffer() exp = ( - '\x01\x00\x00\x01\x01', - '\x19\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x03\x48\x61\x6d\x00'\ - '\x0c\x21\x00\x09\x00\x00\x00\xfd\x01\x00\x1f\x00\x00', - '\x05\x00\x00\x03\xfe\x00\x00\x02\x00', - '\x04\x00\x00\x04\x03\x48\x61\x6d', - '\x05\x00\x00\x05\xfe\x00\x00\x02\x00', + [('MyISAM',), ('InnoDB',), ('BLACKHOLE',), ('CSV',), ('MEMORY',), + ('FEDERATED',), ('ARCHIVE',), ('MRG_MYISAM',)], + {'status_flag': 32, 'warning_count': 0} + ) + res = self.cnx.get_rows() + self.assertEqual(exp, res) + + self.__helper_get_rows_buffer() + rows = exp[0] + i = 0 + while i < len(rows): + exp = (rows[i:i + 2], None) + res = self.cnx.get_rows(2) + self.assertEqual(exp, res) + i += 2 + exp = ([], {'status_flag': 32, 'warning_count': 0}) + self.assertEqual(exp, self.cnx.get_rows()) + + # Test unread results + self.cnx.unread_result = False + self.assertRaises(errors.InternalError, self.cnx.get_rows) + + # Test multiple results + self.cnx._have_next_results = False + self.__helper_get_rows_buffer(toggle_next_result=True) + exp = {'status_flag': 8, 'warning_count': 0} + self.assertEqual(exp, self.cnx.get_rows()[-1]) + self.assertTrue(self.cnx._have_next_result) + + def test_get_row(self): + """Get a row from the MySQL resultset""" + self.cnx._socket.sock = tests.DummySocket() + self.__helper_get_rows_buffer() + expall = ( + [('MyISAM',), ('InnoDB',), ('BLACKHOLE',), ('CSV',), ('MEMORY',), + ('FEDERATED',), ('ARCHIVE',), ('MRG_MYISAM',)], + {'status_flag': 32, 'warning_count': 0} + ) + + rows = expall[0] + for row in rows: + res = self.cnx.get_row() + exp = (row, None) + self.assertEqual(exp, res) + exp = ([], {'status_flag': 32, 'warning_count': 0}) + self.assertEqual(exp, self.cnx.get_rows()) + + # Test unread results + self.cnx.unread_results = False + self.assertRaises(errors.InternalError, self.cnx.get_row) + + def test_cmd_init_db(self): + """Send the Init_db-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(OK_PACKET) + self.assertEqual(OK_PACKET_RESULT, self.cnx.cmd_init_db('test')) + + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet( + '\x2c\x00\x00\x01\xff\x19\x04\x23\x34\x32\x30\x30'\ + '\x30\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x64\x61\x74'\ + '\x61\x62\x61\x73\x65\x20\x27\x75\x6e\x6b\x6e\x6f'\ + '\x77\x6e\x5f\x64\x61\x74\x61\x62\x61\x73\x65\x27' + ) + self.assertRaises(errors.ProgrammingError, + self.cnx.cmd_init_db, 'unknown_database') + + def test_cmd_query(self): + """Send a query to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(OK_PACKET) + res = self.cnx.cmd_query("SET AUTOCOMMIT = OFF") + self.assertEqual(OK_PACKET_RESULT, res) + + packets = [ + '\x01\x00\x00\x01\x01', + '\x17\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x01'\ + '\x31\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ + '\x00\x00\x00', + '\x05\x00\x00\x03\xfe\x00\x00\x00\x00' + ] + + # query = "SELECT 1" + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packets(packets) + exp = { + 'eof': {'status_flag': 0, 'warning_count': 0}, + 'columns': [('1', 8, None, None, None, None, 0, 129)] + } + res = self.cnx.cmd_query("SELECT 1") + self.assertEqual(exp, res) + self.assertRaises(errors.InternalError, + self.cnx.cmd_query, 'SELECT 2') + self.cnx.unread_result = False + + # Forge the packets so the multiple result flag is set + packets[-1] = packets[-1][:-2] + '\x08' + packets[-1][-1:] + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packets(packets) + self.assertRaises(errors.InterfaceError, + self.cnx.cmd_query, "SELECT 1") + + def test_cmd_query_iter(self): + """Send queries to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(OK_PACKET) + res = self.cnx.cmd_query_iter("SET AUTOCOMMIT = OFF").next() + self.assertEqual(OK_PACKET_RESULT, res) + + packets = [ + '\x01\x00\x00\x01\x01', + '\x17\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x01'\ + '\x31\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ + '\x00\x00\x00', + '\x05\x00\x00\x03\xfe\x00\x00\x08\x00', + '\x02\x00\x00\x04\x01\x31', + '\x05\x00\x00\x05\xfe\x00\x00\x08\x00', + '\x07\x00\x00\x06\x00\x01\x00\x08\x00\x00\x00', + '\x01\x00\x00\x07\x01', + '\x17\x00\x00\x08\x03\x64\x65\x66\x00\x00\x00\x01'\ + '\x32\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ + '\x00\x00\x00', + '\x05\x00\x00\x09\xfe\x00\x00\x00\x00', + '\x02\x00\x00\x0a\x01\x32', + '\x05\x00\x00\x0b\xfe\x00\x00\x00\x00', + ] + exp = [ + { 'columns': [('1', 8, None, None, None, None, 0, 129)], + 'eof': {'status_flag': 8, 'warning_count': 0} }, + ([('1',)], {'status_flag': 8, 'warning_count': 0}), + {'affected_rows': 1, + 'field_count': 0, + 'insert_id': 0, + 'server_status': 8, + 'warning_count': 0}, + {'columns': [('2', 8, None, None, None, None, 0, 129)], + 'eof': {'status_flag': 0, 'warning_count': 0}}, + ([('2',)], {'status_flag': 0, 'warning_count': 0}), + ] + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packets(packets) + results = [] + for result in self.cnx.cmd_query_iter("SELECT 1; SELECT 2"): + results.append(result) + if 'columns' in result: + results.append(self.cnx.get_rows()) + self.assertEqual(exp, results) + + def test_cmd_refresh(self): + """Send the Refresh-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(OK_PACKET) + refresh = constants.RefreshOption.LOG | constants.RefreshOption.THREADS + + self.assertEqual(OK_PACKET_RESULT, self.cnx.cmd_refresh(refresh)) + + def test_cmd_quit(self): + """Send the Quit-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.assertEqual('\x01', self.cnx.cmd_quit()) + + def test_cmd_shutdown(self): + """Send the Shutdown-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(EOF_PACKET) + self.assertEqual(EOF_PACKET_RESULT, self.cnx.cmd_shutdown()) + exp = ['\x01\x00\x00\x00\x08'] + self.assertEqual(exp, self.cnx._socket.sock._client_sends) + + self.cnx._socket.sock.reset() + self.assertRaises(errors.InterfaceError, + self.cnx.cmd_shutdown, 99) + + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(EOF_PACKET) + self.cnx.cmd_shutdown( + constants.ShutdownType.SHUTDOWN_WAIT_CONNECTIONS) + exp = ['\x02\x00\x00\x00\x08\x01'] + self.assertEqual(exp, self.cnx._socket.sock._client_sends) + + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet( + '\x4a\x00\x00\x01\xff\xcb\x04\x23\x34\x32\x30\x30'\ + '\x30\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69'\ + '\x65\x64\x3b\x20\x79\x6f\x75\x20\x6e\x65\x65\x64'\ + '\x20\x74\x68\x65\x20\x53\x48\x55\x54\x44\x4f\x57'\ + '\x4e\x20\x70\x72\x69\x76\x69\x6c\x65\x67\x65\x20'\ + '\x66\x6f\x72\x20\x74\x68\x69\x73\x20\x6f\x70\x65'\ + '\x72\x61\x74\x69\x6f\x6e' ) - result = [] - self.cnx.send(req,0) - buf = self.cnx.recv() - while buf: - result.append(buf) - if len(result) == len(exp): - self.cnx.send('\x01',0) - buf = self.cnx.recv() - - for i,expdata in enumerate(exp): - self.assertEqual(expdata, result[i]) - - def test_set_connection_timeout(self): - """Set the connection timeout""" - exp = 5 - self.cnx.set_connection_timeout(exp) - self.assertEqual(exp, self.cnx.connection_timeout) - -class MySQLUnixSocketTests(tests.MySQLConnectorTests): + self.assertRaises(errors.ProgrammingError, self.cnx.cmd_shutdown) - def setUp(self): + def test_cmd_statistics(self): + """Send the Statistics-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + goodpkt = '\x88\x00\x00\x01\x55\x70\x74\x69\x6d\x65\x3a\x20'\ + '\x31\x34\x36\x32\x34\x35\x20\x20\x54\x68\x72\x65'\ + '\x61\x64\x73\x3a\x20\x32\x20\x20\x51\x75\x65\x73'\ + '\x74\x69\x6f\x6e\x73\x3a\x20\x33\x36\x33\x35\x20'\ + '\x20\x53\x6c\x6f\x77\x20\x71\x75\x65\x72\x69\x65'\ + '\x73\x3a\x20\x30\x20\x20\x4f\x70\x65\x6e\x73\x3a'\ + '\x20\x33\x39\x32\x20\x20\x46\x6c\x75\x73\x68\x20'\ + '\x74\x61\x62\x6c\x65\x73\x3a\x20\x31\x20\x20\x4f'\ + '\x70\x65\x6e\x20\x74\x61\x62\x6c\x65\x73\x3a\x20'\ + '\x36\x34\x20\x20\x51\x75\x65\x72\x69\x65\x73\x20'\ + '\x70\x65\x72\x20\x73\x65\x63\x6f\x6e\x64\x20\x61'\ + '\x76\x67\x3a\x20\x30\x2e\x32\x34' + self.cnx._socket.sock.add_packet(goodpkt) + exp = { + 'Uptime': 146245L, + 'Open tables': 64L, + 'Queries per second avg': Decimal('0.24'), + 'Slow queries': 0L, + 'Threads': 2L, + 'Questions': 3635L, + 'Flush tables': 1L, + 'Opens': 392L + } + self.assertEqual(exp, self.cnx.cmd_statistics()) + + badpkt = '\x88\x00\x00\x01\x55\x70\x74\x69\x6d\x65\x3a\x20'\ + '\x31\x34\x36\x32\x34\x35\x20\x54\x68\x72\x65'\ + '\x61\x64\x73\x3a\x20\x32\x20\x20\x51\x75\x65\x73'\ + '\x74\x69\x6f\x6e\x73\x3a\x20\x33\x36\x33\x35\x20'\ + '\x20\x53\x6c\x6f\x77\x20\x71\x75\x65\x72\x69\x65'\ + '\x73\x3a\x20\x30\x20\x20\x4f\x70\x65\x6e\x73\x3a'\ + '\x20\x33\x39\x32\x20\x20\x46\x6c\x75\x73\x68\x20'\ + '\x74\x61\x62\x6c\x65\x73\x3a\x20\x31\x20\x20\x4f'\ + '\x70\x65\x6e\x20\x74\x61\x62\x6c\x65\x73\x3a\x20'\ + '\x36\x34\x20\x20\x51\x75\x65\x72\x69\x65\x73\x20'\ + '\x70\x65\x72\x20\x73\x65\x63\x6f\x6e\x64\x20\x61'\ + '\x76\x67\x3a\x20\x30\x2e\x32\x34' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(badpkt) + self.assertRaises(errors.InterfaceError, self.cnx.cmd_statistics) + + badpkt = '\x88\x00\x00\x01\x55\x70\x74\x69\x6d\x65\x3a\x20'\ + '\x55\x70\x36\x32\x34\x35\x20\x20\x54\x68\x72\x65'\ + '\x61\x64\x73\x3a\x20\x32\x20\x20\x51\x75\x65\x73'\ + '\x74\x69\x6f\x6e\x73\x3a\x20\x33\x36\x33\x35\x20'\ + '\x20\x53\x6c\x6f\x77\x20\x71\x75\x65\x72\x69\x65'\ + '\x73\x3a\x20\x30\x20\x20\x4f\x70\x65\x6e\x73\x3a'\ + '\x20\x33\x39\x32\x20\x20\x46\x6c\x75\x73\x68\x20'\ + '\x74\x61\x62\x6c\x65\x73\x3a\x20\x31\x20\x20\x4f'\ + '\x70\x65\x6e\x20\x74\x61\x62\x6c\x65\x73\x3a\x20'\ + '\x36\x34\x20\x20\x51\x75\x65\x72\x69\x65\x73\x20'\ + '\x70\x65\x72\x20\x73\x65\x63\x6f\x6e\x64\x20\x61'\ + '\x76\x67\x3a\x20\x30\x2e\x32\x34' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(badpkt) + self.assertRaises(errors.InterfaceError, self.cnx.cmd_statistics) + + def test_cmd_process_info(self): + """Send the Process-Info-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.assertRaises(errors.NotSupportedError, self.cnx.cmd_process_info) + + def test_cmd_process_kill(self): + """Send the Process-Kill-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(OK_PACKET) + self.assertEqual(OK_PACKET_RESULT, self.cnx.cmd_process_kill(1)) + + pkt = '\x1f\x00\x00\x01\xff\x46\x04\x23\x48\x59\x30\x30'\ + '\x30\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x74\x68\x72'\ + '\x65\x61\x64\x20\x69\x64\x3a\x20\x31\x30\x30' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(pkt) + self.assertRaises(errors.DatabaseError, + self.cnx.cmd_process_kill, 100) + + pkt = '\x29\x00\x00\x01\xff\x47\x04\x23\x48\x59\x30\x30'\ + '\x30\x59\x6f\x75\x20\x61\x72\x65\x20\x6e\x6f\x74'\ + '\x20\x6f\x77\x6e\x65\x72\x20\x6f\x66\x20\x74\x68'\ + '\x72\x65\x61\x64\x20\x31\x36\x30\x35' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(pkt) + self.assertRaises(errors.DatabaseError, + self.cnx.cmd_process_kill, 1605) + + def test_cmd_debug(self): + """Send the Debug-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + pkt = '\x05\x00\x00\x01\xfe\x00\x00\x00\x00' + self.cnx._socket.sock.add_packet(pkt) + exp = { + 'status_flag': 0, + 'warning_count': 0 + } + self.assertEqual(exp, self.cnx.cmd_debug()) + + pkt = '\x47\x00\x00\x01\xff\xcb\x04\x23\x34\x32\x30\x30'\ + '\x30\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69'\ + '\x65\x64\x3b\x20\x79\x6f\x75\x20\x6e\x65\x65\x64'\ + '\x20\x74\x68\x65\x20\x53\x55\x50\x45\x52\x20\x70'\ + '\x72\x69\x76\x69\x6c\x65\x67\x65\x20\x66\x6f\x72'\ + '\x20\x74\x68\x69\x73\x20\x6f\x70\x65\x72\x61\x74'\ + '\x69\x6f\x6e' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(pkt) + self.assertRaises(errors.ProgrammingError, self.cnx.cmd_debug) + + def test_cmd_ping(self): + """Send the Ping-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._socket.sock.add_packet(OK_PACKET) + self.assertEqual(OK_PACKET_RESULT, self.cnx.cmd_ping()) + + self.assertRaises(errors.Error, self.cnx.cmd_ping) + + def test_cmd_change_user(self): + """Send the Change-User-command to MySQL""" + self.cnx._socket.sock = tests.DummySocket() + self.cnx._handshake = { + 'protocol': 10, + 'server_version_original': '5.0.30-enterprise-gpl-log', + 'charset': 8, + 'server_threadid': 265, + 'capabilities': 41516, + 'server_status': 2, + 'scramble': 'h4i6oP!OLng9&PD@WrYH' + } + + self.cnx._socket.sock.add_packet(OK_PACKET) + exp = OK_PACKET_RESULT.copy() + exp['server_status'] = 2 + self.cnx.cmd_change_user(username='ham', password='spam', + database='python') + + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet( + '\x45\x00\x00\x01\xff\x14\x04\x23\x34\x32\x30\x30'\ + '\x30\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69'\ + '\x65\x64\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20'\ + '\x27\x68\x61\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c'\ + '\x68\x6f\x73\x74\x27\x20\x74\x6f\x20\x64\x61\x74'\ + '\x61\x62\x61\x73\x65\x20\x27\x6d\x79\x73\x71\x6c'\ + '\x27') + self.assertRaises(errors.ProgrammingError, self.cnx.cmd_change_user, + username='ham', password='spam', database='mysql') + + def test__do_handshake(self): + """Handle the handshake-packet sent by MySQL""" + self.cnx._socket.sock = tests.DummySocket() + handshake = \ + '\x47\x00\x00\x00\x0a\x35\x2e\x30\x2e\x33\x30\x2d'\ + '\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65\x2d\x67'\ + '\x70\x6c\x2d\x6c\x6f\x67\x00\x09\x01\x00\x00\x68'\ + '\x34\x69\x36\x6f\x50\x21\x4f\x00\x2c\xa2\x08\x02'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72'\ + '\x59\x48\x00' + exp = { + 'protocol': 10, + 'server_version_original': '5.0.30-enterprise-gpl-log', + 'charset': 8, + 'server_threadid': 265, + 'capabilities': 41516, + 'server_status': 2, + 'scramble': 'h4i6oP!OLng9&PD@WrYH' + } + + self.cnx._socket.sock.add_packet(handshake) + self.cnx._do_handshake() + self.assertEqual(exp, self.cnx._handshake) + + self.assertRaises(errors.InterfaceError, self.cnx._do_handshake) + + # Handshake with version set to Z.Z.ZZ to simulate bad version + false_handshake = \ + '\x47\x00\x00\x00\x0a\x5a\x2e\x5a\x2e\x5a\x5a\x2d'\ + '\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65\x2d\x67'\ + '\x70\x6c\x2d\x6c\x6f\x67\x00\x09\x01\x00\x00\x68'\ + '\x34\x69\x36\x6f\x50\x21\x4f\x00\x2c\xa2\x08\x02'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72'\ + '\x59\x48\x00' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(false_handshake) + self.assertRaises(errors.InterfaceError, self.cnx._do_handshake) + + # Handshake with version set to 4.0.23 + unsupported_handshake = \ + '\x47\x00\x00\x00\x0a\x34\x2e\x30\x2e\x32\x33\x2d'\ + '\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65\x2d\x67'\ + '\x70\x6c\x2d\x6c\x6f\x67\x00\x09\x01\x00\x00\x68'\ + '\x34\x69\x36\x6f\x50\x21\x4f\x00\x2c\xa2\x08\x02'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72'\ + '\x59\x48\x00' + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(unsupported_handshake) + self.assertRaises(errors.InterfaceError, self.cnx._do_handshake) + + def test__do_auth(self): + """Authenticate with the MySQL server""" + self.cnx._socket.sock = tests.DummySocket() + flags = constants.ClientFlag.get_default() + kwargs = { + 'username': 'ham', + 'password': 'spam', + 'database': 'test', + 'charset': 33, + 'client_flags': flags, + } + + self.cnx._socket.sock.add_packet(OK_PACKET) + self.assertEqual(True, self.cnx._do_auth(**kwargs)) + + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet('\x01\x00\x00\x02\xfe') + self.assertRaises(errors.NotSupportedError, + self.cnx._do_auth, **kwargs) + + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packets([ + '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00', + '\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00']) + self.cnx.set_client_flags([-constants.ClientFlag.CONNECT_WITH_DB]) + self.assertEqual(True, self.cnx._do_auth(**kwargs)) + + # Using an unknown database should raise an error + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packets([ + '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00', + '\x24\x00\x00\x01\xff\x19\x04\x23\x34\x32\x30\x30'\ + '\x30\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x64\x61\x74'\ + '\x61\x62\x61\x73\x65\x20\x27\x61\x73\x64\x66\x61'\ + '\x73\x64\x66\x27']) + flags &= ~constants.ClientFlag.CONNECT_WITH_DB + kwargs['client_flags'] = flags + self.assertRaises(errors.ProgrammingError, + self.cnx._do_auth, **kwargs) + + def test__do_auth_ssl(self): + """Authenticate with the MySQL server""" + if not tests.SSL_AVAILABLE: + return + self.cnx._socket.sock = tests.DummySocket() + flags = constants.ClientFlag.get_default() + flags |= constants.ClientFlag.SSL + kwargs = { + 'username': 'ham', + 'password': 'spam', + 'database': 'test', + 'charset': 33, + 'client_flags': flags, + 'ssl_options': { + 'ca': os.path.join(tests.SSL_DIR, 'tests_CA_cert.pem'), + 'cert': os.path.join(tests.SSL_DIR, 'tests_client_cert.pem'), + 'key': os.path.join(tests.SSL_DIR, 'tests_client_key.pem'), + }, + } + + self.cnx._handshake['scramble'] = 'h4i6oP!OLng9&PD@WrYH' + + # We check if do_auth send the autherization for SSL and the + # normal authorization. + exp = [ + self.cnx._protocol.make_auth_ssl( + charset=kwargs['charset'], + client_flags=kwargs['client_flags']), + self.cnx._protocol.make_auth( + self.cnx._handshake['scramble'], kwargs['username'], + kwargs['password'], kwargs['database'], + charset=kwargs['charset'], + client_flags=kwargs['client_flags']), + ] + self.cnx._socket.switch_to_ssl = lambda ca,cert,key: None + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packets([ + '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00', + '\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00']) + self.cnx._do_auth(**kwargs) + self.assertEqual( + exp, [ p[4:] for p in self.cnx._socket.sock._client_sends]) + + def test_config(self): + """Configure the MySQL connection + + These tests do not actually connect to MySQL, but they make + sure everything is setup before calling _open_connection() and + _post_connection(). + """ + cnx = _DummyMySQLConnection() + default_config = connection.DEFAULT_CONFIGURATION.copy() + + # Should fail because 'dsn' is given + self.assertRaises(errors.NotSupportedError, cnx.config, + **default_config) + + # Remove unsupported arguments + del default_config['dsn'] + default_config.update({ + 'ssl_ca': 'CACert', + 'ssl_cert': 'ServerCert', + 'ssl_key': 'ServerKey', + 'ssl_verify_cert': False + }) + try: + cnx.config(**default_config) + except AttributeError, e: + self.fail("Config does not accept a supported argument: %s" %\ + str(e)) + + # Add an argument which we don't allow + default_config['spam'] = 'SPAM' + self.assertRaises(AttributeError, cnx.config, **default_config) + + # We do not support dsn + self.assertRaises(errors.NotSupportedError, cnx.connect, dsn='ham') + + exp = { + 'host': 'localhost.local', + 'port': 3306, + 'unix_socket': '/tmp/mysql.sock' + } + cnx.config(**exp) + self.assertEqual(exp['port'], cnx._port) + self.assertEqual(exp['host'], cnx._host) + self.assertEqual(exp['unix_socket'], cnx._unix_socket) + + exp = (None, 'test', 'mysql ') + for database in exp: + cnx.config(database=database) + if database is not None: + database = database.strip() + failmsg = "Failed setting database to '%s'" % database + self.assertEqual(database, cnx._database, msg=failmsg) + cnx.config(user='ham') + self.assertEqual('ham', cnx._user) + + cnx.config(raise_on_warnings=True) + self.assertEqual(True, cnx._raise_on_warnings) + cnx.config(get_warnings=False) + self.assertEqual(False, cnx._get_warnings) + cnx.config(connection_timeout=123) + self.assertEqual(123, cnx._connection_timeout) + for toggle in [True,False]: + cnx.config(buffered=toggle) + self.assertEqual(toggle, cnx._buffered) + cnx.config(raw=toggle) + self.assertEqual(toggle, cnx._raw) + for toggle in [False,True]: + cnx.config(use_unicode=toggle) + self.assertEqual(toggle, cnx._use_unicode) + + # Test client flags + cnx = _DummyMySQLConnection() + cnx.client_flags = constants.ClientFlag.get_default() + flag = exp = constants.ClientFlag.COMPRESS + cnx.config(client_flags=flag) + self.assertEqual(exp, cnx._client_flags) + + # Setting client flags using a list + cnx = _DummyMySQLConnection() + cnx.client_flags = constants.ClientFlag.get_default() + flags = [constants.ClientFlag.COMPRESS, + constants.ClientFlag.FOUND_ROWS] + exp = constants.ClientFlag.get_default() + for flag in flags: + exp |= flag + cnx.config(client_flags=flags) + self.assertEqual(exp, cnx._client_flags) + + # and unsetting client flags again + exp = constants.ClientFlag.get_default() + flags = [-constants.ClientFlag.COMPRESS, + -constants.ClientFlag.FOUND_ROWS] + cnx.config(client_flags=flags) + self.assertEqual(exp, cnx._client_flags) + + # Test character set + # utf8 is default, which is mapped to 33 + self.assertEqual(33, cnx._charset_id) + cnx.config(charset='latin1') + self.assertEqual(8, cnx._charset_id) + cnx.config(charset='latin1', collation='latin1_general_ci') + self.assertEqual(48, cnx._charset_id) + cnx.config(collation='latin1_general_ci') + self.assertEqual(48, cnx._charset_id) + + # Test SSL configuration + exp = { + 'ca': 'CACert', + 'cert': 'ServerCert', + 'key': 'ServerKey', + 'verify_cert': False + } + cnx.config(ssl_ca=exp['ca'], ssl_cert=exp['cert'], + ssl_key=exp['key']) + self.assertEqual(exp, cnx._ssl) + + exp['verify_cert'] = True + + cnx.config(ssl_ca=exp['ca'], ssl_cert=exp['cert'], + ssl_key=exp['key'], ssl_verify_cert=exp['verify_cert']) + self.assertEqual(exp, cnx._ssl) + + # Missing SSL configuration should raise an AttributeError + cnx._ssl = {} + try: + cnx.config(ssl_ca=exp['ca']) + except AttributeError, err: + errmsg = str(err) + self.assertTrue('ssl_cert' in errmsg and 'ssl_key' in errmsg) + else: + self.fail("Testing SSL attributes failed.") + + cnx._ssl = {} + try: + cnx.config(ssl_cert=exp['cert']) + except AttributeError, err: + errmsg = str(err) + self.assertTrue('ssl_key' in errmsg) + else: + self.fail("Testing SSL attributes failed.") + + cnx._ssl = {} + try: + cnx.config(ssl_verify_cert=True) + except AttributeError, err: + errmsg = str(err) + self.assertTrue('ssl_key' in errmsg and 'ssl_cert' in errmsg + and 'ssl_ca' in errmsg) + else: + self.fail("Testing SSL attributes failed.") + + + # Compatibility tests: MySQLdb + cnx = _DummyMySQLConnection() + cnx.connect(db='mysql', passwd='spam', connect_timeout=123) + self.assertEqual('mysql', cnx._database) + self.assertEqual('spam', cnx._password) + self.assertEqual(123, cnx._connection_timeout) + + def test__get_connection(self): + """Get connection based on configuration""" + if os.name != 'nt': + res = self.cnx._get_connection() + self.assertTrue(isinstance(res, network.MySQLUnixSocket)) + + self.cnx._unix_socket = None + self.cnx._connection_timeout = 123 + res = self.cnx._get_connection() + self.assertTrue(isinstance(res, network.MySQLTCPSocket)) + self.assertEqual(self.cnx._connection_timeout, + res._connection_timeout) + + def test__open_connection(self): + """Open the connection to the MySQL server""" + # Force TCP Connection + self.cnx._unix_socket = None + flags = constants.ClientFlag.get_default() + self.cnx._open_connection() + self.assertTrue(isinstance(self.cnx._socket, + network.MySQLTCPSocket)) + self.cnx.close() + + self.cnx._client_flags |= constants.ClientFlag.COMPRESS + self.cnx._open_connection() + self.assertEqual(self.cnx._socket.recv_compressed, + self.cnx._socket.recv) + self.assertEqual(self.cnx._socket.send_compressed, + self.cnx._socket.send) + + def test__post_connection(self): + """Executes commands after connection has been established""" + self.cnx._charset_id = 33 + self.cnx._autocommit = True + self.cnx._time_zone = "-09:00" + self.cnx._sql_mode = "NO_ZERO_DATE" + self.cnx._post_connection() + self.assertEqual('utf8', self.cnx.charset) + self.assertEqual(self.cnx._autocommit, self.cnx.autocommit) + self.assertEqual(self.cnx._time_zone, self.cnx.time_zone) + self.assertEqual(self.cnx._sql_mode, self.cnx.sql_mode) + + def test_connect(self): + """Connect to the MySQL server""" config = self.getMySQLConfig() - self._unix_socket = config['unix_socket'] - self.cnx = connection.MySQLUnixSocket( - unix_socket=config['unix_socket']) + config['unix_socket'] = None + config['connection_timeout'] = 3 + + cnx = connection.MySQLConnection() + config['host'] = tests.MYSQL_CONFIG['host'] + try: + cnx.connect(**config) + cnx.close() + except StandardError, e: + self.fail("Failed connecting to '%s': %s" % ( + tests.MYSQL_CONFIG['host'], str(e))) + + config['host'] = self.getFakeHostname() + self.assertRaises(errors.InterfaceError, cnx.connect, **config) - def tearDown(self): + def test_is_connected(self): + """Check connection to MySQL Server""" + self.assertEqual(True, self.cnx.is_connected()) + self.cnx.disconnect() + self.assertEqual(False, self.cnx.is_connected()) + + def test_reconnect(self): + """Reconnecting to the MySQL Server""" + supported_arguments = { + 'attempts': 1, + 'delay': 0, + } + self.checkArguments(self.cnx.reconnect, supported_arguments) + + _test_reconnect_delay = """ + from mysql.connector import connection + config = { + 'unix_socket': None, + 'host': 'some-fake-hostname.on.mars', + 'connection_timeout': 1, + } + cnx = connection.MySQLConnection() + cnx.config(**config) + try: + cnx.reconnect(attempts=2, delay=3) + except: + pass + """ + + # Check the delay + timer = timeit.Timer(_test_reconnect_delay) + result = timer.timeit(number=1) + self.assertTrue(result > 2 and result < 12, + "2 <= result < 12, was %d" % result) + + def test_ping(self): + """Ping the MySQL server""" + supported_arguments = { + 'reconnect': False, + 'attempts': 1, + 'delay': 0, + } + self.checkArguments(self.cnx.ping, supported_arguments) + try: - self.cnx.close_connection() - except: - pass + self.cnx.ping() + except errors.InterfaceError, e: + self.fail("Ping should have not raised an error") + + self.cnx.disconnect() + self.assertRaises(errors.InterfaceError, self.cnx.ping) - def test_init(self): - """MySQLUnixSocket initialization""" - exp = dict( - unix_socket=self._unix_socket, - sock = None, - connection_timeout = None, - buffer = deque(), - recvsize = 1024*8, - ) + def test_set_converter_class(self): + """Set the converter class""" + class TestConverter(object): + def __init__(self, charset, unicode): + pass - for k,v in exp.items(): - self.assertEqual(v, self.cnx.__dict__[k]) + self.cnx.set_converter_class(TestConverter) + self.assertTrue(isinstance(self.cnx.converter, TestConverter)) + self.assertEqual(self.cnx._converter_class, TestConverter) - def test_get_address(self): - """Get path to the Unix socket""" - exp = self._unix_socket - self.assertEqual(exp, self.cnx.get_address()) + def test_get_server_version(self): + """Get the MySQL version""" + self.assertEqual(self.cnx._server_version, + self.cnx.get_server_version()) - def test_open_connection(self): - """Open a connection using a Unix socket""" + def test_get_server_info(self): + """Get the original MySQL version information""" + self.assertEqual(self.cnx._handshake['server_version_original'], + self.cnx.get_server_info()) + + del self.cnx._handshake['server_version_original'] + self.assertEqual(None, self.cnx.get_server_info()) + + def test_connection_id(self): + """MySQL connection ID""" + self.assertEqual(self.cnx._handshake['server_threadid'], + self.cnx.connection_id) + + del self.cnx._handshake['server_threadid'] + self.assertEqual(None, self.cnx.connection_id) + + def test_set_login(self): + """Set login information for MySQL""" + exp = ('Ham ', ' Spam ') + self.cnx.set_login(*exp) + self.assertEqual(exp[0].strip(), self.cnx._user) + self.assertEqual(exp[1].strip(), self.cnx._password) + + self.cnx.set_login() + self.assertEqual('', self.cnx._user) + self.assertEqual('', self.cnx._password) + + def test__set_unread_result(self): + """Toggle unread result set""" + self.cnx._set_unread_result(True) + self.assertEqual(True, self.cnx._unread_result) + self.cnx._set_unread_result(False) + self.assertEqual(False, self.cnx._unread_result) + self.assertRaises(ValueError, self.cnx._set_unread_result, 1) + + def test__get_unread_result(self): + """Check for unread result set""" + self.cnx._unread_result = True + self.assertEqual(True, self.cnx._get_unread_result()) + self.cnx._unread_result = False + self.assertEqual(False, self.cnx._get_unread_result()) + + def test_unread_results(self): + """Check and toggle unread result using property""" + self.cnx.unread_result = True + self.assertEqual(True, self.cnx._unread_result) + self.cnx.unread_result = False + self.assertEqual(False, self.cnx._unread_result) + try: - self.cnx.open_connection() + self.cnx.unread_result = 1 + except ValueError: + pass # Expected except: - self.fail() + self.fail("Expected ValueError to be raised") -class MySQLTCPSocketTests(tests.MySQLConnectorTests): + def test__set_getwarnings(self): + """Toggle whether to get warnings""" + self.cnx._set_getwarnings(True) + self.assertEqual(True, self.cnx._get_warnings) + self.cnx._set_getwarnings(False) + self.assertEqual(False, self.cnx._get_warnings) + self.assertRaises(ValueError, self.cnx._set_getwarnings, 1) - def setUp(self): - config = self.getMySQLConfig() - self._host = config['host'] - self._port = config['port'] - self.cnx = connection.MySQLTCPSocket( - host=self._host, port=self._port) + def test__get_getwarnings(self): + """Check whether we need to get warnings""" + self.cnx._get_warnings = True + self.assertEqual(True, self.cnx._get_getwarnings()) + self.cnx._get_warnings = False + self.assertEqual(False, self.cnx._get_getwarnings()) - def tearDown(self): + def test_get_warnings(self): + """Check and toggle the get_warnings property""" + self.cnx.get_warnings = True + self.assertEqual(True, self.cnx._get_warnings) + self.cnx.get_warnings = False + self.assertEqual(False, self.cnx._get_warnings) + try: - self.cnx.close_connection() + self.cnx.get_warnings = 1 + except ValueError: + pass # Expected except: - pass + self.fail("Expected ValueError to be raised") + + def test_set_charset_collation(self): + """Set the characater set and collation""" + self.cnx.set_charset_collation('latin1') + self.assertEqual(8, self.cnx._charset_id) + self.cnx.set_charset_collation('latin1', 'latin1_general_ci') + self.assertEqual(48, self.cnx._charset_id) + self.cnx.set_charset_collation('latin1', None) + self.assertEqual(8, self.cnx._charset_id) + + self.cnx.set_charset_collation(collation='greek_bin') + self.assertEqual(70, self.cnx._charset_id) + + self.assertRaises(errors.ProgrammingError, + self.cnx.set_charset_collation, 666) + self.assertRaises(errors.ProgrammingError, + self.cnx.set_charset_collation, 'spam') + self.assertRaises(errors.ProgrammingError, + self.cnx.set_charset_collation, 'latin1', 'spam') + self.assertRaises(errors.ProgrammingError, + self.cnx.set_charset_collation, None, 'spam') + self.assertRaises(ValueError, + self.cnx.set_charset_collation, object()) + + def test_charset(self): + """Get characater set name""" + self.cnx.set_charset_collation('latin1', 'latin1_general_ci') + self.assertEqual('latin1', self.cnx.charset) + self.cnx._charset_id = 70 + self.assertEqual('greek', self.cnx.charset) + self.cnx._charset_id = 9 + self.assertEqual('latin2', self.cnx.charset) + self.cnx._charset_id = 1234567 + try: + self.cnx.charset + except errors.ProgrammingError: + pass # This is expected + except: + self.fail("Expected errors.ProgrammingError to be raised") + + def test_collation(self): + """Get collation name""" + exp = 'latin2_general_ci' + self.cnx.set_charset_collation(collation=exp) + self.assertEqual(exp, self.cnx.collation) + self.cnx._charset_id = 70 + self.assertEqual('greek_bin', self.cnx.collation) + self.cnx._charset_id = 9 + self.assertEqual('latin2_general_ci', self.cnx.collation) + + self.cnx._charset_id = 1234567 + try: + self.cnx.collation + except errors.ProgrammingError: + pass # This is expected + except: + self.fail("Expected errors.ProgrammingError to be raised") + + def test_set_client_flags(self): + """Set the client flags""" + self.assertRaises(errors.ProgrammingError, + self.cnx.set_client_flags, 'Spam') + self.assertRaises(errors.ProgrammingError, + self.cnx.set_client_flags, 0) - def test_init(self): - """MySQLTCPSocket initialization""" - exp = dict( - server_host=self._host, - server_port=self._port, - sock = None, - connection_timeout = None, - buffer = deque(), - recvsize = 1024*8, - ) + default_flags = constants.ClientFlag.get_default() + + exp = default_flags + self.assertEqual(exp, self.cnx.set_client_flags(exp)) + self.assertEqual(exp, self.cnx._client_flags) + + exp = default_flags + exp |= constants.ClientFlag.SSL + exp |= constants.ClientFlag.FOUND_ROWS + exp &= ~constants.ClientFlag.MULTI_RESULTS + flags = [ + constants.ClientFlag.SSL, + constants.ClientFlag.FOUND_ROWS, + -constants.ClientFlag.MULTI_RESULTS + ] + self.assertEqual(exp, self.cnx.set_client_flags(flags)) + self.assertEqual(exp, self.cnx._client_flags) + + def test_isset_client_flag(self): + """Check if a client flag is set""" + default_flags = constants.ClientFlag.get_default() + self.cnx._client_flags = default_flags + cases = [ + (constants.ClientFlag.LONG_PASSWD, True), + (constants.ClientFlag.SSL, False) + ] + for flag, exp in cases: + self.assertEqual(exp, self.cnx.isset_client_flag(flag)) + + def test_user(self): + exp = 'ham' + self.cnx._user = exp + self.assertEqual(exp, self.cnx.user) + + def test_host(self): + exp = 'ham' + self.cnx._host = exp + self.assertEqual(exp, self.cnx.server_host) + + def test_port(self): + exp = 'ham' + self.cnx._port = exp + self.assertEqual(exp, self.cnx.server_port) + + def test_unix_socket(self): + exp = 'ham' + self.cnx._unix_socket = exp + self.assertEqual(exp, self.cnx.unix_socket) + + def test_set_database(self): + exp = 'mysql' + self.cnx.set_database(exp) + self.assertEqual(exp, self.cnx._info_query("SELECT DATABASE()")[0]) + + def test_get_database(self): + exp = 'mysql' + exp = self.cnx._info_query("SELECT DATABASE()")[0] + self.assertEqual(exp, self.cnx.get_database()) + + def test_database(self): + exp = 'mysql' + self.cnx.database = exp + self.assertEqual(exp, self.cnx.database) - for k,v in exp.items(): - self.assertEqual(v, self.cnx.__dict__[k]) + def test_set_time_zone(self): + """Set the time zone""" + cnx = _DummyMySQLConnection() + exp = "-09:00" + cnx.connect(time_zone=exp) + self.assertEqual(exp,cnx._time_zone) + + exp = "+03:00" + self.cnx.set_time_zone(exp) + self.assertEqual(exp,self.cnx._time_zone) + self.assertEqual(exp,self.cnx._info_query( + "SELECT @@session.time_zone")[0]) + + def test_get_time_zone(self): + """Get the time zone from current MySQL Session""" + exp = "-08:00" + self.cnx._info_query("SET @@session.time_zone = '%s'" % exp) + self.assertEqual(exp,self.cnx.get_time_zone()) - def test_get_address(self): - """Get TCP/IP address""" - exp = "%s:%s" % (self._host,self._port) - self.assertEqual(exp, self.cnx.get_address()) + def test_time_zone(self): + """Set and get the time zone through property""" + exp = "+05:00" + self.cnx.time_zone = exp + self.assertEqual(exp,self.cnx._time_zone) + self.assertEqual(exp,self.cnx.time_zone) + self.assertEqual(self.cnx.get_time_zone(),self.cnx.time_zone) - def test_open_connection(self): - """Open a connection using TCP""" + def test_set_sql_mode(self): + """Set SQL Mode""" + # Use an unknown SQL Mode + self.assertRaises(errors.ProgrammingError,self.cnx.set_sql_mode, 'HAM') + + # Set an SQL Mode try: - self.cnx.open_connection() + self.cnx.set_sql_mode('TRADITIONAL') + except Exception, e: + self.fail("Failed setting SQL Mode") + + # Set SQL Mode to a list of modes + sql_mode = 'TRADITIONAL' + exp = ('STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,' + 'NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,' + 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION') + try: + self.cnx.set_sql_mode(exp) + result = self.cnx.get_sql_mode() + except Exception, e: + self.fail("Failed setting SQL Mode with multiple modes") + self.assertEqual(exp,result) + + exp = sorted([ + constants.SQLMode.NO_ZERO_DATE, + constants.SQLMode.REAL_AS_FLOAT + ]) + self.cnx.sql_mode = exp + self.assertEqual(exp, sorted(self.cnx.sql_mode.split(','))) + + def test_get_sql_mode(self): + """Get SQL Mode""" + config = self.getMySQLConfig() + config['sql_mode'] = '' + self.cnx = connection.MySQLConnection(**config) + + # SQL Modes must be empty + self.assertEqual('',self.cnx.get_sql_mode()) + + # Set SQL Mode and check + sql_mode = exp = 'NO_ZERO_IN_DATE' + self.cnx.set_sql_mode(sql_mode) + self.assertEqual(exp,self.cnx.get_sql_mode()) + + # Unset the SQL Mode again + self.cnx.set_sql_mode('') + self.assertEqual('',self.cnx.get_sql_mode()) + + def test_sql_mode(self): + """Set and get SQL Mode through property""" + config = self.getMySQLConfig() + config['sql_mode'] = '' + self.cnx = connection.MySQLConnection(**config) + + sql_mode = exp = 'NO_ZERO_IN_DATE' + self.cnx.sql_mode = sql_mode + self.assertEqual(exp,self.cnx.sql_mode) + + def test_set_autocommit(self): + self.cnx.set_autocommit(True) + res = self.cnx._info_query("SELECT @@session.autocommit")[0] + self.assertEqual(1, res) + self.cnx.set_autocommit(0) + res = self.cnx._info_query("SELECT @@session.autocommit")[0] + self.assertEqual(0, res) + + def test_get_autocommit(self): + cases = [False, True] + for exp in cases: + self.cnx.set_autocommit(exp) + res = self.cnx._info_query("SELECT @@session.autocommit")[0] + self.assertEqual(exp, cases[res]) + + def test_autocommit(self): + for exp in [False, True]: + self.cnx.autocommit = exp + self.assertEqual(exp, self.cnx.autocommit) + + def test__set_raise_on_warnings(self): + """Toggle whether to get warnings""" + self.cnx._set_raise_on_warnings(True) + self.assertEqual(True, self.cnx._raise_on_warnings) + self.cnx._set_raise_on_warnings(False) + self.assertEqual(False, self.cnx._raise_on_warnings) + self.assertRaises(ValueError, self.cnx._set_raise_on_warnings, 1) + + def test__get_raise_on_warnings(self): + """Check whether we need to get warnings""" + self.cnx._raise_on_warnings = True + self.assertEqual(True, self.cnx._get_raise_on_warnings()) + self.cnx._raise_on_warnings = False + self.assertEqual(False, self.cnx._get_raise_on_warnings()) + + def test_raise_on_warnings(self): + """Check and toggle the get_warnings property""" + self.cnx.raise_on_warnings = True + self.assertEqual(True, self.cnx._get_raise_on_warnings()) + self.cnx.raise_on_warnings = False + self.assertEqual(False, self.cnx._get_raise_on_warnings()) + + try: + self.cnx.get_warnings = 1 + except ValueError: + pass # Expected except: - self.fail() - \ No newline at end of file + self.fail("Expected ValueError to be raised") + + def test_cursor(self): + class FalseCursor(object): + pass + + class TrueCursor(cursor.CursorBase): + def __init__(self, cnx): + pass + + self.assertRaises(errors.ProgrammingError, self.cnx.cursor, + cursor_class=FalseCursor) + self.assertTrue(isinstance(self.cnx.cursor(cursor_class=TrueCursor), + TrueCursor)) + + cases = [ + ({}, cursor.MySQLCursor), + ({'buffered': True}, cursor.MySQLCursorBuffered), + ({'raw': True}, cursor.MySQLCursorRaw), + ({'buffered': True, 'raw': True}, cursor.MySQLCursorBufferedRaw), + ] + for kwargs, exp in cases: + self.assertTrue(isinstance(self.cnx.cursor(**kwargs),exp)) + + # Test when connection is closed + self.cnx.close() + self.assertRaises(errors.OperationalError, self.cnx.cursor) + + def test__send_data(self): + self.assertRaises(ValueError, self.cnx._send_data, 'spam') + + self.cnx._socket.sock = tests.DummySocket() + + data = [ + "1\tham\t'ham spam'", + "2\tfoo\t'foo bar'" + ] + csv_data = '\n'.join(data) + + fp = StringIO.StringIO(csv_data) + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(OK_PACKET) + exp = [ + "\x20\x00\x00\x02\x31\x09\x68\x61\x6d\x09\x27\x68\x61\x6d" + "\x20\x73\x70\x61\x6d\x27\x0a\x32\x09\x66\x6f\x6f\x09\x27" + "\x66\x6f\x6f\x20\x62\x61\x72\x27" + ] + + self.assertEqual(OK_PACKET, self.cnx._send_data(fp, False)) + self.assertEqual(exp, self.cnx._socket.sock._client_sends) + + fp = StringIO.StringIO(csv_data) + self.cnx._socket.sock.reset() + self.cnx._socket.sock.add_packet(OK_PACKET) + exp.append('\x00\x00\x00\x03') + self.assertEqual(OK_PACKET, self.cnx._send_data(fp, True)) + self.assertEqual(exp, self.cnx._socket.sock._client_sends) + + fp = StringIO.StringIO(csv_data) + self.cnx._socket = None + self.assertRaises(errors.OperationalError, + self.cnx._send_data, fp, False) + # Nothing to read, but try to send empty packet + self.assertRaises(errors.OperationalError, + self.cnx._send_data, fp, True) diff --git a/lib/tests/test_constants.py b/lib/tests/test_constants.py index 9b6ae56..5724fb5 100644 --- a/lib/tests/test_constants.py +++ b/lib/tests/test_constants.py @@ -1,32 +1,29 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for mysql.connector.constants """ -import sys -import struct - import tests from mysql.connector import constants, errors @@ -48,6 +45,15 @@ def test_flag_is_set(self): self.assertTrue(constants.flag_is_set(d,flags)) self.assertFalse(constants.flag_is_set(1 << 4,flags)) + + def test_MAX_PACKET_LENGTH(self): + """Check MAX_PACKET_LENGTH""" + self.assertEqual(16777215, constants.MAX_PACKET_LENGTH) + + def test_NET_BUFFER_LENGTH(self): + """Check NET_BUFFER_LENGTH""" + self.assertEqual(8192, constants.NET_BUFFER_LENGTH) + class FieldTypeTests(tests.MySQLConnectorTests): @@ -267,13 +273,16 @@ def test_get_charset_info(self): data = exp[1:] self.assertEqual(exp, func(data[0],data[1])) + self.assertEqual(exp, func(None,data[1])) self.assertEqual(exp, func(exp[0])) - - exception = errors.ProgrammingError - data = ('utf8','utf8_OOOOPS_ci',False) - - self.assertRaises(exception, - constants.CharacterSet.get_charset_info, data[0], data[1]) + + self.assertRaises(errors.ProgrammingError, + constants.CharacterSet.get_charset_info, 666) + self.assertRaises(errors.ProgrammingError, + constants.CharacterSet.get_charset_info, charset='utf8', + collation='utf8_spam_ci') + self.assertRaises(errors.ProgrammingError, + constants.CharacterSet.get_charset_info, collation='utf8_spam_ci') def test_get_supported(self): """Get list of all supported character sets""" @@ -286,4 +295,87 @@ def test_get_supported(self): self.assertEqual(exp, constants.CharacterSet.get_supported()) - +class SQLModesTests(tests.MySQLConnectorTests): + + modes = ( + 'REAL_AS_FLOAT', + 'PIPES_AS_CONCAT', + 'ANSI_QUOTES', + 'IGNORE_SPACE', + 'NOT_USED', + 'ONLY_FULL_GROUP_BY', + 'NO_UNSIGNED_SUBTRACTION', + 'NO_DIR_IN_CREATE', + 'POSTGRESQL', + 'ORACLE', + 'MSSQL', + 'DB2', + 'MAXDB', + 'NO_KEY_OPTIONS', + 'NO_TABLE_OPTIONS', + 'NO_FIELD_OPTIONS', + 'MYSQL323', + 'MYSQL40', + 'ANSI', + 'NO_AUTO_VALUE_ON_ZERO', + 'NO_BACKSLASH_ESCAPES', + 'STRICT_TRANS_TABLES', + 'STRICT_ALL_TABLES', + 'NO_ZERO_IN_DATE', + 'NO_ZERO_DATE', + 'INVALID_DATES', + 'ERROR_FOR_DIVISION_BY_ZERO', + 'TRADITIONAL', + 'NO_AUTO_CREATE_USER', + 'HIGH_NOT_PRECEDENCE', + 'NO_ENGINE_SUBSTITUTION', + 'PAD_CHAR_TO_FULL_LENGTH', + ) + + def test_get_info(self): + for mode in SQLModesTests.modes: + self.assertEqual(mode, getattr(constants.SQLMode, mode), + 'Wrong info for SQL Mode %s' % mode) + + def test_get_full_info(self): + modes = tuple(sorted(SQLModesTests.modes)) + self.assertEqual(modes, + constants.SQLMode.get_full_info()) + + +class ShutdownTypeTests(tests.MySQLConnectorTests): + """Test COM_SHUTDOWN types""" + desc = { + 'SHUTDOWN_DEFAULT': (0, + "defaults to SHUTDOWN_WAIT_ALL_BUFFERS"), + 'SHUTDOWN_WAIT_CONNECTIONS': (1, + "wait for existing connections to finish"), + 'SHUTDOWN_WAIT_TRANSACTIONS': (2, + "wait for existing trans to finish"), + 'SHUTDOWN_WAIT_UPDATES': (8, + "wait for existing updates to finish"), + 'SHUTDOWN_WAIT_ALL_BUFFERS': (10, + "flush InnoDB and other storage engine buffers"), + 'SHUTDOWN_WAIT_CRITICAL_BUFFERS': (11, + "don't flush InnoDB buffers, " + "flush other storage engines' buffers"), + 'KILL_QUERY': (254, "(no description)"), + 'KILL_CONNECTION': (255, "(no description)"), + } + + def test_attributes(self): + """Check attributes for FieldType""" + self.assertEqual('', constants.ShutdownType.prefix) + + for k,v in self.desc.items(): + self.failUnless(constants.ShutdownType.__dict__.has_key(k), + '%s is not an attribute of ShutdownType' % k) + self.assertEqual(v[0],constants.ShutdownType.__dict__[k], + '%s attribute of ShutdownType has wrong value' % k) + + def test_get_desc(self): + """Get field flag by name""" + for k,v in self.desc.items(): + exp = v[1] + res = constants.ShutdownType.get_desc(k) + self.assertEqual(exp, res) diff --git a/lib/tests/test_conversion.py b/lib/tests/test_conversion.py index 986474a..70d3c16 100644 --- a/lib/tests/test_conversion.py +++ b/lib/tests/test_conversion.py @@ -1,26 +1,26 @@ # -*- coding: utf-8 -*- # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) - +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for mysql.connector.conversion """ @@ -160,7 +160,7 @@ def test_to_mysql(self): datetime.date(2008, 5, 7), datetime.time(20, 03, 23), st_now, - datetime.timedelta(hours=40,minutes=30,seconds=12), + datetime.timedelta(hours=40, minutes=30, seconds=12), Decimal('3.14'), ) exp = ( @@ -173,7 +173,7 @@ def test_to_mysql(self): '2008-05-07 20:01:23', '2008-05-07', '20:03:23', - time.strftime('%Y-%m-%d %H:%M:%S',st_now), + time.strftime('%Y-%m-%d %H:%M:%S', st_now), '40:30:12', '3.14', ) @@ -196,20 +196,23 @@ def test__unicode_to_mysql(self): self.assertEqual(exp, res) - def test__none_to_mysql(self): + def test__nonetype_to_mysql(self): """Python None stays None for MySQL.""" data = None - res = self.cnv._none_to_mysql(data) + res = self.cnv._nonetype_to_mysql(data) self.assertEqual(data, res) def test__datetime_to_mysql(self): - """A datetime.datetime becomes formated like Y-m-d H:M:S""" - data = datetime.datetime(2008, 5, 7, 20, 01, 23) - res = self.cnv._datetime_to_mysql(data) - exp = data.strftime('%Y-%m-%d %H:%M:%S') - - self.assertEqual(exp, res) + """A datetime.datetime becomes formated like Y-m-d H:M:S.f""" + cases = [ + (datetime.datetime(2008, 5, 7, 20, 1, 23), + '2008-05-07 20:01:23'), + (datetime.datetime(2012, 5, 2, 20, 1, 23, 10101), + '2012-05-02 20:01:23.010101') + ] + for data, exp in cases: + self.assertEqual(exp, self.cnv._datetime_to_mysql(data)) def test__date_to_mysql(self): """A datetime.date becomes formated like Y-m-d""" @@ -220,15 +223,16 @@ def test__date_to_mysql(self): self.assertEqual(exp, res) def test__time_to_mysql(self): - """A datetime.time becomes formated like Y-m-d H:M:S""" - data = datetime.time(20, 03, 23) - res = self.cnv._time_to_mysql(data) - exp = data.strftime('%H:%M:%S') - - self.assertEqual(exp, res) + """A datetime.time becomes formated like Y-m-d H:M:S[.f]""" + cases = [ + (datetime.time(20, 3, 23), '20:03:23'), + (datetime.time(20, 3, 23, 10101), '20:03:23.010101'), + ] + for data, exp in cases: + self.assertEqual(exp, self.cnv._time_to_mysql(data)) def test__struct_time_to_mysql(self): - """A time.struct_time becomes formated like Y-m-d H:M:S""" + """A time.struct_time becomes formated like Y-m-d H:M:S[.f]""" data = time.localtime() res = self.cnv._struct_time_to_mysql(data) exp = time.strftime('%Y-%m-%d %H:%M:%S',data) @@ -236,16 +240,31 @@ def test__struct_time_to_mysql(self): self.assertEqual(exp, res) def test__timedelta_to_mysql(self): - """A datetime.timedelta becomes format like 'H:M:S'""" - data1 = datetime.timedelta(hours=40,minutes=30,seconds=12) - data2 = datetime.timedelta(hours=-40,minutes=30,seconds=12) - data3 = datetime.timedelta(hours=40,minutes=-1,seconds=12) - data4 = datetime.timedelta(hours=-40,minutes=60,seconds=12) - - self.assertEqual('40:30:12', self.cnv._timedelta_to_mysql(data1)) - self.assertEqual('-40:30:12', self.cnv._timedelta_to_mysql(data2)) - self.assertEqual('39:59:12', self.cnv._timedelta_to_mysql(data3)) - self.assertEqual('-39:00:12', self.cnv._timedelta_to_mysql(data4)) + """A datetime.timedelta becomes format like 'H:M:S[.f]'""" + cases = [ + (datetime.timedelta(hours=40, minutes=30, seconds=12), + '40:30:12'), + (datetime.timedelta(hours=-40, minutes=30, seconds=12), + '-40:30:12'), + (datetime.timedelta(hours=40, minutes=-1, seconds=12), + '39:59:12'), + (datetime.timedelta(hours=-40, minutes=60, seconds=12), + '-39:00:12'), + (datetime.timedelta(hours=40, minutes=30, seconds=12, + microseconds=10101), + '40:30:12.010101'), + (datetime.timedelta(hours=-40, minutes=30, seconds=12, + microseconds=10101), + '-40:30:12.010101'), + (datetime.timedelta(hours=40, minutes=-1, seconds=12, + microseconds=10101), + '39:59:12.010101'), + (datetime.timedelta(hours=-40, minutes=60, seconds=12, + microseconds=10101), + '-39:00:12.010101'), + ] + for data, exp in cases: + self.assertEqual(exp, self.cnv._timedelta_to_mysql(data)) def test__decimal_to_mysql(self): """A decimal.Decimal becomes a string.""" @@ -284,40 +303,53 @@ def test_to_python(self): res = tuple([ self.cnv.to_python(v[1],v[0]) for v in data ]) self.failUnlessEqual(res, exp) - def test__float(self): - """convert a MySQL FLOAT/DOUBLE to a Python float type""" + def test__FLOAT_to_python(self): + """Convert a MySQL FLOAT/DOUBLE to a Python float type""" data = '3.14' exp = float(data) - res = self.cnv._float(data) + res = self.cnv._FLOAT_to_python(data) self.assertEqual(exp, res) + + self.assertEqual(self.cnv._FLOAT_to_python, + self.cnv._DOUBLE_to_python) - def test__int(self): - """convert a MySQL TINY/SHORT/INT24/INT to a Python int type""" + def test__INT_to_python(self): + """Convert a MySQL TINY/SHORT/INT24/INT to a Python int type""" data = '128' exp = int(data) - res = self.cnv._int(data) + res = self.cnv._INT_to_python(data) self.assertEqual(exp, res) + + self.assertEqual(self.cnv._INT_to_python, self.cnv._TINY_to_python) + self.assertEqual(self.cnv._INT_to_python, self.cnv._SHORT_to_python) + self.assertEqual(self.cnv._INT_to_python, self.cnv._INT24_to_python) - def test__long(self): - """convert a MySQL LONG/LONGLONG to a Python long type""" + def test__LONG_to_python(self): + """Convert a MySQL LONG/LONGLONG to a Python long type""" data = '1281288' exp = long(data) - res = self.cnv._long(data) + res = self.cnv._LONG_to_python(data) self.assertEqual(exp, res) + + self.assertEqual(self.cnv._LONG_to_python, + self.cnv._LONGLONG_to_python) - def test__decimal(self): - """convert a MySQL DECIMAL to a Python decimal.Decimal type""" + def test__DECIMAL_to_python(self): + """Convert a MySQL DECIMAL to a Python decimal.Decimal type""" data = '3.14' exp = Decimal(data) - res = self.cnv._decimal(data) + res = self.cnv._DECIMAL_to_python(data) self.assertEqual(exp, res) + self.assertEqual(self.cnv._DECIMAL_to_python, + self.cnv._NEWDECIMAL_to_python) + def test__BIT_to_python(self): - """convert a MySQL BIT to Python int""" + """Convert a MySQL BIT to Python int""" data = [ '\x80', '\x80\x00', @@ -335,7 +367,7 @@ def test__BIT_to_python(self): self.assertEqual(exp,res) def test__DATE_to_python(self): - """convert a MySQL DATE to a Python datetime.date type""" + """Convert a MySQL DATE to a Python datetime.date type""" data = '2008-05-07' exp = datetime.date(2008, 5, 7) res = self.cnv._DATE_to_python(data) @@ -348,30 +380,37 @@ def test__DATE_to_python(self): self.assertEqual(None, res) def test__TIME_to_python(self): - """convert a MySQL TIME to a Python datetime.time type""" - data1 = '45:34:10' - exp1 = datetime.timedelta(hours=45, minutes=34, seconds=10) - data2 = '-45:34:10' - exp2 = datetime.timedelta(hours=-45, minutes=34, seconds=10) - - self.assertEqual(exp1, self.cnv._TIME_to_python(data1)) - self.assertEqual(exp2, self.cnv._TIME_to_python(data2)) + """Convert a MySQL TIME to a Python datetime.time type""" + cases = [ + ('45:34:10', + datetime.timedelta(hours=45, minutes=34, seconds=10)), + ('-45:34:10', + datetime.timedelta(hours=-45, minutes=34, seconds=10)), + ('45:34:10.010101', + datetime.timedelta(hours=45, minutes=34, seconds=10, + microseconds=10101)), + ('-45:34:10.010101', + datetime.timedelta(hours=-45, minutes=34, seconds=10, + microseconds=10101)), + ] + for data, exp in cases: + self.assertEqual(exp, self.cnv._TIME_to_python(data)) def test__DATETIME_to_python(self): - """convert a MySQL DATETIME to a Python datetime.datetime type""" - data = '2008-05-07 22:34:10' - exp = datetime.datetime(2008, 5, 7, 22, 34, 10) - res = self.cnv._DATETIME_to_python(data) - - self.assertEqual(exp, res) - - res = self.cnv._DATETIME_to_python('0000-00-00 00:00:00') - self.assertEqual(None, res) - res = self.cnv._DATETIME_to_python('1000-00-00 00:00:00') - self.assertEqual(None, res) + """Convert a MySQL DATETIME to a Python datetime.datetime type""" + cases = [ + ('2008-05-07 22:34:10', + datetime.datetime(2008, 5, 7, 22, 34, 10)), + ('2008-05-07 22:34:10.010101', + datetime.datetime(2008, 5, 7, 22, 34, 10, 10101)), + ('0000-00-00 00:00:00', None), + ('1000-00-00 00:00:00', None), + ] + for data, exp in cases: + self.assertEqual(exp, self.cnv._DATETIME_to_python(data)) def test__YEAR_to_python(self): - """convert a MySQL YEAR to Python int""" + """Convert a MySQL YEAR to Python int""" data = '2008' exp = 2008 @@ -380,7 +419,7 @@ def test__YEAR_to_python(self): self.assertRaises(ValueError,self.cnv._YEAR_to_python,data) def test__SET_to_python(self): - """convert a MySQL SET type to a Python sequence + """Convert a MySQL SET type to a Python sequence This actually calls hte _STRING_to_python() method since a SET is returned as string by MySQL. However, the description of the field @@ -394,7 +433,7 @@ def test__SET_to_python(self): self.assertEqual(exp, res) def test__STRING_to_python_utf8(self): - """convert a UTF-8 MySQL STRING/VAR_STRING to a Python Unicode type""" + """Convert a UTF-8 MySQL STRING/VAR_STRING to a Python Unicode type""" self.cnv.set_charset('utf8') # default data = '\xc3\xa4 utf8 string' exp = unicode(data,'utf8') @@ -403,7 +442,7 @@ def test__STRING_to_python_utf8(self): self.assertEqual(exp, res) def test__STRING_to_python_latin1(self): - """convert a ISO-8859-1 MySQL STRING/VAR_STRING to a Python non-Unicode string""" + """Convert a ISO-8859-1 MySQL STRING/VAR_STRING to a Python non-Unicode string""" self.cnv.set_charset('latin1') self.cnv.set_unicode(False) data = '\xe4 latin string' @@ -415,7 +454,7 @@ def test__STRING_to_python_latin1(self): self.cnv.set_unicode(True) def test__STRING_to_python_binary(self): - """convert a STRING BINARY to Python bytes type""" + """Convert a STRING BINARY to Python bytes type""" data = '\x33\xfd\x34\xed' desc = ('foo', constants.FieldType.STRING, 2, 3, 4, 5, 6, constants.FieldFlag.BINARY) res = self.cnv._STRING_to_python(data,desc) @@ -423,10 +462,9 @@ def test__STRING_to_python_binary(self): self.assertEqual(data,res) def test__BLOB_to_python_binary(self): - """convert a BLOB BINARY to Python bytes type""" + """Convert a BLOB BINARY to Python bytes type""" data = '\x33\xfd\x34\xed' desc = ('foo', constants.FieldType.BLOB, 2, 3, 4, 5, 6, constants.FieldFlag.BINARY) res = self.cnv._BLOB_to_python(data,desc) self.assertEqual(data,res) - diff --git a/lib/tests/test_cursor.py b/lib/tests/test_cursor.py index 09e5feb..22bd088 100644 --- a/lib/tests/test_cursor.py +++ b/lib/tests/test_cursor.py @@ -1,88 +1,125 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) - +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for mysql.connector.cursor """ -from collections import deque +import traceback +import new +import itertools from decimal import Decimal import time import datetime import inspect +import re import tests -from mysql.connector import connection, cursor, conversion, protocol, utils, errors, constants +from mysql.connector import (connection, cursor, conversion, protocol, + utils, errors, constants) -class TestsCursor(tests.MySQLConnectorTests): - - def tearDown(self): - if hasattr(self,'c') and isinstance(self.c,cursor.MySQLCursor): - self.c.close() - if hasattr(self,'db') and\ - isinstance(self.db,connection.MySQLConnection): - self.db.close() - - def _test_execute_setup(self,db,tbl="myconnpy_cursor",engine="MyISAM"): +class TestsCursor(tests.MySQLConnectorTests): + def _test_execute_setup(self,connection, + tbl="myconnpy_cursor", engine="MyISAM"): - self._test_execute_cleanup(db,tbl) + self._test_execute_cleanup(connection, tbl) stmt_create = """CREATE TABLE %s (col1 INT, col2 VARCHAR(30), PRIMARY KEY (col1)) ENGINE=%s""" % (tbl,engine) try: - cursor = db.cursor() + cursor = connection.cursor() cursor.execute(stmt_create) except (StandardError), e: self.fail("Failed setting up test table; %s" % e) cursor.close() - def _test_execute_cleanup(self,db,tbl="myconnpy_cursor"): + def _test_execute_cleanup(self, connection, tbl="myconnpy_cursor"): stmt_drop = """DROP TABLE IF EXISTS %s""" % (tbl) try: - cursor = db.cursor() + cursor = connection.cursor() cursor.execute(stmt_drop) except (StandardError), e: self.fail("Failed cleaning up test table; %s" % e) cursor.close() +class CursorModule(tests.MySQLConnectorTests): + """ + Tests for the cursor module functions and attributes + """ + def test_RE_SQL_INSERT_VALUES(self): + regex = cursor.RE_SQL_INSERT_VALUES + + cases = [ + ("(%s, %s)", + "INSERT INTO t1 VALUES (%s, %s)"), + ("( %s, \n %s )", + "INSERT INTO t1 VALUES ( %s, \n %s )"), + ("(%(c1)s, %(c2)s)", + "INSERT INTO t1 VALUES (%(c1)s, %(c2)s)"), + ("(\n%(c1)s\n, \n%(c2)s\n)", + "INSERT INTO t1 VALUES \n(\n%(c1)s\n, \n%(c2)s\n)"), + ("( %(c1)s , %(c2)s )", + "INSERT INTO t1 VALUES ( %(c1)s , %(c2)s ) ON DUPLICATE"), + ("(%s, %s, NOW())", + "INSERT INTO t1 VALUES (%s, %s, NOW())"), + ("(%s, CONCAT('a', 'b'), %s, NOW())", + "INSERT INTO t1 VALUES (%s, CONCAT('a', 'b'), %s, NOW())"), + ("( NOW(), %s, \n, CONCAT('a', 'b'), %s )", + "INSERT INTO t1 VALUES " + " ( NOW(), %s, \n, CONCAT('a', 'b'), %s )"), + ("(%(c1)s, NOW(6), %(c2)s)", + "INSERT INTO t1 VALUES (%(c1)s, NOW(6), %(c2)s)"), + ("(\n%(c1)s\n, \n%(c2)s, REPEAT('a', 20)\n)", + "INSERT INTO t1 VALUES " + "\n(\n%(c1)s\n, \n%(c2)s, REPEAT('a', 20)\n)"), + ("( %(c1)s ,NOW(),REPEAT('a', 20)\n), %(c2)s )", + "INSERT INTO t1 VALUES " + " ( %(c1)s ,NOW(),REPEAT('a', 20)\n), %(c2)s ) ON DUPLICATE"), + ] + + for exp, stmt in cases: + m = re.search(regex, stmt) + self.assertTrue(m, "multipe-row insert failed: " + stmt) + self.assertEqual(exp, m.group(1)) + class CursorBaseTests(tests.MySQLConnectorTests): def setUp(self): self.c = cursor.CursorBase() - def test_description(self): - """CursorBase object description-attribute""" - self.checkAttr(self.c,'description',None) - - def test_rowcount(self): - """CursorBase object rowcount-attribute""" - self.checkAttr(self.c,'rowcount',-1) - - def test_arraysize(self): - """CursorBase object arraysize-attribute""" - self.checkAttr(self.c,'arraysize',1) + def test___init__(self): + exp = { + '_description': None, + '_rowcount': -1, + '_last_insert_id' : None, + 'arraysize': 1, + } + + for key, value in exp.items(): + self.assertEqual(value, getattr(self.c, key), + msg="Default for '%s' did not match." % key) def test_callproc(self): """CursorBase object callproc()-method""" @@ -154,66 +191,130 @@ def test_setoutputsize(self): except (SyntaxError, TypeError): self.fail("CursorBase setoutputsize(): wrong arguments") + def test_description(self): + self.assertEqual(None, self.c.description) + self.assertEqual(self.c._description, self.c.description) + self.c._description = 'ham' + self.assertEqual('ham', self.c.description) + try: + self.c.description = 'spam' + except AttributeError: + pass + else: + self.fail('CursorBase.description is not read-only') + + def test_rowcount(self): + self.assertEqual(-1, self.c.rowcount) + self.assertEqual(self.c._rowcount, self.c.rowcount) + self.c._rowcount = 2 + self.assertEqual(2, self.c.rowcount) + try: + self.c.rowcount = 3 + except AttributeError: + pass + else: + self.fail('CursorBase.rowcount is not read-only') + + def test_last_insert_id(self): + self.assertEqual(None, self.c.lastrowid) + self.assertEqual(self.c._last_insert_id, self.c.lastrowid) + self.c._last_insert_id = 2 + self.assertEqual(2, self.c.lastrowid) + try: + self.c.lastrowid = 3 + except AttributeError: + pass + else: + self.fail('CursorBase.lastrowid is not read-only') + class MySQLCursorTests(TestsCursor): def setUp(self): - self.c = cursor.MySQLCursor(db=None) - self.db = None + self.c = cursor.MySQLCursor(connection=None) + self.connection = None def test_init(self): """MySQLCursor object init""" try: - c = cursor.MySQLCursor(db=None) + c = cursor.MySQLCursor(connection=None) except (SyntaxError, TypeError), e: self.fail("Failed initializing MySQLCursor; %s" % e) - - self.assertRaises(errors.InterfaceError,cursor.MySQLCursor,db='foo') - - def test__result(self): - """MySQLCursor object _results-attribute""" - self.checkAttr(self.c,'_results',deque()) - - def test__nextrow(self): - """MySQLCursor object _nextrow-attribute""" - self.checkAttr(self.c,'_nextrow',(None,None)) - - def test_lastrowid(self): - """MySQLCursor object lastrowid-attribute""" - self.checkAttr(self.c,'lastrowid',None) - def test__warnings(self): - """MySQLCursor object warnings-attribute""" - self.checkAttr(self.c,'_warnings',None) + exp = { + '_connection' : None, + '_stored_results' : [], + '_nextrow' : (None, None), + '_warnings' : None, + '_warning_count' : 0, + '_executed' : None, + '_executed_list' : [], + } + + for key, value in exp.items(): + self.assertEqual(value, getattr(c, key), + msg="Default for '%s' did not match." % key) - def test_set_connection(self): - """MySQLCursor object set_connection()-method""" - self.checkMethod(self.c,'set_connection') + self.assertRaises(errors.InterfaceError, cursor.MySQLCursor, + connection='foo') - self.assertRaises(errors.InterfaceError, self.c.set_connection, 'foo') - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c.set_connection(self.db) + def test__set_connection(self): + """MySQLCursor object _set_connection()-method""" + self.checkMethod(self.c, '_set_connection') + + self.assertRaises(errors.InterfaceError, + self.c._set_connection, 'foo') + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c._set_connection(self.connection) self.c.close() def test__reset_result(self): """MySQLCursor object _reset_result()-method""" self.checkMethod(self.c,'_reset_result') + def reset(self): + self._test = "Reset called" + self.c.reset = new.instancemethod(reset, self.c, cursor.MySQLCursor) + + exp = { + 'rowcount': -1, + '_stored_results' : [], + '_nextrow' : (None, None), + '_warnings' : None, + '_warning_count' : 0, + '_executed' : None, + '_executed_list' : [], + } + self.c._reset_result() - self.assertEqual((None,None), self.c._nextrow, - "_nextrow is not reset to 0") - self.assertEqual(None, self.c._warnings, - "_warnings is not reset to empty list") - self.assertEqual(0, self.c._warning_count, - "_warning_count is not reset to 0") - self.assertEqual((), self.c.description, - "description is not reset to empty tuple") + for key, value in exp.items(): + self.assertEqual(value, getattr(self.c, key), + msg="'%s' was not reset." % key) + + # MySQLCursor._reset_result() must call MySQLCursor.reset() + self.assertEqual('Reset called', self.c._test) + + def test__have_unread_result(self): + """MySQLCursor object _have_unread_result()-method""" + self.checkMethod(self.c, '_have_unread_result') + class FakeConnection(object): + def __init__(self): + self.unread_result = False + + self.c = cursor.MySQLCursor() + self.c._connection = FakeConnection() + + self.c._connection.unread_result = True + self.assertTrue(self.c._have_unread_result()) + self.c._connection.unread_result = False + self.assertFalse(self.c._have_unread_result()) + def test_next(self): """MySQLCursor object next()-method""" self.checkMethod(self.c,'next') - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = cursor.MySQLCursor(self.db) + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = cursor.MySQLCursor(self.connection) self.assertRaises(StopIteration,self.c.next) self.c.execute("SELECT SHA1('myconnpy')") exp = (u'c5e24647dbb63447682164d81b34fe493a83610b',) @@ -222,24 +323,11 @@ def test_next(self): def test_close(self): """MySQLCursor object close()-method""" - self.checkMethod(self.c,'close') + self.checkMethod(self.c, 'close') self.assertEqual(False, self.c.close(), - "close() should return False with no connection") - - db1 = connection.MySQLConnection(**self.getMySQLConfig()) - self.assertEqual([],db1.cursors, - "New MySQL-object should have no cursors.") - c1 = cursor.MySQLCursor(db1) - self.assertEqual([c1],db1.cursors) - self.assertEqual(True, c1.close(), - "close() should return True when succesful") - self.assertEqual([],db1.cursors, - "Closing last cursor MySQL-object should leave list empty.") - - c1 = cursor.MySQLCursor(db1) - db1.remove_cursor(c1) - self.assertEqual(False, c1.close()) + "close() should return False with no connection") + self.assertEqual(None, self.c._connection) def test__process_params(self): """MySQLCursor object _process_params()-method""" @@ -290,8 +378,8 @@ def test__process_params(self): "'40:30:12'", ) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() self.assertEqual((),self.c._process_params(()), "_process_params() should return a tuple") res = self.c._process_params(data) @@ -348,8 +436,8 @@ def test__process_params_dict(self): 'r' : "'40:30:12'", } - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() self.assertEqual({},self.c._process_params_dict({}), "_process_params_dict() should return a dict") self.assertEqual(exp,self.c._process_params_dict(data)) @@ -361,9 +449,10 @@ def test__fetch_warnings(self): self.assertRaises(errors.InterfaceError,self.c._fetch_warnings) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.db.set_warnings(fetch=True) - self.c = self.db.cursor() + config = self.getMySQLConfig() + config['get_warnings'] = True + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() self.c.execute("SELECT 'a' + 'b'") self.c.fetchone() exp = [ @@ -377,32 +466,64 @@ def test__handle_noresultset(self): """MySQLCursor object _handle_noresultset()-method""" self.checkMethod(self.c,'_handle_noresultset') - self.assertRaises(errors.ProgrammingError,self.c._handle_noresultset,None) + self.assertRaises(errors.ProgrammingError, + self.c._handle_noresultset, None) data = { 'affected_rows':1, 'insert_id':10, 'warning_count': 100, 'server_status': 8, } - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() self.c._handle_noresultset(data) self.assertEqual(data['affected_rows'],self.c.rowcount) - self.assertEqual(data['insert_id'],self.c.lastrowid) + self.assertEqual(data['insert_id'], self.c._last_insert_id) self.assertEqual(data['warning_count'],self.c._warning_count) - self.assertTrue(self.c._more_results) self.c.close() + + def test__handle_result(self): + """MySQLCursor object _handle_result()-method""" + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() + + self.assertRaises(errors.InterfaceError, self.c._handle_result, None) + self.assertRaises(errors.InterfaceError, self.c._handle_result, + 'spam') + self.assertRaises(errors.InterfaceError, self.c._handle_result, + { 'spam':5 }) + + cases = [ + { 'affected_rows': 99999, + 'insert_id': 10, + 'warning_count': 100, + 'server_status': 8, + }, + { 'eof': {'status_flag': 0, 'warning_count': 0}, + 'columns': [('1', 8, None, None, None, None, 0, 129)] + }, + ] + self.c._handle_result(cases[0]) + self.assertEqual(cases[0]['affected_rows'], self.c.rowcount) + self.assertFalse(self.c._connection.unread_result) + self.assertFalse(self.c._have_unread_result()) + + self.c._handle_result(cases[1]) + self.assertEqual(cases[1]['columns'], self.c.description) + self.assertTrue(self.c._connection.unread_result) + self.assertTrue(self.c._have_unread_result()) def test_execute(self): """MySQLCursor object execute()-method""" self.checkMethod(self.c,'execute') - self.assertEqual(0,self.c.execute(None,None)) + self.assertEqual(None, self.c.execute(None, None)) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.db.set_warnings(fetch=True) - self.c = self.db.cursor() + config = self.getMySQLConfig() + config['get_warnings'] = True + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() self.assertRaises(errors.ProgrammingError,self.c.execute, 'SELECT %s,%s,%s', ('foo','bar',)) @@ -417,18 +538,18 @@ def test_execute(self): ] self.assertTrue(self.cmpResult(exp, self.c._warnings)) - self.c.execute("SELECT SHA1('myconnpy')") - exp = [(u'c5e24647dbb63447682164d81b34fe493a83610b',)] + self.c.execute("SELECT BINARY 'myconnpy'") + exp = [(u'myconnpy',)] self.assertEqual(exp, self.c.fetchall()) self.c.close() tbl = 'myconnpy_cursor' - self._test_execute_setup(self.db,tbl) + self._test_execute_setup(self.connection,tbl) stmt_insert = "INSERT INTO %s (col1,col2) VALUES (%%s,%%s)" % (tbl) - self.c = self.db.cursor() + self.c = self.connection.cursor() res = self.c.execute(stmt_insert, (1,100)) - self.assertEqual(1,res,"Return value of execute() is wrong.") + self.assertEqual(None, res, "Return value of execute() is wrong.") stmt_select = "SELECT col1,col2 FROM %s ORDER BY col1" % (tbl) self.c.execute(stmt_select) self.assertEqual([(1L, u'100')], @@ -439,60 +560,64 @@ def test_execute(self): self.c.execute(stmt, data) self.assertEqual([(1L, u'100')],self.c.fetchall()) - self._test_execute_cleanup(self.db,tbl) + self._test_execute_cleanup(self.connection,tbl) self.c.close() def test_executemany(self): """MySQLCursor object executemany()-method""" self.checkMethod(self.c,'executemany') - self.assertEqual(0,self.c.executemany(None,[])) + self.assertEqual(None, self.c.executemany(None, [])) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.db.set_warnings(fetch=True) - self.c = self.db.cursor() - self.assertRaises(errors.InterfaceError,self.c.executemany, - 'foo',None) - self.assertRaises(errors.ProgrammingError,self.c.executemany, - 'foo','foo') - self.assertEqual(0,self.c.executemany('foo',[])) - self.assertRaises(errors.ProgrammingError,self.c.executemany, - 'foo',['foo']) + config = self.getMySQLConfig() + config['get_warnings'] = True + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() + self.assertRaises(errors.InterfaceError, self.c.executemany, + 'foo', None) + self.assertRaises(errors.ProgrammingError, self.c.executemany, + 'foo', 'foo') + self.assertEqual(None, self.c.executemany('foo', [])) + self.assertRaises(errors.ProgrammingError, self.c.executemany, + 'foo', ['foo']) self.assertRaises(errors.ProgrammingError,self.c.executemany, - 'SELECT %s', [('foo',),'foo']) + 'SELECT %s', [('foo',), 'foo']) + self.assertRaises(errors.InterfaceError, + self.c.executemany, + "INSERT INTO t1 1 %s", [(1,),(2,)]) self.c.executemany("SELECT SHA1(%s)", [('foo',),('bar',)]) self.assertEqual(None,self.c.fetchone()) self.c.close() tbl = 'myconnpy_cursor' - self._test_execute_setup(self.db,tbl) + self._test_execute_setup(self.connection,tbl) stmt_insert = "INSERT INTO %s (col1,col2) VALUES (%%s,%%s)" % (tbl) stmt_select = "SELECT col1,col2 FROM %s ORDER BY col1" % (tbl) - self.c = self.db.cursor() + self.c = self.connection.cursor() - res = self.c.executemany(stmt_insert,[(1,100),(2,200),(3,300)]) - self.assertEqual(3,res, - "Return value of executemany() is wrong w/o result.") + self.c.executemany(stmt_insert,[(1,100),(2,200),(3,300)]) + self.assertEqual(3, self.c.rowcount) + + self.c.executemany("SELECT %s",[('f',),('o',),('o',)]) + self.assertEqual(3, self.c.rowcount) - res = self.c.executemany("SELECT %s",[('f',),('o',),('o',)]) - self.assertEqual(3,res) - data = [{'id':2},{'id':3}] stmt = "SELECT * FROM %s WHERE col1 <= %%(id)s" % tbl - self.assertEqual(5,self.c.executemany(stmt, data)) + self.c.executemany(stmt, data) + self.assertEqual(5, self.c.rowcount) self.c.execute(stmt_select) self.assertEqual([(1L, u'100'), (2L, u'200'), (3L, u'300')], - self.c.fetchall(),"Multi insert test failed") + self.c.fetchall(), "Multi insert test failed") data = [{'id':2},{'id':3}] stmt = "DELETE FROM %s WHERE col1 = %%(id)s" % tbl self.c.executemany(stmt,data) self.assertEqual(2,self.c.rowcount) - self._test_execute_cleanup(self.db,tbl) + self._test_execute_cleanup(self.connection, tbl) self.c.close() def test_fetchwarnings(self): @@ -508,44 +633,57 @@ def test_fetchwarnings(self): self.assertEqual(exp,self.c.fetchwarnings()) self.c.close() - def test_next_proc_resultset(self): - """MySQLCursor object next_proc_resultset()-method""" - self.checkMethod(self.c,'next_resultset') + def test_stored_results(self): + """MySQLCursor object stored_results()-method""" + self.checkMethod(self.c, 'stored_results') - self.assertEqual(deque(),self.c._results) - self.assertEqual(None,self.c.next_proc_resultset()) - self.c._results.append('abc') - self.assertEqual('abc',self.c.next_proc_resultset()) - self.assertEqual(None,self.c.next_proc_resultset()) + self.assertEqual([], self.c._stored_results) + self.assertTrue(hasattr(self.c.stored_results(), '__iter__')) + self.c._stored_results.append('abc') + self.assertEqual('abc', self.c.stored_results().next()) + try: + result = self.c.stored_results().next() + except StopIteration: + pass + except: + self.fail("StopIteration not raised") - def _test_callproc_setup(self,db): + def _test_callproc_setup(self, connection): - self._test_callproc_cleanup(db) - stmt_create1 = """CREATE PROCEDURE myconnpy_sp_1 - (IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN SET pProd := pFac1 * pFac2; - END;""" + self._test_callproc_cleanup(connection) + stmt_create1 = ( + "CREATE PROCEDURE myconnpy_sp_1" + "(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) " + "BEGIN SET pProd := pFac1 * pFac2; END;") - stmt_create2 = """CREATE PROCEDURE myconnpy_sp_2 - (IN pFac1 INT, IN pFac2 INT, OUT pProd INT) - BEGIN SELECT 'abc'; SELECT 'def'; SET pProd := pFac1 * pFac2; - END;""" + stmt_create2 = ( + "CREATE PROCEDURE myconnpy_sp_2" + "(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) " + "BEGIN SELECT 'abc'; SELECT 'def'; SET pProd := pFac1 * pFac2; END;" + ) + + stmt_create3 = ( + "CREATE PROCEDURE myconnpy_sp_3" + "(IN pStr1 VARCHAR(20), IN pStr2 VARCHAR(20), " + "OUT pConCat VARCHAR(100)) " + "BEGIN SET pConCat := CONCAT(pStr1, pStr2); END;") try: - cursor = db.cursor() + cursor = connection.cursor() cursor.execute(stmt_create1) cursor.execute(stmt_create2) + cursor.execute(stmt_create3) except errors.Error, e: self.fail("Failed setting up test stored routine; %s" % e) cursor.close() - def _test_callproc_cleanup(self,db): + def _test_callproc_cleanup(self, connection): - sp_names = ('myconnpy_sp_1','myconnpy_sp_2') + sp_names = ('myconnpy_sp_1', 'myconnpy_sp_2', 'myconnpy_sp_3') stmt_drop = "DROP PROCEDURE IF EXISTS %s" try: - cursor = db.cursor() + cursor = connection.cursor() for sp_name in sp_names: cursor.execute(stmt_drop % sp_name) except errors.Error, e: @@ -556,30 +694,39 @@ def test_callproc(self): """MySQLCursor object callproc()-method""" self.checkMethod(self.c,'callproc') - self.assertRaises(errors.ProgrammingError,self.c.callproc,None,None) - - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.db.set_warnings(fetch=True) - self._test_callproc_setup(self.db) - self.c = self.db.cursor() + self.assertRaises(ValueError, self.c.callproc, None) + self.assertRaises(ValueError, self.c.callproc, 'sp1', None) - exp = ('5', '4', 20L) - result = self.c.callproc('myconnpy_sp_1',(5,4,0)) - self.assertEqual(deque(),self.c._results) + config = self.getMySQLConfig() + config['get_warnings'] = True + self.connection = connection.MySQLConnection(**config) + self._test_callproc_setup(self.connection) + self.c = self.connection.cursor() + + exp = (5, 4, 20) + result = self.c.callproc('myconnpy_sp_1', (exp[0], exp[1], 0)) + self.assertEqual([], self.c._stored_results) self.assertEqual(exp, result) - exp = ('6', '5', 30L) - result = self.c.callproc('myconnpy_sp_2',(6,5,0)) - self.assertTrue(isinstance(self.c._results,deque)) + exp = (6, 5, 30) + result = self.c.callproc('myconnpy_sp_2', (exp[0], exp[1], 0)) + self.assertTrue(isinstance(self.c._stored_results, list)) self.assertEqual(exp, result) - c1 = self.c.next_proc_resultset() - self.assertEqual(('abc',),c1.fetchone()) - c2 = self.c.next_proc_resultset() - self.assertEqual(('def',),c2.fetchone()) - self.assertEqual(None, self.c.next_proc_resultset()) + exp_results = [ + ('abc',), + ('def',) + ] + for result, exp in itertools.izip(self.c.stored_results(), + iter(exp_results)): + self.assertEqual(exp, result.fetchone()) + + exp = ('ham', 'spam', 'hamspam') + result = self.c.callproc('myconnpy_sp_3', (exp[0], exp[1], '')) + self.assertTrue(isinstance(self.c._stored_results, list)) + self.assertEqual(exp, result) - self._test_callproc_cleanup(self.db) + self._test_callproc_cleanup(self.connection) self.c.close() def test_fetchone(self): @@ -588,8 +735,8 @@ def test_fetchone(self): self.assertEqual(None,self.c.fetchone()) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() self.c.execute("SELECT SHA1('myconnpy')") exp = (u'c5e24647dbb63447682164d81b34fe493a83610b',) self.assertEqual(exp, self.c.fetchone()) @@ -602,13 +749,13 @@ def test_fetchmany(self): self.assertEqual([],self.c.fetchmany()) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) tbl = 'myconnpy_fetch' - self._test_execute_setup(self.db,tbl) + self._test_execute_setup(self.connection,tbl) stmt_insert = "INSERT INTO %s (col1,col2) VALUES (%%s,%%s)" % (tbl) stmt_select = "SELECT col1,col2 FROM %s ORDER BY col1 DESC" % (tbl) - self.c = self.db.cursor() + self.c = self.connection.cursor() nrRows = 10 data = [ (i,"%s" % (i*100)) for i in range(0,nrRows)] self.c.executemany(stmt_insert,data) @@ -626,7 +773,7 @@ def test_fetchmany(self): self.assertTrue(self.cmpResult(exp,rows), "Fetching next 3 rows test failed.") self.assertEqual([],self.c.fetchmany()) - self._test_execute_cleanup(self.db,tbl) + self._test_execute_cleanup(self.connection,tbl) self.c.close() def test_fetchall(self): @@ -635,13 +782,13 @@ def test_fetchall(self): self.assertRaises(errors.InterfaceError,self.c.fetchall) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) tbl = 'myconnpy_fetch' - self._test_execute_setup(self.db,tbl) + self._test_execute_setup(self.connection,tbl) stmt_insert = "INSERT INTO %s (col1,col2) VALUES (%%s,%%s)" % (tbl) stmt_select = "SELECT col1,col2 FROM %s ORDER BY col1 ASC" % (tbl) - self.c = self.db.cursor() + self.c = self.connection.cursor() self.c.execute("SELECT * FROM %s" % tbl) self.assertEqual([],self.c.fetchall(), "fetchall() with empty result should return []") @@ -652,13 +799,13 @@ def test_fetchall(self): self.assertTrue(self.cmpResult(data,self.c.fetchall()), "Fetching all rows failed.") self.assertEqual(None,self.c.fetchone()) - self._test_execute_cleanup(self.db,tbl) + self._test_execute_cleanup(self.connection,tbl) self.c.close() def test_raise_on_warning(self): - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.db.set_warnings(raise_on_warnings=True) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.connection.raise_on_warnings = True + self.c = self.connection.cursor() try: self.c.execute("SELECT 'a' + 'b'") self.c.fetchall() @@ -672,8 +819,8 @@ def test__unicode__(self): self.assertEqual("MySQLCursor: (Nothing executed yet)", "%s" % self.c.__unicode__()) - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() self.c.execute("SELECT VERSION()") self.c.fetchone() self.assertEqual("MySQLCursor: SELECT VERSION()", @@ -690,29 +837,44 @@ def test__str__(self): "%s" % self.c.__str__()) def test_column_names(self): - self.db = connection.MySQLConnection(**self.getMySQLConfig()) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**self.getMySQLConfig()) + self.c = self.connection.cursor() stmt = "SELECT NOW() as now, 'The time' as label, 123 FROM dual" exp = (u'now', u'label', u'123') self.c.execute(stmt) self.c.fetchone() self.assertEqual(exp, self.c.column_names) self.c.close() + + def test_statement(self): + self.c = cursor.MySQLCursor() + exp = 'SELECT * FROM ham' + self.c._executed = exp + self.assertEqual(exp, self.c.statement) + self.c._executed = ' ' + exp + ' ' + self.assertEqual(exp, self.c.statement) + + def test_with_rows(self): + self.c = cursor.MySQLCursor() + self.assertFalse(self.c.with_rows) + self.c._description = ('ham','spam') + self.assertTrue(self.c.with_rows) class MySQLCursorBufferedTests(TestsCursor): def setUp(self): - self.c = cursor.MySQLCursorBuffered(db=None) - self.db = None + self.c = cursor.MySQLCursorBuffered(connection=None) + self.connection = None def test_init(self): """MySQLCursorBuffered object init""" try: - c = cursor.MySQLCursorBuffered(db=None) + c = cursor.MySQLCursorBuffered(connection=None) except (SyntaxError, TypeError), e: self.fail("Failed initializing MySQLCursorBuffered; %s" % e) - self.assertRaises(errors.InterfaceError,cursor.MySQLCursorBuffered,db='foo') + self.assertRaises(errors.InterfaceError,cursor.MySQLCursorBuffered, + connection='foo') def test__next_row(self): """MySQLCursorBuffered object _next_row-attribute""" @@ -727,105 +889,25 @@ def test_execute(self): """ self.checkMethod(self.c,'execute') - self.assertEqual(0,self.c.execute(None,None)) + self.assertEqual(None, self.c.execute(None, None)) config = self.getMySQLConfig() config['buffered'] = True - self.db = connection.MySQLConnection(**config) - self.db.set_warnings(fetch=True) - self.c = self.db.cursor() + config['get_warnings'] = True + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() self.assertEqual(True,isinstance(self.c,cursor.MySQLCursorBuffered)) - - self.c.execute("SELECT 'a' + 'b'") - exp = [ - (u'Warning', 1292L, u"Truncated incorrect DOUBLE value: 'a'"), - (u'Warning', 1292L, u"Truncated incorrect DOUBLE value: 'b'") - ] - self.assertTrue(self.cmpResult(exp, self.c._warnings)) - - self.c.execute("SELECT SHA1('myconnpy')") - self.assertEqual(0,self.c._next_row) - exp = [['c5e24647dbb63447682164d81b34fe493a83610b']] - self.assertTrue(self.cmpResult(exp, self.c._rows)) - exp = [(u'c5e24647dbb63447682164d81b34fe493a83610b',)] - self.assertTrue(self.cmpResult(exp, self.c.fetchall())) - - tbl = 'myconnpy_cursor' - self._test_execute_setup(self.db,tbl) - stmt_insert = "INSERT INTO %s (col1,col2) VALUES (%%s,%%s)" % (tbl) - - data = [(1,100),(2,200),(3,300)] - for rec in data: - self.c.execute(stmt_insert, rec) - - self.c.execute("SELECT * FROM %s" % (tbl)) - self.assertEqual(0,self.c._next_row) - self.c.fetchone() - self.assertEqual(1,self.c._next_row) - self.c.fetchmany(2) - self.assertEqual(3,self.c._next_row) - - self._test_execute_cleanup(self.db,tbl) - self.c.close() - def test_executemany(self): - """MySQLCursorBuffered object executemany()-method""" - self.checkMethod(self.c,'executemany') - - self.assertEqual(0,self.c.executemany(None,[])) - - config = self.getMySQLConfig() - config['buffered'] = True - self.db = connection.MySQLConnection(**config) - self.db.set_warnings(fetch=True) - self.c = self.db.cursor() - self.assertRaises(errors.InterfaceError,self.c.executemany, - 'foo',None) - self.assertRaises(errors.ProgrammingError,self.c.executemany, - 'foo','foo') - self.assertEqual(0,self.c.executemany('foo',[])) - self.assertRaises(errors.ProgrammingError,self.c.executemany, - 'foo',['foo']) - self.assertRaises(errors.ProgrammingError,self.c.executemany, - 'SELECT %s', [('foo',),'foo']) - - self.c.executemany("SELECT SHA1(%s)", [('foo',),('bar',),('foobar',)]) - exp = (u'8843d7f92416211de9ebb963ff4ce28125932878',) - self.assertEqual(exp,self.c.fetchone()) - self.c.close() - - tbl = 'myconnpy_cursor' - self._test_execute_setup(self.db,tbl) - stmt_insert = "INSERT INTO %s (col1,col2) VALUES (%%s,%%s)" % (tbl) - stmt_select = "SELECT col1,col2 FROM %s ORDER BY col1" % (tbl) - - self.c = self.db.cursor() - - res = self.c.executemany(stmt_insert,[(1,100),(2,200),(3,300)]) - self.assertEqual(3,res, - "Return value of executemany() is wrong w/o result.") - - res = self.c.executemany("SELECT %s",[('f',),('o',),('o',)]) - self.assertEqual(3,res) - - data = [{'id':2},{'id':3}] - stmt = "SELECT * FROM %s WHERE col1 <= %%(id)s" % tbl - self.assertEqual(5,self.c.executemany(stmt, data)) - - exp = [(1L, u'100'), (2L, u'200'), (3L, u'300')] - self.c.execute(stmt_select) - self.assertTrue(self.cmpResult(exp, - self.c.fetchall()),"Multi insert test failed") - self._test_execute_cleanup(self.db,tbl) - self.c.close() + self.c.execute("SELECT 1") + self.assertEqual([('1',)], self.c._rows) def test_raise_on_warning(self): config = self.getMySQLConfig() config['buffered'] = True config['raise_on_warnings'] = True - self.db = connection.MySQLConnection(**config) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() try: self.c.execute("SELECT 'a' + 'b'") except errors.Error: @@ -833,18 +915,24 @@ def test_raise_on_warning(self): else: self.fail("Did not get exception while raising warnings.") + def test_with_rows(self): + c = cursor.MySQLCursorBuffered() + self.assertFalse(c.with_rows) + c._rows = [('ham',)] + self.assertTrue(c.with_rows) + class MySQLCursorRawTests(TestsCursor): def setUp(self): config = self.getMySQLConfig() config['raw'] = True - self.db = connection.MySQLConnection(**config) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() def tearDown(self): self.c.close() - self.db.close() + self.connection.close() def test_fetchone(self): self.checkMethod(self.c,'fetchone') @@ -862,12 +950,12 @@ def setUp(self): config['raw'] = True config['buffered'] = True - self.db = connection.MySQLConnection(**config) - self.c = self.db.cursor() + self.connection = connection.MySQLConnection(**config) + self.c = self.connection.cursor() def tearDown(self): self.c.close() - self.db.close() + self.connection.close() def test_fetchone(self): self.checkMethod(self.c,'fetchone') @@ -887,3 +975,4 @@ def test_fetchall(self): exp = [('1','string','2010-12-31', '2.5')] self.assertEqual(exp,self.c.fetchall()) + diff --git a/lib/tests/test_errorcode.py b/lib/tests/test_errorcode.py new file mode 100644 index 0000000..17f3419 --- /dev/null +++ b/lib/tests/test_errorcode.py @@ -0,0 +1,75 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Unittests for mysql.connector.errorcode +""" + +from datetime import datetime, date +import tests +from mysql.connector import errorcode + +class ErrorCodeTests(tests.MySQLConnectorTests): + def test__GENERATED_ON(self): + self.assertTrue(isinstance(errorcode._GENERATED_ON, str)) + try: + ymd = [ int(v) for v in errorcode._GENERATED_ON.split('-', 3)] + generatedon = date(*ymd) + except ValueError, err: + self.fail(err) + + delta = (datetime.now().date() - generatedon).days + self.assertTrue(delta < 120, + "errorcode.py is more than 120 days old (%d)" % delta) + + def test__MYSQL_VERSION(self): + minimum = (5, 6, 6) + self.assertTrue(isinstance(errorcode._MYSQL_VERSION, tuple)) + self.assertTrue(len(errorcode._MYSQL_VERSION) == 3) + self.assertTrue(errorcode._MYSQL_VERSION >= minimum) + + def _check_code(self, code, nr): + try: + self.assertEqual(getattr(errorcode, code), nr) + except AttributeError, err: + self.fail(err) + + def test_server_error_codes(self): + cases = { + 'ER_HASHCHK': 1000, + 'ER_TRG_INVALID_CREATION_CTX': 1604, + 'ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION': 1792, + } + + for code, nr in cases.items(): + self._check_code(code, nr) + + def test_client_error_codes(self): + cases = { + 'CR_UNKNOWN_ERROR': 2000, + 'CR_PROBE_SLAVE_STATUS': 2022, + 'CR_AUTH_PLUGIN_CANNOT_LOAD': 2059, + } + + for code, nr in cases.items(): + self._check_code(code, nr) + diff --git a/lib/tests/test_errors.py b/lib/tests/test_errors.py new file mode 100644 index 0000000..8128e9f --- /dev/null +++ b/lib/tests/test_errors.py @@ -0,0 +1,191 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Unittests for mysql.connector.errors +""" + +import sys +import tests +from mysql.connector import errors + +class ErrorsTests(tests.MySQLConnectorTests): + def test_custom_error_exception(self): + customfunc = errors.custom_error_exception + self.assertRaises(ValueError, customfunc, 'spam') + self.assertRaises(ValueError, customfunc, 1) + self.assertRaises(ValueError, customfunc, 1, 'spam') + + case = (1,errors.InterfaceError) + exp = { 1: errors.InterfaceError } + self.assertEqual(exp, customfunc(*case)) + + exp = case = { 1: errors.InterfaceError, 2: errors.ProgrammingError } + self.assertEqual(exp, customfunc(case)) + + case = { 1: errors.InterfaceError, 2: None } + self.assertRaises(ValueError, customfunc, case) + case = { 1: errors.InterfaceError, 2: str() } + self.assertRaises(ValueError, customfunc, case) + case = { '1': errors.InterfaceError } + self.assertRaises(ValueError, customfunc, case) + + self.assertEqual({}, customfunc({})) + self.assertEqual({}, errors._CUSTOM_ERROR_EXCEPTIONS) + + def test_get_mysql_exception(self): + tests = { + errors.ProgrammingError: ( + '24', '25', '26', '27', '28', '2A', '2C', + '34', '35', '37', '3C', '3D', '3F', '42'), + errors.DataError: ('02', '21', '22'), + errors.NotSupportedError: ('0A',), + errors.IntegrityError: ('23', 'XA'), + errors.InternalError: ('40', '44'), + errors.OperationalError: ('08', 'HZ', '0K'), + errors.DatabaseError: ('07', '2B', '2D', '2E', '33', 'ZZ', 'HY'), + } + + msg = 'Ham' + for exp, errlist in tests.items(): + for sqlstate in errlist: + errno = 1000 + res = errors.get_mysql_exception(errno, msg, sqlstate) + self.assertTrue(isinstance(res, exp), + "SQLState %s should be %s" % ( + sqlstate, exp.__name__)) + self.assertEqual(sqlstate, res.sqlstate) + self.assertEqual("%d (%s): %s" % (errno, sqlstate, msg), + res.msg) + + errno = 1064 + sqlstate = "42000" + msg = "You have an error in your SQL syntax" + exp = "1064 (42000): You have an error in your SQL syntax" + err = errors.get_mysql_exception(errno, msg, sqlstate) + self.assertEqual(exp, str(err)) + + # Custom exceptions + errors._CUSTOM_ERROR_EXCEPTIONS[1064] = errors.DatabaseError + self.assertTrue( + isinstance(errors.get_mysql_exception(1064, None, None), + errors.DatabaseError)) + errors._CUSTOM_ERROR_EXCEPTIONS = {} + + def test_get_exception(self): + ok_packet = '\x07\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00' + err_packet = '\x47\x00\x00\x02\xff\x15\x04\x23\x32\x38\x30\x30\x30'\ + '\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69\x65\x64'\ + '\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20\x27\x68\x61'\ + '\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74'\ + '\x27\x20\x28\x75\x73\x69\x6e\x67\x20\x70\x61\x73\x73'\ + '\x77\x6f\x72\x64\x3a\x20\x59\x45\x53\x29' + self.assertTrue(isinstance(errors.get_exception(err_packet), + errors.ProgrammingError)) + + self.assertRaises(ValueError, + errors.get_exception, ok_packet) + + res = errors.get_exception('\x47\x00\x00\x02\xff\x15') + self.assertTrue(isinstance(res, errors.InterfaceError)) + + +class ErrorTest(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.Error, StandardError)) + + err = errors.Error(None) + self.assertEqual(-1, err.errno) + self.assertEqual('Unknown error', err.msg) + + err = errors.Error('Ham', errno=1) + self.assertEqual(1, err.errno) + self.assertEqual('1: Ham', err.msg) + + err = errors.Error('Ham', errno=1, sqlstate="SPAM") + self.assertEqual(1, err.errno) + self.assertEqual('1 (SPAM): Ham', err.msg) + + err = errors.Error(errno=2000) + self.assertEqual('2000: Unknown MySQL error', err.msg) + + err = errors.Error(errno=2003, values=('/path/to/ham', 2)) + self.assertEqual( + u"2003: Can't connect to MySQL server on '/path/to/ham' (2)", + err.msg) + + err = errors.Error(errno=2001, values=('ham',)) + if '(Warning:' in str(err): + self.fail('Found %d in error message.') + + err = errors.Error(errno=2003, values=('ham',)) + self.assertEqual( + u"2003: Can't connect to MySQL server on '%-.100s' (%s) " + u"(Warning: not enough arguments for format string)", + err.msg) + + def test___str__(self): + exp = "Spam" + self.assertEqual(exp, str(errors.Error(exp))) + +class WarningTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.Warning, StandardError)) + +class InterfaceErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.InterfaceError, errors.Error)) + +class DatabaseErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.DatabaseError, errors.Error)) + +class InternalErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.InternalError, + errors.DatabaseError)) + +class OperationalErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.OperationalError, + errors.DatabaseError)) + +class ProgrammingErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.ProgrammingError, + errors.DatabaseError)) + +class IntegrityErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.IntegrityError, + errors.DatabaseError)) + +class DataErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.DataError, + errors.DatabaseError)) + +class NotSupportedErrorTests(tests.MySQLConnectorTests): + def test___init__(self): + self.assertTrue(issubclass(errors.NotSupportedError, + errors.DatabaseError)) + diff --git a/lib/tests/test_examples.py b/lib/tests/test_examples.py index d1eb999..4aa9a9b 100644 --- a/lib/tests/test_examples.py +++ b/lib/tests/test_examples.py @@ -1,102 +1,169 @@ +# -*- coding: utf-8 -*- # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for examples """ import sys +import logging -import mysql.connector as myconn - +import mysql.connector import tests +logger = logging.getLogger(tests.LOGGER_NAME) + class TestExamples(tests.MySQLConnectorTests): + def setUp(self): + config = self.getMySQLConfig() + self.cnx = mysql.connector.connect(**config) + + def tearDown(self): + self.cnx.close() - def _exec_main(self, exp): + def _exec_main(self, example): try: - exp.main(self.getMySQLConfig()) + return example.main(self.getMySQLConfig()) except StandardError, e: self.fail(e) def test_dates(self): """examples/dates.py""" try: - import examples.dates as exp + import examples.dates as example except StandardError, e: self.fail(e) - self._exec_main(exp) + output = example.main(self.getMySQLConfig()) + exp = [' 1 | 1977-06-14 | 1977-06-14 21:10:00 | 21:10:00 |', + ' 2 | None | None | 0:00:00 |', + ' 3 | None | None | 0:00:00 |'] + self.assertEqual(output, exp) + + example.DATA.append(('0000-00-00',None,'00:00:00'),) + self.assertRaises(mysql.connector.errors.IntegrityError, + example.main, self.getMySQLConfig()) def test_engines(self): """examples/engines.py""" try: - import examples.engines as exp + import examples.engines as example except: self.fail() - self._exec_main(exp) + output = self._exec_main(example) + + # Can't check output as it might be different per MySQL instance + # We check only if MyISAM is present + found = False + for s in output: + if s.find('MyISAM') > -1: + found = True + break + + self.assertTrue(found,'MyISAM engine not found in output') def test_inserts(self): """examples/inserts.py""" try: - import examples.inserts as exp + import examples.inserts as example except StandardError, e: self.fail(e) - self._exec_main(exp) - + output = self._exec_main(example) + exp = [u'1 | Geert | 30\nInfo: c..\n', + u'2 | Jan | 30\nInfo: c..\n', u'3 | Michel | 30\nInfo: c..\n'] + self.assertEqual(output,exp,'Output was not correct') + def test_transactions(self): """examples/transactions.py""" - - db = myconn.connect(**self.getMySQLConfig()) + db = mysql.connector.connect(**self.getMySQLConfig()) r = self.haveEngine(db,'InnoDB') db.close() if not r: return try: - import examples.transaction as exp + import examples.transaction as example except StandardError, e: self.fail(e) - self._exec_main(exp) + output = self._exec_main(example) + exp = ['Inserting data', 'Rolling back transaction', + 'No data, all is fine.', 'Data before commit:', + u'4 | Geert', u'5 | Jan', u'6 | Michel', 'Data after commit:', + u'4 | Geert', u'5 | Jan', u'6 | Michel'] + self.assertEqual(output,exp,'Output was not correct') def test_unicode(self): """examples/unicode.py""" try: - import examples.unicode as exp + import examples.unicode as example except StandardError, e: self.fail(e) - self._exec_main(exp) - + output = self._exec_main(example) + exp = ['Unicode string: \xc2\xbfHabla espa\xc3\xb1ol?', + 'Unicode string coming from db: \xc2\xbfHabla espa\xc3\xb1ol?'] + self.assertEqual(output,exp,'Output was not correct') + def test_warnings(self): """examples/warnings.py""" try: - import examples.warnings as exp + import examples.warnings as example except StandardError, e: self.fail(e) - self._exec_main(exp) - + output = self._exec_main(example) + exp = ["Executing 'SELECT 'abc'+1'", + u"1292: Truncated incorrect DOUBLE value: 'abc'"] + self.assertEqual(output,exp,'Output was not correct') + + example.STMT = "SELECT 'abc'" + self.assertRaises(StandardError, example.main, self.getMySQLConfig()) + def test_multi_resultsets(self): """examples/multi_resultsets.py""" try: - import examples.multi_resultsets as exp + import examples.multi_resultsets as example + except StandardError, e: + self.fail(e) + output = self._exec_main(example) + exp = ['Inserted 1 row', 'Number of rows: 1', 'Inserted 2 rows', + u'Names in table: Geert Jan Michel'] + self.assertEqual(output,exp,'Output was not correct') + + def test_microseconds(self): + """examples/microseconds.py""" + try: + import examples.microseconds as example except StandardError, e: self.fail(e) - self._exec_main(exp) + output = self._exec_main(example) + + if self.cnx.get_server_version() < (5,6,4): + exp = "does not support fractional precision for timestamps." + self.assertTrue(output[0].endswith(exp)) + else: + exp = [ + ' 1 | 1 | 0:00:47.510000', + ' 1 | 2 | 0:00:47.020000', + ' 1 | 3 | 0:00:47.650000', + ' 1 | 4 | 0:00:46.060000', + ] + self.assertEqual(output, exp, 'Output was not correct') + diff --git a/lib/tests/test_locales.py b/lib/tests/test_locales.py new file mode 100644 index 0000000..e867d5d --- /dev/null +++ b/lib/tests/test_locales.py @@ -0,0 +1,122 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Unittests for mysql.connector.locales +""" + +from datetime import datetime, date +import tests +from mysql.connector import errorcode, locales + +def _get_client_errors(): + errors = {} + for name in dir(errorcode): + if name.startswith('CR_'): + errors[name] = getattr(errorcode, name) + return errors + +class LocalesModulesTests(tests.MySQLConnectorTests): + def test_defaults(self): + # There should always be 'eng' + try: + from mysql.connector.locales import eng + except ImportError: + self.fail("locales.eng could not be imported") + + # There should always be 'eng.client_error' + try: + from mysql.connector.locales.eng import client_error + except ImportError: + self.fail("locales.eng.client_error could not be imported") + + def test_get_client_error(self): + try: + locales.get_client_error(2000, language='spam') + except ImportError, err: + self.assertEqual("No localization support for language 'spam'", + str(err)) + else: + self.fail("ImportError not raised") + + exp = u"Unknown MySQL error" + self.assertEqual(exp, locales.get_client_error(2000)) + self.assertEqual(exp, locales.get_client_error('CR_UNKNOWN_ERROR')) + + try: + locales.get_client_error(tuple()) + except ValueError, err: + self.assertEqual( + "error argument needs to be either an integer or string", + str(err)) + else: + self.fail("ValueError not raised") + +class LocalesEngClientErrorTests(tests.MySQLConnectorTests): + """Testing locales.eng.client_error""" + def test__GENERATED_ON(self): + try: + from mysql.connector.locales.eng import client_error + except ImportError: + self.fail("locales.eng.client_error could not be imported") + + self.assertTrue(isinstance(client_error._GENERATED_ON, str)) + try: + ymd = [ int(v) for v in errorcode._GENERATED_ON.split('-', 3)] + generatedon = date(*ymd) + except ValueError, err: + self.fail(err) + + delta = (datetime.now().date() - generatedon).days + self.assertTrue( + delta < 120, + "eng/client_error.py is more than 120 days old (%d)" % delta) + + def test__MYSQL_VERSION(self): + try: + from mysql.connector.locales.eng import client_error + except ImportError: + self.fail("locales.eng.client_error could not be imported") + + minimum = (5, 6, 6) + self.assertTrue(isinstance(client_error._MYSQL_VERSION, tuple)) + self.assertTrue(len(client_error._MYSQL_VERSION) == 3) + self.assertTrue(client_error._MYSQL_VERSION >= minimum) + + def test_messages(self): + try: + from mysql.connector.locales.eng import client_error + except ImportError: + self.fail("locales.eng.client_error could not be imported") + + errors = _get_client_errors() + + count = 0 + for name in dir(client_error): + if name.startswith('CR_'): + count += 1 + self.assertEqual(len(errors), count) + + for name in errors.keys(): + self.assertTrue(isinstance(getattr(client_error, name), unicode)) + + diff --git a/lib/tests/test_mysql_datatypes.py b/lib/tests/test_mysql_datatypes.py index 5973225..c76d638 100644 --- a/lib/tests/test_mysql_datatypes.py +++ b/lib/tests/test_mysql_datatypes.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for MySQL data types """ @@ -40,6 +40,7 @@ class TestsDataTypes(tests.MySQLConnectorTests): 'float': 'myconnpy_mysql_float', 'decimal': 'myconnpy_mysql_decimal', 'temporal': 'myconnpy_mysql_temporal', + 'temporal_year': 'myconnpy_mysql_temporal_year', } def _get_insert_stmt(self, tbl, cols): @@ -272,12 +273,12 @@ def test_temporal_datetime(self): """MySQL temporal date/time data types""" c = self.db.cursor() c.execute("SET SESSION time_zone = '+00:00'") + columns = [ 't_date', 't_datetime', 't_time', 't_timestamp', - 't_year_2', 't_year_4', ] c.execute("""CREATE TABLE %s ( @@ -286,7 +287,6 @@ def test_temporal_datetime(self): `t_datetime` DATETIME, `t_time` TIME, `t_timestamp` TIMESTAMP DEFAULT 0, - `t_year_2` YEAR(2), `t_year_4` YEAR(4), PRIMARY KEY (id) )""" % (self.tables['temporal'])) @@ -299,26 +299,22 @@ def test_temporal_datetime(self): datetime.datetime(2010,1,17,19,31,12), datetime.timedelta(hours=43,minutes=32,seconds=21), datetime.datetime(2010,1,17,19,31,12), - 10, 0), (datetime.date(1000,1,1), datetime.datetime(1000,1,1,0,0,0), datetime.timedelta(hours=-838,minutes=59,seconds=59), datetime.datetime(*time.gmtime(1)[:6]), - 70, 1901), (datetime.date(9999,12,31), datetime.datetime(9999,12,31,23,59,59), datetime.timedelta(hours=838,minutes=59,seconds=59), datetime.datetime(2038,1,19,3,14,7), - 69, 2155), ] c.executemany(insert, data) c.execute(select) rows = c.fetchall() - from pprint import pprint def compare(name, d, r): self.assertEqual(d,r,"%s %s != %s" % (name,d,r)) @@ -326,6 +322,20 @@ def compare(name, d, r): for j in (range(0,len(data))): for i,col in enumerate(columns): compare("%s (data[%d])" % (col,j),data[j][i],rows[j][i]) + + # Testing YEAR(2), which is now obsolete since MySQL 5.6.6 + tblname = self.tables['temporal_year'] + c.execute("CREATE TABLE %s (" + "`id` int NOT NULL AUTO_INCREMENT KEY, " + "`t_year_2` YEAR(2))" % tblname) + c.execute(self._get_insert_stmt(tblname, ['t_year_2']), (10,)) + c.execute(self._get_select_stmt(tblname, ['t_year_2'])) + row = c.fetchone() + + if tests.MYSQL_VERSION >= (5, 6, 6): + self.assertEqual(2010, row[0]) + else: + self.assertEqual(10, row[0]) c.close() diff --git a/lib/tests/test_network.py b/lib/tests/test_network.py new file mode 100644 index 0000000..07bc41b --- /dev/null +++ b/lib/tests/test_network.py @@ -0,0 +1,424 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +"""Unittests for mysql.connector.network +""" + +import os +import socket +import logging +from collections import deque + +import tests +from mysql.connector import (network, connection, errors, constants, utils) + +logger = logging.getLogger(tests.LOGGER_NAME) + +class NetworkTests(tests.MySQLConnectorTests): + """Testing mysql.connector.network functions""" + + def test__prepare_packets(self): + """Prepare packets for sending""" + data = ('abcdefghijklmn', 1) + exp = ['\x0e\x00\x00\x01abcdefghijklmn'] + self.assertEqual(exp, network._prepare_packets(*(data))) + + data = ('a' * (constants.MAX_PACKET_LENGTH + 1000), 2) + exp = [ + '\xff\xff\xff\x02' + ('a' * constants.MAX_PACKET_LENGTH), + '\xe8\x03\x00\x03' + ('a' * 1000) + ] + self.assertEqual(exp, network._prepare_packets(*(data))) + + +class BaseMySQLSocketTests(tests.MySQLConnectorTests): + """Testing mysql.connector.network.BaseMySQLSocket""" + def setUp(self): + config = self.getMySQLConfig() + self._host = config['host'] + self._port = config['port'] + self.cnx = network.BaseMySQLSocket() + + def tearDown(self): + try: + self.cnx.close_connection() + except: + pass + + def _get_socket(self): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + logger.debug("Get socket for %s:%d" % (self._host, self._port)) + sock.connect((self._host, self._port)) + return sock + + def test_init(self): + """MySQLSocket initialization""" + exp = { + 'sock': None, + '_connection_timeout': None, + '_packet_number': -1, + '_packet_queue': deque(), + 'recvsize': 1024*8, + } + + for key, value in exp.items(): + self.assertEqual(value, self.cnx.__dict__[key]) + + def test_next_packet_number(self): + """Test packet number property""" + self.assertEqual(0, self.cnx.next_packet_number) + self.assertEqual(0, self.cnx._packet_number) + self.assertEqual(1, self.cnx.next_packet_number) + self.assertEqual(1, self.cnx._packet_number) + self.cnx._packet_number = 255 + self.assertEqual(0, self.cnx.next_packet_number) + + def test_open_connection(self): + """Opening a connection""" + self.assertRaises(NotImplementedError, self.cnx.open_connection) + + def test_get_address(self): + """Get the address of a connection""" + self.assertRaises(NotImplementedError, self.cnx.get_address) + + def test_close_connection(self): + """Closing a connection""" + self.cnx.close_connection() + self.assertEqual(None, self.cnx.sock) + self.assertRaises(TypeError, self.cnx._packet_queue) + + def test_send_plain(self): + """Send plain data through the socket""" + data = 'asddfasdfasdf' + self.assertRaises(errors.OperationalError, self.cnx.send_plain, + data, 0) + + self.cnx.sock = tests.DummySocket() + data = [ + ('\x03\x53\x45\x4c\x45\x43\x54\x20\x22\x61\x62\x63\x22',1), + ('\x03\x53\x45\x4c\x45\x43\x54\x20\x22' + + ('\x61' * (constants.MAX_PACKET_LENGTH+1000)) + '\x22', 2)] + + self.assertRaises(Exception, self.cnx.send_plain, None, None) + + for buf in data: + exp = network._prepare_packets(*buf) + try: + self.cnx.send_plain(*buf) + except Exception, err: + self.fail("Failed sending pktnr %r: %s" % (buf[1], err)) + self.assertEqual(exp, self.cnx.sock._client_sends) + self.cnx.sock.reset() + + def test_send_compressed(self): + """Send compressed data through the socket""" + data = 'asddfasdfasdf' + self.assertRaises(errors.OperationalError, self.cnx.send_compressed, + data, 0) + + self.cnx.sock = tests.DummySocket() + self.assertRaises(Exception, self.cnx.send_compressed, None, None) + + # Small packet + data = ('\x03\x53\x45\x4c\x45\x43\x54\x20\x22\x61\x62\x63\x22', 1) + exp = ['\x11\x00\x00\x00\x00\x00\x00\r\x00\x00\x01\x03SELECT "abc"'] + try: + self.cnx.send_compressed(*data) + except Exception, e: + self.fail("Failed sending pktnr %r: %s" % (data[1], e)) + self.assertEqual(exp, self.cnx.sock._client_sends) + self.cnx.sock.reset() + + # Slightly bigger packet (not getting compressed) + data = ('\x03\x53\x45\x4c\x45\x43\x54\x20\x22\x61\x62\x63\x22', 1) + exp = (24, '\x11\x00\x00\x00\x00\x00\x00\x0d\x00\x00\x01\x03' + '\x53\x45\x4c\x45\x43\x54\x20\x22') + try: + self.cnx.send_compressed(*data) + except Exception, e: + self.fail("Failed sending pktnr %r: %s" % (data[1], e)) + received = self.cnx.sock._client_sends[0] + self.assertEqual(exp, (len(received), received[:20])) + self.cnx.sock.reset() + + # Big packet + data = ('\x03\x53\x45\x4c\x45\x43\x54\x20\x22' + + '\x61'*(constants.MAX_PACKET_LENGTH+1000) + '\x22', 2) + exp = [ + (63,'\x38\x00\x00\x00\x00\x40\x00\x78\x9c\xed\xc1\x31' + '\x0d\x00\x20\x0c\x00\xb0\x04\x8c'), + (16322, '\xbb\x3f\x00\x01\xf9\xc3\xff\x78\x9c\xec\xc1\x81' + '\x00\x00\x00\x00\x80\x20\xd6\xfd')] + try: + self.cnx.send_compressed(*data) + except Exception, e: + self.fail("Failed sending pktnr %r: %s" % (data[1], e)) + received = [ (len(r), r[:20]) for r in self.cnx.sock._client_sends ] + self.assertEqual(exp, received) + self.cnx.sock.reset() + + def test_recv_plain(self): + """Receive data from the socket""" + self.cnx.sock = tests.DummySocket() + def get_address(): + return 'dummy' + self.cnx.get_address = get_address + + # Receive a packet which is not 4 bytes long + self.cnx.sock.add_packet('\01\01\01') + self.assertRaises(errors.InterfaceError, self.cnx.recv_plain) + + # Receive the header of a packet, but nothing more + self.cnx.sock.add_packet('\01\00\00\00') + self.assertRaises(errors.InterfaceError, self.cnx.recv_plain) + + # Socket fails to receive and produces an error + self.cnx.sock.raise_socket_error() + self.assertRaises(errors.InterfaceError, self.cnx.recv_plain) + + # Receive packets after a query, SELECT "Ham" + exp = [ + '\x01\x00\x00\x01\x01', + '\x19\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x03\x48\x61\x6d\x00'\ + '\x0c\x21\x00\x09\x00\x00\x00\xfd\x01\x00\x1f\x00\x00', + '\x05\x00\x00\x03\xfe\x00\x00\x02\x00', + '\x04\x00\x00\x04\x03\x48\x61\x6d', + '\x05\x00\x00\x05\xfe\x00\x00\x02\x00', + ] + self.cnx.sock.reset() + self.cnx.sock.add_packets(exp) + length_exp = len(exp) + result = [] + packet = self.cnx.recv_plain() + while packet: + result.append(packet) + if length_exp == len(result): + break + packet = self.cnx.recv_plain() + self.assertEqual(exp, result) + + def test_recv_compressed(self): + """Receive compressed data from the socket""" + self.cnx.sock = tests.DummySocket() + def get_address(): + return 'dummy' + self.cnx.get_address = get_address + + # Receive a packet which is not 7 bytes long + self.cnx.sock.add_packet('\01\01\01\01\01\01') + self.assertRaises(errors.InterfaceError, self.cnx.recv_compressed) + + # Receive the header of a packet, but nothing more + self.cnx.sock.add_packet('\01\00\00\00\00\00\00') + self.assertRaises(errors.InterfaceError, self.cnx.recv_compressed) + + # Socket fails to receive and produces an error + self.cnx.sock.raise_socket_error() + self.assertRaises(errors.InterfaceError, self.cnx.recv_compressed) + + # Receive result of query SELECT REPEAT('a',1*1024*1024), 'XYZ' + packets = ( + "\x80\x00\x00\x01\x00\x40\x00\x78\x9c\xed\xcb\xbd\x0a\x81\x01" + "\x14\xc7\xe1\xff\xeb\xa5\x28\x83\x4d\x26\x99\x7c\x44\x21\x37" + "\x60\xb0\x4b\x06\x6c\x0a\xd7\xe3\x7a\x15\x79\xc9\xec\x0a\x9e" + "\x67\x38\x9d\xd3\xaf\x53\x24\x45\x6d\x96\xd4\xca\xcb\xf5\x96" + "\xa4\xbb\xdb\x6c\x37\xeb\xfd\x68\x78\x1e\x4e\x17\x93\xc5\x7c" + "\xb9\xfa\x8e\x71\xda\x83\xaa\xde\xf3\x28\xd2\x4f\x7a\x49\xf9" + "\x7b\x28\x0f\xc7\xd3\x27\xb6\xaa\xfd\xf9\x8d\x8d\xa4\xfe\xaa" + "\xae\x34\xd3\x69\x3c\x93\xce\x19\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\xf8\xeb\x0d\xe7\xa5\x29\xb8", + + "\x05\x04\x00\x02\x68\xc0\x0f\x78\x9c\xed\xc1\x31\x01\x00\x00" + "\x08\x03\xa0\xc3\x92\xea\xb7\xfe\x25\x8c\x60\x01\x20\x01" + + "\x00"*999+"\xe0\x53\x3d\x7b\x0a\x29\x40\x7b" + ) + exp = [ + "\x01\x00\x00\x01\x02", + "\x2d\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x17\x52\x45\x50" + "\x45\x41\x54\x28\x27\x61\x27\x2c\x31\x2a\x31\x30\x32\x34\x2a" + "\x31\x30\x32\x34\x29\x00\x0c\x21\x00\x00\x00\x90\x00\xfa\x01" + "\x00\x1f\x00\x00", + "\x19\x00\x00\x03\x03\x64\x65\x66\x00\x00\x00\x03\x58\x59\x5a" + "\x00\x0c\x21\x00\x09\x00\x00\x00\xfd\x01\x00\x1f\x00\x00", + "\x05\x00\x00\x04\xfe\x00\x00\x00\x00", + "\x08\x00\x10\x05\xfd\x00\x00\x10" + + "\x61"*1*1024*1024+"\x03\x58\x59\x5a" + ] + self.cnx.sock.reset() + self.cnx.sock.add_packets(packets) + length_exp = len(exp) + packet = self.cnx.recv_compressed() + counter = 0 + while packet and counter < length_exp: + self.assertEqual(exp[counter], packet) + packet = self.cnx.recv_compressed() + counter += 1 + + def test_set_connection_timeout(self): + """Set the connection timeout""" + exp = 5 + self.cnx.set_connection_timeout(exp) + self.assertEqual(exp, self.cnx._connection_timeout) + + def test_switch_to_ssl(self): + """Switch the socket to use SSL""" + if not tests.SSL_AVAILABLE: + tests.MESSAGES['WARNINGS'].append( + "Could not test switch to SSL. Make sure Python supports SSL.") + return + + args = { + 'ca': os.path.join(tests.SSL_DIR, 'tests_CA_cert.pem'), + 'cert': os.path.join(tests.SSL_DIR, 'tests_client_cert.pem'), + 'key': os.path.join(tests.SSL_DIR, 'tests_client_key.pem'), + } + self.assertRaises(errors.InterfaceError, + self.cnx.switch_to_ssl, **args) + + # Handshake failure + (family, socktype, proto) = socket.getaddrinfo(self._host, + self._port)[0][0:3] + sock = socket.socket(family, socktype, proto) + sock.settimeout(4) + sock.connect((self._host, self._port)) + self.cnx.sock = sock + self.assertRaises(errors.InterfaceError, + self.cnx.switch_to_ssl, **args) + +class MySQLUnixSocketTests(tests.MySQLConnectorTests): + """Testing mysql.connector.network.MySQLUnixSocket""" + def setUp(self): + config = self.getMySQLConfig() + self._unix_socket = config['unix_socket'] + self.cnx = network.MySQLUnixSocket(unix_socket=config['unix_socket']) + + def tearDown(self): + try: + self.cnx.close_connection() + except: + pass + + def test_init(self): + """MySQLUnixSocket initialization""" + exp = { + '_unix_socket': self._unix_socket, + } + + for key, value in exp.items(): + self.assertEqual(value, self.cnx.__dict__[key]) + + def test_get_address(self): + """Get path to the Unix socket""" + exp = self._unix_socket + self.assertEqual(exp, self.cnx.get_address()) + + def test_open_connection(self): + """Open a connection using a Unix socket""" + if os.name == 'nt': + self.assertRaises(errors.InterfaceError, self.cnx.open_connection) + else: + try: + self.cnx.open_connection() + except Exception, err: + self.fail(str(err)) + +class MySQLTCPSocketTests(tests.MySQLConnectorTests): + """Testing mysql.connector.network..MySQLTCPSocket""" + def setUp(self): + config = self.getMySQLConfig() + self._host = config['host'] + self._port = config['port'] + self.cnx = network.MySQLTCPSocket(host=self._host, port=self._port) + + def tearDown(self): + try: + self.cnx.close_connection() + except: + pass + + def test_init(self): + """MySQLTCPSocket initialization""" + exp = { + 'server_host': self._host, + 'server_port': self._port, + 'force_ipv6': False, + '_family': 0, + } + + for key, value in exp.items(): + self.assertEqual(value, self.cnx.__dict__[key]) + + def test_get_address(self): + """Get TCP/IP address""" + exp = "%s:%s" % (self._host, self._port) + self.assertEqual(exp, self.cnx.get_address()) + + def test_open_connection(self): + """Open a connection using TCP""" + try: + self.cnx.open_connection() + except Exception, err: + self.fail(str(err)) + + config = self.getMySQLConfig() + self._host = config['host'] + self._port = config['port'] + + cases = [ + # Address, Expected Family, Should Raise, Force IPv6 + ] + if not tests.IPV6_AVAILABLE: + tests.MESSAGES['WARNINGS'].append( + "MySQLTCPSocketTests.test_open_connection: " + "Could not test IPv6. Use options " + "--bind-address=:: --host=::1 and" + " make sure the OS and Python supports IPv6.") + cases.extend([ + (tests.MYSQL_CONFIG['host'], socket.AF_INET, False, False), + ]) + else: + # Testing IPv6 + cases.extend([ + ('::1', socket.AF_INET6, False, False), + ('2001::14:06:77', socket.AF_INET6, True, False), + ('xx:00:xx', socket.AF_INET6, True, False), + ]) + + for addr, family, should_raise, force in cases: + try: + sock = network.MySQLTCPSocket(host=addr, + port=self._port, + force_ipv6=force) + sock.set_connection_timeout(1) + sock.open_connection() + except (errors.InterfaceError, socket.error), err: + if not should_raise: + self.fail('%s incorrectly raised socket.error' % addr) + else: + if should_raise: + self.fail('%s should have raised socket.error' % addr) + else: + self.assertEqual(family, sock._family, + "Family for %s did not match: %d != %d" % ( + addr, family, sock._family)) + sock.close_connection() diff --git a/lib/tests/test_pep249.py b/lib/tests/test_pep249.py index 94756bc..53b3af5 100644 --- a/lib/tests/test_pep249.py +++ b/lib/tests/test_pep249.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for PEP-249 diff --git a/lib/tests/test_protocol.py b/lib/tests/test_protocol.py index 9aec36c..43b7158 100644 --- a/lib/tests/test_protocol.py +++ b/lib/tests/test_protocol.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for mysql.connector.protocol """ @@ -29,180 +29,46 @@ from collections import deque import tests -from mysql.connector import connection, cursor, conversion, protocol, utils, errors, constants +from mysql.connector import (connection, network, + protocol, errors, constants) -class Decorators(tests.MySQLConnectorTests): - - def test_packet_is_error(self): - """Decorator raises when buffer is an Error packet""" - errpkt = \ - '\x47\x00\x00\x02\xff\x15\x04\x23\x32\x38\x30\x30\x30'\ - '\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69\x65\x64'\ - '\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20\x27\x68\x61'\ - '\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74'\ - '\x27\x20\x28\x75\x73\x69\x6e\x67\x20\x70\x61\x73\x73'\ - '\x77\x6f\x72\x64\x3a\x20\x59\x45\x53\x29' +OK_PACKET = '\x07\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00' +OK_PACKET_RESULT = { + 'insert_id': 0, + 'affected_rows': 1, + 'field_count': 0, + 'warning_count': 1, + 'server_status': 0 + } - @protocol.packet_is_error(0) - def check(buf): - self.fail("Decorator did not raise error") - self.assertRaises(errors.OperationalError,check,errpkt) - - @protocol.packet_is_error(99) - def check(buf): - self.fail("Decorator did not raise error") - self.assertRaises(errors.InterfaceError,check,errpkt) - - noterr = '\x05\x00\x00\x00\xfe\x01' - self.assertRaises(errors.InterfaceError,check,noterr) - - def test_packet_is_ok(self): - """Decorator raises when buffer is not OK packet""" - - okpkt = '\x01\x00\x00\x01\x00' - - @protocol.packet_is_ok(0) - def check(buf): - return True - self.assertEqual(True,check(okpkt)) - - @protocol.packet_is_ok(99) - def check(buf): - return True - self.assertRaises(errors.InterfaceError,check,okpkt) - - notok = '\x01\x00\x00\x00\xff' - self.assertRaises(errors.InterfaceError,check,notok) - - def test_packet_is_eof(self): - """Decorator raises when buffer is not EOF packet""" - - eofpkt = '\x01\x00\x00\x00\xfe\x00\x00\x00\x00' - - @protocol.packet_is_eof(0) - def check(buf): - return True - self.assertEqual(True,check(eofpkt)) - - @protocol.packet_is_eof(99) - def check(buf): - return True - self.assertRaises(errors.InterfaceError,check,eofpkt) - - noteof = '\x01\x00\x00\x00\xff' - self.assertRaises(errors.InterfaceError,check,noteof) - - def test_set_pktnr(self): - """Decorator sets the pktnr-member from given packet""" - - pkt = '\x01\x00\x00\x01\x00' - - class Check(object): - - @protocol.set_pktnr(1) - def check(self, buf): - pass - - @protocol.set_pktnr(2) - def check_broken(self, buf): - pass - - @protocol.set_pktnr(1) - def check_broken2(self, buf): - pass - - c = Check() - c.check(pkt) - self.assertEqual(1,c.pktnr) - self.assertRaises(errors.InterfaceError,c.check_broken,pkt) - - pkt = '\x01\x00\x00' - self.assertRaises(errors.InterfaceError,c.check_broken2,pkt) - - def test_reset_pktnr(self): - """Decorator resetting pktnr-member to -1""" - - class Check(object): - - @protocol.reset_pktnr - def check(self): - pass - - c = Check() - c.pktnr = 2 - c.check() - self.assertEqual(-1,c.pktnr) +ERR_PACKET = '\x47\x00\x00\x02\xff\x15\x04\x23\x32\x38\x30\x30\x30'\ + '\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69\x65\x64'\ + '\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20\x27\x68\x61'\ + '\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74'\ + '\x27\x20\x28\x75\x73\x69\x6e\x67\x20\x70\x61\x73\x73'\ + '\x77\x6f\x72\x64\x3a\x20\x59\x45\x53\x29' -class MySQLProtocol(tests.MySQLConnectorTests): - - def setUp(self): - - class Cnx(object): - def queue_buffer(self, pkts): - for p in pkts: - self.buf.append(p) - - def __init__(self): - self.buf = deque() - - def recv(self): - try: - buf = self.buf.popleft() - if buf[4] == '\xff': - errors.raise_error(buf) - else: - return buf - except IndexError: - pass - - def send(self, buf, pktnr=None): - pass - - def open_connection(self): - pass - - self.cnx = Cnx() - self.prtcl = protocol.MySQLProtocol(self.cnx) - - def _disabled_test_MySQLProtocol_raise_error(self): - """Raise an errors.Error when buffer has a MySQL error - """ - errpkt = \ - '\x47\x00\x00\x02\xff\x15\x04\x23\x32\x38\x30\x30\x30'\ - '\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69\x65\x64'\ - '\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20\x27\x68\x61'\ - '\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c\x68\x6f\x73\x74'\ - '\x27\x20\x28\x75\x73\x69\x6e\x67\x20\x70\x61\x73\x73'\ - '\x77\x6f\x72\x64\x3a\x20\x59\x45\x53\x29' +EOF_PACKET = '\x05\x00\x00\x00\xfe\x00\x00\x00\x00' +EOF_PACKET_RESULT = {'status_flag': 0, 'warning_count': 0} - self.assertRaises(errors.OperationalError, - protocol.MySQLProtocol.raise_error,errpkt) +SEED = '\x3b\x55\x78\x7d\x2c\x5f\x7c\x72\x49\x52'\ + '\x3f\x28\x47\x6f\x77\x28\x5f\x28\x46\x69' + +class MySQLProtocolTests(tests.MySQLConnectorTests): + def setUp(self): + self._protocol = protocol.MySQLProtocol() - try: - protocol.MySQLProtocol.raise_error(errpkt) - except errors.Error, e: - self.assertEqual(1045,e.errno) - - def test___init__(self): - """Initializing a MySQLProtocol instance""" - self.assertEqual(0,self.prtcl.client_flags) - self.assertEqual(self.cnx,self.prtcl.conn) - self.assertEqual(-1,self.prtcl.pktnr) - def test__scramble_password(self): """Scramble a password ready to send to MySQL""" password = 'spam' - seed = '\x3b\x55\x78\x7d\x2c\x5f\x7c\x72\x49\x52'\ - '\x3f\x28\x47\x6f\x77\x28\x5f\x28\x46\x69' hashed = '\x3a\x07\x66\xba\xba\x01\xce\xbe\x55\xe6'\ '\x29\x88\xaa\xae\xdb\x00\xb3\x4d\x91\x5b' - self.assertEqual(hashed,self.prtcl._scramble_password(password,seed)) + res = self._protocol._scramble_password(password, SEED) + self.assertEqual(hashed, res) - def test__pkt_make_auth(self): + def test_make_auth(self): """Make a MySQL authentication packet""" - seed = '\x3b\x55\x78\x7d\x2c\x5f\x7c\x72\x49\x52'\ - '\x3f\x28\x47\x6f\x77\x28\x5f\x28\x46\x69' exp = { 'allset':\ '\x0d\xa2\x03\x00\x00\x00\x00\x40'\ @@ -232,52 +98,73 @@ def test__pkt_make_auth(self): '\x5b\x00', } flags = constants.ClientFlag.get_default() - kwargs = dict(username='ham',password='spam', - database='test',seed=None, charset=33, - client_flags=flags) + kwargs = { + 'seed': None, + 'username': 'ham', + 'password': 'spam', + 'database': 'test', + 'charset': 33, + 'client_flags': flags} + self.assertRaises(errors.ProgrammingError, - self.prtcl._pkt_make_auth,**kwargs) + self._protocol.make_auth, **kwargs) - kwargs['seed'] = seed - res = self.prtcl._pkt_make_auth(**kwargs) - self.assertEqual(exp['allset'],res) - - kwargs['seed'] = None - self.prtcl.scramble = seed - res = self.prtcl._pkt_make_auth(**kwargs) - self.assertEqual(exp['allset'],res) + kwargs['seed'] = SEED + res = self._protocol.make_auth(**kwargs) + self.assertEqual(exp['allset'], res) kwargs['password'] = None - res = self.prtcl._pkt_make_auth(**kwargs) - self.assertEqual(exp['nopass'],res) + res = self._protocol.make_auth(**kwargs) + self.assertEqual(exp['nopass'], res) kwargs['password'] = 'spam' kwargs['database'] = None - res = self.prtcl._pkt_make_auth(**kwargs) - self.assertEqual(exp['nodb'],res) + res = self._protocol.make_auth(**kwargs) + self.assertEqual(exp['nodb'], res) kwargs['username'] = None kwargs['database'] = 'test' - res = self.prtcl._pkt_make_auth(**kwargs) - self.assertEqual(exp['nouser'],res) + res = self._protocol.make_auth(**kwargs) + self.assertEqual(exp['nouser'], res) + + def test_make_auth_ssl(self): + """Make a SSL authentication packet""" + cases = [ + ({}, + '\x00\x00\x00\x00\x00\x00\x00\x40\x21\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00'), + ({'charset': 8}, + '\x00\x00\x00\x00\x00\x00\x00\x40\x08\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00'), + ({'client_flags': 240141}, + '\x0d\xaa\x03\x00\x00\x00\x00\x40\x21\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00'), + ({'charset': 8, 'client_flags': 240141, + 'max_allowed_packet': 2147483648}, + '\x0d\xaa\x03\x00\x00\x00\x00\x80\x08\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ + '\x00\x00\x00\x00'), + ] + for kwargs, exp in cases: + self.assertEqual(exp, self._protocol.make_auth_ssl(**kwargs)) - def test__pkt_make_command(self): + def test_make_command(self): """Make a generic MySQL command packet""" exp = '\x01\x68\x61\x6d' - res = self.prtcl._pkt_make_command(1,'ham') + res = self._protocol.make_command(1, 'ham') self.assertEqual(exp, res) - res = self.prtcl._pkt_make_command(1,argument='ham') + res = self._protocol.make_command(1, argument='ham') self.assertEqual(exp, res) exp = '\x03' - res = self.prtcl._pkt_make_command(3) + res = self._protocol.make_command(3) self.assertEqual(exp, res) - def test__pkt_make_changeuser(self): + def test_make_change_user(self): """Make a change user MySQL packet""" - seed = '\x3b\x55\x78\x7d\x2c\x5f\x7c\x72\x49\x52'\ - '\x3f\x28\x47\x6f\x77\x28\x5f\x28\x46\x69' - exp = { 'allset':\ '\x11\x68\x61\x6d\x00\x14\x3a\x07'\ @@ -288,26 +175,26 @@ def test__pkt_make_changeuser(self): '\x11\x68\x61\x6d\x00\x00\x74\x65'\ '\x73\x74\x00\x08\x00', } - kwargs = dict(username='ham', - password='spam', database='test', - charset=8, seed=None) + kwargs = { + 'seed': None, + 'username': 'ham', + 'password': 'spam', + 'database': 'test', + 'charset': 8, + 'client_flags': constants.ClientFlag.get_default() + } self.assertRaises(errors.ProgrammingError, - self.prtcl._pkt_make_changeuser,**kwargs) - self.prtcl.client_flags = constants.ClientFlag.get_default() - kwargs['seed'] = seed - res = self.prtcl._pkt_make_changeuser(**kwargs) - self.assertEqual(exp['allset'],res) + self._protocol.make_change_user, **kwargs) - kwargs['seed'] = None - self.prtcl.scramble = seed - res = self.prtcl._pkt_make_changeuser(**kwargs) - self.assertEqual(exp['allset'],res) + kwargs['seed'] = SEED + res = self._protocol.make_change_user(**kwargs) + self.assertEqual(exp['allset'], res) kwargs['password'] = None - res = self.prtcl._pkt_make_changeuser(**kwargs) - self.assertEqual(exp['nopass'],res) + res = self._protocol.make_change_user(**kwargs) + self.assertEqual(exp['nopass'], res) - def test__pkt_parse_handshake(self): + def test_parse_handshake(self): """Parse handshake-packet sent by MySQL""" handshake = \ '\x47\x00\x00\x00\x0a\x35\x2e\x30\x2e\x33\x30\x2d'\ @@ -327,444 +214,41 @@ def test__pkt_parse_handshake(self): 'scramble': 'h4i6oP!OLng9&PD@WrYH' } - res = self.prtcl._pkt_parse_handshake(handshake) - self.assertEqual(exp,res) + res = self._protocol.parse_handshake(handshake) + self.assertEqual(exp, res) - def test__pkt_parse_ok(self): + def test_parse_ok(self): """Parse OK-packet sent by MySQL""" - okpkt = '\x07\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00' - exp = { - 'insert_id': 0, - 'affected_rows': 1, - 'field_count': 0, - 'warning_count': 1, - 'server_status': 0 - } - res = self.prtcl._pkt_parse_ok(okpkt) - self.assertEqual(exp,res) + res = self._protocol.parse_ok(OK_PACKET) + self.assertEqual(OK_PACKET_RESULT, res) - okpkt += '\x04spam' + okpkt = OK_PACKET + '\x04spam' + exp = OK_PACKET_RESULT.copy() exp['info_msg'] = 'spam' - res = self.prtcl._pkt_parse_ok(okpkt) - self.assertEqual(exp,res) + res = self._protocol.parse_ok(okpkt) + self.assertEqual(exp, res) + + def test_parse_column_count(self): + """Parse the number of columns""" + packet = '\x01\x00\x00\x01\x03' + res = self._protocol.parse_column_count(packet) + self.assertEqual(3, res) - def test__pkt_parse_field(self): + def test_parse_column(self): """Parse field-packet sent by MySQL""" - fldpkt = \ + column_packet = \ '\x1a\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x04'\ '\x53\x70\x61\x6d\x00\x0c\x21\x00\x09\x00\x00\x00'\ '\xfd\x01\x00\x1f\x00\x00' exp = ('Spam', 253, None, None, None, None, 0, 1) - res = self.prtcl._pkt_parse_field(fldpkt) - self.assertEqual(exp,res) + res = self._protocol.parse_column(column_packet) + self.assertEqual(exp, res) - def test__pkt_parse_eof(self): + def test_parse_eof(self): """Parse EOF-packet sent by MySQL""" - eofpkt = '\x05\x00\x00\x05\xfe\x01\x00\x00\x00' - exp = {'status_flag': 0, 'warning_count': 1} - res = self.prtcl._pkt_parse_eof(eofpkt) - self.assertEqual(exp,res) - - def test_do_handshake(self): - """Get handshake sent by MySQL""" - handshake = \ - '\x47\x00\x00\x00\x0a\x35\x2e\x30\x2e\x33\x30\x2d'\ - '\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65\x2d\x67'\ - '\x70\x6c\x2d\x6c\x6f\x67\x00\x09\x01\x00\x00\x68'\ - '\x34\x69\x36\x6f\x50\x21\x4f\x00\x2c\xa2\x08\x02'\ - '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72'\ - '\x59\x48\x00' - exp = { - 'protocol': 10, - 'server_version_original': '5.0.30-enterprise-gpl-log', - 'charset': 8, - 'server_threadid': 265, - 'capabilities': 41516, - 'server_status': 2, - 'scramble': 'h4i6oP!OLng9&PD@WrYH' - } - self.prtcl.conn.buf.append(handshake) - self.prtcl.do_handshake() - for k,v in exp.items(): - self.assertEqual(self.prtcl.__dict__[k],exp[k]) - - def test_do_auth(self): - """Authenticate with the MySQL server""" - seed = '\x3b\x55\x78\x7d\x2c\x5f\x7c\x72\x49\x52'\ - '\x3f\x28\x47\x6f\x77\x28\x5f\x28\x46\x69' - okpkt = '\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00' - self.prtcl.scramble = seed - flags = constants.ClientFlag.get_default() - kwargs = dict(username='ham',password='spam', - database='test',charset=33, client_flags=flags) - - self.prtcl.conn.buf.append( - '\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00') - res = self.prtcl.do_auth(**kwargs) - self.assertEqual(True,res) - - self.prtcl.conn.buf.append('\x01\x00\x00\x02\xfe') - self.assertRaises(errors.NotSupportedError, - self.prtcl.do_auth,**kwargs) - - self.prtcl.conn.queue_buffer([ - '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00', - '\x07\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00']) - flags &= ~constants.ClientFlag.CONNECT_WITH_DB - kwargs['client_flags'] = flags - res = self.prtcl.do_auth(**kwargs) - self.assertEqual(True,res) - - self.prtcl.conn.queue_buffer([ - '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00', - '\x24\x00\x00\x01\xff\x19\x04\x23\x34\x32\x30\x30'\ - '\x30\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x64\x61\x74'\ - '\x61\x62\x61\x73\x65\x20\x27\x61\x73\x64\x66\x61'\ - '\x73\x64\x66\x27']) - flags &= ~constants.ClientFlag.CONNECT_WITH_DB - kwargs['client_flags'] = flags - self.assertRaises(errors.ProgrammingError,self.prtcl.do_auth,**kwargs) - - def test_handle_handshake(self): - """Handle the handshake-packet sent by MySQL""" - handshake = \ - '\x47\x00\x00\x00\x0a\x35\x2e\x30\x2e\x33\x30\x2d'\ - '\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65\x2d\x67'\ - '\x70\x6c\x2d\x6c\x6f\x67\x00\x09\x01\x00\x00\x68'\ - '\x34\x69\x36\x6f\x50\x21\x4f\x00\x2c\xa2\x08\x02'\ - '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72'\ - '\x59\x48\x00' - exp = { - 'protocol': 10, - 'server_version_original': '5.0.30-enterprise-gpl-log', - 'charset': 8, - 'server_threadid': 265, - 'capabilities': 41516, - 'server_status': 2, - 'scramble': 'h4i6oP!OLng9&PD@WrYH' - } - - self.prtcl.handle_handshake(handshake) - for k,v in exp.items(): - self.assertEqual(self.prtcl.__dict__[k],exp[k]) - - self.assertRaises(errors.InterfaceError, - self.prtcl.handle_handshake, - '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00') - - def test__handle_ok(self): - """Handle an OK-packet sent by MySQL""" - okpkt = '\x07\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00' - exp = { - 'insert_id': 0, - 'affected_rows': 1, - 'field_count': 0, - 'warning_count': 1, - 'server_status': 0 - } - - self.assertEqual(exp,self.prtcl._handle_ok(okpkt)) - self.assertRaises(errors.InterfaceError, - self.prtcl._handle_ok,'\x01\x00\x00\x02\xfe') - - def test__handle_eof(self): - """Handle an EOF-packet sent by MySQL""" - eofpkt = '\x05\x00\x00\x05\xfe\x01\x00\x00\x00' - exp = {'status_flag': 0, 'warning_count': 1} - - self.assertEqual(exp,self.prtcl._handle_eof(eofpkt)) - - okpkt = '\x07\x00\x00\x01\x00\x01\x00\x00\x00\x01\x00' - self.assertRaises(errors.InterfaceError, - self.prtcl._handle_eof,okpkt) - - def test__handle_resultset(self): - """Handle a resultset sent by MySQL""" - def fillbuffer(): - self.prtcl.conn.queue_buffer([ - '\x17\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x01'\ - '\x31\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ - '\x00\x00\x00', - '\x05\x00\x00\x03\xfe\x00\x00\x00\x00']) - - fillbuffer() - exp = (1, - [('1', 8, None, None, None, None, 0, 129)], - {'status_flag': 0, 'warning_count': 0} - ) - res = self.prtcl._handle_resultset('\x01\x00\x00\x01\x01') - self.assertEqual(exp,res) - - fillbuffer() - self.prtcl.conn.buf[1] = '\x00' - - self.assertRaises(errors.InterfaceError, - self.prtcl._handle_resultset,'\x01\x00\x00\x01\x00') - - def __helper_get_rows_buffer(self, pkts=None): - pkts = pkts or [ - '\x07\x00\x00\x04\x06\x4d\x79\x49\x53\x41\x4d', - '\x07\x00\x00\x05\x06\x49\x6e\x6e\x6f\x44\x42', - '\x0a\x00\x00\x06\x09\x42\x4c\x41\x43\x4b\x48\x4f\x4c\x45', - '\x04\x00\x00\x07\x03\x43\x53\x56', - '\x07\x00\x00\x08\x06\x4d\x45\x4d\x4f\x52\x59', - '\x0a\x00\x00\x09\x09\x46\x45\x44\x45\x52\x41\x54\x45\x44', - '\x08\x00\x00\x0a\x07\x41\x52\x43\x48\x49\x56\x45', - '\x0b\x00\x00\x0b\x0a\x4d\x52\x47\x5f\x4d\x59\x49\x53\x41\x4d', - '\x05\x00\x00\x0c\xfe\x00\x00\x20\x00', - ] - self.prtcl.conn.queue_buffer(pkts) - - def test_get_rows(self): - """Get rows from the MySQL resultset""" - self.__helper_get_rows_buffer() - exp = ( - [('MyISAM',), ('InnoDB',), ('BLACKHOLE',), ('CSV',), ('MEMORY',), - ('FEDERATED',), ('ARCHIVE',), ('MRG_MYISAM',)], - {'status_flag': 32, 'warning_count': 0} - ) - res = self.prtcl.get_rows() - self.assertEqual(exp,res) - - self.__helper_get_rows_buffer() - rows = exp[0] - i = 0 - while i < len(rows): - exp = (rows[i:i+2], None) - res = self.prtcl.get_rows(2) - self.assertEqual(exp,res) - i += 2 - exp = ([], {'status_flag': 32, 'warning_count': 0}) - self.assertEqual(exp,self.prtcl.get_rows()) - - def test_get_row(self): - """Get a row from the MySQL resultset""" - self.__helper_get_rows_buffer() - expall = ( - [('MyISAM',), ('InnoDB',), ('BLACKHOLE',), ('CSV',), ('MEMORY',), - ('FEDERATED',), ('ARCHIVE',), ('MRG_MYISAM',)], - {'status_flag': 32, 'warning_count': 0} - ) - - rows = expall[0] - for row in rows: - res = self.prtcl.get_row() - exp = (row,None) - self.assertEqual(exp,res) - exp = ([], {'status_flag': 32, 'warning_count': 0}) - self.assertEqual(exp,self.prtcl.get_rows()) - - def test_cmd_query(self): - """Send a query to MySQL""" - self.prtcl.conn.buf.append( - '\x07\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00') - exp = {'insert_id': 0, 'affected_rows': 0, 'field_count': 0, - 'warning_count': 0, 'server_status': 0} - res = self.prtcl.cmd_query("SET AUTOCOMMIT = OFF") - self.assertEqual(exp,res) - - # query = "SET AUTOCOMMIT=OFF" - self.prtcl.conn.queue_buffer([ - '\x01\x00\x00\x01\x01', - '\x17\x00\x00\x02\x03\x64\x65\x66\x00\x00\x00\x01'\ - '\x31\x00\x0c\x3f\x00\x01\x00\x00\x00\x08\x81\x00'\ - '\x00\x00\x00', - '\x05\x00\x00\x03\xfe\x00\x00\x00\x00']) - exp = (1, [('1', 8, None, None, None, None, 0, 129)]) - res = self.prtcl.cmd_query("SELECT 1") - self.assertEqual(exp,res) - - def test_cmd_refresh(self): - """Send the Refresh-command to MySQL""" - self.prtcl.conn.buf.append( - '\x07\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00') - refresh = constants.RefreshOption.LOG|constants.RefreshOption.THREADS - - exp = {'insert_id': 0, 'affected_rows': 0, 'field_count': 0, - 'warning_count': 0, 'server_status': 0} - self.assertEqual(exp, self.prtcl.cmd_refresh(refresh)) - - def test_cmd_quit(self): - """Send the Quit-command to MySQL""" - exp = '\x01' - self.assertEqual(exp, self.prtcl.cmd_quit()) - - def test_cmd_init_db(self): - """Send the Init_db-command to MySQL""" - self.prtcl.conn.buf.append( - '\x07\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00') - exp = {'insert_id': 0, 'affected_rows': 0, 'field_count': 0, - 'warning_count': 0, 'server_status': 0} - self.assertEqual(exp, self.prtcl.cmd_init_db('test')) - - self.prtcl.conn.buf.append( - '\x2c\x00\x00\x01\xff\x19\x04\x23\x34\x32\x30\x30'\ - '\x30\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x64\x61\x74'\ - '\x61\x62\x61\x73\x65\x20\x27\x75\x6e\x6b\x6e\x6f'\ - '\x77\x6e\x5f\x64\x61\x74\x61\x62\x61\x73\x65\x27' - ) - self.assertRaises(errors.ProgrammingError, - self.prtcl.cmd_init_db,'unknown_database') - - def test_cmd_shutdown(self): - """Send the Shutdown-command to MySQL""" - - self.prtcl.conn.buf.append( - '\x05\x00\x00\x01\xfe\x00\x00\x00\x00') - exp = {'status_flag': 0, 'warning_count': 0} - self.assertEqual(exp, self.prtcl.cmd_shutdown()) - - self.prtcl.conn.buf.append( - '\x4a\x00\x00\x01\xff\xcb\x04\x23\x34\x32\x30\x30'\ - '\x30\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69'\ - '\x65\x64\x3b\x20\x79\x6f\x75\x20\x6e\x65\x65\x64'\ - '\x20\x74\x68\x65\x20\x53\x48\x55\x54\x44\x4f\x57'\ - '\x4e\x20\x70\x72\x69\x76\x69\x6c\x65\x67\x65\x20'\ - '\x66\x6f\x72\x20\x74\x68\x69\x73\x20\x6f\x70\x65'\ - '\x72\x61\x74\x69\x6f\x6e' - ) - self.assertRaises(errors.OperationalError, - self.prtcl.cmd_shutdown) - - def test_cmd_statistics(self): - """Send the Statistics-command to MySQL""" - goodpkt = '\x88\x00\x00\x01\x55\x70\x74\x69\x6d\x65\x3a\x20'\ - '\x31\x34\x36\x32\x34\x35\x20\x20\x54\x68\x72\x65'\ - '\x61\x64\x73\x3a\x20\x32\x20\x20\x51\x75\x65\x73'\ - '\x74\x69\x6f\x6e\x73\x3a\x20\x33\x36\x33\x35\x20'\ - '\x20\x53\x6c\x6f\x77\x20\x71\x75\x65\x72\x69\x65'\ - '\x73\x3a\x20\x30\x20\x20\x4f\x70\x65\x6e\x73\x3a'\ - '\x20\x33\x39\x32\x20\x20\x46\x6c\x75\x73\x68\x20'\ - '\x74\x61\x62\x6c\x65\x73\x3a\x20\x31\x20\x20\x4f'\ - '\x70\x65\x6e\x20\x74\x61\x62\x6c\x65\x73\x3a\x20'\ - '\x36\x34\x20\x20\x51\x75\x65\x72\x69\x65\x73\x20'\ - '\x70\x65\x72\x20\x73\x65\x63\x6f\x6e\x64\x20\x61'\ - '\x76\x67\x3a\x20\x30\x2e\x32\x34' - self.prtcl.conn.buf.append(goodpkt) - exp = {'Uptime': 146245L, 'Open tables': 64L, - 'Queries per second avg': decimal.Decimal('0.24'), - 'Slow queries': 0L, 'Threads': 2L, 'Questions': 3635L, - 'Flush tables': 1L, 'Opens': 392L} - self.assertEqual(exp, self.prtcl.cmd_statistics()) - - badpkt = '\x88\x00\x00\x01\x55\x70\x74\x69\x6d\x65\x3a\x20'\ - '\x31\x34\x36\x32\x34\x35\x20\x54\x68\x72\x65'\ - '\x61\x64\x73\x3a\x20\x32\x20\x20\x51\x75\x65\x73'\ - '\x74\x69\x6f\x6e\x73\x3a\x20\x33\x36\x33\x35\x20'\ - '\x20\x53\x6c\x6f\x77\x20\x71\x75\x65\x72\x69\x65'\ - '\x73\x3a\x20\x30\x20\x20\x4f\x70\x65\x6e\x73\x3a'\ - '\x20\x33\x39\x32\x20\x20\x46\x6c\x75\x73\x68\x20'\ - '\x74\x61\x62\x6c\x65\x73\x3a\x20\x31\x20\x20\x4f'\ - '\x70\x65\x6e\x20\x74\x61\x62\x6c\x65\x73\x3a\x20'\ - '\x36\x34\x20\x20\x51\x75\x65\x72\x69\x65\x73\x20'\ - '\x70\x65\x72\x20\x73\x65\x63\x6f\x6e\x64\x20\x61'\ - '\x76\x67\x3a\x20\x30\x2e\x32\x34' - self.prtcl.conn.buf.append(badpkt) - self.assertRaises(errors.InterfaceError, self.prtcl.cmd_statistics) - - badpkt = '\x88\x00\x00\x01\x55\x70\x74\x69\x6d\x65\x3a\x20'\ - '\x55\x70\x36\x32\x34\x35\x20\x20\x54\x68\x72\x65'\ - '\x61\x64\x73\x3a\x20\x32\x20\x20\x51\x75\x65\x73'\ - '\x74\x69\x6f\x6e\x73\x3a\x20\x33\x36\x33\x35\x20'\ - '\x20\x53\x6c\x6f\x77\x20\x71\x75\x65\x72\x69\x65'\ - '\x73\x3a\x20\x30\x20\x20\x4f\x70\x65\x6e\x73\x3a'\ - '\x20\x33\x39\x32\x20\x20\x46\x6c\x75\x73\x68\x20'\ - '\x74\x61\x62\x6c\x65\x73\x3a\x20\x31\x20\x20\x4f'\ - '\x70\x65\x6e\x20\x74\x61\x62\x6c\x65\x73\x3a\x20'\ - '\x36\x34\x20\x20\x51\x75\x65\x72\x69\x65\x73\x20'\ - '\x70\x65\x72\x20\x73\x65\x63\x6f\x6e\x64\x20\x61'\ - '\x76\x67\x3a\x20\x30\x2e\x32\x34' - self.prtcl.conn.buf.append(badpkt) - self.assertRaises(errors.InterfaceError, self.prtcl.cmd_statistics) - - def test_cmd_process_info(self): - """Send the Process-Info-command to MySQL""" - self.assertRaises(errors.NotSupportedError, - self.prtcl.cmd_process_info) - - def test_cmd_process_kill(self): - """Send the Process-Kill-command to MySQL""" - self.prtcl.conn.buf.append( - '\x07\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00') - exp = {'insert_id': 0, 'affected_rows': 0, 'field_count': 0, - 'warning_count': 0, 'server_status': 0} - self.assertEqual(exp, self.prtcl.cmd_process_kill(1)) - - pkt = '\x1f\x00\x00\x01\xff\x46\x04\x23\x48\x59\x30\x30'\ - '\x30\x55\x6e\x6b\x6e\x6f\x77\x6e\x20\x74\x68\x72'\ - '\x65\x61\x64\x20\x69\x64\x3a\x20\x31\x30\x30' - self.prtcl.conn.buf.append(pkt) - self.assertRaises(errors.InternalError, - self.prtcl.cmd_process_kill, 100) - - pkt = '\x29\x00\x00\x01\xff\x47\x04\x23\x48\x59\x30\x30'\ - '\x30\x59\x6f\x75\x20\x61\x72\x65\x20\x6e\x6f\x74'\ - '\x20\x6f\x77\x6e\x65\x72\x20\x6f\x66\x20\x74\x68'\ - '\x72\x65\x61\x64\x20\x31\x36\x30\x35' - self.prtcl.conn.buf.append(pkt) - self.assertRaises(errors.OperationalError, - self.prtcl.cmd_process_kill, 1605) - - def test_cmd_debug(self): - """Send the Debug-command to MySQL""" - pkt = '\x05\x00\x00\x01\xfe\x00\x00\x00\x00' - self.prtcl.conn.buf.append(pkt) - exp = {'status_flag': 0, 'warning_count': 0} - self.assertEqual(exp, self.prtcl.cmd_debug()) - - pkt = '\x47\x00\x00\x01\xff\xcb\x04\x23\x34\x32\x30\x30'\ - '\x30\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69'\ - '\x65\x64\x3b\x20\x79\x6f\x75\x20\x6e\x65\x65\x64'\ - '\x20\x74\x68\x65\x20\x53\x55\x50\x45\x52\x20\x70'\ - '\x72\x69\x76\x69\x6c\x65\x67\x65\x20\x66\x6f\x72'\ - '\x20\x74\x68\x69\x73\x20\x6f\x70\x65\x72\x61\x74'\ - '\x69\x6f\x6e' - self.prtcl.conn.buf.append(pkt) - self.assertRaises(errors.OperationalError, - self.prtcl.cmd_debug) - - def test_cmd_ping(self): - """Send the Ping-command to MySQL""" - self.prtcl.conn.buf.append( - '\x07\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00') - exp = {'insert_id': 0, 'affected_rows': 0, 'field_count': 0, - 'warning_count': 0, 'server_status': 0} - self.assertEqual(exp, self.prtcl.cmd_ping()) - - self.assertRaises(errors.Error,self.prtcl.cmd_ping) - - - def test_cmd_change_user(self): - """Send the Change-User-command to MySQL""" - - handshake = \ - '\x47\x00\x00\x00\x0a\x35\x2e\x30\x2e\x33\x30\x2d'\ - '\x65\x6e\x74\x65\x72\x70\x72\x69\x73\x65\x2d\x67'\ - '\x70\x6c\x2d\x6c\x6f\x67\x00\x09\x01\x00\x00\x68'\ - '\x34\x69\x36\x6f\x50\x21\x4f\x00\x2c\xa2\x08\x02'\ - '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\ - '\x00\x00\x4c\x6e\x67\x39\x26\x50\x44\x40\x57\x72'\ - '\x59\x48\x00' - self.prtcl.handle_handshake(handshake) - - self.prtcl.conn.buf.append( - '\x07\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00') - exp = {'insert_id': 0, 'affected_rows': 0, 'field_count': 0, - 'warning_count': 0, 'server_status': 2} - self.prtcl.cmd_change_user(username='ham', - password='spam',database='python') - - self.prtcl.conn.buf.append( - '\x45\x00\x00\x01\xff\x14\x04\x23\x34\x32\x30\x30'\ - '\x30\x41\x63\x63\x65\x73\x73\x20\x64\x65\x6e\x69'\ - '\x65\x64\x20\x66\x6f\x72\x20\x75\x73\x65\x72\x20'\ - '\x27\x68\x61\x6d\x27\x40\x27\x6c\x6f\x63\x61\x6c'\ - '\x68\x6f\x73\x74\x27\x20\x74\x6f\x20\x64\x61\x74'\ - '\x61\x62\x61\x73\x65\x20\x27\x6d\x79\x73\x71\x6c'\ - '\x27') - self.assertRaises(errors.OperationalError, - self.prtcl.cmd_change_user,username='ham', - password='spam',database='mysql') + res = self._protocol.parse_eof(EOF_PACKET) + self.assertEqual(EOF_PACKET_RESULT, res) + def test_read_text_result(self): + # Tested by MySQLConnectionTests.test_get_rows() and .test_get_row() + pass diff --git a/lib/tests/test_utils.py b/lib/tests/test_utils.py index eb76c73..6e28076 100644 --- a/lib/tests/test_utils.py +++ b/lib/tests/test_utils.py @@ -1,25 +1,25 @@ # MySQL Connector/Python - MySQL driver written in Python. -# Copyright (c) 2009,2010, Oracle and/or its affiliates. All rights reserved. -# Use is subject to license terms. (See COPYING) +# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. +# MySQL Connector/Python is licensed under the terms of the GPLv2 +# , like most +# MySQL Connectors. There are special exceptions to the terms and +# conditions of the GPLv2 as it is applied to this software, see the +# FOSS License Exception +# . +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation. -# -# There are special exceptions to the terms and conditions of the GNU -# General Public License as it is applied to this software. View the -# full text of the exception in file EXCEPTIONS-CLIENT in the directory -# of this software distribution or see the FOSS License Exception at -# www.mysql.com. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """Unittests for mysql.connector.utils """