diff -Nru mysql-connector-python-1.1.6/ChangeLog mysql-connector-python-2.0.4/ChangeLog --- mysql-connector-python-1.1.6/ChangeLog 2014-02-11 21:37:15.000000000 +0000 +++ mysql-connector-python-2.0.4/ChangeLog 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ -MySQL Connector/Python - Release Notes / ChangeLog -Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. - -Full release notes: - http://dev.mysql.com/doc/relnotes/connector-python/en/ - - -1.1.6 (2014-01-11) ------------------- -* BUG#18187561: Fixed Django backend errors with time functions - -1.1.5 (2014-01-31) ------------------- -* BUG#17857712: Added fixes to use CPY with Django 1.6 -* BUG#18040042: Fixed clearing session with pooled connection -* BUG#17826833: Fixed executemany for INSERT with no VALUES-clause -* BUG#17573172: Added support for access modes for START TRANSACTION -* BUG#18054810: Fixed output of pylint to include msg_id -* BUG#17889076: Fixed error handling for unittests.py using -t option -* BUG#17780576: Added support for utf8mb4 character set -* BUG#17958420: Fix error handling parse_column_count() - -1.1.4 (2013-12-11) ------------------- -* BUG#17890173: Improve performance reading results -* BUG#17589172: Fixed GPL to/from community RPM upgrades -* BUG#17035093: Removed extra empty feature in MSI -* BUG#17888821: Updated MSI upgrade codes -* BUG#17943078: Adding support for building OS X packages - -1.1.3b1 (2013-11-15) --------------------- -* WL#7298: Refactored unittesting -* WL#7230: Added unit tests performing code analysis -* BUG#17079344: Fixed saving multi-byte strings with \x5c -* BUG#17578937: Fixed reconnecting of pooled connections -* BUG#17620395: Fixed packaging on older Debian systems -* BUG#17372107: Fixed TCP/IP connection for older FreeBSD - - -1.1.2a1 (2013-01-15) --------------------- - -* BUG#17547529: pid option changed to pid-file -* BUG#17414258: Added check to disallow changing size of pool -* BUG#17422299: Fixed cmd_shutdown() for MySQL 5.6 -* BUG#17406263: Fixed error message when pool is exhausted -* BUG#17461777: Fixed reconnect() to stop once it succeeds -* BUG#17215197: Added prepared argument for MySQLConnection.cursor() -* BUG#17424009: Fixed RuntimeError on exit using Python v3 -* BUG#17406398: Adding constraints on the pool name -* BUG#17401406: Adding constraints on pool size -* BUG#17353327: Fixed commercial MSI upgrade error -* BUG#17485966: Removed alpha/beta tags from distributions -* BUG#17473273: Fixed last_executed_query() for Django -* BUG#13551483: Added converter_class connection argument -* BUG#13369592: Added compress connection argument -* BUG#17353301: Fixed MSI to remove previous installations -* BUG#14273043: Fix warnings.py example - - -1.1.1a1 (2013-08-28) --------------------- - -* WL#6499: Adding database backend for Django -* WL#6080: Added Connection Pooling -* WL#7080: Added Distutils command to create Debian packages -* BUG#17022399: Fixed error message executing while disconnected -* BUG#16933795: Fixed errors.Error to retain the given message -* BUG#17065366: Fixed executemany using MySQL functions as values -* BUG#17041240: Fixed error closing cursor with unread results -* BUG#17041412: Fixed fetchall() to include self._nextrow -* BUG#17002411: Fixed loading data using LOAD DATA LOCAL INFILE -* BUG#16234441: Fixed shutdown using different shutdown types -* BUG#16660356: Fixed executemany() to do nothing given nothing -* BUG#16655208: Fixed Unicode database names with Python v2 -* BUG#17028999: Added timing for individual unittests -* BUG#16656621: Fixed rollback to consume unread results -* BUG#17028999: Added timing for individual unittests -* BUG#16662920: Fixed fetching remaining rows in buffered cursors -* BUG#16736916: Fixed FileNotFound with LOAD DATA INFILE -* BUG#16972089: Fixed hyphen in version by removing it -* BUG#16748411: Fixed catching errors of socket.getaddrinfo() - - -1.1.0a1 (2013-06-12) --------------------- - -* BUG#16896702: Updated errors using MySQL 5.7.1 -* WL#6148: Added support for MySQL Prepared Statements -* BUG#16234372: Fixed relative imports in Python v3 code -* WL#6573: Added method to start transactions explicitly -* WL#6517: Added more standard attributes for getting the version -* WL#6574: Added the possibility to check for an ongoing transaction diff -Nru mysql-connector-python-1.1.6/CHANGES.txt mysql-connector-python-2.0.4/CHANGES.txt --- mysql-connector-python-1.1.6/CHANGES.txt 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/CHANGES.txt 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,78 @@ +==================================================== +MySQL Connector/Python 2.0 - Release Notes & Changes +==================================================== + +MySQL Connector/Python +Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. + +Full release notes: + http://dev.mysql.com/doc/relnotes/connector-python/en/ + +v2.0.4 +====== + +- BUG20324089: Fix HASH based sharding with MySQL Fabric +- BUG20462427: Fix receiving large field data from server +- BUG20301989: Fix conversion of empty set +- BUG20407036: Fix incorrect arguments to mysld_stmt_execute error +- BUG20106629: Support Django Safetext and SafeBytes type + +v2.0.3 +====== + +- BUG19703022: Fix using passwords with integers only in option files +- BUG19777815: Add support for warnings with MySQLCursor.callproc() +- BUG19972427: Fix creating of redundant connections in Django +- BUG19331658: Fix connection pooling with MySQL Fabric +- BUG19930054: Lost connection to server error during query +- BUG19803702: Fix reporting errors with non-ascii characters + +v2.0.2 +====== + +- BUG19500097: Fix string decoding with binary character set +- BUG19677659: Move testings of errors to internal repository +- BUG19549363: Raise error when compression used with reset_session +- BUG19642249: Improve errors reporting invalid sharding keys +- BUG19711759: Fix Pylint issue in network module +- BUG19667984: Fix converting invalid datetime values in Django backend +- BUG19660283: Fix failing unit tests with MySQL server 5.7.5 +- BUG19584051: Fix comparison of type_code of columns for PEP-249 +- BUG19522948: Fix data corruption with TEXT and prepared statements +- BUG19584116: Fix extra signal causing runtime error in Django + + +v2.0.1 +====== + +- WL7954: Add support for RANGE_STRING Fabric sharding type +- WL7955: Add support for RANGE_DATETIME Fabric sharding type +- BUG19440592: Fix exception not captured when SSL is unavailable +- BUG19481761: Fix option files with !include with trailing newline +- BUG18798953: Add MySQLConnection.shutdown() to abruply close connection +- BUG19168737: Fix unsupported connection argument error with option files +- BUG19282158: Fix NULL values to work with prepared statements +- BUG19179711: Fix using '%s' in django backend +- BUG19169143: Fix raising error with duplicate option files +- BUG19170287: Fix duplicate section error with Python v3 +- BUG19163169: Add support for Django 1.7 +- BUG19225481: Fix floating point inaccuracy with Python v2 +- BUG19164627: Fix cursor trying to decode linestring data as utf-8 + + +v2.0.0a1 +======== + +- BUG19207922: Fix fetching multiple results when executing procedure +- BUG19184025: Fix results containing NULL +- BUG19169990: Fix sending/receiving using compressed connection +- BUG18956789: Fix Django TimeField 00:00:00 converting to MySQL NULL +- WL7228: Allow Connector/Python to use MySQL option files +- BUG18843153: Fix Django to check connection on each request +- WL7937: Allow LOAD DATA LOCAL INFILE by default +- BUG18742429: Fix prepared statements returning lots of columns +- BUG18814880: Fix stopping mysqld running unit tests on Windows +- WL7292: Add cursors returning dict and namedtuple as rows +- WL7462: Consolidate Python v2 and v3 code +- WL7716: Move commercial files and distribution to CPYINT + diff -Nru mysql-connector-python-1.1.6/COPYING mysql-connector-python-2.0.4/COPYING --- mysql-connector-python-1.1.6/COPYING 2014-02-11 21:37:15.000000000 +0000 +++ mysql-connector-python-2.0.4/COPYING 1970-01-01 00:00:00.000000000 +0000 @@ -1,347 +0,0 @@ -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. - - - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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; either version 2 of the License, or - (at your option) any later version. - - 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff -Nru mysql-connector-python-1.1.6/debian/changelog mysql-connector-python-2.0.4/debian/changelog --- mysql-connector-python-1.1.6/debian/changelog 2014-03-09 17:15:37.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/changelog 2017-05-19 04:16:05.000000000 +0000 @@ -1,3 +1,60 @@ +mysql-connector-python (2.0.4-1~ubuntu14.04.1~ppa1) trusty; urgency=medium + + * No-change backport to trusty + + -- Micah Gersten Thu, 18 May 2017 23:16:05 -0500 + +mysql-connector-python (2.0.4-1) unstable; urgency=medium + + * New upstream release + + -- Sandro Tosi Sun, 02 Aug 2015 17:32:23 +0100 + +mysql-connector-python (2.0.3-1) experimental; urgency=medium + + * New upstream release + * debian/copyright + - extend packaging copyright years + - extend upstream copyright years + * debian/{docs, rules} + - update doc location + * debian/rules + - update examples location + * debian/control + - bump Standards-Version to 3.9.6 (no changes needed) + * debian/{compat, control} + - bump dh compat level to 9 + + -- Sandro Tosi Mon, 02 Mar 2015 23:25:31 +0000 + +mysql-connector-python (1.2.3-2) unstable; urgency=low + + [ Brian May ] + * debian/patches/fix_last_executed_query.patch + - fix last_executed_query to return unicode. Closes: #761136 + + [ Sandro Tosi ] + * debian/rules + - re-enable unittests at build-time (failing, so not blocking build if + failing); Closes: #739189 + + -- Sandro Tosi Wed, 17 Sep 2014 22:18:14 +0100 + +mysql-connector-python (1.2.3-1) unstable; urgency=low + + * New upstream version. + * Compatible with Django 1.7. Closes: #758844. + + -- Brian May Thu, 04 Sep 2014 13:59:17 +1000 + +mysql-connector-python (1.2.2-1) unstable; urgency=low + + * New upstream release. + * Django warnings become errors. This has been fixed. + http://bugs.mysql.com/bug.php?id=71806. + + -- Brian May Wed, 09 Jul 2014 09:00:14 +1000 + mysql-connector-python (1.1.6-1) unstable; urgency=medium * New upstream release diff -Nru mysql-connector-python-1.1.6/debian/compat mysql-connector-python-2.0.4/debian/compat --- mysql-connector-python-1.1.6/debian/compat 2011-12-03 17:32:11.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/compat 2015-03-02 22:46:36.000000000 +0000 @@ -1 +1 @@ -7 +9 diff -Nru mysql-connector-python-1.1.6/debian/control mysql-connector-python-2.0.4/debian/control --- mysql-connector-python-1.1.6/debian/control 2014-03-09 17:14:45.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/control 2015-03-02 22:46:36.000000000 +0000 @@ -2,9 +2,10 @@ Section: python Priority: optional Maintainer: Sandro Tosi -Uploaders: Debian Python Modules Team -Build-Depends: debhelper (>= 7.0.50~), python-all, dh-python, python3-all, mysql-server -Standards-Version: 3.9.5 +Uploaders: Debian Python Modules Team , + Brian May +Build-Depends: debhelper (>= 9), python-all, dh-python, python3-all, mysql-server +Standards-Version: 3.9.6 X-Python-Version: all X-Python3-Version: >= 3.1 Homepage: http://dev.mysql.com/doc/connector-python/en/index.html diff -Nru mysql-connector-python-1.1.6/debian/copyright mysql-connector-python-2.0.4/debian/copyright --- mysql-connector-python-1.1.6/debian/copyright 2014-02-18 15:19:34.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/copyright 2015-03-02 22:06:05.000000000 +0000 @@ -3,7 +3,7 @@ Source: http://dev.mysql.com/downloads/connector/python/ Files: * -Copyright: Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved. +Copyright: Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. Use is subject to license terms. License: GPL-2 (with FOSS License Exception) This program is free software; you can redistribute it and/or modify @@ -33,7 +33,7 @@ Files: debian/* -Copyright: 2011-2014 Sandro Tosi +Copyright: 2011-2015 Sandro Tosi License: GPL-2 On Debian systems, the complete text of the GNU General Public License v2 can be found in "/usr/share/common-licenses/GPL-2". diff -Nru mysql-connector-python-1.1.6/debian/docs mysql-connector-python-2.0.4/debian/docs --- mysql-connector-python-1.1.6/debian/docs 2014-01-18 11:46:10.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/docs 1970-01-01 00:00:00.000000000 +0000 @@ -1,2 +0,0 @@ -README -docs/README_DOCS.txt diff -Nru mysql-connector-python-1.1.6/debian/patches/fix_last_executed_query.patch mysql-connector-python-2.0.4/debian/patches/fix_last_executed_query.patch --- mysql-connector-python-1.1.6/debian/patches/fix_last_executed_query.patch 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/patches/fix_last_executed_query.patch 2015-03-02 22:06:05.000000000 +0000 @@ -0,0 +1,14 @@ +Index: mysql-connector-python/lib/mysql/connector/django/base.py +=================================================================== +--- mysql-connector-python.orig/lib/mysql/connector/django/base.py 2014-08-15 07:47:44.000000000 +1000 ++++ mysql-connector-python/lib/mysql/connector/django/base.py 2014-09-11 09:00:39.894957233 +1000 +@@ -372,7 +372,8 @@ + return 'MATCH ({0}) AGAINST (%s IN BOOLEAN MODE)'.format(field_name) + + def last_executed_query(self, cursor, sql, params): +- return cursor.statement ++ from django.utils.encoding import force_text ++ return force_text(cursor.statement) + + def no_limit_value(self): + # 2**64 - 1, as recommended by the MySQL documentation diff -Nru mysql-connector-python-1.1.6/debian/patches/series mysql-connector-python-2.0.4/debian/patches/series --- mysql-connector-python-1.1.6/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/patches/series 2014-09-12 00:31:30.000000000 +0000 @@ -0,0 +1 @@ +fix_last_executed_query.patch diff -Nru mysql-connector-python-1.1.6/debian/rules mysql-connector-python-2.0.4/debian/rules --- mysql-connector-python-1.1.6/debian/rules 2014-02-18 15:22:38.000000000 +0000 +++ mysql-connector-python-2.0.4/debian/rules 2015-03-02 22:44:29.000000000 +0000 @@ -11,8 +11,7 @@ dh $@ --with python2,python3 override_dh_installexamples: - dh_installexamples -ppython-mysql.connector python2/examples/*.py - dh_installexamples -ppython3-mysql.connector python3/examples/*.py + dh_installexamples -A examples/*.py override_dh_install: set -e; \ @@ -25,16 +24,15 @@ done dh_install -# Disable test execution, currently broken upstream - see 739189 -#override_dh_installdocs: -# dh_installdocs -A docs/README_DOCS.txt +override_dh_installdocs: + dh_installdocs -A docs/README_DOCS.txt README.txt -#override_dh_auto_test: -#ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS))) -# set -e ; \ -# for python in $(PYTHON2) $(PYTHON3); do \ -# LIB=$$($$python -c "from distutils.command.build import build ; from distutils.core import Distribution ; b = build(Distribution()) ; b.finalize_options() ; print (b.build_purelib)") ;\ -# PYTHONPATH=$(CURDIR)/$$LIB $$python unittests.py --with-mysql=/usr/ --mysql-topdir=$(CURDIR)/mysql_myconnpy/ --verbosity=2 --bind-address=:: --host=::1 --stats ; \ -# rm -rf $(CURDIR)/mysql_myconnpy/ ; \ -# done -#endif +override_dh_auto_test: +ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS))) + -for python in $(PYTHON2) $(PYTHON3); do \ + LIB=$$($$python -c "from distutils.command.build import build ; from distutils.core import Distribution ; b = build(Distribution()) ; b.finalize_options() ; print (b.build_purelib)") ;\ + mkdir -p $(CURDIR)/mysql_myconnpy/ ; \ + PYTHONPATH=$(CURDIR)/$$LIB $$python unittests.py --with-mysql=/usr/ --mysql-topdir=$(CURDIR)/mysql_myconnpy/ --verbosity=2 --bind-address=:: --host=::1 --stats ; \ + rm -rf $(CURDIR)/mysql_myconnpy/ ; \ + done +endif diff -Nru mysql-connector-python-1.1.6/examples/dates.py mysql-connector-python-2.0.4/examples/dates.py --- mysql-connector-python-1.1.6/examples/dates.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/dates.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" + +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 + +""" + +from datetime import datetime + +import mysql.connector + + +# 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 {0}".format(tbl) + cursor.execute(stmt_drop) + + stmt_create = ( + "CREATE TABLE {0} ( " + " `id` tinyint(4) NOT NULL AUTO_INCREMENT, " + " `c1` date DEFAULT NULL, " + " `c2` datetime NOT NULL, " + " `c3` time DEFAULT NULL, " + " `changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP " + " ON UPDATE CURRENT_TIMESTAMP, " + "PRIMARY KEY (`id`))" + ).format(tbl) + cursor.execute(stmt_create) + + # not using executemany to handle errors better + stmt_insert = ("INSERT INTO {0} (c1,c2,c3) VALUES " + "(%s,%s,%s)".format(tbl)) + for data in DATA: + try: + cursor.execute(stmt_insert, data) + except (mysql.connector.errors.Error, TypeError) as exc: + output.append("Failed inserting {0}\nError: {1}\n".format( + data, exc)) + raise + + # Read the names again and print them + stmt_select = "SELECT * FROM {0} ORDER BY id".format(tbl) + cursor.execute(stmt_select) + + for row in cursor.fetchall(): + output.append("%3s | %10s | %19s | %8s |" % ( + row[0], + row[1], + row[2], + row[3], + )) + + # Cleaning up, dropping the table again + cursor.execute(stmt_drop) + + cursor.close() + db.close() + return output + + +if __name__ == '__main__': + # + # Configure MySQL login and database to use in config.py + # + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/engines.py mysql-connector-python-2.0.4/examples/engines.py --- mysql-connector-python-1.1.6/examples/engines.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/engines.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" + +Example using MySQL Connector/Python showing: +* that show engines works.. + +""" + +import sys, os + +import mysql.connector + +def main(config): + output = [] + db = mysql.connector.Connect(**config) + cursor = db.cursor() + + # Select it again and show it + stmt_select = "SHOW ENGINES" + cursor.execute(stmt_select) + rows = cursor.fetchall() + + for row in rows: + output.append(repr(row)) + + db.close() + return output + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/inserts.py mysql-connector-python-2.0.4/examples/inserts.py --- mysql-connector-python-1.1.6/examples/inserts.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/inserts.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,95 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" +Example using MySQL Connector/Python showing: +* dropping and creating a table +* inserting 3 rows using executemany() +* selecting data and showing it + +""" + +import mysql.connector + + +def main(config): + output = [] + cnx = mysql.connector.connect(**config) + cur = cnx.cursor() + + # Drop table if exists, and create it new + stmt_drop = "DROP TABLE IF EXISTS names" + cur.execute(stmt_drop) + + stmt_create = ( + "CREATE TABLE names (" + " id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, " + " name VARCHAR(30) DEFAULT '' NOT NULL, " + " info TEXT DEFAULT '', " + " age TINYINT UNSIGNED DEFAULT '30', " + "PRIMARY KEY (id))" + ) + cur.execute(stmt_create) + + info = "abc" * 10000 + + # Insert 3 records + names = (('Geert', info, 30), ('Jan', info, 31), ('Michel', info, 32)) + stmt_insert = "INSERT INTO names (name, info, age) VALUES (%s, %s, %s)" + cur.executemany(stmt_insert, names) + cnx.commit() + + # Read the names again and print them + stmt_select = "SELECT id, name, info, age FROM names ORDER BY id" + cur.execute(stmt_select) + + for row in cur.fetchall(): + output.append("%d | %s | %d\nInfo: %s..\n" % ( + row[0], row[1], row[3], row[2][:20])) + + # Cleaning up, dropping the table again + cur.execute(stmt_drop) + + cur.close() + cnx.close() + return output + + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/microseconds.py mysql-connector-python-2.0.4/examples/microseconds.py --- mysql-connector-python-1.1.6/examples/microseconds.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/microseconds.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" +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. +""" + +from datetime import time + +import mysql.connector + +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 {0} does not support fractional precision"\ + " for timestamps.".format(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") + for row in cursor: + output.append("{0: 2d} | {1: 2d} | {2}".format(*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__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) + diff -Nru mysql-connector-python-1.1.6/examples/multi_resultsets.py mysql-connector-python-2.0.4/examples/multi_resultsets.py --- mysql-connector-python-1.1.6/examples/multi_resultsets.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/multi_resultsets.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" + +Example using MySQL Connector/Python showing: +* sending multiple statements and iterating over the results + +""" + +import mysql.connector + + +def main(config): + output = [] + db = mysql.connector.Connect(**config) + cursor = db.cursor() + + # Drop table if exists, and create it new + stmt_drop = "DROP TABLE IF EXISTS names" + cursor.execute(stmt_drop) + + stmt_create = ( + "CREATE TABLE names (" + " id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, " + " name VARCHAR(30) DEFAULT '' NOT NULL, " + " info TEXT DEFAULT '', " + " age TINYINT UNSIGNED DEFAULT '30', " + " PRIMARY KEY (id))" + ) + cursor.execute(stmt_create) + + info = "abc" * 10000 + + stmts = [ + "INSERT INTO names (name) VALUES ('Geert')", + "SELECT COUNT(*) AS cnt FROM names", + "INSERT INTO names (name) VALUES ('Jan'),('Michel')", + "SELECT name FROM names", + ] + + # 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: {0}".format(result.fetchone()[0])) + else: + output.append("Inserted {0} row{1}".format( + result.rowcount, + 's' if result.rowcount > 1 else '')) + + cursor.execute(stmt_drop) + + cursor.close() + db.close() + return output + + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/mysql_warnings.py mysql-connector-python-2.0.4/examples/mysql_warnings.py --- mysql-connector-python-1.1.6/examples/mysql_warnings.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/mysql_warnings.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" + +Example using MySQL Connector/Python showing: +* using warnings + +""" + +import mysql.connector + +STMT = "SELECT 'abc'+1" + + +def main(config): + output = [] + config['get_warnings'] = True + db = mysql.connector.Connect(**config) + cursor = db.cursor() + db.sql_mode = '' + + output.append("Executing '%s'" % STMT) + cursor.execute(STMT) + cursor.fetchall() + + warnings = cursor.fetchwarnings() + if warnings: + for w in warnings: + output.append("%d: %s" % (w[1],w[2])) + else: + output.append("We should have got warnings.") + raise Exception("Got no warnings") + + cursor.close() + db.close() + return output + + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/prepared_statements.py mysql-connector-python-2.0.4/examples/prepared_statements.py --- mysql-connector-python-1.1.6/examples/prepared_statements.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/prepared_statements.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 +"""Example using MySQL Prepared Statements + +Example using MySQL Connector/Python showing: +* usage of Prepared Statements +""" + +import mysql.connector +from mysql.connector.cursor import MySQLCursorPrepared + + +def main(config): + output = [] + cnx = mysql.connector.Connect(**config) + + curprep = cnx.cursor(cursor_class=MySQLCursorPrepared) + cur = cnx.cursor() + + # Drop table if exists, and create it new + stmt_drop = "DROP TABLE IF EXISTS names" + cur.execute(stmt_drop) + + stmt_create = ( + "CREATE TABLE names (" + "id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, " + "name VARCHAR(30) DEFAULT '' NOT NULL, " + "cnt TINYINT UNSIGNED DEFAULT 0, " + "PRIMARY KEY (id))" + ) + cur.execute(stmt_create) + + # Connector/Python also allows ? as placeholders for MySQL Prepared + # statements. + prepstmt = "INSERT INTO names (name) VALUES (%s)" + + # Preparing the statement is done only once. It can be done before + # without data, or later with data. + curprep.execute(prepstmt) + + # Insert 3 records + names = ('Geert', 'Jan', 'Michel') + for name in names: + curprep.execute(prepstmt, (name,)) + cnx.commit() + + # We use a normal cursor issue a SELECT + output.append("Inserted data") + cur.execute("SELECT id, name FROM names") + for row in cur: + output.append("{0} | {1}".format(*row)) + + # Cleaning up, dropping the table again + cur.execute(stmt_drop) + + cnx.close() + return output + + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/transaction.py mysql-connector-python-2.0.4/examples/transaction.py --- mysql-connector-python-1.1.6/examples/transaction.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/transaction.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,137 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" + +Example using MySQL Connector/Python showing: +* dropping and creating a table +* using warnings +* doing a transaction, rolling it back and committing one. + +""" + +import mysql.connector + + +def main(config): + output = [] + db = mysql.connector.Connect(**config) + cursor = db.cursor() + + # Drop table if exists, and create it new + stmt_drop = "DROP TABLE IF EXISTS names" + cursor.execute(stmt_drop) + + stmt_create = """ + CREATE TABLE names ( + id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(30) DEFAULT '' NOT NULL, + cnt TINYINT UNSIGNED DEFAULT 0, + PRIMARY KEY (id) + ) ENGINE=InnoDB""" + cursor.execute(stmt_create) + + warnings = cursor.fetchwarnings() + if warnings: + ids = [ i for l,i,m in warnings] + output.append("Oh oh.. we got warnings..") + if 1266 in ids: + 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 + output.append("Inserting data") + names = ( ('Geert',), ('Jan',), ('Michel',) ) + stmt_insert = "INSERT INTO names (name) VALUES (%s)" + cursor.executemany(stmt_insert, names) + + # Roll back!!!! + output.append("Rolling back transaction") + db.rollback() + + # There should be no data! + stmt_select = "SELECT id, name FROM names ORDER BY id" + cursor.execute(stmt_select) + rows = None + try: + rows = cursor.fetchall() + except mysql.connector.InterfaceError as e: + raise + + if rows == []: + output.append("No data, all is fine.") + else: + output.append("Something is wrong, we have data although we rolled back!") + output.append(rows) + cursor.close() + db.close() + return output + + # Do the insert again. + cursor.executemany(stmt_insert, names) + + # Data should be already there + cursor.execute(stmt_select) + output.append("Data before commit:") + for row in cursor.fetchall(): + output.append("%d | %s" % (row[0], row[1])) + + # Do a commit + db.commit() + + cursor.execute(stmt_select) + output.append("Data after commit:") + for row in cursor.fetchall(): + output.append("%d | %s" % (row[0], row[1])) + + # Cleaning up, dropping the table again + cursor.execute(stmt_drop) + + cursor.close() + db.close() + return output + + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/unicode.py mysql-connector-python-2.0.4/examples/unicode.py --- mysql-connector-python-1.1.6/examples/unicode.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/unicode.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" + +Example using MySQL Connector/Python showing: +* the usefulness of unicode, if it works correctly.. +* dropping and creating a table +* inserting and selecting a row +""" + +import mysql.connector + +info = """ +For this to work you need to make sure your terminal can output +unicode character correctly. Check if the encoding of your terminal +is set to UTF-8. +""" + +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?" + output.append("Unicode string: %s" % unistr) + + # Drop table if exists, and create it new + stmt_drop = "DROP TABLE IF EXISTS unicode" + cursor.execute(stmt_drop) + + stmt_create = ( + "CREATE TABLE unicode (" + " id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT, " + " str VARCHAR(50) DEFAULT '' NOT NULL, " + " PRIMARY KEY (id)" + ") CHARACTER SET 'utf8'" + ) + cursor.execute(stmt_create) + + # Insert a row + stmt_insert = "INSERT INTO unicode (str) VALUES (%s)" + cursor.execute(stmt_insert, (unistr,)) + + # Select it again and show it + stmt_select = "SELECT str FROM unicode WHERE id = %s" + cursor.execute(stmt_select, (1,)) + row = cursor.fetchone() + + output.append("Unicode string coming from db: " + row[0]) + + # Cleaning up, dropping the table again + cursor.execute(stmt_drop) + + cursor.close() + db.close() + return output + +if __name__ == '__main__': + + config = { + 'host': 'localhost', + 'port': 3306, + 'database': 'test', + 'user': 'root', + 'password': '', + 'charset': 'utf8', + 'use_unicode': True, + 'get_warnings': True, + } + + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/examples/warnings.py mysql-connector-python-2.0.4/examples/warnings.py --- mysql-connector-python-1.1.6/examples/warnings.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/examples/warnings.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,70 @@ +#!/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 + +""" + +Example using MySQL Connector/Python showing: +* using warnings + +""" + +STMT = "SELECT 'abc'+1" + +def main(config): + output = [] + config['get_warnings'] = True + db = mysql.connector.Connect(**config) + cursor = db.cursor() + db.sql_mode = '' + + output.append("Executing '%s'" % STMT) + cursor.execute(STMT) + cursor.fetchall() + + warnings = cursor.fetchwarnings() + if warnings: + for w in warnings: + output.append("%d: %s" % (w[1],w[2])) + else: + output.append("We should have got warnings.") + raise Exception("Got no warnings") + + cursor.close() + db.close() + return output + +if __name__ == '__main__': + # + # Configure MySQL login and database to use in config.py + # + import config + config = config.Config.dbinfo().copy() + out = main(config) + print('\n'.join(out)) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/authentication.py mysql-connector-python-2.0.4/lib/mysql/connector/authentication.py --- mysql-connector-python-1.1.6/lib/mysql/connector/authentication.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/authentication.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,191 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2014, 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 + +"""Implementing support for MySQL Authentication Plugins""" + +from hashlib import sha1 +import struct + +from . import errors +from .catch23 import PY2, isstr + + +class BaseAuthPlugin(object): + """Base class for authentication plugins + + + Classes inheriting from BaseAuthPlugin should implement the method + prepare_password(). When instantiating, auth_data argument is + required. The username, password and database are optional. The + ssl_enabled argument can be used to tell the plugin whether SSL is + active or not. + + The method auth_response() method is used to retrieve the password + which was prepared by prepare_password(). + """ + + requires_ssl = False + plugin_name = '' + + def __init__(self, auth_data, username=None, password=None, database=None, + ssl_enabled=False): + """Initialization""" + self._auth_data = auth_data + self._username = username + self._password = password + self._database = database + self._ssl_enabled = ssl_enabled + + def prepare_password(self): + """Prepares and returns password to be send to MySQL + + This method needs to be implemented by classes inheriting from + this class. It is used by the auth_response() method. + + Raises NotImplementedError. + """ + raise NotImplementedError + + def auth_response(self): + """Returns the prepared password to send to MySQL + + Raises InterfaceError on errors. For example, when SSL is required + by not enabled. + + Returns str + """ + if self.requires_ssl and not self._ssl_enabled: + raise errors.InterfaceError("{name} requires SSL".format( + name=self.plugin_name)) + return self.prepare_password() + + +class MySQLNativePasswordAuthPlugin(BaseAuthPlugin): + """Class implementing the MySQL Native Password authentication plugin""" + + requires_ssl = False + plugin_name = 'mysql_native_password' + + def prepare_password(self): + """Prepares and returns password as native MySQL 4.1+ password""" + if not self._auth_data: + raise errors.InterfaceError("Missing authentication data (seed)") + + if not self._password: + return b'\x00' + password = self._password + + if isstr(self._password): + password = self._password.encode('utf-8') + else: + password = self._password + + if PY2: + password = buffer(password) # pylint: disable=E0602 + try: + auth_data = buffer(self._auth_data) # pylint: disable=E0602 + except TypeError: + raise errors.InterfaceError("Authentication data incorrect") + else: + password = password + auth_data = self._auth_data + + hash4 = None + try: + hash1 = sha1(password).digest() + hash2 = sha1(hash1).digest() + hash3 = sha1(auth_data + hash2).digest() + if PY2: + xored = [ord(h1) ^ ord(h3) for (h1, h3) in zip(hash1, hash3)] + else: + xored = [h1 ^ h3 for (h1, h3) in zip(hash1, hash3)] + hash4 = struct.pack('20B', *xored) + except Exception as exc: + raise errors.InterfaceError( + "Failed scrambling password; {0}".format(exc)) + + return hash4 + + +class MySQLClearPasswordAuthPlugin(BaseAuthPlugin): + """Class implementing the MySQL Clear Password authentication plugin""" + + requires_ssl = True + plugin_name = 'mysql_clear_password' + + def prepare_password(self): + """Returns password as as clear text""" + if not self._password: + return b'\x00' + password = self._password + + if PY2: + if isinstance(password, unicode): # pylint: disable=E0602 + password = password.encode('utf8') + elif isinstance(password, str): + password = password.encode('utf8') + + return password + b'\x00' + + +class MySQLSHA256PasswordAuthPlugin(BaseAuthPlugin): + """Class implementing the MySQL SHA256 authentication plugin + + Note that encrypting using RSA is not supported since the Python + Standard Library does not provide this OpenSSL functionality. + """ + + requires_ssl = True + plugin_name = 'sha256_password' + + def prepare_password(self): + """Returns password as as clear text""" + if not self._password: + return b'\x00' + password = self._password + + if PY2: + if isinstance(password, unicode): # pylint: disable=E0602 + password = password.encode('utf8') + elif isinstance(password, str): + password = password.encode('utf8') + + return password + b'\x00' + + +def get_auth_plugin(plugin_name): + """Return authentication class based on plugin name + + This function returns the class for the authentication plugin plugin_name. + The returned class is a subclass of BaseAuthPlugin. + + Raises errors.NotSupportedError when plugin_name is not supported. + + Returns subclass of BaseAuthPlugin. + """ + for authclass in BaseAuthPlugin.__subclasses__(): # pylint: disable=E1101 + if authclass.plugin_name == plugin_name: + return authclass + + raise errors.NotSupportedError( + "Authentication plugin '{0}' is not supported".format(plugin_name)) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/catch23.py mysql-connector-python-2.0.4/lib/mysql/connector/catch23.py --- mysql-connector-python-1.1.6/lib/mysql/connector/catch23.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/catch23.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,85 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2014, 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 + +"""Python v2 to v3 migration module""" + +from decimal import Decimal +import struct +import sys + +from .custom_types import HexLiteral + +# pylint: disable=E0602,E1103 + +PY2 = sys.version_info[0] == 2 + +if PY2: + NUMERIC_TYPES = (int, float, Decimal, HexLiteral, long) + UNICODE_TYPES = (unicode,) # This should not contain str +else: + NUMERIC_TYPES = (int, float, Decimal, HexLiteral) + UNICODE_TYPES = (str,) + +def init_bytearray(payload=b'', encoding='utf-8'): + """Initializes a bytearray from the payload""" + if isinstance(payload, bytearray): + return payload + + if PY2: + return bytearray(payload) + + if isinstance(payload, int): + return bytearray(payload) + elif not isinstance(payload, bytes): + try: + return bytearray(payload.encode(encoding=encoding)) + except AttributeError: + raise ValueError("payload must be a str or bytes") + + + return bytearray(payload) + + +def isstr(obj): + """Returns whether a variable is a string""" + if PY2: + return isinstance(obj, basestring) + else: + return isinstance(obj, str) + +def isunicode(obj): + """Returns whether a variable is a of unicode type""" + if PY2: + return isinstance(obj, unicode) + else: + return isinstance(obj, str) + + +if PY2: + def struct_unpack(fmt, buf): + """Wrapper around struct.unpack handling buffer as bytes and strings""" + if isinstance(buf, (bytearray, bytes)): + return struct.unpack_from(fmt, buffer(buf)) + return struct.unpack_from(fmt, buf) +else: + struct_unpack = struct.unpack # pylint: disable=C0103 diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/charsets.py mysql-connector-python-2.0.4/lib/mysql/connector/charsets.py --- mysql-connector-python-1.1.6/lib/mysql/connector/charsets.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/charsets.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,286 @@ +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 = '2014-05-23' +_MYSQL_VERSION = (5, 7, 4) + +"""This module contains the MySQL Server Character Sets""" + +MYSQL_CHARACTER_SETS = [ + # (character set name, collation, default) + None, + ("big5", "big5_chinese_ci", True), # 1 + ("latin2", "latin2_czech_cs", False), # 2 + ("dec8", "dec8_swedish_ci", True), # 3 + ("cp850", "cp850_general_ci", True), # 4 + ("latin1", "latin1_german1_ci", False), # 5 + ("hp8", "hp8_english_ci", True), # 6 + ("koi8r", "koi8r_general_ci", True), # 7 + ("latin1", "latin1_swedish_ci", True), # 8 + ("latin2", "latin2_general_ci", True), # 9 + ("swe7", "swe7_swedish_ci", True), # 10 + ("ascii", "ascii_general_ci", True), # 11 + ("ujis", "ujis_japanese_ci", True), # 12 + ("sjis", "sjis_japanese_ci", True), # 13 + ("cp1251", "cp1251_bulgarian_ci", False), # 14 + ("latin1", "latin1_danish_ci", False), # 15 + ("hebrew", "hebrew_general_ci", True), # 16 + None, + ("tis620", "tis620_thai_ci", True), # 18 + ("euckr", "euckr_korean_ci", True), # 19 + ("latin7", "latin7_estonian_cs", False), # 20 + ("latin2", "latin2_hungarian_ci", False), # 21 + ("koi8u", "koi8u_general_ci", True), # 22 + ("cp1251", "cp1251_ukrainian_ci", False), # 23 + ("gb2312", "gb2312_chinese_ci", True), # 24 + ("greek", "greek_general_ci", True), # 25 + ("cp1250", "cp1250_general_ci", True), # 26 + ("latin2", "latin2_croatian_ci", False), # 27 + ("gbk", "gbk_chinese_ci", True), # 28 + ("cp1257", "cp1257_lithuanian_ci", False), # 29 + ("latin5", "latin5_turkish_ci", True), # 30 + ("latin1", "latin1_german2_ci", False), # 31 + ("armscii8", "armscii8_general_ci", True), # 32 + ("utf8", "utf8_general_ci", True), # 33 + ("cp1250", "cp1250_czech_cs", False), # 34 + ("ucs2", "ucs2_general_ci", True), # 35 + ("cp866", "cp866_general_ci", True), # 36 + ("keybcs2", "keybcs2_general_ci", True), # 37 + ("macce", "macce_general_ci", True), # 38 + ("macroman", "macroman_general_ci", True), # 39 + ("cp852", "cp852_general_ci", True), # 40 + ("latin7", "latin7_general_ci", True), # 41 + ("latin7", "latin7_general_cs", False), # 42 + ("macce", "macce_bin", False), # 43 + ("cp1250", "cp1250_croatian_ci", False), # 44 + ("utf8mb4", "utf8mb4_general_ci", True), # 45 + ("utf8mb4", "utf8mb4_bin", False), # 46 + ("latin1", "latin1_bin", False), # 47 + ("latin1", "latin1_general_ci", False), # 48 + ("latin1", "latin1_general_cs", False), # 49 + ("cp1251", "cp1251_bin", False), # 50 + ("cp1251", "cp1251_general_ci", True), # 51 + ("cp1251", "cp1251_general_cs", False), # 52 + ("macroman", "macroman_bin", False), # 53 + ("utf16", "utf16_general_ci", True), # 54 + ("utf16", "utf16_bin", False), # 55 + ("utf16le", "utf16le_general_ci", True), # 56 + ("cp1256", "cp1256_general_ci", True), # 57 + ("cp1257", "cp1257_bin", False), # 58 + ("cp1257", "cp1257_general_ci", True), # 59 + ("utf32", "utf32_general_ci", True), # 60 + ("utf32", "utf32_bin", False), # 61 + ("utf16le", "utf16le_bin", False), # 62 + ("binary", "binary", True), # 63 + ("armscii8", "armscii8_bin", False), # 64 + ("ascii", "ascii_bin", False), # 65 + ("cp1250", "cp1250_bin", False), # 66 + ("cp1256", "cp1256_bin", False), # 67 + ("cp866", "cp866_bin", False), # 68 + ("dec8", "dec8_bin", False), # 69 + ("greek", "greek_bin", False), # 70 + ("hebrew", "hebrew_bin", False), # 71 + ("hp8", "hp8_bin", False), # 72 + ("keybcs2", "keybcs2_bin", False), # 73 + ("koi8r", "koi8r_bin", False), # 74 + ("koi8u", "koi8u_bin", False), # 75 + None, + ("latin2", "latin2_bin", False), # 77 + ("latin5", "latin5_bin", False), # 78 + ("latin7", "latin7_bin", False), # 79 + ("cp850", "cp850_bin", False), # 80 + ("cp852", "cp852_bin", False), # 81 + ("swe7", "swe7_bin", False), # 82 + ("utf8", "utf8_bin", False), # 83 + ("big5", "big5_bin", False), # 84 + ("euckr", "euckr_bin", False), # 85 + ("gb2312", "gb2312_bin", False), # 86 + ("gbk", "gbk_bin", False), # 87 + ("sjis", "sjis_bin", False), # 88 + ("tis620", "tis620_bin", False), # 89 + ("ucs2", "ucs2_bin", False), # 90 + ("ujis", "ujis_bin", False), # 91 + ("geostd8", "geostd8_general_ci", True), # 92 + ("geostd8", "geostd8_bin", False), # 93 + ("latin1", "latin1_spanish_ci", False), # 94 + ("cp932", "cp932_japanese_ci", True), # 95 + ("cp932", "cp932_bin", False), # 96 + ("eucjpms", "eucjpms_japanese_ci", True), # 97 + ("eucjpms", "eucjpms_bin", False), # 98 + ("cp1250", "cp1250_polish_ci", False), # 99 + None, + ("utf16", "utf16_unicode_ci", False), # 101 + ("utf16", "utf16_icelandic_ci", False), # 102 + ("utf16", "utf16_latvian_ci", False), # 103 + ("utf16", "utf16_romanian_ci", False), # 104 + ("utf16", "utf16_slovenian_ci", False), # 105 + ("utf16", "utf16_polish_ci", False), # 106 + ("utf16", "utf16_estonian_ci", False), # 107 + ("utf16", "utf16_spanish_ci", False), # 108 + ("utf16", "utf16_swedish_ci", False), # 109 + ("utf16", "utf16_turkish_ci", False), # 110 + ("utf16", "utf16_czech_ci", False), # 111 + ("utf16", "utf16_danish_ci", False), # 112 + ("utf16", "utf16_lithuanian_ci", False), # 113 + ("utf16", "utf16_slovak_ci", False), # 114 + ("utf16", "utf16_spanish2_ci", False), # 115 + ("utf16", "utf16_roman_ci", False), # 116 + ("utf16", "utf16_persian_ci", False), # 117 + ("utf16", "utf16_esperanto_ci", False), # 118 + ("utf16", "utf16_hungarian_ci", False), # 119 + ("utf16", "utf16_sinhala_ci", False), # 120 + ("utf16", "utf16_german2_ci", False), # 121 + ("utf16", "utf16_croatian_ci", False), # 122 + ("utf16", "utf16_unicode_520_ci", False), # 123 + ("utf16", "utf16_vietnamese_ci", False), # 124 + None, + None, + None, + ("ucs2", "ucs2_unicode_ci", False), # 128 + ("ucs2", "ucs2_icelandic_ci", False), # 129 + ("ucs2", "ucs2_latvian_ci", False), # 130 + ("ucs2", "ucs2_romanian_ci", False), # 131 + ("ucs2", "ucs2_slovenian_ci", False), # 132 + ("ucs2", "ucs2_polish_ci", False), # 133 + ("ucs2", "ucs2_estonian_ci", False), # 134 + ("ucs2", "ucs2_spanish_ci", False), # 135 + ("ucs2", "ucs2_swedish_ci", False), # 136 + ("ucs2", "ucs2_turkish_ci", False), # 137 + ("ucs2", "ucs2_czech_ci", False), # 138 + ("ucs2", "ucs2_danish_ci", False), # 139 + ("ucs2", "ucs2_lithuanian_ci", False), # 140 + ("ucs2", "ucs2_slovak_ci", False), # 141 + ("ucs2", "ucs2_spanish2_ci", False), # 142 + ("ucs2", "ucs2_roman_ci", False), # 143 + ("ucs2", "ucs2_persian_ci", False), # 144 + ("ucs2", "ucs2_esperanto_ci", False), # 145 + ("ucs2", "ucs2_hungarian_ci", False), # 146 + ("ucs2", "ucs2_sinhala_ci", False), # 147 + ("ucs2", "ucs2_german2_ci", False), # 148 + ("ucs2", "ucs2_croatian_ci", False), # 149 + ("ucs2", "ucs2_unicode_520_ci", False), # 150 + ("ucs2", "ucs2_vietnamese_ci", False), # 151 + None, + None, + None, + None, + None, + None, + None, + ("ucs2", "ucs2_general_mysql500_ci", False), # 159 + ("utf32", "utf32_unicode_ci", False), # 160 + ("utf32", "utf32_icelandic_ci", False), # 161 + ("utf32", "utf32_latvian_ci", False), # 162 + ("utf32", "utf32_romanian_ci", False), # 163 + ("utf32", "utf32_slovenian_ci", False), # 164 + ("utf32", "utf32_polish_ci", False), # 165 + ("utf32", "utf32_estonian_ci", False), # 166 + ("utf32", "utf32_spanish_ci", False), # 167 + ("utf32", "utf32_swedish_ci", False), # 168 + ("utf32", "utf32_turkish_ci", False), # 169 + ("utf32", "utf32_czech_ci", False), # 170 + ("utf32", "utf32_danish_ci", False), # 171 + ("utf32", "utf32_lithuanian_ci", False), # 172 + ("utf32", "utf32_slovak_ci", False), # 173 + ("utf32", "utf32_spanish2_ci", False), # 174 + ("utf32", "utf32_roman_ci", False), # 175 + ("utf32", "utf32_persian_ci", False), # 176 + ("utf32", "utf32_esperanto_ci", False), # 177 + ("utf32", "utf32_hungarian_ci", False), # 178 + ("utf32", "utf32_sinhala_ci", False), # 179 + ("utf32", "utf32_german2_ci", False), # 180 + ("utf32", "utf32_croatian_ci", False), # 181 + ("utf32", "utf32_unicode_520_ci", False), # 182 + ("utf32", "utf32_vietnamese_ci", False), # 183 + None, + None, + None, + None, + None, + None, + None, + None, + ("utf8", "utf8_unicode_ci", False), # 192 + ("utf8", "utf8_icelandic_ci", False), # 193 + ("utf8", "utf8_latvian_ci", False), # 194 + ("utf8", "utf8_romanian_ci", False), # 195 + ("utf8", "utf8_slovenian_ci", False), # 196 + ("utf8", "utf8_polish_ci", False), # 197 + ("utf8", "utf8_estonian_ci", False), # 198 + ("utf8", "utf8_spanish_ci", False), # 199 + ("utf8", "utf8_swedish_ci", False), # 200 + ("utf8", "utf8_turkish_ci", False), # 201 + ("utf8", "utf8_czech_ci", False), # 202 + ("utf8", "utf8_danish_ci", False), # 203 + ("utf8", "utf8_lithuanian_ci", False), # 204 + ("utf8", "utf8_slovak_ci", False), # 205 + ("utf8", "utf8_spanish2_ci", False), # 206 + ("utf8", "utf8_roman_ci", False), # 207 + ("utf8", "utf8_persian_ci", False), # 208 + ("utf8", "utf8_esperanto_ci", False), # 209 + ("utf8", "utf8_hungarian_ci", False), # 210 + ("utf8", "utf8_sinhala_ci", False), # 211 + ("utf8", "utf8_german2_ci", False), # 212 + ("utf8", "utf8_croatian_ci", False), # 213 + ("utf8", "utf8_unicode_520_ci", False), # 214 + ("utf8", "utf8_vietnamese_ci", False), # 215 + None, + None, + None, + None, + None, + None, + None, + ("utf8", "utf8_general_mysql500_ci", False), # 223 + ("utf8mb4", "utf8mb4_unicode_ci", False), # 224 + ("utf8mb4", "utf8mb4_icelandic_ci", False), # 225 + ("utf8mb4", "utf8mb4_latvian_ci", False), # 226 + ("utf8mb4", "utf8mb4_romanian_ci", False), # 227 + ("utf8mb4", "utf8mb4_slovenian_ci", False), # 228 + ("utf8mb4", "utf8mb4_polish_ci", False), # 229 + ("utf8mb4", "utf8mb4_estonian_ci", False), # 230 + ("utf8mb4", "utf8mb4_spanish_ci", False), # 231 + ("utf8mb4", "utf8mb4_swedish_ci", False), # 232 + ("utf8mb4", "utf8mb4_turkish_ci", False), # 233 + ("utf8mb4", "utf8mb4_czech_ci", False), # 234 + ("utf8mb4", "utf8mb4_danish_ci", False), # 235 + ("utf8mb4", "utf8mb4_lithuanian_ci", False), # 236 + ("utf8mb4", "utf8mb4_slovak_ci", False), # 237 + ("utf8mb4", "utf8mb4_spanish2_ci", False), # 238 + ("utf8mb4", "utf8mb4_roman_ci", False), # 239 + ("utf8mb4", "utf8mb4_persian_ci", False), # 240 + ("utf8mb4", "utf8mb4_esperanto_ci", False), # 241 + ("utf8mb4", "utf8mb4_hungarian_ci", False), # 242 + ("utf8mb4", "utf8mb4_sinhala_ci", False), # 243 + ("utf8mb4", "utf8mb4_german2_ci", False), # 244 + ("utf8mb4", "utf8mb4_croatian_ci", False), # 245 + ("utf8mb4", "utf8mb4_unicode_520_ci", False), # 246 + ("utf8mb4", "utf8mb4_vietnamese_ci", False), # 247 + ("gb18030", "gb18030_chinese_ci", True), # 248 + ("gb18030", "gb18030_bin", False), # 249 + ("gb18030", "gb18030_unicode_520_ci", False), # 250 +] + diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/connection.py mysql-connector-python-2.0.4/lib/mysql/connector/connection.py --- mysql-connector-python-1.1.6/lib/mysql/connector/connection.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/connection.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,1677 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +"""Implementing communication with MySQL servers. +""" + +from io import IOBase +import os +import re +import time + +from . import errors +from .authentication import get_auth_plugin +from .catch23 import PY2, isstr +from .constants import ( + ClientFlag, ServerCmd, CharacterSet, ServerFlag, + flag_is_set, ShutdownType, NET_BUFFER_LENGTH +) +from .conversion import MySQLConverterBase, MySQLConverter +from .cursor import ( + CursorBase, MySQLCursor, MySQLCursorRaw, + MySQLCursorBuffered, MySQLCursorBufferedRaw, MySQLCursorPrepared, + MySQLCursorDict, MySQLCursorBufferedDict, MySQLCursorNamedTuple, + MySQLCursorBufferedNamedTuple) +from .network import MySQLUnixSocket, MySQLTCPSocket +from .protocol import MySQLProtocol +from .utils import int4store + +DEFAULT_CONFIGURATION = { + 'database': None, + 'user': '', + 'password': '', + 'host': '127.0.0.1', + 'port': 3306, + 'unix_socket': None, + 'use_unicode': True, + 'charset': 'utf8', + 'collation': None, + 'converter_class': MySQLConverter, + 'autocommit': False, + 'time_zone': None, + 'sql_mode': None, + 'get_warnings': False, + 'raise_on_warnings': False, + 'connection_timeout': None, + 'client_flags': 0, + 'compress': False, + '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, + 'auth_plugin': None, + 'allow_local_infile': True, +} + + +class MySQLConnection(object): + """Connection to a MySQL Server""" + def __init__(self, *args, **kwargs): + self._protocol = None + self._socket = None + self._handshake = None + self._server_version = None + self.converter = None + self._converter_class = MySQLConverter + + self._client_flags = ClientFlag.get_default() + self._charset_id = 33 + self._sql_mode = None + self._time_zone = None + self._autocommit = False + + self._user = '' + self._password = '' + self._database = '' + self._host = '127.0.0.1' + self._port = 3306 + self._unix_socket = None + self._client_host = '' + self._client_port = 0 + self._ssl = {} + self._force_ipv6 = False + + 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._have_next_result = False + self._raw = False + self._in_transaction = False + + self._prepared_statements = None + + self._ssl_active = False + self._auth_plugin = None + self._pool_config_version = None + self._compress = False + + if len(kwargs) > 0: + self.connect(**kwargs) + + def _get_self(self): + """Return self for weakref.proxy + + 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] == 255: + raise errors.get_exception(packet) + + try: + handshake = self._protocol.parse_handshake(packet) + except Exception as err: + raise errors.InterfaceError( + 'Failed parsing handshake; {0}'.format(err)) + + if PY2: + regex_ver = re.compile(r"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") + else: + # pylint: disable=W1401 + regex_ver = re.compile(br"^(\d{1,2})\.(\d{1,2})\.(\d{1,3})(.*)") + # pylint: enable=W1401 + 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 b'fabric' in match.group(4).lower(): + if version < (1, 4): + raise errors.InterfaceError( + "MySQL Fabric '{0}'' is not supported".format( + handshake['server_version_original'])) + elif version < (4, 1): + raise errors.InterfaceError( + "MySQL Version '{0}' is not supported.".format( + handshake['server_version_original'])) + + if handshake['capabilities'] & ClientFlag.PLUGIN_AUTH: + self.set_client_flags([ClientFlag.PLUGIN_AUTH]) + + 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 + + Authentication happens in two parts. We first send a response to the + handshake. The MySQL server will then send either an AuthSwitchRequest + or an error packet. + + Raises NotSupportedError when we get the old, insecure password + reply back. Raises any error coming from MySQL. + """ + self._ssl_active = False + 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) + self._ssl_active = True + + packet = self._protocol.make_auth( + handshake=self._handshake, + username=username, password=password, database=database, + charset=charset, client_flags=client_flags, + ssl_enabled=self._ssl_active, + auth_plugin=self._auth_plugin) + self._socket.send(packet) + self._auth_switch_request(username, password) + + if not (client_flags & ClientFlag.CONNECT_WITH_DB) and database: + self.cmd_init_db(database) + + return True + + def _auth_switch_request(self, username=None, password=None): + """Handle second part of authentication + + Raises NotSupportedError when we get the old, insecure password + reply back. Raises any error coming from MySQL. + """ + packet = self._socket.recv() + if packet[4] == 254 and len(packet) == 5: + raise errors.NotSupportedError( + "Authentication with old (insecure) passwords " + "is not supported. For more information, lookup " + "Password Hashing in the latest MySQL manual") + elif packet[4] == 254: + # AuthSwitchRequest + (new_auth_plugin, + auth_data) = self._protocol.parse_auth_switch_request(packet) + auth = get_auth_plugin(new_auth_plugin)( + auth_data, password=password, ssl_enabled=self._ssl_active) + response = auth.auth_response() + if response == b'\x00': + self._socket.send(b'') + else: + self._socket.send(response) + packet = self._socket.recv() + if packet[4] != 1: + return self._handle_ok(packet) + else: + auth_data = self._protocol.parse_auth_more_data(packet) + elif packet[4] == 255: + raise errors.get_exception(packet) + + def config(self, **kwargs): + """Configure the MySQL Connection + + This method allows you to configure the MySQLConnection instance. + + 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 + + try: + if config['compress']: + self._compress = True + self.set_client_flags([ClientFlag.COMPRESS]) + except KeyError: + pass # Missing compress argument is OK + + try: + if not config['allow_local_infile']: + self.set_client_flags([-ClientFlag.LOCAL_FILES]) + except KeyError: + pass # Missing allow_local_infile 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] + + # Set converter class + try: + self.set_converter_class(config['converter_class']) + except KeyError: + pass # Using default converter class + except TypeError: + raise AttributeError("Converter class should be a subclass " + "of conversion.MySQLConverterBase.") + + # 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) + + # 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") + + # Other configuration + set_ssl_flag = False + for key, value in config.items(): + try: + DEFAULT_CONFIGURATION[key] + except KeyError: + raise AttributeError("Unsupported argument '{0}'".format(key)) + # SSL Configuration + if key.startswith('ssl_'): + 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'] + # Make sure both ssl_key/ssl_cert are set, or neither (XOR) + if 'ca' not in self._ssl or self._ssl['ca'] is None: + raise AttributeError( + "Missing ssl_ca argument.") + if bool('key' in self._ssl) != bool('cert' in self._ssl): + raise AttributeError( + "ssl_key and ssl_cert need to be both " + "specified, or neither." + ) + # Make sure key/cert are set to None + elif not set(('key', 'cert')) <= set(self._ssl): + self._ssl['key'] = None + self._ssl['cert'] = None + elif (self._ssl['key'] is None) != (self._ssl['cert'] is None): + raise AttributeError( + "ssl_key and ssl_cert need to be both " + "set, or neither." + ) + self.set_client_flags([ClientFlag.SSL]) + + def _get_connection(self, prtcls=None): + """Get connection based on configuration + + This method will return the appropriated connection object using + the connection parameters. + + Returns subclass of MySQLBaseSocket. + """ + conn = None + if self.unix_socket and os.name != 'nt': + conn = MySQLUnixSocket(unix_socket=self.unix_socket) + else: + conn = MySQLTCPSocket(host=self.server_host, + port=self.server_port, + force_ipv6=self._force_ipv6) + conn.set_connection_timeout(self._connection_timeout) + return conn + + def _open_connection(self): + """Open the connection to the MySQL server + + This method sets up and opens the connection to the MySQL server. + + Raises on errors. + """ + 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(self._converter_class) + 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(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 shutdown(self): + """Shut down connection to MySQL Server. + """ + if not self._socket: + return + + try: + self._socket.shutdown() + except (AttributeError, errors.Error): + pass # Getting an exception would mean we are disconnected. + + def disconnect(self): + """Disconnect from the MySQL server + """ + if not self._socket: + return + + try: + self.cmd_quit() + self._socket.close_connection() + except (AttributeError, errors.Error): + pass # Getting an exception would mean we are disconnected. + close = disconnect + + def _send_cmd(self, command, argument=None, packet_number=0, packet=None, + expect_response=True): + """Send a command to the MySQL server + + This method sends a command with an optional argument. + If packet is not None, it will be sent and the argument will be + ignored. + + The packet_number is optional and should usually not be used. + + Some commands might not result in the MySQL server returning + a response. If a command does not return anything, you should + set expect_response to False. The _send_cmd method will then + return None instead of a MySQL packet. + + Returns a MySQL packet or None. + """ + if self.unread_result: + raise errors.InternalError("Unread result found.") + + try: + self._socket.send( + self._protocol.make_command(command, packet or argument), + packet_number) + except AttributeError: + raise errors.OperationalError("MySQL Connection not available.") + + if not expect_response: + return None + return self._socket.recv() + + def _send_data(self, data_file, 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. + """ + if self.unread_result: + raise errors.InternalError("Unread result found.") + + if not hasattr(data_file, 'read'): + raise ValueError("expecting a file-like object") + + try: + buf = data_file.read(NET_BUFFER_LENGTH - 16) + while buf: + self._socket.send(buf) + buf = data_file.read(NET_BUFFER_LENGTH - 16) + except AttributeError: + raise errors.OperationalError("MySQL Connection not available.") + + if send_empty_packet: + try: + self._socket.send(b'') + except AttributeError: + raise errors.OperationalError( + "MySQL Connection not available.") + + return self._socket.recv() + + def _handle_server_status(self, flags): + """Handle the server flags found in MySQL packets + + This method handles the server flags send by MySQL OK and EOF + packets. It, for example, checks whether there exists more result + sets or whether there is an ongoing transaction. + """ + self._have_next_result = flag_is_set(ServerFlag.MORE_RESULTS_EXISTS, + flags) + self._in_transaction = flag_is_set(ServerFlag.STATUS_IN_TRANS, flags) + + @property + def in_transaction(self): + """MySQL session has started a transaction + """ + return self._in_transaction + + 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() + """ + if packet[4] == 0: + ok_pkt = self._protocol.parse_ok(packet) + self._handle_server_status(ok_pkt['server_status']) + return ok_pkt + elif packet[4] == 255: + 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 packet[4] == 254: + eof = self._protocol.parse_eof(packet) + self._handle_server_status(eof['status_flag']) + return eof + elif packet[4] == 255: + 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: + data_file = open(filename, 'rb') + except IOError: + # Send a empty packet to cancel the operation + try: + self._socket.send(b'') + except AttributeError: + raise errors.OperationalError( + "MySQL Connection not available.") + raise errors.InterfaceError( + "File '{0}' could not be read".format(filename)) + + return self._handle_ok(self._send_data(data_file, + 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] == 0: + return self._handle_ok(packet) + elif packet[4] == 251: + if PY2: + filename = str(packet[5:]) + else: + filename = packet[5:].decode() + return self._handle_load_data_infile(filename) + elif packet[4] == 254: + return self._handle_eof(packet) + elif packet[4] == 255: + 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 range(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, binary=False, columns=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.") + + if binary: + rows = self._protocol.read_binary_result( + self._socket, columns, count) + else: + rows = self._protocol.read_text_result(self._socket, count) + if rows[-1] is not None: + self._handle_server_status(rows[-1]['status_flag']) + self.unread_result = False + + return rows + + def get_row(self, binary=False, columns=None): + """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, binary=binary, columns=columns) + 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.encode('utf-8'))) + + def cmd_query(self, query): + """Send a query to the MySQL server + + This method send the query to the MySQL server and returns the result. + + If there was a text result, a tuple will be returned consisting of + the number of columns and a list containing information about these + columns. + + When the query doesn't return a text result, the OK or EOF packet + information as dictionary will be returned. In case the result was + an error, exception errors.Error will be raised. + + Returns a tuple() + """ + if not isinstance(query, bytes): + query = query.encode('utf-8') + result = self._handle_result(self._send_cmd(ServerCmd.QUERY, query)) + + 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. + """ + if not isinstance(statements, bytearray): + if isstr(statements): + statements = bytearray(statements.encode('utf-8')) + else: + statements = bytearray(statements) + + # 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.") + yield self._handle_result(self._socket.recv()) + + 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 constants.RefreshOption. + Usage example: + RefreshOption = mysql.connector.RefreshOption + refresh = RefreshOption.LOG | RefreshOption.THREADS + cnx.cmd_refresh(refresh) + + The result is a dictionary with the OK packet 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. + + 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() + """ + if shutdown_type: + if not ShutdownType.get_info(shutdown_type): + raise errors.InterfaceError("Invalid shutdown type") + atype = shutdown_type + else: + atype = ShutdownType.SHUTDOWN_DEFAULT + 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.") + + if self._compress: + raise errors.NotSupportedError("Change user is not supported with " + "compression.") + + packet = self._protocol.make_change_user( + handshake=self._handshake, + username=username, password=password, database=database, + charset=charset, client_flags=self._client_flags, + ssl_enabled=self._ssl_active, + auth_plugin=self._auth_plugin) + self._socket.send(packet, 0) + + ok_packet = self._auth_switch_request(username, password) + + try: + if not (self._client_flags & ClientFlag.CONNECT_WITH_DB) \ + and database: + self.cmd_init_db(database) + except: + raise + + self._charset_id = charset + self._post_connection() + + return ok_packet + + 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: + return False # This method does not raise + return True + + def reset_session(self, user_variables=None, session_variables=None): + """Clears the current active session + + This method resets the session state, if the MySQL server is 5.7.3 + or later active session will be reset without re-authenticating. + For other server versions session will be reset by re-authenticating. + + It is possible to provide a sequence of variables and their values to + be set after clearing the session. This is possible for both user + defined variables and session variables. + This method takes two arguments user_variables and session_variables + which are dictionaries. + + Raises OperationalError if not connected, InternalError if there are + unread results and InterfaceError on errors. + """ + if not self.is_connected(): + raise errors.OperationalError("MySQL Connection not available.") + + try: + self.cmd_reset_connection() + except errors.NotSupportedError: + if self._compress: + raise errors.NotSupportedError( + "Reset session is not supported with compression for " + "MySQL server version 5.7.2 or earlier.") + else: + self.cmd_change_user(self._user, self._password, + self._database, self._charset_id) + + cur = self.cursor() + if user_variables: + for key, value in user_variables.items(): + cur.execute("SET @`{0}` = %s".format(key), (value,)) + if session_variables: + for key, value in session_variables.items(): + cur.execute("SET SESSION `{0}` = %s".format(key), (value,)) + + 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.disconnect() + self.connect() + if self.is_connected(): + break + except Exception as err: # pylint: disable=W0703 + if counter == attempts: + msg = "Can not reconnect to MySQL after {0} "\ + "attempt(s): {1}".format(attempts, str(err)) + raise errors.InterfaceError(msg) + 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: + 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. + """ + if issubclass(convclass, MySQLConverterBase): + charset_name = CharacterSet.get_info(self._charset_id)[0] + self._converter_class = convclass + self.converter = convclass(charset_name, self._use_unicode) + else: + raise TypeError("Converter class should be a subclass " + "of conversion.MySQLConverterBase.") + + def get_server_version(self): + """Get the MySQL version + + This method returns the MySQL server version as a tuple. If not + previously connected, it will return None. + + Returns a tuple or None. + """ + return self._server_version + + def get_server_info(self): + """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""" + try: + return self._handshake['server_threadid'] + except KeyError: + return None + + def set_login(self, username=None, password=None): + """Set login information for MySQL + + Set the username and/or password for the user connecting to + the MySQL Server. + """ + if username is not None: + self._user = username.strip() + else: + self._user = '' + if password is not None: + self._password = password.strip() + else: + self._password = '' + + def set_unicode(self, value=True): + """Toggle unicode mode + + Set whether we return string fields as unicode or not. + Default is True. + """ + self._use_unicode = value + if self.converter: + self.converter.set_unicode(value) + + 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 + (self._charset_id, charset_name, collation_name) = \ + CharacterSet.get_charset_info(charset) + 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 '{0}' COLLATE '{1}'".format( + 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. + The server is queried when the connection is active. If not connected, + the configured character set name is returned. + + Returns a string. + """ + return CharacterSet.get_info(self._charset_id)[0] + + @property + def python_charset(self): + """Returns the Python character set for current connection + + This property returns the character set name of the current connection. + Note that, unlike property charset, this checks if the previously set + character set is supported by Python and if not, it returns the + equivalent character set that Python supports. + + Returns a string. + """ + encoding = CharacterSet.get_info(self._charset_id)[0] + if encoding in ('utf8mb4', 'binary'): + return 'utf8' + else: + return encoding + + @property + def collation(self): + """Returns the collation for current connection + + This property returns the collation name of the current connection. + The server is queried when the connection is active. If not connected, + the configured collation name is returned. + + Returns a string. + """ + return CharacterSet.get_charset_info(self._charset_id)[2] + + def set_client_flags(self, flags): + """Set the client flags + + The flags-argument can be either an int or a list (or tuple) of + ClientFlag-values. If it is an integer, it will set client_flags + to flags as is. + If flags is a list (or tuple), each flag will be set or unset + 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 + """ + 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: + raise errors.ProgrammingError( + "set_client_flags expect integer (>0) or set") + return self._client_flags + + def isset_client_flag(self, flag): + """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._user + + @property + def server_host(self): + """MySQL server IP address or name""" + return self._host + + @property + def server_port(self): + "MySQL server TCP/IP 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 result + + 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): + """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] + database = property(get_database, set_database, doc="Current database") + + def set_time_zone(self, value): + """Set the time zone""" + self.cmd_query("SET @@session.time_zone = '{0}'".format(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): + """Set the SQL mode + + This method sets the SQL Mode for the current connection. The value + argument can be either a string with comma separate 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 = '{0}'".format(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): + """Toggle autocommit""" + switch = 'ON' if value else 'OFF' + self._execute_query("SET @@session.autocommit = {0}".format(switch)) + self._autocommit = value + + def get_autocommit(self): + """Get whether autocommit is on or off""" + value = self._info_query("SELECT @@session.autocommit")[0] + return True if value == 1 else False + autocommit = property(get_autocommit, set_autocommit, + doc="autocommit value for current MySQL session") + + def _set_getwarnings(self, toggle): + """Set whether warnings should be automatically retrieved + + 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 whether 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 whether to raise on warnings "\ + "(implies retrieving warnings).") + + def cursor(self, buffered=None, raw=None, prepared=None, cursor_class=None, + dictionary=None, named_tuple=None): + """Instantiates and returns a cursor + + By default, MySQLCursor is returned. Depending on the options + while connecting, a buffered and/or raw cursor is instantiated + instead. Also depending upon the cursor options, rows can be + returned as dictionary or named tuple. + + Dictionary and namedtuple based cursors are available with buffered + output but not raw. + + It is possible to also give a custom cursor through the + cursor_class parameter, but it needs to be a subclass of + mysql.connector.cursor.CursorBase. + + Raises ProgrammingError when cursor_class is not a subclass of + CursorBase. Raises ValueError when cursor is not available. + + 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, CursorBase): + raise errors.ProgrammingError( + "Cursor class needs be to 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 + if dictionary is True: + cursor_type |= 4 + if named_tuple is True: + cursor_type |= 8 + if prepared is True: + cursor_type |= 16 + + types = { + 0: MySQLCursor, # 0 + 1: MySQLCursorBuffered, + 2: MySQLCursorRaw, + 3: MySQLCursorBufferedRaw, + 4: MySQLCursorDict, + 5: MySQLCursorBufferedDict, + 8: MySQLCursorNamedTuple, + 9: MySQLCursorBufferedNamedTuple, + 16: MySQLCursorPrepared + } + try: + return (types[cursor_type])(self) + except KeyError: + args = ('buffered', 'raw', 'dictionary', 'named_tuple', 'prepared') + raise ValueError('Cursor not available with given criteria: ' + + ', '.join([args[i] for i in range(5) + if cursor_type & (1 << i) != 0])) + + def start_transaction(self, consistent_snapshot=False, + isolation_level=None, readonly=None): + """Start a transaction + + This method explicitly starts a transaction sending the + START TRANSACTION statement to the MySQL server. You can optionally + set whether there should be a consistent snapshot, which + isolation level you need or which access mode i.e. READ ONLY or + READ WRITE. + + For example, to start a transaction with isolation level SERIALIZABLE, + you would do the following: + >>> cnx = mysql.connector.connect(..) + >>> cnx.start_transaction(isolation_level='SERIALIZABLE') + + Raises ProgrammingError when a transaction is already in progress + and when ValueError when isolation_level specifies an Unknown + level. + """ + if self.in_transaction: + raise errors.ProgrammingError("Transaction already in progress") + + if isolation_level: + level = isolation_level.strip().replace('-', ' ').upper() + levels = ['READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ', + 'SERIALIZABLE'] + + if level not in levels: + raise ValueError( + 'Unknown isolation level "{0}"'.format(isolation_level)) + + self._execute_query( + "SET TRANSACTION ISOLATION LEVEL {0}".format(level)) + + if readonly is not None: + if self._server_version < (5, 6, 5): + raise ValueError( + "MySQL server version {0} does not support " + "this feature".format(self._server_version)) + + if readonly: + access_mode = 'READ ONLY' + else: + access_mode = 'READ WRITE' + self._execute_query( + "SET TRANSACTION {0}".format(access_mode)) + + query = "START TRANSACTION" + if consistent_snapshot: + query += " WITH CONSISTENT SNAPSHOT" + self._execute_query(query) + + def commit(self): + """Commit current transaction""" + self._execute_query("COMMIT") + + def rollback(self): + """Rollback current transaction""" + if self._unread_result: + self.get_rows() + + self._execute_query("ROLLBACK") + + def _execute_query(self, query): + """Execute a query + + This method simply calls 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.cmd_query(query) + + def _info_query(self, query): + """Send a query which only returns 1 row""" + cursor = self.cursor(buffered=True) + cursor.execute(query) + return cursor.fetchone() + + def _handle_binary_ok(self, packet): + """Handle a MySQL Binary Protocol OK packet + + This method handles a MySQL Binary Protocol 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() + """ + if packet[4] == 0: + return self._protocol.parse_binary_prepare_ok(packet) + elif packet[4] == 255: + raise errors.get_exception(packet) + raise errors.InterfaceError('Expected Binary OK packet') + + def _handle_binary_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 tuple returned by this method consist of: + - the number of columns in the result, + - a list of tuples with information about the columns, + - the EOF packet information as a dictionary. + + Returns tuple() or dict() + """ + if not packet or len(packet) < 4: + raise errors.InterfaceError('Empty response') + elif packet[4] == 0: + return self._handle_ok(packet) + elif packet[4] == 254: + return self._handle_eof(packet) + elif packet[4] == 255: + raise errors.get_exception(packet) + + # We have a binary 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 range(0, column_count): + columns[i] = self._protocol.parse_column(self._socket.recv()) + + eof = self._handle_eof(self._socket.recv()) + return (column_count, columns, eof) + + def cmd_stmt_prepare(self, statement): + """Prepare a MySQL statement + + This method will send the PREPARE command to MySQL together with the + given statement. + + Returns a dict() + """ + packet = self._send_cmd(ServerCmd.STMT_PREPARE, statement) + result = self._handle_binary_ok(packet) + + result['columns'] = [] + result['parameters'] = [] + if result['num_params'] > 0: + for _ in range(0, result['num_params']): + result['parameters'].append( + self._protocol.parse_column(self._socket.recv())) + self._handle_eof(self._socket.recv()) + if result['num_columns'] > 0: + for _ in range(0, result['num_columns']): + result['columns'].append( + self._protocol.parse_column(self._socket.recv())) + self._handle_eof(self._socket.recv()) + + return result + + def cmd_stmt_execute(self, statement_id, data=(), parameters=(), flags=0): + """Execute a prepared MySQL statement""" + parameters = list(parameters) + long_data_used = {} + + if data: + for param_id, _ in enumerate(parameters): + if isinstance(data[param_id], IOBase): + binary = True + try: + binary = 'b' not in data[param_id].mode + except AttributeError: + pass + self.cmd_stmt_send_long_data(statement_id, param_id, + data[param_id]) + long_data_used[param_id] = (binary,) + + execute_packet = self._protocol.make_stmt_execute( + statement_id, data, tuple(parameters), flags, + long_data_used, self.charset) + packet = self._send_cmd(ServerCmd.STMT_EXECUTE, packet=execute_packet) + result = self._handle_binary_result(packet) + return result + + def cmd_stmt_close(self, statement_id): + """Deallocate a prepared MySQL statement + + This method deallocates the prepared statement using the + statement_id. Note that the MySQL server does not return + anything. + """ + self._send_cmd(ServerCmd.STMT_CLOSE, int4store(statement_id), + expect_response=False) + + def cmd_stmt_send_long_data(self, statement_id, param_id, data): + """Send data for a column + + This methods send data for a column (for example BLOB) for statement + identified by statement_id. The param_id indicate which parameter + the data belongs too. + The data argument should be a file-like object. + + Since MySQL does not send anything back, no error is raised. When + the MySQL server is not reachable, an OperationalError is raised. + + cmd_stmt_send_long_data should be called before cmd_stmt_execute. + + The total bytes send is returned. + + Returns int. + """ + chunk_size = 8192 + total_sent = 0 + # pylint: disable=W0212 + prepare_packet = self._protocol._prepare_stmt_send_long_data + # pylint: enable=W0212 + try: + buf = data.read(chunk_size) + while buf: + packet = prepare_packet(statement_id, param_id, buf) + self._send_cmd(ServerCmd.STMT_SEND_LONG_DATA, packet=packet, + expect_response=False) + total_sent += len(buf) + buf = data.read(chunk_size) + except AttributeError: + raise errors.OperationalError("MySQL Connection not available.") + + return total_sent + + def cmd_stmt_reset(self, statement_id): + """Reset data for prepared statement sent as long data + + The result is a dictionary with OK packet information. + + Returns a dict() + """ + self._handle_ok(self._send_cmd(ServerCmd.STMT_RESET, + int4store(statement_id))) + + def cmd_reset_connection(self): + """Resets the session state without re-authenticating + + Works only for MySQL server 5.7.3 or later. + The result is a dictionary with OK packet information. + + Returns a dict() + """ + if self._server_version < (5, 7, 3): + raise errors.NotSupportedError("MySQL version 5.7.2 and " + "earlier does not support " + "COM_RESET_CONNECTION.") + self._handle_ok(self._send_cmd(ServerCmd.RESET_CONNECTION)) + self._post_connection() diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/constants.py mysql-connector-python-2.0.4/lib/mysql/connector/constants.py --- mysql-connector-python-1.1.6/lib/mysql/connector/constants.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/constants.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,701 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +"""Various MySQL constants and character sets +""" + +from .errors import ProgrammingError +from .charsets import MYSQL_CHARACTER_SETS + +MAX_PACKET_LENGTH = 16777215 +NET_BUFFER_LENGTH = 8192 + + +def flag_is_set(flag, flags): + """Checks if the flag is set + + Returns boolean""" + if (flags & flag) > 0: + return True + return False + + +class _Constants(object): + """ + Base class for constants + """ + prefix = '' + desc = {} + + def __new__(cls): + raise TypeError("Can not instanciate from %s" % cls.__name__) + + @classmethod + def get_desc(cls, name): + """Get description of given constant""" + try: + return cls.desc[name][1] + except: + return None + + @classmethod + def get_info(cls, num): + """Get information about given constant""" + for name, info in cls.desc.items(): + if info[0] == num: + return name + return None + + @classmethod + def get_full_info(cls): + """get full information about given constant""" + res = () + try: + res = ["%s : %s" % (k, v[1]) for k, v in cls.desc.items()] + except Exception as err: # pylint: disable=W0703 + res = ('No information found in constant class.%s' % err) + + return res + + +class _Flags(_Constants): + """Base class for classes describing flags + """ + + @classmethod + def get_bit_info(cls, value): + """Get the name of all bits set + + Returns a list of strings.""" + res = [] + for name, info in cls.desc.items(): + if value & info[0]: + res.append(name) + return res + + +class FieldType(_Constants): + """MySQL Field Types + """ + prefix = 'FIELD_TYPE_' + DECIMAL = 0x00 + TINY = 0x01 + SHORT = 0x02 + LONG = 0x03 + FLOAT = 0x04 + DOUBLE = 0x05 + NULL = 0x06 + TIMESTAMP = 0x07 + LONGLONG = 0x08 + INT24 = 0x09 + DATE = 0x0a + TIME = 0x0b + DATETIME = 0x0c + YEAR = 0x0d + NEWDATE = 0x0e + VARCHAR = 0x0f + BIT = 0x10 + NEWDECIMAL = 0xf6 + ENUM = 0xf7 + SET = 0xf8 + TINY_BLOB = 0xf9 + MEDIUM_BLOB = 0xfa + LONG_BLOB = 0xfb + BLOB = 0xfc + VAR_STRING = 0xfd + STRING = 0xfe + GEOMETRY = 0xff + + desc = { + 'DECIMAL': (0x00, 'DECIMAL'), + 'TINY': (0x01, 'TINY'), + 'SHORT': (0x02, 'SHORT'), + 'LONG': (0x03, 'LONG'), + 'FLOAT': (0x04, 'FLOAT'), + 'DOUBLE': (0x05, 'DOUBLE'), + 'NULL': (0x06, 'NULL'), + 'TIMESTAMP': (0x07, 'TIMESTAMP'), + 'LONGLONG': (0x08, 'LONGLONG'), + 'INT24': (0x09, 'INT24'), + 'DATE': (0x0a, 'DATE'), + 'TIME': (0x0b, 'TIME'), + 'DATETIME': (0x0c, 'DATETIME'), + 'YEAR': (0x0d, 'YEAR'), + 'NEWDATE': (0x0e, 'NEWDATE'), + 'VARCHAR': (0x0f, 'VARCHAR'), + 'BIT': (0x10, 'BIT'), + 'NEWDECIMAL': (0xf6, 'NEWDECIMAL'), + 'ENUM': (0xf7, 'ENUM'), + 'SET': (0xf8, 'SET'), + 'TINY_BLOB': (0xf9, 'TINY_BLOB'), + 'MEDIUM_BLOB': (0xfa, 'MEDIUM_BLOB'), + 'LONG_BLOB': (0xfb, 'LONG_BLOB'), + 'BLOB': (0xfc, 'BLOB'), + 'VAR_STRING': (0xfd, 'VAR_STRING'), + 'STRING': (0xfe, 'STRING'), + 'GEOMETRY': (0xff, 'GEOMETRY'), + } + + @classmethod + def get_string_types(cls): + """Get the list of all string types""" + return [ + cls.VARCHAR, + cls.ENUM, + cls.VAR_STRING, cls.STRING, + ] + + @classmethod + def get_binary_types(cls): + """Get the list of all binary types""" + return [ + cls.TINY_BLOB, cls.MEDIUM_BLOB, + cls.LONG_BLOB, cls.BLOB, + ] + + @classmethod + def get_number_types(cls): + """Get the list of all number types""" + return [ + cls.DECIMAL, cls.NEWDECIMAL, + cls.TINY, cls.SHORT, cls.LONG, + cls.FLOAT, cls.DOUBLE, + cls.LONGLONG, cls.INT24, + cls.BIT, + cls.YEAR, + ] + + @classmethod + def get_timestamp_types(cls): + """Get the list of all timestamp types""" + return [ + cls.DATETIME, cls.TIMESTAMP, + ] + + +class FieldFlag(_Flags): + """MySQL Field Flags + + Field flags as found in MySQL sources mysql-src/include/mysql_com.h + """ + _prefix = '' + NOT_NULL = 1 << 0 + PRI_KEY = 1 << 1 + UNIQUE_KEY = 1 << 2 + MULTIPLE_KEY = 1 << 3 + BLOB = 1 << 4 + UNSIGNED = 1 << 5 + ZEROFILL = 1 << 6 + BINARY = 1 << 7 + + ENUM = 1 << 8 + AUTO_INCREMENT = 1 << 9 + TIMESTAMP = 1 << 10 + SET = 1 << 11 + + NO_DEFAULT_VALUE = 1 << 12 + ON_UPDATE_NOW = 1 << 13 + NUM = 1 << 14 + PART_KEY = 1 << 15 + GROUP = 1 << 14 # SAME AS NUM !!!!!!!???? + UNIQUE = 1 << 16 + BINCMP = 1 << 17 + + GET_FIXED_FIELDS = 1 << 18 + FIELD_IN_PART_FUNC = 1 << 19 + FIELD_IN_ADD_INDEX = 1 << 20 + FIELD_IS_RENAMED = 1 << 21 + + desc = { + 'NOT_NULL': (1 << 0, "Field can't be NULL"), + 'PRI_KEY': (1 << 1, "Field is part of a primary key"), + 'UNIQUE_KEY': (1 << 2, "Field is part of a unique key"), + 'MULTIPLE_KEY': (1 << 3, "Field is part of a key"), + 'BLOB': (1 << 4, "Field is a blob"), + 'UNSIGNED': (1 << 5, "Field is unsigned"), + 'ZEROFILL': (1 << 6, "Field is zerofill"), + 'BINARY': (1 << 7, "Field is binary "), + 'ENUM': (1 << 8, "field is an enum"), + 'AUTO_INCREMENT': (1 << 9, "field is a autoincrement field"), + 'TIMESTAMP': (1 << 10, "Field is a timestamp"), + 'SET': (1 << 11, "field is a set"), + 'NO_DEFAULT_VALUE': (1 << 12, "Field doesn't have default value"), + 'ON_UPDATE_NOW': (1 << 13, "Field is set to NOW on UPDATE"), + 'NUM': (1 << 14, "Field is num (for clients)"), + + 'PART_KEY': (1 << 15, "Intern; Part of some key"), + 'GROUP': (1 << 14, "Intern: Group field"), # Same as NUM + 'UNIQUE': (1 << 16, "Intern: Used by sql_yacc"), + 'BINCMP': (1 << 17, "Intern: Used by sql_yacc"), + 'GET_FIXED_FIELDS': (1 << 18, "Used to get fields in item tree"), + 'FIELD_IN_PART_FUNC': (1 << 19, "Field part of partition func"), + 'FIELD_IN_ADD_INDEX': (1 << 20, "Intern: Field used in ADD INDEX"), + 'FIELD_IS_RENAMED': (1 << 21, "Intern: Field is being renamed"), + } + + +class ServerCmd(_Constants): + """MySQL Server Commands + """ + _prefix = 'COM_' + SLEEP = 0 + QUIT = 1 + INIT_DB = 2 + QUERY = 3 + FIELD_LIST = 4 + CREATE_DB = 5 + DROP_DB = 6 + REFRESH = 7 + SHUTDOWN = 8 + STATISTICS = 9 + PROCESS_INFO = 10 + CONNECT = 11 + PROCESS_KILL = 12 + DEBUG = 13 + PING = 14 + TIME = 15 + DELAYED_INSERT = 16 + CHANGE_USER = 17 + BINLOG_DUMP = 18 + TABLE_DUMP = 19 + CONNECT_OUT = 20 + REGISTER_SLAVE = 21 + STMT_PREPARE = 22 + STMT_EXECUTE = 23 + STMT_SEND_LONG_DATA = 24 + STMT_CLOSE = 25 + STMT_RESET = 26 + SET_OPTION = 27 + STMT_FETCH = 28 + DAEMON = 29 + BINLOG_DUMP_GTID = 30 + RESET_CONNECTION = 31 + + desc = { + 'SLEEP': (0, 'SLEEP'), + 'QUIT': (1, 'QUIT'), + 'INIT_DB': (2, 'INIT_DB'), + 'QUERY': (3, 'QUERY'), + 'FIELD_LIST': (4, 'FIELD_LIST'), + 'CREATE_DB': (5, 'CREATE_DB'), + 'DROP_DB': (6, 'DROP_DB'), + 'REFRESH': (7, 'REFRESH'), + 'SHUTDOWN': (8, 'SHUTDOWN'), + 'STATISTICS': (9, 'STATISTICS'), + 'PROCESS_INFO': (10, 'PROCESS_INFO'), + 'CONNECT': (11, 'CONNECT'), + 'PROCESS_KILL': (12, 'PROCESS_KILL'), + 'DEBUG': (13, 'DEBUG'), + 'PING': (14, 'PING'), + 'TIME': (15, 'TIME'), + 'DELAYED_INSERT': (16, 'DELAYED_INSERT'), + 'CHANGE_USER': (17, 'CHANGE_USER'), + 'BINLOG_DUMP': (18, 'BINLOG_DUMP'), + 'TABLE_DUMP': (19, 'TABLE_DUMP'), + 'CONNECT_OUT': (20, 'CONNECT_OUT'), + 'REGISTER_SLAVE': (21, 'REGISTER_SLAVE'), + 'STMT_PREPARE': (22, 'STMT_PREPARE'), + 'STMT_EXECUTE': (23, 'STMT_EXECUTE'), + 'STMT_SEND_LONG_DATA': (24, 'STMT_SEND_LONG_DATA'), + 'STMT_CLOSE': (25, 'STMT_CLOSE'), + 'STMT_RESET': (26, 'STMT_RESET'), + 'SET_OPTION': (27, 'SET_OPTION'), + 'STMT_FETCH': (28, 'STMT_FETCH'), + 'DAEMON': (29, 'DAEMON'), + 'BINLOG_DUMP_GTID': (30, 'BINLOG_DUMP_GTID'), + 'RESET_CONNECTION': (31, 'RESET_CONNECTION'), + } + + +class ClientFlag(_Flags): + """MySQL Client Flags + + Client options as found in the MySQL sources mysql-src/include/mysql_com.h + """ + LONG_PASSWD = 1 << 0 + FOUND_ROWS = 1 << 1 + LONG_FLAG = 1 << 2 + CONNECT_WITH_DB = 1 << 3 + NO_SCHEMA = 1 << 4 + COMPRESS = 1 << 5 + ODBC = 1 << 6 + LOCAL_FILES = 1 << 7 + IGNORE_SPACE = 1 << 8 + PROTOCOL_41 = 1 << 9 + INTERACTIVE = 1 << 10 + SSL = 1 << 11 + IGNORE_SIGPIPE = 1 << 12 + TRANSACTIONS = 1 << 13 + RESERVED = 1 << 14 + SECURE_CONNECTION = 1 << 15 + MULTI_STATEMENTS = 1 << 16 + MULTI_RESULTS = 1 << 17 + PS_MULTI_RESULTS = 1 << 18 + PLUGIN_AUTH = 1 << 19 + CONNECT_ARGS = 1 << 20 + PLUGIN_AUTH_LENENC_CLIENT_DATA = 1 << 21 + CAN_HANDLE_EXPIRED_PASSWORDS = 1 << 22 + SSL_VERIFY_SERVER_CERT = 1 << 30 + REMEMBER_OPTIONS = 1 << 31 + + desc = { + 'LONG_PASSWD': (1 << 0, 'New more secure passwords'), + 'FOUND_ROWS': (1 << 1, 'Found instead of affected rows'), + 'LONG_FLAG': (1 << 2, 'Get all column flags'), + 'CONNECT_WITH_DB': (1 << 3, 'One can specify db on connect'), + 'NO_SCHEMA': (1 << 4, "Don't allow database.table.column"), + 'COMPRESS': (1 << 5, 'Can use compression protocol'), + 'ODBC': (1 << 6, 'ODBC client'), + 'LOCAL_FILES': (1 << 7, 'Can use LOAD DATA LOCAL'), + 'IGNORE_SPACE': (1 << 8, "Ignore spaces before ''"), + 'PROTOCOL_41': (1 << 9, 'New 4.1 protocol'), + 'INTERACTIVE': (1 << 10, 'This is an interactive client'), + 'SSL': (1 << 11, 'Switch to SSL after handshake'), + 'IGNORE_SIGPIPE': (1 << 12, 'IGNORE sigpipes'), + 'TRANSACTIONS': (1 << 13, 'Client knows about transactions'), + 'RESERVED': (1 << 14, 'Old flag for 4.1 protocol'), + 'SECURE_CONNECTION': (1 << 15, 'New 4.1 authentication'), + 'MULTI_STATEMENTS': (1 << 16, 'Enable/disable multi-stmt support'), + 'MULTI_RESULTS': (1 << 17, 'Enable/disable multi-results'), + 'SSL_VERIFY_SERVER_CERT': (1 << 30, ''), + 'REMEMBER_OPTIONS': (1 << 31, ''), + } + + default = [ + LONG_PASSWD, + LONG_FLAG, + CONNECT_WITH_DB, + PROTOCOL_41, + TRANSACTIONS, + SECURE_CONNECTION, + MULTI_STATEMENTS, + MULTI_RESULTS, + LOCAL_FILES, + ] + + @classmethod + def get_default(cls): + """Get the default client options set + + Returns a flag with all the default client options set""" + flags = 0 + for option in cls.default: + flags |= option + return flags + + +class ServerFlag(_Flags): + """MySQL Server Flags + + Server flags as found in the MySQL sources mysql-src/include/mysql_com.h + """ + _prefix = 'SERVER_' + STATUS_IN_TRANS = 1 << 0 + STATUS_AUTOCOMMIT = 1 << 1 + MORE_RESULTS_EXISTS = 1 << 3 + QUERY_NO_GOOD_INDEX_USED = 1 << 4 + QUERY_NO_INDEX_USED = 1 << 5 + STATUS_CURSOR_EXISTS = 1 << 6 + STATUS_LAST_ROW_SENT = 1 << 7 + STATUS_DB_DROPPED = 1 << 8 + STATUS_NO_BACKSLASH_ESCAPES = 1 << 9 + + desc = { + 'SERVER_STATUS_IN_TRANS': (1 << 0, + 'Transaction has started'), + 'SERVER_STATUS_AUTOCOMMIT': (1 << 1, + 'Server in auto_commit mode'), + 'SERVER_MORE_RESULTS_EXISTS': (1 << 3, + 'Multi query - ' + 'next query exists'), + 'SERVER_QUERY_NO_GOOD_INDEX_USED': (1 << 4, ''), + 'SERVER_QUERY_NO_INDEX_USED': (1 << 5, ''), + 'SERVER_STATUS_CURSOR_EXISTS': (1 << 6, ''), + 'SERVER_STATUS_LAST_ROW_SENT': (1 << 7, ''), + 'SERVER_STATUS_DB_DROPPED': (1 << 8, 'A database was dropped'), + 'SERVER_STATUS_NO_BACKSLASH_ESCAPES': (1 << 9, ''), + } + + +class RefreshOption(_Constants): + """MySQL Refresh command options + + Options used when sending the COM_REFRESH server command. + """ + _prefix = 'REFRESH_' + GRANT = 1 << 0 + LOG = 1 << 1 + TABLES = 1 << 2 + HOST = 1 << 3 + STATUS = 1 << 4 + THREADS = 1 << 5 + SLAVE = 1 << 6 + + desc = { + 'GRANT': (1 << 0, 'Refresh grant tables'), + 'LOG': (1 << 1, 'Start on new log file'), + 'TABLES': (1 << 2, 'close all tables'), + 'HOSTS': (1 << 3, 'Flush host cache'), + 'STATUS': (1 << 4, 'Flush status variables'), + 'THREADS': (1 << 5, 'Flush thread cache'), + 'SLAVE': (1 << 6, 'Reset master info and restart slave thread'), + } + + +class ShutdownType(_Constants): + """MySQL Shutdown types + + Shutdown types used by the COM_SHUTDOWN server command. + """ + _prefix = '' + SHUTDOWN_DEFAULT = b'\x00' + SHUTDOWN_WAIT_CONNECTIONS = b'\x01' + SHUTDOWN_WAIT_TRANSACTIONS = b'\x02' + SHUTDOWN_WAIT_UPDATES = b'\x08' + SHUTDOWN_WAIT_ALL_BUFFERS = b'\x10' + SHUTDOWN_WAIT_CRITICAL_BUFFERS = b'\x11' + KILL_QUERY = b'\xfe' + KILL_CONNECTION = b'\xff' + + desc = { + 'SHUTDOWN_DEFAULT': ( + SHUTDOWN_DEFAULT, + "defaults to SHUTDOWN_WAIT_ALL_BUFFERS"), + 'SHUTDOWN_WAIT_CONNECTIONS': ( + SHUTDOWN_WAIT_CONNECTIONS, + "wait for existing connections to finish"), + 'SHUTDOWN_WAIT_TRANSACTIONS': ( + SHUTDOWN_WAIT_TRANSACTIONS, + "wait for existing trans to finish"), + 'SHUTDOWN_WAIT_UPDATES': ( + SHUTDOWN_WAIT_UPDATES, + "wait for existing updates to finish"), + 'SHUTDOWN_WAIT_ALL_BUFFERS': ( + SHUTDOWN_WAIT_ALL_BUFFERS, + "flush InnoDB and other storage engine buffers"), + 'SHUTDOWN_WAIT_CRITICAL_BUFFERS': ( + SHUTDOWN_WAIT_CRITICAL_BUFFERS, + "don't flush InnoDB buffers, " + "flush other storage engines' buffers"), + 'KILL_QUERY': ( + KILL_QUERY, + "(no description)"), + 'KILL_CONNECTION': ( + KILL_CONNECTION, + "(no description)"), + } + + +class CharacterSet(_Constants): + """MySQL supported character sets and collations + + List of character sets with their collations supported by MySQL. This + maps to the character set we get from the server within the handshake + packet. + + The list is hardcode so we avoid a database query when getting the + name of the used character set or collation. + """ + desc = MYSQL_CHARACTER_SETS + + # Multi-byte character sets which use 5c (backslash) in characters + slash_charsets = (1, 13, 28, 84, 87, 88) + + @classmethod + def get_info(cls, setid): + """Retrieves character set information as tuple using an ID + + Retrieves character set and collation information based on the + given MySQL ID. + + Raises ProgrammingError when character set is not supported. + + Returns a tuple. + """ + try: + return cls.desc[setid][0:2] + except IndexError: + raise ProgrammingError( + "Character set '{0}' unsupported".format(setid)) + + @classmethod + def get_desc(cls, setid): + """Retrieves character set information as string using an ID + + Retrieves character set and collation information based on the + given MySQL ID. + + Returns a tuple. + """ + try: + return "%s/%s" % cls.get_info(setid) + except: + raise + + @classmethod + def get_default_collation(cls, charset): + """Retrieves the default collation for given character set + + Raises ProgrammingError when character set is not supported. + + Returns list (collation, charset, index) + """ + if isinstance(charset, int): + try: + info = cls.desc[charset] + return info[1], info[0], charset + except: + ProgrammingError("Character set ID '%s' unsupported." % ( + charset)) + + for cid, info in enumerate(cls.desc): + if info is None: + continue + if info[0] == charset and info[2] is True: + return info[1], info[0], cid + + raise ProgrammingError("Character set '%s' unsupported." % (charset)) + + @classmethod + 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 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 with (id, characterset name, collation) + """ + if isinstance(charset, int): + try: + info = cls.desc[charset] + return (charset, info[0], info[1]) + except IndexError: + ProgrammingError("Character set ID {0} unknown.".format( + 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 '{0}' unknown.".format(collation)) + else: + 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 '{0}' unknown.".format( + charset)) + + @classmethod + def get_supported(cls): + """Retrieves a list with names of all supproted character sets + + Returns a tuple. + """ + res = [] + for info in cls.desc: + if info and info[0] not in res: + res.append(info[0]) + return tuple(res) + + +class SQLMode(_Constants): # pylint: disable=R0921 + """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 available 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 hasattr(getattr(cls, key), '__call__'): + res.append(key) + return tuple(sorted(res)) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/conversion.py mysql-connector-python-2.0.4/lib/mysql/connector/conversion.py --- mysql-connector-python-1.1.6/lib/mysql/connector/conversion.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/conversion.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,573 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2015, 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 + +"""Converting MySQL and Python types +""" + +import datetime +import time +from decimal import Decimal + +from .constants import FieldType, FieldFlag, CharacterSet +from .catch23 import PY2, NUMERIC_TYPES, struct_unpack +from .custom_types import HexLiteral + + +class MySQLConverterBase(object): + """Base class for conversion classes + + All class dealing with converting to and from MySQL data types must + be a subclass of this class. + """ + + def __init__(self, charset='utf8', use_unicode=True): + self.python_types = None + self.mysql_types = None + self.charset = None + self.charset_id = 0 + self.use_unicode = None + self.set_charset(charset) + self.set_unicode(use_unicode) + + def set_charset(self, charset): + """Set character set""" + if charset == 'utf8mb4': + charset = 'utf8' + if charset is not None: + self.charset = charset + else: + # default to utf8 + self.charset = 'utf8' + self.charset_id = CharacterSet.get_charset_info(self.charset)[0] + + def set_unicode(self, value=True): + """Set whether to use Unicode""" + self.use_unicode = value + + def to_mysql(self, value): + """Convert Python data type to MySQL""" + type_name = value.__class__.__name__.lower() + try: + return getattr(self, "_{0}_to_mysql".format(type_name))(value) + except AttributeError: + return value + + def to_python(self, vtype, value): + """Convert MySQL data type to Python""" + return value + + def escape(self, buf): + """Escape buffer for sending to MySQL""" + return buf + + def quote(self, buf): + """Quote buffer for sending to MySQL""" + return str(buf) + + +class MySQLConverter(MySQLConverterBase): + """Default conversion class for MySQL Connector/Python. + + o escape method: for escaping values send to MySQL + o quoting method: for quoting values send to MySQL in statements + o conversion mapping: maps Python and MySQL data types to + function for converting them. + + Whenever one needs to convert values differently, a converter_class + argument can be given while instantiating a new connection like + cnx.connect(converter_class=CustomMySQLConverterClass). + + """ + + def __init__(self, charset=None, use_unicode=True): + MySQLConverterBase.__init__(self, charset, use_unicode) + self._cache_field_types = {} + + def escape(self, value): + """ + Escapes special characters as they are expected to by when MySQL + receives them. + As found in MySQL source mysys/charset.c + + Returns the value if not a string, or the escaped string. + """ + if value is None: + return value + elif isinstance(value, NUMERIC_TYPES): + return value + if isinstance(value, (bytes, bytearray)): + value = value.replace(b'\\', b'\\\\') + value = value.replace(b'\n', b'\\n') + value = value.replace(b'\r', b'\\r') + value = value.replace(b'\047', b'\134\047') # single quotes + value = value.replace(b'\042', b'\134\042') # double quotes + value = value.replace(b'\032', b'\134\032') # for Win32 + else: + value = value.replace('\\', '\\\\') + value = value.replace('\n', '\\n') + value = value.replace('\r', '\\r') + value = value.replace('\047', '\134\047') # single quotes + value = value.replace('\042', '\134\042') # double quotes + value = value.replace('\032', '\134\032') # for Win32 + return value + + def quote(self, buf): + """ + Quote the parameters for commands. General rules: + o numbers are returns as bytes using ascii codec + o None is returned as bytearray(b'NULL') + o Everything else is single quoted '' + + Returns a bytearray object. + """ + if isinstance(buf, NUMERIC_TYPES): + if PY2: + if isinstance(buf, float): + return repr(buf) + else: + return str(buf) + else: + return str(buf).encode('ascii') + elif isinstance(buf, type(None)): + return bytearray(b"NULL") + else: + return bytearray(b"'" + buf + b"'") + + def to_mysql(self, value): + """Convert Python data type to MySQL""" + type_name = value.__class__.__name__.lower() + try: + return getattr(self, "_{0}_to_mysql".format(type_name))(value) + except AttributeError: + raise TypeError("Python '{0}' cannot be converted to a " + "MySQL type".format(type_name)) + + def _int_to_mysql(self, value): + """Convert value to int""" + return int(value) + + def _long_to_mysql(self, value): + """Convert value to int""" + return int(value) + + def _float_to_mysql(self, value): + """Convert value to float""" + return float(value) + + def _str_to_mysql(self, value): + """Convert value to string""" + if PY2: + return str(value) + return self._unicode_to_mysql(value) + + def _unicode_to_mysql(self, value): + """Convert unicode""" + charset = self.charset + charset_id = self.charset_id + if charset == 'binary': + charset = 'utf8' + charset_id = CharacterSet.get_charset_info(charset)[0] + encoded = value.encode(charset) + if charset_id in CharacterSet.slash_charsets: + if b'\x5c' in encoded: + return HexLiteral(value, charset) + return encoded + + def _bytes_to_mysql(self, value): + """Convert value to bytes""" + return value + + def _bytearray_to_mysql(self, value): + """Convert value to bytes""" + return str(value) + + def _bool_to_mysql(self, value): + """Convert value to boolean""" + if value: + return 1 + else: + return 0 + + 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 conversion + from None to NULL happens in the quoting functionality. + + Return None. + """ + return None + + 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[.%f] + + If the instance isn't a datetime.datetime type, it return None. + + Returns a bytes. + """ + if value.microsecond: + fmt = '{0:d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:06d}' + return fmt.format( + value.year, value.month, value.day, + value.hour, value.minute, value.second, + value.microsecond).encode('ascii') + + fmt = '{0:d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}' + return fmt.format( + value.year, value.month, value.day, + value.hour, value.minute, value.second).encode('ascii') + + def _date_to_mysql(self, value): + """ + Converts a date instance to a string suitable for MySQL. + The returned string has format: %Y-%m-%d + + If the instance isn't a datetime.date type, it return None. + + Returns a bytes. + """ + return '{0:d}-{1:02d}-{2:02d}'.format(value.year, value.month, + value.day).encode('ascii') + + def _time_to_mysql(self, value): + """ + Converts a time instance to a string suitable for MySQL. + The returned string has format: %H:%M:%S[.%f] + + If the instance isn't a datetime.time type, it return None. + + Returns a bytes. + """ + if value.microsecond: + return value.strftime('%H:%M:%S.%f').encode('ascii') + return value.strftime('%H:%M:%S').encode('ascii') + + def _struct_time_to_mysql(self, value): + """ + Converts a time.struct_time sequence to a string suitable + for MySQL. + The returned string has format: %Y-%m-%d %H:%M:%S + + Returns a bytes or None when not valid. + """ + return time.strftime('%Y-%m-%d %H:%M:%S', value).encode('ascii') + + def _timedelta_to_mysql(self, value): + """ + Converts a timedelta instance to a string suitable for MySQL. + The returned string has format: %H:%M:%S + + Returns a bytes. + """ + seconds = abs(value.days * 86400 + value.seconds) + + if value.microseconds: + fmt = '{0:02d}:{1:02d}:{2:02d}.{3:06d}' + if value.days < 0: + mcs = 1000000 - value.microseconds + seconds -= 1 + else: + mcs = value.microseconds + else: + fmt = '{0:02d}:{1:02d}:{2:02d}' + + if value.days < 0: + fmt = '-' + fmt + + (hours, remainder) = divmod(seconds, 3600) + (mins, secs) = divmod(remainder, 60) + + if value.microseconds: + result = fmt.format(hours, mins, secs, mcs) + else: + result = fmt.format(hours, mins, secs) + + if PY2: + return result + else: + return result.encode('ascii') + + def _decimal_to_mysql(self, value): + """ + Converts a decimal.Decimal instance to a string suitable for + MySQL. + + Returns a bytes or None when not valid. + """ + if isinstance(value, Decimal): + return str(value).encode('ascii') + + return None + + def to_python(self, flddsc, value): + """ + Converts a given value coming from MySQL to a certain type in Python. + The flddsc contains additional information for the field in the + table. It's an element from MySQLCursor.description. + + Returns a mixed value. + """ + if value == 0 and flddsc[1] != FieldType.BIT: # \x00 + # Don't go further when we hit a NULL value + return None + if value is None: + return None + + if not self._cache_field_types: + self._cache_field_types = {} + for name, info in FieldType.desc.items(): + try: + self._cache_field_types[info[0]] = getattr( + self, '_{0}_to_python'.format(name)) + except AttributeError: + # We ignore field types which has no method + pass + + try: + return self._cache_field_types[flddsc[1]](value, flddsc) + except KeyError: + # If one type is not defined, we just return the value as str + try: + return value.decode('utf-8') + except UnicodeDecodeError: + return value + except ValueError as err: + raise ValueError("%s (field %s)" % (err, flddsc[0])) + except TypeError as err: + raise TypeError("%s (field %s)" % (err, flddsc[0])) + except: + raise + + def row_to_python(self, row, fields): + """Convert a MySQL text result row to Python types + + The row argument is a sequence containing text result returned + by a MySQL server. Each value of the row is converted to the + using the field type information in the fields argument. + + Returns a tuple. + """ + i = 0 + result = [None]*len(fields) + + if not self._cache_field_types: + self._cache_field_types = {} + for name, info in FieldType.desc.items(): + try: + self._cache_field_types[info[0]] = getattr( + self, '_{0}_to_python'.format(name)) + except AttributeError: + # We ignore field types which has no method + pass + + for field in fields: + field_type = field[1] + + if (row[i] == 0 and field_type != FieldType.BIT) or row[i] is None: + # Don't convert NULL value + i += 1 + continue + + try: + result[i] = self._cache_field_types[field_type](row[i], field) + except KeyError: + # If one type is not defined, we just return the value as str + try: + result[i] = row[i].decode('utf-8') + except UnicodeDecodeError: + result[i] = row[i] + except (ValueError, TypeError) as err: + err.message = "{0} (field {1})".format(str(err), field[0]) + raise + + i += 1 + + return tuple(result) + + def _FLOAT_to_python(self, value, desc=None): # pylint: disable=C0103 + """ + Returns value as float type. + """ + return float(value) + + _DOUBLE_to_python = _FLOAT_to_python + + def _INT_to_python(self, value, desc=None): # pylint: disable=C0103 + """ + Returns value as int type. + """ + return int(value) + + _TINY_to_python = _INT_to_python + _SHORT_to_python = _INT_to_python + _INT24_to_python = _INT_to_python + _LONG_to_python = _INT_to_python + _LONGLONG_to_python = _INT_to_python + + def _DECIMAL_to_python(self, value, desc=None): # pylint: disable=C0103 + """ + Returns value as a decimal.Decimal. + """ + val = value.decode(self.charset) + return Decimal(val) + + _NEWDECIMAL_to_python = _DECIMAL_to_python + + def _str(self, value, desc=None): + """ + Returns value as str type. + """ + return str(value) + + def _BIT_to_python(self, value, dsc=None): # pylint: disable=C0103 + """Returns BIT columntype as integer""" + int_val = value + if len(int_val) < 8: + int_val = b'\x00' * (8 - len(int_val)) + int_val + return struct_unpack('>Q', int_val)[0] + + def _DATE_to_python(self, value, dsc=None): # pylint: disable=C0103 + """ + Returns DATE column type as datetime.date type. + """ + try: + parts = value.split(b'-') + return datetime.date(int(parts[0]), int(parts[1]), int(parts[2])) + except ValueError: + return None + + _NEWDATE_to_python = _DATE_to_python + + def _TIME_to_python(self, value, dsc=None): # pylint: disable=C0103 + """ + Returns TIME column type as datetime.time type. + """ + time_val = None + try: + (hms, mcs) = value.split(b'.') + mcs = int(mcs.ljust(6, b'0')) + except ValueError: + hms = value + mcs = 0 + try: + (hours, mins, secs) = [int(d) for d in hms.split(b':')] + if value[0] == 45 or value[0] == '-': # if PY3 or PY2 + mins, secs, mcs = -mins, -secs, -mcs + time_val = datetime.timedelta(hours=hours, minutes=mins, + seconds=secs, microseconds=mcs) + except ValueError: + raise ValueError( + "Could not convert {0} to python datetime.timedelta".format( + value)) + else: + return time_val + + def _DATETIME_to_python(self, value, dsc=None): # pylint: disable=C0103 + """ + Returns DATETIME column type as datetime.datetime type. + """ + datetime_val = None + try: + (date_, time_) = value.split(b' ') + if len(time_) > 8: + (hms, mcs) = time_.split(b'.') + mcs = int(mcs.ljust(6, b'0')) + else: + hms = time_ + mcs = 0 + dtval = [int(i) for i in date_.split(b'-')] + \ + [int(i) for i in hms.split(b':')] + [mcs, ] + datetime_val = datetime.datetime(*dtval) + except ValueError: + datetime_val = None + + return datetime_val + + _TIMESTAMP_to_python = _DATETIME_to_python + + def _YEAR_to_python(self, value, desc=None): # pylint: disable=C0103 + """Returns YEAR column type as integer""" + try: + year = int(value) + except ValueError: + raise ValueError("Failed converting YEAR to int (%s)" % value) + + return year + + def _SET_to_python(self, value, dsc=None): # pylint: disable=C0103 + """Returns SET column typs as set + + Actually, MySQL protocol sees a SET as a string type field. So this + code isn't called directly, but used by STRING_to_python() method. + + Returns SET column type as a set. + """ + set_type = None + val = value.decode(self.charset) + if not val: + return set() + try: + set_type = set(val.split(',')) + except ValueError: + raise ValueError("Could not convert set %s to a sequence." % value) + return set_type + + def _STRING_to_python(self, value, dsc=None): # pylint: disable=C0103 + """ + Note that a SET is a string too, but using the FieldFlag we can see + whether we have to split it. + + Returns string typed columns as string type. + """ + if dsc is not None: + # Check if we deal with a SET + if dsc[7] & FieldFlag.SET: + return self._SET_to_python(value, dsc) + if dsc[7] & FieldFlag.BINARY: + return value + + if self.charset == 'binary': + return value + if isinstance(value, (bytes, bytearray)) and self.use_unicode: + return value.decode(self.charset) + + return value + + _VAR_STRING_to_python = _STRING_to_python + + def _BLOB_to_python(self, value, dsc=None): # pylint: disable=C0103 + """Convert BLOB data type to Python""" + if dsc is not None: + if dsc[7] & FieldFlag.BINARY: + if PY2: + return value + else: + return bytes(value) + + return self._STRING_to_python(value, dsc) + + _LONG_BLOB_to_python = _BLOB_to_python + _MEDIUM_BLOB_to_python = _BLOB_to_python + _TINY_BLOB_to_python = _BLOB_to_python diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/cursor.py mysql-connector-python-2.0.4/lib/mysql/connector/cursor.py --- mysql-connector-python-1.1.6/lib/mysql/connector/cursor.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/cursor.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,1282 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +"""Cursor classes +""" + +from collections import namedtuple +import re +import weakref + +from . import errors + +SQL_COMMENT = r"\/\*.*?\*\/" +RE_SQL_COMMENT = re.compile( + r'''({0})|(["'`][^"'`]*?({0})[^"'`]*?["'`])'''.format(SQL_COMMENT), + re.I | re.M | re.S) +RE_SQL_ON_DUPLICATE = re.compile( + r'''\s*ON\s+DUPLICATE\s+KEY(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$''', + re.I | re.M | re.S) +RE_SQL_INSERT_STMT = re.compile( + r"({0}|\s)*INSERT({0}|\s)*INTO.+VALUES.*".format(SQL_COMMENT), + re.I | re.M | re.S) +RE_SQL_INSERT_VALUES = re.compile(r'.*VALUES\s*(\(.*\)).*', re.I | re.M | re.S) +RE_PY_PARAM = re.compile(b'(%s)') +RE_SQL_SPLIT_STMTS = re.compile( + b''';(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''') +RE_SQL_FIND_PARAM = re.compile( + b'''%s(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''') + +_ERR_NO_RESULT_TO_FETCH = "No result set to fetch from." + + +class _ParamSubstitutor(object): + """ + Substitutes parameters into SQL statement. + """ + def __init__(self, params): + self.params = params + self.index = 0 + + def __call__(self, matchobj): + index = self.index + self.index += 1 + try: + return bytes(self.params[index]) + except IndexError: + raise errors.ProgrammingError( + "Not enough parameters for the SQL statement") + + @property + def remaining(self): + """Returns number of parameters remaining to be substituted""" + return len(self.params) - self.index + + +class CursorBase(object): + """ + Base for defining MySQLCursor. This class is a skeleton and defines + methods and members as required for the Python Database API + Specification v2.0. + + It's better to inherite from MySQLCursor. + """ + + def __init__(self): + self._description = None + self._rowcount = -1 + self._last_insert_id = None + self.arraysize = 1 + + def callproc(self, procname, args=()): + """Calls a stored procedue with the given arguments + + The arguments will be set during this session, meaning + they will be called like ___arg where + is an enumeration (+1) of the arguments. + + Coding Example: + 1) Definining the Stored Routine in MySQL: + CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) + BEGIN + SET pProd := pFac1 * pFac2; + END + + 2) Executing in Python: + args = (5,5,0) # 0 is to hold pprod + cursor.callproc('multiply', args) + print(cursor.fetchone()) + + Does not return a value, but a result set will be + available when the CALL-statement execute successfully. + Raises exceptions when something is wrong. + """ + pass + + def close(self): + """Close the cursor.""" + pass + + def execute(self, operation, params=(), 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,)) + + 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. + + Returns an iterator when multi is True, otherwise None. + """ + pass + + def executemany(self, operation, seqparams): + """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. + """ + pass + + def fetchone(self): + """Returns next row of a query result set + + Returns a tuple or None. + """ + pass + + def fetchmany(self, size=1): + """Returns the next set of rows of a query result, returning a + list of tuples. When no more rows are available, it returns an + empty list. + + The number of rows returned can be specified using the size argument, + which defaults to one + """ + pass + + def fetchall(self): + """Returns all rows of a query result set + + Returns a list of tuples. + """ + pass + + def nextset(self): + """Not Implemented.""" + pass + + def setinputsizes(self, sizes): + """Not Implemented.""" + pass + + def setoutputsize(self, size, column=None): + """Not Implemented.""" + pass + + def reset(self): + """Reset the cursor to default""" + 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 for interacting with MySQL + + 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 (PEP-249) + """ + def __init__(self, connection=None): + CursorBase.__init__(self) + self._connection = None + self._stored_results = [] + self._nextrow = (None, None) + self._warnings = None + self._warning_count = 0 + self._executed = None + self._executed_list = [] + self._binary = False + + if connection is not None: + self._set_connection(connection) + + def __iter__(self): + """ + Iteration over the result set which calls self.fetchone() + and returns the next row. + """ + return iter(self.fetchone, None) + + def _set_connection(self, connection): + """Set the connection""" + try: + self._connection = weakref.proxy(connection) + self._connection._protocol # pylint: disable=W0212,W0104 + except (AttributeError, TypeError): + raise errors.InterfaceError(errno=2048) + + def _reset_result(self): + """Reset the cursor to default""" + self._rowcount = -1 + self._nextrow = (None, None) + self._stored_results = [] + self._warnings = None + self._warning_count = 0 + 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): + """Used for iterating over the result set.""" + return self.__next__() + + def __next__(self): + """ + Used for iterating over the result set. Calles self.fetchone() + to get the next row. + """ + try: + row = self.fetchone() + except errors.InterfaceError: + raise StopIteration + if not row: + raise StopIteration + return row + + def close(self): + """Close the cursor + + Returns True when successful, otherwise False. + """ + 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): + """Process query parameters given as dictionary""" + try: + to_mysql = self._connection.converter.to_mysql + escape = self._connection.converter.escape + quote = self._connection.converter.quote + res = {} + for key, value in list(params.items()): + conv = value + conv = to_mysql(conv) + conv = escape(conv) + conv = quote(conv) + res["%({0})s".format(key).encode()] = conv + except Exception as err: + raise errors.ProgrammingError( + "Failed processing pyformat-parameters; %s" % err) + else: + return res + + def _process_params(self, params): + """Process query parameters.""" + try: + res = params + + to_mysql = self._connection.converter.to_mysql + escape = self._connection.converter.escape + quote = self._connection.converter.quote + + res = [to_mysql(i) for i in res] + res = [escape(i) for i in res] + res = [quote(i) for i in res] + except Exception as err: + raise errors.ProgrammingError( + "Failed processing format-parameters; %s" % err) + else: + return tuple(res) + + def _handle_noresultset(self, res): + """Handles result of execute() when there is no result set + """ + try: + self._rowcount = res['affected_rows'] + self._last_insert_id = res['insert_id'] + self._warning_count = res['warning_count'] + except (KeyError, TypeError) as err: + raise errors.ProgrammingError( + "Failed handling non-resultset; {0}".format(err)) + + if self._connection.get_warnings is True and self._warning_count: + self._warnings = self._fetch_warnings() + + def _handle_resultset(self): + """Handles result set + + This method handles the result set and is called after reading + and storing column information in _handle_result(). For non-buffering + cursors, this method is usually doing nothing. + """ + pass + + 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 + + This method is only used when multiple statements are executed + by the execute() method. It uses zip() to make an iterator from the + given query_iter (result of MySQLConnection.cmd_query_iter()) and + the list of statements that were executed. + """ + executed_list = RE_SQL_SPLIT_STMTS.split(self._executed) + + i = 0 + while True: + result = next(query_iter) + self._reset_result() + self._handle_result(result) + try: + self._executed = executed_list[i].strip() + i += 1 + except IndexError: + self._executed = executed_list[0] + + 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,)) + + 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. + + Returns an iterator when multi is True, otherwise None. + """ + if not operation: + return None + + if not self._connection: + raise errors.ProgrammingError("Cursor is not connected.") + if self._connection.unread_result is True: + raise errors.InternalError("Unread result found.") + + self._reset_result() + stmt = '' + + try: + if not isinstance(operation, (bytes, bytearray)): + stmt = operation.encode(self._connection.python_charset) + else: + stmt = operation + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise errors.ProgrammingError(str(err)) + + if params is not None: + if isinstance(params, dict): + for key, value in self._process_params_dict(params).items(): + stmt = stmt.replace(key, value) + elif isinstance(params, (list, tuple)): + psub = _ParamSubstitutor(self._process_params(params)) + stmt = RE_PY_PARAM.sub(psub, stmt) + if psub.remaining != 0: + raise errors.ProgrammingError( + "Not all parameters were used in the SQL statement") + + if multi: + self._executed = stmt + 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: + if self._connection._have_next_result: # pylint: disable=W0212 + raise errors.InterfaceError( + "Use multi=True when executing multiple statements") + raise + return None + + def _batch_insert(self, operation, seq_params): + """Implements multi row insert""" + def remove_comments(match): + """Remove comments from INSERT statements. + + This function is used while removing comments from INSERT + statements. If the matched string is a comment not enclosed + by quotes, it returns an empty string, else the string itself. + """ + if match.group(1): + return "" + else: + return match.group(2) + + tmp = re.sub(RE_SQL_ON_DUPLICATE, '', + re.sub(RE_SQL_COMMENT, remove_comments, operation)) + + matches = re.search(RE_SQL_INSERT_VALUES, tmp) + if not matches: + raise errors.InterfaceError( + "Failed rewriting statement for multi-row INSERT. " + "Check SQL syntax." + ) + fmt = matches.group(1).encode(self._connection.charset) + values = [] + + try: + stmt = operation.encode(self._connection.charset) + for params in seq_params: + tmp = fmt + if isinstance(params, dict): + for key, value in self._process_params_dict(params).items(): + tmp = tmp.replace(key, value) + else: + psub = _ParamSubstitutor(self._process_params(params)) + tmp = RE_PY_PARAM.sub(psub, tmp) + if psub.remaining != 0: + raise errors.ProgrammingError( + "Not all parameters were used in the SQL statement") + #for p in self._process_params(params): + # tmp = tmp.replace(b'%s',p,1) + values.append(tmp) + if fmt in stmt: + stmt = stmt.replace(fmt, b','.join(values), 1) + self._executed = stmt + return stmt + else: + return None + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise errors.ProgrammingError(str(err)) + except errors.Error: + raise + except Exception as err: + raise errors.InterfaceError( + "Failed executing the operation; %s" % err) + + def executemany(self, operation, seq_params): + """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 or not seq_params: + return None + if self._connection.unread_result is True: + raise errors.InternalError("Unread result found.") + if not isinstance(seq_params, (list, tuple)): + raise errors.ProgrammingError( + "Parameters for query must be list or tuple.") + + # Optimize INSERTs by batching them + if re.match(RE_SQL_INSERT_STMT, operation): + if not seq_params: + self._rowcount = 0 + return + stmt = self._batch_insert(operation, seq_params) + if stmt is not None: + return self.execute(stmt) + + rowcnt = 0 + try: + 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) as err: + raise errors.InterfaceError( + "Failed executing the operation; {0}".format(err)) + except: + # Raise whatever execute() raises + raise + self._rowcount = rowcnt + + def stored_results(self): + """Returns an iterator for stored results + + 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 procedure with the given arguments + + The arguments will be set during this session, meaning + they will be called like ___arg where + is an enumeration (+1) of the arguments. + + Coding Example: + 1) Defining the Stored Routine in MySQL: + CREATE PROCEDURE multiply(IN pFac1 INT, IN pFac2 INT, OUT pProd INT) + BEGIN + SET pProd := pFac1 * pFac2; + END + + 2) Executing in Python: + args = (5, 5, 0) # 0 is to hold pprod + cursor.callproc('multiply', args) + print(cursor.fetchone()) + + For OUT and INOUT parameters the user should provide the + type of the parameter as well. The argument should be a + tuple with first item as the value of the parameter to pass + and second argument the type of the argument. + + In the above example, one can call callproc method like: + args = (5, 5, (0, 'INT')) + cursor.callproc('multiply', args) + + The type of the argument given in the tuple will be used by + the MySQL CAST function to convert the values in the corresponding + MySQL type (See CAST in MySQL Reference for more information) + + Does not return a value, but a result set will be + 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 = "@_{name}_arg{index}" + self._stored_results = [] + + results = [] + try: + argnames = [] + argtypes = [] + if args: + for idx, arg in enumerate(args): + argname = argfmt.format(name=procname, index=idx + 1) + argnames.append(argname) + if isinstance(arg, tuple): + argtypes.append(" CAST({0} AS {1})".format(argname, + arg[1])) + self.execute("SET {0}=%s".format(argname), (arg[0],)) + else: + argtypes.append(argname) + self.execute("SET {0}=%s".format(argname), (arg,)) + + call = "CALL {0}({1})".format(procname, ','.join(argnames)) + + for result in self._connection.cmd_query_iter(call): + # pylint: disable=W0212 + tmp = MySQLCursorBuffered(self._connection._get_self()) + tmp._handle_result(result) + if tmp._warnings is not None: + self._warnings = tmp._warnings + # pylint: enable=W0212 + if 'columns' in result: + results.append(tmp) + + if argnames: + select = "SELECT {0}".format(','.join(argtypes)) + self.execute(select) + self._stored_results = results + return self.fetchone() + else: + self._stored_results = results + return () + + except errors.Error: + raise + except Exception as err: + raise errors.InterfaceError( + "Failed calling stored routine; {0}".format(err)) + + def getlastrowid(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. + + Returns a long value or None. + """ + return self._last_insert_id + + def _fetch_warnings(self): + """ + Fetch warnings doing a SHOW WARNINGS. Can be called after getting + the result. + + Returns a result set or None when there were no warnings. + """ + res = [] + try: + cur = self._connection.cursor() + cur.execute("SHOW WARNINGS") + res = cur.fetchall() + cur.close() + except Exception as err: + raise errors.InterfaceError( + "Failed getting warnings; %s" % err) + + if self._connection.raise_on_warnings is True: + raise errors.get_mysql_exception(res[0][1], res[0][2]) + else: + if len(res): + return res + + return None + + def _handle_eof(self, eof): + """Handle EOF packet""" + self._connection.unread_result = False + self._nextrow = (None, None) + self._warning_count = eof['warning_count'] + if self._connection.get_warnings is True and eof['warning_count']: + self._warnings = self._fetch_warnings() + + def _fetch_row(self): + """Returns the next row in the result set + + Returns a tuple or None. + """ + if not self._have_unread_result(): + return None + row = None + + if self._nextrow == (None, None): + (row, eof) = self._connection.get_row( + binary=self._binary, columns=self.description) + else: + (row, eof) = self._nextrow + + if row: + self._nextrow = self._connection.get_row( + binary=self._binary, columns=self.description) + eof = self._nextrow[1] + if eof is not None: + self._handle_eof(eof) + if self._rowcount == -1: + self._rowcount = 1 + else: + self._rowcount += 1 + if eof: + self._handle_eof(eof) + + return row + + def fetchwarnings(self): + """Returns Warnings.""" + return self._warnings + + def fetchone(self): + """Returns next row of a query result set + + Returns a tuple or None. + """ + row = self._fetch_row() + if row: + return self._connection.converter.row_to_python( + row, self.description) + return None + + def fetchmany(self, size=None): + res = [] + cnt = (size or self.arraysize) + while cnt > 0 and self._have_unread_result(): + cnt -= 1 + row = self.fetchone() + if row: + res.append(row) + return res + + def fetchall(self): + if not self._have_unread_result(): + raise errors.InterfaceError("No result set to fetch from.") + (rows, eof) = self._connection.get_rows() + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) + res = [self._connection.converter.row_to_python(row, self.description) + 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] 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. + """ + try: + return self._executed.strip().decode('utf8') + except AttributeError: + 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 __str__(self): + fmt = "MySQLCursor: %s" + if self._executed: + executed = bytearray(self._executed).decode('utf-8') + if len(executed) > 30: + res = fmt % (executed[:30] + '..') + else: + res = fmt % (executed) + else: + res = fmt % '(Nothing executed yet)' + return res + + +class MySQLCursorBuffered(MySQLCursor): + """Cursor which fetches rows within execute()""" + + def __init__(self, connection=None): + MySQLCursor.__init__(self, connection) + self._rows = None + self._next_row = 0 + + def _handle_resultset(self): + (self._rows, eof) = self._connection.get_rows() + self._rowcount = len(self._rows) + self._handle_eof(eof) + self._next_row = 0 + try: + self._connection.unread_result = False + except: + pass + + def reset(self): + self._rows = None + + def _fetch_row(self): + row = None + try: + row = self._rows[self._next_row] + except: + return None + else: + self._next_row += 1 + return row + return None + + def fetchall(self): + if self._rows is None: + raise errors.InterfaceError("No result set to fetch from.") + res = [] + for row in self._rows[self._next_row:]: + res.append(self._connection.converter.row_to_python( + row, self.description)) + self._next_row = len(self._rows) + return res + + def fetchmany(self, size=None): + res = [] + cnt = (size or self.arraysize) + while cnt > 0: + cnt -= 1 + row = self.fetchone() + if row: + res.append(row) + + return res + + @property + def with_rows(self): + return self._rows is not None + + +class MySQLCursorRaw(MySQLCursor): + """ + Skips conversion from MySQL datatypes to Python types when fetching rows. + """ + def fetchone(self): + row = self._fetch_row() + if row: + return row + return None + + def fetchall(self): + if not self._have_unread_result(): + raise errors.InterfaceError("No result set to fetch from.") + (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): + """ + Cursor which skips conversion from MySQL datatypes to Python types when + fetching rows and fetches rows within execute(). + """ + def fetchone(self): + row = self._fetch_row() + if row: + return row + return None + + def fetchall(self): + if self._rows is None: + raise errors.InterfaceError("No result set to fetch from.") + return [r for r in self._rows[self._next_row:]] + + @property + def with_rows(self): + return self._rows is not None + + +class MySQLCursorPrepared(MySQLCursor): + """Cursor using MySQL Prepared Statements + """ + def __init__(self, connection=None): + super(MySQLCursorPrepared, self).__init__(connection) + self._rows = None + self._next_row = 0 + self._prepared = None + self._binary = True + self._have_result = None + + def callproc(self, *args, **kwargs): + """Calls a stored procedue + + Not supported with MySQLCursorPrepared. + """ + raise errors.NotSupportedError() + + def close(self): + """Close the cursor + + This method will try to deallocate the prepared statement and close + the cursor. + """ + if self._prepared: + try: + self._connection.cmd_stmt_close(self._prepared['statement_id']) + except errors.Error: + # We tried to deallocate, but it's OK when we fail. + pass + self._prepared = None + super(MySQLCursorPrepared, self).close() + + def _row_to_python(self, rowdata, desc=None): + """Convert row data from MySQL to Python types + + The conversion is done while reading binary data in the + protocol module. + """ + pass + + def _handle_result(self, res): + """Handle result after execution""" + if isinstance(res, dict): + self._connection.unread_result = False + self._have_result = False + self._handle_noresultset(res) + else: + self._description = res[1] + self._connection.unread_result = True + self._have_result = True + + def execute(self, operation, params=(), multi=False): # multi is unused + """Prepare and execute a MySQL Prepared Statement + + This method will preare the given operation and execute it using + the optionally given parameters. + + If the cursor instance already had a prepared statement, it is + first closed. + """ + if operation is not self._executed: + if self._prepared: + self._connection.cmd_stmt_close(self._prepared['statement_id']) + + self._executed = operation + try: + if not isinstance(operation, bytes): + operation = operation.encode(self._connection.charset) + except (UnicodeDecodeError, UnicodeEncodeError) as err: + raise errors.ProgrammingError(str(err)) + + # need to convert %s to ? before sending it to MySQL + if b'%s' in operation: + operation = re.sub(RE_SQL_FIND_PARAM, b'?', operation) + + try: + self._prepared = self._connection.cmd_stmt_prepare(operation) + except errors.Error: + self._executed = None + raise + + self._connection.cmd_stmt_reset(self._prepared['statement_id']) + + if self._prepared['parameters'] and not params: + return + elif len(self._prepared['parameters']) != len(params): + raise errors.ProgrammingError( + errno=1210, + msg="Incorrect number of arguments " \ + "executing prepared statement") + + res = self._connection.cmd_stmt_execute( + self._prepared['statement_id'], + data=params, + parameters=self._prepared['parameters']) + self._handle_result(res) + + def executemany(self, operation, seq_params): + """Prepare and execute a MySQL Prepared Statement many times + + This method will prepare the given operation and execute with each + tuple found the list seq_params. + + If the cursor instance already had a prepared statement, it is + first closed. + + executemany() simply calls execute(). + """ + rowcnt = 0 + try: + 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) as err: + raise errors.InterfaceError( + "Failed executing the operation; {error}".format(error=err)) + except: + # Raise whatever execute() raises + raise + self._rowcount = rowcnt + + def fetchone(self): + """Returns next row of a query result set + + Returns a tuple or None. + """ + return self._fetch_row() or None + + def fetchmany(self, size=None): + res = [] + cnt = (size or self.arraysize) + while cnt > 0 and self._have_unread_result(): + cnt -= 1 + row = self._fetch_row() + if row: + res.append(row) + return res + + def fetchall(self): + if not self._have_unread_result(): + raise errors.InterfaceError("No result set to fetch from.") + (rows, eof) = self._connection.get_rows( + binary=self._binary, columns=self.description) + self._rowcount = len(rows) + self._handle_eof(eof) + return rows + + +class MySQLCursorDict(MySQLCursor): + """ + Cursor fetching rows as dictionaries. + + The fetch methods of this class will return dictionaries instead of tuples. + Each row is a dictionary that looks like: + row = { + "col1": value1, + "col2": value2 + } + """ + def _row_to_python(self, rowdata, desc=None): + """Convert a MySQL text result row to Python types + + Returns a dictionary. + """ + row = self._connection.converter.row_to_python(rowdata, desc) + if row: + return dict(zip(self.column_names, row)) + return None + + def fetchone(self): + """Returns next row of a query result set + """ + row = self._fetch_row() + if row: + return self._row_to_python(row, self.description) + return None + + def fetchall(self): + """Returns all rows of a query result set + """ + if not self._have_unread_result(): + raise errors.InterfaceError(_ERR_NO_RESULT_TO_FETCH) + (rows, eof) = self._connection.get_rows() + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) + res = [self._row_to_python(row, self.description) + 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 + + +class MySQLCursorNamedTuple(MySQLCursor): + """ + Cursor fetching rows as named tuple. + + The fetch methods of this class will return namedtuples instead of tuples. + Each row is returned as a namedtuple and the values can be accessed as: + row.col1, row.col2 + """ + def _row_to_python(self, rowdata, desc=None): + """Convert a MySQL text result row to Python types + + Returns a named tuple. + """ + row = self._connection.converter.row_to_python(rowdata, desc) + if row: + # pylint: disable=W0201 + self.named_tuple = namedtuple('Row', self.column_names) + # pylint: enable=W0201 + return self.named_tuple(*row) + + def fetchone(self): + """Returns next row of a query result set + """ + row = self._fetch_row() + if row: + return self._row_to_python(row, self.description) + return None + + def fetchall(self): + """Returns all rows of a query result set + """ + if not self._have_unread_result(): + raise errors.InterfaceError(_ERR_NO_RESULT_TO_FETCH) + (rows, eof) = self._connection.get_rows() + if self._nextrow[0]: + rows.insert(0, self._nextrow[0]) + res = [self._row_to_python(row, self.description) + 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 + + +class MySQLCursorBufferedDict(MySQLCursorDict, MySQLCursorBuffered): + """ + Buffered Cursor fetching rows as dictionaries. + """ + def fetchone(self): + """Returns next row of a query result set + """ + row = self._fetch_row() + if row: + return self._row_to_python(row, self.description) + return None + + def fetchall(self): + """Returns all rows of a query result set + """ + if self._rows is None: + raise errors.InterfaceError(_ERR_NO_RESULT_TO_FETCH) + res = [] + for row in self._rows[self._next_row:]: + res.append(self._row_to_python( + row, self.description)) + self._next_row = len(self._rows) + return res + + +class MySQLCursorBufferedNamedTuple(MySQLCursorNamedTuple, MySQLCursorBuffered): + """ + Buffered Cursor fetching rows as named tuple. + """ + def fetchone(self): + """Returns next row of a query result set + """ + row = self._fetch_row() + if row: + return self._row_to_python(row, self.description) + return None + + def fetchall(self): + """Returns all rows of a query result set + """ + if self._rows is None: + raise errors.InterfaceError(_ERR_NO_RESULT_TO_FETCH) + res = [] + for row in self._rows[self._next_row:]: + res.append(self._row_to_python( + row, self.description)) + self._next_row = len(self._rows) + return res diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/custom_types.py mysql-connector-python-2.0.4/lib/mysql/connector/custom_types.py --- mysql-connector-python-1.1.6/lib/mysql/connector/custom_types.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/custom_types.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,45 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2014, 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 + +"""Custom Python types used by MySQL Connector/Python""" + + +import sys + + +class HexLiteral(str): + + """Class holding MySQL hex literals""" + + def __new__(cls, str_, charset='utf8'): + if sys.version_info[0] == 2: + hexed = ["%02x" % ord(i) for i in str_.encode(charset)] + else: + hexed = ["%02x" % i for i in str_.encode(charset)] + obj = str.__new__(cls, ''.join(hexed)) + obj.charset = charset + obj.original = str_ + return obj + + def __str__(self): + return '0x' + self diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/dbapi.py mysql-connector-python-2.0.4/lib/mysql/connector/dbapi.py --- mysql-connector-python-1.1.6/lib/mysql/connector/dbapi.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/dbapi.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,75 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 module implements some constructors and singletons as required by the +DB API v2.0 (PEP-249). +""" + +# Python Db API v2 +apilevel = '2.0' +threadsafety = 1 +paramstyle = 'pyformat' + +import time +import datetime + +from . import constants + +class _DBAPITypeObject(object): + + def __init__(self, *values): + self.values = values + + def __eq__(self, other): + if other in self.values: + return True + else: + return False + + def __ne__(self, other): + if other in self.values: + return False + else: + return True + +Date = datetime.date +Time = datetime.time +Timestamp = datetime.datetime + +def DateFromTicks(ticks): + return Date(*time.localtime(ticks)[:3]) + +def TimeFromTicks(ticks): + return Time(*time.localtime(ticks)[3:6]) + +def TimestampFromTicks(ticks): + return Timestamp(*time.localtime(ticks)[:6]) + +Binary = bytes + +STRING = _DBAPITypeObject(*constants.FieldType.get_string_types()) +BINARY = _DBAPITypeObject(*constants.FieldType.get_binary_types()) +NUMBER = _DBAPITypeObject(*constants.FieldType.get_number_types()) +DATETIME = _DBAPITypeObject(*constants.FieldType.get_timestamp_types()) +ROWID = _DBAPITypeObject() diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/base.py mysql-connector-python-2.0.4/lib/mysql/connector/django/base.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/base.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/base.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,733 @@ +# MySQL Connector/Python - MySQL driver written in Python. + +"""Django database Backend using MySQL Connector/Python + +This Django database backend is heavily based on the MySQL backend coming +with Django. + +Changes include: +* Support for microseconds (MySQL 5.6.3 and later) +* Using INFORMATION_SCHEMA where possible +* Using new defaults for, for example SQL_AUTO_IS_NULL + +Requires and comes with MySQL Connector/Python v1.1 and later: + http://dev.mysql.com/downloads/connector/python/ +""" + + +from __future__ import unicode_literals + +import sys + +import django +from django.utils.functional import cached_property + +try: + import mysql.connector + from mysql.connector.conversion import MySQLConverter +except ImportError as err: + from django.core.exceptions import ImproperlyConfigured + raise ImproperlyConfigured( + "Error loading mysql.connector module: {0}".format(err)) + +try: + version = mysql.connector.__version_info__[0:3] +except AttributeError: + from mysql.connector.version import VERSION + version = VERSION[0:3] + +if version < (1, 1): + from django.core.exceptions import ImproperlyConfigured + raise ImproperlyConfigured( + "MySQL Connector/Python v1.1.0 or newer " + "is required; you have %s" % mysql.connector.__version__) + +from django.db import utils +if django.VERSION < (1, 7): + from django.db.backends import util +else: + from django.db.backends import utils as backend_utils +from django.db.backends import (BaseDatabaseFeatures, BaseDatabaseOperations, + BaseDatabaseWrapper) +from django.db.backends.signals import connection_created +from django.utils import (six, timezone, dateparse) +from django.conf import settings + +from mysql.connector.django.client import DatabaseClient +from mysql.connector.django.creation import DatabaseCreation +from mysql.connector.django.introspection import DatabaseIntrospection +from mysql.connector.django.validation import DatabaseValidation +if django.VERSION >= (1, 7): + from mysql.connector.django.schema import DatabaseSchemaEditor + +try: + import pytz + HAVE_PYTZ = True +except ImportError: + HAVE_PYTZ = False + +DatabaseError = mysql.connector.DatabaseError +IntegrityError = mysql.connector.IntegrityError +NotSupportedError = mysql.connector.NotSupportedError + + +class DjangoMySQLConverter(MySQLConverter): + """Custom converter for Django""" + def _TIME_to_python(self, value, dsc=None): + """Return MySQL TIME data type as datetime.time() + + Returns datetime.time() + """ + return dateparse.parse_time(value.decode('utf-8')) + + def _DATETIME_to_python(self, value, dsc=None): + """Connector/Python always returns naive datetime.datetime + + Connector/Python always returns naive timestamps since MySQL has + no time zone support. Since Django needs non-naive, we need to add + the UTC time zone. + + Returns datetime.datetime() + """ + if not value: + return None + dt = MySQLConverter._DATETIME_to_python(self, value) + if dt is None: + return None + if settings.USE_TZ and timezone.is_naive(dt): + dt = dt.replace(tzinfo=timezone.utc) + return dt + + def _safetext_to_mysql(self, value): + return self._str_to_mysql(value) + + def _safebytes_to_mysql(self, value): + return self._bytes_to_mysql(value) + + +class CursorWrapper(object): + """Wrapper around MySQL Connector/Python's cursor class. + + The cursor class is defined by the options passed to MySQL + Connector/Python. If buffered option is True in those options, + MySQLCursorBuffered will be used. + """ + def __init__(self, cursor): + self.cursor = cursor + + def _execute_wrapper(self, method, query, args): + """Wrapper around execute() and executemany()""" + try: + return method(query, args) + except (mysql.connector.ProgrammingError) as err: + six.reraise(utils.ProgrammingError, + utils.ProgrammingError(err.msg), sys.exc_info()[2]) + except (mysql.connector.IntegrityError) as err: + six.reraise(utils.IntegrityError, + utils.IntegrityError(err.msg), sys.exc_info()[2]) + except mysql.connector.OperationalError as err: + six.reraise(utils.DatabaseError, + utils.DatabaseError(err.msg), sys.exc_info()[2]) + except mysql.connector.DatabaseError as err: + six.reraise(utils.DatabaseError, + utils.DatabaseError(err.msg), sys.exc_info()[2]) + + def execute(self, query, args=None): + """Executes the given operation + + This wrapper method around the execute()-method of the cursor is + mainly needed to re-raise using different exceptions. + """ + return self._execute_wrapper(self.cursor.execute, query, args) + + def executemany(self, query, args): + """Executes the given operation + + This wrapper method around the executemany()-method of the cursor is + mainly needed to re-raise using different exceptions. + """ + return self._execute_wrapper(self.cursor.executemany, query, args) + + def __getattr__(self, attr): + """Return attribute of wrapped cursor""" + return getattr(self.cursor, attr) + + def __iter__(self): + """Returns iterator over wrapped cursor""" + return iter(self.cursor) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.close() + + +class DatabaseFeatures(BaseDatabaseFeatures): + """Features specific to MySQL + + Microsecond precision is supported since MySQL 5.6.3 and turned on + by default. + """ + empty_fetchmany_value = [] + update_can_self_select = False + allows_group_by_pk = True + related_fields_match_type = True + allow_sliced_subqueries = False + has_bulk_insert = True + has_select_for_update = True + has_select_for_update_nowait = False + supports_forward_references = False + supports_long_model_names = False + supports_binary_field = six.PY2 + supports_microsecond_precision = False # toggled in __init__() + supports_regex_backreferencing = False + supports_date_lookup_using_string = False + can_introspect_binary_field = False + can_introspect_boolean_field = False + supports_timezones = False + requires_explicit_null_ordering_when_grouping = True + allows_auto_pk_0 = False + allows_primary_key_0 = False + uses_savepoints = True + atomic_transactions = False + supports_column_check_constraints = False + + def __init__(self, connection): + super(DatabaseFeatures, self).__init__(connection) + self.supports_microsecond_precision = self._microseconds_precision() + + def _microseconds_precision(self): + if self.connection.mysql_version >= (5, 6, 3): + return True + return False + + @cached_property + def _mysql_storage_engine(self): + """Get default storage engine of MySQL + + This method creates a table without ENGINE table option and inspects + which engine was used. + + Used by Django tests. + """ + tblname = 'INTROSPECT_TEST' + + droptable = 'DROP TABLE IF EXISTS {table}'.format(table=tblname) + with self.connection.cursor() as cursor: + cursor.execute(droptable) + cursor.execute('CREATE TABLE {table} (X INT)'.format(table=tblname)) + + if self.connection.mysql_version >= (5, 0, 0): + cursor.execute( + "SELECT ENGINE FROM INFORMATION_SCHEMA.TABLES " + "WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s", + (self.connection.settings_dict['NAME'], tblname)) + engine = cursor.fetchone()[0] + else: + # Very old MySQL servers.. + cursor.execute("SHOW TABLE STATUS WHERE Name='{table}'".format( + table=tblname)) + engine = cursor.fetchone()[1] + cursor.execute(droptable) + + self._cached_storage_engine = engine + return engine + + @cached_property + def can_introspect_foreign_keys(self): + """Confirm support for introspected foreign keys + + Only the InnoDB storage engine supports Foreigen Key (not taking + into account MySQL Cluster here). + """ + return self._mysql_storage_engine == 'InnoDB' + + @cached_property + def has_zoneinfo_database(self): + """Tests if the time zone definitions are installed + + MySQL accepts full time zones names (eg. Africa/Nairobi) but rejects + abbreviations (eg. EAT). When pytz isn't installed and the current + time zone is LocalTimezone (the only sensible value in this context), + the current time zone name will be an abbreviation. As a consequence, + MySQL cannot perform time zone conversions reliably. + """ + # Django 1.6 + if not HAVE_PYTZ: + return False + + with self.connection.cursor() as cursor: + cursor.execute("SELECT 1 FROM mysql.time_zone LIMIT 1") + return cursor.fetchall() != [] + + +class DatabaseOperations(BaseDatabaseOperations): + compiler_module = "mysql.connector.django.compiler" + + # MySQL stores positive fields as UNSIGNED ints. + if django.VERSION >= (1, 7): + integer_field_ranges = dict(BaseDatabaseOperations.integer_field_ranges, + PositiveSmallIntegerField=(0, 4294967295), + PositiveIntegerField=( + 0, 18446744073709551615),) + + def date_extract_sql(self, lookup_type, field_name): + # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html + if lookup_type == 'week_day': + # DAYOFWEEK() returns an integer, 1-7, Sunday=1. + # Note: WEEKDAY() returns 0-6, Monday=0. + return "DAYOFWEEK({0})".format(field_name) + else: + return "EXTRACT({0} FROM {1})".format( + lookup_type.upper(), field_name) + + def date_trunc_sql(self, lookup_type, field_name): + """Returns SQL simulating DATE_TRUNC + + This function uses MySQL functions DATE_FORMAT and CAST to + simulate DATE_TRUNC. + + The field_name is returned when lookup_type is not supported. + """ + fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] + format = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S') + format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') + try: + i = fields.index(lookup_type) + 1 + except ValueError: + # Wrong lookup type, just return the value from MySQL as-is + sql = field_name + else: + format_str = ''.join([f for f in format[:i]] + + [f for f in format_def[i:]]) + sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format( + field_name, format_str) + return sql + + def datetime_extract_sql(self, lookup_type, field_name, tzname): + # Django 1.6 + if settings.USE_TZ: + field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name) + params = [tzname] + else: + params = [] + + # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html + if lookup_type == 'week_day': + # DAYOFWEEK() returns an integer, 1-7, Sunday=1. + # Note: WEEKDAY() returns 0-6, Monday=0. + sql = "DAYOFWEEK({0})".format(field_name) + else: + sql = "EXTRACT({0} FROM {1})".format(lookup_type.upper(), + field_name) + return sql, params + + def datetime_trunc_sql(self, lookup_type, field_name, tzname): + # Django 1.6 + if settings.USE_TZ: + field_name = "CONVERT_TZ({0}, 'UTC', %s)".format(field_name) + params = [tzname] + else: + params = [] + fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] + format_ = ('%Y-', '%m', '-%d', ' %H:', '%i', ':%S') + format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') + try: + i = fields.index(lookup_type) + 1 + except ValueError: + sql = field_name + else: + format_str = ''.join([f for f in format_[:i]] + + [f for f in format_def[i:]]) + sql = "CAST(DATE_FORMAT({0}, '{1}') AS DATETIME)".format( + field_name, format_str) + return sql, params + + def date_interval_sql(self, sql, connector, timedelta): + """Returns SQL for calculating date/time intervals + """ + fmt = ( + "({sql} {connector} INTERVAL '{days} " + "0:0:{secs}:{msecs}' DAY_MICROSECOND)" + ) + return fmt.format( + sql=sql, + connector=connector, + days=timedelta.days, + secs=timedelta.seconds, + msecs=timedelta.microseconds + ) + + def drop_foreignkey_sql(self): + return "DROP FOREIGN KEY" + + def force_no_ordering(self): + """ + "ORDER BY NULL" prevents MySQL from implicitly ordering by grouped + columns. If no ordering would otherwise be applied, we don't want any + implicit sorting going on. + """ + return ["NULL"] + + def fulltext_search_sql(self, field_name): + return 'MATCH ({0}) AGAINST (%s IN BOOLEAN MODE)'.format(field_name) + + def last_executed_query(self, cursor, sql, params): + return cursor.statement + + def no_limit_value(self): + # 2**64 - 1, as recommended by the MySQL documentation + return 18446744073709551615 + + def quote_name(self, name): + if name.startswith("`") and name.endswith("`"): + return name # Quoting once is enough. + return "`{0}`".format(name) + + def random_function_sql(self): + return 'RAND()' + + def sql_flush(self, style, tables, sequences, allow_cascade=False): + if tables: + sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + for table in tables: + sql.append('{keyword} {table};'.format( + keyword=style.SQL_KEYWORD('TRUNCATE'), + table=style.SQL_FIELD(self.quote_name(table)))) + sql.append('SET FOREIGN_KEY_CHECKS = 1;') + sql.extend(self.sequence_reset_by_name_sql(style, sequences)) + return sql + else: + return [] + + def sequence_reset_by_name_sql(self, style, sequences): + # Truncate already resets the AUTO_INCREMENT field from + # MySQL version 5.0.13 onwards. Refs #16961. + res = [] + if self.connection.mysql_version < (5, 0, 13): + fmt = "{alter} {table} {{tablename}} {auto_inc} {field};".format( + alter=style.SQL_KEYWORD('ALTER'), + table=style.SQL_KEYWORD('TABLE'), + auto_inc=style.SQL_KEYWORD('AUTO_INCREMENT'), + field=style.SQL_FIELD('= 1') + ) + for sequence in sequences: + tablename = style.SQL_TABLE(self.quote_name(sequence['table'])) + res.append(fmt.format(tablename=tablename)) + return res + return res + + def validate_autopk_value(self, value): + # MySQLism: zero in AUTO_INCREMENT field does not work. Refs #17653. + if value == 0: + raise ValueError('The database backend does not accept 0 as a ' + 'value for AutoField.') + return value + + def value_to_db_datetime(self, value): + if value is None: + return None + # MySQL doesn't support tz-aware times + if timezone.is_aware(value): + if settings.USE_TZ: + value = value.astimezone(timezone.utc).replace(tzinfo=None) + else: + raise ValueError( + "MySQL backend does not support timezone-aware times." + ) + + return self.connection.converter.to_mysql(value) + + def value_to_db_time(self, value): + if value is None: + return None + + # MySQL doesn't support tz-aware times + if timezone.is_aware(value): + raise ValueError("MySQL backend does not support timezone-aware " + "times.") + + return self.connection.converter.to_mysql(value) + + def year_lookup_bounds(self, value): + # Again, no microseconds + first = '{0}-01-01 00:00:00' + second = '{0}-12-31 23:59:59.999999' + return [first.format(value), second.format(value)] + + def year_lookup_bounds_for_datetime_field(self, value): + # Django 1.6 + # Again, no microseconds + first, second = super(DatabaseOperations, + self).year_lookup_bounds_for_datetime_field(value) + if self.connection.mysql_version >= (5, 6, 4): + return [first.replace(microsecond=0), second] + else: + return [first.replace(microsecond=0), + second.replace(microsecond=0)] + + def max_name_length(self): + return 64 + + def bulk_insert_sql(self, fields, num_values): + items_sql = "({0})".format(", ".join(["%s"] * len(fields))) + return "VALUES " + ", ".join([items_sql] * num_values) + + def savepoint_create_sql(self, sid): + return "SAVEPOINT {0}".format(sid) + + def savepoint_commit_sql(self, sid): + return "RELEASE SAVEPOINT {0}".format(sid) + + def savepoint_rollback_sql(self, sid): + return "ROLLBACK TO SAVEPOINT {0}".format(sid) + + def combine_expression(self, connector, sub_expressions): + """ + MySQL requires special cases for ^ operators in query expressions + """ + if connector == '^': + return 'POW(%s)' % ','.join(sub_expressions) + return super(DatabaseOperations, self).combine_expression( + connector, sub_expressions) + + +class DatabaseWrapper(BaseDatabaseWrapper): + vendor = 'mysql' + operators = { + 'exact': '= %s', + 'iexact': 'LIKE %s', + 'contains': 'LIKE BINARY %s', + 'icontains': 'LIKE %s', + 'regex': 'REGEXP BINARY %s', + 'iregex': 'REGEXP %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE BINARY %s', + 'endswith': 'LIKE BINARY %s', + 'istartswith': 'LIKE %s', + 'iendswith': 'LIKE %s', + } + + Database = mysql.connector + + def __init__(self, *args, **kwargs): + super(DatabaseWrapper, self).__init__(*args, **kwargs) + + self.converter = DjangoMySQLConverter() + self.ops = DatabaseOperations(self) + self.features = DatabaseFeatures(self) + self.client = DatabaseClient(self) + self.creation = DatabaseCreation(self) + self.introspection = DatabaseIntrospection(self) + self.validation = DatabaseValidation(self) + + def _valid_connection(self): + if self.connection: + return self.connection.is_connected() + return False + + def get_connection_params(self): + # Django 1.6 + kwargs = { + 'charset': 'utf8', + 'use_unicode': True, + 'buffered': True, + } + + settings_dict = self.settings_dict + + if settings_dict['USER']: + kwargs['user'] = settings_dict['USER'] + if settings_dict['NAME']: + kwargs['database'] = settings_dict['NAME'] + if settings_dict['PASSWORD']: + kwargs['passwd'] = settings_dict['PASSWORD'] + if settings_dict['HOST'].startswith('/'): + kwargs['unix_socket'] = settings_dict['HOST'] + elif settings_dict['HOST']: + kwargs['host'] = settings_dict['HOST'] + if settings_dict['PORT']: + kwargs['port'] = int(settings_dict['PORT']) + + # Raise exceptions for database warnings if DEBUG is on + kwargs['raise_on_warnings'] = settings.DEBUG + + kwargs['client_flags'] = [ + # Need potentially affected rows on UPDATE + mysql.connector.constants.ClientFlag.FOUND_ROWS, + ] + try: + kwargs.update(settings_dict['OPTIONS']) + except KeyError: + # OPTIONS missing is OK + pass + + return kwargs + + def get_new_connection(self, conn_params): + # Django 1.6 + cnx = mysql.connector.connect(**conn_params) + cnx.set_converter_class(DjangoMySQLConverter) + + return cnx + + def init_connection_state(self): + # Django 1.6 + if self.mysql_version < (5, 5, 3): + # See sysvar_sql_auto_is_null in MySQL Reference manual + self.connection.cmd_query("SET SQL_AUTO_IS_NULL = 0") + + if 'AUTOCOMMIT' in self.settings_dict: + try: + # Django 1.6 + self.set_autocommit(self.settings_dict['AUTOCOMMIT']) + except AttributeError: + self._set_autocommit(self.settings_dict['AUTOCOMMIT']) + + def create_cursor(self): + # Django 1.6 + cursor = self.connection.cursor() + return CursorWrapper(cursor) + + def _connect(self): + """Setup the connection with MySQL""" + self.connection = self.get_new_connection(self.get_connection_params()) + connection_created.send(sender=self.__class__, connection=self) + self.init_connection_state() + + def _cursor(self): + """Return a CursorWrapper object + + Returns a CursorWrapper + """ + try: + # Django 1.6 + return super(DatabaseWrapper, self)._cursor() + except AttributeError: + if not self.connection: + self._connect() + return self.create_cursor() + + def get_server_version(self): + """Returns the MySQL server version of current connection + + Returns a tuple + """ + try: + # Django 1.6 + self.ensure_connection() + except AttributeError: + if not self.connection: + self._connect() + + return self.connection.get_server_version() + + def disable_constraint_checking(self): + """Disables foreign key checks + + Disables foreign key checks, primarily for use in adding rows with + forward references. Always returns True, + to indicate constraint checks need to be re-enabled. + + Returns True + """ + self.cursor().execute('SET @@session.foreign_key_checks = 0') + return True + + def enable_constraint_checking(self): + """Re-enable foreign key checks + + Re-enable foreign key checks after they have been disabled. + """ + # Override needs_rollback in case constraint_checks_disabled is + # nested inside transaction.atomic. + if django.VERSION >= (1, 6): + self.needs_rollback, needs_rollback = False, self.needs_rollback + try: + self.cursor().execute('SET @@session.foreign_key_checks = 1') + finally: + if django.VERSION >= (1, 6): + self.needs_rollback = needs_rollback + + def check_constraints(self, table_names=None): + """Check rows in tables for invalid foreign key references + + Checks each table name in `table_names` for rows with invalid foreign + key references. This method is intended to be used in conjunction with + `disable_constraint_checking()` and `enable_constraint_checking()`, to + determine if rows with invalid references were entered while + constraint checks were off. + + Raises an IntegrityError on the first invalid foreign key reference + encountered (if any) and provides detailed information about the + invalid reference in the error message. + + Backends can override this method if they can more directly apply + constraint checking (e.g. via "SET CONSTRAINTS ALL IMMEDIATE") + """ + ref_query = """ + SELECT REFERRING.`{0}`, REFERRING.`{1}` FROM `{2}` as REFERRING + LEFT JOIN `{3}` as REFERRED + ON (REFERRING.`{4}` = REFERRED.`{5}`) + WHERE REFERRING.`{6}` IS NOT NULL AND REFERRED.`{7}` IS NULL""" + cursor = self.cursor() + if table_names is None: + table_names = self.introspection.table_names(cursor) + for table_name in table_names: + primary_key_column_name = \ + self.introspection.get_primary_key_column(cursor, table_name) + if not primary_key_column_name: + continue + key_columns = self.introspection.get_key_columns(cursor, + table_name) + for column_name, referenced_table_name, referenced_column_name \ + in key_columns: + cursor.execute(ref_query.format(primary_key_column_name, + column_name, table_name, + referenced_table_name, + column_name, + referenced_column_name, + column_name, + referenced_column_name)) + for bad_row in cursor.fetchall(): + msg = ("The row in table '{0}' with primary key '{1}' has " + "an invalid foreign key: {2}.{3} contains a value " + "'{4}' that does not have a corresponding value in " + "{5}.{6}.".format(table_name, bad_row[0], + table_name, column_name, + bad_row[1], referenced_table_name, + referenced_column_name)) + raise utils.IntegrityError(msg) + + def _rollback(self): + try: + BaseDatabaseWrapper._rollback(self) + except NotSupportedError: + pass + + def _set_autocommit(self, autocommit): + # Django 1.6 + with self.wrap_database_errors: + self.connection.autocommit = autocommit + + def schema_editor(self, *args, **kwargs): + """Returns a new instance of this backend's SchemaEditor""" + # Django 1.7 + return DatabaseSchemaEditor(self, *args, **kwargs) + + def is_usable(self): + # Django 1.6 + return self.connection.is_connected() + + @cached_property + def mysql_version(self): + config = self.get_connection_params() + temp_conn = mysql.connector.connect(**config) + server_version = temp_conn.get_server_version() + temp_conn.close() + + return server_version diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/client.py mysql-connector-python-2.0.4/lib/mysql/connector/django/client.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/client.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/client.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,53 @@ +# MySQL Connector/Python - MySQL driver written in Python. + + +import os +import sys + +from django.db.backends import BaseDatabaseClient + + +class DatabaseClient(BaseDatabaseClient): + executable_name = 'mysql' + + def runshell(self): + settings_dict = self.connection.settings_dict + args = [self.executable_name] + + db = settings_dict['OPTIONS'].get('database', settings_dict['NAME']) + user = settings_dict['OPTIONS'].get('user', + settings_dict['USER']) + passwd = settings_dict['OPTIONS'].get('password', + settings_dict['PASSWORD']) + host = settings_dict['OPTIONS'].get('host', settings_dict['HOST']) + port = settings_dict['OPTIONS'].get('port', settings_dict['PORT']) + defaults_file = settings_dict['OPTIONS'].get('read_default_file') + + # --defaults-file should always be the first option + if defaults_file: + args.append("--defaults-file={0}".format(defaults_file)) + + # We force SQL_MODE to TRADITIONAL + args.append("--init-command=SET @@session.SQL_MODE=TRADITIONAL") + + if user: + args.append("--user={0}".format(user)) + if passwd: + args.append("--password={0}".format(passwd)) + + if host: + if '/' in host: + args.append("--socket={0}".format(host)) + else: + args.append("--host={0}".format(host)) + + if port: + args.append("--port={0}".format(port)) + + if db: + args.append("--database={0}".format(db)) + + if os.name == 'nt': + sys.exit(os.system(' '.join(args))) + else: + os.execvp(self.executable_name, args) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/compiler.py mysql-connector-python-2.0.4/lib/mysql/connector/django/compiler.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/compiler.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/compiler.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,52 @@ +# MySQL Connector/Python - MySQL driver written in Python. + + +import django +from django.db.models.sql import compiler +from django.utils.six.moves import zip_longest + + +class SQLCompiler(compiler.SQLCompiler): + def resolve_columns(self, row, fields=()): + values = [] + index_extra_select = len(self.query.extra_select) + bool_fields = ("BooleanField", "NullBooleanField") + for value, field in zip_longest(row[index_extra_select:], fields): + if (field and field.get_internal_type() in bool_fields and + value in (0, 1)): + value = bool(value) + values.append(value) + return row[:index_extra_select] + tuple(values) + + def as_subquery_condition(self, alias, columns, qn): + # Django 1.6 + qn2 = self.connection.ops.quote_name + sql, params = self.as_sql() + column_list = ', '.join( + ['%s.%s' % (qn(alias), qn2(column)) for column in columns]) + return '({0}) IN ({1})'.format(column_list, sql), params + + +class SQLInsertCompiler(compiler.SQLInsertCompiler, SQLCompiler): + pass + + +class SQLDeleteCompiler(compiler.SQLDeleteCompiler, SQLCompiler): + pass + + +class SQLUpdateCompiler(compiler.SQLUpdateCompiler, SQLCompiler): + pass + + +class SQLAggregateCompiler(compiler.SQLAggregateCompiler, SQLCompiler): + pass + + +class SQLDateCompiler(compiler.SQLDateCompiler, SQLCompiler): + pass + +if django.VERSION >= (1, 6): + class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, SQLCompiler): + # Django 1.6 + pass diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/creation.py mysql-connector-python-2.0.4/lib/mysql/connector/django/creation.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/creation.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/creation.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,137 @@ +# MySQL Connector/Python - MySQL driver written in Python. + +import django +from django.db import models +from django.db.backends.creation import BaseDatabaseCreation + +if django.VERSION < (1, 7): + from django.db.backends.util import truncate_name +else: + from django.db.backends.utils import truncate_name + +class DatabaseCreation(BaseDatabaseCreation): + """Maps Django Field object with MySQL data types + """ + def __init__(self, connection): + super(DatabaseCreation, self).__init__(connection) + + self.data_types = { + 'AutoField': 'integer AUTO_INCREMENT', + 'BinaryField': 'longblob', + 'BooleanField': 'bool', + 'CharField': 'varchar(%(max_length)s)', + 'CommaSeparatedIntegerField': 'varchar(%(max_length)s)', + 'DateField': 'date', + 'DateTimeField': 'datetime', # ms support set later + 'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)', + 'FileField': 'varchar(%(max_length)s)', + 'FilePathField': 'varchar(%(max_length)s)', + 'FloatField': 'double precision', + 'IntegerField': 'integer', + 'BigIntegerField': 'bigint', + 'IPAddressField': 'char(15)', + + 'GenericIPAddressField': 'char(39)', + 'NullBooleanField': 'bool', + 'OneToOneField': 'integer', + 'PositiveIntegerField': 'integer UNSIGNED', + 'PositiveSmallIntegerField': 'smallint UNSIGNED', + 'SlugField': 'varchar(%(max_length)s)', + 'SmallIntegerField': 'smallint', + 'TextField': 'longtext', + 'TimeField': 'time', # ms support set later + } + + # Support for microseconds + if self.connection.mysql_version >= (5, 6, 4): + self.data_types.update({ + 'DateTimeField': 'datetime(6)', + 'TimeField': 'time(6)', + }) + + def sql_table_creation_suffix(self): + suffix = [] + if django.VERSION < (1, 7): + if self.connection.settings_dict['TEST_CHARSET']: + suffix.append('CHARACTER SET {0}'.format( + self.connection.settings_dict['TEST_CHARSET'])) + if self.connection.settings_dict['TEST_COLLATION']: + suffix.append('COLLATE {0}'.format( + self.connection.settings_dict['TEST_COLLATION'])) + + else: + test_settings = self.connection.settings_dict['TEST'] + if test_settings['CHARSET']: + suffix.append('CHARACTER SET %s' % test_settings['CHARSET']) + if test_settings['COLLATION']: + suffix.append('COLLATE %s' % test_settings['COLLATION']) + + return ' '.join(suffix) + + if django.VERSION < (1, 6): + def sql_for_inline_foreign_key_references(self, field, known_models, + style): + "All inline references are pending under MySQL" + return [], True + else: + def sql_for_inline_foreign_key_references(self, model, field, + known_models, style): + "All inline references are pending under MySQL" + return [], True + + def sql_for_inline_many_to_many_references(self, model, field, style): + opts = model._meta + qn = self.connection.ops.quote_name + + columndef = ' {column} {type} {options},' + table_output = [ + columndef.format( + column=style.SQL_FIELD(qn(field.m2m_column_name())), + type=style.SQL_COLTYPE(models.ForeignKey(model).db_type( + connection=self.connection)), + options=style.SQL_KEYWORD('NOT NULL') + ), + columndef.format( + column=style.SQL_FIELD(qn(field.m2m_reverse_name())), + type=style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type( + connection=self.connection)), + options=style.SQL_KEYWORD('NOT NULL') + ), + ] + + deferred = [ + (field.m2m_db_table(), field.m2m_column_name(), opts.db_table, + opts.pk.column), + (field.m2m_db_table(), field.m2m_reverse_name(), + field.rel.to._meta.db_table, field.rel.to._meta.pk.column) + ] + return table_output, deferred + + def sql_destroy_indexes_for_fields(self, model, fields, style): + # Django 1.6 + if len(fields) == 1 and fields[0].db_tablespace: + tablespace_sql = self.connection.ops.tablespace_sql( + fields[0].db_tablespace) + elif model._meta.db_tablespace: + tablespace_sql = self.connection.ops.tablespace_sql( + model._meta.db_tablespace) + else: + tablespace_sql = "" + if tablespace_sql: + tablespace_sql = " " + tablespace_sql + + field_names = [] + qn = self.connection.ops.quote_name + for f in fields: + field_names.append(style.SQL_FIELD(qn(f.column))) + + index_name = "{0}_{1}".format(model._meta.db_table, + self._digest([f.name for f in fields])) + + return [ + style.SQL_KEYWORD("DROP INDEX") + " " + + style.SQL_TABLE(qn(truncate_name(index_name, + self.connection.ops.max_name_length()))) + " " + + style.SQL_KEYWORD("ON") + " " + + style.SQL_TABLE(qn(model._meta.db_table)) + ";", + ] diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/introspection.py mysql-connector-python-2.0.4/lib/mysql/connector/django/introspection.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/introspection.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/introspection.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,223 @@ +# MySQL Connector/Python - MySQL driver written in Python. + + +import re +import django +from django.db.backends import BaseDatabaseIntrospection + +if django.VERSION >= (1, 6): + from django.db.backends import FieldInfo + from django.utils.encoding import force_text + if django.VERSION >= (1, 7): + from django.utils.datastructures import OrderedSet + +from mysql.connector.constants import FieldType + +foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) " + r"REFERENCES `([^`]*)` \(`([^`]*)`\)") + + +class DatabaseIntrospection(BaseDatabaseIntrospection): + data_types_reverse = { + FieldType.BLOB: 'TextField', + FieldType.DECIMAL: 'DecimalField', + FieldType.NEWDECIMAL: 'DecimalField', + FieldType.DATE: 'DateField', + FieldType.DATETIME: 'DateTimeField', + FieldType.DOUBLE: 'FloatField', + FieldType.FLOAT: 'FloatField', + FieldType.INT24: 'IntegerField', + FieldType.LONG: 'IntegerField', + FieldType.LONGLONG: 'BigIntegerField', + FieldType.SHORT: 'IntegerField', + FieldType.STRING: 'CharField', + FieldType.TIME: 'TimeField', + FieldType.TIMESTAMP: 'DateTimeField', + FieldType.TINY: 'IntegerField', + FieldType.TINY_BLOB: 'TextField', + FieldType.MEDIUM_BLOB: 'TextField', + FieldType.LONG_BLOB: 'TextField', + FieldType.VAR_STRING: 'CharField', + } + + def get_table_list(self, cursor): + "Returns a list of table names in the current database." + cursor.execute("SHOW TABLES") + return [row[0] for row in cursor.fetchall()] + + def get_table_description(self, cursor, table_name): + """ + Returns a description of the table, with the DB-API cursor.description + interface." + """ + # varchar length returned by cursor.description is an internal length, + # not visible length (#5725), use information_schema database to fix + # this + cursor.execute( + "SELECT column_name, character_maximum_length " + "FROM INFORMATION_SCHEMA.COLUMNS " + "WHERE table_name = %s AND table_schema = DATABASE() " + "AND character_maximum_length IS NOT NULL", [table_name]) + length_map = dict(cursor.fetchall()) + + # Also getting precision and scale from information_schema (see #5014) + cursor.execute( + "SELECT column_name, numeric_precision, numeric_scale FROM " + "INFORMATION_SCHEMA.COLUMNS WHERE table_name = %s AND " + "table_schema = DATABASE() AND data_type='decimal'", [table_name]) + numeric_map = dict((line[0], tuple([int(n) for n in line[1:]])) + for line in cursor.fetchall()) + + cursor.execute("SELECT * FROM {0} LIMIT 1".format( + self.connection.ops.quote_name(table_name))) + if django.VERSION >= (1, 6): + return [FieldInfo(*((force_text(line[0]),) + + line[1:3] + + (length_map.get(line[0], line[3]),) + + numeric_map.get(line[0], line[4:6]) + + (line[6],))) + for line in cursor.description] + else: + return [line[:3] + (length_map.get(line[0], line[3]),) + line[4:] + for line in cursor.description] + + def _name_to_index(self, cursor, table_name): + """ + Returns a dictionary of {field_name: field_index} for the given table. + Indexes are 0-based. + """ + return dict((d[0], i) for i, d in enumerate( + self.get_table_description(cursor, table_name))) + + def get_relations(self, cursor, table_name): + """ + Returns a dictionary of {field_index: (field_index_other_table, + other_table)} + representing all relationships to the given table. Indexes are 0-based. + """ + my_field_dict = self._name_to_index(cursor, table_name) + constraints = self.get_key_columns(cursor, table_name) + relations = {} + for my_fieldname, other_table, other_field in constraints: + other_field_index = self._name_to_index(cursor, + other_table)[other_field] + my_field_index = my_field_dict[my_fieldname] + relations[my_field_index] = (other_field_index, other_table) + return relations + + def get_key_columns(self, cursor, table_name): + """ + Returns a list of (column_name, referenced_table_name, + referenced_column_name) for all key columns in given table. + """ + key_columns = [] + cursor.execute( + "SELECT column_name, referenced_table_name, referenced_column_name " + "FROM information_schema.key_column_usage " + "WHERE table_name = %s " + "AND table_schema = DATABASE() " + "AND referenced_table_name IS NOT NULL " + "AND referenced_column_name IS NOT NULL", [table_name]) + key_columns.extend(cursor.fetchall()) + return key_columns + + def get_indexes(self, cursor, table_name): + cursor.execute("SHOW INDEX FROM {0}" + "".format(self.connection.ops.quote_name(table_name))) + # Do a two-pass search for indexes: on first pass check which indexes + # are multicolumn, on second pass check which single-column indexes + # are present. + rows = list(cursor.fetchall()) + multicol_indexes = set() + for row in rows: + if row[3] > 1: + multicol_indexes.add(row[2]) + indexes = {} + for row in rows: + if row[2] in multicol_indexes: + continue + if row[4] not in indexes: + indexes[row[4]] = {'primary_key': False, 'unique': False} + # It's possible to have the unique and PK constraints in + # separate indexes. + if row[2] == 'PRIMARY': + indexes[row[4]]['primary_key'] = True + if not row[1]: + indexes[row[4]]['unique'] = True + return indexes + + def get_primary_key_column(self, cursor, table_name): + """ + Returns the name of the primary key column for the given table + """ + # Django 1.6 + for column in self.get_indexes(cursor, table_name).items(): + if column[1]['primary_key']: + return column[0] + return None + + def get_constraints(self, cursor, table_name): + """ + Retrieves any constraints or keys (unique, pk, fk, check, index) across + one or more columns. + """ + # Django 1.7 + constraints = {} + # Get the actual constraint names and columns + name_query = ( + "SELECT kc.`constraint_name`, kc.`column_name`, " + "kc.`referenced_table_name`, kc.`referenced_column_name` " + "FROM information_schema.key_column_usage AS kc " + "WHERE " + "kc.table_schema = %s AND " + "kc.table_name = %s" + ) + cursor.execute(name_query, [self.connection.settings_dict['NAME'], + table_name]) + for constraint, column, ref_table, ref_column in cursor.fetchall(): + if constraint not in constraints: + constraints[constraint] = { + 'columns': OrderedSet(), + 'primary_key': False, + 'unique': False, + 'index': False, + 'check': False, + 'foreign_key': (ref_table, ref_column) if ref_column else None, + } + constraints[constraint]['columns'].add(column) + # Now get the constraint types + type_query = """ + SELECT c.constraint_name, c.constraint_type + FROM information_schema.table_constraints AS c + WHERE + c.table_schema = %s AND + c.table_name = %s + """ + cursor.execute(type_query, [self.connection.settings_dict['NAME'], + table_name]) + for constraint, kind in cursor.fetchall(): + if kind.lower() == "primary key": + constraints[constraint]['primary_key'] = True + constraints[constraint]['unique'] = True + elif kind.lower() == "unique": + constraints[constraint]['unique'] = True + # Now add in the indexes + cursor.execute("SHOW INDEX FROM %s" % self.connection.ops.quote_name( + table_name)) + for table, non_unique, index, colseq, column in [x[:5] for x in + cursor.fetchall()]: + if index not in constraints: + constraints[index] = { + 'columns': OrderedSet(), + 'primary_key': False, + 'unique': False, + 'index': True, + 'check': False, + 'foreign_key': None, + } + constraints[index]['index'] = True + constraints[index]['columns'].add(column) + # Convert the sorted sets to lists + for constraint in constraints.values(): + constraint['columns'] = list(constraint['columns']) + return constraints diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/schema.py mysql-connector-python-2.0.4/lib/mysql/connector/django/schema.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/schema.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/schema.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,57 @@ +# MySQL Connector/Python - MySQL driver written in Python. + +# New file added for Django 1.7 + +from django.db.backends.schema import BaseDatabaseSchemaEditor +from django.db.models import NOT_PROVIDED + + +class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): + + sql_rename_table = "RENAME TABLE %(old_table)s TO %(new_table)s" + + sql_alter_column_null = "MODIFY %(column)s %(type)s NULL" + sql_alter_column_not_null = "MODIFY %(column)s %(type)s NOT NULL" + sql_alter_column_type = "MODIFY %(column)s %(type)s" + sql_rename_column = "ALTER TABLE %(table)s CHANGE %(old_column)s " \ + "%(new_column)s %(type)s" + + sql_delete_unique = "ALTER TABLE %(table)s DROP INDEX %(name)s" + + sql_create_fk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s FOREIGN " \ + "KEY (%(column)s) REFERENCES %(to_table)s (%(to_column)s)" + sql_delete_fk = "ALTER TABLE %(table)s DROP FOREIGN KEY %(name)s" + + sql_delete_index = "DROP INDEX %(name)s ON %(table)s" + + sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" + + alter_string_set_null = 'MODIFY %(column)s %(type)s NULL;' + alter_string_drop_null = 'MODIFY %(column)s %(type)s NOT NULL;' + + sql_create_pk = "ALTER TABLE %(table)s ADD CONSTRAINT %(name)s " \ + "PRIMARY KEY (%(columns)s)" + sql_delete_pk = "ALTER TABLE %(table)s DROP PRIMARY KEY" + + def quote_value(self, value): + # Inner import to allow module to fail to load gracefully + from mysql.connector.conversion import MySQLConverter + return MySQLConverter.quote(MySQLConverter.escape(value)) + + def skip_default(self, field): + """ + MySQL doesn't accept default values for longtext and longblob + and implicitly treats these columns as nullable. + """ + return field.db_type(self.connection) in ('longtext', 'longblob') + + def add_field(self, model, field): + super(DatabaseSchemaEditor, self).add_field(model, field) + + # Simulate the effect of a one-off default. + if self.skip_default(field) and field.default not in (None, NOT_PROVIDED): + effective_default = self.effective_default(field) + self.execute('UPDATE %(table)s SET %(column)s = %%s' % { + 'table': self.quote_name(model._meta.db_table), + 'column': self.quote_name(field.column), + }, [effective_default]) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/django/validation.py mysql-connector-python-2.0.4/lib/mysql/connector/django/validation.py --- mysql-connector-python-1.1.6/lib/mysql/connector/django/validation.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/django/validation.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,58 @@ +# MySQL Connector/Python - MySQL driver written in Python. + +import django +from django.db.backends import BaseDatabaseValidation + +if django.VERSION < (1, 7): + from django.db import models +else: + from django.core import checks + from django.db import connection + + +class DatabaseValidation(BaseDatabaseValidation): + if django.VERSION < (1, 7): + def validate_field(self, errors, opts, f): + """ + MySQL has the following field length restriction: + No character (varchar) fields can have a length exceeding 255 + characters if they have a unique index on them. + """ + varchar_fields = (models.CharField, + models.CommaSeparatedIntegerField, + models.SlugField) + if isinstance(f, varchar_fields) and f.max_length > 255 and f.unique: + msg = ('"%(name)s": %(cls)s cannot have a "max_length" greater ' + 'than 255 when using "unique=True".') + errors.add(opts, msg % {'name': f.name, + 'cls': f.__class__.__name__}) + + else: + def check_field(self, field, **kwargs): + """ + MySQL has the following field length restriction: + No character (varchar) fields can have a length exceeding 255 + characters if they have a unique index on them. + """ + # Django 1.7 + errors = super(DatabaseValidation, self).check_field(field, + **kwargs) + + # Ignore any related fields. + if getattr(field, 'rel', None) is None: + field_type = field.db_type(connection) + + if (field_type.startswith('varchar') # Look for CharFields... + and field.unique # ... that are unique + and (field.max_length is None or + int(field.max_length) > 255)): + errors.append( + checks.Error( + ('MySQL does not allow unique CharFields to have a ' + 'max_length > 255.'), + hint=None, + obj=field, + id='mysql.E001', + ) + ) + return errors diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/errorcode.py mysql-connector-python-2.0.4/lib/mysql/connector/errorcode.py --- mysql-connector-python-1.1.6/lib/mysql/connector/errorcode.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/errorcode.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,1055 @@ +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 = '2014-10-10' +_MYSQL_VERSION = (5, 7, 5) + +"""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 +ER_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_UNUSED5 = 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_UNUSED6 = 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_MTS_EVENT_BIGGER_PENDING_JOBS_SIZE_MAX = 1864 +ER_INNODB_NO_FT_USES_PARSER = 1865 +ER_BINLOG_LOGICAL_CORRUPTION = 1866 +ER_WARN_PURGE_LOG_IN_USE = 1867 +ER_WARN_PURGE_LOG_IS_ACTIVE = 1868 +ER_AUTO_INCREMENT_CONFLICT = 1869 +WARN_ON_BLOCKHOLE_IN_RBR = 1870 +ER_SLAVE_MI_INIT_REPOSITORY = 1871 +ER_SLAVE_RLI_INIT_REPOSITORY = 1872 +ER_ACCESS_DENIED_CHANGE_USER_ERROR = 1873 +ER_INNODB_READ_ONLY = 1874 +ER_STOP_SLAVE_SQL_THREAD_TIMEOUT = 1875 +ER_STOP_SLAVE_IO_THREAD_TIMEOUT = 1876 +ER_TABLE_CORRUPT = 1877 +ER_TEMP_FILE_WRITE_FAILURE = 1878 +ER_INNODB_FT_AUX_NOT_HEX_ID = 1879 +ER_OLD_TEMPORALS_UPGRADED = 1880 +ER_INNODB_FORCED_RECOVERY = 1881 +ER_AES_INVALID_IV = 1882 +ER_PLUGIN_CANNOT_BE_UNINSTALLED = 1883 +ER_GTID_UNSAFE_BINLOG_SPLITTABLE_STATEMENT_AND_GTID_GROUP = 1884 +ER_FILE_CORRUPT = 1885 +ER_ERROR_ON_MASTER = 1886 +ER_INCONSISTENT_ERROR = 1887 +ER_STORAGE_ENGINE_NOT_LOADED = 1888 +ER_GET_STACKED_DA_WITHOUT_ACTIVE_HANDLER = 1889 +ER_WARN_LEGACY_SYNTAX_CONVERTED = 1890 +ER_BINLOG_UNSAFE_FULLTEXT_PLUGIN = 1891 +ER_CANNOT_DISCARD_TEMPORARY_TABLE = 1892 +ER_FK_DEPTH_EXCEEDED = 1893 +ER_COL_COUNT_DOESNT_MATCH_PLEASE_UPDATE_V2 = 1894 +ER_WARN_TRIGGER_DOESNT_HAVE_CREATED = 1895 +ER_REFERENCED_TRG_DOES_NOT_EXIST = 1896 +ER_EXPLAIN_NOT_SUPPORTED = 1897 +ER_INVALID_FIELD_SIZE = 1898 +ER_MISSING_HA_CREATE_OPTION = 1899 +ER_ENGINE_OUT_OF_MEMORY = 1900 +ER_PASSWORD_EXPIRE_ANONYMOUS_USER = 1901 +ER_SLAVE_SQL_THREAD_MUST_STOP = 1902 +ER_NO_FT_MATERIALIZED_SUBQUERY = 1903 +ER_INNODB_UNDO_LOG_FULL = 1904 +ER_INVALID_ARGUMENT_FOR_LOGARITHM = 1905 +ER_SLAVE_IO_THREAD_MUST_STOP = 1906 +ER_WARN_OPEN_TEMP_TABLES_MUST_BE_ZERO = 1907 +ER_WARN_ONLY_MASTER_LOG_FILE_NO_POS = 1908 +ER_QUERY_TIMEOUT = 1909 +ER_NON_RO_SELECT_DISABLE_TIMER = 1910 +ER_DUP_LIST_ENTRY = 1911 +ER_SQL_MODE_NO_EFFECT = 1912 +ER_AGGREGATE_ORDER_FOR_UNION = 1913 +ER_AGGREGATE_ORDER_NON_AGG_QUERY = 1914 +ER_SLAVE_WORKER_STOPPED_PREVIOUS_THD_ERROR = 1915 +ER_DONT_SUPPORT_SLAVE_PRESERVE_COMMIT_ORDER = 1916 +ER_SERVER_OFFLINE_MODE = 1917 +ER_GIS_DIFFERENT_SRIDS = 1918 +ER_GIS_UNSUPPORTED_ARGUMENT = 1919 +ER_GIS_UNKNOWN_ERROR = 1920 +ER_GIS_UNKNOWN_EXCEPTION = 1921 +ER_GIS_INVALID_DATA = 1922 +ER_BOOST_GEOMETRY_EMPTY_INPUT_EXCEPTION = 1923 +ER_BOOST_GEOMETRY_CENTROID_EXCEPTION = 1924 +ER_BOOST_GEOMETRY_OVERLAY_INVALID_INPUT_EXCEPTION = 1925 +ER_BOOST_GEOMETRY_TURN_INFO_EXCEPTION = 1926 +ER_BOOST_GEOMETRY_SELF_INTERSECTION_POINT_EXCEPTION = 1927 +ER_BOOST_GEOMETRY_UNKNOWN_EXCEPTION = 1928 +ER_STD_BAD_ALLOC_ERROR = 1929 +ER_STD_DOMAIN_ERROR = 1930 +ER_STD_LENGTH_ERROR = 1931 +ER_STD_INVALID_ARGUMENT = 1932 +ER_STD_OUT_OF_RANGE_ERROR = 1933 +ER_STD_OVERFLOW_ERROR = 1934 +ER_STD_RANGE_ERROR = 1935 +ER_STD_UNDERFLOW_ERROR = 1936 +ER_STD_LOGIC_ERROR = 1937 +ER_STD_RUNTIME_ERROR = 1938 +ER_STD_UNKNOWN_EXCEPTION = 1939 +ER_GIS_DATA_WRONG_ENDIANESS = 1940 +ER_CHANGE_MASTER_PASSWORD_LENGTH = 1941 +ER_USER_LOCK_WRONG_NAME = 1942 +ER_USER_LOCK_DEADLOCK = 1943 +ER_REPLACE_INACCESSIBLE_ROWS = 1944 +ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_GIS = 1945 +ER_ILLEGAL_USER_VAR = 1946 +ER_GTID_MODE_OFF = 1947 +ER_UNSUPPORTED_BY_REPLICATION_THREAD = 1948 +ER_INCORRECT_TYPE = 1949 +ER_FIELD_IN_ORDER_NOT_SELECT = 1950 +ER_AGGREGATE_IN_ORDER_NOT_SELECT = 1951 +ER_INVALID_RPL_WILD_TABLE_FILTER_PATTERN = 1952 +ER_NET_OK_PACKET_TOO_LARGE = 1953 +ER_INVALID_JSON_DATA = 1954 +ER_INVALID_GEOJSON_MISSING_MEMBER = 1955 +ER_INVALID_GEOJSON_WRONG_TYPE = 1956 +ER_INVALID_GEOJSON_UNSPECIFIED = 1957 +ER_DIMENSION_UNSUPPORTED = 1958 +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_UNUSED_1 = 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 -Nru mysql-connector-python-1.1.6/lib/mysql/connector/errors.py mysql-connector-python-2.0.4/lib/mysql/connector/errors.py --- mysql-connector-python-1.1.6/lib/mysql/connector/errors.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/errors.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,296 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +"""Python exceptions +""" + +from . import utils +from .locales import get_client_error +from .catch23 import PY2 + +# _CUSTOM_ERROR_EXCEPTIONS holds custom exceptions and is ued by the +# function custom_error_exception. _ERROR_EXCEPTIONS (at bottom of module) +# is similar, but hardcoded exceptions. +_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({}) + + Returns a dictionary. + """ + global _CUSTOM_ERROR_EXCEPTIONS # pylint: disable=W0603 + + 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 + + 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. + + 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 + + try: + return _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 + + Returns an exception object based on the MySQL error in the given + packet. + + Returns an Error-Object. + """ + errno = errmsg = None + + if packet[4] != 255: + raise ValueError("Packet is not an error packet") + + sqlstate = None + try: + packet = packet[5:] + (packet, errno) = utils.read_int(packet, 2) + if packet[0] != 35: + # Error without SQLState + if isinstance(packet, (bytes, bytearray)): + errmsg = packet.decode('utf8') + else: + errmsg = packet + else: + (packet, sqlstate) = utils.read_bytes(packet[1:], 5) + sqlstate = sqlstate.decode('utf8') + errmsg = packet.decode('utf8') + except Exception as err: # pylint: disable=W0703 + return InterfaceError("Failed getting Error information (%r)" % err) + else: + return get_mysql_exception(errno, errmsg, sqlstate) + + +class Error(Exception): + """Exception that is base class for all other error exceptions""" + def __init__(self, msg=None, errno=None, values=None, sqlstate=None): + super(Error, self).__init__() + self.msg = msg + self._full_msg = self.msg + self.errno = errno or -1 + self.sqlstate = sqlstate + + if not self.msg and (2000 <= self.errno < 3000): + self.msg = get_client_error(self.errno) + if values is not None: + try: + self.msg = self.msg % values + except TypeError as err: + self.msg = "{0} (Warning: {1})".format(self.msg, str(err)) + elif not self.msg: + self._full_msg = self.msg = 'Unknown error' + + if self.msg and self.errno != -1: + fields = { + 'errno': self.errno, + 'msg': self.msg.encode('utf8') if PY2 else self.msg + } + if self.sqlstate: + fmt = '{errno} ({state}): {msg}' + fields['state'] = self.sqlstate + else: + fmt = '{errno}: {msg}' + self._full_msg = fmt.format(**fields) + + self.args = (self.errno, self._full_msg, self.sqlstate) + + def __str__(self): + return self._full_msg + + +class Warning(Exception): # pylint: disable=W0622 + """Exception for important warnings""" + pass + + +class InterfaceError(Error): + """Exception for errors related to the interface""" + pass + + +class DatabaseError(Error): + """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 + + +class PoolError(Error): + """Exception for errors relating to connection pooling""" + pass + + +class MySQLFabricError(Error): + """Exception for errors relating to MySQL Fabric""" + +_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 +} + +_ERROR_EXCEPTIONS = { + 1243: ProgrammingError, + 1210: ProgrammingError, +} diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/fabric/balancing.py mysql-connector-python-2.0.4/lib/mysql/connector/fabric/balancing.py --- mysql-connector-python-1.1.6/lib/mysql/connector/fabric/balancing.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/fabric/balancing.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,159 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 + +"""Implementing load balancing""" + + +import decimal + + +def _calc_ratio(part, whole): + """Calculate ratio + + Returns int + """ + return int((part/whole*100).quantize( + decimal.Decimal('1'), rounding=decimal.ROUND_HALF_DOWN)) + + +class BaseScheduling(object): + + """Base class for all scheduling classes dealing with load balancing""" + + def __init__(self): + """Initialize""" + self._members = [] + self._ratios = [] + + def set_members(self, *args): + """Set members and ratios + + This methods sets the members using the arguments passed. Each + argument must be a sequence where the second item is the weight. + The first element is an identifier. For example: + + ('server1', 0.6), ('server2', 0.8) + + Setting members means that the load will be reset. If the members + are the same as previously set, nothing will be reset or set. + + If no arguments were given the members will be set to an empty + list. + + Raises ValueError when weight can't be converted to a Decimal. + """ + raise NotImplementedError + + def get_next(self): + """Returns the next member""" + raise NotImplementedError + + @property + def members(self): + """Returns the members of this loadbalancer""" + return self._members + + @property + def ratios(self): + """Returns the ratios for all members""" + return self._ratios + + +class WeightedRoundRobin(BaseScheduling): + + """Class for doing Weighted Round Robin balancing""" + + def __init__(self, *args): + """Initializing""" + super(WeightedRoundRobin, self).__init__() + self._load = [] + self._next_member = 0 + self._nr_members = 0 + + if args: + self.set_members(*args) + + @property + def load(self): + """Returns the current load""" + return self._load + + def set_members(self, *args): + if not args: + # Reset members if nothing was given + self._members = [] + return + new_members = [] + for member in args: + member = list(member) + try: + member[1] = decimal.Decimal(str(member[1])) + except decimal.InvalidOperation: + raise ValueError("Member '{member}' is invalid".format( + member=member)) + new_members.append(tuple(member)) + + new_members.sort(key=lambda x: x[1], reverse=True) + if self._members == new_members: + return + self._members = new_members + self._nr_members = len(new_members) + + min_weight = min(i[1] for i in self._members) + self._ratios = [] + for _, weight in self._members: + self._ratios.append(int(weight/min_weight * 100)) + self.reset() + + def reset(self): + """Reset the load""" + self._next_member = 0 + self._load = [0] * self._nr_members + + def get_next(self): + """Returns the next member""" + if self._ratios == self._load: + self.reset() + + # Figure out the member to return + current = self._next_member + + while self._load[current] == self._ratios[current]: + current = (current + 1) % self._nr_members + + # Update the load and set next member + self._load[current] += 1 + self._next_member = (current + 1) % self._nr_members + + # Return current + return self._members[current] + + def __repr__(self): + return "{class_}(load={load}, ratios={ratios})".format( + class_=self.__class__, + load=self.load, + ratios=self.ratios + ) + + def __eq__(self, other): + return self._members == other.members diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/fabric/caching.py mysql-connector-python-2.0.4/lib/mysql/connector/fabric/caching.py --- mysql-connector-python-1.1.6/lib/mysql/connector/fabric/caching.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/fabric/caching.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,281 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2015, 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 + +"""Implementing caching mechanisms for MySQL Fabric""" + + +import bisect +from datetime import datetime, timedelta +from hashlib import sha1 +import logging +import threading + +from . import FabricShard + +_LOGGER = logging.getLogger('myconnpy-fabric') +_CACHE_TTL = 1 * 60 # 1 minute + + +def insort_right_rev(alist, new_element, low=0, high=None): + """Similar to bisect.insort_right but for reverse sorted lists + + This code is similar to the Python code found in Lib/bisect.py. + We simply change the comparison from 'less than' to 'greater than'. + """ + + if low < 0: + raise ValueError('low must be non-negative') + if high is None: + high = len(alist) + while low < high: + middle = (low + high) // 2 + if new_element > alist[middle]: + high = middle + else: + low = middle + 1 + alist.insert(low, new_element) + + +class CacheEntry(object): + + """Base class for MySQL Fabric cache entries""" + + def __init__(self, version=None, fabric_uuid=None, ttl=_CACHE_TTL): + self.version = version + self.fabric_uuid = fabric_uuid + self.last_updated = datetime.utcnow() + self._ttl = ttl + + @classmethod + def hash_index(cls, part1, part2=None): + """Create hash for indexing""" + raise NotImplementedError + + @property + def invalid(self): + """Returns True if entry is not valid any longer + + This property returns True when the entry is not valid any longer. + The entry is valid when now > (last updated + ttl), where ttl is + in seconds. + """ + if not self.last_updated: + return False + atime = self.last_updated + timedelta(seconds=self._ttl) + return datetime.utcnow() > atime + + def reset_ttl(self): + """Reset the Time to Live""" + self.last_updated = datetime.utcnow() + + def invalidate(self): + """Invalidates the cache entry""" + self.last_updated = None + + +class CacheShardTable(CacheEntry): + + """Cache entry for a Fabric sharded table""" + + def __init__(self, shard, version=None, fabric_uuid=None): + if not isinstance(shard, FabricShard): + raise ValueError("shard argument must be a FabricShard instance") + super(CacheShardTable, self).__init__(version=version, + fabric_uuid=fabric_uuid) + self.partitioning = {} + self._shard = shard + self.keys = [] + self.keys_reversed = [] + + if shard.key and shard.group: + self.add_partition(shard.key, shard.group) + + def __getattr__(self, attr): + return getattr(self._shard, attr) + + def add_partition(self, key, group): + """Add sharding information for a group""" + if self.shard_type == 'RANGE': + key = int(key) + elif self.shard_type == 'RANGE_DATETIME': + try: + if ':' in key: + key = datetime.strptime(key, "%Y-%m-%d %H:%M:%S") + else: + key = datetime.strptime(key, "%Y-%m-%d").date() + except: + raise ValueError( + "RANGE_DATETIME key could not be parsed, was: {0}".format( + key + )) + elif self.shard_type == 'RANGE_STRING': + pass + elif self.shard_type == "HASH": + pass + else: + raise ValueError("Unsupported sharding type {0}".format( + self.shard_type + )) + self.partitioning[key] = { + 'group': group, + } + self.reset_ttl() + bisect.insort_right(self.keys, key) + insort_right_rev(self.keys_reversed, key) + + @classmethod + def hash_index(cls, part1, part2=None): + """Create hash for indexing""" + return sha1(part1.encode('utf-8') + part2.encode('utf-8')).hexdigest() + + def __repr__(self): + return "{class_}({database}.{table}.{column})".format( + class_=self.__class__, + database=self.database, + table=self.table, + column=self.column + ) + + +class CacheGroup(CacheEntry): + """Cache entry for a Fabric group""" + def __init__(self, group_name, servers): + super(CacheGroup, self).__init__(version=None, fabric_uuid=None) + self.group_name = group_name + self.servers = servers + + @classmethod + def hash_index(cls, part1, part2=None): + """Create hash for indexing""" + return sha1(part1.encode('utf-8')).hexdigest() + + def __repr__(self): + return "{class_}({group})".format( + class_=self.__class__, + group=self.group_name, + ) + +class FabricCache(object): + """Singleton class for caching Fabric data + + Only one instance of this class can exists globally. + """ + def __init__(self, ttl=_CACHE_TTL): + self._ttl = ttl + self._sharding = {} + self._groups = {} + self.__sharding_lock = threading.Lock() + self.__groups_lock = threading.Lock() + + def remove_group(self, entry_hash): + """Remove cache entry for group""" + with self.__groups_lock: + try: + del self._groups[entry_hash] + except KeyError: + # not cached, that's OK + pass + else: + _LOGGER.debug("Group removed from cache") + + def remove_shardtable(self, entry_hash): + """Remove cache entry for shard""" + with self.__sharding_lock: + try: + del self._sharding[entry_hash] + except KeyError: + # not cached, that's OK + pass + + def sharding_cache_table(self, shard, version=None, fabric_uuid=None): + """Cache information about a shard""" + entry_hash = CacheShardTable.hash_index(shard.database, shard.table) + + with self.__sharding_lock: + try: + entry = self._sharding[entry_hash] + entry.add_partition(shard.key, shard.group) + except KeyError: + # New cache entry + entry = CacheShardTable(shard, version=version, + fabric_uuid=fabric_uuid) + self._sharding[entry_hash] = entry + + def cache_group(self, group_name, servers): + """Cache information about a group""" + entry_hash = CacheGroup.hash_index(group_name) + + with self.__groups_lock: + try: + entry = self._groups[entry_hash] + entry.servers = servers + entry.reset_ttl() + _LOGGER.debug("Recaching group {0} with {1}".format( + group_name, servers)) + except KeyError: + # New cache entry + entry = CacheGroup(group_name, servers) + self._groups[entry_hash] = entry + _LOGGER.debug("Caching group {0} with {1}".format( + group_name, servers)) + + def sharding_search(self, database, table): + """Search cache for a shard based on database and table""" + entry_hash = CacheShardTable.hash_index(database, table) + + entry = None + try: + entry = self._sharding[entry_hash] + if entry.invalid: + _LOGGER.debug("{entry} invalidated".format(entry)) + self.remove_shardtable(entry_hash) + return None + except KeyError: + # Nothing in cache + return None + + return entry + + def group_search(self, group_name): + """Search cache for a group based on its name""" + entry_hash = CacheGroup.hash_index(group_name) + + entry = None + try: + entry = self._groups[entry_hash] + if entry.invalid: + _LOGGER.debug("{entry} invalidated".format(entry)) + self.remove_group(entry_hash) + return None + except KeyError: + # Nothing in cache + return None + + return entry + + def __repr__(self): + return "{class_}(groups={nrgroups},shards={nrshards})".format( + class_=self.__class__, + nrgroups=len(self._groups), + nrshards=len(self._sharding) + ) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/fabric/connection.py mysql-connector-python-2.0.4/lib/mysql/connector/fabric/connection.py --- mysql-connector-python-1.1.6/lib/mysql/connector/fabric/connection.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/fabric/connection.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,1328 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 + +"""Implementing communication with MySQL Fabric""" + +import sys +import datetime +import time +import uuid +from base64 import b16decode +from bisect import bisect +from hashlib import md5 +import logging +import socket +import collections + +# pylint: disable=F0401,E0611 +try: + from xmlrpclib import Fault, ServerProxy, Transport + import urllib2 + from httplib import BadStatusLine +except ImportError: + # Python v3 + from xmlrpc.client import Fault, ServerProxy, Transport + import urllib.request as urllib2 + from http.client import BadStatusLine + +if sys.version_info[0] == 2: + try: + from httplib import HTTPSConnection + except ImportError: + HAVE_SSL = False + else: + HAVE_SSL = True +else: + try: + from http.client import HTTPSConnection + except ImportError: + HAVE_SSL = False + else: + HAVE_SSL = True +# pylint: enable=F0401,E0611 + +import mysql.connector +from ..pooling import MySQLConnectionPool +from ..errors import ( + Error, InterfaceError, NotSupportedError, MySQLFabricError, InternalError, + DatabaseError +) +from ..cursor import ( + MySQLCursor, MySQLCursorBuffered, + MySQLCursorRaw, MySQLCursorBufferedRaw +) +from .. import errorcode +from . import FabricMySQLServer, FabricShard +from .caching import FabricCache +from .balancing import WeightedRoundRobin +from .. import version +from ..catch23 import PY2, isunicode, UNICODE_TYPES + +RESET_CACHE_ON_ERROR = ( + errorcode.CR_SERVER_LOST, + errorcode.ER_OPTION_PREVENTS_STATEMENT, +) + + +# Errors to be reported to Fabric +REPORT_ERRORS = ( + errorcode.CR_SERVER_LOST, + errorcode.CR_SERVER_GONE_ERROR, + errorcode.CR_CONN_HOST_ERROR, + errorcode.CR_CONNECTION_ERROR, + errorcode.CR_IPSOCK_ERROR, +) +REPORT_ERRORS_EXTRA = [] + +MYSQL_FABRIC_PORT = 32274 + +FABRICS = {} + +# For attempting to connect with Fabric +_CNX_ATTEMPT_DELAY = 1 +_CNX_ATTEMPT_MAX = 3 + +_GETCNX_ATTEMPT_DELAY = 1 +_GETCNX_ATTEMPT_MAX = 3 + +MODE_READONLY = 1 +MODE_WRITEONLY = 2 +MODE_READWRITE = 3 + +STATUS_FAULTY = 0 +STATUS_SPARE = 1 +STATUS_SECONDARY = 2 +STATUS_PRIMARY = 3 + +SCOPE_GLOBAL = 'GLOBAL' +SCOPE_LOCAL = 'LOCAL' + +_SERVER_STATUS_FAULTY = 'FAULTY' + +_CNX_PROPERTIES = { + # name: ((valid_types), description, default) + 'group': ((str,), "Name of group of servers", None), + 'key': (tuple([int, str, datetime.datetime, + datetime.date] + list(UNICODE_TYPES)), + "Sharding key", None), + 'tables': ((tuple, list), "List of tables in query", None), + 'mode': ((int,), "Read-Only, Write-Only or Read-Write", MODE_READWRITE), + 'shard': ((str,), "Identity of the shard for direct connection", None), + 'mapping': ((str,), "", None), + 'scope': ((str,), "GLOBAL for accessing Global Group, or LOCAL", + SCOPE_LOCAL), + 'attempts': ((int,), "Attempts for getting connection", + _GETCNX_ATTEMPT_MAX), + 'attempt_delay': ((int,), "Seconds to wait between each attempt", + _GETCNX_ATTEMPT_DELAY), +} + +_LOGGER = logging.getLogger('myconnpy-fabric') + +class FabricResponse(object): + """Class used to parse a response got from Fabric. + """ + + SUPPORTED_VERSION = 1 + + def __init__(self, data): + """Initialize the FabricResponse object + """ + (format_version, fabric_uuid_str, ttl, error, rows) = data + if error: + raise InterfaceError(error) + if format_version != FabricResponse.SUPPORTED_VERSION: + raise InterfaceError( + "Supported protocol has version {sversion}. Got a response " + "from MySQL Fabric with version {gversion}.".format( + sversion=FabricResponse.SUPPORTED_VERSION, + gversion=format_version) + ) + self.format_version = format_version + self.fabric_uuid_str = fabric_uuid_str + self.ttl = ttl + self.coded_rows = rows + +class FabricSet(FabricResponse): + """Iterator to navigate through the result set returned from Fabric + """ + def __init__(self, data): + """Initialize the FabricSet object. + """ + super(FabricSet, self).__init__(data) + assert len(self.coded_rows) == 1 + self.__names = self.coded_rows[0]['info']['names'] + self.__rows = self.coded_rows[0]['rows'] + assert all(len(self.__names) == len(row) for row in self.__rows) or \ + len(self.__rows) == 0 + self.__result = collections.namedtuple('ResultSet', self.__names) + + def rowcount(self): + """The number of rows in the result set. + """ + return len(self.__rows) + + def rows(self): + """Iterate over the rows of the result set. + + Each row is a named tuple. + """ + for row in self.__rows: + yield self.__result(*row) + + def row(self, index): + """Indexing method for a row. + + Each row is a named tuple. + """ + return self.__result(*self.__rows[index]) + + +def extra_failure_report(error_codes): + """Add MySQL error to be reported to Fabric + + This function adds error_codes to the error list to be reported to + Fabric. To reset the custom error reporting list, pass None or empty + list. + + The error_codes argument can be either a MySQL error code defined in the + errorcode module, or list of error codes. + + Raises AttributeError when code is not an int. + """ + global REPORT_ERRORS_EXTRA # pylint: disable=W0603 + + if not error_codes: + REPORT_ERRORS_EXTRA = [] + + if not isinstance(error_codes, (list, tuple)): + error_codes = [error_codes] + + for code in error_codes: + if not isinstance(code, int) or not (code >= 1000 and code < 3000): + raise AttributeError("Unknown or invalid error code.") + REPORT_ERRORS_EXTRA.append(code) + + +def _fabric_xmlrpc_uri(host, port): + """Create an XMLRPC URI for connecting to Fabric + + This method will create a URI using the host and TCP/IP + port suitable for connecting to a MySQL Fabric instance. + + Returns a URI. + """ + return 'http://{host}:{port}'.format(host=host, port=port) + + +def _fabric_server_uuid(host, port): + """Create a UUID using host and port""" + return uuid.uuid3(uuid.NAMESPACE_URL, _fabric_xmlrpc_uri(host, port)) + + +def _validate_ssl_args(ssl_ca, ssl_key, ssl_cert): + """Validate the SSL argument. + + Raises AttributeError is required argument is not set. + + Returns dict or None. + """ + if not HAVE_SSL: + raise InterfaceError("Python does not support SSL") + if any([ssl_ca, ssl_key, ssl_cert]): + if not ssl_ca: + raise AttributeError("Missing ssl_ca argument.") + if (ssl_key or ssl_cert) and not (ssl_key and ssl_cert): + raise AttributeError( + "ssl_key and ssl_cert need to be both " + "specified, or neither." + ) + return { + 'ca': ssl_ca, + 'key': ssl_key, + 'cert': ssl_cert, + } + + return None + + +if HAVE_SSL: + class FabricHTTPSHandler(urllib2.HTTPSHandler): + + """Class handling HTTPS connections""" + + def __init__(self, ssl_config): #pylint: disable=E1002 + """Initialize""" + if PY2: + urllib2.HTTPSHandler.__init__(self) + else: + super().__init__() # pylint: disable=W0104 + self._ssl_config = ssl_config + + def https_open(self, req): + """Open HTTPS connection""" + return self.do_open(self.get_https_connection, req) + + def get_https_connection(self, host, timeout=300): + """Returns a HTTPSConnection""" + return HTTPSConnection( + host, + key_file=self._ssl_config['key'], + cert_file=self._ssl_config['cert'] + ) + + +class FabricTransport(Transport): + + """Custom XMLRPC Transport for Fabric""" + + user_agent = 'MySQL Connector Python/{0}'.format(version.VERSION_TEXT) + + def __init__(self, username, password, #pylint: disable=E1002 + verbose=0, use_datetime=False, https_handler=None): + """Initialize""" + if PY2: + Transport.__init__(self, use_datetime=False) + else: + super().__init__(use_datetime=False) + self._username = username + self._password = password + self._use_datetime = use_datetime + self.verbose = verbose + self._username = username + self._password = password + + self._handlers = [] + + if self._username and self._password: + self._passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm() + self._auth_handler = urllib2.HTTPDigestAuthHandler(self._passmgr) + else: + self._auth_handler = None + self._passmgr = None + + if https_handler: + self._handlers.append(https_handler) + self._scheme = 'https' + else: + self._scheme = 'http' + + if self._auth_handler: + self._handlers.append(self._auth_handler) + + def request(self, host, handler, request_body, verbose=0): + """Send XMLRPC request""" + uri = '{scheme}://{host}{handler}'.format(scheme=self._scheme, + host=host, handler=handler) + + if self._passmgr: + self._passmgr.add_password(None, uri, self._username, + self._password) + if self.verbose: + _LOGGER.debug("FabricTransport: {0}".format(uri)) + + opener = urllib2.build_opener(*self._handlers) + + headers = { + 'Content-Type': 'text/xml', + 'User-Agent': self.user_agent, + } + req = urllib2.Request(uri, request_body, headers=headers) + + try: + return self.parse_response(opener.open(req)) + except (urllib2.URLError, urllib2.HTTPError) as exc: + try: + code = -1 + if exc.code == 400: + reason = 'Permission denied' + code = exc.code + else: + reason = exc.reason + msg = "{reason} ({code})".format(reason=reason, code=code) + except AttributeError: + if 'SSL' in str(exc): + msg = "SSL error" + else: + msg = str(exc) + raise InterfaceError("Connection with Fabric failed: " + msg) + except BadStatusLine: + raise InterfaceError("Connection with Fabric failed: check SSL") + + +class Fabric(object): + + """Class managing MySQL Fabric instances""" + + def __init__(self, host, username=None, password=None, + port=MYSQL_FABRIC_PORT, + connect_attempts=_CNX_ATTEMPT_MAX, + connect_delay=_CNX_ATTEMPT_DELAY, + report_errors=False, + ssl_ca=None, ssl_key=None, ssl_cert=None, user=None): + """Initialize""" + self._fabric_instances = {} + self._fabric_uuid = None + self._ttl = 1 * 60 # one minute by default + self._version_token = None + self._connect_attempts = connect_attempts + self._connect_delay = connect_delay + self._cache = FabricCache() + self._group_balancers = {} + self._init_host = host + self._init_port = port + self._ssl = _validate_ssl_args(ssl_ca, ssl_key, ssl_cert) + self._report_errors = report_errors + + if user and username: + raise ValueError("can not specify both user and username") + self._username = user or username + self._password = password + + @property + def username(self): + """Return username used to authenticate with Fabric""" + return self._username + + @property + def password(self): + """Return password used to authenticate with Fabric""" + return self._password + + @property + def ssl_config(self): + """Return the SSL configuration""" + return self._ssl + + def seed(self, host=None, port=None): + """Get MySQL Fabric Instances + + This method uses host and port to connect to a MySQL Fabric server + and get all the instances managing the same metadata. + + Raises InterfaceError on errors. + """ + host = host or self._init_host + port = port or self._init_port + + fabinst = FabricConnection(self, host, port, + connect_attempts=self._connect_attempts, + connect_delay=self._connect_delay) + fabinst.connect() + fabric_uuid, fabric_version, ttl, fabrics = self.get_fabric_servers( + fabinst) + + if not fabrics: + # Raise, something went wrong. + raise InterfaceError("Failed getting list of Fabric servers") + + if self._version_token == fabric_version: + return + + _LOGGER.info( + "Loading Fabric configuration version {version}".format( + version=fabric_version)) + self._fabric_uuid = fabric_uuid + self._version_token = fabric_version + if ttl > 0: + self._ttl = ttl + + # Update the Fabric servers + for fabric in fabrics: + inst = FabricConnection(self, fabric['host'], fabric['port'], + connect_attempts=self._connect_attempts, + connect_delay=self._connect_delay) + inst_uuid = inst.uuid + if inst_uuid not in self._fabric_instances: + inst.connect() + self._fabric_instances[inst_uuid] = inst + _LOGGER.debug( + "Added new Fabric server {host}:{port}".format( + host=inst.host, port=inst.port)) + + def reset_cache(self, group=None): + """Reset cached information + + This method destroys all cached information. + """ + if group: + _LOGGER.debug("Resetting cache for group '{group}'".format( + group=group)) + self.get_group_servers(group, use_cache=False) + else: + _LOGGER.debug("Resetting cache") + self._cache = FabricCache() + + def get_instance(self): + """Get a MySQL Fabric Instance + + This method will get the next available MySQL Fabric Instance. + + Raises InterfaceError when no instance is available or connected. + """ + nxt = 0 + errmsg = "No MySQL Fabric instance available" + if not self._fabric_instances: + raise InterfaceError(errmsg + " (not seeded?)") + if PY2: + instance_list = self._fabric_instances.keys() + inst = self._fabric_instances[instance_list[nxt]] + else: + inst = self._fabric_instances[list(self._fabric_instances)[nxt]] + if not inst.is_connected: + inst.connect() + return inst + + def report_failure(self, server_uuid, errno): + """Report failure to Fabric + + This method sets the status of a MySQL server identified by + server_uuid. + """ + if not self._report_errors: + return + + errno = int(errno) + current_host = socket.getfqdn() + + if errno in REPORT_ERRORS or errno in REPORT_ERRORS_EXTRA: + _LOGGER.debug("Reporting error %d of server %s", errno, + server_uuid) + inst = self.get_instance() + try: + data = inst.proxy.threat.report_failure( + server_uuid, current_host, errno + ) + FabricResponse(data) + except (Fault, socket.error) as exc: + _LOGGER.debug("Failed reporting server to Fabric (%s)", + str(exc)) + # Not requiring further action + + def get_fabric_servers(self, fabric_cnx=None): + """Get all MySQL Fabric instances + + This method looks up the other MySQL Fabric instances which uses + the same metadata. The returned list contains dictionaries with + connection information such ass host and port. For example: + + [ + {'host': 'fabric_prod_1.example.com', 'port': 32274 }, + {'host': 'fabric_prod_2.example.com', 'port': 32274 }, + ] + + Returns a list of dictionaries + """ + inst = fabric_cnx or self.get_instance() + result = [] + err_msg = "Looking up Fabric servers failed using {host}:{port}: {err}" + try: + data = inst.proxy.dump.fabric_nodes('protocol.xmlrpc') + fset = FabricSet(data) + for row in fset.rows(): + result.append({'host': row.host, 'port': row.port}) + except (Fault, socket.error) as exc: + msg = err_msg.format(err=str(exc), host=inst.host, port=inst.port) + raise InterfaceError(msg) + except (TypeError, AttributeError) as exc: + msg = err_msg.format( + err="No Fabric server available ({0})".format(exc), + host=inst.host, port=inst.port) + raise InterfaceError(msg) + + try: + fabric_uuid = uuid.UUID(fset.fabric_uuid_str) + except TypeError: + fabric_uuid = uuid.uuid4() + + fabric_version = 0 + + return fabric_uuid, fabric_version, fset.ttl, result + + def get_group_servers(self, group, use_cache=True): + """Get all MySQL servers in a group + + This method returns information about all MySQL part of the + given high-availability group. When use_cache is set to + True, the cached information will be used. + + Raises InterfaceError on errors. + + Returns list of FabricMySQLServer objects. + """ + # Get group information from cache + if use_cache: + entry = self._cache.group_search(group) + if entry: + # Cache group information + return entry.servers + + inst = self.get_instance() + result = [] + try: + data = inst.proxy.dump.servers(self._version_token, group) + fset = FabricSet(data) + except (Fault, socket.error) as exc: + msg = ("Looking up MySQL servers failed for group " + "{group}: {error}").format(error=str(exc), group=group) + raise InterfaceError(msg) + + weights = [] + for row in fset.rows(): + # We make sure, when using local groups, we skip the global group + if row.group_id == group: + mysqlserver = FabricMySQLServer( + row.server_uuid, row.group_id, row.host, row.port, + row.mode, row.status, row.weight + ) + result.append(mysqlserver) + if mysqlserver.status == STATUS_SECONDARY: + weights.append((mysqlserver.uuid, mysqlserver.weight)) + + self._cache.cache_group(group, result) + if weights: + self._group_balancers[group] = WeightedRoundRobin(*weights) + + return result + + def get_group_server(self, group, mode=None, status=None): + """Get a MySQL server from a group + + The method uses MySQL Fabric to get the correct MySQL server + for the specified group. You can specify mode or status, but + not both. + + The mode argument will decide whether the primary or a secondary + server is returned. When no secondary server is available, the + primary is returned. + + Status is used to force getting either a primary or a secondary. + + The returned tuple contains host, port and uuid. + + Raises InterfaceError on errors; ValueError when both mode + and status are given. + + Returns a FabricMySQLServer object. + """ + if mode and status: + raise ValueError( + "Either mode or status must be given, not both") + + errmsg = "No MySQL server available for group '{group}'" + + servers = self.get_group_servers(group, use_cache=True) + if not servers: + raise InterfaceError(errmsg.format(group=group)) + + # Get the Master and return list (host, port, UUID) + primary = None + secondary = [] + for server in servers: + if server.status == STATUS_SECONDARY: + secondary.append(server) + elif server.status == STATUS_PRIMARY: + primary = server + + if mode in (MODE_WRITEONLY, MODE_READWRITE) or status == STATUS_PRIMARY: + if not primary: + self.reset_cache(group=group) + raise InterfaceError((errmsg + ' {query}={value}').format( + query='status' if status else 'mode', + group=group, + value=status or mode)) + return primary + + # Return primary if no secondary is available + if not secondary and primary: + return primary + elif group in self._group_balancers: + next_secondary = self._group_balancers[group].get_next()[0] + for mysqlserver in secondary: + if next_secondary == mysqlserver.uuid: + return mysqlserver + + self.reset_cache(group=group) + raise InterfaceError(errmsg.format(group=group, mode=mode)) + + def get_sharding_information(self, tables=None, database=None): + """Get and cache the sharding information for given tables + + This method is fetching sharding information from MySQL Fabric + and caches the result. The tables argument must be sequence + of sequences contain the name of the database and table. If no + database is given, the value for the database argument will + be used. + + Examples: + tables = [('salary',), ('employees',)] + get_sharding_information(tables, database='employees') + + tables = [('salary', 'employees'), ('employees', employees)] + get_sharding_information(tables) + + Raises InterfaceError on errors; ValueError when something is wrong + with the tables argument. + """ + if not isinstance(tables, (list, tuple)): + raise ValueError("tables should be a sequence") + + patterns = [] + for table in tables: + if not isinstance(table, (list, tuple)) and not database: + raise ValueError("No database specified for table {0}".format( + table)) + + if isinstance(table, (list, tuple)): + dbase = table[1] + tbl = table[0] + else: + dbase = database + tbl = table + patterns.append("{0}.{1}".format(dbase, tbl)) + + inst = self.get_instance() + try: + data = inst.proxy.dump.sharding_information( + self._version_token, ','.join(patterns) + ) + fset = FabricSet(data) + except (Fault, socket.error) as exc: + msg = "Looking up sharding information failed : {error}".format( + error=str(exc)) + raise InterfaceError(msg) + + for row in fset.rows(): + self._cache.sharding_cache_table( + FabricShard(row.schema_name, row.table_name, row.column_name, + row.lower_bound, row.shard_id, row.type_name, + row.group_id, row.global_group) + ) + + def get_shard_server(self, tables, key, scope=SCOPE_LOCAL, mode=None): + """Get MySQL server information for a particular shard + + Raises DatabaseError when the table is unknown or when tables are not + on the same shard. ValueError is raised when there is a problem + with the methods arguments. InterfaceError is raised for other errors. + """ + if not isinstance(tables, (list, tuple)): + raise ValueError("tables should be a sequence") + + groups = [] + + for dbobj in tables: + try: + database, table = dbobj.split('.') + except ValueError: + raise ValueError( + "tables should be given as ., " + "was {0}".format(dbobj)) + + entry = self._cache.sharding_search(database, table) + if not entry: + self.get_sharding_information((table,), database) + entry = self._cache.sharding_search(database, table) + if not entry: + raise DatabaseError( + errno=errorcode.ER_BAD_TABLE_ERROR, + msg="Unknown table '{database}.{table}'".format( + database=database, table=table)) + + if scope == 'GLOBAL': + return self.get_group_server(entry.global_group, mode=mode) + + if entry.shard_type == 'RANGE': + try: + range_key = int(key) + except ValueError: + raise ValueError("Key must be an integer for RANGE") + partitions = entry.keys + index = partitions[bisect(partitions, range_key) - 1] + partition = entry.partitioning[index] + elif entry.shard_type == 'RANGE_DATETIME': + if not isinstance(key, (datetime.date, datetime.datetime)): + raise ValueError( + "Key must be datetime.date or datetime.datetime for " + "RANGE_DATETIME") + index = None + for partkey in entry.keys_reversed: + if key >= partkey: + index = partkey + break + try: + partition = entry.partitioning[index] + except KeyError: + raise ValueError("Key invalid; was '{0}'".format(key)) + elif entry.shard_type == 'RANGE_STRING': + if not isunicode(key): + raise ValueError("Key must be a unicode value") + index = None + for partkey in entry.keys_reversed: + if key >= partkey: + index = partkey + break + try: + partition = entry.partitioning[index] + except KeyError: + raise ValueError("Key invalid; was '{0}'".format(key)) + elif entry.shard_type == 'HASH': + md5key = md5(str(key)) + index = entry.keys_reversed[-1] + for partkey in entry.keys_reversed: + if md5key.digest() >= b16decode(partkey): + index = partkey + break + partition = entry.partitioning[index] + else: + raise InterfaceError( + "Unsupported sharding type {0}".format(entry.shard_type)) + + groups.append(partition['group']) + if not all(group == groups[0] for group in groups): + raise DatabaseError( + "Tables are located in different shards.") + + return self.get_group_server(groups[0], mode=mode) + + def execute(self, group, command, *args, **kwargs): + """Execute a Fabric command from given group + + This method will execute the given Fabric command from the given group + using the given arguments. It returns an instance of FabricSet. + + Raises ValueError when group.command is not valid and raises + InterfaceError when an error occurs while executing. + + Returns FabricSet. + """ + inst = self.get_instance() + try: + grp = getattr(inst.proxy, group) + cmd = getattr(grp, command) + except AttributeError as exc: + raise ValueError("{group}.{command} not available ({err})".format( + group=group, command=command, err=str(exc))) + + fab_set = None + try: + data = cmd(*args, **kwargs) + fab_set = FabricSet(data) + except (Fault, socket.error, InterfaceError) as exc: + msg = "Executing {group}.{command} failed: {error}".format( + group=group, command=command, error=str(exc)) + raise InterfaceError(msg) + + return fab_set + + +class FabricConnection(object): + + """Class holding a connection to a MySQL Fabric server""" + + def __init__(self, fabric, host, port=MYSQL_FABRIC_PORT, + connect_attempts=_CNX_ATTEMPT_MAX, + connect_delay=_CNX_ATTEMPT_DELAY): + """Initialize""" + if not isinstance(fabric, Fabric): + raise ValueError("fabric must be instance of class Fabric") + self._fabric = fabric + self._host = host + self._port = port + self._proxy = None + self._connect_attempts = connect_attempts + self._connect_delay = connect_delay + + @property + def host(self): + """Returns server IP or name of current Fabric connection""" + return self._host + + @property + def port(self): + """Returns TCP/IP port of current Fabric connection""" + return self._port + + @property + def uri(self): + """Returns the XMLRPC URI for current Fabric connection""" + return _fabric_xmlrpc_uri(self._host, self._port) + + @property + def uuid(self): + """Returns UUID of the Fabric server we are connected with""" + return _fabric_server_uuid(self._host, self._port) + + @property + def proxy(self): + """Returns the XMLRPC Proxy of current Fabric connection""" + return self._proxy + + def _xmlrpc_get_proxy(self): + """Return the XMLRPC server proxy instance to MySQL Fabric + + This method tries to get a valid connection to a MySQL Fabric + server. + + Returns a XMLRPC ServerProxy instance. + """ + if self.is_connected: + return self._proxy + + attempts = self._connect_attempts + delay = self._connect_delay + + proxy = None + counter = 0 + while counter != attempts: + counter += 1 + try: + if self._fabric.ssl_config: + if not HAVE_SSL: + raise InterfaceError("Python does not support SSL") + https_handler = FabricHTTPSHandler(self._fabric.ssl_config) + else: + https_handler = None + + transport = FabricTransport(self._fabric.username, + self._fabric.password, + verbose=0, + https_handler=https_handler) + proxy = ServerProxy(self.uri, transport=transport, verbose=0) + proxy._some_nonexisting_method() # pylint: disable=W0212 + except Fault: + # We are actually connected + return proxy + except socket.error as exc: + if counter == attempts: + raise InterfaceError( + "Connection to MySQL Fabric failed ({0})".format(exc)) + _LOGGER.debug( + "Retrying {host}:{port}, attempts {counter}".format( + host=self.host, port=self.port, counter=counter)) + if delay > 0: + time.sleep(delay) + + def connect(self): + """Connect with MySQL Fabric""" + self._proxy = self._xmlrpc_get_proxy() + + @property + def is_connected(self): + """Check whether connection with Fabric is valid + + Return True if we can still interact with the Fabric server; False + if Not. + + Returns True or False. + """ + try: + self._proxy._some_nonexisting_method() # pylint: disable=W0212 + except Fault: + return True + except (TypeError, AttributeError): + return False + else: + return False + + def __repr__(self): + return "{class_}(host={host}, port={port})".format( + class_=self.__class__, + host=self._host, + port=self._port, + ) + + +class MySQLFabricConnection(object): + + """Connection to a MySQL server through MySQL Fabric""" + + def __init__(self, **kwargs): + """Initialize""" + self._mysql_cnx = None + self._fabric = None + self._fabric_mysql_server = None + self._mysql_config = None + self._cnx_properties = {} + self.reset_properties() + + # Validity of fabric-argument is checked in config()-method + if 'fabric' not in kwargs: + raise ValueError("Configuration parameters for Fabric missing") + + if kwargs: + self.store_config(**kwargs) + + def __getattr__(self, attr): + """Return the return value of the MySQLConnection instance""" + if attr.startswith('cmd_'): + raise NotSupportedError( + "Calling {attr} is not supported for connections managed by " + "MySQL Fabric.".format(attr=attr)) + return getattr(self._mysql_cnx, attr) + + @property + def fabric_uuid(self): + """Returns the Fabric UUID of the MySQL server""" + if self._fabric_mysql_server: + return self._fabric_mysql_server.uuid + return None + + @property + def properties(self): + """Returns connection properties""" + return self._cnx_properties + + def reset_cache(self, group=None): + """Reset cache for this connection's group""" + if not group and self._fabric_mysql_server: + group = self._fabric_mysql_server.group + self._fabric.reset_cache(group=group) + + def is_connected(self): + """Check whether we are connected with the MySQL server + + Returns True or False + """ + return self._mysql_cnx is not None + + def reset_properties(self): + """Resets the connection properties + + This method can be called to reset the connection properties to + their default values. + """ + self._cnx_properties = {} + for key, attr in _CNX_PROPERTIES.items(): + self._cnx_properties[key] = attr[2] + + def set_property(self, **properties): + """Set one or more connection properties + + Arguments to the set_property() method will be used as properties. + They are validated against the _CNX_PROPERTIES constant. + + Raise ValueError in case an invalid property is being set. TypeError + is raised when the type of the value is not correct. + + To unset a property, set it to None. + """ + try: + self.close() + except Error: + # We tried, but it's OK when we fail. + pass + + props = self._cnx_properties + + for name, value in properties.items(): + if name not in _CNX_PROPERTIES: + raise ValueError( + "Invalid property connection {0}".format(name)) + elif value and not isinstance(value, _CNX_PROPERTIES[name][0]): + valid_types_str = ' or '.join( + [atype.__name__ for atype in _CNX_PROPERTIES[name][0]]) + raise TypeError( + "{name} is not valid, excepted {typename}".format( + name=name, typename=valid_types_str)) + + if (name == 'group' and value and + (props['key'] or props['tables'])): + raise ValueError( + "'group' property can not be set when 'key' or " + "'tables' are set") + elif name in ('key', 'tables') and value and props['group']: + raise ValueError( + "'key' and 'tables' property can not be " + "set together with 'group'") + elif name == 'scope' and value not in (SCOPE_LOCAL, SCOPE_GLOBAL): + raise ValueError("Invalid value for 'scope'") + elif name == 'mode' and value not in ( + MODE_READWRITE, MODE_READONLY): + raise ValueError("Invalid value for 'mode'") + + if value is None: + # Set the default + props[name] = _CNX_PROPERTIES[name][2] + else: + props[name] = value + + def _configure_fabric(self, config): + """Configure the Fabric connection + + The config argument can be either a dictionary containing the + necessary information to setup the connection. Or config can + be an instance of Fabric. + """ + if isinstance(config, Fabric): + self._fabric = config + else: + required_keys = ['host'] + for required_key in required_keys: + if required_key not in config: + raise ValueError( + "Missing configuration parameter '{parameter}' " + "for fabric".format(parameter=required_key)) + host = config['host'] + port = config.get('port', MYSQL_FABRIC_PORT) + server_uuid = _fabric_server_uuid(host, port) + try: + self._fabric = FABRICS[server_uuid] + except KeyError: + _LOGGER.debug("New Fabric connection") + self._fabric = Fabric(**config) + self._fabric.seed() + # Cache the new connection + FABRICS[server_uuid] = self._fabric + + def store_config(self, **kwargs): + """Store configuration of MySQL connections to use with Fabric + + The configuration found in the dictionary kwargs is used + when instanciating a MySQLConnection object. The host and port + entries are used to connect to MySQL Fabric. + + Raises ValueError when the Fabric configuration parameter + is not correct or missing; AttributeError is raised when + when a paramater is not valid. + """ + config = kwargs.copy() + + # Configure the Fabric connection + if 'fabric' in config: + self._configure_fabric(config['fabric']) + del config['fabric'] + + if 'unix_socket' in config: + _LOGGER.warning("MySQL Fabric does not use UNIX sockets.") + config['unix_socket'] = None + + # Try to use the configuration + test_config = config.copy() + if 'pool_name' in test_config: + del test_config['pool_name'] + if 'pool_size' in test_config: + del test_config['pool_size'] + if 'pool_reset_session' in test_config: + del test_config['pool_reset_session'] + try: + pool = MySQLConnectionPool(pool_name=str(uuid.uuid4())) + pool.set_config(**test_config) + except AttributeError as err: + raise AttributeError( + "Connection configuration not valid: {0}".format(err)) + + self._mysql_config = config + + def _connect(self): + """Get a MySQL server based on properties and connect + + This method gets a MySQL server from MySQL Fabric using already + properties set using the set_property() method. You can specify how + many times and the delay between trying using attempts and + attempt_delay. + + Raises ValueError when there are problems with arguments or + properties; InterfaceError on connectivity errors. + """ + if self.is_connected(): + return + props = self._cnx_properties + attempts = props['attempts'] + attempt_delay = props['attempt_delay'] + + dbconfig = self._mysql_config.copy() + counter = 0 + while counter != attempts: + counter += 1 + try: + group = None + if props['tables']: + if props['scope'] == 'LOCAL' and not props['key']: + raise ValueError( + "Scope 'LOCAL' needs key property to be set") + mysqlserver = self._fabric.get_shard_server( + props['tables'], props['key'], + scope=props['scope'], + mode=props['mode']) + elif props['group']: + group = props['group'] + mysqlserver = self._fabric.get_group_server( + group, mode=props['mode']) + else: + raise ValueError( + "Missing group or key and tables properties") + except InterfaceError as exc: + _LOGGER.debug( + "Trying to get MySQL server (attempt {0}; {1})".format( + counter, exc)) + if counter == attempts: + raise InterfaceError("Error getting connection: {0}".format( + exc)) + if attempt_delay > 0: + _LOGGER.debug("Waiting {0}".format(attempt_delay)) + time.sleep(attempt_delay) + continue + + # Make sure we do not change the stored configuration + dbconfig['host'] = mysqlserver.host + dbconfig['port'] = mysqlserver.port + try: + self._mysql_cnx = mysql.connector.connect(**dbconfig) + except Error as exc: + if counter == attempts: + self.reset_cache(mysqlserver.group) + self._fabric.report_failure(mysqlserver.uuid, exc.errno) + raise InterfaceError( + "Reported faulty server to Fabric ({0})".format(exc)) + if attempt_delay > 0: + time.sleep(attempt_delay) + continue + else: + self._fabric_mysql_server = mysqlserver + break + + def disconnect(self): + """Close connection to MySQL server""" + try: + self.rollback() + self._mysql_cnx.close() + except AttributeError: + pass # There was no connection + except Error: + raise + finally: + self._mysql_cnx = None + self._fabric_mysql_server = None + close = disconnect + + def cursor(self, buffered=None, raw=None, prepared=None, cursor_class=None): + """Instantiates and returns a cursor + + This method is similar to MySQLConnection.cursor() except that + it checks whether the connection is available and raises + an InterfaceError when not. + + cursor_class argument is not supported and will raise a + NotSupportedError exception. + + Returns a MySQLCursor or subclass. + """ + self._connect() + if cursor_class: + raise NotSupportedError( + "Custom cursors not supported with MySQL Fabric") + + if prepared: + raise NotSupportedError( + "Prepared Statements are not supported with MySQL Fabric") + + if self._unread_result is True: + raise InternalError("Unread result found.") + + 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 handle_mysql_error(self, exc): + """Handles MySQL errors + + This method takes a mysql.connector.errors.Error exception + and checks the error code. Based on the value, it takes + certain actions such as clearing the cache. + """ + if exc.errno in RESET_CACHE_ON_ERROR: + self.reset_cache() + self.disconnect() + raise MySQLFabricError( + "Temporary error ({error}); " + "retry transaction".format(error=str(exc))) + + raise exc + + def commit(self): + """Commit current transaction + + Raises whatever MySQLConnection.commit() raises, but + raises MySQLFabricError when MySQL returns error + ER_OPTION_PREVENTS_STATEMENT. + """ + try: + self._mysql_cnx.commit() + except Error as exc: + self.handle_mysql_error(exc) + + def rollback(self): + """Rollback current transaction + + Raises whatever MySQLConnection.rollback() raises, but + raises MySQLFabricError when MySQL returns error + ER_OPTION_PREVENTS_STATEMENT. + """ + try: + self._mysql_cnx.rollback() + except Error as exc: + self.handle_mysql_error(exc) + + def cmd_query(self, statement): + """Send a statement to the MySQL server + + Raises whatever MySQLConnection.cmd_query() raises, but + raises MySQLFabricError when MySQL returns error + ER_OPTION_PREVENTS_STATEMENT. + + Returns a dictionary. + """ + self._connect() + try: + return self._mysql_cnx.cmd_query(statement) + except Error as exc: + self.handle_mysql_error(exc) + + def cmd_query_iter(self, statements): + """Send one or more statements to the MySQL server + + Raises whatever MySQLConnection.cmd_query_iter() raises, but + raises MySQLFabricError when MySQL returns error + ER_OPTION_PREVENTS_STATEMENT. + + Returns a dictionary. + """ + self._connect() + try: + return self._mysql_cnx.cmd_query_iter(statements) + except Error as exc: + self.handle_mysql_error(exc) diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/fabric/__init__.py mysql-connector-python-2.0.4/lib/mysql/connector/fabric/__init__.py --- mysql-connector-python-1.1.6/lib/mysql/connector/fabric/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/fabric/__init__.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,76 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 + +"""MySQL Fabric support""" + + +from collections import namedtuple + +# Order of field_names must match how Fabric is returning the data +FabricMySQLServer = namedtuple( + 'FabricMySQLServer', + ['uuid', 'group', 'host', 'port', 'mode', 'status', 'weight'] + ) + +# Order of field_names must match how Fabric is returning the data +FabricShard = namedtuple( + 'FabricShard', + ['database', 'table', 'column', 'key', + 'shard', 'shard_type', 'group', 'global_group'] + ) + +CNX_FABRIC_ARGS = ['fabric_host', 'fabric_username', 'fabric_password', + 'fabric_port', 'fabric_connect_attempts', + 'fabric_connect_delay', 'fabric_report_errors', + 'fabric_ssl_ca', 'fabric_ssl_key', 'fabric_ssl_cert', + 'fabric_user'] + +from .connection import ( + MODE_READONLY, MODE_READWRITE, + STATUS_PRIMARY, STATUS_SECONDARY, + SCOPE_GLOBAL, SCOPE_LOCAL, + Fabric, FabricConnection, + MySQLFabricConnection, + FabricSet, +) + + +def connect(**kwargs): + """Create a MySQLFabricConnection object""" + return MySQLFabricConnection(**kwargs) + +__all__ = [ + 'MODE_READWRITE', + 'MODE_READONLY', + 'STATUS_PRIMARY', + 'STATUS_SECONDARY', + 'SCOPE_GLOBAL', + 'SCOPE_LOCAL', + 'FabricMySQLServer', + 'FabricShard', + 'connect', + 'Fabric', + 'FabricConnection', + 'MySQLFabricConnection', + 'FabricSet', +] diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/__init__.py mysql-connector-python-2.0.4/lib/mysql/connector/__init__.py --- mysql-connector-python-1.1.6/lib/mysql/connector/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/__init__.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,186 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2009, 2014, 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 + +""" +MySQL Connector/Python - MySQL drive written in Python +""" + +from . import version +from .connection import MySQLConnection +from .errors import ( # pylint: disable=W0622 + Error, Warning, InterfaceError, DatabaseError, + NotSupportedError, DataError, IntegrityError, ProgrammingError, + OperationalError, InternalError, custom_error_exception, PoolError) +from .constants import FieldFlag, FieldType, CharacterSet, \ + RefreshOption, ClientFlag +from .dbapi import ( + Date, Time, Timestamp, Binary, DateFromTicks, DateFromTicks, + TimestampFromTicks, TimeFromTicks, + STRING, BINARY, NUMBER, DATETIME, ROWID, + apilevel, threadsafety, paramstyle) +from .optionfiles import read_option_files + +_CONNECTION_POOLS = {} + + +def _get_pooled_connection(**kwargs): + """Return a pooled MySQL connection""" + # If no pool name specified, generate one + from .pooling import ( + MySQLConnectionPool, generate_pool_name, + CONNECTION_POOL_LOCK) + + try: + pool_name = kwargs['pool_name'] + except KeyError: + pool_name = generate_pool_name(**kwargs) + + # Setup the pool, ensuring only 1 thread can update at a time + with CONNECTION_POOL_LOCK: + if pool_name not in _CONNECTION_POOLS: + _CONNECTION_POOLS[pool_name] = MySQLConnectionPool(**kwargs) + elif isinstance(_CONNECTION_POOLS[pool_name], MySQLConnectionPool): + # pool_size must be the same + check_size = _CONNECTION_POOLS[pool_name].pool_size + if ('pool_size' in kwargs + and kwargs['pool_size'] != check_size): + raise PoolError("Size can not be changed " + "for active pools.") + + # Return pooled connection + try: + return _CONNECTION_POOLS[pool_name].get_connection() + except AttributeError: + raise InterfaceError( + "Failed getting connection from pool '{0}'".format(pool_name)) + + +def _get_failover_connection(**kwargs): + """Return a MySQL connection and try to failover if needed + + An InterfaceError is raise when no MySQL is available. ValueError is + raised when the failover server configuration contains an illegal + connection argument. Supported arguments are user, password, host, port, + unix_socket and database. ValueError is also raised when the failover + argument was not provided. + + Returns MySQLConnection instance. + """ + config = kwargs.copy() + try: + failover = config['failover'] + except KeyError: + raise ValueError('failover argument not provided') + del config['failover'] + + support_cnx_args = set( + ['user', 'password', 'host', 'port', 'unix_socket', + 'database', 'pool_name', 'pool_size']) + + # First check if we can add all use the configuration + for server in failover: + diff = set(server.keys()) - support_cnx_args + if diff: + raise ValueError( + "Unsupported connection argument {0} in failover: {1}".format( + 's' if len(diff) > 1 else '', + ', '.join(diff))) + + for server in failover: + new_config = config.copy() + new_config.update(server) + try: + return connect(**new_config) + except Error: + # If we failed to connect, we try the next server + pass + + raise InterfaceError("Could not failover: no MySQL server available") + + +def connect(*args, **kwargs): + """Create or get a MySQL connection object + + In its simpliest form, Connect() will open a connection to a + MySQL server and return a MySQLConnection object. + + When any connection pooling arguments are given, for example pool_name + or pool_size, a pool is created or a previously one is used to return + a PooledMySQLConnection. + + Returns MySQLConnection or PooledMySQLConnection. + """ + # Option files + if 'option_files' in kwargs: + new_config = read_option_files(**kwargs) + return connect(**new_config) + + if all(['fabric' in kwargs, 'failover' in kwargs]): + raise InterfaceError("fabric and failover arguments can not be used") + + if 'fabric' in kwargs: + if 'pool_name' in kwargs: + raise AttributeError("'pool_name' argument is not supported with " + " MySQL Fabric. Use 'pool_size' instead.") + from .fabric import connect as fabric_connect + return fabric_connect(*args, **kwargs) + + # Failover + if 'failover' in kwargs: + return _get_failover_connection(**kwargs) + + # Pooled connections + try: + from .pooling import CNX_POOL_ARGS + if any([key in kwargs for key in CNX_POOL_ARGS]): + return _get_pooled_connection(**kwargs) + except NameError: + # No pooling + pass + + # Regular connection + return MySQLConnection(*args, **kwargs) +Connect = connect # pylint: disable=C0103 + +__version_info__ = version.VERSION +__version__ = version.VERSION_TEXT + +__all__ = [ + 'MySQLConnection', 'Connect', 'custom_error_exception', + + # Some useful constants + 'FieldType', 'FieldFlag', 'ClientFlag', 'CharacterSet', 'RefreshOption', + + # Error handling + 'Error', 'Warning', + 'InterfaceError', 'DatabaseError', + 'NotSupportedError', 'DataError', 'IntegrityError', 'ProgrammingError', + 'OperationalError', 'InternalError', + + # DBAPI PEP 249 required exports + 'connect', 'apilevel', 'threadsafety', 'paramstyle', + 'Date', 'Time', 'Timestamp', 'Binary', + 'DateFromTicks', 'DateFromTicks', 'TimestampFromTicks', 'TimeFromTicks', + 'STRING', 'BINARY', 'NUMBER', + 'DATETIME', 'ROWID', + ] diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/locales/eng/client_error.py mysql-connector-python-2.0.4/lib/mysql/connector/locales/eng/client_error.py --- mysql-connector-python-1.1.6/lib/mysql/connector/locales/eng/client_error.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/locales/eng/client_error.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2013, 2014, 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 = '2014-10-10' +_MYSQL_VERSION = (5, 7, 5) + +# 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_UNUSED_1 = 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 -Nru mysql-connector-python-1.1.6/lib/mysql/connector/locales/eng/__init__.py mysql-connector-python-2.0.4/lib/mysql/connector/locales/eng/__init__.py --- mysql-connector-python-1.1.6/lib/mysql/connector/locales/eng/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/locales/eng/__init__.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,25 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2014, 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 + +"""English Content +""" diff -Nru mysql-connector-python-1.1.6/lib/mysql/connector/locales/__init__.py mysql-connector-python-2.0.4/lib/mysql/connector/locales/__init__.py --- mysql-connector-python-1.1.6/lib/mysql/connector/locales/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/locales/__init__.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,71 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2014, 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 + +"""Translations +""" + +__all__ = [ + 'get_client_error' +] + +from .. 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.{0}'.format(language), + globals(), locals(), ['client_error']) + except ImportError: + raise ImportError("No localization support for language '{0}'".format( + 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 -Nru mysql-connector-python-1.1.6/lib/mysql/connector/network.py mysql-connector-python-2.0.4/lib/mysql/connector/network.py --- mysql-connector-python-1.1.6/lib/mysql/connector/network.py 1970-01-01 00:00:00.000000000 +0000 +++ mysql-connector-python-2.0.4/lib/mysql/connector/network.py 2015-04-01 06:28:05.000000000 +0000 @@ -0,0 +1,477 @@ +# MySQL Connector/Python - MySQL driver written in Python. +# Copyright (c) 2012, 2015, 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. +""" + +from collections import deque +import socket +import struct +import sys +import zlib + +try: + import ssl +except: + # If import fails, we don't have SSL support. + pass + +from . import constants, errors +from .catch23 import PY2, init_bytearray, struct_unpack + + +def _strioerror(err): + """Reformat the IOError error message + + This function reformats the IOError error message. + """ + if not err.errno: + return str(err) + return '{errno} {strerr}'.format(errno=err.errno, strerr=err.strerror) + + +def _prepare_packets(buf, pktnr): + """Prepare a packet for sending to the MySQL server""" + pkts = [] + pllen = len(buf) + maxpktlen = constants.MAX_PACKET_LENGTH + while pllen > maxpktlen: + pkts.append(b'\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 shutdown(self): + """Shut down the socket before closing it""" + try: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + del self._packet_queue + except (socket.error, AttributeError): + pass + + 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 # pylint: disable=W0104 + else: + self._packet_number = packet_number + packets = _prepare_packets(buf, self._packet_number) + for packet in packets: + try: + if PY2: + self.sock.sendall(buffer(packet)) # pylint: disable=E0602 + else: + self.sock.sendall(packet) + except IOError as err: + raise errors.OperationalError( + errno=2055, values=(self.get_address(), _strioerror(err))) + except AttributeError: + raise errors.OperationalError(errno=2006) + + 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 # pylint: disable=W0104 + 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) + if PY2: + tmpbuf = bytearray() + for pkt in pkts: + tmpbuf += pkt + tmpbuf = buffer(tmpbuf) # pylint: disable=E0602 + else: + tmpbuf = b''.join(pkts) + del pkts + seqid = 0 + zbuf = zlib.compress(tmpbuf[:16384]) + header = (struct.pack(' maxpktlen: + zbuf = zlib.compress(tmpbuf[:maxpktlen]) + header = (struct.pack(' 50: + zbuf = zlib.compress(pkt) + zpkts.append(struct.pack(' 0: + raise errors.InterfaceError(errno=2013) + packet_view = packet_view[read:] + rest -= read + return packet + except IOError as err: + raise errors.OperationalError( + errno=2055, values=(self.get_address(), _strioerror(err))) + + def recv_py26_plain(self): + """Receive packets from the MySQL server""" + try: + # Read the header of the MySQL packet, 4 bytes + header = bytearray(b'') + header_len = 0 + while header_len < 4: + chunk = self.sock.recv(4 - header_len) + if not chunk: + raise errors.InterfaceError(errno=2013) + header += chunk + header_len = len(header) + + # Save the packet number and payload length + self._packet_number = header[3] + payload_len = struct_unpack(" 0: + chunk = self.sock.recv(rest) + if not chunk: + raise errors.InterfaceError(errno=2013) + payload += chunk + rest = payload_len - len(payload) + return header + payload + except IOError as err: + raise errors.OperationalError( + errno=2055, values=(self.get_address(), _strioerror(err))) + + if sys.version_info[0:2] == (2, 6): + recv = recv_py26_plain + recv_plain = recv_py26_plain + else: + 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. +# +# 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 + +"""Implements parser to parse MySQL option files. +""" + +import codecs +import io +import os +import re + +from .catch23 import PY2 +from .connection import DEFAULT_CONFIGURATION +from .pooling import CNX_POOL_ARGS +from .fabric import CNX_FABRIC_ARGS + +# pylint: disable=F0401 +if PY2: + from ConfigParser import SafeConfigParser, MissingSectionHeaderError +else: + from configparser import (ConfigParser as SafeConfigParser, + MissingSectionHeaderError) +# pylint: enable=F0401 + +DEFAULT_EXTENSIONS = { + 'nt': ('ini', 'cnf'), + 'posix': ('cnf',) +} + + +def read_option_files(**config): + """ + Read option files for connection parameters. + + Checks if connection arguments contain option file arguments, and then + reads option files accordingly. + """ + if 'option_files' in config: + try: + if isinstance(config['option_groups'], str): + config['option_groups'] = [config['option_groups']] + groups = config['option_groups'] + del config['option_groups'] + except KeyError: + groups = ['client', 'connector_python'] + + if isinstance(config['option_files'], str): + config['option_files'] = [config['option_files']] + option_parser = MySQLOptionsParser(list(config['option_files']), + keep_dashes=False) + del config['option_files'] + + config_from_file = option_parser.get_groups_as_dict_with_priority( + *groups) + config_options = {} + fabric_options = {} + for group in groups: + try: + for option, value in config_from_file[group].items(): + try: + if option == 'socket': + option = 'unix_socket' + + if option in CNX_FABRIC_ARGS: + if (option not in fabric_options or + fabric_options[option][1] <= value[1]): + fabric_options[option] = value + continue + + if (option not in CNX_POOL_ARGS and + option not in ['fabric', 'failover']): + # pylint: disable=W0104 + DEFAULT_CONFIGURATION[option] + # pylint: enable=W0104 + + if (option not in config_options or + config_options[option][1] <= value[1]): + config_options[option] = value + except KeyError: + if group is 'connector_python': + raise AttributeError("Unsupported argument " + "'{0}'".format(option)) + except KeyError: + continue + + not_evaluate = ('password', 'passwd') + for option, value in config_options.items(): + if option not in config: + try: + if option in not_evaluate: + config[option] = value[0] + else: + config[option] = eval(value[0]) # pylint: disable=W0123 + except (NameError, SyntaxError): + config[option] = value[0] + + if fabric_options: + config['fabric'] = {} + for option, value in fabric_options.items(): + try: + # pylint: disable=W0123 + config['fabric'][option.split('_', 1)[1]] = eval(value[0]) + # pylint: enable=W0123 + except (NameError, SyntaxError): + config['fabric'][option.split('_', 1)[1]] = value[0] + return config + + +class MySQLOptionsParser(SafeConfigParser): # pylint: disable=R0901 + """This class implements methods to parse MySQL option files""" + + def __init__(self, files=None, keep_dashes=True): # pylint: disable=W0231 + """Initialize + + If defaults is True, default option files are read first + + Raises ValueError if defaults is set to True but defaults files + cannot be found. + """ + + # Regular expression to allow options with no value(For Python v2.6) + self.OPTCRE = re.compile( # pylint: disable=C0103 + r'(?P